diff --git a/src/backend/core/AbstractColumn.cpp b/src/backend/core/AbstractColumn.cpp index 554d28e89..328931261 100644 --- a/src/backend/core/AbstractColumn.cpp +++ b/src/backend/core/AbstractColumn.cpp @@ -1,769 +1,769 @@ /*************************************************************************** File : AbstractColumn.cpp Project : LabPlot Description : Interface definition for data with column logic -------------------------------------------------------------------- Copyright : (C) 2007,2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2017-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/core/AbstractColumn.h" #include "backend/core/AbstractColumnPrivate.h" #include "backend/core/abstractcolumncommands.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/SignallingUndoCommand.h" #include #include #include #include /** * \class AbstractColumn * \brief Interface definition for data with column logic * * This is an abstract base class for column-based data, * i.e. mathematically a vector or technically a 1D array or list. * It only defines the interface but has no data members itself. * * Classes derived from this are typically table columns or outputs * of filters which can be chained between table columns and plots. * From the point of view of the plot functions there will be no difference * between a table column and a filter output since both use this interface. * * Classes derived from this will either store a * vector with entries of one certain data type, e.g. double, QString, * QDateTime, or generate such values on demand. To determine the data * type of a class derived from this, use the columnMode() function. * AbstractColumn defines all access functions for all supported data * types but only those corresponding to the return value of columnMode() * will return a meaningful value. Calling functions not belonging to * the data type of the column is safe, but will do nothing (writing * function) or return some default value (reading functions). * * This class also defines all signals which indicate a data change. * Any class whose output values are subject to change over time must emit * the according signals. These signals notify any object working with the * column before and after a change of the column. * In some cases it will be necessary for a class using * the column to connect aboutToBeDestroyed(), to react * to a column's deletion, e.g. a filter's reaction to a * table deletion. * * All writing functions have a "do nothing" standard implementation to * make deriving a read-only class very easy without bothering about the * writing interface. */ /** * \brief Ctor * * \param name the column name (= aspect name) */ AbstractColumn::AbstractColumn(const QString &name, AspectType type) : AbstractAspect(name, type), d( new AbstractColumnPrivate(this) ) { } AbstractColumn::~AbstractColumn() { emit aboutToBeDestroyed(this); delete d; } QStringList AbstractColumn::dateFormats() { static const QStringList dates{"yyyy-MM-dd", "yyyy/MM/dd", "dd/MM/yyyy", "dd/MM/yy", "dd.MM.yyyy", "dd.MM.yy", "MM/yyyy", "dd.MM.", "yyyyMMdd"}; return dates; } QStringList AbstractColumn::timeFormats() { static const QStringList times{"hh", "hh ap", "hh:mm", "hh:mm ap", "hh:mm:ss", "hh:mm:ss.zzz", "hh:mm:ss:zzz", "mm:ss.zzz", "hhmmss"}; return times; } QStringList AbstractColumn::dateTimeFormats() { // any combination of date and times QStringList dateTimes = dateFormats(); for (const auto& t : timeFormats()) dateTimes << t; for (const auto& d : dateFormats()) for (const auto& t : timeFormats()) dateTimes << d + ' ' + t; return dateTimes; } /** * \brief Convenience method for mode-dependent icon */ QIcon AbstractColumn::iconForMode(ColumnMode mode) { switch (mode) { - case AbstractColumn::Numeric: - case AbstractColumn::Integer: - case AbstractColumn::BigInt: + case ColumnMode::Numeric: + case ColumnMode::Integer: + case ColumnMode::BigInt: break; - case AbstractColumn::Text: + case ColumnMode::Text: return QIcon::fromTheme("draw-text"); - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case ColumnMode::DateTime: + case ColumnMode::Month: + case ColumnMode::Day: return QIcon::fromTheme("chronometer"); } return QIcon::fromTheme("x-shape-text"); } /** * \fn bool AbstractColumn::isReadOnly() const * \brief Return whether the object is read-only */ /** * \fn AbstractColumn::ColumnMode AbstractColumn::columnMode() const * \brief Return the column mode * * This function is most used by tables but can also be used * by plots. The column mode specifies how to interpret * the values in the column additional to the data type. */ /** * \brief Set the column mode * * This sets the column mode and, if * necessary, converts it to another datatype. */ void AbstractColumn::setColumnMode(AbstractColumn::ColumnMode) {} /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool AbstractColumn::copy(const AbstractColumn *other) { Q_UNUSED(other) return false; } /** * \brief Copies part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param destination_start first row to copy in * \param num_rows the number of rows to copy */ bool AbstractColumn::copy(const AbstractColumn *source, int source_start, int destination_start, int num_rows) { Q_UNUSED(source) Q_UNUSED(source_start) Q_UNUSED(destination_start) Q_UNUSED(num_rows) return false; } /** * \fn int AbstractColumn::rowCount() const * \brief Return the data vector size */ /** * \fn int AbstractColumn::availableRowCount() const * \brief Return the number of available data rows */ /** * \brief Insert some empty (or initialized with invalid values) rows */ void AbstractColumn::insertRows(int before, int count) { beginMacro( i18np("%1: insert 1 row", "%1: insert %2 rows", name(), count) ); exec(new SignallingUndoCommand("pre-signal", this, "rowsAboutToBeInserted", "rowsRemoved", Q_ARG(const AbstractColumn*,this), Q_ARG(int,before), Q_ARG(int,count))); handleRowInsertion(before, count); exec(new SignallingUndoCommand("post-signal", this, "rowsInserted", "rowsAboutToBeRemoved", Q_ARG(const AbstractColumn*,this), Q_ARG(int,before), Q_ARG(int,count))); endMacro(); } void AbstractColumn::handleRowInsertion(int before, int count) { exec(new AbstractColumnInsertRowsCmd(this, before, count)); } /** * \brief Remove 'count' rows starting from row 'first' */ void AbstractColumn::removeRows(int first, int count) { beginMacro( i18np("%1: remove 1 row", "%1: remove %2 rows", name(), count) ); exec(new SignallingUndoCommand("change signal", this, "rowsAboutToBeRemoved", "rowsInserted", Q_ARG(const AbstractColumn*,this), Q_ARG(int,first), Q_ARG(int,count))); handleRowRemoval(first, count); exec(new SignallingUndoCommand("change signal", this, "rowsRemoved", "rowsAboutToBeInserted", Q_ARG(const AbstractColumn*,this), Q_ARG(int,first), Q_ARG(int,count))); endMacro(); } void AbstractColumn::handleRowRemoval(int first, int count) { exec(new AbstractColumnRemoveRowsCmd(this, first, count)); } /** * \fn AbstractColumn::PlotDesignation AbstractColumn::plotDesignation() const * \brief Return the column plot designation */ /** * \brief Set the column plot designation */ void AbstractColumn::setPlotDesignation(AbstractColumn::PlotDesignation pd) { Q_UNUSED(pd) } bool AbstractColumn::isNumeric() const { - const AbstractColumn::ColumnMode mode = columnMode(); - return (mode == AbstractColumn::Numeric || mode == AbstractColumn::Integer || mode == AbstractColumn::BigInt); + const auto mode = columnMode(); + return (mode == ColumnMode::Numeric || mode == ColumnMode::Integer || mode == ColumnMode::BigInt); } bool AbstractColumn::isPlottable() const { - const AbstractColumn::ColumnMode mode = columnMode(); - return (mode == AbstractColumn::Numeric || mode == AbstractColumn::Integer || mode == AbstractColumn::BigInt || mode == AbstractColumn::DateTime); + const auto mode = columnMode(); + return (mode == ColumnMode::Numeric || mode == ColumnMode::Integer || mode == ColumnMode::BigInt || mode == ColumnMode::DateTime); } /** * \brief Clear the whole column */ void AbstractColumn::clear() {} /** * \brief Convenience method for mode-independent testing of validity */ bool AbstractColumn::isValid(int row) const { switch (columnMode()) { - case AbstractColumn::Numeric: + case ColumnMode::Numeric: return !(std::isnan(valueAt(row)) || std::isinf(valueAt(row))); - case AbstractColumn::Integer: // there is no invalid integer - case AbstractColumn::BigInt: + case ColumnMode::Integer: // there is no invalid integer + case ColumnMode::BigInt: return true; - case AbstractColumn::Text: + case ColumnMode::Text: return !textAt(row).isNull(); - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case ColumnMode::DateTime: + case ColumnMode::Month: + case ColumnMode::Day: return dateTimeAt(row).isValid(); } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name IntervalAttribute related functions //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Return whether a certain row is masked */ bool AbstractColumn::isMasked(int row) const { return d->m_masking.isSet(row); } /** * \brief Return whether a certain interval of rows is fully masked */ bool AbstractColumn::isMasked(const Interval& i) const { return d->m_masking.isSet(i); } /** * \brief Return all intervals of masked rows */ QVector< Interval > AbstractColumn::maskedIntervals() const { return d->m_masking.intervals(); } /** * \brief Clear all masking information */ void AbstractColumn::clearMasks() { exec(new AbstractColumnClearMasksCmd(d), "maskingAboutToChange", "maskingChanged", Q_ARG(const AbstractColumn*,this)); } /** * \brief Set an interval masked * * \param i the interval * \param mask true: mask, false: unmask */ void AbstractColumn::setMasked(const Interval& i, bool mask) { exec(new AbstractColumnSetMaskedCmd(d, i, mask), "maskingAboutToChange", "maskingChanged", Q_ARG(const AbstractColumn*,this)); } /** * \brief Overloaded function for convenience */ void AbstractColumn::setMasked(int row, bool mask) { setMasked(Interval(row,row), mask); } //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name Formula related functions //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Return the formula associated with row 'row' */ QString AbstractColumn::formula(int row) const { Q_UNUSED(row); return QString(); } /** * \brief Return the intervals that have associated formulas * * This can be used to make a list of formulas with their intervals. * Here is some example code: * * \code * QStringList list; * QVector< Interval > intervals = my_column.formulaIntervals(); * foreach(Interval interval, intervals) * list << QString(interval.toString() + ": " + my_column.formula(interval.start())); * \endcode */ QVector< Interval > AbstractColumn::formulaIntervals() const { return QVector< Interval >(); } /** * \brief Set a formula string for an interval of rows */ void AbstractColumn::setFormula(const Interval& i, const QString& formula) { Q_UNUSED(i) Q_UNUSED(formula) } /** * \brief Overloaded function for convenience */ void AbstractColumn::setFormula(int row, const QString& formula) { Q_UNUSED(row) Q_UNUSED(formula) } /** * \brief Clear all formulas */ void AbstractColumn::clearFormulas() {}; //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name type specific functions //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Return the content of row 'row'. * * Use this only when columnMode() is Text */ QString AbstractColumn::textAt(int row) const { Q_UNUSED(row); return QString(); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Text */ void AbstractColumn::setTextAt(int row, const QString& new_value) { Q_UNUSED(row) Q_UNUSED(new_value) } /** * \brief Replace a range of values * * Use this only when columnMode() is Text */ void AbstractColumn::replaceTexts(int first, const QVector& new_values) { Q_UNUSED(first) Q_UNUSED(new_values) }; /** * \brief Return the date part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDate AbstractColumn::dateAt(int row) const { Q_UNUSED(row); return QDate{}; } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void AbstractColumn::setDateAt(int row, QDate new_value) { Q_UNUSED(row) Q_UNUSED(new_value) }; /** * \brief Return the time part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QTime AbstractColumn::timeAt(int row) const { Q_UNUSED(row); return QTime{}; } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void AbstractColumn::setTimeAt(int row, QTime new_value) { Q_UNUSED(row) Q_UNUSED(new_value) } /** * \brief Return the QDateTime in row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDateTime AbstractColumn::dateTimeAt(int row) const { Q_UNUSED(row); return QDateTime(); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void AbstractColumn::setDateTimeAt(int row, const QDateTime& new_value) { Q_UNUSED(row) Q_UNUSED(new_value) }; /** * \brief Replace a range of values * * Use this only when columnMode() is DateTime, Month or Day */ void AbstractColumn::replaceDateTimes(int first, const QVector& new_values) { Q_UNUSED(first) Q_UNUSED(new_values) }; /** * \brief Return the double value in row 'row' * * Use this only when columnMode() is Numeric */ double AbstractColumn::valueAt(int row) const { Q_UNUSED(row); return NAN; } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Numeric */ void AbstractColumn::setValueAt(int row, const double new_value) { Q_UNUSED(row) Q_UNUSED(new_value) }; /** * \brief Replace a range of values * * Use this only when columnMode() is Numeric */ void AbstractColumn::replaceValues(int first, const QVector& new_values) { Q_UNUSED(first) Q_UNUSED(new_values) } /** * \brief Return the integer value in row 'row' * * Use this only when columnMode() is Integer */ int AbstractColumn::integerAt(int row) const { Q_UNUSED(row); return 42; } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Integer */ void AbstractColumn::setIntegerAt(int row, const int new_value) { Q_UNUSED(row) Q_UNUSED(new_value) }; /** * \brief Replace a range of values * * Use this only when columnMode() is Integer */ void AbstractColumn::replaceInteger(int first, const QVector& new_values) { Q_UNUSED(first) Q_UNUSED(new_values) } /** * \brief Return the bigint value in row 'row' * * Use this only when columnMode() is BigInt */ qint64 AbstractColumn::bigIntAt(int row) const { Q_UNUSED(row); return 42; } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is BigInt */ void AbstractColumn::setBigIntAt(int row, const qint64 new_value) { Q_UNUSED(row) Q_UNUSED(new_value) }; /** * \brief Replace a range of values * * Use this only when columnMode() is BigInt */ void AbstractColumn::replaceBigInt(int first, const QVector& new_values) { Q_UNUSED(first) Q_UNUSED(new_values) } /** * Returns the properties hold by this column (no, monotonic increasing, monotonic decreasing,...) * Is used in XYCurve to improve the search velocity for the y value for a specific x value */ AbstractColumn::Properties AbstractColumn::properties() const { return AbstractColumn::Properties::No; } /**********************************************************************/ double AbstractColumn::minimum(int count) const { Q_UNUSED(count); return -INFINITY; } double AbstractColumn::minimum(int startIndex, int endIndex) const { Q_UNUSED(startIndex); Q_UNUSED(endIndex); return -INFINITY; } double AbstractColumn::maximum(int count) const { Q_UNUSED(count); return INFINITY; } double AbstractColumn::maximum(int startIndex, int endIndex) const { Q_UNUSED(startIndex); Q_UNUSED(endIndex); return INFINITY; } bool AbstractColumn::indicesMinMax(double v1, double v2, int& start, int& end) const { Q_UNUSED(v1) Q_UNUSED(v2) Q_UNUSED(start) Q_UNUSED(end) return false; } int AbstractColumn::indexForValue(double x) const { Q_UNUSED(x) return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \fn void AbstractColumn::plotDesignationAboutToChange(const AbstractColumn *source) * \brief Column plot designation will be changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::plotDesignationChanged(const AbstractColumn *source) * \brief Column plot designation changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::modeAboutToChange(const AbstractColumn *source) * \brief Column mode (possibly also the data type) will be changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::modeChanged(const AbstractColumn *source) * \brief Column mode (possibly also the data type) changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::dataAboutToChange(const AbstractColumn *source) * \brief Data of the column will be changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::dataChanged(const AbstractColumn *source) * \brief Data of the column has changed * * Important: When data has changed also the number * of rows in the column may have changed without * any other signal emission. * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::rowsAboutToBeInserted(const AbstractColumn *source, int before, int count) * \brief Rows will be inserted * * \param source the column that emitted the signal * \param before the row to insert before * \param count the number of rows to be inserted */ /** * \fn void AbstractColumn::rowsInserted(const AbstractColumn *source, int before, int count) * \brief Rows have been inserted * * \param source the column that emitted the signal * \param before the row to insert before * \param count the number of rows to be inserted */ /** * \fn void AbstractColumn::rowsAboutToBeRemoved(const AbstractColumn *source, int first, int count) * \brief Rows will be deleted * * \param source the column that emitted the signal * \param first the first row to be deleted * \param count the number of rows to be deleted */ /** * \fn void AbstractColumn::rowsRemoved(const AbstractColumn *source, int first, int count) * \brief Rows have been deleted * * \param source the column that emitted the signal * \param first the first row that was deleted * \param count the number of deleted rows */ /** * \fn void AbstractColumn::maskingAboutToChange(const AbstractColumn *source) * \brief Rows are about to be masked or unmasked */ /** * \fn void AbstractColumn::maskingChanged(const AbstractColumn *source) * \brief Rows have been masked or unmasked */ /** * \fn void AbstractColumn::aboutToBeDestroyed(const AbstractColumn *source) * \brief Emitted shortl before this column is deleted * * \param source the object emitting this signal * * This is needed by AbstractFilter. */ /** * \brief Read XML mask element */ bool AbstractColumn::XmlReadMask(XmlStreamReader *reader) { Q_ASSERT(reader->isStartElement() && reader->name() == "mask"); bool ok1, ok2; int start, end; start = reader->readAttributeInt("start_row", &ok1); end = reader->readAttributeInt("end_row", &ok2); if (!ok1 || !ok2) { reader->raiseError(i18n("invalid or missing start or end row")); return false; } setMasked(Interval(start,end)); if (!reader->skipToEndElement()) return false; return true; } /** * \brief Write XML mask element */ void AbstractColumn::XmlWriteMask(QXmlStreamWriter *writer) const { for (const auto& interval : maskedIntervals()) { writer->writeStartElement("mask"); writer->writeAttribute("start_row", QString::number(interval.start())); writer->writeAttribute("end_row", QString::number(interval.end())); writer->writeEndElement(); } } diff --git a/src/backend/core/AbstractColumn.h b/src/backend/core/AbstractColumn.h index 5bd79cda5..7d188cacd 100644 --- a/src/backend/core/AbstractColumn.h +++ b/src/backend/core/AbstractColumn.h @@ -1,253 +1,253 @@ /*************************************************************************** File : AbstractColumn.h Project : LabPlot Description : Interface definition for data with column logic -------------------------------------------------------------------- Copyright : (C) 2007,2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2013 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-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 * * * ***************************************************************************/ #ifndef ABSTRACTCOLUMN_H #define ABSTRACTCOLUMN_H #include "backend/core/AbstractAspect.h" #include // NAN class AbstractColumnPrivate; class AbstractSimpleFilter; class QStringList; class QString; class QDateTime; class QDate; class QTime; template class QVector; template class Interval; class AbstractColumn : public AbstractAspect { Q_OBJECT Q_ENUMS(PlotDesignation) Q_ENUMS(ColumnMode) public: enum class PlotDesignation {NoDesignation, X, Y, Z, XError, XErrorPlus, XErrorMinus, YError, YErrorMinus, YErrorPlus}; - enum ColumnMode { + enum class ColumnMode { // BASIC FORMATS Numeric = 0, // double Text = 1, // QString // Time = 2 and Date = 3 are skipped to avoid problems with old obsolete values Month = 4, // month of year: numeric or "Jan", etc. Day = 5, // day of week: numeric or "Mon", etc. DateTime = 6, // any date-time format // Bool = 7, // bool // FLOATING POINT // 10 = Half precision // Float = 11, // float // 12 = Long double // 13 = Quad precision // 14 = decimal 32 // 15 = decimal 64 // 16 = decimal 128 // COMPLEX // 17 = complex // 18 = complex // 19 = complex // INTEGER // Int8 = 20, // qint8 (char) // UInt8 = 21, // quint8 (unsigned char) // Int16 = 22, // qint16 (short) // UInt16 = 23, // quint16 (unsigned short) Integer = 24, // qint32 (int) // UInt32 = 25, // quint32 (unsigned int) BigInt = 26, // qint64 (long) // UInt64 = 27, // quint64 (unsigned long) // MISC // QBrush = 30 // QColor // QFont // QPoint // QQuaternion // QVector2D, QVector3D, QVector4D // QMatrix // etc. }; enum class Properties { No = 0x00, Constant = 0x01, MonotonicIncreasing = 0x02, // prev_value >= value for all values in column MonotonicDecreasing = 0x04 // prev_value <= value for all values in column // add new values with next bit set (0x08) }; struct ColumnStatistics { ColumnStatistics() { size = 0; minimum = NAN; maximum = NAN; arithmeticMean = NAN; geometricMean = NAN; harmonicMean = NAN; contraharmonicMean = NAN; mode = NAN; firstQuartile = NAN; median = NAN; thirdQuartile = NAN; iqr = NAN; trimean = NAN; variance = NAN; standardDeviation = NAN; meanDeviation = NAN; meanDeviationAroundMedian = NAN; medianDeviation = NAN; skewness = NAN; kurtosis = NAN; entropy = NAN; } int size; double minimum; double maximum; double arithmeticMean; double geometricMean; double harmonicMean; double contraharmonicMean; double mode; double firstQuartile; double median; double thirdQuartile; double iqr; double trimean; double variance; double standardDeviation; double meanDeviation; // mean absolute deviation around mean double meanDeviationAroundMedian; // mean absolute deviation around median double medianDeviation; // median absolute deviation double skewness; double kurtosis; double entropy; }; AbstractColumn(const QString& name, AspectType type); ~AbstractColumn() override; static QStringList dateFormats(); // supported date formats static QStringList timeFormats(); // supported time formats static QStringList dateTimeFormats(); // supported datetime formats static QIcon iconForMode(ColumnMode mode); virtual bool isReadOnly() const { return true; }; virtual ColumnMode columnMode() const = 0; virtual void setColumnMode(AbstractColumn::ColumnMode); virtual PlotDesignation plotDesignation() const = 0; virtual QString plotDesignationString() const = 0; virtual void setPlotDesignation(AbstractColumn::PlotDesignation); bool isNumeric() const; bool isPlottable() const; virtual bool copy(const AbstractColumn *source); virtual bool copy(const AbstractColumn *source, int source_start, int dest_start, int num_rows); virtual int rowCount() const = 0; virtual int availableRowCount() const = 0; void insertRows(int before, int count); void removeRows(int first, int count); virtual void clear(); virtual double maximum(int count = 0) const; virtual double maximum(int startIndex, int endIndex) const; virtual double minimum(int count = 0) const; virtual double minimum(int startIndex, int endIndex) const; virtual bool indicesMinMax(double v1, double v2, int& start, int& end) const; virtual int indexForValue(double x) const; bool isValid(int row) const; bool isMasked(int row) const; bool isMasked(const Interval& i) const; QVector< Interval > maskedIntervals() const; void clearMasks(); void setMasked(const Interval& i, bool mask = true); void setMasked(int row, bool mask = true); virtual QString formula(int row) const; virtual QVector< Interval > formulaIntervals() const; virtual void setFormula(const Interval& i, const QString& formula); virtual void setFormula(int row, const QString& formula); virtual void clearFormulas(); virtual QString textAt(int row) const; virtual void setTextAt(int row, const QString& new_value); virtual void replaceTexts(int first, const QVector& new_values); virtual QDate dateAt(int row) const; virtual void setDateAt(int row, QDate new_value); virtual QTime timeAt(int row) const; virtual void setTimeAt(int row, QTime new_value); virtual QDateTime dateTimeAt(int row) const; virtual void setDateTimeAt(int row, const QDateTime& new_value); virtual void replaceDateTimes(int first, const QVector& new_values); virtual double valueAt(int row) const; virtual void setValueAt(int row, double new_value); virtual void replaceValues(int first, const QVector& new_values); virtual int integerAt(int row) const; virtual void setIntegerAt(int row, int new_value); virtual void replaceInteger(int first, const QVector& new_values); virtual qint64 bigIntAt(int row) const; virtual void setBigIntAt(int row, qint64 new_value); virtual void replaceBigInt(int first, const QVector& new_values); virtual Properties properties() const; signals: void plotDesignationAboutToChange(const AbstractColumn* source); void plotDesignationChanged(const AbstractColumn* source); void modeAboutToChange(const AbstractColumn* source); void modeChanged(const AbstractColumn* source); void dataAboutToChange(const AbstractColumn* source); void dataChanged(const AbstractColumn* source); void formatChanged(const AbstractColumn* source); void rowsAboutToBeInserted(const AbstractColumn* source, int before, int count); void rowsInserted(const AbstractColumn* source, int before, int count); void rowsAboutToBeRemoved(const AbstractColumn* source, int first, int count); void rowsRemoved(const AbstractColumn* source, int first, int count); void maskingAboutToChange(const AbstractColumn* source); void maskingChanged(const AbstractColumn* source); void aboutToBeDestroyed(const AbstractColumn* source); void reset(const AbstractColumn* source); // this signal is emitted when the column is reused for another purpose. The curves must know that and disconnect all connections protected: bool XmlReadMask(XmlStreamReader*); void XmlWriteMask(QXmlStreamWriter*) const; virtual void handleRowInsertion(int before, int count); virtual void handleRowRemoval(int first, int count); private: AbstractColumnPrivate* d; friend class AbstractColumnRemoveRowsCmd; friend class AbstractColumnInsertRowsCmd; friend class AbstractColumnClearMasksCmd; friend class AbstractColumnSetMaskedCmd; }; #endif diff --git a/src/backend/core/AbstractSimpleFilter.cpp b/src/backend/core/AbstractSimpleFilter.cpp index 898ed7a41..36b9f388b 100644 --- a/src/backend/core/AbstractSimpleFilter.cpp +++ b/src/backend/core/AbstractSimpleFilter.cpp @@ -1,437 +1,437 @@ /*************************************************************************** File : AbstractSimpleFilter.cpp Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007,2008 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs*gmx.net Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Simplified filter interface for filters with only one output port. ***************************************************************************/ /*************************************************************************** * * * 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 "AbstractSimpleFilter.h" #include "backend/lib/XmlStreamReader.h" #include #include #include #include /** * \class AbstractSimpleFilter * \brief Simplified filter interface for filters with only one output port. * * This class is only meant to simplify implementation of a restricted subtype of filter. * It should not be instantiated directly. You should always either derive from * AbstractFilter or (if necessary) provide an actual (non-abstract) implementation. * * The trick here is that, in a sense, the filter is its own output port. This means you * can implement a complete filter in only one class and don't have to coordinate data * transfer between a filter class and a data source class. * Additionally, AbstractSimpleFilter offers some useful convenience methods which make writing * filters as painless as possible. * * For the data type of the output, all types supported by AbstractColumn (currently double, QString and * QDateTime) are supported. * * \section tutorial1 Tutorial, Step 1 * The simplest filter you can write assumes there's also only one input port and rows on the * input correspond 1:1 to rows in the output. All you need to specify is what data type you * want to have (in this example double) on the input port and how to compute the output values: * * \code * 01 #include "AbstractSimpleFilter.h" * 02 class TutorialFilter1 : public AbstractSimpleFilter * 03 { * 04 protected: * 05 virtual bool inputAcceptable(int, AbstractColumn *source) { * 06 return (source->columnMode() == AbstractColumn::Numeric); * 07 } * 08 public: * 09 virtual AbstractColumn::ColumnMode columnMode() const { return AbstractColumn::Numeric; } * 10 * 11 virtual double valueAt(int row) const { * 12 if (!m_inputs.value(0)) return 0.0; * 13 double input_value = m_inputs.value(0)->valueAt(row); * 14 return input_value * input_value; * 15 } * 16 }; * \endcode * * This filter reads an input value (line 13) and returns its square (line 14). * Reimplementing inputAcceptable() makes sure that the data source really is of type * double (lines 5 to 7). Otherwise, the source will be rejected by AbstractFilter::input(). * The output type is reported by reimplementing columnMode() (line 09). * Before you actually use m_inputs.value(0), make sure that the input port has * been connected to a data source (line 12). * Otherwise line 13 would result in a crash. That's it, we've already written a * fully-functional filter! * * Equivalently, you can write 1:1-filters for QString or QDateTime inputs by checking for * AbstractColumn::TypeQString or AbstractColumn::TypeQDateTime in line 6. You would then use * AbstractColumn::textAt(row) or AbstractColumn::dateTimeAt(row) in line 13 to access the input data. * For QString output, you need to implement AbstractColumn::textAt(row). * For QDateTime output, you have to implement three methods: * \code * virtual QDateTime dateTimeAt(int row) const; * virtual QDate dateAt(int row) const; * virtual QTime timeAt(int row) const; * \endcode * * \section tutorial2 Tutorial, Step 2 * Now for something slightly more interesting: a filter that uses only every second row of its * input. We no longer have a 1:1 correspondence between input and output rows, so we'll have * to do a bit more work in order to have everything come out as expected. * We'll use double-typed input and output again: * \code * 01 #include "AbstractSimpleFilter.h" * 02 class TutorialFilter2 : public AbstractSimpleFilter * 03 { * 04 protected: * 05 virtual bool inputAcceptable(int, AbstractColumn *source) { * 06 return (source->columnMode() == AbstractColumn::Numeric); * 07 } * 08 public: * 09 virtual AbstractColumn::ColumnMode columnMode() const { return AbstractColumn::Numeric; } * \endcode * Even rows (including row 0) get dropped, odd rows are renumbered: * \code * 10 public: * 11 virtual double valueAt(int row) const { * 12 if (!m_inputs.value(0)) return 0.0; * 13 return m_inputs.value(0)->valueAt(2*row + 1); * 14 } * \endcode */ // TODO: should simple filters have a name argument? /** * \brief Ctor */ AbstractSimpleFilter::AbstractSimpleFilter() : AbstractFilter("SimpleFilter"), m_output_column(new SimpleFilterColumn(this)) { addChildFast(m_output_column); } /** * \brief Default to one input port. */ int AbstractSimpleFilter::inputCount() const { return 1; } /** * \brief We manage only one output port (don't override unless you really know what you are doing). */ int AbstractSimpleFilter::outputCount() const { return 1; } /** * \brief Copy plot designation of input port 0. */ AbstractColumn::PlotDesignation AbstractSimpleFilter::plotDesignation() const { return m_inputs.value(0) ? m_inputs.at(0)->plotDesignation() : AbstractColumn::PlotDesignation::NoDesignation; } /** * \brief Copy plot designation string of input port 0. */ QString AbstractSimpleFilter::plotDesignationString() const { return m_inputs.value(0) ? m_inputs.at(0)->plotDesignationString() : QString(""); } /** * \brief Return the column mode * * This function is most used by tables but can also be used * by plots. The column mode specifies how to interpret * the values in the column additional to the data type. */ AbstractColumn::ColumnMode AbstractSimpleFilter::columnMode() const { // calling this function while m_input is empty is a sign of very bad code // nevertheless it will return some rather meaningless value to // avoid crashes - return m_inputs.value(0) ? m_inputs.at(0)->columnMode() : AbstractColumn::Text; + return m_inputs.value(0) ? m_inputs.at(0)->columnMode() : AbstractColumn::ColumnMode::Text; } /** * \brief Return the content of row 'row'. * * Use this only when columnMode() is Text */ QString AbstractSimpleFilter::textAt(int row) const { return m_inputs.value(0) ? m_inputs.at(0)->textAt(row) : QString(); } /** * \brief Return the date part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDate AbstractSimpleFilter::dateAt(int row) const { return m_inputs.value(0) ? m_inputs.at(0)->dateAt(row) : QDate(); } /** * \brief Return the time part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QTime AbstractSimpleFilter::timeAt(int row) const { return m_inputs.value(0) ? m_inputs.at(0)->timeAt(row) : QTime(); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDateTime AbstractSimpleFilter::dateTimeAt(int row) const { return m_inputs.value(0) ? m_inputs.at(0)->dateTimeAt(row) : QDateTime(); } /** * \brief Return the double value in row 'row' * * Use this only when columnMode() is Numeric */ double AbstractSimpleFilter::valueAt(int row) const { return m_inputs.value(0) ? m_inputs.at(0)->valueAt(row) : 0.0; } /** * \brief Return the integer value in row 'row' * * Use this only when columnMode() is Integer */ int AbstractSimpleFilter::integerAt(int row) const { return m_inputs.value(0) ? m_inputs.at(0)->integerAt(row) : 0; } /** * \brief Return the bigint value in row 'row' * * Use this only when columnMode() is BigInt */ qint64 AbstractSimpleFilter::bigIntAt(int row) const { return m_inputs.value(0) ? m_inputs.at(0)->bigIntAt(row) : 0; } /** * \brief Number of output rows == number of input rows * * ... unless overridden in a subclass. */ int AbstractSimpleFilter::rowCount() const { return m_inputs.value(0) ? m_inputs.at(0)->rowCount() : 0; } /** * \brief Number of output rows == number of input rows * * ... unless overridden in a subclass. */ int AbstractSimpleFilter::availableRowCount() const { return m_inputs.value(0) ? m_inputs.at(0)->availableRowCount() : 0; } /** * \brief Rows that will change when the given input interval changes. * * This implementation assumes a 1:1 correspondence between input and output rows, but can be * overridden in subclasses. */ QList< Interval > AbstractSimpleFilter::dependentRows(const Interval& inputRange) const { return QList< Interval >() << inputRange; } //////////////////////////////////////////////////////////////////////////////////////////////////// //!\name signal handlers //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// void AbstractSimpleFilter::inputPlotDesignationAboutToChange(const AbstractColumn*) { emit m_output_column->plotDesignationAboutToChange(m_output_column); } void AbstractSimpleFilter::inputPlotDesignationChanged(const AbstractColumn*) { emit m_output_column->plotDesignationChanged(m_output_column); } void AbstractSimpleFilter::inputModeAboutToChange(const AbstractColumn*) { emit m_output_column->dataAboutToChange(m_output_column); } void AbstractSimpleFilter::inputModeChanged(const AbstractColumn*) { emit m_output_column->dataChanged(m_output_column); } void AbstractSimpleFilter::inputDataAboutToChange(const AbstractColumn*) { emit m_output_column->dataAboutToChange(m_output_column); } void AbstractSimpleFilter::inputDataChanged(const AbstractColumn*) { emit m_output_column->dataChanged(m_output_column); } void AbstractSimpleFilter::inputRowsAboutToBeInserted(const AbstractColumn * source, int before, int count) { Q_UNUSED(source); Q_UNUSED(count); foreach (const Interval& output_range, dependentRows(Interval(before, before))) emit m_output_column->rowsAboutToBeInserted(m_output_column, output_range.start(), output_range.size()); } void AbstractSimpleFilter::inputRowsInserted(const AbstractColumn * source, int before, int count) { Q_UNUSED(source); Q_UNUSED(count); foreach (const Interval& output_range, dependentRows(Interval(before, before))) emit m_output_column->rowsInserted(m_output_column, output_range.start(), output_range.size()); } void AbstractSimpleFilter::inputRowsAboutToBeRemoved(const AbstractColumn * source, int first, int count) { Q_UNUSED(source); foreach (const Interval& output_range, dependentRows(Interval(first, first+count-1))) emit m_output_column->rowsAboutToBeRemoved(m_output_column, output_range.start(), output_range.size()); } void AbstractSimpleFilter::inputRowsRemoved(const AbstractColumn * source, int first, int count) { Q_UNUSED(source); foreach (const Interval& output_range, dependentRows(Interval(first, first+count-1))) emit m_output_column->rowsRemoved(m_output_column, output_range.start(), output_range.size()); } //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Return a pointer to #m_output_column on port 0 (don't override unless you really know what you are doing). */ AbstractColumn *AbstractSimpleFilter::output(int port) { return port == 0 ? static_cast(m_output_column) : nullptr; } const AbstractColumn *AbstractSimpleFilter::output(int port) const { return port == 0 ? static_cast(m_output_column) : nullptr; } //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name serialize/deserialize //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Save to XML */ void AbstractSimpleFilter::save(QXmlStreamWriter * writer) const { writer->writeStartElement("simple_filter"); writeBasicAttributes(writer); writeExtraAttributes(writer); writer->writeAttribute("filter_name", metaObject()->className()); writeCommentElement(writer); writer->writeEndElement(); } /** * \brief Override this in derived classes if they have other attributes than filter_name */ void AbstractSimpleFilter::writeExtraAttributes(QXmlStreamWriter * writer) const { Q_UNUSED(writer) } //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Load from XML */ bool AbstractSimpleFilter::load(XmlStreamReader* reader, bool preview) { Q_UNUSED(preview); //TODO if (!readBasicAttributes(reader)) return false; QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value(reader->namespaceUri().toString(), "filter_name").toString(); if (str != metaObject()->className()) { reader->raiseError(i18n("incompatible filter type")); return false; } // read child elements while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement()) break; if (reader->isStartElement()) { if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } } return !reader->hasError(); } //////////////////////////////////////////////////////////////////////////////////////////////////// //! \class SimpleFilterColumn //////////////////////////////////////////////////////////////////////////////////////////////////// AbstractColumn::ColumnMode SimpleFilterColumn::columnMode() const { return m_owner->columnMode(); } QString SimpleFilterColumn::textAt(int row) const { return m_owner->textAt(row); } QDate SimpleFilterColumn::dateAt(int row) const { return m_owner->dateAt(row); } QTime SimpleFilterColumn::timeAt(int row) const { return m_owner->timeAt(row); } QDateTime SimpleFilterColumn::dateTimeAt(int row) const { return m_owner->dateTimeAt(row); } double SimpleFilterColumn::valueAt(int row) const { return m_owner->valueAt(row); } int SimpleFilterColumn::integerAt(int row) const { return m_owner->integerAt(row); } qint64 SimpleFilterColumn::bigIntAt(int row) const { return m_owner->bigIntAt(row); } diff --git a/src/backend/core/column/Column.cpp b/src/backend/core/column/Column.cpp index 77cebabe4..5a93eb4a0 100644 --- a/src/backend/core/column/Column.cpp +++ b/src/backend/core/column/Column.cpp @@ -1,2237 +1,2237 @@ /*************************************************************************** File : Column.cpp Project : LabPlot Description : Aspect that manages a column -------------------------------------------------------------------- Copyright : (C) 2007-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2013-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 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/core/column/Column.h" #include "backend/core/column/ColumnPrivate.h" #include "backend/core/column/ColumnStringIO.h" #include "backend/core/column/columncommands.h" #include "backend/core/Project.h" #include "backend/lib/XmlStreamReader.h" #include "backend/core/datatypes/String2DateTimeFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/Histogram.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/XYAnalysisCurve.h" extern "C" { #include #include #include } #include #include #include #include #include #include #include #include /** * \class Column * \brief Aspect that manages a column * * This class represents a column, i.e., (mathematically) a 1D vector of * values with a header. It provides a public reading and (undo aware) writing * interface as defined in AbstractColumn. A column * can have one of currently three data types: double, QString, or * QDateTime. The string representation of the values can differ depending * on the mode of the column. * * Column inherits from AbstractAspect and is intended to be a child * of the corresponding Spreadsheet in the aspect hierarchy. Columns don't * have a view as they are intended to be displayed inside a spreadsheet. */ Column::Column(const QString& name, ColumnMode mode) : AbstractColumn(name, AspectType::Column), d(new ColumnPrivate(this, mode)) { init(); } /** * \brief Common part of ctors */ void Column::init() { m_string_io = new ColumnStringIO(this); d->inputFilter()->input(0, m_string_io); d->outputFilter()->input(0, this); d->inputFilter()->setHidden(true); d->outputFilter()->setHidden(true); addChildFast(d->inputFilter()); addChildFast(d->outputFilter()); m_suppressDataChangedSignal = false; m_copyDataAction = new QAction(QIcon::fromTheme("edit-copy"), i18n("Copy Data"), this); connect(m_copyDataAction, &QAction::triggered, this, &Column::copyData); m_usedInActionGroup = new QActionGroup(this); connect(m_usedInActionGroup, &QActionGroup::triggered, this, &Column::navigateTo); connect(this, &AbstractColumn::maskingChanged, this, [=]{d->propertiesAvailable = false;}); } Column::~Column() { delete m_string_io; delete d; } QMenu* Column::createContextMenu() { QMenu* menu = AbstractAspect::createContextMenu(); QAction* firstAction{nullptr}; //insert after "rename" and "delete" actions, if available. //MQTTTopic columns don't have these actions if (menu->actions().size() > 1) firstAction = menu->actions().at(1); //add actions available in SpreadsheetView //TODO: we don't need to add anything from the view for MQTTTopic columns. //at the moment it's ok to check to the null pointer for firstAction here. //later, once we have some actions in the menu also for MQTT topics we'll //need to explicitly to dynamic_cast for MQTTTopic if (firstAction) emit requestProjectContextMenu(menu); //"Used in" menu containing all curves where the column is used QMenu* usedInMenu = new QMenu(i18n("Used in")); usedInMenu->setIcon(QIcon::fromTheme("go-next-view")); //remove previously added actions for (auto* action : m_usedInActionGroup->actions()) m_usedInActionGroup->removeAction(action); Project* project = this->project(); //add curves where the column is currently in use usedInMenu->addSection(i18n("XY-Curves")); auto curves = project->children(AbstractAspect::ChildIndexFlag::Recursive); for (const auto* curve : curves) { bool used = false; const auto* analysisCurve = dynamic_cast(curve); if (analysisCurve) { if (analysisCurve->dataSourceType() == XYAnalysisCurve::DataSourceSpreadsheet && (analysisCurve->xDataColumn() == this || analysisCurve->yDataColumn() == this || analysisCurve->y2DataColumn() == this) ) used = true; } else { if (curve->xColumn() == this || curve->yColumn() == this) used = true; } if (used) { QAction* action = new QAction(curve->icon(), curve->name(), m_usedInActionGroup); action->setData(curve->path()); usedInMenu->addAction(action); } } //add histograms where the column is used usedInMenu->addSection(i18n("Histograms")); auto hists = project->children(AbstractAspect::ChildIndexFlag::Recursive); for (const auto* hist : hists) { bool used = (hist->dataColumn() == this); if (used) { QAction* action = new QAction(hist->icon(), hist->name(), m_usedInActionGroup); action->setData(hist->path()); usedInMenu->addAction(action); } } //add calculated columns where the column is used in formula variables usedInMenu->addSection(i18n("Calculated Columns")); QVector columns = project->children(AbstractAspect::ChildIndexFlag::Recursive); const QString& path = this->path(); for (const auto* column : columns) { auto paths = column->formulaVariableColumnPaths(); if (paths.indexOf(path) != -1) { QAction* action = new QAction(column->icon(), column->name(), m_usedInActionGroup); action->setData(column->path()); usedInMenu->addAction(action); } } if (firstAction) menu->insertSeparator(firstAction); menu->insertMenu(firstAction, usedInMenu); menu->insertSeparator(firstAction); menu->insertAction(firstAction, m_copyDataAction); menu->insertSeparator(firstAction); return menu; } void Column::navigateTo(QAction* action) { project()->navigateTo(action->data().toString()); } /*! * copies the values of the column to the clipboard */ void Column::copyData() { QLocale locale; QString output; int rows = rowCount(); - if (columnMode() == AbstractColumn::Numeric) { + if (columnMode() == ColumnMode::Numeric) { const Double2StringFilter* filter = static_cast(outputFilter()); char format = filter->numericFormat(); for (int r = 0; r < rows; r++) { output += locale.toString(valueAt(r), format, 16); // copy with max. precision if (r < rows-1) output += '\n'; } - } else if (columnMode() == AbstractColumn::Integer || columnMode() == AbstractColumn::BigInt) { + } else if (columnMode() == ColumnMode::Integer || columnMode() == ColumnMode::BigInt) { for (int r = 0; r < rowCount(); r++) { output += QString::number(valueAt(r)); if (r < rows-1) output += '\n'; } } else { for (int r = 0; r < rowCount(); r++) { output += asStringColumn()->textAt(r); if (r < rows-1) output += '\n'; } } QApplication::clipboard()->setText(output); } /*! * */ void Column::setSuppressDataChangedSignal(bool b) { m_suppressDataChangedSignal = b; } void Column::addUsedInPlots(QVector& plots) { const Project* project = this->project(); //when executing tests we don't create any project, //add a null-pointer check for tests here. if (!project) return; auto curves = project->children(AbstractAspect::ChildIndexFlag::Recursive); //determine the plots where the column is consumed for (const auto* curve : curves) { if (curve->xColumn() == this || curve->yColumn() == this || (curve->xErrorType() == XYCurve::SymmetricError && curve->xErrorPlusColumn() == this) || (curve->xErrorType() == XYCurve::AsymmetricError && (curve->xErrorPlusColumn() == this ||curve->xErrorMinusColumn() == this)) || (curve->yErrorType() == XYCurve::SymmetricError && curve->yErrorPlusColumn() == this) || (curve->yErrorType() == XYCurve::AsymmetricError && (curve->yErrorPlusColumn() == this ||curve->yErrorMinusColumn() == this)) ) { auto* plot = static_cast(curve->parentAspect()); if (plots.indexOf(plot) == -1) plots << plot; } } auto hists = project->children(AbstractAspect::ChildIndexFlag::Recursive); for (const auto* hist : hists) { if (hist->dataColumn() == this ) { auto* plot = static_cast(hist->parentAspect()); if (plots.indexOf(plot) == -1) plots << plot; } } } /** * \brief Set the column mode * * This sets the column mode and, if * necessary, converts it to another datatype. */ void Column::setColumnMode(AbstractColumn::ColumnMode mode) { if (mode == columnMode()) return; DEBUG("Column::setColumnMode()"); beginMacro(i18n("%1: change column type", name())); auto* old_input_filter = d->inputFilter(); auto* old_output_filter = d->outputFilter(); exec(new ColumnSetModeCmd(d, mode)); if (d->inputFilter() != old_input_filter) { removeChild(old_input_filter); addChild(d->inputFilter()); d->inputFilter()->input(0, m_string_io); } if (d->outputFilter() != old_output_filter) { removeChild(old_output_filter); addChild(d->outputFilter()); d->outputFilter()->input(0, this); } endMacro(); DEBUG("Column::setColumnMode() DONE"); } void Column::setColumnModeFast(AbstractColumn::ColumnMode mode) { if (mode == columnMode()) return; auto* old_input_filter = d->inputFilter(); auto* old_output_filter = d->outputFilter(); exec(new ColumnSetModeCmd(d, mode)); if (d->inputFilter() != old_input_filter) { removeChild(old_input_filter); addChildFast(d->inputFilter()); d->inputFilter()->input(0, m_string_io); } if (d->outputFilter() != old_output_filter) { removeChild(old_output_filter); addChildFast(d->outputFilter()); d->outputFilter()->input(0, this); } } bool Column::isDraggable() const { return true; } QVector Column::dropableOn() const { return QVector{AspectType::CartesianPlot}; } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool Column::copy(const AbstractColumn* other) { Q_CHECK_PTR(other); if (other->columnMode() != columnMode()) return false; exec(new ColumnFullCopyCmd(d, other)); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool Column::copy(const AbstractColumn* source, int source_start, int dest_start, int num_rows) { Q_CHECK_PTR(source); if (source->columnMode() != columnMode()) return false; exec(new ColumnPartialCopyCmd(d, source, source_start, dest_start, num_rows)); return true; } /** * \brief Insert some empty (or initialized with zero) rows */ void Column::handleRowInsertion(int before, int count) { AbstractColumn::handleRowInsertion(before, count); exec(new ColumnInsertRowsCmd(d, before, count)); if (!m_suppressDataChangedSignal) emit dataChanged(this); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; } /** * \brief Remove 'count' rows starting from row 'first' */ void Column::handleRowRemoval(int first, int count) { AbstractColumn::handleRowRemoval(first, count); exec(new ColumnRemoveRowsCmd(d, first, count)); if (!m_suppressDataChangedSignal) emit dataChanged(this); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; } /** * \brief Set the column plot designation */ void Column::setPlotDesignation(AbstractColumn::PlotDesignation pd) { if (pd != plotDesignation()) exec(new ColumnSetPlotDesignationCmd(d, pd)); } /** * \brief Get width */ int Column::width() const { return d->width(); } /** * \brief Set width */ void Column::setWidth(int value) { d->setWidth(value); } /** * \brief Clear the whole column */ void Column::clear() { exec(new ColumnClearCmd(d)); } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //! \name Formula related functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Returns the formula used to generate column values */ QString Column:: formula() const { return d->formula(); } const QStringList& Column::formulaVariableNames() const { return d->formulaVariableNames(); } const QVector& Column::formulaVariableColumns() const { return d->formulaVariableColumns(); } const QStringList& Column::formulaVariableColumnPaths() const { return d->formulaVariableColumnPaths(); } void Column::setformulVariableColumnsPath(int index, const QString& path) { d->setformulVariableColumnsPath(index, path); } void Column::setformulVariableColumn(int index, Column* column) { d->setformulVariableColumn(index, column); } bool Column::formulaAutoUpdate() const { return d->formulaAutoUpdate(); } /** * \brief Sets the formula used to generate column values */ void Column::setFormula(const QString& formula, const QStringList& variableNames, const QVector& columns, bool autoUpdate) { exec(new ColumnSetGlobalFormulaCmd(d, formula, variableNames, columns, autoUpdate)); } /*! * in case the cell values are calculated via a global column formula, * updates the values on data changes in all the dependent changes in the * "variable columns". */ void Column::updateFormula() { d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; d->updateFormula(); } /** * \brief Set a formula string for an interval of rows */ void Column::setFormula(const Interval& i, const QString& formula) { exec(new ColumnSetFormulaCmd(d, i, formula)); } /** * \brief Overloaded function for convenience */ void Column::setFormula(int row, const QString& formula) { setFormula(Interval(row, row), formula); } /** * \brief Clear all formulas */ void Column::clearFormulas() { exec(new ColumnClearFormulasCmd(d)); } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //! \name type specific functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Text */ void Column::setTextAt(int row, const QString& new_value) { DEBUG("Column::setTextAt()"); exec(new ColumnSetTextCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is Text */ void Column::replaceTexts(int first, const QVector& new_values) { DEBUG("Column::replaceTexts()"); if (!new_values.isEmpty()) //TODO: do we really need this check? exec(new ColumnReplaceTextsCmd(d, first, new_values)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void Column::setDateAt(int row, QDate new_value) { setDateTimeAt(row, QDateTime(new_value, timeAt(row))); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void Column::setTimeAt(int row, QTime new_value) { setDateTimeAt(row, QDateTime(dateAt(row), new_value)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void Column::setDateTimeAt(int row, const QDateTime& new_value) { exec(new ColumnSetDateTimeCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is DateTime, Month or Day */ void Column::replaceDateTimes(int first, const QVector& new_values) { if (!new_values.isEmpty()) exec(new ColumnReplaceDateTimesCmd(d, first, new_values)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Numeric */ void Column::setValueAt(int row, const double new_value) { DEBUG("Column::setValueAt()") DEBUG("setvalue") exec(new ColumnSetValueCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is Numeric */ void Column::replaceValues(int first, const QVector& new_values) { DEBUG("Column::replaceValues()") if (!new_values.isEmpty()) exec(new ColumnReplaceValuesCmd(d, first, new_values)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Integer */ void Column::setIntegerAt(int row, const int new_value) { DEBUG("Column::setIntegerAt()") exec(new ColumnSetIntegerCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is Integer */ void Column::replaceInteger(int first, const QVector& new_values) { DEBUG("Column::replaceInteger()") if (!new_values.isEmpty()) exec(new ColumnReplaceIntegerCmd(d, first, new_values)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is BigInt */ void Column::setBigIntAt(int row, const qint64 new_value) { DEBUG("Column::setBigIntAt()") d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnSetBigIntCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is BigInt */ void Column::replaceBigInt(int first, const QVector& new_values) { DEBUG("Column::replaceInteger()") if (!new_values.isEmpty()) exec(new ColumnReplaceBigIntCmd(d, first, new_values)); } /*! * \brief Column::properties * Returns the column properties of this curve (monoton increasing, monoton decreasing, ... ) * \see AbstractColumn::properties */ AbstractColumn::Properties Column::properties() const { if (!d->propertiesAvailable) d->updateProperties(); return d->properties; } const Column::ColumnStatistics& Column::statistics() const { if (!d->statisticsAvailable) calculateStatistics(); return d->statistics; } void Column::calculateStatistics() const { - if ( (columnMode() != AbstractColumn::Numeric) && (columnMode() != AbstractColumn::Integer) - && (columnMode() != AbstractColumn::BigInt) ) + if ( (columnMode() != ColumnMode::Numeric) && (columnMode() != ColumnMode::Integer) + && (columnMode() != ColumnMode::BigInt) ) return; d->statistics = ColumnStatistics(); ColumnStatistics& statistics = d->statistics; int rowValuesSize = 0; int notNanCount = 0; double val; double columnSum = 0.0; double columnProduct = 1.0; double columnSumNeg = 0.0; double columnSumSquare = 0.0; statistics.minimum = INFINITY; statistics.maximum = -INFINITY; QMap frequencyOfValues; QVector rowData; - if (columnMode() == AbstractColumn::Numeric) { + if (columnMode() == ColumnMode::Numeric) { auto* rowValues = reinterpret_cast*>(data()); rowValuesSize = rowValues->size(); rowData.reserve(rowValuesSize); for (int row = 0; row < rowValuesSize; ++row) { val = rowValues->value(row); if (std::isnan(val) || isMasked(row)) continue; if (val < statistics.minimum) statistics.minimum = val; if (val > statistics.maximum) statistics.maximum = val; columnSum += val; columnSumNeg += (1.0 / val); columnSumSquare += val*val; columnProduct *= val; if (frequencyOfValues.contains(val)) frequencyOfValues.operator [](val)++; else frequencyOfValues.insert(val, 1); ++notNanCount; rowData.push_back(val); } - } else if (columnMode() == AbstractColumn::Integer) { + } else if (columnMode() == ColumnMode::Integer) { //TODO: code duplication because of the reinterpret_cast... auto* rowValues = reinterpret_cast*>(data()); rowValuesSize = rowValues->size(); rowData.reserve(rowValuesSize); for (int row = 0; row < rowValuesSize; ++row) { val = rowValues->value(row); if (std::isnan(val) || isMasked(row)) continue; if (val < statistics.minimum) statistics.minimum = val; if (val > statistics.maximum) statistics.maximum = val; columnSum += val; columnSumNeg += (1.0 / val); columnSumSquare += val*val; columnProduct *= val; if (frequencyOfValues.contains(val)) frequencyOfValues.operator [](val)++; else frequencyOfValues.insert(val, 1); ++notNanCount; rowData.push_back(val); } - } else if (columnMode() == AbstractColumn::BigInt) { + } else if (columnMode() == ColumnMode::BigInt) { //TODO: code duplication because of the reinterpret_cast... auto* rowValues = reinterpret_cast*>(data()); rowValuesSize = rowValues->size(); rowData.reserve(rowValuesSize); for (int row = 0; row < rowValuesSize; ++row) { val = rowValues->value(row); if (std::isnan(val) || isMasked(row)) continue; if (val < statistics.minimum) statistics.minimum = val; if (val > statistics.maximum) statistics.maximum = val; columnSum += val; columnSumNeg += (1.0 / val); columnSumSquare += val*val; columnProduct *= val; if (frequencyOfValues.contains(val)) frequencyOfValues.operator [](val)++; else frequencyOfValues.insert(val, 1); ++notNanCount; rowData.push_back(val); } } if (notNanCount == 0) { d->statisticsAvailable = true; return; } if (rowData.size() < rowValuesSize) rowData.squeeze(); statistics.size = notNanCount; statistics.arithmeticMean = columnSum / notNanCount; statistics.geometricMean = pow(columnProduct, 1.0 / notNanCount); statistics.harmonicMean = notNanCount / columnSumNeg; statistics.contraharmonicMean = columnSumSquare / columnSum; //calculate the mode, the most frequent value in the data set int maxFreq = 0; double mode = NAN; QMap::const_iterator it = frequencyOfValues.constBegin(); while (it != frequencyOfValues.constEnd()) { if (it.value() > maxFreq) { maxFreq = it.value(); mode = it.key(); } ++it; } //check how many times the max frequency occurs in the data set. //if more than once, we have a multi-modal distribution and don't show any mode it = frequencyOfValues.constBegin(); int maxFreqOccurance = 0; while (it != frequencyOfValues.constEnd()) { if (it.value() == maxFreq) ++maxFreqOccurance; if (maxFreqOccurance > 1) { mode = NAN; break; } ++it; } statistics.mode = mode; double columnSumVariance = 0; double columnSumMeanDeviation = 0.0; double columnSumMedianDeviation = 0.0; double sumForCentralMoment_r3 = 0.0; double sumForCentralMoment_r4 = 0.0; //sort the data to calculate the percentiles gsl_sort(rowData.data(), 1, notNanCount); // statistics.median = (notNanCount%2) ? rowData.at((int)((notNanCount-1)/2)) : // (rowData.at((int)((notNanCount-1)/2)) + rowData.at((int)(notNanCount/2)))/2.0; statistics.firstQuartile = gsl_stats_quantile_from_sorted_data(rowData.data(), 1, notNanCount, 0.25); statistics.median = gsl_stats_quantile_from_sorted_data(rowData.data(), 1, notNanCount, 0.50); statistics.thirdQuartile = gsl_stats_quantile_from_sorted_data(rowData.data(), 1, notNanCount, 0.75); statistics.iqr = statistics.thirdQuartile - statistics.firstQuartile; statistics.trimean = (statistics.firstQuartile + 2*statistics.median + statistics.thirdQuartile) / 4; QVector absoluteMedianList; absoluteMedianList.reserve((int)notNanCount); absoluteMedianList.resize((int)notNanCount); for (int row = 0; row < notNanCount; ++row) { val = rowData.value(row); columnSumVariance += gsl_pow_2(val - statistics.arithmeticMean); sumForCentralMoment_r3 += gsl_pow_3(val - statistics.arithmeticMean); sumForCentralMoment_r4 += gsl_pow_4(val - statistics.arithmeticMean); columnSumMeanDeviation += fabs(val - statistics.arithmeticMean); absoluteMedianList[row] = fabs(val - statistics.median); columnSumMedianDeviation += absoluteMedianList[row]; } statistics.meanDeviationAroundMedian = columnSumMedianDeviation / notNanCount; //sort the data to calculate the median gsl_sort(absoluteMedianList.data(), 1, notNanCount); statistics.medianDeviation = (notNanCount%2) ? absoluteMedianList.at((int)((notNanCount-1)/2)) : (absoluteMedianList.at((int)((notNanCount-1)/2)) + absoluteMedianList.at((int)(notNanCount/2)))/2.0; const double centralMoment_r3 = sumForCentralMoment_r3 / notNanCount; const double centralMoment_r4 = sumForCentralMoment_r4 / notNanCount; statistics.variance = columnSumVariance / notNanCount; statistics.standardDeviation = sqrt(statistics.variance * notNanCount / (notNanCount - 1)); statistics.skewness = centralMoment_r3 / gsl_pow_3(statistics.standardDeviation); statistics.kurtosis = (centralMoment_r4 / gsl_pow_4(statistics.standardDeviation)) - 3.0; statistics.meanDeviation = columnSumMeanDeviation / notNanCount; double entropy = 0.0; for (const auto& v : frequencyOfValues) { const double frequencyNorm = static_cast(v) / notNanCount; entropy += (frequencyNorm * log2(frequencyNorm)); } statistics.entropy = -entropy; d->statisticsAvailable = true; } ////////////////////////////////////////////////////////////////////////////////////////////// void* Column::data() const { return d->data(); } /*! * return \c true if the column has numeric values, \c false otherwise. */ bool Column::hasValues() const { if (d->hasValuesAvailable) return d->hasValues; bool foundValues = false; switch (columnMode()) { - case AbstractColumn::Numeric: { + case ColumnMode::Numeric: { for (int row = 0; row < rowCount(); ++row) { if (!std::isnan(valueAt(row))) { foundValues = true; break; } } break; } - case AbstractColumn::Text: { + case ColumnMode::Text: { for (int row = 0; row < rowCount(); ++row) { if (!textAt(row).isEmpty()) { foundValues = true; break; } } break; } - case AbstractColumn::Integer: - case AbstractColumn::BigInt: + case ColumnMode::Integer: + case ColumnMode::BigInt: //integer column has always valid values foundValues = true; break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: { + case ColumnMode::DateTime: + case ColumnMode::Month: + case ColumnMode::Day: { for (int row = 0; row < rowCount(); ++row) { if (dateTimeAt(row).isValid()) { foundValues = true; break; } } break; } } d->hasValues = foundValues; d->hasValuesAvailable = true; return d->hasValues; } /** * \brief Return the content of row 'row'. * * Use this only when columnMode() is Text */ QString Column::textAt(int row) const { return d->textAt(row); } /** * \brief Return the date part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDate Column::dateAt(int row) const { return d->dateAt(row); } /** * \brief Return the time part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QTime Column::timeAt(int row) const { return d->timeAt(row); } /** * \brief Return the QDateTime in row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDateTime Column::dateTimeAt(int row) const { return d->dateTimeAt(row); } /** * \brief Return the double value in row 'row' */ double Column::valueAt(int row) const { return d->valueAt(row); } /** * \brief Return the int value in row 'row' */ int Column::integerAt(int row) const { return d->integerAt(row); } /** * \brief Return the bigint value in row 'row' */ qint64 Column::bigIntAt(int row) const { return d->bigIntAt(row); } /* * call this function if the data of the column was changed directly via the data()-pointer * and not via the setValueAt() in order to emit the dataChanged-signal. * This is used e.g. in \c XYFitCurvePrivate::recalculate() */ void Column::setChanged() { d->propertiesAvailable = false; if (!m_suppressDataChangedSignal) emit dataChanged(this); d->statisticsAvailable = false; d->hasValuesAvailable = false; } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// /** * \brief Return an icon to be used for decorating the views and spreadsheet column headers */ QIcon Column::icon() const { return iconForMode(columnMode()); } //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name serialize/deserialize //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Save the column as XML */ void Column::save(QXmlStreamWriter* writer) const { writer->writeStartElement("column"); writeBasicAttributes(writer); writer->writeAttribute("rows", QString::number(rowCount())); writer->writeAttribute("designation", QString::number(static_cast(plotDesignation()))); - writer->writeAttribute("mode", QString::number(columnMode())); + writer->writeAttribute("mode", QString::number(static_cast(columnMode()))); writer->writeAttribute("width", QString::number(width())); //save the formula used to generate column values, if available if (!formula().isEmpty() ) { writer->writeStartElement("formula"); writer->writeAttribute("autoUpdate", QString::number(d->formulaAutoUpdate())); writer->writeTextElement("text", formula()); writer->writeStartElement("variableNames"); for (const auto& name : formulaVariableNames()) writer->writeTextElement("name", name); writer->writeEndElement(); writer->writeStartElement("columnPathes"); for (const auto& path : formulaVariableColumnPaths()) writer->writeTextElement("path", path); writer->writeEndElement(); writer->writeEndElement(); } writeCommentElement(writer); writer->writeStartElement("input_filter"); d->inputFilter()->save(writer); writer->writeEndElement(); writer->writeStartElement("output_filter"); d->outputFilter()->save(writer); writer->writeEndElement(); XmlWriteMask(writer); //TODO: formula in cells is not implemented yet // QVector< Interval > formulas = formulaIntervals(); // foreach(const Interval& interval, formulas) { // writer->writeStartElement("formula"); // writer->writeAttribute("start_row", QString::number(interval.start())); // writer->writeAttribute("end_row", QString::number(interval.end())); // writer->writeCharacters(formula(interval.start())); // writer->writeEndElement(); // } int i; switch (columnMode()) { - case AbstractColumn::Numeric: { + case ColumnMode::Numeric: { const char* data = reinterpret_cast(static_cast< QVector* >(d->data())->constData()); size_t size = d->rowCount() * sizeof(double); writer->writeCharacters(QByteArray::fromRawData(data, (int)size).toBase64()); break; } - case AbstractColumn::Integer: { + case ColumnMode::Integer: { const char* data = reinterpret_cast(static_cast< QVector* >(d->data())->constData()); size_t size = d->rowCount() * sizeof(int); writer->writeCharacters(QByteArray::fromRawData(data, (int)size).toBase64()); break; } - case AbstractColumn::BigInt: { + case ColumnMode::BigInt: { const char* data = reinterpret_cast(static_cast< QVector* >(d->data())->constData()); size_t size = d->rowCount() * sizeof(qint64); writer->writeCharacters(QByteArray::fromRawData(data, (int)size).toBase64()); break; } - case AbstractColumn::Text: + case ColumnMode::Text: for (i = 0; i < rowCount(); ++i) { writer->writeStartElement("row"); writer->writeAttribute("index", QString::number(i)); writer->writeCharacters(textAt(i)); writer->writeEndElement(); } break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case ColumnMode::DateTime: + case ColumnMode::Month: + case ColumnMode::Day: for (i = 0; i < rowCount(); ++i) { writer->writeStartElement("row"); writer->writeAttribute("index", QString::number(i)); writer->writeCharacters(dateTimeAt(i).toString("yyyy-dd-MM hh:mm:ss:zzz")); writer->writeEndElement(); } break; } writer->writeEndElement(); // "column" } //TODO: extra header class DecodeColumnTask : public QRunnable { public: DecodeColumnTask(ColumnPrivate* priv, const QString& content) { m_private = priv; m_content = content; }; void run() override { QByteArray bytes = QByteArray::fromBase64(m_content.toLatin1()); - if (m_private->columnMode() == AbstractColumn::Numeric) { + if (m_private->columnMode() == AbstractColumn::ColumnMode::Numeric) { auto* data = new QVector(bytes.size()/(int)sizeof(double)); memcpy(data->data(), bytes.data(), bytes.size()); m_private->replaceData(data); - } else if (m_private->columnMode() == AbstractColumn::BigInt) { + } else if (m_private->columnMode() == AbstractColumn::ColumnMode::BigInt) { auto* data = new QVector(bytes.size()/(int)sizeof(qint64)); memcpy(data->data(), bytes.data(), bytes.size()); m_private->replaceData(data); } else { auto* data = new QVector(bytes.size()/(int)sizeof(int)); memcpy(data->data(), bytes.data(), bytes.size()); m_private->replaceData(data); } } private: ColumnPrivate* m_private; QString m_content; }; /** * \brief Load the column from XML */ bool Column::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value("rows").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("rows").toString()); else d->resizeTo(str.toInt()); str = attribs.value("designation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("designation").toString()); else d->setPlotDesignation( AbstractColumn::PlotDesignation(str.toInt()) ); str = attribs.value("mode").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("mode").toString()); else setColumnModeFast( AbstractColumn::ColumnMode(str.toInt()) ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else d->setWidth(str.toInt()); // read child elements while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement()) break; if (reader->isStartElement()) { bool ret_val = true; if (reader->name() == "comment") ret_val = readCommentElement(reader); else if (reader->name() == "input_filter") ret_val = XmlReadInputFilter(reader); else if (reader->name() == "output_filter") ret_val = XmlReadOutputFilter(reader); else if (reader->name() == "mask") ret_val = XmlReadMask(reader); else if (reader->name() == "formula") ret_val = XmlReadFormula(reader); else if (reader->name() == "row") ret_val = XmlReadRow(reader); else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } if (!ret_val) return false; } if (!preview) { QString content = reader->text().toString().trimmed(); - if (!content.isEmpty() && ( columnMode() == AbstractColumn::Numeric || - columnMode() == AbstractColumn::Integer || columnMode() == AbstractColumn::BigInt)) { + if (!content.isEmpty() && ( columnMode() == ColumnMode::Numeric || + columnMode() == ColumnMode::Integer || columnMode() == ColumnMode::BigInt)) { auto* task = new DecodeColumnTask(d, content); QThreadPool::globalInstance()->start(task); } } } return !reader->error(); } void Column::finalizeLoad() { d->finalizeLoad(); } /** * \brief Read XML input filter element */ bool Column::XmlReadInputFilter(XmlStreamReader* reader) { Q_ASSERT(reader->isStartElement() == true && reader->name() == "input_filter"); if (!reader->skipToNextTag()) return false; if (!d->inputFilter()->load(reader, false)) return false; if (!reader->skipToNextTag()) return false; Q_ASSERT(reader->isEndElement() == true && reader->name() == "input_filter"); return true; } /** * \brief Read XML output filter element */ bool Column::XmlReadOutputFilter(XmlStreamReader* reader) { Q_ASSERT(reader->isStartElement() == true && reader->name() == "output_filter"); if (!reader->skipToNextTag()) return false; if (!d->outputFilter()->load(reader, false)) return false; if (!reader->skipToNextTag()) return false; Q_ASSERT(reader->isEndElement() == true && reader->name() == "output_filter"); return true; } /** * \brief Read XML formula element */ bool Column::XmlReadFormula(XmlStreamReader* reader) { QString formula; QStringList variableNames; QStringList columnPathes; bool autoUpdate = reader->attributes().value("autoUpdate").toInt(); while (reader->readNext()) { if (reader->isEndElement()) break; if (reader->name() == "text") formula = reader->readElementText(); else if (reader->name() == "variableNames") { while (reader->readNext()) { if (reader->name() == "variableNames" && reader->isEndElement()) break; if (reader->isStartElement()) variableNames << reader->readElementText(); } } else if (reader->name() == "columnPathes") { while (reader->readNext()) { if (reader->name() == "columnPathes" && reader->isEndElement()) break; if (reader->isStartElement()) columnPathes << reader->readElementText(); } } } d->setFormula(formula, variableNames, columnPathes, autoUpdate); return true; } //TODO: read cell formula, not implemented yet // bool Column::XmlReadFormula(XmlStreamReader* reader) // { // Q_ASSERT(reader->isStartElement() && reader->name() == "formula"); // // bool ok1, ok2; // int start, end; // start = reader->readAttributeInt("start_row", &ok1); // end = reader->readAttributeInt("end_row", &ok2); // if (!ok1 || !ok2) // { // reader->raiseError(i18n("invalid or missing start or end row")); // return false; // } // setFormula(Interval(start,end), reader->readElementText()); // // return true; // } /** * \brief Read XML row element */ bool Column::XmlReadRow(XmlStreamReader* reader) { Q_ASSERT(reader->isStartElement() == true && reader->name() == "row"); // QXmlStreamAttributes attribs = reader->attributes(); bool ok; int index = reader->readAttributeInt("index", &ok); if (!ok) { reader->raiseError(i18n("invalid or missing row index")); return false; } QString str = reader->readElementText(); switch (columnMode()) { - case AbstractColumn::Numeric: { + case ColumnMode::Numeric: { double value = str.toDouble(&ok); if (!ok) { reader->raiseError(i18n("invalid row value")); return false; } setValueAt(index, value); break; } - case AbstractColumn::Integer: { + case ColumnMode::Integer: { int value = str.toInt(&ok); if (!ok) { reader->raiseError(i18n("invalid row value")); return false; } setIntegerAt(index, value); break; } - case AbstractColumn::BigInt: { + case ColumnMode::BigInt: { qint64 value = str.toLongLong(&ok); if (!ok) { reader->raiseError(i18n("invalid row value")); return false; } setBigIntAt(index, value); break; } - case AbstractColumn::Text: + case ColumnMode::Text: setTextAt(index, str); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case ColumnMode::DateTime: + case ColumnMode::Month: + case ColumnMode::Day: QDateTime date_time = QDateTime::fromString(str,"yyyy-dd-MM hh:mm:ss:zzz"); setDateTimeAt(index, date_time); break; } return true; } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// /** * \brief Return whether the object is read-only */ bool Column::isReadOnly() const { return false; } /** * \brief Return the column mode * * This function is mostly used by spreadsheets but can also be used * by plots. The column mode specifies how to interpret * the values in the column additional to the data type. */ AbstractColumn::ColumnMode Column::columnMode() const { return d->columnMode(); } /** * \brief Return the data vector size */ int Column::rowCount() const { return d->rowCount(); } /** * \brief Return the number of available data rows * * This returns the number of rows that actually contain data. * Rows beyond this can be masked etc. but should be ignored by filters, * plots etc. */ int Column::availableRowCount() const { return d->availableRowCount(); } /** * \brief Return the column plot designation */ AbstractColumn::PlotDesignation Column::plotDesignation() const { return d->plotDesignation(); } QString Column::plotDesignationString() const { switch (plotDesignation()) { case PlotDesignation::NoDesignation: return QString(""); case PlotDesignation::X: return QLatin1String("[X]"); case PlotDesignation::Y: return QLatin1String("[Y]"); case PlotDesignation::Z: return QLatin1String("[Z]"); case PlotDesignation::XError: return QLatin1String("[") + i18n("X-error") + QLatin1Char(']'); case PlotDesignation::XErrorPlus: return QLatin1String("[") + i18n("X-error +") + QLatin1Char(']'); case PlotDesignation::XErrorMinus: return QLatin1String("[") + i18n("X-error -") + QLatin1Char(']'); case PlotDesignation::YError: return QLatin1String("[") + i18n("Y-error") + QLatin1Char(']'); case PlotDesignation::YErrorPlus: return QLatin1String("[") + i18n("Y-error +") + QLatin1Char(']'); case PlotDesignation::YErrorMinus: return QLatin1String("[") + i18n("Y-error -") + QLatin1Char(']'); } return QString(""); } AbstractSimpleFilter* Column::outputFilter() const { return d->outputFilter(); } /** * \brief Return a wrapper column object used for String I/O. */ ColumnStringIO* Column::asStringColumn() const { return m_string_io; } //////////////////////////////////////////////////////////////////////////////// //! \name IntervalAttribute related functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the formula associated with row 'row' */ QString Column::formula(int row) const { return d->formula(row); } /** * \brief Return the intervals that have associated formulas * * This can be used to make a list of formulas with their intervals. * Here is some example code: * * \code * QStringList list; * QVector< Interval > intervals = my_column.formulaIntervals(); * foreach(Interval interval, intervals) * list << QString(interval.toString() + ": " + my_column.formula(interval.start())); * \endcode */ QVector< Interval > Column::formulaIntervals() const { return d->formulaIntervals(); } void Column::handleFormatChange() { DEBUG("Column::handleFormatChange() mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, columnMode())); - if (columnMode() == AbstractColumn::DateTime) { + if (columnMode() == ColumnMode::DateTime) { auto* input_filter = static_cast(d->inputFilter()); auto* output_filter = static_cast(d->outputFilter()); DEBUG("change format " << STDSTRING(input_filter->format()) << " to " << STDSTRING(output_filter->format())); input_filter->setFormat(output_filter->format()); } emit aspectDescriptionChanged(this); // the icon for the type changed if (!m_suppressDataChangedSignal) emit formatChanged(this); // all cells must be repainted d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; DEBUG("Column::handleFormatChange() DONE"); } /*! * calculates the minimal value in the column. * for \c count = 0, the minimum of all elements is returned. * for \c count > 0, the minimum of the first \p count elements is returned. * for \c count < 0, the minimum of the last \p count elements is returned. */ double Column::minimum(int count) const { double min = INFINITY; if (count == 0 && d->statisticsAvailable) min = const_cast(this)->statistics().minimum; else { int start, end; if (count == 0) { start = 0; end = rowCount(); } else if (count > 0) { start = 0; end = qMin(rowCount(), count); } else { start = qMax(rowCount() + count, 0); end = rowCount(); } return minimum(start, end); } return min; } /*! * \brief Column::minimum * Calculates the minimum value in the column between the \p startIndex and \p endIndex, endIndex is excluded. * If startIndex is greater than endIndex the indices are swapped * \p startIndex * \p endIndex */ double Column::minimum(int startIndex, int endIndex) const { double min = INFINITY; if (rowCount() == 0) return min; if (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) std::swap(startIndex, endIndex); startIndex = qMax(startIndex, 0); endIndex = qMax(endIndex, 0); startIndex = qMin(startIndex, rowCount() - 1); endIndex = qMin(endIndex, rowCount() - 1); int foundIndex = 0; ColumnMode mode = columnMode(); Properties property = properties(); if (property == Properties::No) { // skipping values is only in Properties::No needed, because // when there are invalid values the property must be Properties::No switch (mode) { - case Numeric: { + case ColumnMode::Numeric: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const double val = vec->at(row); if (std::isnan(val)) continue; if (val < min) min = val; } break; } - case Integer: { + case ColumnMode::Integer: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const int val = vec->at(row); if (val < min) min = val; } break; } - case BigInt: { + case ColumnMode::BigInt: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const qint64 val = vec->at(row); if (val < min) min = val; } break; } - case Text: + case ColumnMode::Text: break; - case DateTime: { + case ColumnMode::DateTime: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const qint64 val = vec->at(row).toMSecsSinceEpoch(); if (val < min) min = val; } break; } - case Day: - case Month: + case ColumnMode::Day: + case ColumnMode::Month: default: break; } return min; } // use the properties knowledge to determine maximum faster if (property == Properties::Constant || property == Properties::MonotonicIncreasing) foundIndex = startIndex; else if (property == Properties::MonotonicDecreasing) foundIndex = endIndex; switch (mode) { - case Numeric: - case Integer: - case BigInt: + case ColumnMode::Numeric: + case ColumnMode::Integer: + case ColumnMode::BigInt: return valueAt(foundIndex); - case DateTime: - case Month: - case Day: + case ColumnMode::DateTime: + case ColumnMode::Month: + case ColumnMode::Day: return dateTimeAt(foundIndex).toMSecsSinceEpoch(); - case Text: + case ColumnMode::Text: default: break; } return min; } /*! * calculates the maximal value in the column. * for \c count = 0, the maximum of all elements is returned. * for \c count > 0, the maximum of the first \p count elements is returned. * for \c count < 0, the maximum of the last \p count elements is returned. */ double Column::maximum(int count) const { double max = -INFINITY; if (count == 0 && d->statisticsAvailable) max = const_cast(this)->statistics().maximum; else { int start, end; if (count == 0) { start = 0; end = rowCount(); } else if (count > 0) { start = 0; end = qMin(rowCount(), count); } else { start = qMax(rowCount() + count, 0); end = rowCount(); } return maximum(start, end); } return max; } /*! * \brief Column::maximum * Calculates the maximum value in the column between the \p startIndex and \p endIndex. * If startIndex is greater than endIndex the indices are swapped * \p startIndex * \p endIndex */ double Column::maximum(int startIndex, int endIndex) const { double max = -INFINITY; if (rowCount() == 0) return max; if (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) std::swap(startIndex, endIndex); startIndex = qMax(startIndex, 0); endIndex = qMax(endIndex, 0); startIndex = qMin(startIndex, rowCount() - 1); endIndex = qMin(endIndex, rowCount() - 1); int foundIndex = 0; ColumnMode mode = columnMode(); Properties property = properties(); if (property == Properties::No) { switch (mode) { - case Numeric: { + case ColumnMode::Numeric: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const double val = vec->at(row); if (std::isnan(val)) continue; if (val > max) max = val; } break; } - case Integer: { + case ColumnMode::Integer: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const int val = vec->at(row); if (val > max) max = val; } break; } - case BigInt: { + case ColumnMode::BigInt: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const qint64 val = vec->at(row); if (val > max) max = val; } break; } - case Text: + case ColumnMode::Text: break; - case DateTime: { + case ColumnMode::DateTime: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const qint64 val = vec->at(row).toMSecsSinceEpoch(); if (val > max) max = val; } break; } - case Day: - case Month: + case ColumnMode::Day: + case ColumnMode::Month: default: break; } return max; } // use the properties knowledge to determine maximum faster if (property == Properties::Constant || property == Properties::MonotonicDecreasing) foundIndex = startIndex; else if (property == Properties::MonotonicIncreasing) foundIndex = endIndex; switch (mode) { - case Numeric: - case Integer: - case BigInt: + case ColumnMode::Numeric: + case ColumnMode::Integer: + case ColumnMode::BigInt: return valueAt(foundIndex); - case DateTime: - case Month: - case Day: + case ColumnMode::DateTime: + case ColumnMode::Month: + case ColumnMode::Day: return dateTimeAt(foundIndex).toMSecsSinceEpoch(); - case Text: + case ColumnMode::Text: default: break; } return max; } /*! * calculates log2(x)+1 for an integer value. * Used in y(double x) to calculate the maximum steps * source: https://stackoverflow.com/questions/11376288/fast-computing-of-log2-for-64-bit-integers * source: https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup * @param value * @return returns calculated value */ // TODO: testing if it is faster than calculating log2. int Column::calculateMaxSteps (unsigned int value) { const std::array LogTable256 = { -1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 }; unsigned int r; // r will be lg(v) unsigned int t, tt; // temporaries if ((tt = value >> 16)) r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt]; else r = (t = value >> 8) ? 8 + LogTable256[t] : LogTable256[value]; return r+1; } /*! * Find index which corresponds to a @p x . In a vector of values * 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 * @return -1 if index not found, otherwise the index */ int Column::indexForValue(double x, QVector& column, Properties properties) { int rowCount = column.count(); if (rowCount == 0) return -1; double prevValue = 0; //qint64 prevValueDateTime = 0; if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) { // bisects the index every time, so it is possible to find the value in log_2(rowCount) steps bool increase = true; if(properties == AbstractColumn::Properties::MonotonicDecreasing) increase = false; int lowerIndex = 0; int higherIndex = rowCount-1; unsigned int maxSteps = calculateMaxSteps(static_cast(rowCount))+1; for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); double value = column[index]; if (higherIndex - lowerIndex < 2) { if (qAbs(column[lowerIndex] - x) < qAbs(column[higherIndex] - x)) index = lowerIndex; else index = higherIndex; return index; } if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; } } else if (properties == AbstractColumn::Properties::Constant) { return 0; } else { // AbstractColumn::Properties::No // naiv way int index = 0; prevValue = column[0]; for (int row = 0; row < rowCount; row++) { double value = column[row]; if (qAbs(value - x) <= qAbs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 prevValue = value; index = row; } } return index; } return -1; } /*! * Find index which corresponds to a @p x . In a vector of values * 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 * @return -1 if index not found, otherwise the index */ int Column::indexForValue(const double x, const QVector& points, Properties properties) { int rowCount = points.count(); if (rowCount == 0) return -1; double prevValue = 0; //qint64 prevValueDateTime = 0; if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) { // bisects the index every time, so it is possible to find the value in log_2(rowCount) steps bool increase = true; if(properties == AbstractColumn::Properties::MonotonicDecreasing) increase = false; int lowerIndex = 0; int higherIndex = rowCount - 1; unsigned int maxSteps = calculateMaxSteps(static_cast(rowCount))+1; for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); double value = points[index].x(); if (higherIndex - lowerIndex < 2) { if (qAbs(points[lowerIndex].x() - x) < qAbs(points[higherIndex].x() - x)) index = lowerIndex; else index = higherIndex; return index; } if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; } } else if (properties == AbstractColumn::Properties::Constant) { return 0; } else { // AbstractColumn::Properties::No // naiv way prevValue = points[0].x(); int index = 0; for (int row = 0; row < rowCount; row++) { double value = points[row].x(); if (qAbs(value - x) <= qAbs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 prevValue = value; index = row; } } return index; } return -1; } /*! * Find index which corresponds to a @p x . In a vector of values * 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 * @return -1 if index not found, otherwise the index */ int Column::indexForValue(double x, QVector& lines, Properties properties) { int rowCount = lines.count(); if (rowCount == 0) return -1; // use only p1 to find index double prevValue = 0; //qint64 prevValueDateTime = 0; if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) { // bisects the index every time, so it is possible to find the value in log_2(rowCount) steps bool increase = true; if(properties == AbstractColumn::Properties::MonotonicDecreasing) increase = false; int lowerIndex = 0; int higherIndex = rowCount-1; unsigned int maxSteps = calculateMaxSteps(static_cast(rowCount))+1; for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); double value = lines[index].p1().x(); if (higherIndex - lowerIndex < 2) { if (qAbs(lines[lowerIndex].p1().x() - x) < qAbs(lines[higherIndex].p1().x() - x)) index = lowerIndex; else index = higherIndex; return index; } if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; } } else if (properties == AbstractColumn::Properties::Constant) { return 0; } else { // AbstractColumn::Properties::No // naiv way int index = 0; prevValue = lines[0].p1().x(); for (int row = 0; row < rowCount; row++) { double value = lines[row].p1().x(); if (qAbs(value - x) <= qAbs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 prevValue = value; index = row; } } return index; } return -1; } int Column::indexForValue(double x) const { double prevValue = 0; qint64 prevValueDateTime = 0; auto mode = columnMode(); auto property = properties(); if (property == Properties::MonotonicIncreasing || property == Properties::MonotonicDecreasing) { // bisects the index every time, so it is possible to find the value in log_2(rowCount) steps bool increase = (property != Properties::MonotonicDecreasing); int lowerIndex = 0; int higherIndex = rowCount() - 1; unsigned int maxSteps = calculateMaxSteps(static_cast(rowCount())) + 1; switch (mode) { case ColumnMode::Numeric: case ColumnMode::Integer: case ColumnMode::BigInt: for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); double value = valueAt(index); if (higherIndex - lowerIndex < 2) { if (qAbs(valueAt(lowerIndex) - x) < qAbs(valueAt(higherIndex) - x)) index = lowerIndex; else index = higherIndex; return index; } if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; } break; case ColumnMode::Text: break; case ColumnMode::DateTime: case ColumnMode::Month: case ColumnMode::Day: { qint64 xInt64 = static_cast(x); for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); qint64 value = dateTimeAt(index).toMSecsSinceEpoch(); if (higherIndex - lowerIndex < 2) { if (abs(dateTimeAt(lowerIndex).toMSecsSinceEpoch() - xInt64) < abs(dateTimeAt(higherIndex).toMSecsSinceEpoch() - xInt64)) index = lowerIndex; else index = higherIndex; return index; } if (value > xInt64 && increase) higherIndex = index; else if (value >= xInt64 && !increase) lowerIndex = index; else if (value <= xInt64 && increase) lowerIndex = index; else if (value < xInt64 && !increase) higherIndex = index; } } } } else if (property == Properties::Constant) { if (rowCount() > 0) return 0; else return -1; } else { // naiv way int index = 0; switch (mode) { case ColumnMode::Numeric: case ColumnMode::Integer: case ColumnMode::BigInt: for (int row = 0; row < rowCount(); row++) { if (!isValid(row) || isMasked(row)) continue; if (row == 0) prevValue = valueAt(row); double value = valueAt(row); if (abs(value - x) <= abs(prevValue - x)) { // <= prevents also that row - 1 become < 0 if (row < rowCount() - 1) { prevValue = value; index = row; } } } return index; case ColumnMode::Text: break; case ColumnMode::DateTime: case ColumnMode::Month: case ColumnMode::Day: { qint64 xInt64 = static_cast(x); for (int row = 0; row < rowCount(); row++) { if (!isValid(row) || isMasked(row)) continue; if (row == 0) prevValueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); qint64 value = dateTimeAt(row).toMSecsSinceEpoch(); if (abs(value - xInt64) <= abs(prevValueDateTime - xInt64)) { // "<=" prevents also that row - 1 become < 0 prevValueDateTime = value; index = row; } } return index; } } } return -1; } /*! * Finds the minimal and maximal index which are between v1 and v2 * \brief Column::indicesForX * \param v1 * \param v2 * \param start * \param end * \return */ bool Column::indicesMinMax(double v1, double v2, int& start, int& end) const { start = -1; end = -1; if (rowCount() == 0) return false; // Assumption: v1 is always the smaller value if (v1 > v2) qSwap(v1, v2); Properties property = properties(); if (property == Properties::MonotonicIncreasing || property == Properties::MonotonicDecreasing) { start = indexForValue(v1); end = indexForValue(v2); switch (columnMode()) { - case Integer: - case BigInt: - case Numeric: { + case ColumnMode::Integer: + case ColumnMode::BigInt: + case ColumnMode::Numeric: { if (start > 0 && valueAt(start - 1) <= v2 && valueAt(start - 1) >= v1) start--; if (end < rowCount() - 1 && valueAt(end + 1) <= v2 && valueAt(end + 1) >= v1) end++; break; } - case DateTime: - case Month: - case Day: { + case ColumnMode::DateTime: + case ColumnMode::Month: + case ColumnMode::Day: { qint64 v1int64 = v1; qint64 v2int64 = v2; qint64 value; if (start > 0) { value = dateTimeAt(start -1).toMSecsSinceEpoch(); if (value <= v2int64 && value >= v1int64) start--; } if (end > rowCount() - 1) { value = dateTimeAt(end + 1).toMSecsSinceEpoch(); if (value <= v2int64 && value >= v1int64) end++; } break; } - case Text: + case ColumnMode::Text: return false; } return true; } else if (property == Properties::Constant) { start = 0; end = rowCount() - 1; return true; } // property == Properties::No switch (columnMode()) { - case Integer: - case BigInt: - case Numeric: { + case ColumnMode::Integer: + case ColumnMode::BigInt: + case ColumnMode::Numeric: { double value; for (int i = 0; i < rowCount(); i++) { if (!isValid(i) || isMasked(i)) continue; value = valueAt(i); if (value <= v2 && value >= v1) { end = i; if (start < 0) start = i; } } break; } - case DateTime: - case Month: - case Day: { + case ColumnMode::DateTime: + case ColumnMode::Month: + case ColumnMode::Day: { qint64 value; qint64 v2int64 = v2; qint64 v1int64 = v2; for (int i = 0; i < rowCount(); i++) { if (!isValid(i) || isMasked(i)) continue; value = dateTimeAt(i).toMSecsSinceEpoch(); if (value <= v2int64 && value >= v1int64) { end = i; if (start < 0) start = i; } } break; } - case Text: + case ColumnMode::Text: return false; } return true; } diff --git a/src/backend/core/column/Column.h b/src/backend/core/column/Column.h index 6fd04fd4b..707352d4a 100644 --- a/src/backend/core/column/Column.h +++ b/src/backend/core/column/Column.h @@ -1,177 +1,177 @@ /*************************************************************************** File : Column.h Project : LabPlot Description : Aspect that manages a column -------------------------------------------------------------------- Copyright : (C) 2007-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2013-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 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 COLUMN_H #define COLUMN_H #include "backend/core/AbstractSimpleFilter.h" #include "backend/lib/XmlStreamReader.h" #include "backend/core/column/ColumnPrivate.h" class CartesianPlot; class ColumnStringIO; class QActionGroup; class Column : public AbstractColumn { Q_OBJECT public: - explicit Column(const QString& name, AbstractColumn::ColumnMode = AbstractColumn::Numeric); + explicit Column(const QString& name, AbstractColumn::ColumnMode = ColumnMode::Numeric); // template constructor for all supported data types (AbstractColumn::ColumnMode) must be defined in header template - Column(const QString& name, QVector data, AbstractColumn::ColumnMode mode = AbstractColumn::Numeric) + Column(const QString& name, QVector data, AbstractColumn::ColumnMode mode = ColumnMode::Numeric) : AbstractColumn(name, AspectType::Column), d(new ColumnPrivate(this, mode, new QVector(data))) { init(); } void init(); ~Column() override; QIcon icon() const override; QMenu* createContextMenu() override; AbstractColumn::ColumnMode columnMode() const override; void setColumnMode(AbstractColumn::ColumnMode) override; void setColumnModeFast(AbstractColumn::ColumnMode); bool isDraggable() const override; QVector dropableOn() const override; bool copy(const AbstractColumn*) override; bool copy(const AbstractColumn* source, int source_start, int dest_start, int num_rows) override; AbstractColumn::PlotDesignation plotDesignation() const override; QString plotDesignationString() const override; void setPlotDesignation(AbstractColumn::PlotDesignation) override; bool isReadOnly() const override; int rowCount() const override; int availableRowCount() const override; int width() const; void setWidth(const int); void clear() override; AbstractSimpleFilter* outputFilter() const; ColumnStringIO* asStringColumn() const; void setFormula(const QString& formula, const QStringList& variableNames, const QVector& columns, bool autoUpdate); QString formula() const; const QStringList& formulaVariableNames() const; const QVector& formulaVariableColumns() const; const QStringList& formulaVariableColumnPaths() const; void setformulVariableColumnsPath(int index, const QString& path); void setformulVariableColumn(int index, Column*); bool formulaAutoUpdate() const; QString formula(int) const override; QVector< Interval > formulaIntervals() const override; void setFormula(const Interval&, const QString&) override; void setFormula(int, const QString&) override; void clearFormulas() override; const AbstractColumn::ColumnStatistics& statistics() const; void* data() const; bool hasValues() const; QString textAt(int) const override; void setTextAt(int, const QString&) override; void replaceTexts(int, const QVector&) override; QDate dateAt(int) const override; void setDateAt(int, QDate) override; QTime timeAt(int) const override; void setTimeAt(int, QTime) override; QDateTime dateTimeAt(int) const override; void setDateTimeAt(int, const QDateTime&) override; void replaceDateTimes(int, const QVector&) override; double valueAt(int) const override; void setValueAt(int, double) override; void replaceValues(int, const QVector&) override; int integerAt(int) const override; void setIntegerAt(int, int) override; void replaceInteger(int, const QVector&) override; qint64 bigIntAt(int) const override; void setBigIntAt(int, qint64) override; void replaceBigInt(int, const QVector&) override; Properties properties() const override; double maximum(int count = 0) const override; double maximum(int startIndex, int endIndex) const override; double minimum(int count = 0) const override; double minimum(int startIndex, int endIndex) const override; static int calculateMaxSteps(unsigned int value); static int indexForValue(double x, QVector& column, Properties properties = Properties::No); static int indexForValue(const double x, const QVector &column, Properties properties = Properties::No); static int indexForValue(double x, QVector& lines, Properties properties = Properties::No); int indexForValue(double x) const override; bool indicesMinMax(double v1, double v2, int& start, int& end) const override; void setChanged(); void setSuppressDataChangedSignal(const bool); void addUsedInPlots(QVector&); void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; void finalizeLoad(); public slots: void updateFormula(); private: bool XmlReadInputFilter(XmlStreamReader*); bool XmlReadOutputFilter(XmlStreamReader*); bool XmlReadFormula(XmlStreamReader*); bool XmlReadRow(XmlStreamReader*); void handleRowInsertion(int before, int count) override; void handleRowRemoval(int first, int count) override; void calculateStatistics() const; bool m_suppressDataChangedSignal{false}; QAction* m_copyDataAction{nullptr}; QActionGroup* m_usedInActionGroup{nullptr}; ColumnPrivate* d; ColumnStringIO* m_string_io; signals: void requestProjectContextMenu(QMenu*); private slots: void navigateTo(QAction*); void handleFormatChange(); void copyData(); friend class ColumnPrivate; friend class ColumnStringIO; }; #endif diff --git a/src/backend/core/column/ColumnPrivate.cpp b/src/backend/core/column/ColumnPrivate.cpp index 028803fce..0275d20c1 100644 --- a/src/backend/core/column/ColumnPrivate.cpp +++ b/src/backend/core/column/ColumnPrivate.cpp @@ -1,1807 +1,1807 @@ /*************************************************************************** File : ColumnPrivate.cpp Project : AbstractColumn Description : Private data class of Column -------------------------------------------------------------------- Copyright : (C) 2007-2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-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 "ColumnPrivate.h" #include "ColumnStringIO.h" #include "Column.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/datatypes/filter.h" #include "backend/gsl/ExpressionParser.h" ColumnPrivate::ColumnPrivate(Column* owner, AbstractColumn::ColumnMode mode) : m_column_mode(mode), m_owner(owner) { Q_ASSERT(owner != nullptr); switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: m_input_filter = new String2DoubleFilter(); m_output_filter = new Double2StringFilter('g'); m_data = new QVector(); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: m_input_filter = new String2IntegerFilter(); m_output_filter = new Integer2StringFilter(); m_data = new QVector(); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: m_input_filter = new String2BigIntFilter(); m_output_filter = new BigInt2StringFilter(); m_data = new QVector(); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); m_data = new QStringList(); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: m_input_filter = new String2DateTimeFilter(); m_output_filter = new DateTime2StringFilter(); m_data = new QVector(); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: m_input_filter = new String2MonthFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("MMMM"); m_data = new QVector(); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: m_input_filter = new String2DayOfWeekFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("dddd"); m_data = new QVector(); break; } connect(m_output_filter, &AbstractSimpleFilter::formatChanged, m_owner, &Column::handleFormatChange); //m_input_filter->setName("InputFilter"); //m_output_filter->setName("OutputFilter"); } /** * \brief Special ctor (to be called from Column only!) */ ColumnPrivate::ColumnPrivate(Column* owner, AbstractColumn::ColumnMode mode, void* data) : m_column_mode(mode), m_data(data), m_owner(owner) { switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: m_input_filter = new String2DoubleFilter(); m_output_filter = new Double2StringFilter(); connect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: m_input_filter = new String2IntegerFilter(); m_output_filter = new Integer2StringFilter(); connect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: m_input_filter = new String2BigIntFilter(); m_output_filter = new BigInt2StringFilter(); connect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: m_input_filter = new String2DateTimeFilter(); m_output_filter = new DateTime2StringFilter(); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: m_input_filter = new String2MonthFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("MMMM"); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: m_input_filter = new String2DayOfWeekFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("dddd"); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } //m_input_filter->setName("InputFilter"); //m_output_filter->setName("OutputFilter"); } ColumnPrivate::~ColumnPrivate() { if (!m_data) return; switch (m_column_mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: delete static_cast*>(m_data); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: delete static_cast*>(m_data); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: delete static_cast*>(m_data); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: delete static_cast*>(m_data); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: delete static_cast*>(m_data); break; } } AbstractColumn::ColumnMode ColumnPrivate::columnMode() const { return m_column_mode; } /** * \brief Set the column mode * * This sets the column mode and, if * necessary, converts it to another datatype. * Remark: setting the mode back to undefined (the * initial value) is not supported. */ void ColumnPrivate::setColumnMode(AbstractColumn::ColumnMode mode) { DEBUG("ColumnPrivate::setColumnMode() " << ENUM_TO_STRING(AbstractColumn, ColumnMode, m_column_mode) << " -> " << ENUM_TO_STRING(AbstractColumn, ColumnMode, mode)) if (mode == m_column_mode) return; void* old_data = m_data; // remark: the deletion of the old data will be done in the dtor of a command AbstractSimpleFilter* filter = nullptr, *new_in_filter = nullptr, *new_out_filter = nullptr; bool filter_is_temporary = false; // it can also become outputFilter(), which we may not delete here Column* temp_col = nullptr; emit m_owner->modeAboutToChange(m_owner); // determine the conversion filter and allocate the new data vector switch (m_column_mode) { // old mode - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: filter = new Double2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data))); m_data = new QVector(); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: filter = new Double2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data))); m_data = new QVector(); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: filter = new Double2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: filter = new Double2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: filter = new Double2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; } // switch(mode) break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: filter = new Integer2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: filter = new Integer2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: filter = new Integer2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: filter = new Integer2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: filter = new Integer2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { disconnect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: filter = new BigInt2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: filter = new BigInt2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: filter = new BigInt2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: filter = new BigInt2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: filter = new BigInt2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { switch (mode) { - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: break; - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: filter = new String2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: filter = new String2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: filter = new String2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: filter = new String2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: filter = new String2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: filter = new String2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: { + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: { disconnect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QStringList(); break; - case AbstractColumn::Numeric: - if (m_column_mode == AbstractColumn::Month) + case AbstractColumn::ColumnMode::Numeric: + if (m_column_mode == AbstractColumn::ColumnMode::Month) filter = new Month2DoubleFilter(); - else if (m_column_mode == AbstractColumn::Day) + else if (m_column_mode == AbstractColumn::ColumnMode::Day) filter = new DayOfWeek2DoubleFilter(); else filter = new DateTime2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::Integer: - if (m_column_mode == AbstractColumn::Month) + case AbstractColumn::ColumnMode::Integer: + if (m_column_mode == AbstractColumn::ColumnMode::Month) filter = new Month2IntegerFilter(); - else if (m_column_mode == AbstractColumn::Day) + else if (m_column_mode == AbstractColumn::ColumnMode::Day) filter = new DayOfWeek2IntegerFilter(); else filter = new DateTime2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; - case AbstractColumn::BigInt: - if (m_column_mode == AbstractColumn::Month) + case AbstractColumn::ColumnMode::BigInt: + if (m_column_mode == AbstractColumn::ColumnMode::Month) filter = new Month2BigIntFilter(); - else if (m_column_mode == AbstractColumn::Day) + else if (m_column_mode == AbstractColumn::ColumnMode::Day) filter = new DayOfWeek2BigIntFilter(); else filter = new DateTime2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } } // determine the new input and output filters switch (mode) { // new mode - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: new_in_filter = new String2DoubleFilter(); new_out_filter = new Double2StringFilter(); connect(static_cast(new_out_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: new_in_filter = new String2IntegerFilter(); new_out_filter = new Integer2StringFilter(); connect(static_cast(new_out_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: new_in_filter = new String2BigIntFilter(); new_out_filter = new BigInt2StringFilter(); connect(static_cast(new_out_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: new_in_filter = new SimpleCopyThroughFilter(); new_out_filter = new SimpleCopyThroughFilter(); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: new_in_filter = new String2DateTimeFilter(); new_out_filter = new DateTime2StringFilter(); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: new_in_filter = new String2MonthFilter(); new_out_filter = new DateTime2StringFilter(); static_cast(new_out_filter)->setFormat("MMMM"); DEBUG(" Month out_filter format: " << STDSTRING(static_cast(new_out_filter)->format())); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: new_in_filter = new String2DayOfWeekFilter(); new_out_filter = new DateTime2StringFilter(); static_cast(new_out_filter)->setFormat("dddd"); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } // switch(mode) m_column_mode = mode; //new_in_filter->setName("InputFilter"); //new_out_filter->setName("OutputFilter"); m_input_filter = new_in_filter; m_output_filter = new_out_filter; m_input_filter->input(0, m_owner->m_string_io); m_output_filter->input(0, m_owner); m_input_filter->setHidden(true); m_output_filter->setHidden(true); if (temp_col) { // if temp_col == 0, only the input/output filters need to be changed // copy the filtered, i.e. converted, column (mode is orig mode) DEBUG(" temp_col column mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, temp_col->columnMode())); filter->input(0, temp_col); DEBUG(" filter->output size = " << filter->output(0)->rowCount()); copy(filter->output(0)); delete temp_col; } if (filter_is_temporary) delete filter; emit m_owner->modeChanged(m_owner); DEBUG("ColumnPrivate::setColumnMode() DONE"); } /** * \brief Replace all mode related members * * Replace column mode, data type, data pointer and filters directly */ void ColumnPrivate::replaceModeData(AbstractColumn::ColumnMode mode, void* data, AbstractSimpleFilter* in_filter, AbstractSimpleFilter* out_filter) { DEBUG("ColumnPrivate::replaceModeData()"); emit m_owner->modeAboutToChange(m_owner); // disconnect formatChanged() switch (m_column_mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: disconnect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: disconnect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } m_column_mode = mode; m_data = data; //in_filter->setName("InputFilter"); //out_filter->setName("OutputFilter"); m_input_filter = in_filter; m_output_filter = out_filter; m_input_filter->input(0, m_owner->m_string_io); m_output_filter->input(0, m_owner); // connect formatChanged() switch (m_column_mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: connect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: connect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: connect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } emit m_owner->modeChanged(m_owner); } /** * \brief Replace data pointer */ void ColumnPrivate::replaceData(void* data) { DEBUG("ColumnPrivate::replaceData()"); emit m_owner->dataAboutToChange(m_owner); m_data = data; invalidate(); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool ColumnPrivate::copy(const AbstractColumn* other) { // DEBUG("ColumnPrivate::copy(other)"); if (other->columnMode() != columnMode()) return false; // DEBUG(" mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, columnMode())); int num_rows = other->rowCount(); // DEBUG(" rows " << num_rows); emit m_owner->dataAboutToChange(m_owner); resizeTo(num_rows); // copy the data switch (m_column_mode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->bigIntAt(i); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { auto* vec = static_cast*>(m_data); for (int i = 0; i < num_rows; ++i) vec->replace(i, other->textAt(i)); break; } - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: { + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: { auto* vec = static_cast*>(m_data); for (int i = 0; i < num_rows; ++i) vec->replace(i, other->dateTimeAt(i)); break; } } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool ColumnPrivate::copy(const AbstractColumn* source, int source_start, int dest_start, int num_rows) { // DEBUG("ColumnPrivate::copy()"); if (source->columnMode() != m_column_mode) return false; if (num_rows == 0) return true; emit m_owner->dataAboutToChange(m_owner); if (dest_start + num_rows > rowCount()) resizeTo(dest_start + num_rows); // copy the data switch (m_column_mode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->valueAt(source_start + i); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->integerAt(source_start + i); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->bigIntAt(source_start + i); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int i = 0; i < num_rows; i++) static_cast*>(m_data)->replace(dest_start+i, source->textAt(source_start + i)); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: for (int i = 0; i < num_rows; i++) static_cast*>(m_data)->replace(dest_start+i, source->dateTimeAt(source_start + i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool ColumnPrivate::copy(const ColumnPrivate* other) { if (other->columnMode() != m_column_mode) return false; int num_rows = other->rowCount(); emit m_owner->dataAboutToChange(m_owner); resizeTo(num_rows); // copy the data switch (m_column_mode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->bigIntAt(i); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->textAt(i)); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->dateTimeAt(i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool ColumnPrivate::copy(const ColumnPrivate* source, int source_start, int dest_start, int num_rows) { if (source->columnMode() != m_column_mode) return false; if (num_rows == 0) return true; emit m_owner->dataAboutToChange(m_owner); if (dest_start + num_rows > rowCount()) resizeTo(dest_start + num_rows); // copy the data switch (m_column_mode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->valueAt(source_start + i); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->integerAt(source_start + i); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->bigIntAt(source_start + i); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(dest_start+i, source->textAt(source_start + i)); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: for (int i = 0; i *>(m_data)->replace(dest_start+i, source->dateTimeAt(source_start + i)); break; } invalidate(); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Return the data vector size * * This returns the size of the column container */ int ColumnPrivate::rowCount() const { switch (m_column_mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: return static_cast*>(m_data)->size(); - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: return static_cast*>(m_data)->size(); - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: return static_cast*>(m_data)->size(); - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: return static_cast*>(m_data)->size(); - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: return static_cast*>(m_data)->size(); } return 0; } /** * \brief Return the number of available rows * * This returns the number of rows that actually contain data. * Rows beyond this can be masked etc. but should be ignored by filters, * plots etc. */ int ColumnPrivate::availableRowCount() const { int availableRowCount = 0; for (int row = 0; row < rowCount(); row++) { if (m_owner->isValid(row) && !m_owner->isMasked(row)) availableRowCount++; } return availableRowCount; } /** * \brief Resize the vector to the specified number of rows * * Since selecting and masking rows higher than the * real internal number of rows is supported, this * does not change the interval attributes. Also * no signal is emitted. If the new rows are filled * with values AbstractColumn::dataChanged() * must be emitted. */ void ColumnPrivate::resizeTo(int new_size) { int old_size = rowCount(); if (new_size == old_size) return; // DEBUG("ColumnPrivate::resizeTo() " << old_size << " -> " << new_size); switch (m_column_mode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, NAN); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, 0); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, 0); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { int new_rows = new_size - old_size; if (new_rows > 0) { for (int i = 0; i < new_rows; ++i) static_cast*>(m_data)->append(QString()); } else { for (int i = 0; i < -new_rows; ++i) static_cast*>(m_data)->removeLast(); } break; } - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: { + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: { int new_rows = new_size - old_size; if (new_rows > 0) { for (int i = 0; i < new_rows; ++i) static_cast*>(m_data)->append(QDateTime()); } else { for (int i = 0; i < -new_rows; ++i) static_cast*>(m_data)->removeLast(); } break; } } } /** * \brief Insert some empty (or initialized with zero) rows */ void ColumnPrivate::insertRows(int before, int count) { if (count == 0) return; m_formulas.insertRows(before, count); if (before <= rowCount()) { switch (m_column_mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: static_cast*>(m_data)->insert(before, count, NAN); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: static_cast*>(m_data)->insert(before, count, 0); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: static_cast*>(m_data)->insert(before, count, 0); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: for (int i = 0; i < count; ++i) static_cast*>(m_data)->insert(before, QDateTime()); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int i = 0; i < count; ++i) static_cast*>(m_data)->insert(before, QString()); break; } } } /** * \brief Remove 'count' rows starting from row 'first' */ void ColumnPrivate::removeRows(int first, int count) { if (count == 0) return; m_formulas.removeRows(first, count); if (first < rowCount()) { int corrected_count = count; if (first + count > rowCount()) corrected_count = rowCount() - first; switch (m_column_mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: static_cast*>(m_data)->remove(first, corrected_count); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: static_cast*>(m_data)->remove(first, corrected_count); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: static_cast*>(m_data)->remove(first, corrected_count); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: for (int i = 0; i < corrected_count; ++i) static_cast*>(m_data)->removeAt(first); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int i = 0; i < corrected_count; ++i) static_cast*>(m_data)->removeAt(first); break; } } } //! Return the column name QString ColumnPrivate::name() const { return m_owner->name(); } /** * \brief Return the column plot designation */ AbstractColumn::PlotDesignation ColumnPrivate::plotDesignation() const { return m_plot_designation; } /** * \brief Set the column plot designation */ void ColumnPrivate::setPlotDesignation(AbstractColumn::PlotDesignation pd) { emit m_owner->plotDesignationAboutToChange(m_owner); m_plot_designation = pd; emit m_owner->plotDesignationChanged(m_owner); } /** * \brief Get width */ int ColumnPrivate::width() const { return m_width; } /** * \brief Set width */ void ColumnPrivate::setWidth(int value) { m_width = value; } /** * \brief Return the data pointer */ void* ColumnPrivate::data() const { return m_data; } /** * \brief Return the input filter (for string -> data type conversion) */ AbstractSimpleFilter *ColumnPrivate::inputFilter() const { return m_input_filter; } /** * \brief Return the output filter (for data type -> string conversion) */ AbstractSimpleFilter *ColumnPrivate::outputFilter() const { return m_output_filter; } //////////////////////////////////////////////////////////////////////////////// //! \name Formula related functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the formula last used to generate data for the column */ QString ColumnPrivate::formula() const { return m_formula; } bool ColumnPrivate::formulaAutoUpdate() const { return m_formulaAutoUpdate; } /** * \brief Sets the formula used to generate column values */ void ColumnPrivate::setFormula(const QString& formula, const QStringList& variableNames, const QVector& variableColumns, bool autoUpdate) { m_formula = formula; m_formulaVariableNames = variableNames; m_formulaVariableColumns = variableColumns; m_formulaAutoUpdate = autoUpdate; for (auto connection: m_connectionsUpdateFormula) disconnect(connection); m_formulaVariableColumnPaths.clear(); for (auto column : variableColumns) { m_formulaVariableColumnPaths << column->path(); if (autoUpdate) connectFormulaColumn(column); } } /*! * called after the import of the project was done and all columns were loaded in \sa Project::load() * to establish the required slot-signal connections for the formula update */ void ColumnPrivate::finalizeLoad() { if (m_formulaAutoUpdate) { for (auto column : m_formulaVariableColumns) connectFormulaColumn(column); } } /*! * \brief ColumnPrivate::connectFormulaColumn * This function is used to connect the columns to the needed slots for updating formulas * \param column */ void ColumnPrivate::connectFormulaColumn(const AbstractColumn* column) { if (!column) return; m_connectionsUpdateFormula << connect(column, &Column::dataChanged, m_owner, &Column::updateFormula); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &ColumnPrivate::formulaVariableColumnRemoved); connect(column, &AbstractColumn::reset, this, &ColumnPrivate::formulaVariableColumnRemoved); connect(column->parentAspect(), &AbstractAspect::aspectAdded, this, &ColumnPrivate::formulaVariableColumnAdded); } /*! * helper function used in \c Column::load() to set parameters read from the xml file. * \param variableColumnPaths is used to restore the pointers to columns from pathes * after the project was loaded in Project::load(). */ void ColumnPrivate::setFormula(const QString& formula, const QStringList& variableNames, const QStringList& variableColumnPaths, bool autoUpdate) { m_formula = formula; m_formulaVariableNames = variableNames; m_formulaVariableColumnPaths = variableColumnPaths; m_formulaVariableColumns.resize(variableColumnPaths.length()); m_formulaAutoUpdate = autoUpdate; } const QStringList& ColumnPrivate::formulaVariableNames() const { return m_formulaVariableNames; } const QVector& ColumnPrivate::formulaVariableColumns() const { return m_formulaVariableColumns; } const QStringList& ColumnPrivate::formulaVariableColumnPaths() const { return m_formulaVariableColumnPaths; } void ColumnPrivate::setformulVariableColumnsPath(int index, const QString& path) { m_formulaVariableColumnPaths[index] = path; } void ColumnPrivate::setformulVariableColumn(int index, Column* column) { if (m_formulaVariableColumns[index]) // if there exists already a valid column, disconnect it first disconnect(m_formulaVariableColumns[index], nullptr, this, nullptr); m_formulaVariableColumns[index] = column; connectFormulaColumn(column); } /*! * \sa FunctionValuesDialog::generate() */ void ColumnPrivate::updateFormula() { //determine variable names and the data vectors of the specified columns QVector*> xVectors; QVector*> xNewVectors; int maxRowCount = 0; bool valid = true; for (auto column : m_formulaVariableColumns) { if (!column) { valid = false; break; } - if (column->columnMode() == AbstractColumn::Integer || column->columnMode() == AbstractColumn::BigInt) { + if (column->columnMode() == AbstractColumn::ColumnMode::Integer || column->columnMode() == AbstractColumn::ColumnMode::BigInt) { //convert integers to doubles first auto* xVector = new QVector(column->rowCount()); for (int i = 0; irowCount(); ++i) xVector->operator[](i) = column->valueAt(i); xNewVectors << xVector; xVectors << xVector; } else xVectors << static_cast* >(column->data()); if (column->rowCount() > maxRowCount) maxRowCount = column->rowCount(); } if (valid) { //resize the spreadsheet if one of the data vectors from //other spreadsheet(s) has more elements than the parent spreadsheet Spreadsheet* spreadsheet = static_cast(m_owner->parentAspect()); if (spreadsheet->rowCount() < maxRowCount) spreadsheet->setRowCount(maxRowCount); //create new vector for storing the calculated values //the vectors with the variable data can be smaller then the result vector. So, not all values in the result vector might get initialized. //->"clean" the result vector first QVector new_data(rowCount(), NAN); //evaluate the expression for f(x_1, x_2, ...) and write the calculated values into a new vector. ExpressionParser* parser = ExpressionParser::getInstance(); parser->evaluateCartesian(m_formula, m_formulaVariableNames, xVectors, &new_data); replaceValues(0, new_data); // initialize remaining rows with NAN int remainingRows = rowCount() - maxRowCount; if (remainingRows > 0) { QVector emptyRows(remainingRows, NAN); replaceValues(maxRowCount, emptyRows); } } else { QVector new_data(rowCount(), NAN); replaceValues(0, new_data); } //delete help vectors created for the conversion from int to double for (auto* vector : xNewVectors) delete vector; } void ColumnPrivate::formulaVariableColumnRemoved(const AbstractAspect* aspect) { const Column* column = dynamic_cast(aspect); disconnect(column, nullptr, this, nullptr); //TODO: why is const_cast required here?!? int index = m_formulaVariableColumns.indexOf(const_cast(column)); if (index != -1) { m_formulaVariableColumns[index] = nullptr; updateFormula(); } } void ColumnPrivate::formulaVariableColumnAdded(const AbstractAspect* aspect) { int index = m_formulaVariableColumnPaths.indexOf(aspect->path()); if (index != -1) { const Column* column = dynamic_cast(aspect); m_formulaVariableColumns[index] = const_cast(column); updateFormula(); } } /** * \brief Return the formula associated with row 'row' */ QString ColumnPrivate::formula(int row) const { return m_formulas.value(row); } /** * \brief Return the intervals that have associated formulas * * This can be used to make a list of formulas with their intervals. * Here is some example code: * * \code * QStringList list; * QVector< Interval > intervals = my_column.formulaIntervals(); * foreach(Interval interval, intervals) * list << QString(interval.toString() + ": " + my_column.formula(interval.start())); * \endcode */ QVector< Interval > ColumnPrivate::formulaIntervals() const { return m_formulas.intervals(); } /** * \brief Set a formula string for an interval of rows */ void ColumnPrivate::setFormula(const Interval& i, const QString& formula) { m_formulas.setValue(i, formula); } /** * \brief Overloaded function for convenience */ void ColumnPrivate::setFormula(int row, const QString& formula) { setFormula(Interval(row,row), formula); } /** * \brief Clear all formulas */ void ColumnPrivate::clearFormulas() { m_formulas.clear(); } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //! \name type specific functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the content of row 'row'. * * Use this only when columnMode() is Text */ QString ColumnPrivate::textAt(int row) const { - if (m_column_mode != AbstractColumn::Text) return QString(); + if (m_column_mode != AbstractColumn::ColumnMode::Text) return QString(); return static_cast*>(m_data)->value(row); } /** * \brief Return the date part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDate ColumnPrivate::dateAt(int row) const { - if (m_column_mode != AbstractColumn::DateTime && - m_column_mode != AbstractColumn::Month && - m_column_mode != AbstractColumn::Day) + if (m_column_mode != AbstractColumn::ColumnMode::DateTime && + m_column_mode != AbstractColumn::ColumnMode::Month && + m_column_mode != AbstractColumn::ColumnMode::Day) return QDate{}; return dateTimeAt(row).date(); } /** * \brief Return the time part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QTime ColumnPrivate::timeAt(int row) const { - if (m_column_mode != AbstractColumn::DateTime && - m_column_mode != AbstractColumn::Month && - m_column_mode != AbstractColumn::Day) + if (m_column_mode != AbstractColumn::ColumnMode::DateTime && + m_column_mode != AbstractColumn::ColumnMode::Month && + m_column_mode != AbstractColumn::ColumnMode::Day) return QTime{}; return dateTimeAt(row).time(); } /** * \brief Return the QDateTime in row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDateTime ColumnPrivate::dateTimeAt(int row) const { - if (m_column_mode != AbstractColumn::DateTime && - m_column_mode != AbstractColumn::Month && - m_column_mode != AbstractColumn::Day) + if (m_column_mode != AbstractColumn::ColumnMode::DateTime && + m_column_mode != AbstractColumn::ColumnMode::Month && + m_column_mode != AbstractColumn::ColumnMode::Day) return QDateTime(); return static_cast*>(m_data)->value(row); } /** * \brief Return the double value in row 'row' for columns with type Numeric, Integer or BigInt. * This function has to be used everywhere where the exact type (double, int or qint64) is not relevant for numerical calculations. * For cases where the integer value is needed without any implicit conversions, \sa integerAt() has to be used. */ double ColumnPrivate::valueAt(int row) const { - if (m_column_mode == AbstractColumn::Numeric) + if (m_column_mode == AbstractColumn::ColumnMode::Numeric) return static_cast*>(m_data)->value(row, NAN); - else if (m_column_mode == AbstractColumn::Integer) + else if (m_column_mode == AbstractColumn::ColumnMode::Integer) return static_cast*>(m_data)->value(row, 0); - else if (m_column_mode == AbstractColumn::BigInt) + else if (m_column_mode == AbstractColumn::ColumnMode::BigInt) return static_cast*>(m_data)->value(row, 0); else return NAN; } /** * \brief Return the int value in row 'row' */ int ColumnPrivate::integerAt(int row) const { - if (m_column_mode != AbstractColumn::Integer) return 0; + if (m_column_mode != AbstractColumn::ColumnMode::Integer) return 0; return static_cast*>(m_data)->value(row, 0); } /** * \brief Return the bigint value in row 'row' */ qint64 ColumnPrivate::bigIntAt(int row) const { - if (m_column_mode != AbstractColumn::BigInt) return 0; + if (m_column_mode != AbstractColumn::ColumnMode::BigInt) return 0; return static_cast*>(m_data)->value(row, 0); } void ColumnPrivate::invalidate() { statisticsAvailable = false; hasValuesAvailable = false; propertiesAvailable = false; } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Text */ void ColumnPrivate::setTextAt(int row, const QString& new_value) { - if (m_column_mode != AbstractColumn::Text) return; + if (m_column_mode != AbstractColumn::ColumnMode::Text) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row + 1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Text */ void ColumnPrivate::replaceTexts(int first, const QVector& new_values) { - if (m_column_mode != AbstractColumn::Text) return; + if (m_column_mode != AbstractColumn::ColumnMode::Text) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(first+i, new_values.at(i)); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setDateAt(int row, QDate new_value) { - if (m_column_mode != AbstractColumn::DateTime && - m_column_mode != AbstractColumn::Month && - m_column_mode != AbstractColumn::Day) + if (m_column_mode != AbstractColumn::ColumnMode::DateTime && + m_column_mode != AbstractColumn::ColumnMode::Month && + m_column_mode != AbstractColumn::ColumnMode::Day) return; setDateTimeAt(row, QDateTime(new_value, timeAt(row))); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setTimeAt(int row, QTime new_value) { - if (m_column_mode != AbstractColumn::DateTime && - m_column_mode != AbstractColumn::Month && - m_column_mode != AbstractColumn::Day) + if (m_column_mode != AbstractColumn::ColumnMode::DateTime && + m_column_mode != AbstractColumn::ColumnMode::Month && + m_column_mode != AbstractColumn::ColumnMode::Day) return; setDateTimeAt(row, QDateTime(dateAt(row), new_value)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setDateTimeAt(int row, const QDateTime& new_value) { - if (m_column_mode != AbstractColumn::DateTime && - m_column_mode != AbstractColumn::Month && - m_column_mode != AbstractColumn::Day) + if (m_column_mode != AbstractColumn::ColumnMode::DateTime && + m_column_mode != AbstractColumn::ColumnMode::Month && + m_column_mode != AbstractColumn::ColumnMode::Day) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast< QVector* >(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::replaceDateTimes(int first, const QVector& new_values) { - if (m_column_mode != AbstractColumn::DateTime && - m_column_mode != AbstractColumn::Month && - m_column_mode != AbstractColumn::Day) + if (m_column_mode != AbstractColumn::ColumnMode::DateTime && + m_column_mode != AbstractColumn::ColumnMode::Month && + m_column_mode != AbstractColumn::ColumnMode::Day) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(first+i, new_values.at(i)); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Numeric */ void ColumnPrivate::setValueAt(int row, double new_value) { // DEBUG("ColumnPrivate::setValueAt()"); - if (m_column_mode != AbstractColumn::Numeric) return; + if (m_column_mode != AbstractColumn::ColumnMode::Numeric) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Numeric */ void ColumnPrivate::replaceValues(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceValues()"); - if (m_column_mode != AbstractColumn::Numeric) return; + if (m_column_mode != AbstractColumn::ColumnMode::Numeric) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Integer */ void ColumnPrivate::setIntegerAt(int row, int new_value) { DEBUG("ColumnPrivate::setIntegerAt()"); - if (m_column_mode != AbstractColumn::Integer) return; + if (m_column_mode != AbstractColumn::ColumnMode::Integer) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Integer */ void ColumnPrivate::replaceInteger(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceInteger()"); - if (m_column_mode != AbstractColumn::Integer) return; + if (m_column_mode != AbstractColumn::ColumnMode::Integer) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is BigInt */ void ColumnPrivate::setBigIntAt(int row, qint64 new_value) { DEBUG("ColumnPrivate::setBigIntAt()"); - if (m_column_mode != AbstractColumn::BigInt) return; + if (m_column_mode != AbstractColumn::ColumnMode::BigInt) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is BigInt */ void ColumnPrivate::replaceBigInt(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceBigInt()"); - if (m_column_mode != AbstractColumn::BigInt) return; + if (m_column_mode != AbstractColumn::ColumnMode::BigInt) return; invalidate(); emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /*! * Updates the properties. Will be called, when data in the column changed. * The properties will be used to speed up some algorithms. * See where variable properties will be used. */ void ColumnPrivate::updateProperties() { // TODO: for double Properties::Constant will never be used. Use an epsilon (difference smaller than epsilon is zero) if (rowCount() == 0) { properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } double prevValue = NAN; int prevValueInt = 0; qint64 prevValueBigInt = 0; qint64 prevValueDatetime = 0; - if (m_column_mode == AbstractColumn::Integer) + if (m_column_mode == AbstractColumn::ColumnMode::Integer) prevValueInt = integerAt(0); - else if (m_column_mode == AbstractColumn::BigInt) + else if (m_column_mode == AbstractColumn::ColumnMode::BigInt) prevValueBigInt = bigIntAt(0); - else if (m_column_mode == AbstractColumn::Numeric) + else if (m_column_mode == AbstractColumn::ColumnMode::Numeric) prevValue = valueAt(0); - else if (m_column_mode == AbstractColumn::DateTime || - m_column_mode == AbstractColumn::Month || - m_column_mode == AbstractColumn::Day) + else if (m_column_mode == AbstractColumn::ColumnMode::DateTime || + m_column_mode == AbstractColumn::ColumnMode::Month || + m_column_mode == AbstractColumn::ColumnMode::Day) prevValueDatetime = dateTimeAt(0).toMSecsSinceEpoch(); else { properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } int monotonic_decreasing = -1; int monotonic_increasing = -1; double value; int valueInt; qint64 valueBigInt; qint64 valueDateTime; for (int row = 1; row < rowCount(); row++) { if (!m_owner->isValid(row) || m_owner->isMasked(row)) { // if there is one invalid or masked value, the property is No, because // otherwise it's difficult to find the correct index in indexForValue(). // You don't know if you should increase the index or decrease it when // you hit an invalid value properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } - if (m_column_mode == AbstractColumn::Integer) { + if (m_column_mode == AbstractColumn::ColumnMode::Integer) { valueInt = integerAt(row); if (valueInt > prevValueInt) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (valueInt < prevValueInt) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValueInt = valueInt; - } else if (m_column_mode == AbstractColumn::BigInt) { + } else if (m_column_mode == AbstractColumn::ColumnMode::BigInt) { valueBigInt = bigIntAt(row); if (valueBigInt > prevValueBigInt) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (valueBigInt < prevValueBigInt) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValueBigInt = valueBigInt; - } else if (m_column_mode == AbstractColumn::Numeric) { + } else if (m_column_mode == AbstractColumn::ColumnMode::Numeric) { value = valueAt(row); if (std::isnan(value)) { monotonic_increasing = 0; monotonic_decreasing = 0; break; } if (value > prevValue) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (value < prevValue) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValue = value; - } else if (m_column_mode == AbstractColumn::DateTime || - m_column_mode == AbstractColumn::Month || - m_column_mode == AbstractColumn::Day) { + } else if (m_column_mode == AbstractColumn::ColumnMode::DateTime || + m_column_mode == AbstractColumn::ColumnMode::Month || + m_column_mode == AbstractColumn::ColumnMode::Day) { valueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); if (valueDateTime > prevValueDatetime) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (valueDateTime < prevValueDatetime) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValueDatetime = valueDateTime; } } properties = AbstractColumn::Properties::No; if (monotonic_increasing > 0 && monotonic_decreasing > 0) properties = AbstractColumn::Properties::Constant; else if (monotonic_decreasing > 0) properties = AbstractColumn::Properties::MonotonicDecreasing; else if (monotonic_increasing > 0) properties = AbstractColumn::Properties::MonotonicIncreasing; propertiesAvailable = true; } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the interval attribute representing the formula strings */ IntervalAttribute ColumnPrivate::formulaAttribute() const { return m_formulas; } /** * \brief Replace the interval attribute for the formula strings */ void ColumnPrivate::replaceFormulas(const IntervalAttribute& formulas) { m_formulas = formulas; } diff --git a/src/backend/core/column/ColumnStringIO.cpp b/src/backend/core/column/ColumnStringIO.cpp index 741de2038..738da561c 100644 --- a/src/backend/core/column/ColumnStringIO.cpp +++ b/src/backend/core/column/ColumnStringIO.cpp @@ -1,104 +1,104 @@ /*************************************************************************** File : ColumnStringIO.cpp Project : LabPlot Description : String-IO interface of Column. -------------------------------------------------------------------- Copyright : (C) 2007-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2013-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/core/column/Column.h" #include "backend/core/column/ColumnPrivate.h" #include "backend/core/column/ColumnStringIO.h" /** * \class ColumnStringIO * \brief String-IO interface of Column. */ ColumnStringIO::ColumnStringIO(Column* owner) : AbstractColumn(QString(), AspectType::ColumnStringIO), m_owner(owner) { } AbstractColumn::ColumnMode ColumnStringIO::columnMode() const { - return AbstractColumn::Text; + return AbstractColumn::ColumnMode::Text; } AbstractColumn::PlotDesignation ColumnStringIO::plotDesignation() const { return m_owner->plotDesignation(); } QString ColumnStringIO::plotDesignationString() const { return m_owner->plotDesignationString(); } int ColumnStringIO::rowCount() const { return m_owner->rowCount(); } int ColumnStringIO::availableRowCount() const { return m_owner->availableRowCount(); } bool ColumnStringIO::isValid(int row) const { if (m_setting) return true; return m_owner->isValid(row); } void ColumnStringIO::setTextAt(int row, const QString &value) { m_setting = true; m_to_set = value; m_owner->copy(m_owner->d->inputFilter()->output(0), 0, row, 1); m_setting = false; m_to_set.clear(); m_owner->setChanged(); } QString ColumnStringIO::textAt(int row) const { if (m_setting) return m_to_set; else return m_owner->d->outputFilter()->output(0)->textAt(row); } bool ColumnStringIO::copy(const AbstractColumn *other) { - if (other->columnMode() != AbstractColumn::Text) return false; + if (other->columnMode() != AbstractColumn::ColumnMode::Text) return false; m_owner->d->inputFilter()->input(0,other); m_owner->copy(m_owner->d->inputFilter()->output(0)); m_owner->d->inputFilter()->input(0,this); return true; } bool ColumnStringIO::copy(const AbstractColumn *source, int source_start, int dest_start, int num_rows) { - if (source->columnMode() != AbstractColumn::Text) return false; + if (source->columnMode() != AbstractColumn::ColumnMode::Text) return false; m_owner->d->inputFilter()->input(0,source); m_owner->copy(m_owner->d->inputFilter()->output(0), source_start, dest_start, num_rows); m_owner->d->inputFilter()->input(0,this); return true; } void ColumnStringIO::replaceTexts(int start_row, const QVector& texts) { Column tmp("tmp", texts); copy(&tmp, 0, start_row, texts.size()); } diff --git a/src/backend/core/column/columncommands.cpp b/src/backend/core/column/columncommands.cpp index a96162620..41cb285bf 100644 --- a/src/backend/core/column/columncommands.cpp +++ b/src/backend/core/column/columncommands.cpp @@ -1,1276 +1,1276 @@ /*************************************************************************** File : columncommands.cpp Project : AbstractColumn Description : Commands to be called by Column to modify ColumnPrivate -------------------------------------------------------------------- Copyright : (C) 2007,2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2010 by Knut Franke (knut.franke@gmx.de) Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-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 "columncommands.h" #include "ColumnPrivate.h" #include /** *************************************************************************** * \class ColumnSetModeCmd * \brief Set the column mode ** ***************************************************************************/ /** * \var ColumnSetModeCmd::m_col * \brief The private column data to modify */ /** * \var ColumnSetModeCmd::m_old_mode * \brief The previous mode */ /** * \var ColumnSetModeCmd::m_mode * \brief The new mode */ /** * \var ColumnSetModeCmd::m_old_data * \brief Pointer to old data */ /** * \var ColumnSetModeCmd::m_new_data * \brief Pointer to new data */ /** * \var ColumnSetModeCmd::m_new_in_filter * \brief The new input filter */ /** * \var ColumnSetModeCmd::m_new_out_filter * \brief The new output filter */ /** * \var ColumnSetModeCmd::m_old_in_filter * \brief The old input filter */ /** * \var ColumnSetModeCmd::m_old_out_filter * \brief The old output filter */ /** * \var ColumnSetModeCmd::m_undone * \brief Flag indicating whether this command has been undone (and not redone). */ /** * \var ColumnSetModeCmd::m_executed * \brief Flag indicating whether the command has been executed at least once. */ /** * \brief Ctor */ ColumnSetModeCmd::ColumnSetModeCmd(ColumnPrivate* col, AbstractColumn::ColumnMode mode, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_mode(mode) { setText(i18n("%1: change column type", col->name())); } /** * \brief Dtor */ ColumnSetModeCmd::~ColumnSetModeCmd() { if (m_undone) { if (m_new_data != m_old_data) switch (m_mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: delete static_cast*>(m_new_data); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: delete static_cast*>(m_new_data); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: delete static_cast*>(m_new_data); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: delete static_cast*>(m_new_data); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: delete static_cast*>(m_new_data); break; } } else { if (m_new_data != m_old_data) switch (m_old_mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: delete static_cast*>(m_old_data); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: delete static_cast*>(m_old_data); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: delete static_cast*>(m_old_data); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: delete static_cast*>(m_old_data); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: delete static_cast*>(m_old_data); break; } } } /** * \brief Execute the command */ void ColumnSetModeCmd::redo() { if (!m_executed) { // save old values m_old_mode = m_col->columnMode(); m_old_data = m_col->data(); m_old_in_filter = m_col->inputFilter(); m_old_out_filter = m_col->outputFilter(); // do the conversion m_col->setColumnMode(m_mode); // save new values m_new_data = m_col->data(); m_new_in_filter = m_col->inputFilter(); m_new_out_filter = m_col->outputFilter(); m_executed = true; } else { // set to saved new values m_col->replaceModeData(m_mode, m_new_data, m_new_in_filter, m_new_out_filter); } m_undone = false; } /** * \brief Undo the command */ void ColumnSetModeCmd::undo() { // reset to old values m_col->replaceModeData(m_old_mode, m_old_data, m_old_in_filter, m_old_out_filter); m_undone = true; } /** *************************************************************************** * \class ColumnFullCopyCmd * \brief Copy a complete column ** ***************************************************************************/ /** * \var ColumnFullCopyCmd::m_col * \brief The private column data to modify */ /** * \var ColumnFullCopyCmd::m_src * \brief The column to copy */ /** * \var ColumnFullCopyCmd::m_backup * \brief A backup column */ /** * \var ColumnFullCopyCmd::m_backup_owner * \brief A dummy owner for the backup column * * This is needed because a ColumnPrivate must have an owner. We want access * to the ColumnPrivate object to access its data pointer for fast data * replacement without too much copying. */ /** * \brief Ctor */ ColumnFullCopyCmd::ColumnFullCopyCmd(ColumnPrivate* col, const AbstractColumn* src, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_src(src) { setText(i18n("%1: change cell values", col->name())); } /** * \brief Dtor */ ColumnFullCopyCmd::~ColumnFullCopyCmd() { delete m_backup; delete m_backup_owner; } /** * \brief Execute the command */ void ColumnFullCopyCmd::redo() { if (m_backup == nullptr) { m_backup_owner = new Column("temp", m_src->columnMode()); m_backup = new ColumnPrivate(m_backup_owner, m_src->columnMode()); m_backup->copy(m_col); m_col->copy(m_src); } else { // swap data of orig. column and backup void* data_temp = m_col->data(); m_col->replaceData(m_backup->data()); m_backup->replaceData(data_temp); } } /** * \brief Undo the command */ void ColumnFullCopyCmd::undo() { // swap data of orig. column and backup void* data_temp = m_col->data(); m_col->replaceData(m_backup->data()); m_backup->replaceData(data_temp); } /** *************************************************************************** * \class ColumnPartialCopyCmd * \brief Copy parts of a column ** ***************************************************************************/ /** * \var ColumnPartialCopyCmd::m_col * \brief The private column data to modify */ /** * \var ColumnPartialCopyCmd::m_src * \brief The column to copy */ /** * \var ColumnPartialCopyCmd::m_col_backup * \brief A backup of the original column */ /** * \var ColumnPartialCopyCmd::m_src_backup * \brief A backup of the source column */ /** * \var ColumnPartialCopyCmd::m_col_backup_owner * \brief A dummy owner for the backup column * * This is needed because a ColumnPrivate must have an owner and * we must have a ColumnPrivate object as backup. * Using a Column object as backup would lead to an infinite loop. */ /** * \var ColumnPartialCopyCmd::m_src_backup_owner * \brief A dummy owner for the source backup column * * This is needed because a ColumnPrivate must have an owner and * we must have a ColumnPrivate object as backup. * Using a Column object as backup would lead to an infinite loop. */ /** * \var ColumnPartialCopyCmd::m_src_start * \brief Start index in source column */ /** * \var ColumnPartialCopyCmd::m_dest_start * \brief Start index in destination column */ /** * \var ColumnPartialCopyCmd::m_num_rows * \brief Number of rows to copy */ /** * \var ColumnPartialCopyCmd::m_old_row_count * \brief Previous number of rows in the destination column */ /** * \brief Ctor */ ColumnPartialCopyCmd::ColumnPartialCopyCmd(ColumnPrivate* col, const AbstractColumn* src, int src_start, int dest_start, int num_rows, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_src(src), m_src_start(src_start), m_dest_start(dest_start), m_num_rows(num_rows) { setText(i18n("%1: change cell values", col->name())); } /** * \brief Dtor */ ColumnPartialCopyCmd::~ColumnPartialCopyCmd() { delete m_src_backup; delete m_col_backup; delete m_src_backup_owner; delete m_col_backup_owner; } /** * \brief Execute the command */ void ColumnPartialCopyCmd::redo() { if (m_src_backup == nullptr) { // copy the relevant rows of source and destination column into backup columns m_src_backup_owner = new Column("temp", m_col->columnMode()); m_src_backup = new ColumnPrivate(m_src_backup_owner, m_col->columnMode()); m_src_backup->copy(m_src, m_src_start, 0, m_num_rows); m_col_backup_owner = new Column("temp", m_col->columnMode()); m_col_backup = new ColumnPrivate(m_col_backup_owner, m_col->columnMode()); m_col_backup->copy(m_col, m_dest_start, 0, m_num_rows); m_old_row_count = m_col->rowCount(); } m_col->copy(m_src_backup, 0, m_dest_start, m_num_rows); } /** * \brief Undo the command */ void ColumnPartialCopyCmd::undo() { m_col->copy(m_col_backup, 0, m_dest_start, m_num_rows); m_col->resizeTo(m_old_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnInsertRowsCmd * \brief Insert empty rows ** ***************************************************************************/ /** * \var ColumnInsertRowsCmd::m_col * \brief The private column data to modify */ /** * \brief Ctor */ ColumnInsertRowsCmd::ColumnInsertRowsCmd(ColumnPrivate* col, int before, int count, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_before(before), m_count(count) { } /** * \brief Execute the command */ void ColumnInsertRowsCmd::redo() { m_col->insertRows(m_before, m_count); } /** * \brief Undo the command */ void ColumnInsertRowsCmd::undo() { m_col->removeRows(m_before, m_count); } /** *************************************************************************** * \class ColumnRemoveRowsCmd * \brief Remove consecutive rows from a column ** ***************************************************************************/ /** * \var ColumnRemoveRowsCmd::m_col * \brief The private column data to modify */ /** * \var ColumnRemoveRowsCmd::m_data_row_count * \brief Number of removed rows actually containing data */ /** * \var ColumnRemoveRowsCmd::m_old_size * \brief The number of rows before the removal */ /** * \var ColumnRemoveRowsCmd::m_backup * \brief Column saving the removed rows */ /** * \var ColumnRemoveRowsCmd::m_backup_owner * \brief A dummy owner for the backup column * * This is needed because a ColumnPrivate must have an owner. We want access * to the ColumnPrivate object to access its data pointer for fast data * replacement without too much copying. */ /** * \var ColumnRemoveRowsCmd::m_formulas * \brief Backup of the formula attribute */ /** * \brief Ctor */ ColumnRemoveRowsCmd::ColumnRemoveRowsCmd(ColumnPrivate* col, int first, int count, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_first(first), m_count(count) { } /** * \brief Dtor */ ColumnRemoveRowsCmd::~ColumnRemoveRowsCmd() { delete m_backup; delete m_backup_owner; } /** * \brief Execute the command */ void ColumnRemoveRowsCmd::redo() { if (m_backup == nullptr) { if (m_first >= m_col->rowCount()) m_data_row_count = 0; else if (m_first + m_count > m_col->rowCount()) m_data_row_count = m_col->rowCount() - m_first; else m_data_row_count = m_count; m_old_size = m_col->rowCount(); m_backup_owner = new Column("temp", m_col->columnMode()); m_backup = new ColumnPrivate(m_backup_owner, m_col->columnMode()); m_backup->copy(m_col, m_first, 0, m_data_row_count); m_formulas = m_col->formulaAttribute(); } m_col->removeRows(m_first, m_count); } /** * \brief Undo the command */ void ColumnRemoveRowsCmd::undo() { m_col->insertRows(m_first, m_count); m_col->copy(m_backup, 0, m_first, m_data_row_count); m_col->resizeTo(m_old_size); m_col->replaceFormulas(m_formulas); } /** *************************************************************************** * \class ColumnSetPlotDesignationCmd * \brief Sets a column's plot designation ** ***************************************************************************/ /** * \var ColumnSetPlotDesignationCmd::m_col * \brief The private column data to modify */ /** * \var ColumnSetPlotDesignation::m_new_pd * \brief New plot designation */ /** * \var ColumnSetPlotDesignation::m_old_pd * \brief Old plot designation */ /** * \brief Ctor */ ColumnSetPlotDesignationCmd::ColumnSetPlotDesignationCmd(ColumnPrivate* col, AbstractColumn::PlotDesignation pd, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_new_pd(pd) { setText(i18n("%1: set plot designation", col->name())); } /** * \brief Execute the command */ void ColumnSetPlotDesignationCmd::redo() { m_old_pd = m_col->plotDesignation(); m_col->setPlotDesignation(m_new_pd); } /** * \brief Undo the command */ void ColumnSetPlotDesignationCmd::undo() { m_col->setPlotDesignation(m_old_pd); } /** *************************************************************************** * \class ColumnClearCmd * \brief Clear the column ** ***************************************************************************/ /** * \var ColumnClearCmd::m_col * \brief The private column data to modify */ /** * \var ColumnClearCmd::m_data * \brief Pointer to the old data pointer */ /** * \var ColumnClearCmd::m_empty_data * \brief Pointer to an empty data vector */ /** * \var ColumnClearCmd::m_undone * \brief Status flag */ /** * \brief Ctor */ ColumnClearCmd::ColumnClearCmd(ColumnPrivate* col, QUndoCommand* parent) : QUndoCommand(parent), m_col(col) { setText(i18n("%1: clear column", col->name())); } /** * \brief Dtor */ ColumnClearCmd::~ColumnClearCmd() { if (m_undone) { if (!m_empty_data) return; switch (m_col->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: delete static_cast*>(m_empty_data); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: delete static_cast*>(m_empty_data); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: delete static_cast*>(m_empty_data); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: delete static_cast*>(m_empty_data); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: delete static_cast*>(m_empty_data); break; } } else { if (!m_data) return; switch (m_col->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: delete static_cast*>(m_data); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: delete static_cast*>(m_data); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: delete static_cast*>(m_data); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: delete static_cast*>(m_data); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: delete static_cast*>(m_data); break; } } } /** * \brief Execute the command */ void ColumnClearCmd::redo() { if (!m_empty_data) { const int rowCount = m_col->rowCount(); switch (m_col->columnMode()) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { auto* vec = new QVector(rowCount); m_empty_data = vec; for (int i = 0; i < rowCount; ++i) vec->operator[](i) = NAN; break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { auto* vec = new QVector(rowCount); m_empty_data = vec; for (int i = 0; i < rowCount; ++i) vec->operator[](i) = 0; break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { auto* vec = new QVector(rowCount); m_empty_data = vec; for (int i = 0; i < rowCount; ++i) vec->operator[](i) = 0; break; } - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: m_empty_data = new QVector(); for (int i = 0; i < rowCount; ++i) static_cast< QVector*>(m_empty_data)->append(QDateTime()); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: m_empty_data = new QVector(); for (int i = 0; i < rowCount; ++i) static_cast*>(m_empty_data)->append(QString()); break; } m_data = m_col->data(); } m_col->replaceData(m_empty_data); m_undone = false; } /** * \brief Undo the command */ void ColumnClearCmd::undo() { m_col->replaceData(m_data); m_undone = true; } /** *************************************************************************** * \class ColumSetGlobalFormulaCmd * \brief Set the formula for the entire column (global formula) ** ***************************************************************************/ ColumnSetGlobalFormulaCmd::ColumnSetGlobalFormulaCmd(ColumnPrivate* col, QString formula, QStringList variableNames, QVector variableColumns, bool autoUpdate) : QUndoCommand(), m_col(col), m_newFormula(std::move(formula)), m_newVariableNames(std::move(variableNames)), m_newVariableColumns(std::move(variableColumns)), m_newAutoUpdate(autoUpdate) { setText(i18n("%1: set formula", col->name())); } void ColumnSetGlobalFormulaCmd::redo() { if (!m_copied) { m_formula = m_col->formula(); m_variableNames = m_col->formulaVariableNames(); m_variableColumns = m_col->formulaVariableColumns(); m_autoUpdate = m_col->formulaAutoUpdate(); m_copied = true; } m_col->setFormula(m_newFormula, m_newVariableNames, m_newVariableColumns, m_newAutoUpdate); } void ColumnSetGlobalFormulaCmd::undo() { m_col->setFormula(m_formula, m_variableNames, m_variableColumns, m_newAutoUpdate); } /** *************************************************************************** * \class ColumSetFormulaCmd * \brief Set the formula for a given interval ** ***************************************************************************/ /** * \var ColumnSetFormulaCmd::m_col * \brief The private column data to modify */ /** * \var ColumnSetFormulaCmd::m_interval * \brief The interval */ /** * \var ColumnSetFormulaCmd::m_formula * \brief The new formula */ /** * \var ColumnSetFormulaCmd::m_formulas * \brief Interval attribute backup */ /** * \var ColumnSetFormulaCmd::m_copied * \brief A status flag */ /** * \brief Ctor */ ColumnSetFormulaCmd::ColumnSetFormulaCmd(ColumnPrivate* col, const Interval& interval, QString formula, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_interval(interval), m_newFormula(std::move(formula)) { setText(i18n("%1: set cell formula", col->name())); } void ColumnSetFormulaCmd::redo() { if (!m_copied) { m_formulas = m_col->formulaAttribute(); m_copied = true; } m_col->setFormula(m_interval, m_newFormula); } void ColumnSetFormulaCmd::undo() { m_col->replaceFormulas(m_formulas); } /** *************************************************************************** * \class ColumnClearFormulasCmd * \brief Clear all associated formulas ** ***************************************************************************/ /** * \var ColumClearFormulasCmd::m_col * \brief The private column data to modify */ /** * \var ColumnClearFormulasCmd::m_formulas * \brief The old formulas */ /** * \var ColumnClearFormulasCmd::m_copied * \brief A status flag */ /** * \brief Ctor */ ColumnClearFormulasCmd::ColumnClearFormulasCmd(ColumnPrivate* col, QUndoCommand* parent) : QUndoCommand(parent), m_col(col) { setText(i18n("%1: clear all formulas", col->name())); } /** * \brief Execute the command */ void ColumnClearFormulasCmd::redo() { if (!m_copied) { m_formulas = m_col->formulaAttribute(); m_copied = true; } m_col->clearFormulas(); } /** * \brief Undo the command */ void ColumnClearFormulasCmd::undo() { m_col->replaceFormulas(m_formulas); } /** *************************************************************************** * \class ColumnSetTextCmd * \brief Set the text for a string cell ** ***************************************************************************/ /** * \var ColumnSetTextCmd::m_col * \brief The private column data to modify */ /** * \var ColumnSetTextCmd::m_row * \brief The row to modify */ /** * \var ColumnSetTextCmd::m_new_value * \brief The new value */ /** * \var ColumnSetTextCmd::m_old_value * \brief The old value */ /** * \var ColumnSetTextCmd::m_row_count * \brief The old number of rows */ /** * \brief Ctor */ ColumnSetTextCmd::ColumnSetTextCmd(ColumnPrivate* col, int row, QString new_value, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_row(row), m_new_value(std::move(new_value)) { setText(i18n("%1: set text for row %2", col->name(), row)); } /** * \brief Execute the command */ void ColumnSetTextCmd::redo() { m_old_value = m_col->textAt(m_row); m_row_count = m_col->rowCount(); m_col->setTextAt(m_row, m_new_value); } /** * \brief Undo the command */ void ColumnSetTextCmd::undo() { m_col->setTextAt(m_row, m_old_value); m_col->resizeTo(m_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnSetValueCmd * \brief Set the value for a double cell ** ***************************************************************************/ /** * \var ColumnSetValueCmd::m_col * \brief The private column data to modify */ /** * \var ColumnSetValueCmd::m_row * \brief The row to modify */ /** * \var ColumnSetValueCmd::m_new_value * \brief The new value */ /** * \var ColumnSetValueCmd::m_old_value * \brief The old value */ /** * \var ColumnSetValueCmd::m_row_count * \brief The old number of rows */ /** * \brief Ctor */ ColumnSetValueCmd::ColumnSetValueCmd(ColumnPrivate* col, int row, double new_value, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_row(row), m_new_value(new_value) { setText(i18n("%1: set value for row %2", col->name(), row)); } /** * \brief Execute the command */ void ColumnSetValueCmd::redo() { m_old_value = m_col->valueAt(m_row); m_row_count = m_col->rowCount(); m_col->setValueAt(m_row, m_new_value); } /** * \brief Undo the command */ void ColumnSetValueCmd::undo() { m_col->setValueAt(m_row, m_old_value); m_col->resizeTo(m_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnSetIntegerCmd * \brief Set the value for a int cell ** ***************************************************************************/ ColumnSetIntegerCmd::ColumnSetIntegerCmd(ColumnPrivate* col, int row, int new_value, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_row(row), m_new_value(new_value) { DEBUG("ColumnSetIntegerCmd::ColumnSetIntegerCmd()"); setText(i18n("%1: set value for row %2", col->name(), row)); } /** * \brief Execute the command */ void ColumnSetIntegerCmd::redo() { m_old_value = m_col->integerAt(m_row); m_row_count = m_col->rowCount(); m_col->setIntegerAt(m_row, m_new_value); } /** * \brief Undo the command */ void ColumnSetIntegerCmd::undo() { m_col->setIntegerAt(m_row, m_old_value); m_col->resizeTo(m_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnSetBigIntCmd * \brief Set the value for a bigint cell ** ***************************************************************************/ ColumnSetBigIntCmd::ColumnSetBigIntCmd(ColumnPrivate* col, int row, qint64 new_value, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_row(row), m_new_value(new_value) { DEBUG("ColumnSetIntegerCmd::ColumnSetIntegerCmd()"); setText(i18n("%1: set value for row %2", col->name(), row)); } /** * \brief Execute the command */ void ColumnSetBigIntCmd::redo() { m_old_value = m_col->bigIntAt(m_row); m_row_count = m_col->rowCount(); m_col->setBigIntAt(m_row, m_new_value); } /** * \brief Undo the command */ void ColumnSetBigIntCmd::undo() { m_col->setBigIntAt(m_row, m_old_value); m_col->resizeTo(m_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnSetDataTimeCmd * \brief Set the value of a date-time cell ** ***************************************************************************/ /** * \var ColumnSetDateTimeCmd::m_col * \brief The private column data to modify */ /** * \var ColumnSetDateTimeCmd::m_row * \brief The row to modify */ /** * \var ColumnSetDateTimeCmd::m_new_value * \brief The new value */ /** * \var ColumnSetDateTimeCmd::m_old_value * \brief The old value */ /** * \var ColumnSetDateTimeCmd::m_row_count * \brief The old number of rows */ /** * \brief Ctor */ ColumnSetDateTimeCmd::ColumnSetDateTimeCmd(ColumnPrivate* col, int row, QDateTime new_value, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_row(row), m_new_value(std::move(new_value)) { setText(i18n("%1: set value for row %2", col->name(), row)); } /** * \brief Execute the command */ void ColumnSetDateTimeCmd::redo() { m_old_value = m_col->dateTimeAt(m_row); m_row_count = m_col->rowCount(); m_col->setDateTimeAt(m_row, m_new_value); } /** * \brief Undo the command */ void ColumnSetDateTimeCmd::undo() { m_col->setDateTimeAt(m_row, m_old_value); m_col->resizeTo(m_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnReplaceTextsCmd * \brief Replace a range of strings in a string column ** ***************************************************************************/ /** * \var ColumnReplaceTextsCmd::m_col * \brief The private column data to modify */ /** * \var ColumnReplaceTextsCmd::m_first * \brief The first row to replace */ /** * \var ColumnReplaceTextsCmd::m_new_values * \brief The new values */ /** * \var ColumnReplaceTextsCmd::m_old_values * \brief The old values */ /** * \var ColumnReplaceTextsCmd::m_copied * \brief Status flag */ /** * \var ColumnReplaceTextsCmd::m_row_count * \brief The old number of rows */ /** * \brief Ctor */ ColumnReplaceTextsCmd::ColumnReplaceTextsCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_first(first), m_new_values(new_values) { setText(i18n("%1: replace the texts for rows %2 to %3", col->name(), first, first + new_values.count() - 1)); } /** * \brief Execute the command */ void ColumnReplaceTextsCmd::redo() { if (!m_copied) { m_old_values = static_cast*>(m_col->data())->mid(m_first, m_new_values.count()); m_row_count = m_col->rowCount(); m_copied = true; } m_col->replaceTexts(m_first, m_new_values); } /** * \brief Undo the command */ void ColumnReplaceTextsCmd::undo() { m_col->replaceTexts(m_first, m_old_values); m_col->resizeTo(m_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnReplaceValuesCmd * \brief Replace a range of doubles in a double column ** ***************************************************************************/ /** * \var ColumnReplaceValuesCmd::m_col * \brief The private column data to modify */ /** * \var ColumnReplaceValuesCmd::m_first * \brief The first row to replace */ /** * \var ColumnReplaceValuesCmd::m_new_values * \brief The new values */ /** * \var ColumnReplaceValuesCmd::m_old_values * \brief The old values */ /** * \var ColumnReplaceValuesCmd::m_copied * \brief Status flag */ /** * \var ColumnReplaceValuesCmd::m_row_count * \brief The old number of rows */ /** * \brief Ctor */ ColumnReplaceValuesCmd::ColumnReplaceValuesCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_first(first), m_new_values(new_values) { setText(i18n("%1: replace the values for rows %2 to %3", col->name(), first, first + new_values.count() -1)); } /** * \brief Execute the command */ void ColumnReplaceValuesCmd::redo() { if (!m_copied) { m_old_values = static_cast*>(m_col->data())->mid(m_first, m_new_values.count()); m_row_count = m_col->rowCount(); m_copied = true; } m_col->replaceValues(m_first, m_new_values); } /** * \brief Undo the command */ void ColumnReplaceValuesCmd::undo() { m_col->replaceValues(m_first, m_old_values); m_col->resizeTo(m_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnReplaceIntegerCmd * \brief Replace a range of integers in a int column ** ***************************************************************************/ ColumnReplaceIntegerCmd::ColumnReplaceIntegerCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_first(first), m_new_values(new_values) { setText(i18n("%1: replace the values for rows %2 to %3", col->name(), first, first + new_values.count() - 1)); } /** * \brief Execute the command */ void ColumnReplaceIntegerCmd::redo() { if (!m_copied) { m_old_values = static_cast*>(m_col->data())->mid(m_first, m_new_values.count()); m_row_count = m_col->rowCount(); m_copied = true; } m_col->replaceInteger(m_first, m_new_values); } /** * \brief Undo the command */ void ColumnReplaceIntegerCmd::undo() { m_col->replaceInteger(m_first, m_old_values); m_col->resizeTo(m_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnReplaceBigIntCmd * \brief Replace a range of integers in a int column ** ***************************************************************************/ ColumnReplaceBigIntCmd::ColumnReplaceBigIntCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_first(first), m_new_values(new_values) { setText(i18n("%1: replace the values for rows %2 to %3", col->name(), first, first + new_values.count() - 1)); } /** * \brief Execute the command */ void ColumnReplaceBigIntCmd::redo() { if (!m_copied) { m_old_values = static_cast*>(m_col->data())->mid(m_first, m_new_values.count()); m_row_count = m_col->rowCount(); m_copied = true; } m_col->replaceBigInt(m_first, m_new_values); } /** * \brief Undo the command */ void ColumnReplaceBigIntCmd::undo() { m_col->replaceBigInt(m_first, m_old_values); m_col->resizeTo(m_row_count); m_col->replaceData(m_col->data()); } /** *************************************************************************** * \class ColumnReplaceDateTimesCmd * \brief Replace a range of date-times in a date-time column ** ***************************************************************************/ /** * \var ColumnReplaceDateTimesCmd::m_col * \brief The private column data to modify */ /** * \var ColumnReplaceDateTimesCmd::m_first * \brief The first row to replace */ /** * \var ColumnReplaceDateTimesCmd::m_new_values * \brief The new values */ /** * \var ColumnReplaceDateTimesCmd::m_old_values * \brief The old values */ /** * \var ColumnReplaceDateTimesCmd::m_copied * \brief Status flag */ /** * \var ColumnReplaceDateTimesCmd::m_row_count * \brief The old number of rows */ /** * \brief Ctor */ ColumnReplaceDateTimesCmd::ColumnReplaceDateTimesCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent) : QUndoCommand(parent), m_col(col), m_first(first), m_new_values(new_values) { setText(i18n("%1: replace the values for rows %2 to %3", col->name(), first, first + new_values.count() -1)); } /** * \brief Execute the command */ void ColumnReplaceDateTimesCmd::redo() { if (!m_copied) { m_old_values = static_cast*>(m_col->data())->mid(m_first, m_new_values.count()); m_row_count = m_col->rowCount(); m_copied = true; } m_col->replaceDateTimes(m_first, m_new_values); } /** * \brief Undo the command */ void ColumnReplaceDateTimesCmd::undo() { m_col->replaceDateTimes(m_first, m_old_values); m_col->replaceData(m_col->data()); m_col->resizeTo(m_row_count); } diff --git a/src/backend/core/column/columncommands.h b/src/backend/core/column/columncommands.h index 2070aa132..bbc8b24ba 100644 --- a/src/backend/core/column/columncommands.h +++ b/src/backend/core/column/columncommands.h @@ -1,366 +1,366 @@ /*************************************************************************** File : columncommands.h Project : LabPlot Description : Commands to be called by Column to modify ColumnPrivate -------------------------------------------------------------------- Copyright : (C) 2007,2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2010 by Knut Franke (knut.franke@gmx.de) Copyright : (C) 2009-2017 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 COLUMNCOMMANDS_H #define COLUMNCOMMANDS_H #include "backend/lib/IntervalAttribute.h" #include "backend/core/column/Column.h" #include #include class QStringList; class AbstractSimpleFilter; class ColumnSetModeCmd : public QUndoCommand { public: explicit ColumnSetModeCmd(ColumnPrivate* col, AbstractColumn::ColumnMode mode, QUndoCommand* parent = nullptr); ~ColumnSetModeCmd() override; void redo() override; void undo() override; private: ColumnPrivate* m_col; - AbstractColumn::ColumnMode m_old_mode{AbstractColumn::Numeric}; + AbstractColumn::ColumnMode m_old_mode{AbstractColumn::ColumnMode::Numeric}; AbstractColumn::ColumnMode m_mode; void* m_old_data{nullptr}; void* m_new_data{nullptr}; AbstractSimpleFilter* m_new_in_filter{nullptr}; AbstractSimpleFilter* m_new_out_filter{nullptr}; AbstractSimpleFilter* m_old_in_filter{nullptr}; AbstractSimpleFilter* m_old_out_filter{nullptr}; bool m_undone{false}; bool m_executed{false}; }; class ColumnFullCopyCmd : public QUndoCommand { public: explicit ColumnFullCopyCmd(ColumnPrivate* col, const AbstractColumn* src, QUndoCommand* parent = nullptr); ~ColumnFullCopyCmd() override; void redo() override; void undo() override; private: ColumnPrivate* m_col; const AbstractColumn* m_src; ColumnPrivate* m_backup{nullptr}; Column* m_backup_owner{nullptr}; }; class ColumnPartialCopyCmd : public QUndoCommand { public: explicit ColumnPartialCopyCmd(ColumnPrivate* col, const AbstractColumn* src, int src_start, int dest_start, int num_rows, QUndoCommand* parent = nullptr); ~ColumnPartialCopyCmd() override; void redo() override; void undo() override; private: ColumnPrivate* m_col; const AbstractColumn * m_src; ColumnPrivate* m_col_backup{nullptr}; ColumnPrivate* m_src_backup{nullptr}; Column* m_col_backup_owner{nullptr}; Column* m_src_backup_owner{nullptr}; int m_src_start; int m_dest_start; int m_num_rows; int m_old_row_count{0}; }; class ColumnInsertRowsCmd : public QUndoCommand { public: explicit ColumnInsertRowsCmd(ColumnPrivate* col, int before, int count, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_before, m_count; }; class ColumnRemoveRowsCmd : public QUndoCommand { public: explicit ColumnRemoveRowsCmd(ColumnPrivate* col, int first, int count, QUndoCommand* parent = nullptr); ~ColumnRemoveRowsCmd() override; void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_first, m_count; int m_data_row_count{0}; int m_old_size{0}; ColumnPrivate* m_backup{nullptr}; Column* m_backup_owner{nullptr}; IntervalAttribute m_formulas; }; class ColumnSetPlotDesignationCmd : public QUndoCommand { public: explicit ColumnSetPlotDesignationCmd(ColumnPrivate* col, AbstractColumn::PlotDesignation pd, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; AbstractColumn::PlotDesignation m_new_pd; AbstractColumn::PlotDesignation m_old_pd{AbstractColumn::PlotDesignation::X}; }; class ColumnClearCmd : public QUndoCommand { public: explicit ColumnClearCmd(ColumnPrivate* col, QUndoCommand* parent = nullptr); ~ColumnClearCmd() override; void redo() override; void undo() override; private: ColumnPrivate* m_col; void* m_data{nullptr}; void* m_empty_data{nullptr}; bool m_undone{false}; }; class ColumnSetGlobalFormulaCmd : public QUndoCommand { public: explicit ColumnSetGlobalFormulaCmd(ColumnPrivate* col, QString formula, QStringList variableNames, QVector columns, bool autoUpdate); void redo() override; void undo() override; private: ColumnPrivate* m_col; QString m_formula; QStringList m_variableNames; QVector m_variableColumns; bool m_autoUpdate{false}; QString m_newFormula; QStringList m_newVariableNames; QVector m_newVariableColumns; bool m_newAutoUpdate{false}; bool m_copied{false}; }; class ColumnSetFormulaCmd : public QUndoCommand { public: explicit ColumnSetFormulaCmd(ColumnPrivate* col, const Interval& interval, QString formula, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; Interval m_interval; QString m_oldFormula; QString m_newFormula; IntervalAttribute m_formulas; bool m_copied{false}; }; class ColumnClearFormulasCmd : public QUndoCommand { public: explicit ColumnClearFormulasCmd(ColumnPrivate* col, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; IntervalAttribute m_formulas; bool m_copied{false}; }; class ColumnSetTextCmd : public QUndoCommand { public: explicit ColumnSetTextCmd(ColumnPrivate* col, int row, QString new_value, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_row; QString m_new_value; QString m_old_value; int m_row_count{0}; }; class ColumnSetValueCmd : public QUndoCommand { public: explicit ColumnSetValueCmd(ColumnPrivate* col, int row, double new_value, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_row; double m_new_value; double m_old_value{0.}; int m_row_count{0}; }; class ColumnSetIntegerCmd : public QUndoCommand { public: explicit ColumnSetIntegerCmd(ColumnPrivate* col, int row, int new_value, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_row; int m_new_value; int m_old_value{0}; int m_row_count{0}; }; class ColumnSetBigIntCmd : public QUndoCommand { public: explicit ColumnSetBigIntCmd(ColumnPrivate* col, int row, qint64 new_value, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_row; qint64 m_new_value; qint64 m_old_value{0}; qint64 m_row_count{0}; }; class ColumnSetDateTimeCmd : public QUndoCommand { public: explicit ColumnSetDateTimeCmd(ColumnPrivate* col, int row, QDateTime new_value, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_row; QDateTime m_new_value; QDateTime m_old_value; int m_row_count{0}; }; class ColumnReplaceTextsCmd : public QUndoCommand { public: explicit ColumnReplaceTextsCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_first; QVector m_new_values; QVector m_old_values; bool m_copied{false}; int m_row_count{0}; }; class ColumnReplaceValuesCmd : public QUndoCommand { public: explicit ColumnReplaceValuesCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_first; QVector m_new_values; QVector m_old_values; bool m_copied{false}; int m_row_count{0}; }; class ColumnReplaceIntegerCmd : public QUndoCommand { public: explicit ColumnReplaceIntegerCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_first; QVector m_new_values; QVector m_old_values; bool m_copied{false}; int m_row_count{0}; }; class ColumnReplaceBigIntCmd : public QUndoCommand { public: explicit ColumnReplaceBigIntCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_first; QVector m_new_values; QVector m_old_values; bool m_copied{false}; int m_row_count{0}; }; class ColumnReplaceDateTimesCmd : public QUndoCommand { public: explicit ColumnReplaceDateTimesCmd(ColumnPrivate* col, int first, const QVector& new_values, QUndoCommand* parent = nullptr); void redo() override; void undo() override; private: ColumnPrivate* m_col; int m_first; QVector m_new_values; QVector m_old_values; bool m_copied{false}; int m_row_count{0}; }; #endif diff --git a/src/backend/core/datatypes/BigInt2DateTimeFilter.h b/src/backend/core/datatypes/BigInt2DateTimeFilter.h index 185138602..68f7823b2 100644 --- a/src/backend/core/datatypes/BigInt2DateTimeFilter.h +++ b/src/backend/core/datatypes/BigInt2DateTimeFilter.h @@ -1,62 +1,62 @@ /*************************************************************************** File : BigInt2DateTimeFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs@gmx.net Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter int -> QDateTime, interpreting the input numbers as Julian days. ***************************************************************************/ /*************************************************************************** * * * 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 BIGINT2DATE_TIME_FILTER_H #define BIGINT2DATE_TIME_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Conversion filter bigint -> QDateTime, interpreting the input numbers as Julian days. class BigInt2DateTimeFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { if (!m_inputs.value(0)) return QDate(); qint64 inputValue = m_inputs.value(0)->bigIntAt(row); return QDate(1900, 1, 1 + inputValue); } QDateTime dateTimeAt(int row) const override { return QDateTime(dateAt(row), QTime()); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::DateTime; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::DateTime; } protected: //! Using typed ports: only double inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::BigInt; + return source->columnMode() == AbstractColumn::ColumnMode::BigInt; } }; #endif // ifndef BIGINT2DATE_TIME_FILTER_H diff --git a/src/backend/core/datatypes/BigInt2DayOfWeekFilter.h b/src/backend/core/datatypes/BigInt2DayOfWeekFilter.h index 76049df50..1f2365ba1 100644 --- a/src/backend/core/datatypes/BigInt2DayOfWeekFilter.h +++ b/src/backend/core/datatypes/BigInt2DayOfWeekFilter.h @@ -1,65 +1,65 @@ /*************************************************************************** File : BigInt2DayOfWeekFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter bigint -> QDateTime, interpreting the input numbers as days of the week (1 -> Monday). ***************************************************************************/ /*************************************************************************** * * * 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 BIGINT2DAY_OF_WEEK_FILTER_H #define BIGINT2DAY_OF_WEEK_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Conversion filter int -> QDateTime, interpreting the input numbers as days of the week (1 = Monday). class BigInt2DayOfWeekFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { if (!m_inputs.value(0)) return QDate(); qint64 inputValue = m_inputs.value(0)->bigIntAt(row); // Don't use Julian days here since support for years < 1 is bad // Use 1900-01-01 instead (a Monday) return QDate(1900,1,1).addDays(inputValue); } QTime timeAt(int row) const override { Q_UNUSED(row) return QTime(0,0,0,0); } QDateTime dateTimeAt(int row) const override { return QDateTime(dateAt(row), timeAt(row)); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Day; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Day; } protected: //! Using typed ports: only bigint inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::BigInt; + return source->columnMode() == AbstractColumn::ColumnMode::BigInt; } }; #endif // ifndef BIGINT2DAY_OF_WEEK_FILTER_H diff --git a/src/backend/core/datatypes/BigInt2DoubleFilter.h b/src/backend/core/datatypes/BigInt2DoubleFilter.h index 89e6c04ad..eeb90f188 100644 --- a/src/backend/core/datatypes/BigInt2DoubleFilter.h +++ b/src/backend/core/datatypes/BigInt2DoubleFilter.h @@ -1,62 +1,62 @@ /*************************************************************************** File : BigInt2DoubleFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : conversion filter int -> double. ***************************************************************************/ /*************************************************************************** * * * 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 BIGINT2DOUBLE_FILTER_H #define BIGINT2DOUBLE_FILTER_H #include "../AbstractSimpleFilter.h" #include //! conversion filter double -> int. class BigInt2DoubleFilter : public AbstractSimpleFilter { Q_OBJECT public: BigInt2DoubleFilter() {} double valueAt(int row) const override { if (!m_inputs.value(0)) return 0; qint64 value = m_inputs.value(0)->bigIntAt(row); double result = (double)value; //DEBUG("BigInt2Double::integerAt() " << value << " -> " << result); return result; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Numeric; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Numeric; } protected: //! Using typed ports: only bigint inputs are accepted bool inputAcceptable(int, const AbstractColumn *source) override { DEBUG("inputAcceptable(): source type = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, source->columnMode())); - return source->columnMode() == AbstractColumn::BigInt; + return source->columnMode() == AbstractColumn::ColumnMode::BigInt; } }; #endif // ifndef BIGINT2DOUBLE_FILTER_H diff --git a/src/backend/core/datatypes/BigInt2IntegerFilter.h b/src/backend/core/datatypes/BigInt2IntegerFilter.h index 119aa80d0..4d54ee58e 100644 --- a/src/backend/core/datatypes/BigInt2IntegerFilter.h +++ b/src/backend/core/datatypes/BigInt2IntegerFilter.h @@ -1,63 +1,63 @@ /*************************************************************************** File : BigInt2IntegerFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : conversion filter bigint -> integer ***************************************************************************/ /*************************************************************************** * * * 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 BIGINT2INTEGER_FILTER_H #define BIGINT2INTEGER_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! conversion filter bigint -> integer class BigInt2IntegerFilter : public AbstractSimpleFilter { Q_OBJECT public: BigInt2IntegerFilter() {} int integerAt(int row) const override { if (!m_inputs.value(0)) return 0; qint64 value = m_inputs.value(0)->bigIntAt(row); int result = static_cast(value); //DEBUG("BigInt2Integer::integerAt() " << value << " -> " << result); return result; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Integer; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Integer; } protected: //! Using typed ports: only bigint inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::BigInt; + return source->columnMode() == AbstractColumn::ColumnMode::BigInt; } }; #endif // ifndef BIGINT2INTEGER_FILTER_H diff --git a/src/backend/core/datatypes/BigInt2MonthFilter.h b/src/backend/core/datatypes/BigInt2MonthFilter.h index 1f4314f00..6b0f8e213 100644 --- a/src/backend/core/datatypes/BigInt2MonthFilter.h +++ b/src/backend/core/datatypes/BigInt2MonthFilter.h @@ -1,67 +1,67 @@ /*************************************************************************** File : BigInt2MonthFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter bigint -> QDateTime, interpreting the input numbers as months of the year. ***************************************************************************/ /*************************************************************************** * * * 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 BIGINT2MONTH_FILTER_H #define BIGINT2MONTH_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! Conversion filter bigint -> QDateTime, interpreting the input numbers as months of the year. class BigInt2MonthFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { return dateTimeAt(row).date(); } QTime timeAt(int row) const override { return dateTimeAt(row).time(); } QDateTime dateTimeAt(int row) const override { if (!m_inputs.value(0)) return QDateTime(); qint64 inputValue = m_inputs.value(0)->bigIntAt(row); // Don't use Julian days here since support for years < 1 is bad // Use 1900-01-01 instead QDate result_date = QDate(1900,1,1).addMonths(inputValue); QTime result_time = QTime(0,0,0,0); return QDateTime(result_date, result_time); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Month; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Month; } protected: bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::BigInt; + return source->columnMode() == AbstractColumn::ColumnMode::BigInt; } }; #endif // ifndef BIGINT2MONTH_FILTER_H diff --git a/src/backend/core/datatypes/BigInt2StringFilter.h b/src/backend/core/datatypes/BigInt2StringFilter.h index 1b5993e50..58f0fd779 100644 --- a/src/backend/core/datatypes/BigInt2StringFilter.h +++ b/src/backend/core/datatypes/BigInt2StringFilter.h @@ -1,62 +1,62 @@ /*************************************************************************** File : BigInt2StringFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Locale-aware conversion filter bigint -> QString. ***************************************************************************/ /*************************************************************************** * * * 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 BIGINT2STRING_FILTER_H #define BIGINT2STRING_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Locale-aware conversion filter bigint -> QString. class BigInt2StringFilter : public AbstractSimpleFilter { Q_OBJECT public: //! Standard constructor. explicit BigInt2StringFilter() {} //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Text; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Text; } public: QString textAt(int row) const override { if (!m_inputs.value(0)) return QString(); if (m_inputs.value(0)->rowCount() <= row) return QString(); qint64 inputValue = m_inputs.value(0)->bigIntAt(row); return QLocale().toString(inputValue); } protected: //! Using typed ports: only bigint inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::BigInt; + return source->columnMode() == AbstractColumn::ColumnMode::BigInt; } }; #endif // ifndef BIGINT2STRING_FILTER_H diff --git a/src/backend/core/datatypes/DateTime2BigIntFilter.h b/src/backend/core/datatypes/DateTime2BigIntFilter.h index 8efe32227..4ad0b6c80 100644 --- a/src/backend/core/datatypes/DateTime2BigIntFilter.h +++ b/src/backend/core/datatypes/DateTime2BigIntFilter.h @@ -1,59 +1,59 @@ /*************************************************************************** File : DateTime2BigIntFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter QDateTime -> bigint (using Julian day). ***************************************************************************/ /*************************************************************************** * * * 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 DATE_TIME2BIGINT_FILTER_H #define DATE_TIME2BIGINT_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Conversion filter QDateTime -> bigint (using Julian day). class DateTime2BigIntFilter : public AbstractSimpleFilter { Q_OBJECT public: qint64 bigIntAt(int row) const override { //DEBUG("bigIntAt()"); if (!m_inputs.value(0)) return 0; QDateTime inputDate = m_inputs.value(0)->dateTimeAt(row); if (!inputDate.isValid()) return 0; QDateTime start(QDate(1900, 1, 1)); return qint64(start.daysTo(inputDate)); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::BigInt; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::BigInt; } protected: //! Using typed ports: only DateTime inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::DateTime; + return source->columnMode() == AbstractColumn::ColumnMode::DateTime; } }; #endif // ifndef DATE_TIME2BIGINT_FILTER_H diff --git a/src/backend/core/datatypes/DateTime2DoubleFilter.h b/src/backend/core/datatypes/DateTime2DoubleFilter.h index 8e0122716..64c3da436 100644 --- a/src/backend/core/datatypes/DateTime2DoubleFilter.h +++ b/src/backend/core/datatypes/DateTime2DoubleFilter.h @@ -1,61 +1,61 @@ /*************************************************************************** File : DateTime2DoubleFilter.h Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2007 by Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007 by Knut Franke (knut.franke@gmx.de) Description : Conversion filter QDateTime -> double (using Julian day). ***************************************************************************/ /*************************************************************************** * * * 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 DATE_TIME2DOUBLE_FILTER_H #define DATE_TIME2DOUBLE_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! Conversion filter QDateTime -> double (using Julian day). class DateTime2DoubleFilter : public AbstractSimpleFilter { Q_OBJECT public: double valueAt(int row) const override { if (!m_inputs.value(0)) return NAN; QDateTime inputDate = m_inputs.value(0)->dateTimeAt(row); if (!inputDate.isValid()) return NAN; QDateTime start(QDate(1900, 1, 1)); return double(start.daysTo(inputDate)) + double( -inputDate.time().msecsTo(QTime(0,0,0,0)) ) / 86400000.0; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Numeric; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Numeric; } protected: //! Using typed ports: only DateTime inputs are accepted. bool inputAcceptable(int, const AbstractColumn* source) override { - return source->columnMode() == AbstractColumn::DateTime; + return source->columnMode() == AbstractColumn::ColumnMode::DateTime; } }; #endif // ifndef DATE_TIME2DOUBLE_FILTER_H diff --git a/src/backend/core/datatypes/DateTime2IntegerFilter.h b/src/backend/core/datatypes/DateTime2IntegerFilter.h index b4621b29a..d3e82ea4c 100644 --- a/src/backend/core/datatypes/DateTime2IntegerFilter.h +++ b/src/backend/core/datatypes/DateTime2IntegerFilter.h @@ -1,59 +1,59 @@ /*************************************************************************** File : DateTime2IntegerFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter QDateTime -> int (using Julian day). ***************************************************************************/ /*************************************************************************** * * * 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 DATE_TIME2INTEGER_FILTER_H #define DATE_TIME2INTEGER_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Conversion filter QDateTime -> int (using Julian day). class DateTime2IntegerFilter : public AbstractSimpleFilter { Q_OBJECT public: int integerAt(int row) const override { //DEBUG("integerAt()"); if (!m_inputs.value(0)) return 0; QDateTime inputDate = m_inputs.value(0)->dateTimeAt(row); if (!inputDate.isValid()) return 0; QDateTime start(QDate(1900, 1, 1)); return int(start.daysTo(inputDate)); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Integer; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Integer; } protected: //! Using typed ports: only DateTime inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::DateTime; + return source->columnMode() == AbstractColumn::ColumnMode::DateTime; } }; #endif // ifndef DATE_TIME2INTEGER_FILTER_H diff --git a/src/backend/core/datatypes/DateTime2StringFilter.cpp b/src/backend/core/datatypes/DateTime2StringFilter.cpp index 1a259c848..0a9ff7cd5 100644 --- a/src/backend/core/datatypes/DateTime2StringFilter.cpp +++ b/src/backend/core/datatypes/DateTime2StringFilter.cpp @@ -1,107 +1,108 @@ /*************************************************************************** File : DateTime2StringFilter.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2007 by Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007 by Knut Franke (knut.franke@gmx.de) Description : Conversion filter QDateTime -> QString. ***************************************************************************/ /*************************************************************************** * * * 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 "DateTime2StringFilter.h" #include "backend/lib/XmlStreamReader.h" #include #include #include class DateTime2StringFilterSetFormatCmd : public QUndoCommand { public: DateTime2StringFilterSetFormatCmd(DateTime2StringFilter* target, const QString &new_format); void redo() override; void undo() override; private: DateTime2StringFilter* m_target; QString m_other_format; }; void DateTime2StringFilter::setFormat(const QString& format) { exec(new DateTime2StringFilterSetFormatCmd(static_cast(this), format)); } QString DateTime2StringFilter::textAt(int row) const { //DEBUG("DateTime2StringFilter::textAt()"); if (!m_inputs.value(0)) return QString(); QDateTime inputValue = m_inputs.value(0)->dateTimeAt(row); if (!inputValue.isValid()) return QString(); //QDEBUG(" : " << inputValue << " -> " << m_format << " -> " << inputValue.toString(m_format)); return inputValue.toString(m_format); } bool DateTime2StringFilter::inputAcceptable(int, const AbstractColumn *source) { - return (source->columnMode() == AbstractColumn::DateTime) - || (source->columnMode() == AbstractColumn::Day) - || (source->columnMode() == AbstractColumn::Month); + auto mode = source->columnMode(); + return (mode == AbstractColumn::ColumnMode::DateTime) + || (mode == AbstractColumn::ColumnMode::Day) + || (mode == AbstractColumn::ColumnMode::Month); } DateTime2StringFilterSetFormatCmd::DateTime2StringFilterSetFormatCmd(DateTime2StringFilter* target, const QString &new_format) : m_target(target), m_other_format(new_format) { if (m_target->parentAspect()) setText(i18n("%1: set date-time format to %2", m_target->parentAspect()->name(), new_format)); else setText(i18n("set date-time format to %1", new_format)); } void DateTime2StringFilterSetFormatCmd::redo() { QString tmp = m_target->m_format; m_target->m_format = m_other_format; m_other_format = tmp; emit m_target->formatChanged(); } void DateTime2StringFilterSetFormatCmd::undo() { redo(); } void DateTime2StringFilter::writeExtraAttributes(QXmlStreamWriter * writer) const { writer->writeAttribute("format", format()); } bool DateTime2StringFilter::load(XmlStreamReader* reader, bool preview) { if (preview) return true; QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value(reader->namespaceUri().toString(), "format").toString(); if (AbstractSimpleFilter::load(reader, preview)) setFormat(str); else return false; return !reader->hasError(); } diff --git a/src/backend/core/datatypes/DateTime2StringFilter.h b/src/backend/core/datatypes/DateTime2StringFilter.h index 460bb0587..df3a789f1 100644 --- a/src/backend/core/datatypes/DateTime2StringFilter.h +++ b/src/backend/core/datatypes/DateTime2StringFilter.h @@ -1,76 +1,76 @@ /*************************************************************************** File : DateTime2StringFilter.h Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2007 by Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007 by Knut Franke (knut.franke@gmx.de) Description : Conversion filter QDateTime -> QString. ***************************************************************************/ /*************************************************************************** * * * 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 DATE_TIME2STRING_FILTER_H #define DATE_TIME2STRING_FILTER_H #include "backend/core/AbstractSimpleFilter.h" class DateTime2StringFilterSetFormatCmd; //! Conversion filter QDateTime -> QString. class DateTime2StringFilter : public AbstractSimpleFilter { Q_OBJECT public: //! Standard constructor. explicit DateTime2StringFilter(const QString& format="yyyy-MM-dd hh:mm:ss.zzz") : m_format(format) {} //! Set the format string to be used for conversion. void setFormat(const QString& format); //! Return the format string /** * The default format string is "yyyy-MM-dd hh:mm:ss.zzz". * \sa QDate::toString() */ QString format() const { return m_format; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Text; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Text; } private: friend class DateTime2StringFilterSetFormatCmd; //! The format string. QString m_format; public: QString textAt(int row) const override; //! \name XML related functions //@{ void writeExtraAttributes(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; //@} protected: //! Using typed ports: only DateTime inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override; }; #endif // ifndef DATE_TIME2STRING_FILTER_H diff --git a/src/backend/core/datatypes/DayOfWeek2BigIntFilter.h b/src/backend/core/datatypes/DayOfWeek2BigIntFilter.h index a555cacca..68d41ea2e 100644 --- a/src/backend/core/datatypes/DayOfWeek2BigIntFilter.h +++ b/src/backend/core/datatypes/DayOfWeek2BigIntFilter.h @@ -1,59 +1,59 @@ /*************************************************************************** File : DayOfWeek2BigIntFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter QDateTime -> bigint, translating dates into days of the week (Monday -> 1). ***************************************************************************/ /*************************************************************************** * * * 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 DAY_OF_WEEK2BIGINT_FILTER_H #define DAY_OF_WEEK2BIGINT_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Conversion filter QDateTime -> bigint, translating dates into days of the week (Monday -> 1). class DayOfWeek2BigIntFilter : public AbstractSimpleFilter { Q_OBJECT public: qint64 bigIntAt(int row) const override { DEBUG("bigIntAt()"); if (!m_inputs.value(0)) return 0; QDate date = m_inputs.value(0)->dateAt(row); if (!date.isValid()) return 0; return qint64(date.dayOfWeek()); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::BigInt; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::BigInt; } protected: //! Using typed ports: only day inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Day; + return source->columnMode() == AbstractColumn::ColumnMode::Day; } }; #endif // ifndef DAY_OF_WEEK2BIGINT_FILTER_H diff --git a/src/backend/core/datatypes/DayOfWeek2DoubleFilter.h b/src/backend/core/datatypes/DayOfWeek2DoubleFilter.h index aec4134f0..05d27c6e9 100644 --- a/src/backend/core/datatypes/DayOfWeek2DoubleFilter.h +++ b/src/backend/core/datatypes/DayOfWeek2DoubleFilter.h @@ -1,60 +1,60 @@ /*************************************************************************** File : DayOfWeek2DoubleFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs@gmx.net Description : Conversion filter QDateTime -> double, translating dates into days of the week (Monday -> 1). ***************************************************************************/ /*************************************************************************** * * * 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 DAY_OF_WEEK2DOUBLE_FILTER_H #define DAY_OF_WEEK2DOUBLE_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! Conversion filter QDateTime -> double, translating dates into days of the week (Monday -> 1). class DayOfWeek2DoubleFilter : public AbstractSimpleFilter { Q_OBJECT public: double valueAt(int row) const override { if (!m_inputs.value(0)) return NAN; QDate date = m_inputs.value(0)->dateAt(row); if (!date.isValid()) return NAN; return double(date.dayOfWeek()); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Numeric; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Numeric; } protected: //! Using typed ports: only date-time inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Day; + return source->columnMode() == AbstractColumn::ColumnMode::Day; } }; #endif // ifndef DAY_OF_WEEK2DOUBLE_FILTER_H diff --git a/src/backend/core/datatypes/DayOfWeek2IntegerFilter.h b/src/backend/core/datatypes/DayOfWeek2IntegerFilter.h index 4615696b0..89b1fc2c0 100644 --- a/src/backend/core/datatypes/DayOfWeek2IntegerFilter.h +++ b/src/backend/core/datatypes/DayOfWeek2IntegerFilter.h @@ -1,59 +1,59 @@ /*************************************************************************** File : DayOfWeek2IntegerFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter QDateTime -> int, translating dates into days of the week (Monday -> 1). ***************************************************************************/ /*************************************************************************** * * * 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 DAY_OF_WEEK2INTEGER_FILTER_H #define DAY_OF_WEEK2INTEGER_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Conversion filter QDateTime -> int, translating dates into days of the week (Monday -> 1). class DayOfWeek2IntegerFilter : public AbstractSimpleFilter { Q_OBJECT public: int integerAt(int row) const override { DEBUG("integerAt()"); if (!m_inputs.value(0)) return 0; QDate date = m_inputs.value(0)->dateAt(row); if (!date.isValid()) return 0; return int(date.dayOfWeek()); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Integer; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Integer; } protected: //! Using typed ports: only date-time inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Day; + return source->columnMode() == AbstractColumn::ColumnMode::Day; } }; #endif // ifndef DAY_OF_WEEK2INTEGER_FILTER_H diff --git a/src/backend/core/datatypes/Double2BigIntFilter.h b/src/backend/core/datatypes/Double2BigIntFilter.h index 1f6e59b7e..105d37234 100644 --- a/src/backend/core/datatypes/Double2BigIntFilter.h +++ b/src/backend/core/datatypes/Double2BigIntFilter.h @@ -1,65 +1,65 @@ /*************************************************************************** File : Double2BigIntFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : conversion filter double -> bigint ***************************************************************************/ /*************************************************************************** * * * 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 DOUBLE2BIGINT_FILTER_H #define DOUBLE2BIGINT_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! conversion filter double -> int. class Double2BigIntFilter : public AbstractSimpleFilter { Q_OBJECT public: Double2BigIntFilter() {} qint64 bigIntAt(int row) const override { if (!m_inputs.value(0)) return 0; double value = m_inputs.value(0)->valueAt(row); int result = 0; if (!std::isnan(value)) result = (qint64)round(value); //DEBUG("Double2BigInt::integerAt() " << value << " -> " << result); return result; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::BigInt; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::BigInt; } protected: //! Using typed ports: only double inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Numeric; + return source->columnMode() == AbstractColumn::ColumnMode::Numeric; } }; #endif // ifndef DOUBLE2BIGINT_FILTER_H diff --git a/src/backend/core/datatypes/Double2DateTimeFilter.h b/src/backend/core/datatypes/Double2DateTimeFilter.h index 8d317731c..963a25696 100644 --- a/src/backend/core/datatypes/Double2DateTimeFilter.h +++ b/src/backend/core/datatypes/Double2DateTimeFilter.h @@ -1,72 +1,72 @@ /*************************************************************************** File : Double2DateTimeFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs@gmx.net Description : Conversion filter double -> QDateTime, interpreting the input numbers as (fractional) Julian days. ***************************************************************************/ /*************************************************************************** * * * 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 DOUBLE2DATE_TIME_FILTER_H #define DOUBLE2DATE_TIME_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! Conversion filter double -> QDateTime, interpreting the input numbers as (fractional) Julian days. class Double2DateTimeFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { if (!m_inputs.value(0)) return QDate(); double inputValue = m_inputs.value(0)->valueAt(row); if (std::isnan(inputValue)) return QDate(); //QDEBUG(" convert " << inputValue << " to " << QDate(1900, 1, int(inputValue))); return QDate(1900, 1, 1 + int(inputValue)); } QTime timeAt(int row) const override { if (!m_inputs.value(0)) return QTime(); double inputValue = m_inputs.value(0)->valueAt(row); if (std::isnan(inputValue)) return QTime(); // we only want the digits behind the dot and convert them from fraction of day to milliseconds //QDEBUG(" convert " << inputValue << " to " << QTime(0,0,0,0).addMSecs(int( (inputValue - int(inputValue)) * 86400000.0 ))); return QTime(0,0,0,0).addMSecs(int( (inputValue - int(inputValue)) * 86400000.0 )); } QDateTime dateTimeAt(int row) const override { return QDateTime(dateAt(row), timeAt(row)); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::DateTime; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::DateTime; } protected: //! Using typed ports: only double inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Numeric; + return source->columnMode() == AbstractColumn::ColumnMode::Numeric; } }; #endif // ifndef DOUBLE2DATE_TIME_FILTER_H diff --git a/src/backend/core/datatypes/Double2DayOfWeekFilter.h b/src/backend/core/datatypes/Double2DayOfWeekFilter.h index f236e2858..ec718340c 100644 --- a/src/backend/core/datatypes/Double2DayOfWeekFilter.h +++ b/src/backend/core/datatypes/Double2DayOfWeekFilter.h @@ -1,68 +1,68 @@ /*************************************************************************** File : Double2DayOfWeekFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs@gmx.net Description : Conversion filter double -> QDateTime, interpreting the input numbers as days of the week (1 -> Monday). ***************************************************************************/ /*************************************************************************** * * * 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 DOUBLE2DAY_OF_WEEK_FILTER_H #define DOUBLE2DAY_OF_WEEK_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! Conversion filter double -> QDateTime, interpreting the input numbers as days of the week (1 = Monday). class Double2DayOfWeekFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { if (!m_inputs.value(0)) return QDate(); double inputValue = m_inputs.value(0)->valueAt(row); if (std::isnan(inputValue)) return QDate(); // Don't use Julian days here since support for years < 1 is bad // Use 1900-01-01 instead (a Monday) return QDate(1900,1,1).addDays(qRound(inputValue - 1.0)); } QTime timeAt(int row) const override { Q_UNUSED(row) return QTime(0,0,0,0); } QDateTime dateTimeAt(int row) const override { return QDateTime(dateAt(row), timeAt(row)); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Day; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Day; } protected: //! Using typed ports: only double inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Numeric; + return source->columnMode() == AbstractColumn::ColumnMode::Numeric; } }; #endif // ifndef DOUBLE2DAY_OF_WEEK_FILTER_H diff --git a/src/backend/core/datatypes/Double2IntegerFilter.h b/src/backend/core/datatypes/Double2IntegerFilter.h index 5acb738b2..ec77e6e09 100644 --- a/src/backend/core/datatypes/Double2IntegerFilter.h +++ b/src/backend/core/datatypes/Double2IntegerFilter.h @@ -1,65 +1,65 @@ /*************************************************************************** File : Double2IntegerFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : conversion filter double -> int. ***************************************************************************/ /*************************************************************************** * * * 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 DOUBLE2INTEGER_FILTER_H #define DOUBLE2INTEGER_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! conversion filter double -> int. class Double2IntegerFilter : public AbstractSimpleFilter { Q_OBJECT public: Double2IntegerFilter() {} int integerAt(int row) const override { if (!m_inputs.value(0)) return 0; double value = m_inputs.value(0)->valueAt(row); int result = 0; if (!std::isnan(value)) result = (int)round(value); //DEBUG("Double2Integer::integerAt() " << value << " -> " << result); return result; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Integer; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Integer; } protected: //! Using typed ports: only double inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Numeric; + return source->columnMode() == AbstractColumn::ColumnMode::Numeric; } }; #endif // ifndef DOUBLE2INTEGER_FILTER_H diff --git a/src/backend/core/datatypes/Double2MonthFilter.h b/src/backend/core/datatypes/Double2MonthFilter.h index dc7ed15b3..5f095876f 100644 --- a/src/backend/core/datatypes/Double2MonthFilter.h +++ b/src/backend/core/datatypes/Double2MonthFilter.h @@ -1,71 +1,71 @@ /*************************************************************************** File : Double2MonthFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs@gmx.net Description : Conversion filter double -> QDateTime, interpreting the input numbers as months of the year. ***************************************************************************/ /*************************************************************************** * * * 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 DOUBLE2MONTH_FILTER_H #define DOUBLE2MONTH_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! Conversion filter double -> QDateTime, interpreting the input numbers as months of the year. class Double2MonthFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { return dateTimeAt(row).date(); } QTime timeAt(int row) const override { return dateTimeAt(row).time(); } QDateTime dateTimeAt(int row) const override { DEBUG("Double2MonthFilter::dateTimeAt() row = " << row); if (!m_inputs.value(0)) return QDateTime(); double inputValue = m_inputs.value(0)->valueAt(row); if (std::isnan(inputValue)) return QDateTime(); // Don't use Julian days here since support for years < 1 is bad // Use 1900-01-01 instead QDate result_date = QDate(1900,1,1).addMonths(qRound(inputValue - 1.0)); QTime result_time = QTime(0,0,0,0); QDEBUG("value = " << inputValue << " result = " << QDateTime(result_date, result_time)); return QDateTime(result_date, result_time); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Month; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Month; } protected: bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Numeric; + return source->columnMode() == AbstractColumn::ColumnMode::Numeric; } }; #endif // ifndef DOUBLE2MONTH_FILTER_H diff --git a/src/backend/core/datatypes/Double2StringFilter.h b/src/backend/core/datatypes/Double2StringFilter.h index 289f32e3b..be54f1792 100644 --- a/src/backend/core/datatypes/Double2StringFilter.h +++ b/src/backend/core/datatypes/Double2StringFilter.h @@ -1,87 +1,87 @@ /*************************************************************************** File : Double2StringFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs@gmx.net Description : Locale-aware conversion filter double -> QString. ***************************************************************************/ /*************************************************************************** * * * 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 DOUBLE2STRING_FILTER_H #define DOUBLE2STRING_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! Locale-aware conversion filter double -> QString. class Double2StringFilter : public AbstractSimpleFilter { Q_OBJECT public: //! Standard constructor. explicit Double2StringFilter(char format='e', int digits=6) : m_format(format), m_digits(digits) {} //! Set format character as in QString::number void setNumericFormat(char format); //! Set number of displayed digits void setNumDigits(int digits); //! Get format character as in QString::number char numericFormat() const { return m_format; } //! Get number of displayed digits int numDigits() const { return m_digits; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Text; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Text; } private: friend class Double2StringFilterSetFormatCmd; friend class Double2StringFilterSetDigitsCmd; //! Format character as in QString::number char m_format; //! Display digits or precision as in QString::number int m_digits; //! \name XML related functions //@{ void writeExtraAttributes(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; //@} public: QString textAt(int row) const override { //DEBUG("Double2String::textAt()"); if (!m_inputs.value(0)) return QString(); if (m_inputs.value(0)->rowCount() <= row) return QString(); double inputValue = m_inputs.value(0)->valueAt(row); if (std::isnan(inputValue)) return QString(); return QLocale().toString(inputValue, m_format, m_digits); } protected: //! Using typed ports: only double inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Numeric; + return source->columnMode() == AbstractColumn::ColumnMode::Numeric; } }; #endif // ifndef DOUBLE2STRING_FILTER_H diff --git a/src/backend/core/datatypes/Integer2BigIntFilter.h b/src/backend/core/datatypes/Integer2BigIntFilter.h index 77245898f..b8b87e421 100644 --- a/src/backend/core/datatypes/Integer2BigIntFilter.h +++ b/src/backend/core/datatypes/Integer2BigIntFilter.h @@ -1,62 +1,62 @@ /*************************************************************************** File : Integer2BigIntFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : conversion filter int -> bigint ***************************************************************************/ /*************************************************************************** * * * 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 INTEGER2BIGINT_FILTER_H #define INTEGER2BIGINT_FILTER_H #include "../AbstractSimpleFilter.h" #include //! conversion filter integer -> bigint class Integer2BigIntFilter : public AbstractSimpleFilter { Q_OBJECT public: Integer2BigIntFilter() {} qint64 bigIntAt(int row) const override { if (!m_inputs.value(0)) return 0; int value = m_inputs.value(0)->integerAt(row); qint64 result = (qint64)value; //DEBUG("Integer2BigInt::integerAt() " << value << " -> " << result); return result; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::BigInt; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::BigInt; } protected: //! Using typed ports: only integer inputs are accepted bool inputAcceptable(int, const AbstractColumn *source) override { DEBUG("inputAcceptable(): source type = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, source->columnMode())); - return source->columnMode() == AbstractColumn::Integer; + return source->columnMode() == AbstractColumn::ColumnMode::Integer; } }; #endif // ifndef INTEGER2BIGINT_FILTER_H diff --git a/src/backend/core/datatypes/Integer2DateTimeFilter.h b/src/backend/core/datatypes/Integer2DateTimeFilter.h index 9470d9412..a5537f035 100644 --- a/src/backend/core/datatypes/Integer2DateTimeFilter.h +++ b/src/backend/core/datatypes/Integer2DateTimeFilter.h @@ -1,61 +1,61 @@ /*************************************************************************** File : Integer2DateTimeFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs@gmx.net Description : Conversion filter int -> QDateTime, interpreting the input numbers as Julian days. ***************************************************************************/ /*************************************************************************** * * * 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 INTEGER2DATE_TIME_FILTER_H #define INTEGER2DATE_TIME_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Conversion filter double -> QDateTime, interpreting the input numbers as (fractional) Julian days. class Integer2DateTimeFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { if (!m_inputs.value(0)) return QDate(); int inputValue = m_inputs.value(0)->integerAt(row); return QDate(1900, 1, 1 + inputValue); } QDateTime dateTimeAt(int row) const override { return QDateTime(dateAt(row), QTime()); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::DateTime; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::DateTime; } protected: //! Using typed ports: only double inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Integer; + return source->columnMode() == AbstractColumn::ColumnMode::Integer; } }; #endif // ifndef INTEGER2DATE_TIME_FILTER_H diff --git a/src/backend/core/datatypes/Integer2DayOfWeekFilter.h b/src/backend/core/datatypes/Integer2DayOfWeekFilter.h index beb8f8d9e..fe8282337 100644 --- a/src/backend/core/datatypes/Integer2DayOfWeekFilter.h +++ b/src/backend/core/datatypes/Integer2DayOfWeekFilter.h @@ -1,65 +1,65 @@ /*************************************************************************** File : Integer2DayOfWeekFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter int -> QDateTime, interpreting the input numbers as days of the week (1 -> Monday). ***************************************************************************/ /*************************************************************************** * * * 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 INTEGER2DAY_OF_WEEK_FILTER_H #define INTEGER2DAY_OF_WEEK_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Conversion filter int -> QDateTime, interpreting the input numbers as days of the week (1 = Monday). class Integer2DayOfWeekFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { if (!m_inputs.value(0)) return QDate(); int inputValue = m_inputs.value(0)->integerAt(row); // Don't use Julian days here since support for years < 1 is bad // Use 1900-01-01 instead (a Monday) return QDate(1900,1,1).addDays(inputValue); } QTime timeAt(int row) const override { Q_UNUSED(row) return QTime(0,0,0,0); } QDateTime dateTimeAt(int row) const override { return QDateTime(dateAt(row), timeAt(row)); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Day; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Day; } protected: //! Using typed ports: only integer inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Integer; + return source->columnMode() == AbstractColumn::ColumnMode::Integer; } }; #endif // ifndef INTEGER2DAY_OF_WEEK_FILTER_H diff --git a/src/backend/core/datatypes/Integer2DoubleFilter.h b/src/backend/core/datatypes/Integer2DoubleFilter.h index f28c7d2e3..c5e332030 100644 --- a/src/backend/core/datatypes/Integer2DoubleFilter.h +++ b/src/backend/core/datatypes/Integer2DoubleFilter.h @@ -1,62 +1,62 @@ /*************************************************************************** File : Integer2DoubleFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : conversion filter int -> double. ***************************************************************************/ /*************************************************************************** * * * 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 INTEGER2DOUBLE_FILTER_H #define INTEGER2DOUBLE_FILTER_H #include "../AbstractSimpleFilter.h" #include //! conversion filter double -> int. class Integer2DoubleFilter : public AbstractSimpleFilter { Q_OBJECT public: Integer2DoubleFilter() {} double valueAt(int row) const override { if (!m_inputs.value(0)) return 0; int value = m_inputs.value(0)->integerAt(row); double result = (double)value; //DEBUG("Integer2Double::integerAt() " << value << " -> " << result); return result; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Numeric; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Numeric; } protected: //! Using typed ports: only integer inputs are accepted bool inputAcceptable(int, const AbstractColumn *source) override { DEBUG("inputAcceptable(): source type = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, source->columnMode())); - return source->columnMode() == AbstractColumn::Integer; + return source->columnMode() == AbstractColumn::ColumnMode::Integer; } }; #endif // ifndef INTEGER2DOUBLE_FILTER_H diff --git a/src/backend/core/datatypes/Integer2MonthFilter.h b/src/backend/core/datatypes/Integer2MonthFilter.h index 6f3a60c2d..83a330376 100644 --- a/src/backend/core/datatypes/Integer2MonthFilter.h +++ b/src/backend/core/datatypes/Integer2MonthFilter.h @@ -1,67 +1,67 @@ /*************************************************************************** File : Integer2MonthFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter int -> QDateTime, interpreting the input numbers as months of the year. ***************************************************************************/ /*************************************************************************** * * * 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 INTEGER2MONTH_FILTER_H #define INTEGER2MONTH_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! Conversion filter double -> QDateTime, interpreting the input numbers as months of the year. class Integer2MonthFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { return dateTimeAt(row).date(); } QTime timeAt(int row) const override { return dateTimeAt(row).time(); } QDateTime dateTimeAt(int row) const override { if (!m_inputs.value(0)) return QDateTime(); int inputValue = m_inputs.value(0)->integerAt(row); // Don't use Julian days here since support for years < 1 is bad // Use 1900-01-01 instead QDate result_date = QDate(1900,1,1).addMonths(inputValue); QTime result_time = QTime(0,0,0,0); return QDateTime(result_date, result_time); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Month; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Month; } protected: bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Integer; + return source->columnMode() == AbstractColumn::ColumnMode::Integer; } }; #endif // ifndef INTEGER2MONTH_FILTER_H diff --git a/src/backend/core/datatypes/Integer2StringFilter.h b/src/backend/core/datatypes/Integer2StringFilter.h index 210c3dc57..edc04b468 100644 --- a/src/backend/core/datatypes/Integer2StringFilter.h +++ b/src/backend/core/datatypes/Integer2StringFilter.h @@ -1,62 +1,62 @@ /*************************************************************************** File : Integer2StringFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Locale-aware conversion filter int -> QString. ***************************************************************************/ /*************************************************************************** * * * 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 INTEGER2STRING_FILTER_H #define INTEGER2STRING_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Locale-aware conversion filter int -> QString. class Integer2StringFilter : public AbstractSimpleFilter { Q_OBJECT public: //! Standard constructor. explicit Integer2StringFilter() {} //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Text; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Text; } public: QString textAt(int row) const override { if (!m_inputs.value(0)) return QString(); if (m_inputs.value(0)->rowCount() <= row) return QString(); int inputValue = m_inputs.value(0)->integerAt(row); return QLocale().toString(inputValue); } protected: //! Using typed ports: only double inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Integer; + return source->columnMode() == AbstractColumn::ColumnMode::Integer; } }; #endif // ifndef INTEGER2STRING_FILTER_H diff --git a/src/backend/core/datatypes/Month2BigIntFilter.h b/src/backend/core/datatypes/Month2BigIntFilter.h index d609fb8c7..4d9a4e042 100644 --- a/src/backend/core/datatypes/Month2BigIntFilter.h +++ b/src/backend/core/datatypes/Month2BigIntFilter.h @@ -1,62 +1,62 @@ /*************************************************************************** File : Month2BigIntFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter QDateTime -> bigint, translating dates into months (January -> 1). ***************************************************************************/ /*************************************************************************** * * * 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 MONTH2BIGINT_FILTER_H #define MONTH2BIGINT_FILTER_H #include "../AbstractSimpleFilter.h" #include /** * \brief Conversion filter QDateTime -> bigint, translating dates into months (January -> 1). * * \sa QDate::month() */ class Month2BigIntFilter : public AbstractSimpleFilter { Q_OBJECT public: qint64 bigIntAt(int row) const override { DEBUG("bigIntAt()"); if (!m_inputs.value(0)) return 0; QDate inputValue = m_inputs.value(0)->dateAt(row); if (!inputValue.isValid()) return 0; return qint64(inputValue.month()); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::BigInt; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::BigInt; } protected: //! Using typed ports: only date-time inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Month; + return source->columnMode() == AbstractColumn::ColumnMode::Month; } }; #endif // ifndef MONTH2BIGINT_FILTER_H diff --git a/src/backend/core/datatypes/Month2DoubleFilter.h b/src/backend/core/datatypes/Month2DoubleFilter.h index bb8ea199a..bf5317f68 100644 --- a/src/backend/core/datatypes/Month2DoubleFilter.h +++ b/src/backend/core/datatypes/Month2DoubleFilter.h @@ -1,64 +1,64 @@ /*************************************************************************** File : MonthDoubleFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs@gmx.net Description : Conversion filter QDateTime -> double, translating dates into months (January -> 1). ***************************************************************************/ /*************************************************************************** * * * 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 MONTH2DOUBLE_FILTER_H #define MONTH2DOUBLE_FILTER_H #include "../AbstractSimpleFilter.h" #include #include /** * \brief Conversion filter QDateTime -> double, translating dates into months (January -> 1). * * \sa QDate::month() */ class Month2DoubleFilter : public AbstractSimpleFilter { Q_OBJECT public: double valueAt(int row) const override { if (!m_inputs.value(0)) return NAN; QDate inputValue = m_inputs.value(0)->dateAt(row); if (!inputValue.isValid()) return NAN; return double(inputValue.month()); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Numeric; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Numeric; } protected: //! Using typed ports: only date-time inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Month; + return source->columnMode() == AbstractColumn::ColumnMode::Month; } }; #endif // ifndef MONTH2DOUBLE_FILTER_H diff --git a/src/backend/core/datatypes/Month2IntegerFilter.h b/src/backend/core/datatypes/Month2IntegerFilter.h index ece44df0d..c105ae06f 100644 --- a/src/backend/core/datatypes/Month2IntegerFilter.h +++ b/src/backend/core/datatypes/Month2IntegerFilter.h @@ -1,62 +1,62 @@ /*************************************************************************** File : Month2IntegerFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter QDateTime -> double, translating dates into months (January -> 1). ***************************************************************************/ /*************************************************************************** * * * 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 MONTH2INTEGER_FILTER_H #define MONTH2INTEGER_FILTER_H #include "../AbstractSimpleFilter.h" #include /** * \brief Conversion filter QDateTime -> int, translating dates into months (January -> 1). * * \sa QDate::month() */ class Month2IntegerFilter : public AbstractSimpleFilter { Q_OBJECT public: int integerAt(int row) const override { DEBUG("integerAt()"); if (!m_inputs.value(0)) return 0; QDate inputValue = m_inputs.value(0)->dateAt(row); if (!inputValue.isValid()) return 0; return int(inputValue.month()); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Integer; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Integer; } protected: //! Using typed ports: only date-time inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Month; + return source->columnMode() == AbstractColumn::ColumnMode::Month; } }; #endif // ifndef MONTH2INTEGER_FILTER_H diff --git a/src/backend/core/datatypes/String2BigIntFilter.h b/src/backend/core/datatypes/String2BigIntFilter.h index 78a4d87c6..0be655dff 100644 --- a/src/backend/core/datatypes/String2BigIntFilter.h +++ b/src/backend/core/datatypes/String2BigIntFilter.h @@ -1,76 +1,76 @@ /*************************************************************************** File : String2BigIntFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Locale-aware conversion filter QString -> bigint. ***************************************************************************/ /*************************************************************************** * * * 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 STRING2BIGINT_FILTER_H #define STRING2BIGINT_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Locale-aware conversion filter QString -> int. class String2BigIntFilter : public AbstractSimpleFilter { Q_OBJECT public: String2BigIntFilter() : m_use_default_locale(true) {} void setNumericLocale(const QLocale& locale) { m_numeric_locale = locale; m_use_default_locale = false; } void setNumericLocaleToDefault() { m_use_default_locale = true; } qint64 bigIntAt(int row) const override { //DEBUG("String2BigInt::bigIntAt()"); if (!m_inputs.value(0)) return 0; qint64 result; bool valid; QString textValue = m_inputs.value(0)->textAt(row); //DEBUG(" textValue = " << STDSTRING(textValue)); if (m_use_default_locale) // we need a new QLocale instance here in case the default changed since the last call result = QLocale().toLongLong(textValue, &valid); else result = m_numeric_locale.toLongLong(textValue, &valid); //DEBUG(" result = " << result << " valid = " << valid); if (valid) return result; return 0; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::BigInt; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::BigInt; } protected: //! Using typed ports: only string inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Text; + return source->columnMode() == AbstractColumn::ColumnMode::Text; } private: QLocale m_numeric_locale; bool m_use_default_locale; }; #endif // ifndef STRING2BIGINT_FILTER_H diff --git a/src/backend/core/datatypes/String2DateTimeFilter.cpp b/src/backend/core/datatypes/String2DateTimeFilter.cpp index e5c34ec98..8963b6d90 100644 --- a/src/backend/core/datatypes/String2DateTimeFilter.cpp +++ b/src/backend/core/datatypes/String2DateTimeFilter.cpp @@ -1,161 +1,161 @@ /*************************************************************************** File : String2DateTimeFilter.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2007 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2007 Knut Franke (knut.franke@gmx.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Conversion filter QString -> QDateTime. ***************************************************************************/ /*************************************************************************** * * * 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 "String2DateTimeFilter.h" #include #include "backend/lib/XmlStreamReader.h" #include #include #include #include #include class String2DateTimeFilterSetFormatCmd : public QUndoCommand { public: String2DateTimeFilterSetFormatCmd(String2DateTimeFilter* target, const QString &new_format); void redo() override; void undo() override; private: String2DateTimeFilter* m_target; QString m_other_format; }; AbstractColumn::ColumnMode String2DateTimeFilter::columnMode() const { - return AbstractColumn::DateTime; + return AbstractColumn::ColumnMode::DateTime; } QDateTime String2DateTimeFilter::dateTimeAt(int row) const { if (!m_inputs.value(0)) return QDateTime(); QString input_value = m_inputs.value(0)->textAt(row); if (input_value.isEmpty()) return QDateTime(); // first try the selected format string m_format QDateTime result = QDateTime::fromString(input_value, m_format); if (result.isValid()) return result; // fallback: // try other format strings built from date_formats and time_formats // comma and space are valid separators between date and time QStringList strings = input_value.simplified().split(',', QString::SkipEmptyParts); if (strings.size() == 1) strings = strings.at(0).split(' ', QString::SkipEmptyParts); if (strings.size() < 1) return result; // invalid date/time from first attempt QDate date_result; QTime time_result; QString date_string = strings.at(0).trimmed(); QString time_string; if (strings.size() > 1) time_string = strings.at(1).trimmed(); else time_string = date_string; // try to find a valid date for (const auto& format : AbstractColumn::dateFormats()) { date_result = QDate::fromString(date_string, format); if (date_result.isValid()) break; } // try to find a valid time for (const auto& format : AbstractColumn::timeFormats()) { time_result = QTime::fromString(time_string, format); if (time_result.isValid()) break; } if (!date_result.isValid() && time_result.isValid()) date_result.setDate(1900,1,1); // this is what QDateTime does e.g. for QDateTime::fromString("00:00","hh:mm"); else if (date_result.isValid() && !time_result.isValid()) time_result = QTime(0, 0, 0, 0); return QDateTime(date_result, time_result); } QDate String2DateTimeFilter::dateAt(int row) const { return dateTimeAt(row).date(); } QTime String2DateTimeFilter::timeAt(int row) const { return dateTimeAt(row).time(); } bool String2DateTimeFilter::inputAcceptable(int, const AbstractColumn* source) { - return source->columnMode() == AbstractColumn::Text; + return source->columnMode() == AbstractColumn::ColumnMode::Text; } void String2DateTimeFilter::writeExtraAttributes(QXmlStreamWriter* writer) const { writer->writeAttribute("format", format()); } bool String2DateTimeFilter::load(XmlStreamReader* reader, bool preview) { if (preview) return true; QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value(reader->namespaceUri().toString(), "format").toString(); if (AbstractSimpleFilter::load(reader, preview)) setFormat(str); else return false; return !reader->hasError(); } void String2DateTimeFilter::setFormat(const QString& format) { exec(new String2DateTimeFilterSetFormatCmd(this, format)); } String2DateTimeFilterSetFormatCmd::String2DateTimeFilterSetFormatCmd(String2DateTimeFilter* target, const QString &new_format) : m_target(target), m_other_format(new_format) { if (m_target->parentAspect()) setText(i18n("%1: set date-time format to %2", m_target->parentAspect()->name(), new_format)); else setText(i18n("set date-time format to %1", new_format)); } void String2DateTimeFilterSetFormatCmd::redo() { QString tmp = m_target->m_format; m_target->m_format = m_other_format; m_other_format = tmp; emit m_target->formatChanged(); } void String2DateTimeFilterSetFormatCmd::undo() { redo(); } diff --git a/src/backend/core/datatypes/String2DayOfWeekFilter.h b/src/backend/core/datatypes/String2DayOfWeekFilter.h index 33a0f19de..9363316f7 100644 --- a/src/backend/core/datatypes/String2DayOfWeekFilter.h +++ b/src/backend/core/datatypes/String2DayOfWeekFilter.h @@ -1,84 +1,84 @@ /*************************************************************************** File : String2DayOfWeekFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs*gmx.net Description : Conversion filter String -> QDateTime, interpreting the input as days of the week (either numeric or "Mon" etc). ***************************************************************************/ /*************************************************************************** * * * 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 STRING2DAYOFWEEK_FILTER_H #define STRING2DAYOFWEEK_FILTER_H #include "../AbstractSimpleFilter.h" class QDateTime; //! Conversion filter String -> QDateTime, interpreting the input as days of the week (either numeric or "Mon" etc). class String2DayOfWeekFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { return dateTimeAt(row).date(); } QTime timeAt(int row) const override{ return dateTimeAt(row).time(); } QDateTime dateTimeAt(int row) const override { if (!m_inputs.value(0)) return QDateTime(); QString input_value = m_inputs.value(0)->textAt(row); if (input_value.isEmpty()) return QDateTime(); bool ok; int day_value = input_value.toInt(&ok); if(!ok) { QDate temp = QDate::fromString(input_value, "ddd"); if(!temp.isValid()) temp = QDate::fromString(input_value, "dddd"); if(!temp.isValid()) return QDateTime(); else day_value = temp.dayOfWeek(); } // Don't use Julian days here since support for years < 1 is bad // Use 1900-01-01 instead (a Monday) QDate result_date = QDate(1900,1,1).addDays(day_value - 1); QTime result_time = QTime(0,0,0,0); return QDateTime(result_date, result_time); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Day; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Day; } protected: bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Text; + return source->columnMode() == AbstractColumn::ColumnMode::Text; } }; #endif // ifndef STRING2DAYOFWEEK_FILTER_H diff --git a/src/backend/core/datatypes/String2DoubleFilter.h b/src/backend/core/datatypes/String2DoubleFilter.h index 87b5cff46..ae2d23f55 100644 --- a/src/backend/core/datatypes/String2DoubleFilter.h +++ b/src/backend/core/datatypes/String2DoubleFilter.h @@ -1,76 +1,76 @@ /*************************************************************************** File : String2DoubleFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke Email (use @ for *) : knut.franke*gmx.de Description : Locale-aware conversion filter QString -> double. ***************************************************************************/ /*************************************************************************** * * * 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 STRING2DOUBLE_FILTER_H #define STRING2DOUBLE_FILTER_H #include "../AbstractSimpleFilter.h" #include #include //! Locale-aware conversion filter QString -> double. class String2DoubleFilter : public AbstractSimpleFilter { Q_OBJECT public: String2DoubleFilter() : m_use_default_locale(true) {} void setNumericLocale(const QLocale& locale) { m_numeric_locale = locale; m_use_default_locale = false; } void setNumericLocaleToDefault() { m_use_default_locale = true; } double valueAt(int row) const override { DEBUG("String2Double::valueAt()"); if (!m_inputs.value(0)) return 0; double result; bool valid; if (m_use_default_locale) // we need a new QLocale instance here in case the default changed since the last call result = QLocale().toDouble(m_inputs.value(0)->textAt(row), &valid); else result = m_numeric_locale.toDouble(m_inputs.value(0)->textAt(row), &valid); if (valid) return result; return NAN; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Numeric; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Numeric; } protected: //! Using typed ports: only string inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Text; + return source->columnMode() == AbstractColumn::ColumnMode::Text; } private: QLocale m_numeric_locale; bool m_use_default_locale; }; #endif // ifndef STRING2DOUBLE_FILTER_H diff --git a/src/backend/core/datatypes/String2IntegerFilter.h b/src/backend/core/datatypes/String2IntegerFilter.h index 07c4d7a90..96bd6a547 100644 --- a/src/backend/core/datatypes/String2IntegerFilter.h +++ b/src/backend/core/datatypes/String2IntegerFilter.h @@ -1,76 +1,76 @@ /*************************************************************************** File : String2IntegerFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Locale-aware conversion filter QString -> int. ***************************************************************************/ /*************************************************************************** * * * 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 STRING2INTEGER_FILTER_H #define STRING2INTEGER_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Locale-aware conversion filter QString -> int. class String2IntegerFilter : public AbstractSimpleFilter { Q_OBJECT public: String2IntegerFilter() : m_use_default_locale(true) {} void setNumericLocale(const QLocale& locale) { m_numeric_locale = locale; m_use_default_locale = false; } void setNumericLocaleToDefault() { m_use_default_locale = true; } int integerAt(int row) const override { //DEBUG("String2Integer::integerAt()"); if (!m_inputs.value(0)) return 0; int result; bool valid; QString textValue = m_inputs.value(0)->textAt(row); //DEBUG(" textValue = " << STDSTRING(textValue)); if (m_use_default_locale) // we need a new QLocale instance here in case the default changed since the last call result = QLocale().toInt(textValue, &valid); else result = m_numeric_locale.toInt(textValue, &valid); //DEBUG(" result = " << result << " valid = " << valid); if (valid) return result; return 0; } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Integer; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Integer; } protected: //! Using typed ports: only string inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Text; + return source->columnMode() == AbstractColumn::ColumnMode::Text; } private: QLocale m_numeric_locale; bool m_use_default_locale; }; #endif // ifndef STRING2INTEGER_FILTER_H diff --git a/src/backend/core/datatypes/String2MonthFilter.h b/src/backend/core/datatypes/String2MonthFilter.h index 4fd85436e..67446341b 100644 --- a/src/backend/core/datatypes/String2MonthFilter.h +++ b/src/backend/core/datatypes/String2MonthFilter.h @@ -1,83 +1,83 @@ /*************************************************************************** File : String2MonthFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2007 by Knut Franke, Tilman Benkert Email (use @ for *) : knut.franke*gmx.de, thzs*gmx.net Description : Conversion filter String -> QDateTime, interpreting the input as months of the year (either numeric or "Jan" etc). ***************************************************************************/ /*************************************************************************** * * * 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 STRING2MONTH_FILTER_H #define STRING2MONTH_FILTER_H #include "../AbstractSimpleFilter.h" class QDateTime; //! Conversion filter String -> QDateTime, interpreting the input as months of the year (either numeric or "Jan" etc). class String2MonthFilter : public AbstractSimpleFilter { Q_OBJECT public: QDate dateAt(int row) const override { return dateTimeAt(row).date(); } QTime timeAt(int row) const override { return dateTimeAt(row).time(); } QDateTime dateTimeAt(int row) const override { if (!m_inputs.value(0)) return QDateTime(); QString input_value = m_inputs.value(0)->textAt(row); bool ok; int month_value = input_value.toInt(&ok); if(!ok) { QDate temp = QDate::fromString(input_value, "MMM"); if(!temp.isValid()) temp = QDate::fromString(input_value, "MMMM"); if(!temp.isValid()) return QDateTime(); else month_value = temp.month(); } // Don't use Julian days here since support for years < 1 is bad // Use 1900-01-01 instead QDate result_date = QDate(1900,1,1).addMonths(month_value - 1); QTime result_time = QTime(0,0,0,0); return QDateTime(result_date, result_time); } //! Return the data type of the column - AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Month; } + AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::ColumnMode::Month; } protected: bool inputAcceptable(int, const AbstractColumn *source) override { - return source->columnMode() == AbstractColumn::Text; + return source->columnMode() == AbstractColumn::ColumnMode::Text; } }; #endif // ifndef STRING2MONTH_FILTER_H diff --git a/src/backend/datapicker/DatapickerCurve.cpp b/src/backend/datapicker/DatapickerCurve.cpp index 29ea74b68..0acb0603c 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); // point properties d->pointStyle = (Symbol::Style)group.readEntry("PointStyle", (int)Symbol::Cross); d->pointSize = group.readEntry("Size", Worksheet::convertToSceneUnits(7, Worksheet::Point)); d->pointRotationAngle = group.readEntry("Rotation", 0.0); d->pointOpacity = group.readEntry("Opacity", 1.0); d->pointBrush.setStyle( (Qt::BrushStyle)group.readEntry("FillingStyle", (int)Qt::NoBrush) ); d->pointBrush.setColor( group.readEntry("FillingColor", QColor(Qt::black)) ); d->pointPen.setStyle( (Qt::PenStyle)group.readEntry("BorderStyle", (int)Qt::SolidLine) ); d->pointPen.setColor( group.readEntry("BorderColor", QColor(Qt::red)) ); d->pointPen.setWidthF( group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Point)) ); d->pointErrorBarSize = group.readEntry("ErrorBarSize", Worksheet::convertToSceneUnits(8, Worksheet::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->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::Numeric); + 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) { xLabel = QLatin1String("r"); yLabel = QLatin1String("y(deg)"); } else if (type == DatapickerImage::PolarInRadians) { xLabel = QLatin1String("r"); yLabel = QLatin1String("y(rad)"); } else if (type == DatapickerImage::LogarithmicX) { xLabel = QLatin1String("log(x)"); yLabel = QLatin1String("y"); } else if (type == DatapickerImage::LogarithmicY) { xLabel = QLatin1String("x"); yLabel = QLatin1String("log(y)"); } if (type == DatapickerImage::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 ) setPlusDeltaXColumn(appendColumn(QLatin1String("+delta_x"))); else if ( d->plusDeltaXColumn && errors.x == NoError ) { d->plusDeltaXColumn->remove(); d->plusDeltaXColumn = nullptr; } if ( errors.x == AsymmetricError && !d->minusDeltaXColumn ) setMinusDeltaXColumn(appendColumn(QLatin1String("-delta_x"))); else if ( d->minusDeltaXColumn && errors.x != AsymmetricError ) { d->minusDeltaXColumn->remove(); d->minusDeltaXColumn = nullptr; } if ( errors.y != NoError && !d->plusDeltaYColumn ) setPlusDeltaYColumn(appendColumn(QLatin1String("+delta_y"))); else if ( d->plusDeltaYColumn && errors.y == NoError ) { d->plusDeltaYColumn->remove(); d->plusDeltaYColumn = nullptr; } if ( errors.y == AsymmetricError && !d->minusDeltaYColumn ) setMinusDeltaYColumn(appendColumn(QLatin1String("-delta_y"))); else if ( d->minusDeltaYColumn && errors.y != 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->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/datasources/LiveDataSource.cpp b/src/backend/datasources/LiveDataSource.cpp index 247db15da..ce5239cdd 100644 --- a/src/backend/datasources/LiveDataSource.cpp +++ b/src/backend/datasources/LiveDataSource.cpp @@ -1,943 +1,943 @@ /*************************************************************************** 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) m_updateTimer->stop(); else m_pending = false; read(); //restart the timer after update if (m_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) { 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: { 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: 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: delete m_device; m_device = new QFile(m_fileName); break; case 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: 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) connect(m_udpSocket, &QUdpSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_udpSocket, static_cast(&QUdpSocket::error), this, &LiveDataSource::tcpSocketError); break; case 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: 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) connect(m_serialPort, &QSerialPort::readyRead, this, &LiveDataSource::readyRead); connect(m_serialPort, static_cast(&QSerialPort::error), this, &LiveDataSource::serialPortError); break; case MQTT: break; } m_prepared = true; } qint64 bytes = 0; switch (m_sourceType) { case 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: 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: 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: 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: DEBUG(" Reading from serial port"); // reading data here if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); break; case 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) 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: 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: writer->writeAttribute("baudRate", QString::number(m_baudRate)); writer->writeAttribute("serialPortName", m_serialPortName); break; case NetworkTcpSocket: case NetworkUdpSocket: writer->writeAttribute("host", m_host); writer->writeAttribute("port", QString::number(m_port)); break; case LocalSocket: break; case MQTT: break; default: break; } writer->writeAttribute("updateType", QString::number(m_updateType)); writer->writeAttribute("readingType", QString::number(m_readingType)); writer->writeAttribute("sourceType", QString::number(m_sourceType)); writer->writeAttribute("keepNValues", QString::number(m_keepNValues)); if (m_updateType == TimeInterval) writer->writeAttribute("updateInterval", QString::number(m_updateInterval)); if (m_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) { str = attribs.value("updateInterval").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("updateInterval").toString()); else m_updateInterval = str.toInt(); } if (m_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: 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: 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: break; case LocalSocket: break; default: 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::Text); + 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/filters/AbstractFileFilter.cpp b/src/backend/datasources/filters/AbstractFileFilter.cpp index a3d68a224..871268381 100644 --- a/src/backend/datasources/filters/AbstractFileFilter.cpp +++ b/src/backend/datasources/filters/AbstractFileFilter.cpp @@ -1,188 +1,188 @@ /*************************************************************************** File : AbstractFileFilter.h Project : LabPlot Description : file I/O-filter related interface -------------------------------------------------------------------- Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 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/filters/AbstractFileFilter.h" #include "backend/datasources/filters/NgspiceRawAsciiFilter.h" #include "backend/datasources/filters/NgspiceRawBinaryFilter.h" #include "backend/lib/macros.h" #include #include #include #include #include bool AbstractFileFilter::isNan(const QString& s) { const static QStringList nanStrings{"NA", "NAN", "N/A", "-NA", "-NAN", "NULL"}; if (nanStrings.contains(s, Qt::CaseInsensitive)) return true; return false; } AbstractColumn::ColumnMode AbstractFileFilter::columnMode(const QString& valueString, const QString& dateTimeFormat) { QLocale locale; return columnMode(valueString, dateTimeFormat, locale); } AbstractColumn::ColumnMode AbstractFileFilter::columnMode(const QString& valueString, const QString& dateTimeFormat, QLocale::Language lang) { QLocale locale(lang); return columnMode(valueString, dateTimeFormat, locale); } AbstractColumn::ColumnMode AbstractFileFilter::columnMode(const QString& valueString, const QString& dateTimeFormat, const QLocale& locale) { //TODO: use BigInt as default integer? if (valueString.size() == 0) // empty string treated as integer (meaning the non-empty strings will determine the data type) - return AbstractColumn::Integer; + return AbstractColumn::ColumnMode::Integer; if (isNan(valueString)) - return AbstractColumn::Numeric; + return AbstractColumn::ColumnMode::Numeric; const std::string stdValueString = valueString.toStdString(); // check if integer first bool ok; int intValue = locale.toInt(valueString, &ok); DEBUG("string " << stdValueString << ": toInt " << intValue << "?:" << ok); Q_UNUSED(intValue) if (ok || isNan(valueString)) - return AbstractColumn::Integer; + return AbstractColumn::ColumnMode::Integer; //check big integer qint64 bigIntValue = locale.toLongLong(valueString, &ok); DEBUG("string " << stdValueString << ": toBigInt " << bigIntValue << "?:" << ok); Q_UNUSED(bigIntValue) if (ok || isNan(valueString)) - return AbstractColumn::BigInt; + return AbstractColumn::ColumnMode::BigInt; //try to convert to a double - AbstractColumn::ColumnMode mode = AbstractColumn::Numeric; + auto mode = AbstractColumn::ColumnMode::Numeric; double value = locale.toDouble(valueString, &ok); DEBUG("string " << stdValueString << ": toDouble " << value << "?:" << ok); Q_UNUSED(value) //if not a number, check datetime. if that fails: string if (!ok) { QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); if (valueDateTime.isValid()) - mode = AbstractColumn::DateTime; + mode = AbstractColumn::ColumnMode::DateTime; else - mode = AbstractColumn::Text; + mode = AbstractColumn::ColumnMode::Text; } return mode; } /* returns the list of all supported locales for numeric data */ QStringList AbstractFileFilter::numberFormats() { QStringList formats; for (int l = 0; l < ENUM_COUNT(QLocale, Language); ++l) formats << QLocale::languageToString((QLocale::Language)l); return formats; } AbstractFileFilter::FileType AbstractFileFilter::fileType(const QString& fileName) { QString fileInfo; #ifndef HAVE_WINDOWS //check, if we can guess the file type by content QProcess proc; proc.start("file", QStringList() << "-b" << "-z" << fileName); if (!proc.waitForFinished(1000)) { proc.kill(); DEBUG("ERROR: reading file type of file" << STDSTRING(fileName)); return Binary; } fileInfo = proc.readLine(); #endif FileType fileType; QByteArray imageFormat = QImageReader::imageFormat(fileName); if (fileInfo.contains(QLatin1String("JSON")) || fileName.endsWith(QLatin1String("json"), Qt::CaseInsensitive) //json file can be compressed. add all formats supported by KFilterDev, \sa KCompressionDevice::CompressionType || fileName.endsWith(QLatin1String("json.gz"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("json.bz2"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("json.lzma"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("json.xz"), Qt::CaseInsensitive) ) { //*.json files can be recognized as ASCII. so, do the check for the json-extension as first. fileType = JSON; } else if (fileInfo.contains(QLatin1String("ASCII")) || fileName.endsWith(QLatin1String("txt"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("csv"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("dat"), Qt::CaseInsensitive) || fileInfo.contains(QLatin1String("compressed data"))/* for gzipped ascii data */ ) { if (NgspiceRawAsciiFilter::isNgspiceAsciiFile(fileName)) fileType = NgspiceRawAscii; else //probably ascii data fileType = Ascii; } else if (fileInfo.contains(QLatin1String("Hierarchical Data Format")) || fileName.endsWith(QLatin1String("h5"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("hdf"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("hdf5"), Qt::CaseInsensitive) ) fileType = HDF5; else if (fileInfo.contains(QLatin1String("NetCDF Data Format")) || fileName.endsWith(QLatin1String("nc"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("netcdf"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("cdf"), Qt::CaseInsensitive)) fileType = NETCDF; else if (fileInfo.contains(QLatin1String("FITS image data")) || fileName.endsWith(QLatin1String("fits"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("fit"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("fts"), Qt::CaseInsensitive)) fileType = FITS; else if (fileInfo.contains(QLatin1String("ROOT")) //can be "ROOT Data Format" or "ROOT file Version ??? (Compression: 1)" || fileName.endsWith(QLatin1String("root"), Qt::CaseInsensitive)) // TODO find out file description fileType = ROOT; else if (fileInfo.contains("image") || fileInfo.contains("bitmap") || !imageFormat.isEmpty()) fileType = Image; else if (NgspiceRawBinaryFilter::isNgspiceBinaryFile(fileName)) fileType = NgspiceRawBinary; else fileType = Binary; return fileType; } /*! returns the list of all supported data file formats */ QStringList AbstractFileFilter::fileTypes() { return (QStringList() << i18n("ASCII data") << i18n("Binary data") << i18n("Image") << i18n("Hierarchical Data Format 5 (HDF5)") << i18n("Network Common Data Format (NetCDF)") << i18n("Flexible Image Transport System Data Format (FITS)") << i18n("JSON data") << i18n("ROOT (CERN) Histograms") << "Ngspice RAW ASCII" << "Ngspice RAW Binary" ); } diff --git a/src/backend/datasources/filters/AsciiFilter.cpp b/src/backend/datasources/filters/AsciiFilter.cpp index 318c8f539..7e18a0836 100644 --- a/src/backend/datasources/filters/AsciiFilter.cpp +++ b/src/backend/datasources/filters/AsciiFilter.cpp @@ -1,2825 +1,2825 @@ /*************************************************************************** File : AsciiFilter.cpp Project : LabPlot Description : ASCII I/O-filter -------------------------------------------------------------------- Copyright : (C) 2009-2020 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-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 "backend/datasources/LiveDataSource.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/AsciiFilterPrivate.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #include "backend/datasources/MQTTTopic.h" #endif #include #include #include #if defined(Q_OS_LINUX) || defined(Q_OS_BSD4) #include #include #endif #include /*! \class AsciiFilter \brief Manages the import/export of data organized as columns (vectors) from/to an ASCII-file. \ingroup datasources */ AsciiFilter::AsciiFilter() : AbstractFileFilter(Ascii), d(new AsciiFilterPrivate(this)) {} AsciiFilter::~AsciiFilter() = default; /*! reads the content of the device \c device. */ void AsciiFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromDevice(device, dataSource, importMode, lines); } void AsciiFilter::readFromLiveDeviceNotFile(QIODevice &device, AbstractDataSource* dataSource) { d->readFromLiveDevice(device, dataSource); } qint64 AsciiFilter::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) { return d->readFromLiveDevice(device, dataSource, from); } #ifdef HAVE_MQTT QVector AsciiFilter::preview(const QString& message) { return d->preview(message); } /*! reads the content of a message received by the topic. */ void AsciiFilter::readMQTTTopic(const QString& message, AbstractDataSource* dataSource) { d->readMQTTTopic(message, dataSource); } /*! Returns the statistical data, that the MQTTTopic needs for the will message. */ QString AsciiFilter::MQTTColumnStatistics(const MQTTTopic* topic) const { return d->MQTTColumnStatistics(topic); } /*! Returns the column mode of the last column (the value column of the MQTTTopic). */ AbstractColumn::ColumnMode AsciiFilter::MQTTColumnMode() const { return d->MQTTColumnMode(); } /*! After the MQTTTopic is loaded, prepares the filter for reading. */ void AsciiFilter::setPreparedForMQTT(bool prepared, MQTTTopic* topic, const QString& separator) { d->setPreparedForMQTT(prepared, topic, separator); } #endif /*! returns the separator used by the filter. */ QString AsciiFilter::separator() const { return d->separator(); } /*! returns the separator used by the filter. */ int AsciiFilter::isPrepared() { return d->isPrepared(); } /*! reads the content of the file \c fileName. */ void AsciiFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } QVector AsciiFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } QVector AsciiFilter::preview(QIODevice& device) { return d->preview(device); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ //void AsciiFilter::read(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { // d->read(fileName, dataSource, importMode); //} /*! writes the content of the data source \c dataSource to the file \c fileName. */ void AsciiFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /*! loads the predefined filter settings for \c filterName */ void AsciiFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void AsciiFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /*! returns the list with the names of all saved (system wide or user defined) filter settings. */ QStringList AsciiFilter::predefinedFilters() { return QStringList(); } /*! returns the list of all predefined separator characters. */ QStringList AsciiFilter::separatorCharacters() { return (QStringList() << "auto" << "TAB" << "SPACE" << "," << ";" << ":" << ",TAB" << ";TAB" << ":TAB" << ",SPACE" << ";SPACE" << ":SPACE" << "2xSPACE" << "3xSPACE" << "4xSPACE" << "2xTAB"); } /*! returns the list of all predefined comment characters. */ QStringList AsciiFilter::commentCharacters() { return (QStringList() << "#" << "!" << "//" << "+" << "c" << ":" << ";"); } /*! returns the list of all predefined data types. */ QStringList AsciiFilter::dataTypes() { const QMetaObject& mo = AbstractColumn::staticMetaObject; const QMetaEnum& me = mo.enumerator(mo.indexOfEnumerator("ColumnMode")); QStringList list; for (int i = 0; i <= 100; ++i) // me.keyCount() does not work because we have holes in enum if (me.valueToKey(i)) list << me.valueToKey(i); return list; } QString AsciiFilter::fileInfoString(const QString& fileName) { QString info(i18n("Number of columns: %1", AsciiFilter::columnNumber(fileName))); info += QLatin1String("
"); info += i18n("Number of lines: %1", AsciiFilter::lineNumber(fileName)); return info; } /*! returns the number of columns in the file \c fileName. */ int AsciiFilter::columnNumber(const QString& fileName, const QString& separator) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { DEBUG("Could not open file " << STDSTRING(fileName) << " for determining number of columns"); return -1; } QString line = device.readLine(); line.remove(QRegularExpression(QStringLiteral("[\\n\\r]"))); QStringList lineStringList; if (separator.length() > 0) lineStringList = line.split(separator); else lineStringList = line.split(QRegularExpression(QStringLiteral("\\s+"))); DEBUG("number of columns : " << lineStringList.size()); return lineStringList.size(); } size_t AsciiFilter::lineNumber(const QString& fileName) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { DEBUG("Could not open file " << STDSTRING(fileName) << " to determine number of lines"); return 0; } // if (!device.canReadLine()) // return -1; size_t lineCount = 0; #if defined(Q_OS_LINUX) || defined(Q_OS_BSD4) //on linux and BSD use wc, if available, which is much faster than counting lines in the file if (device.compressionType() == KCompressionDevice::None && !QStandardPaths::findExecutable(QLatin1String("wc")).isEmpty()) { QProcess wc; wc.start(QLatin1String("wc"), QStringList() << QLatin1String("-l") << fileName); size_t lineCount = 0; while (wc.waitForReadyRead()) { QString line(wc.readLine()); // wc on macOS has leading spaces: use SkipEmptyParts lineCount = line.split(' ', QString::SkipEmptyParts)[0].toInt(); } return lineCount; } #endif while (!device.atEnd()) { device.readLine(); lineCount++; } return lineCount; } /*! returns the number of lines in the device \c device and 0 if sequential. resets the position to 0! */ size_t AsciiFilter::lineNumber(QIODevice& device) const { if (device.isSequential()) return 0; // if (!device.canReadLine()) // DEBUG("WARNING in AsciiFilter::lineNumber(): device cannot 'readLine()' but using it anyway."); size_t lineCount = 0; device.seek(0); if (d->readingFile) lineCount = lineNumber(d->readingFileName); else { while (!device.atEnd()) { device.readLine(); lineCount++; } } device.seek(0); return lineCount; } void AsciiFilter::setCommentCharacter(const QString& s) { d->commentCharacter = s; } QString AsciiFilter::commentCharacter() const { return d->commentCharacter; } void AsciiFilter::setSeparatingCharacter(const QString& s) { d->separatingCharacter = s; } QString AsciiFilter::separatingCharacter() const { return d->separatingCharacter; } void AsciiFilter::setDateTimeFormat(const QString &f) { d->dateTimeFormat = f; } QString AsciiFilter::dateTimeFormat() const { return d->dateTimeFormat; } void AsciiFilter::setNumberFormat(QLocale::Language lang) { d->numberFormat = lang; } QLocale::Language AsciiFilter::numberFormat() const { return d->numberFormat; } void AsciiFilter::setAutoModeEnabled(const bool b) { d->autoModeEnabled = b; } bool AsciiFilter::isAutoModeEnabled() const { return d->autoModeEnabled; } void AsciiFilter::setHeaderEnabled(const bool b) { d->headerEnabled = b; } bool AsciiFilter::isHeaderEnabled() const { return d->headerEnabled; } void AsciiFilter::setSkipEmptyParts(const bool b) { d->skipEmptyParts = b; } bool AsciiFilter::skipEmptyParts() const { return d->skipEmptyParts; } void AsciiFilter::setCreateIndexEnabled(bool b) { d->createIndexEnabled = b; } bool AsciiFilter::createIndexEnabled() const { return d->createIndexEnabled; } void AsciiFilter::setCreateTimestampEnabled(bool b) { d->createTimestampEnabled = b; } bool AsciiFilter::createTimestampEnabled() const { return d->createTimestampEnabled; } void AsciiFilter::setSimplifyWhitespacesEnabled(bool b) { d->simplifyWhitespacesEnabled = b; } bool AsciiFilter::simplifyWhitespacesEnabled() const { return d->simplifyWhitespacesEnabled; } void AsciiFilter::setNaNValueToZero(bool b) { if (b) d->nanValue = 0; else d->nanValue = std::numeric_limits::quiet_NaN(); } bool AsciiFilter::NaNValueToZeroEnabled() const { return (d->nanValue == 0); } void AsciiFilter::setRemoveQuotesEnabled(bool b) { d->removeQuotesEnabled = b; } bool AsciiFilter::removeQuotesEnabled() const { return d->removeQuotesEnabled; } void AsciiFilter::setVectorNames(const QString& s) { d->vectorNames.clear(); if (!s.simplified().isEmpty()) d->vectorNames = s.simplified().split(' '); } void AsciiFilter::setVectorNames(const QStringList& list) { d->vectorNames = list; } QStringList AsciiFilter::vectorNames() const { return d->vectorNames; } QVector AsciiFilter::columnModes() { return d->columnModes; } void AsciiFilter::setStartRow(const int r) { d->startRow = r; } int AsciiFilter::startRow() const { return d->startRow; } void AsciiFilter::setEndRow(const int r) { d->endRow = r; } int AsciiFilter::endRow() const { return d->endRow; } void AsciiFilter::setStartColumn(const int c) { d->startColumn = c; } int AsciiFilter::startColumn() const { return d->startColumn; } void AsciiFilter::setEndColumn(const int c) { d->endColumn = c; } int AsciiFilter::endColumn() const { return d->endColumn; } //##################################################################### //################### Private implementation ########################## //##################################################################### AsciiFilterPrivate::AsciiFilterPrivate(AsciiFilter* owner) : q(owner) { } /*! * get a single line from device */ QStringList AsciiFilterPrivate::getLineString(QIODevice& device) { QString line; do { // skip comment lines in data lines if (!device.canReadLine()) DEBUG("WARNING in AsciiFilterPrivate::getLineString(): device cannot 'readLine()' but using it anyway."); // line = device.readAll(); line = device.readLine(); } while (!commentCharacter.isEmpty() && line.startsWith(commentCharacter)); line.remove(QRegularExpression(QStringLiteral("[\\n\\r]"))); // remove any newline DEBUG("data line : \'" << STDSTRING(line) << '\''); QStringList lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); //TODO: remove quotes here? if (simplifyWhitespacesEnabled) { for (int i = 0; i < lineStringList.size(); ++i) lineStringList[i] = lineStringList[i].simplified(); } QDEBUG("data line, parsed: " << lineStringList); return lineStringList; } /*! * returns -1 if the device couldn't be opened, 1 if the current read position in the device is at the end and 0 otherwise. */ int AsciiFilterPrivate::prepareDeviceToRead(QIODevice& device) { DEBUG("AsciiFilterPrivate::prepareDeviceToRead(): is sequential = " << device.isSequential() << ", can readLine = " << device.canReadLine()); if (!device.open(QIODevice::ReadOnly)) return -1; if (device.atEnd() && !device.isSequential()) // empty file return 1; ///////////////////////////////////////////////////////////////// // Parse the first line: // Determine the number of columns, create the columns and use (if selected) the first row to name them QString firstLine; // skip the comment lines and read the first line if (!commentCharacter.isEmpty()) { do { if (!device.canReadLine()) DEBUG("WARNING in AsciiFilterPrivate::prepareDeviceToRead(): device cannot 'readLine()' but using it anyway."); if (device.atEnd()) { DEBUG("device at end! Giving up."); if (device.isSequential()) break; else return 1; } firstLine = device.readLine(); } while (firstLine.startsWith(commentCharacter) || firstLine.simplified().isEmpty()); } else firstLine = device.readLine(); // navigate to the line where we asked to start reading from DEBUG(" Skipping " << startRow - 1 << " lines"); for (int i = 0; i < startRow - 1; ++i) { if (!device.canReadLine()) DEBUG("WARNING in AsciiFilterPrivate::prepareDeviceToRead(): device cannot 'readLine()' but using it anyway."); if (device.atEnd()) { DEBUG("device at end! Giving up."); if (device.isSequential()) break; else return 1; } firstLine = device.readLine(); DEBUG(" line = " << STDSTRING(firstLine)); } DEBUG(" device position after first line and comments = " << device.pos()); firstLine.remove(QRegularExpression(QStringLiteral("[\\n\\r]"))); // remove any newline if (removeQuotesEnabled) firstLine = firstLine.remove(QLatin1Char('"')); //TODO: this doesn't work, the split below introduces whitespaces again // if (simplifyWhitespacesEnabled) // firstLine = firstLine.simplified(); DEBUG("First line: \'" << STDSTRING(firstLine) << '\''); // determine separator and split first line QStringList firstLineStringList; if (separatingCharacter == "auto") { DEBUG("automatic separator"); if (firstLine.indexOf(QLatin1Char('\t')) != -1) { //in case we have a mix of tabs and spaces in the header, give the tab character the preference m_separator = QLatin1Char('\t'); firstLineStringList = firstLine.split(m_separator, (QString::SplitBehavior)skipEmptyParts); } else { const QRegularExpression regExp(QStringLiteral("[,;:]?\\s+")); firstLineStringList = firstLine.split(regExp, (QString::SplitBehavior)skipEmptyParts); if (!firstLineStringList.isEmpty()) { int length1 = firstLineStringList.at(0).length(); if (firstLineStringList.size() > 1) m_separator = firstLine.mid(length1, 1); else m_separator = ' '; } } } else { // use given separator // replace symbolic "TAB" with '\t' m_separator = separatingCharacter.replace(QLatin1String("2xTAB"), "\t\t", Qt::CaseInsensitive); m_separator = separatingCharacter.replace(QLatin1String("TAB"), "\t", Qt::CaseInsensitive); // replace symbolic "SPACE" with ' ' m_separator = m_separator.replace(QLatin1String("2xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("3xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("4xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); firstLineStringList = firstLine.split(m_separator, (QString::SplitBehavior)skipEmptyParts); } DEBUG("separator: \'" << STDSTRING(m_separator) << '\''); DEBUG("number of columns: " << firstLineStringList.size()); QDEBUG("first line: " << firstLineStringList); DEBUG("headerEnabled: " << headerEnabled); //optionally, remove potential spaces in the first line //TODO: this part should be obsolete actually if we do firstLine = firstLine.simplified(); above... if (simplifyWhitespacesEnabled) { for (int i = 0; i < firstLineStringList.size(); ++i) firstLineStringList[i] = firstLineStringList[i].simplified(); } //in GUI in AsciiOptionsWidget we start counting from 1, subtract 1 here to start from zero m_actualStartRow = startRow - 1; if (headerEnabled) { // use first line to name vectors vectorNames = firstLineStringList; ++m_actualStartRow; } // set range to read if (endColumn == -1) { if (headerEnabled || vectorNames.size() == 0) endColumn = firstLineStringList.size(); // last column else //number of vector names provided in the import dialog (not more than the maximal number of columns in the file) endColumn = qMin(vectorNames.size(), firstLineStringList.size()); } if (endColumn < startColumn) m_actualCols = 0; else m_actualCols = endColumn - startColumn + 1; if (createIndexEnabled) { vectorNames.prepend(i18n("Index")); m_actualCols++; } QDEBUG("vector names =" << vectorNames); //TEST: readline-seek-readline fails /* qint64 testpos = device.pos(); DEBUG("read data line @ pos " << testpos << " : " << STDSTRING(device.readLine())); device.seek(testpos); testpos = device.pos(); DEBUG("read data line again @ pos " << testpos << " : " << STDSTRING(device.readLine())); */ ///////////////////////////////////////////////////////////////// // parse first data line to determine data type for each column // if the first line was already parsed as the header, read the next line if (headerEnabled && !device.isSequential()) firstLineStringList = getLineString(device); columnModes.resize(m_actualCols); int col = 0; if (createIndexEnabled) { - columnModes[0] = AbstractColumn::Integer; + columnModes[0] = AbstractColumn::ColumnMode::Integer; col = 1; } for (auto& valueString : firstLineStringList) { // parse columns available in first data line if (simplifyWhitespacesEnabled) valueString = valueString.simplified(); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); if (col == m_actualCols) break; columnModes[col++] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); } // parsing more lines to better determine data types for (unsigned int i = 0; i < m_dataTypeLines; ++i) { if (device.atEnd()) // EOF reached break; firstLineStringList = getLineString(device); createIndexEnabled ? col = 1 : col = 0; for (auto& valueString : firstLineStringList) { if (simplifyWhitespacesEnabled) valueString = valueString.simplified(); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); if (col == m_actualCols) break; - AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); + auto mode = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); // numeric: integer -> numeric - if (mode == AbstractColumn::Numeric && columnModes[col] == AbstractColumn::Integer) + if (mode == AbstractColumn::ColumnMode::Numeric && columnModes[col] == AbstractColumn::ColumnMode::Integer) columnModes[col] = mode; // text: non text -> text - if (mode == AbstractColumn::Text && columnModes[col] != AbstractColumn::Text) + if (mode == AbstractColumn::ColumnMode::Text && columnModes[col] != AbstractColumn::ColumnMode::Text) columnModes[col] = mode; // numeric: text -> numeric/integer - if (mode != AbstractColumn::Text && columnModes[col] == AbstractColumn::Text) + if (mode != AbstractColumn::ColumnMode::Text && columnModes[col] == AbstractColumn::ColumnMode::Text) columnModes[col] = mode; col++; } } - QDEBUG("column modes = " << columnModes); + //QDEBUG("column modes = " << columnModes); // ATTENTION: This resets the position in the device to 0 m_actualRows = (int)q->lineNumber(device); const int actualEndRow = (endRow == -1 || endRow > m_actualRows) ? m_actualRows : endRow; if (actualEndRow > m_actualStartRow) m_actualRows = actualEndRow - m_actualStartRow; else m_actualRows = 0; DEBUG("start/end column: " << startColumn << ' ' << endColumn); DEBUG("start/end row: " << m_actualStartRow << ' ' << actualEndRow); DEBUG("actual cols/rows (w/o header): " << m_actualCols << ' ' << m_actualRows); if (m_actualRows == 0 && !device.isSequential()) return 1; return 0; } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { DEBUG("AsciiFilterPrivate::readDataFromFile(): fileName = \'" << STDSTRING(fileName) << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode)); //dirty hack: set readingFile and readingFileName in order to know in lineNumber(QIODevice) //that we're reading from a file and to benefit from much faster wc on linux //TODO: redesign the APIs and remove this later readingFile = true; readingFileName = fileName; KFilterDev device(fileName); readDataFromDevice(device, dataSource, importMode); readingFile = false; } qint64 AsciiFilterPrivate::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) { DEBUG("AsciiFilterPrivate::readFromLiveDevice(): bytes available = " << device.bytesAvailable() << ", from = " << from); if (device.bytesAvailable() <= 0) { DEBUG(" No new data available"); return 0; } //TODO: may be also a matrix? auto* spreadsheet = dynamic_cast(dataSource); if (!spreadsheet) return 0; if (spreadsheet->sourceType() != LiveDataSource::SourceType::FileOrPipe) if (device.isSequential() && device.bytesAvailable() < (int)sizeof(quint16)) return 0; if (!m_prepared) { DEBUG(" Preparing .."); switch (spreadsheet->sourceType()) { case LiveDataSource::SourceType::FileOrPipe: { const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG(" Device error = " << deviceError); return 0; } break; } case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: case LiveDataSource::SourceType::LocalSocket: case LiveDataSource::SourceType::SerialPort: m_actualRows = 1; if (createIndexEnabled) { m_actualCols = 2; - columnModes << AbstractColumn::Integer << AbstractColumn::Numeric; + columnModes << AbstractColumn::ColumnMode::Integer << AbstractColumn::ColumnMode::Numeric; vectorNames << i18n("Index") << i18n("Value"); } else { m_actualCols = 1; - columnModes << AbstractColumn::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; vectorNames << i18n("Value"); } QDEBUG(" vector names = " << vectorNames); break; case LiveDataSource::SourceType::MQTT: break; } // prepare import for spreadsheet spreadsheet->setUndoAware(false); spreadsheet->resize(AbstractFileFilter::Replace, vectorNames, m_actualCols); //columns in a file data source don't have any manual changes. //make the available columns undo unaware and suppress the "data changed" signal. //data changes will be propagated via an explicit Column::setChanged() call once new data was read. for (int i = 0; i < spreadsheet->childCount(); i++) { spreadsheet->child(i)->setUndoAware(false); spreadsheet->child(i)->setSuppressDataChangedSignal(true); } int keepNValues = spreadsheet->keepNValues(); if (keepNValues == 0) spreadsheet->setRowCount(m_actualRows > 1 ? m_actualRows : 1); else { spreadsheet->setRowCount(keepNValues); m_actualRows = keepNValues; } m_dataContainer.resize(m_actualCols); DEBUG(" data source resized to col: " << m_actualCols); DEBUG(" data source rowCount: " << spreadsheet->rowCount()); DEBUG(" Setting data .."); for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) spreadsheet->child(n)->setColumnMode(columnModes[n]); switch (columnModes[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } DEBUG(" Prepared!"); } qint64 bytesread = 0; #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportTotal: "); #endif LiveDataSource::ReadingType readingType; if (!m_prepared) { readingType = LiveDataSource::ReadingType::TillEnd; } else { //we have to read all the data when reading from end //so we set readingType to TillEnd if (spreadsheet->readingType() == LiveDataSource::ReadingType::FromEnd) readingType = LiveDataSource::ReadingType::TillEnd; //if we read the whole file we just start from the beginning of it //and read till end else if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) readingType = LiveDataSource::ReadingType::TillEnd; else readingType = spreadsheet->readingType(); } DEBUG(" Reading type = " << ENUM_TO_STRING(LiveDataSource, ReadingType, readingType)); //move to the last read position, from == total bytes read //since the other source types are sequential we cannot seek on them if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) device.seek(from); //count the new lines, increase actualrows on each //now we read all the new lines, if we want to use sample rate //then here we can do it, if we have actually sample rate number of lines :-? int newLinesForSampleSizeNotTillEnd = 0; int newLinesTillEnd = 0; QVector newData; if (readingType != LiveDataSource::ReadingType::TillEnd) newData.resize(spreadsheet->sampleSize()); int newDataIdx = 0; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportReadingFromFile: "); #endif DEBUG(" source type = " << ENUM_TO_STRING(LiveDataSource, SourceType, spreadsheet->sourceType())); while (!device.atEnd()) { if (readingType != LiveDataSource::ReadingType::TillEnd) { switch (spreadsheet->sourceType()) { // different sources need different read methods case LiveDataSource::SourceType::LocalSocket: newData[newDataIdx++] = device.readAll(); break; case LiveDataSource::SourceType::NetworkUdpSocket: newData[newDataIdx++] = device.read(device.bytesAvailable()); break; case LiveDataSource::SourceType::FileOrPipe: newData.push_back(device.readLine()); break; case LiveDataSource::SourceType::NetworkTcpSocket: //TODO: check serial port case LiveDataSource::SourceType::SerialPort: newData[newDataIdx++] = device.read(device.bytesAvailable()); break; case LiveDataSource::SourceType::MQTT: break; } } else { // ReadingType::TillEnd switch (spreadsheet->sourceType()) { // different sources need different read methods case LiveDataSource::SourceType::LocalSocket: newData.push_back(device.readAll()); break; case LiveDataSource::SourceType::NetworkUdpSocket: newData.push_back(device.read(device.bytesAvailable())); break; case LiveDataSource::SourceType::FileOrPipe: newData.push_back(device.readLine()); break; case LiveDataSource::SourceType::NetworkTcpSocket: //TODO: check serial port case LiveDataSource::SourceType::SerialPort: newData.push_back(device.read(device.bytesAvailable())); break; case LiveDataSource::SourceType::MQTT: break; } } newLinesTillEnd++; if (readingType != LiveDataSource::ReadingType::TillEnd) { newLinesForSampleSizeNotTillEnd++; //for Continuous reading and FromEnd we read sample rate number of lines if possible //here TillEnd and Whole file behave the same if (newLinesForSampleSizeNotTillEnd == spreadsheet->sampleSize()) break; } } QDEBUG(" data read: " << newData); } //now we reset the readingType if (spreadsheet->readingType() == LiveDataSource::ReadingType::FromEnd) readingType = spreadsheet->readingType(); //we had less new lines than the sample size specified if (readingType != LiveDataSource::ReadingType::TillEnd) QDEBUG(" Removed empty lines: " << newData.removeAll(QString())); //back to the last read position before counting when reading from files if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) device.seek(from); const int spreadsheetRowCountBeforeResize = spreadsheet->rowCount(); int currentRow = 0; // indexes the position in the vector(column) int linesToRead = 0; int keepNValues = spreadsheet->keepNValues(); DEBUG(" Increase row count. keepNValues = " << keepNValues); if (m_prepared) { //increase row count if we don't have a fixed size //but only after the preparation step if (keepNValues == 0) { DEBUG(" keep All values"); if (readingType != LiveDataSource::ReadingType::TillEnd) m_actualRows += qMin(newData.size(), spreadsheet->sampleSize()); else { //we don't increase it if we reread the whole file, we reset it if (!(spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile)) m_actualRows += newData.size(); else m_actualRows = newData.size(); } //appending if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) linesToRead = m_actualRows; else linesToRead = m_actualRows - spreadsheetRowCountBeforeResize; } else { // fixed size DEBUG(" keep " << keepNValues << " values"); if (readingType == LiveDataSource::ReadingType::TillEnd) { //we had more lines than the fixed size, so we read m_actualRows number of lines if (newLinesTillEnd > m_actualRows) { linesToRead = m_actualRows; //TODO after reading we should skip the next data lines //because it's TillEnd actually } else linesToRead = newLinesTillEnd; } else { //we read max sample size number of lines when the reading mode //is ContinuouslyFixed or FromEnd, WholeFile is disabled linesToRead = qMin(spreadsheet->sampleSize(), newLinesTillEnd); } } if (linesToRead == 0) return 0; } else // not prepared linesToRead = newLinesTillEnd; DEBUG(" lines to read = " << linesToRead); DEBUG(" actual rows (w/o header) = " << m_actualRows); //TODO // if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe || spreadsheet->sourceType() == LiveDataSource::SourceType::NetworkUdpSocket) { // if (m_actualRows < linesToRead) { // DEBUG(" SET lines to read to " << m_actualRows); // linesToRead = m_actualRows; // } // } //new rows/resize columns if we don't have a fixed size //TODO if the user changes this value..m_resizedToFixedSize..setResizedToFixedSize if (keepNValues == 0) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportResizing: "); #endif if (spreadsheet->rowCount() < m_actualRows) spreadsheet->setRowCount(m_actualRows); if (!m_prepared) currentRow = 0; else { // indexes the position in the vector(column) if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) currentRow = 0; else currentRow = spreadsheetRowCountBeforeResize; } // if we have fixed size, we do this only once in preparation, here we can use // m_prepared and we need something to decide whether it has a fixed size or increasing for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) switch (columnModes[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } } else { // fixed size //when we have a fixed size we have to pop sampleSize number of lines if specified //here popping, setting currentRow if (!m_prepared) { if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) currentRow = 0; else currentRow = m_actualRows - qMin(newLinesTillEnd, m_actualRows); } else { if (readingType == LiveDataSource::ReadingType::TillEnd) { if (newLinesTillEnd > m_actualRows) { currentRow = 0; } else { if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) currentRow = 0; else currentRow = m_actualRows - newLinesTillEnd; } } else { //we read max sample size number of lines when the reading mode //is ContinuouslyFixed or FromEnd currentRow = m_actualRows - qMin(spreadsheet->sampleSize(), newLinesTillEnd); } } if (m_prepared) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportPopping: "); #endif // enable data change signal for (int col = 0; col < m_actualCols; ++col) spreadsheet->child(col)->setSuppressDataChangedSignal(false); for (int row = 0; row < linesToRead; ++row) { for (int col = 0; col < m_actualCols; ++col) { switch (columnModes[col]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector* vector = static_cast*>(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } //TODO - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } } } } // from the last row we read the new data in the spreadsheet DEBUG(" Reading from line " << currentRow << " till end line " << newLinesTillEnd); DEBUG(" Lines to read:" << linesToRead <<", actual rows:" << m_actualRows << ", actual cols:" << m_actualCols); newDataIdx = 0; if (readingType == LiveDataSource::ReadingType::FromEnd) { if (m_prepared) { if (newData.size() > spreadsheet->sampleSize()) newDataIdx = newData.size() - spreadsheet->sampleSize(); //since we skip a couple of lines, we need to count those bytes too for (int i = 0; i < newDataIdx; ++i) bytesread += newData.at(i).size(); } } DEBUG(" newDataIdx: " << newDataIdx); static int indexColumnIdx = 1; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportFillingContainers: "); #endif int row = 0; if (readingType == LiveDataSource::ReadingType::TillEnd || (readingType == LiveDataSource::ReadingType::ContinuousFixed)) { if (headerEnabled) { if (!m_prepared) { row = 1; bytesread += newData.at(0).size(); } } } if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) { if (readingType == LiveDataSource::ReadingType::WholeFile) { if (headerEnabled) { row = 1; bytesread += newData.at(0).size(); } } } QLocale locale(numberFormat); for (; row < linesToRead; ++row) { DEBUG("\n Reading row " << row + 1 << " of " << linesToRead); QString line; if (readingType == LiveDataSource::ReadingType::FromEnd) line = newData.at(newDataIdx++); else line = newData.at(row); //when we read the whole file we don't care about the previous position //so we don't have to count those bytes if (readingType != LiveDataSource::ReadingType::WholeFile) { if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) { bytesread += line.size(); } } if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; QStringList lineStringList; // only FileOrPipe support multiple columns if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); else lineStringList << line; QDEBUG(" line = " << lineStringList << ", separator = \'" << m_separator << "\'"); DEBUG(" Line bytes: " << line.size() << " line: " << STDSTRING(line)); if (simplifyWhitespacesEnabled) { for (int i = 0; i < lineStringList.size(); ++i) lineStringList[i] = lineStringList[i].simplified(); } if (createIndexEnabled) { if (spreadsheet->keepNValues() == 0) lineStringList.prepend(QString::number(currentRow + 1)); else lineStringList.prepend(QString::number(indexColumnIdx++)); } - QDEBUG(" column modes = " << columnModes); + //QDEBUG(" column modes = " << static_cast>(columnModes)); for (int n = 0; n < m_actualCols; ++n) { DEBUG(" actual col = " << n); if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); DEBUG(" value string = " << STDSTRING(valueString)); // set value depending on data type switch (columnModes[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { DEBUG(" Numeric"); bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : nanValue); // qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { DEBUG(" Integer"); bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); // qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { DEBUG(" BigInt"); bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); // qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueString; break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: //TODO break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: //TODO break; } } else { DEBUG(" missing columns in this line"); switch (columnModes[n]) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = nanValue; break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow).clear(); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: //TODO break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: //TODO break; } } } currentRow++; } } if (m_prepared) { //notify all affected columns and plots about the changes PERFTRACE("AsciiLiveDataImport, notify affected columns and plots"); //determine the dependent plots QVector plots; for (int n = 0; n < m_actualCols; ++n) spreadsheet->column(n)->addUsedInPlots(plots); //suppress retransform in the dependent plots for (auto* plot : plots) plot->setSuppressDataChangedSignal(true); for (int n = 0; n < m_actualCols; ++n) spreadsheet->column(n)->setChanged(); //retransform the dependent plots for (auto* plot : plots) { plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } } else m_prepared = true; DEBUG("AsciiFilterPrivate::readFromLiveDevice() DONE"); return bytesread; } /*! reads the content of device \c device to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("AsciiFilterPrivate::readDataFromDevice(): dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode) << ", lines = " << lines); if (!m_prepared) { const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG("Device error = " << deviceError); return; } // matrix data has only one column mode if (dynamic_cast(dataSource)) { auto mode = columnModes[0]; //TODO: remove this when Matrix supports text type - if (mode == AbstractColumn::Text) - mode = AbstractColumn::Numeric; + if (mode == AbstractColumn::ColumnMode::Text) + mode = AbstractColumn::ColumnMode::Numeric; for (auto& c : columnModes) if (c != mode) c = mode; } m_columnOffset = dataSource->prepareImport(m_dataContainer, importMode, m_actualRows, m_actualCols, vectorNames, columnModes); m_prepared = true; } DEBUG("locale = " << STDSTRING(QLocale::languageToString(numberFormat))); QLocale locale(numberFormat); // Read the data int currentRow = 0; // indexes the position in the vector(column) if (lines == -1) lines = m_actualRows; //skip data lines, if required DEBUG(" Skipping " << m_actualStartRow << " lines"); for (int i = 0; i < m_actualStartRow; ++i) device.readLine(); DEBUG(" Reading " << qMin(lines, m_actualRows) << " lines, " << m_actualCols << " columns"); if (qMin(lines, m_actualRows) == 0 || m_actualCols == 0) return; QString line; QString valueString; //Don't put the definition QStringList lineStringList outside of the for-loop, //the compiler doesn't seem to optimize the destructor of QList well enough in this case. lines = qMin(lines, m_actualRows); int progressIndex = 0; const float progressInterval = 0.01*lines; //update on every 1% only for (int i = 0; i < lines; ++i) { line = device.readLine(); // remove any newline line.remove(QLatin1Char('\n')); line.remove(QLatin1Char('\r')); if (removeQuotesEnabled) line.remove(QLatin1Char('"')); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; QStringList lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); // DEBUG(" Line bytes: " << line.size() << " line: " << STDSTRING(line)); if (simplifyWhitespacesEnabled) { for (int i = 0; i < lineStringList.size(); ++i) lineStringList[i] = lineStringList[i].simplified(); } // remove left white spaces if (skipEmptyParts) { for (int n = 0; n < lineStringList.size(); ++n) { valueString = lineStringList.at(n); if (!QString::compare(valueString, " ")) { lineStringList.removeAt(n); n--; } } } for (int n = 0; n < m_actualCols; ++n) { // index column if required if (n == 0 && createIndexEnabled) { static_cast*>(m_dataContainer[0])->operator[](currentRow) = i + 1; continue; } //column counting starts with 1, subtract 1 as well as another 1 for the index column if required int col = createIndexEnabled ? n + startColumn - 2: n + startColumn - 1; if (col < lineStringList.size()) { valueString = lineStringList.at(col); // set value depending on data type switch (columnModes.at(n)) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : nanValue); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { auto* colData = static_cast*>(m_dataContainer[n]); colData->operator[](currentRow) = valueString; break; } - case AbstractColumn::Month: // never happens - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: // never happens + case AbstractColumn::ColumnMode::Day: break; } } else { // missing columns in this line switch (columnModes.at(n)) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = nanValue; break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow).clear(); break; - case AbstractColumn::Month: // never happens - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: // never happens + case AbstractColumn::ColumnMode::Day: break; } } } currentRow++; //ask to update the progress bar only if we have more than 1000 lines //only in 1% steps progressIndex++; if (lines > 1000 && progressIndex > progressInterval) { emit q->completed(100 * currentRow/lines); progressIndex = 0; QApplication::processEvents(QEventLoop::AllEvents, 0); } } DEBUG(" Read " << currentRow << " lines"); //we might have skipped empty lines above. shrink the spreadsheet if the number of read lines (=currentRow) //is smaller than the initial size of the spreadsheet (=m_actualRows). //TODO: should also be relevant for Matrix auto* s = dynamic_cast(dataSource); if (s && currentRow != m_actualRows && importMode == AbstractFileFilter::Replace) s->setRowCount(currentRow); dataSource->finalizeImport(m_columnOffset, startColumn, startColumn + m_actualCols - 1, dateTimeFormat, importMode); } /*! * preview for special devices (local/UDP/TCP socket or serial port) */ QVector AsciiFilterPrivate::preview(QIODevice &device) { DEBUG("AsciiFilterPrivate::preview(): bytesAvailable = " << device.bytesAvailable() << ", isSequential = " << device.isSequential()); QVector dataStrings; if (!(device.bytesAvailable() > 0)) { DEBUG("No new data available"); return dataStrings; } if (device.isSequential() && device.bytesAvailable() < (int)sizeof(quint16)) return dataStrings; #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportTotal: "); #endif int linesToRead = 0; QVector newData; //TODO: serial port "read(nBytes)"? while (!device.atEnd()) { if (device.canReadLine()) newData.push_back(device.readLine()); else // UDP fails otherwise newData.push_back(device.readAll()); linesToRead++; } QDEBUG(" data = " << newData); if (linesToRead == 0) return dataStrings; int col = 0; int colMax = newData.at(0).size(); if (createIndexEnabled) colMax++; columnModes.resize(colMax); if (createIndexEnabled) { columnModes[0] = AbstractColumn::ColumnMode::Integer; col = 1; vectorNames.prepend(i18n("Index")); } vectorNames.append(i18n("Value")); QDEBUG(" vector names = " << vectorNames); for (const auto& valueString : newData.at(0).split(' ', QString::SkipEmptyParts)) { if (col == colMax) break; columnModes[col++] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); } QString line; QLocale locale(numberFormat); QStringList lineString; for (int i = 0; i < linesToRead; ++i) { line = newData.at(i); // remove any newline line = line.remove('\n'); line = line.remove('\r'); if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; QStringList lineStringList = line.split(' ', QString::SkipEmptyParts); if (createIndexEnabled) lineStringList.prepend(QString::number(i + 1)); for (int n = 0; n < lineStringList.size(); ++n) { if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); switch (columnModes[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : nanValue, 'g', 16); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: lineString += valueString; break; - case AbstractColumn::Month: // never happens - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: // never happens + case AbstractColumn::ColumnMode::Day: break; } } else // missing columns in this line lineString += QString(); } dataStrings << lineString; } return dataStrings; } /*! * generates the preview for the file \c fileName reading the provided number of \c lines. */ QVector AsciiFilterPrivate::preview(const QString& fileName, int lines) { QVector dataStrings; //dirty hack: set readingFile and readingFileName in order to know in lineNumber(QIODevice) //that we're reading from a file and to benefit from much faster wc on linux //TODO: redesign the APIs and remove this later readingFile = true; readingFileName = fileName; KFilterDev device(fileName); const int deviceError = prepareDeviceToRead(device); readingFile = false; if (deviceError != 0) { DEBUG("Device error = " << deviceError); return dataStrings; } //number formatting DEBUG("locale = " << STDSTRING(QLocale::languageToString(numberFormat))); QLocale locale(numberFormat); // Read the data if (lines == -1) lines = m_actualRows; // set column names for preview if (!headerEnabled) { int start = 0; if (createIndexEnabled) start = 1; for (int i = start; i < m_actualCols; i++) vectorNames << "Column " + QString::number(i + 1); } QDEBUG(" column names = " << vectorNames); //skip data lines, if required DEBUG(" Skipping " << m_actualStartRow << " lines"); for (int i = 0; i < m_actualStartRow; ++i) device.readLine(); DEBUG(" Generating preview for " << qMin(lines, m_actualRows) << " lines"); QString line; for (int i = 0; i < qMin(lines, m_actualRows); ++i) { line = device.readLine(); // remove any newline line = line.remove('\n'); line = line.remove('\r'); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; QStringList lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); QDEBUG(" line = " << lineStringList); DEBUG(" Line bytes: " << line.size() << " line: " << STDSTRING(line)); if (simplifyWhitespacesEnabled) { for (int i = 0; i < lineStringList.size(); ++i) lineStringList[i] = lineStringList[i].simplified(); } QStringList lineString; for (int n = 0; n < m_actualCols; ++n) { // index column if required if (n == 0 && createIndexEnabled) { lineString += QString::number(i + 1); continue; } //column counting starts with 1, subtract 1 as well as another 1 for the index column if required int col = createIndexEnabled ? n + startColumn - 2: n + startColumn - 1; if (col < lineStringList.size()) { QString valueString = lineStringList.at(col); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); //DEBUG(" valueString = " << STDSTRING(valueString)); if (skipEmptyParts && !QString::compare(valueString, " ")) // handle left white spaces continue; // set value depending on data type switch (columnModes[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : nanValue, 'g', 15); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: lineString += valueString; break; - case AbstractColumn::Month: // never happens - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: // never happens + case AbstractColumn::ColumnMode::Day: break; } } else // missing columns in this line lineString += QString(); } dataStrings << lineString; } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void AsciiFilterPrivate::write(const QString& fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: save data to ascii file } /*! * create datetime from \c string using \c format considering corner cases */ QDateTime AsciiFilterPrivate::parseDateTime(const QString& string, const QString& format) { //DEBUG("string = " << STDSTRING(string) << ", format = " << STDSTRING(format)) QString fixedString(string); QString fixedFormat(format); if (!format.contains("yy")) { // no year given: set temporary to 2000 (must be a leap year to parse "Feb 29") fixedString.append(" 2000"); fixedFormat.append(" yyyy"); } QDateTime dateTime = QDateTime::fromString(fixedString, fixedFormat); //QDEBUG("fromString() =" << dateTime) // interpret 2-digit year smaller than 50 as 20XX if (dateTime.date().year() < 1950 && !format.contains("yyyy")) dateTime = dateTime.addYears(100); //QDEBUG("dateTime fixed =" << dateTime) //DEBUG("dateTime.toString =" << STDSTRING(dateTime.toString(format))) return dateTime; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void AsciiFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "asciiFilter"); writer->writeAttribute( "commentCharacter", d->commentCharacter); writer->writeAttribute( "separatingCharacter", d->separatingCharacter); writer->writeAttribute( "autoMode", QString::number(d->autoModeEnabled)); writer->writeAttribute( "createIndex", QString::number(d->createIndexEnabled)); writer->writeAttribute( "createTimestamp", QString::number(d->createTimestampEnabled)); writer->writeAttribute( "header", QString::number(d->headerEnabled)); writer->writeAttribute( "vectorNames", d->vectorNames.join(' ')); writer->writeAttribute( "skipEmptyParts", QString::number(d->skipEmptyParts)); writer->writeAttribute( "simplifyWhitespaces", QString::number(d->simplifyWhitespacesEnabled)); writer->writeAttribute( "nanValue", QString::number(d->nanValue)); writer->writeAttribute( "removeQuotes", QString::number(d->removeQuotesEnabled)); writer->writeAttribute( "startRow", QString::number(d->startRow)); writer->writeAttribute( "endRow", QString::number(d->endRow)); writer->writeAttribute( "startColumn", QString::number(d->startColumn)); writer->writeAttribute( "endColumn", QString::number(d->endColumn)); writer->writeEndElement(); } /*! Loads from XML. */ bool AsciiFilter::load(XmlStreamReader* reader) { KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); QString str; READ_STRING_VALUE("commentCharacter", commentCharacter); READ_STRING_VALUE("separatingCharacter", separatingCharacter); READ_INT_VALUE("createIndex", createIndexEnabled, bool); READ_INT_VALUE("createTimestamp", createTimestampEnabled, bool); READ_INT_VALUE("autoMode", autoModeEnabled, bool); READ_INT_VALUE("header", headerEnabled, bool); str = attribs.value("vectorNames").toString(); d->vectorNames = str.split(' '); //may be empty READ_INT_VALUE("simplifyWhitespaces", simplifyWhitespacesEnabled, bool); READ_DOUBLE_VALUE("nanValue", nanValue); READ_INT_VALUE("removeQuotes", removeQuotesEnabled, bool); READ_INT_VALUE("skipEmptyParts", skipEmptyParts, bool); READ_INT_VALUE("startRow", startRow, int); READ_INT_VALUE("endRow", endRow, int); READ_INT_VALUE("startColumn", startColumn, int); READ_INT_VALUE("endColumn", endColumn, int); return true; } int AsciiFilterPrivate::isPrepared() { return m_prepared; } #ifdef HAVE_MQTT int AsciiFilterPrivate::prepareToRead(const QString& message) { QStringList lines = message.split('\n'); if (lines.isEmpty()) return 1; // Parse the first line: // Determine the number of columns, create the columns and use (if selected) the first row to name them QString firstLine = lines.at(0); if (simplifyWhitespacesEnabled) firstLine = firstLine.simplified(); DEBUG("First line: \'" << STDSTRING(firstLine) << '\''); // determine separator and split first line QStringList firstLineStringList; if (separatingCharacter == "auto") { DEBUG("automatic separator"); const QRegularExpression regExp(QStringLiteral("[,;:]?\\s+")); firstLineStringList = firstLine.split(regExp, (QString::SplitBehavior)skipEmptyParts); } else { // use given separator // replace symbolic "TAB" with '\t' m_separator = separatingCharacter.replace(QLatin1String("2xTAB"), "\t\t", Qt::CaseInsensitive); m_separator = separatingCharacter.replace(QLatin1String("TAB"), "\t", Qt::CaseInsensitive); // replace symbolic "SPACE" with ' ' m_separator = m_separator.replace(QLatin1String("2xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("3xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("4xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); firstLineStringList = firstLine.split(m_separator, (QString::SplitBehavior)skipEmptyParts); } DEBUG("separator: \'" << STDSTRING(m_separator) << '\''); DEBUG("number of columns: " << firstLineStringList.size()); QDEBUG("first line: " << firstLineStringList); //all columns are read plus the optional column for the index and for the timestamp m_actualCols = firstLineStringList.size() + int(createIndexEnabled) + int(createTimestampEnabled); //column names: //when reading the message strings for different topics, it's not possible to specify vector names //since the different topics can have different content and different number of columns/vectors //->we always set the vector names here to fixed values vectorNames.clear(); columnModes.clear(); //add index column if (createIndexEnabled) { vectorNames << i18n("index"); - columnModes << AbstractColumn::Integer; + columnModes << AbstractColumn::ColumnMode::Integer; } //add timestamp column if (createTimestampEnabled) { vectorNames << i18n("timestamp"); - columnModes << AbstractColumn::DateTime; + columnModes << AbstractColumn::ColumnMode::DateTime; } //parse the first data line to determine data type for each column int i = 1; for (auto& valueString : firstLineStringList) { if (simplifyWhitespacesEnabled) valueString = valueString.simplified(); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); vectorNames << i18n("value %1", i); columnModes << AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); ++i; } m_actualStartRow = startRow; m_actualRows = lines.size(); QDEBUG("column modes = " << columnModes); DEBUG("actual cols/rows (w/o header): " << m_actualCols << ' ' << m_actualRows); return 0; } /*! * generates the preview for the string \s message. */ QVector AsciiFilterPrivate::preview(const QString& message) { QVector dataStrings; prepareToRead(message); //number formatting DEBUG("locale = " << STDSTRING(QLocale::languageToString(numberFormat))); QLocale locale(numberFormat); // Read the data QStringList lines = message.split('\n'); int i = 0; for (auto line : lines) { if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; const QStringList& lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); QDEBUG(" line = " << lineStringList); QStringList lineString; // index column if required if (createIndexEnabled) lineString += QString::number(i + 1); // timestamp column if required if (createTimestampEnabled) lineString += QDateTime::currentDateTime().toString(); int offset = int(createIndexEnabled) + int(createTimestampEnabled); for (int n = 0; n < m_actualCols - offset; ++n) { if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); //DEBUG(" valueString = " << STDSTRING(valueString)); if (skipEmptyParts && !QString::compare(valueString, " ")) // handle left white spaces continue; // set value depending on data type switch (columnModes[n+offset]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : nanValue, 'g', 15); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); lineString += valueString; break; - case AbstractColumn::Month: // never happens - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: // never happens + case AbstractColumn::ColumnMode::Day: break; } } else // missing columns in this line lineString += QString(); } ++i; dataStrings << lineString; } return dataStrings; } /*! * \brief Returns the statistical data that is needed by the topic for its MQTTClient's will message * \param topic */ QString AsciiFilterPrivate::MQTTColumnStatistics(const MQTTTopic* topic) const { Column* const tempColumn = topic->child(m_actualCols - 1); QString statistics; QVector willStatistics = topic->mqttClient()->willStatistics(); //Add every statistical data to the string, the flag of which is set true for (int i = 0; i <= willStatistics.size(); i++) { if (willStatistics[i]) { switch (static_cast(i) ) { case MQTTClient::WillStatisticsType::ArithmeticMean: statistics += QLatin1String("Arithmetic mean: ") + QString::number(tempColumn->statistics().arithmeticMean) + "\n"; break; case MQTTClient::WillStatisticsType::ContraharmonicMean: statistics += QLatin1String("Contraharmonic mean: ") + QString::number(tempColumn->statistics().contraharmonicMean) + "\n"; break; case MQTTClient::WillStatisticsType::Entropy: statistics += QLatin1String("Entropy: ") + QString::number(tempColumn->statistics().entropy) + "\n"; break; case MQTTClient::WillStatisticsType::GeometricMean: statistics += QLatin1String("Geometric mean: ") + QString::number(tempColumn->statistics().geometricMean) + "\n"; break; case MQTTClient::WillStatisticsType::HarmonicMean: statistics += QLatin1String("Harmonic mean: ") + QString::number(tempColumn->statistics().harmonicMean) + "\n"; break; case MQTTClient::WillStatisticsType::Kurtosis: statistics += QLatin1String("Kurtosis: ") + QString::number(tempColumn->statistics().kurtosis) + "\n"; break; case MQTTClient::WillStatisticsType::Maximum: statistics += QLatin1String("Maximum: ") + QString::number(tempColumn->statistics().maximum) + "\n"; break; case MQTTClient::WillStatisticsType::MeanDeviation: statistics += QLatin1String("Mean deviation: ") + QString::number(tempColumn->statistics().meanDeviation) + "\n"; break; case MQTTClient::WillStatisticsType::MeanDeviationAroundMedian: statistics += QLatin1String("Mean deviation around median: ") + QString::number(tempColumn->statistics().meanDeviationAroundMedian) + "\n"; break; case MQTTClient::WillStatisticsType::Median: statistics += QLatin1String("Median: ") + QString::number(tempColumn->statistics().median) + "\n"; break; case MQTTClient::WillStatisticsType::MedianDeviation: statistics += QLatin1String("Median deviation: ") + QString::number(tempColumn->statistics().medianDeviation) + "\n"; break; case MQTTClient::WillStatisticsType::Minimum: statistics += QLatin1String("Minimum: ") + QString::number(tempColumn->statistics().minimum) + "\n"; break; case MQTTClient::WillStatisticsType::Skewness: statistics += QLatin1String("Skewness: ") + QString::number(tempColumn->statistics().skewness) + "\n"; break; case MQTTClient::WillStatisticsType::StandardDeviation: statistics += QLatin1String("Standard deviation: ") + QString::number(tempColumn->statistics().standardDeviation) + "\n"; break; case MQTTClient::WillStatisticsType::Variance: statistics += QLatin1String("Variance: ") + QString::number(tempColumn->statistics().variance) + "\n"; break; case MQTTClient::WillStatisticsType::NoStatistics: default: break; } } } return statistics; } AbstractColumn::ColumnMode AsciiFilterPrivate::MQTTColumnMode() const { return columnModes[m_actualCols - 1]; } /*! * \brief reads the content of a message received by the topic. * Uses the settings defined in the MQTTTopic's MQTTClient * \param message * \param topic * \param dataSource */ void AsciiFilterPrivate::readMQTTTopic(const QString& message, AbstractDataSource* dataSource) { //If the message is empty, there is nothing to do if (message.isEmpty()) { DEBUG("No new data available"); return; } MQTTTopic* spreadsheet = dynamic_cast(dataSource); if (!spreadsheet) return; const int keepNValues = spreadsheet->mqttClient()->keepNValues(); if (!m_prepared) { DEBUG("Start preparing filter for: " << STDSTRING(spreadsheet->topicName())); //Prepare the filter const int mqttPrepareError = prepareToRead(message); if (mqttPrepareError != 0) { DEBUG("Mqtt Prepare Error = " << mqttPrepareError); return; } // prepare import for spreadsheet spreadsheet->setUndoAware(false); spreadsheet->resize(AbstractFileFilter::Replace, vectorNames, m_actualCols); //columns in a MQTTTopic don't have any manual changes. //make the available columns undo unaware and suppress the "data changed" signal. //data changes will be propagated via an explicit Column::setChanged() call once new data was read. for (int i = 0; i < spreadsheet->childCount(); i++) { spreadsheet->child(i)->setUndoAware(false); spreadsheet->child(i)->setSuppressDataChangedSignal(true); } if (keepNValues == 0) spreadsheet->setRowCount(m_actualRows > 1 ? m_actualRows : 1); else { spreadsheet->setRowCount(spreadsheet->mqttClient()->keepNValues()); m_actualRows = spreadsheet->mqttClient()->keepNValues(); } m_dataContainer.resize(m_actualCols); for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) spreadsheet->child(n)->setColumnMode(columnModes[n]); switch (columnModes[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } } #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportTotal: "); #endif MQTTClient::ReadingType readingType; if (!m_prepared) { //if filter is not prepared we read till the end readingType = MQTTClient::ReadingType::TillEnd; } else { //we have to read all the data when reading from end //so we set readingType to TillEnd if (static_cast (spreadsheet->mqttClient()->readingType()) == MQTTClient::ReadingType::FromEnd) readingType = MQTTClient::ReadingType::TillEnd; else readingType = spreadsheet->mqttClient()->readingType(); } //count the new lines, increase actualrows on each //now we read all the new lines, if we want to use sample rate //then here we can do it, if we have actually sample rate number of lines :-? int newLinesForSampleSizeNotTillEnd = 0; int newLinesTillEnd = 0; QVector newData; if (readingType != MQTTClient::ReadingType::TillEnd) { newData.reserve(spreadsheet->mqttClient()->sampleSize()); newData.resize(spreadsheet->mqttClient()->sampleSize()); } int newDataIdx = 0; //TODO: bool sampleSizeReached = false; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportReadingFromFile: "); #endif const QStringList newDataList = message.split(QRegularExpression(QStringLiteral("\n|\r\n|\r")), QString::SkipEmptyParts); for (auto& line : newDataList) { newData.push_back(line); newLinesTillEnd++; if (readingType != MQTTClient::ReadingType::TillEnd) { newLinesForSampleSizeNotTillEnd++; //for Continuous reading and FromEnd we read sample rate number of lines if possible if (newLinesForSampleSizeNotTillEnd == spreadsheet->mqttClient()->sampleSize()) { //TODO: sampleSizeReached = true; break; } } } } qDebug()<<"Processing message done"; //now we reset the readingType if (spreadsheet->mqttClient()->readingType() == MQTTClient::ReadingType::FromEnd) readingType = static_cast(spreadsheet->mqttClient()->readingType()); //we had less new lines than the sample rate specified if (readingType != MQTTClient::ReadingType::TillEnd) qDebug() << "Removed empty lines: " << newData.removeAll(QString()); const int spreadsheetRowCountBeforeResize = spreadsheet->rowCount(); if (m_prepared ) { if (keepNValues == 0) m_actualRows = spreadsheetRowCountBeforeResize; else { //if the keepNValues changed since the last read we have to manage the columns accordingly if (m_actualRows != spreadsheet->mqttClient()->keepNValues()) { if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { spreadsheet->setRowCount(spreadsheet->mqttClient()->keepNValues()); qDebug()<<"rowcount set to: " << spreadsheet->mqttClient()->keepNValues(); } //Calculate the difference between the old and new keepNValues int rowDiff = 0; if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) rowDiff = m_actualRows - spreadsheet->mqttClient()->keepNValues(); if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) rowDiff = spreadsheet->mqttClient()->keepNValues() - m_actualRows; for (int n = 0; n < columnModes.size(); ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) switch (columnModes[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with NaN if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i) = nanValue; } break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with 0 if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i) = 0; } break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with 0 if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i) = 0; } break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with empty lines if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i).clear(); } break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with null datetime if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i) = QDateTime(); } break; } //TODO - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } //if the keepNValues got smaller resize the spreadsheet if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) spreadsheet->setRowCount(spreadsheet->mqttClient()->keepNValues()); //set the new row count m_actualRows = spreadsheet->mqttClient()->keepNValues(); qDebug()<<"actual rows: "<mqttClient()->sampleSize()); else { m_actualRows += newData.size(); } } //fixed size if (keepNValues != 0) { if (readingType == MQTTClient::ReadingType::TillEnd) { //we had more lines than the fixed size, so we read m_actualRows number of lines if (newLinesTillEnd > m_actualRows) { linesToRead = m_actualRows; } else linesToRead = newLinesTillEnd; } else { //we read max sample size number of lines when the reading mode //is ContinuouslyFixed or FromEnd if (spreadsheet->mqttClient()->sampleSize() <= spreadsheet->mqttClient()->keepNValues()) linesToRead = qMin(spreadsheet->mqttClient()->sampleSize(), newLinesTillEnd); else linesToRead = qMin(spreadsheet->mqttClient()->keepNValues(), newLinesTillEnd); } } else linesToRead = m_actualRows - spreadsheetRowCountBeforeResize; if (linesToRead == 0) return; } else { if (keepNValues != 0) linesToRead = newLinesTillEnd > m_actualRows ? m_actualRows : newLinesTillEnd; else linesToRead = newLinesTillEnd; } qDebug()<<"linestoread = " << linesToRead; //new rows/resize columns if we don't have a fixed size if (keepNValues == 0) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportResizing: "); #endif if (spreadsheet->rowCount() < m_actualRows) spreadsheet->setRowCount(m_actualRows); if (!m_prepared) currentRow = 0; else { // indexes the position in the vector(column) currentRow = spreadsheetRowCountBeforeResize; } // if we have fixed size, we do this only once in preparation, here we can use // m_prepared and we need something to decide whether it has a fixed size or increasing for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) switch (columnModes[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } } else { //when we have a fixed size we have to pop sampleSize number of lines if specified //here popping, setting currentRow if (!m_prepared) currentRow = m_actualRows - qMin(newLinesTillEnd, m_actualRows); else { if (readingType == MQTTClient::ReadingType::TillEnd) { if (newLinesTillEnd > m_actualRows) currentRow = 0; else currentRow = m_actualRows - newLinesTillEnd; } else { //we read max sample rate number of lines when the reading mode //is ContinuouslyFixed or FromEnd currentRow = m_actualRows - linesToRead; } } if (m_prepared) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportPopping: "); #endif for (int row = 0; row < linesToRead; ++row) { for (int col = 0; col < m_actualCols; ++col) { switch (columnModes[col]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector* vector = static_cast*>(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } //TODO - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } } } } // from the last row we read the new data in the spreadsheet qDebug() << "reading from line: " << currentRow << " lines till end: " << newLinesTillEnd; qDebug() << "Lines to read: " << linesToRead <<" actual rows: " << m_actualRows; newDataIdx = 0; //From end means that we read the last sample size amount of data if (readingType == MQTTClient::ReadingType::FromEnd) { if (m_prepared) { if (newData.size() > spreadsheet->mqttClient()->sampleSize()) newDataIdx = newData.size() - spreadsheet->mqttClient()->sampleSize(); } } qDebug() << "newDataIdx: " << newDataIdx; //read the data static int indexColumnIdx = 0; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportFillingContainers: "); #endif int row = 0; QLocale locale(numberFormat); for (; row < linesToRead; ++row) { QString line; if (readingType == MQTTClient::ReadingType::FromEnd) line = newData.at(newDataIdx++); else line = newData.at(row); if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) continue; //add index if required int offset = 0; if (createIndexEnabled) { int index = (keepNValues != 0) ? indexColumnIdx++ : currentRow; static_cast*>(m_dataContainer[0])->operator[](currentRow) = index; ++offset; } //add current timestamp if required if (createTimestampEnabled) { static_cast*>(m_dataContainer[offset])->operator[](currentRow) = QDateTime::currentDateTime(); ++offset; } //parse the columns QStringList lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); qDebug()<<"########################################################################"; qDebug()<*>(m_dataContainer[col])->operator[](currentRow) = (isNumber ? value : nanValue); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[col])->operator[](currentRow) = (isNumber ? value : 0); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); static_cast*>(m_dataContainer[col])->operator[](currentRow) = (isNumber ? value : 0); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); static_cast*>(m_dataContainer[col])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); static_cast*>(m_dataContainer[col])->operator[](currentRow) = valueString; break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: //TODO break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: //TODO break; } } else { DEBUG(" missing columns in this line"); switch (columnModes[n]) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: static_cast*>(m_dataContainer[col])->operator[](currentRow) = nanValue; break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: static_cast*>(m_dataContainer[col])->operator[](currentRow) = 0; break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: static_cast*>(m_dataContainer[col])->operator[](currentRow) = 0; break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: static_cast*>(m_dataContainer[col])->operator[](currentRow) = QDateTime(); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: static_cast*>(m_dataContainer[col])->operator[](currentRow).clear(); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: //TODO break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: //TODO break; } } } currentRow++; } } if (m_prepared) { //notify all affected columns and plots about the changes PERFTRACE("AsciiLiveDataImport, notify affected columns and plots"); const Project* project = spreadsheet->project(); QVector curves = project->children(AbstractAspect::ChildIndexFlag::Recursive); QVector plots; for (int n = 0; n < m_actualCols; ++n) { Column* column = spreadsheet->column(n); //determine the plots where the column is consumed for (const auto* curve : curves) { if (curve->xColumn() == column || curve->yColumn() == column) { CartesianPlot* plot = static_cast(curve->parentAspect()); if (plots.indexOf(plot) == -1) { plots << plot; plot->setSuppressDataChangedSignal(true); } } } column->setChanged(); } //loop over all affected plots and retransform them for (auto* const plot : plots) { //TODO setting this back to true triggers again a lot of retransforms in the plot (one for each curve). // plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } } else m_prepared = true; DEBUG("AsciiFilterPrivate::readFromMQTTTopic() DONE"); } /*! * \brief After the MQTTTopic was loaded, the filter is prepared for reading * \param prepared * \param topic * \param separator */ void AsciiFilterPrivate::setPreparedForMQTT(bool prepared, MQTTTopic* topic, const QString& separator) { m_prepared = prepared; //If originally it was prepared we have to restore the settings if (prepared) { m_separator = separator; m_actualCols = endColumn - startColumn + 1; m_actualRows = topic->rowCount(); //set the column modes columnModes.resize(topic->columnCount()); for (int i = 0; i < topic->columnCount(); ++i) { columnModes[i] = topic->column(i)->columnMode(); } //set the data containers m_dataContainer.resize(m_actualCols); for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) topic->child(n)->setColumnMode(columnModes[n]); switch (columnModes[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector* vector = static_cast* >(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector* vector = static_cast* >(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector* vector = static_cast* >(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector* vector = static_cast*>(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { QVector* vector = static_cast* >(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } } } #endif /*! * \brief Returns the separator used by the filter * \return */ QString AsciiFilterPrivate::separator() const { return m_separator; } diff --git a/src/backend/datasources/filters/BinaryFilter.cpp b/src/backend/datasources/filters/BinaryFilter.cpp index 5becc74dd..fed2ccaf1 100644 --- a/src/backend/datasources/filters/BinaryFilter.cpp +++ b/src/backend/datasources/filters/BinaryFilter.cpp @@ -1,613 +1,613 @@ /*************************************************************************** File : BinaryFilter.cpp Project : LabPlot Description : Binary I/O-filter -------------------------------------------------------------------- Copyright : (C) 2015-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 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/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/BinaryFilterPrivate.h" #include "backend/datasources/AbstractDataSource.h" #include "backend/core/column/Column.h" #include #include #include #include #include /*! \class BinaryFilter \brief Manages the import/export of data organized as columns (vectors) from/to a binary file. \ingroup datasources */ BinaryFilter::BinaryFilter():AbstractFileFilter(Binary), d(new BinaryFilterPrivate(this)) {} BinaryFilter::~BinaryFilter() = default; /*! reads the content of the file \c fileName. */ void BinaryFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } /*! reads the content of the device \c device. */ void BinaryFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromDevice(device, dataSource, importMode, lines); } QVector BinaryFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void BinaryFilter::write(const QString & fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /*! returns the list of all predefined data formats. */ QStringList BinaryFilter::dataTypes() { return (QStringList() <<"int8 (8 bit signed integer)" <<"int16 (16 bit signed integer)" <<"int32 (32 bit signed integer)" <<"int64 (64 bit signed integer)" <<"uint8 (8 bit unsigned integer)" <<"uint16 (16 bit unsigned integer)" <<"uint32 (32 bit unsigned integer)" <<"uint64 (64 bit unsigned integer)" <<"real32 (single precision floats)" <<"real64 (double precision floats)" ); } /*! returns the size of the predefined data types */ int BinaryFilter::dataSize(BinaryFilter::DataType type) { std::array sizes = {1,2,4,8,1,2,4,8,4,8}; return sizes[(int)type]; } /*! returns the number of rows (length of vectors) in the file \c fileName. */ size_t BinaryFilter::rowNumber(const QString& fileName, const size_t vectors, const BinaryFilter::DataType type) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) return 0; size_t rows = 0; while (!device.atEnd()) { // one row for (size_t i = 0; i < vectors; ++i) { for (int j = 0; j < BinaryFilter::dataSize(type); ++j) device.read(1); } rows++; } return rows; } /////////////////////////////////////////////////////////////////////// /*! loads the predefined filter settings for \c filterName */ void BinaryFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void BinaryFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /////////////////////////////////////////////////////////////////////// void BinaryFilter::setVectors(const size_t v) { d->vectors = v; } size_t BinaryFilter::vectors() const { return d->vectors; } void BinaryFilter::setDataType(const BinaryFilter::DataType t) { d->dataType = t; } BinaryFilter::DataType BinaryFilter::dataType() const { return d->dataType; } void BinaryFilter::setByteOrder(const QDataStream::ByteOrder b) { d->byteOrder = b; } QDataStream::ByteOrder BinaryFilter::byteOrder() const { return d->byteOrder; } void BinaryFilter::setSkipStartBytes(const size_t s) { d->skipStartBytes = s; } size_t BinaryFilter::skipStartBytes() const { return d->skipStartBytes; } void BinaryFilter::setStartRow(const int s) { d->startRow = s; } int BinaryFilter::startRow() const { return d->startRow; } void BinaryFilter::setEndRow(const int e) { d->endRow = e; } int BinaryFilter::endRow() const { return d->endRow; } void BinaryFilter::setSkipBytes(const size_t s) { d->skipBytes = s; } size_t BinaryFilter::skipBytes() const { return d->skipBytes; } void BinaryFilter::setCreateIndexEnabled(bool b) { d->createIndexEnabled = b; } void BinaryFilter::setAutoModeEnabled(bool b) { d->autoModeEnabled = b; } bool BinaryFilter::isAutoModeEnabled() const { return d->autoModeEnabled; } QString BinaryFilter::fileInfoString(const QString& fileName) { DEBUG("BinaryFilter::fileInfoString()"); QString info; //TODO Q_UNUSED(fileName); return info; } //##################################################################### //################### Private implementation ########################## //##################################################################### BinaryFilterPrivate::BinaryFilterPrivate(BinaryFilter* owner) : q(owner) {} /*! reads the content of the device \c device to the data source \c dataSource or return as string for preview. Uses the settings defined in the data source. */ void BinaryFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { DEBUG("readDataFromFile()"); KFilterDev device(fileName); numRows = BinaryFilter::rowNumber(fileName, vectors, dataType); if (! device.open(QIODevice::ReadOnly)) { DEBUG(" could not open file " << STDSTRING(fileName)); return; } readDataFromDevice(device, dataSource, importMode); } /*! * returns 1 if the current read position in the device is at the end and 0 otherwise. */ int BinaryFilterPrivate::prepareStreamToRead(QDataStream& in) { DEBUG("prepareStreamToRead()"); in.setByteOrder(byteOrder); // catch case that skipStartBytes or startRow is bigger than file if (skipStartBytes >= BinaryFilter::dataSize(dataType) * vectors * numRows || startRow > (int)numRows) return 1; // skip bytes at start for (size_t i = 0; i < skipStartBytes; ++i) { qint8 tmp; in >> tmp; } // skip until start row for (size_t i = 0; i < (startRow-1) * vectors; ++i) { for (int j = 0; j < BinaryFilter::dataSize(dataType); ++j) { qint8 tmp; in >> tmp; } } // set range of rows if (endRow == -1) m_actualRows = (int)numRows - startRow + 1; else if (endRow > (int)numRows - startRow + 1) m_actualRows = (int)numRows; else m_actualRows = endRow - startRow + 1; m_actualCols = (int)vectors; DEBUG("numRows = " << numRows); DEBUG("endRow = " << endRow); DEBUG("actual rows = " << m_actualRows); DEBUG("actual cols = " << m_actualCols); return 0; } /*! reads \c lines lines of the device \c device and return as string for preview. */ QVector BinaryFilterPrivate::preview(const QString& fileName, int lines) { DEBUG("BinaryFilterPrivate::preview( " << STDSTRING(fileName) << ", " << lines << ")"); QVector dataStrings; KFilterDev device(fileName); if (! device.open(QIODevice::ReadOnly)) return dataStrings << (QStringList() << i18n("could not open device")); numRows = BinaryFilter::rowNumber(fileName, vectors, dataType); QDataStream in(&device); const int deviceError = prepareStreamToRead(in); if (deviceError) return dataStrings << (QStringList() << i18n("data selection empty")); //TODO: support other modes columnModes.resize(m_actualCols); //TODO: use given names QStringList vectorNames; if (createIndexEnabled) vectorNames.prepend(i18n("Index")); if (lines == -1) lines = m_actualRows; // read data DEBUG("generating preview for " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(m_actualRows, lines); ++i) { QStringList lineString; //prepend the index if required if (createIndexEnabled) lineString << QString::number(i+1); for (int n = 0; n < m_actualCols; ++n) { //TODO: use ColumnMode when it supports all types switch (dataType) { case BinaryFilter::INT8: { qint8 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::INT16: { qint16 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::INT32: { qint32 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::INT64: { qint64 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::UINT8: { quint8 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::UINT16: { quint16 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::UINT32: { quint32 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::UINT64: { quint64 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::REAL32: { float value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::REAL64: { double value; in >> value; lineString << QString::number(value); break; } } } dataStrings << lineString; emit q->completed(100*i/m_actualRows); } return dataStrings; } /*! reads the content of the file \c fileName to the data source \c dataSource or return as string for preview. Uses the settings defined in the data source. */ void BinaryFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("BinaryFilterPrivate::readDataFromDevice()"); QDataStream in(&device); const int deviceError = prepareStreamToRead(in); if (deviceError) { dataSource->clear(); DEBUG("device error"); return; } if (createIndexEnabled) m_actualCols++; std::vector dataContainer; int columnOffset = 0; //TODO: support other modes columnModes.resize(m_actualCols); //TODO: use given names QStringList vectorNames; if (createIndexEnabled) { vectorNames.prepend(i18n("Index")); - columnModes[0] = AbstractColumn::Integer; + columnModes[0] = AbstractColumn::ColumnMode::Integer; } columnOffset = dataSource->prepareImport(dataContainer, importMode, m_actualRows, m_actualCols, vectorNames, columnModes); if (lines == -1) lines = m_actualRows; // start column int startColumn = 0; if (createIndexEnabled) startColumn++; // read data DEBUG("reading " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(m_actualRows, lines); ++i) { DEBUG("reading row " << i); //prepend the index if required if (createIndexEnabled) static_cast*>(dataContainer[0])->operator[](i) = i+1; for (int n = startColumn; n < m_actualCols; ++n) { DEBUG("reading column " << n); //TODO: use ColumnMode when it supports all types switch (dataType) { case BinaryFilter::INT8: { qint8 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::INT16: { qint16 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::INT32: { qint32 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::INT64: { qint64 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::UINT8: { quint8 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::UINT16: { quint16 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::UINT32: { quint32 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::UINT64: { quint64 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::REAL32: { float value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::REAL64: { double value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } } } if (m_actualRows > 0) emit q->completed(100*i/m_actualRows); } dataSource->finalizeImport(columnOffset, 1, m_actualCols, QString(), importMode); } /*! writes the content of \c dataSource to the file \c fileName. */ void BinaryFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: writing binary files not supported yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void BinaryFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("binaryFilter"); writer->writeAttribute("vectors", QString::number(d->vectors) ); writer->writeAttribute("dataType", QString::number(d->dataType) ); writer->writeAttribute("byteOrder", QString::number(d->byteOrder) ); writer->writeAttribute("autoMode", QString::number(d->autoModeEnabled) ); writer->writeAttribute("startRow", QString::number(d->startRow) ); writer->writeAttribute("endRow", QString::number(d->endRow) ); writer->writeAttribute("skipStartBytes", QString::number(d->skipStartBytes) ); writer->writeAttribute("skipBytes", QString::number(d->skipBytes) ); writer->writeAttribute( "createIndex", QString::number(d->createIndexEnabled) ); writer->writeEndElement(); } /*! Loads from XML. */ bool BinaryFilter::load(XmlStreamReader* reader) { KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); // read attributes QString str = attribs.value("vectors").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("vectors").toString()); else d->vectors = (size_t)str.toULong(); str = attribs.value("dataType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("dataType").toString()); else d->dataType = (BinaryFilter::DataType) str.toInt(); str = attribs.value("byteOrder").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("byteOrder").toString()); else d->byteOrder = (QDataStream::ByteOrder) str.toInt(); str = attribs.value("autoMode").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("autoMode").toString()); else d->autoModeEnabled = str.toInt(); str = attribs.value("startRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("startRow").toString()); else d->startRow = str.toInt(); str = attribs.value("endRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("endRow").toString()); else d->endRow = str.toInt(); str = attribs.value("skipStartBytes").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("skipStartBytes").toString()); else d->skipStartBytes = (size_t)str.toULong(); str = attribs.value("skipBytes").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("skipBytes").toString()); else d->skipBytes = (size_t)str.toULong(); str = attribs.value("createIndex").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("createIndex").toString()); else d->createIndexEnabled = str.toInt(); return true; } diff --git a/src/backend/datasources/filters/FITSFilter.cpp b/src/backend/datasources/filters/FITSFilter.cpp index ae2d16544..2c7ffebd4 100644 --- a/src/backend/datasources/filters/FITSFilter.cpp +++ b/src/backend/datasources/filters/FITSFilter.cpp @@ -1,1604 +1,1604 @@ /*************************************************************************** File : FITSFilter.cpp Project : LabPlot Description : FITS I/O-filter -------------------------------------------------------------------- Copyright : (C) 2016 by Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2017 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 "FITSFilter.h" #include "FITSFilterPrivate.h" #include "backend/core/column/Column.h" #include "backend/core/column/ColumnStringIO.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/matrix/MatrixModel.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/datasources/AbstractDataSource.h" #include "backend/matrix/Matrix.h" #include "commonfrontend/matrix/MatrixView.h" #include #include #include /*! \class FITSFilter * \brief Manages the import/export of data from/to a FITS file. * \since 2.2.0 * \ingroup datasources */ FITSFilter::FITSFilter():AbstractFileFilter(FITS), d(new FITSFilterPrivate(this)) {} FITSFilter::~FITSFilter() = default; void FITSFilter::readDataFromFile(const QString &fileName, AbstractDataSource *dataSource, AbstractFileFilter::ImportMode importMode) { d->readCHDU(fileName, dataSource, importMode); } QVector FITSFilter::readChdu(const QString &fileName, bool* okToMatrix, int lines) { return d->readCHDU(fileName, nullptr, AbstractFileFilter::Replace, okToMatrix, lines); } void FITSFilter::write(const QString &fileName, AbstractDataSource *dataSource) { d->writeCHDU(fileName, dataSource); } void FITSFilter::addNewKeyword(const QString &filename, const QList &keywords) { d->addNewKeyword(filename, keywords); } void FITSFilter::updateKeywords(const QString &fileName, const QList& originals, const QVector& updates) { d->updateKeywords(fileName, originals, updates); } void FITSFilter::deleteKeyword(const QString &fileName, const QList& keywords) { d->deleteKeyword(fileName, keywords); } void FITSFilter::addKeywordUnit(const QString &fileName, const QList &keywords) { d->addKeywordUnit(fileName, keywords); } void FITSFilter::removeExtensions(const QStringList &extensions) { d->removeExtensions(extensions); } void FITSFilter::parseHeader(const QString &fileName, QTableWidget *headerEditTable, bool readKeys, const QList &keys) { d->parseHeader(fileName, headerEditTable, readKeys, keys); } void FITSFilter::parseExtensions(const QString &fileName, QTreeWidget *tw, bool checkPrimary) { d->parseExtensions(fileName, tw, checkPrimary); } QList FITSFilter::chduKeywords(const QString &fileName) { return d->chduKeywords(fileName); } void FITSFilter::loadFilterSettings(const QString& fileName) { Q_UNUSED(fileName) } void FITSFilter::saveFilterSettings(const QString& fileName) const { Q_UNUSED(fileName) } /*! * \brief contains the {StandardKeywords \ MandatoryKeywords} keywords * \return A list of keywords */ QStringList FITSFilter::standardKeywords() { return QStringList() << QLatin1String("(blank)") << QLatin1String("CROTA") << QLatin1String("EQUINOX") << QLatin1String("NAXIS") << QLatin1String("TBCOL") << QLatin1String("TUNIT") << QLatin1String("AUTHOR") << QLatin1String("CRPIX") << QLatin1String("EXTEND") << QLatin1String("OBJECT") << QLatin1String("TDIM") << QLatin1String("TZERO") << QLatin1String("BITPIX") << QLatin1String("CRVAL") << QLatin1String("EXTLEVEL") << QLatin1String("OBSERVER") << QLatin1String("TDISP") << QLatin1String("XTENSION") << QLatin1String("BLANK") << QLatin1String("CTYPE") << QLatin1String("EXTNAME") << QLatin1String("ORIGIN") << QLatin1String("TELESCOP") << QLatin1String("BLOCKED") << QLatin1String("DATAMAX") << QLatin1String("EXTVER") << QLatin1String("BSCALE") << QLatin1String("DATAMIN") << QLatin1String("PSCAL") << QLatin1String("TFORM") << QLatin1String("BUNIT") << QLatin1String("DATE") << QLatin1String("GROUPS") << QLatin1String("PTYPE") << QLatin1String("THEAP") << QLatin1String("BZERO") << QLatin1String("DATE-OBS") << QLatin1String("HISTORY") << QLatin1String("PZERO") << QLatin1String("TNULL") << QLatin1String("CDELT") << QLatin1String("INSTRUME") << QLatin1String("REFERENC") << QLatin1String("TSCAL") << QLatin1String("COMMENT") << QLatin1String("EPOCH") << QLatin1String("NAXIS") << QLatin1String("SIMPLE") << QLatin1String("TTYPE"); } /*! * \brief Returns a list of keywords, that are mandatory for an image extension of a FITS file * see: * https://archive.stsci.edu/fits/fits_standard/node64.html * \return A list of keywords */ QStringList FITSFilter::mandatoryImageExtensionKeywords() { return QStringList() << QLatin1String("XTENSION") << QLatin1String("BITPIX") << QLatin1String("NAXIS") << QLatin1String("PCOUNT") << QLatin1String("GCOUNT") << QLatin1String("END"); } /*! * \brief Returns a list of keywords, that are mandatory for a table extension (ascii or bintable) * of a FITS file * see: * https://archive.stsci.edu/fits/fits_standard/node58.html * https://archive.stsci.edu/fits/fits_standard/node68.html * \return A list of keywords */ QStringList FITSFilter::mandatoryTableExtensionKeywords() { return QStringList() << QLatin1String("XTENSION") << QLatin1String("BITPIX") << QLatin1String("NAXIS") << QLatin1String("NAXIS1") << QLatin1String("NAXIS2") << QLatin1String("PCOUNT") << QLatin1String("GCOUNT") << QLatin1String("TFIELDS") << QLatin1String("END"); } /*! * \brief Returns a list of strings that represent units which are used for autocompletion when adding * keyword units to keywords * \return A list of strings that represent units */ QStringList FITSFilter::units() { return QStringList() << QLatin1String("m (Metre)") << QLatin1String("kg (Kilogram)") << QLatin1String("s (Second)") << QString("M☉ (Solar mass)") << QLatin1String("AU (Astronomical unit") << QLatin1String("l.y (Light year)") << QLatin1String("km (Kilometres") << QLatin1String("pc (Parsec)") << QLatin1String("K (Kelvin)") << QLatin1String("mol (Mole)") << QLatin1String("cd (Candela)"); } /*! * \brief Sets the startColumn to \a column * \param column the column to be set */ void FITSFilter::setStartColumn(const int column) { d->startColumn = column; } /*! * \brief Returns startColumn * \return The startColumn */ int FITSFilter::startColumn() const { return d->startColumn; } /*! * \brief Sets the endColumn to \a column * \param column the column to be set */ void FITSFilter::setEndColumn(const int column) { d->endColumn = column; } /*! * \brief Returns endColumn * \return The endColumn */ int FITSFilter::endColumn() const { return d->endColumn; } /*! * \brief Sets the startRow to \a row * \param row the row to be set */ void FITSFilter::setStartRow(const int row) { d->startRow = row; } /*! * \brief Returns startRow * \return The startRow */ int FITSFilter::startRow() const { return d->startRow; } /*! * \brief Sets the endRow to \a row * \param row the row to be set */ void FITSFilter::setEndRow(const int row) { d->endRow = row; } /*! * \brief Returns endRow * \return The endRow */ int FITSFilter::endRow() const { return d->endRow; } /*! * \brief Sets commentsAsUnits to \a commentsAsUnits * * This is used when spreadsheets are exported to FITS table extensions and comments are used as the * units of the table's columns. * \param commentsAsUnits */ void FITSFilter::setCommentsAsUnits(const bool commentsAsUnits) { d->commentsAsUnits = commentsAsUnits; } /*! * \brief Sets exportTo to \a exportTo * * This is used to decide whether the container should be exported to a FITS image or a FITS table * For an image \a exportTo should be 0, for a table 1 * \param exportTo */ void FITSFilter::setExportTo(const int exportTo) { d->exportTo = exportTo; } QString FITSFilter::fileInfoString(const QString& fileName) { const int imagesCount = FITSFilterPrivate::extensionNames(fileName).values(QLatin1String("IMAGES")).size(); QString info(i18n("Images: %1", QString::number(imagesCount))); info += QLatin1String("
"); const int tablesCount = FITSFilterPrivate::extensionNames(fileName).values(QLatin1String("TABLES")).size(); info += i18n("Tables: %1", QString::number(tablesCount)); return info; } //##################################################################### //################### Private implementation ########################## //##################################################################### FITSFilterPrivate::FITSFilterPrivate(FITSFilter* owner) : q(owner) {} /*! * \brief Read the current header data unit from file \a filename in data source \a dataSource in \a importMode import mode * \param fileName the name of the file to be read * \param dataSource the data source to be filled * \param importMode */ QVector FITSFilterPrivate::readCHDU(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, bool* okToMatrix, int lines) { DEBUG("FITSFilterPrivate::readCHDU() file name = " << STDSTRING(fileName)); QVector dataStrings; #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READONLY, &status)) { DEBUG(" ERROR opening file " << STDSTRING(fileName)); printError(status); return dataStrings; } int chduType; if (fits_get_hdu_type(m_fitsFile, &chduType, &status)) { printError(status); return dataStrings; } long actualRows; int actualCols; int columnOffset = 0; bool noDataSource = (dataSource == nullptr); if (chduType == IMAGE_HDU) { DEBUG("IMAGE_HDU"); int maxdim = 2; int bitpix; int naxis; long naxes[2]; if (fits_get_img_param(m_fitsFile, maxdim, &bitpix, &naxis, naxes, &status)) { printError(status); return dataStrings; } if (naxis == 0) return dataStrings; actualRows = naxes[1]; actualCols = naxes[0]; if (lines == -1) lines = actualRows; else { if (lines > actualRows) lines = actualRows; } if (endRow != -1) { if (!noDataSource) lines = endRow; } if (endColumn != -1) actualCols = endColumn; if (noDataSource) dataStrings.reserve(lines); int i = 0; int j = 0; if (startRow != 1) i = startRow; if (startColumn != 1) j = startColumn; const int jstart = j; //TODO: support other modes QVector columnModes; columnModes.resize(actualCols - j); QStringList vectorNames; std::vector dataContainer; if (!noDataSource) { dataContainer.reserve(actualCols - j); columnOffset = dataSource->prepareImport(dataContainer, importMode, lines - i, actualCols - j, vectorNames, columnModes); } long pixelCount = lines * actualCols; double* data = new double[pixelCount]; if (!data) { qDebug() << "Not enough memory for data"; return dataStrings; } if (fits_read_img(m_fitsFile, TDOUBLE, 1, pixelCount, nullptr, data, nullptr, &status)) { printError(status); return dataStrings << (QStringList() << QString("Error")); } int ii = 0; DEBUG(" Import " << lines << " lines"); for (; i < lines; ++i) { int jj = 0; QStringList line; line.reserve(actualCols - j); for (; j < actualCols; ++j) { if (noDataSource) line << QString::number(data[i*naxes[0] +j]); else static_cast*>(dataContainer[jj++])->operator[](ii) = data[i* naxes[0] + j]; } dataStrings << line; j = jstart; ii++; } delete[] data; if (dataSource) dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); fits_close_file(m_fitsFile, &status); return dataStrings; } else if ((chduType == ASCII_TBL) || (chduType == BINARY_TBL)) { DEBUG("ASCII_TBL or BINARY_TBL"); if (endColumn != -1) actualCols = endColumn; else fits_get_num_cols(m_fitsFile, &actualCols, &status); if (endRow != -1) actualRows = endRow; else fits_get_num_rows(m_fitsFile, &actualRows, &status); QStringList columnNames; QList columnsWidth; QStringList columnUnits; columnUnits.reserve(actualCols); columnsWidth.reserve(actualCols); columnNames.reserve(actualCols); int colWidth; char keyword[FLEN_KEYWORD]; char value[FLEN_VALUE]; int col = 1; if (startColumn != 1) { if (startColumn != 0) col = startColumn; } for (; col <= actualCols; ++col) { status = 0; fits_make_keyn("TTYPE", col, keyword, &status); fits_read_key(m_fitsFile, TSTRING, keyword, value, nullptr, &status); columnNames.append(QLatin1String(value)); fits_make_keyn("TUNIT", col, keyword, &status); fits_read_key(m_fitsFile, TSTRING, keyword, value, nullptr, &status); columnUnits.append(QLatin1String(value)); fits_get_col_display_width(m_fitsFile, col, &colWidth, &status); columnsWidth.append(colWidth); } status = 0; if (lines == -1) lines = actualRows; else if (lines > actualRows) lines = actualRows; if (endRow != -1) lines = endRow; QVector stringDataPointers; std::vector numericDataPointers; QList columnNumericTypes; int startCol = 0; if (startColumn != 1) startCol = startColumn; int startRrow = 0; if (startRow != 1) startRrow = startRow; columnNumericTypes.reserve(actualCols); int datatype; int c = 1; if (startColumn != 1) { if (startColumn != 0) c = startColumn; } QList matrixNumericColumnIndices; for (; c <= actualCols; ++c) { fits_get_coltype(m_fitsFile, c, &datatype, nullptr, nullptr, &status); switch (datatype) { case TSTRING: columnNumericTypes.append(false); break; case TSHORT: columnNumericTypes.append(true); break; case TLONG: columnNumericTypes.append(true); break; case TFLOAT: columnNumericTypes.append(true); break; case TDOUBLE: columnNumericTypes.append(true); break; case TLOGICAL: columnNumericTypes.append(false); break; case TBIT: columnNumericTypes.append(true); break; case TBYTE: columnNumericTypes.append(true); break; case TCOMPLEX: columnNumericTypes.append(true); break; default: columnNumericTypes.append(false); break; } if ((datatype != TSTRING) && (datatype != TLOGICAL)) matrixNumericColumnIndices.append(c); } if (noDataSource) *okToMatrix = matrixNumericColumnIndices.isEmpty() ? false : true; if (!noDataSource) { DEBUG("HAS DataSource"); auto* spreadsheet = dynamic_cast(dataSource); if (spreadsheet) { numericDataPointers.reserve(actualCols - startCol); stringDataPointers.reserve(actualCols - startCol); spreadsheet->setUndoAware(false); columnOffset = spreadsheet->resize(importMode, columnNames, actualCols - startCol); if (importMode == AbstractFileFilter::Replace) { spreadsheet->clear(); spreadsheet->setRowCount(lines - startRrow); } else { if (spreadsheet->rowCount() < (lines - startRrow)) spreadsheet->setRowCount(lines - startRrow); } DEBUG("Reading columns ..."); for (int n = 0; n < actualCols - startCol; ++n) { if (columnNumericTypes.at(n)) { - spreadsheet->column(columnOffset+ n)->setColumnMode(AbstractColumn::Numeric); + spreadsheet->column(columnOffset+ n)->setColumnMode(AbstractColumn::ColumnMode::Numeric); auto* datap = static_cast* >(spreadsheet->column(columnOffset+n)->data()); numericDataPointers.push_back(datap); if (importMode == AbstractFileFilter::Replace) datap->clear(); } else { - spreadsheet->column(columnOffset+ n)->setColumnMode(AbstractColumn::Text); + spreadsheet->column(columnOffset+ n)->setColumnMode(AbstractColumn::ColumnMode::Text); auto* list = static_cast(spreadsheet->column(columnOffset+n)->data()); stringDataPointers.push_back(list); if (importMode == AbstractFileFilter::Replace) list->clear(); } } DEBUG(" ... DONE"); stringDataPointers.squeeze(); } else { numericDataPointers.reserve(matrixNumericColumnIndices.size()); columnOffset = dataSource->prepareImport(numericDataPointers, importMode, lines - startRrow, matrixNumericColumnIndices.size()); } } int row = 1; if (startRow != 1) { if (startRow != 0) row = startRow; } int coll = 1; if (startColumn != 1) { if (startColumn != 0) coll = startColumn; } bool isMatrix = false; if (dynamic_cast(dataSource)) { isMatrix = true; coll = matrixNumericColumnIndices.first(); actualCols = matrixNumericColumnIndices.last(); if (importMode == AbstractFileFilter::Replace) { for (auto* col : numericDataPointers) static_cast*>(col)->clear(); } } char array[FLEN_VALUE]; char* tmpArr[1] = {array}; for (; row <= lines; ++row) { int numericixd = 0; int stringidx = 0; QStringList line; line.reserve(actualCols-coll); for (int col = coll; col <= actualCols; ++col) { if (isMatrix) { if (!matrixNumericColumnIndices.contains(col)) continue; } if (fits_read_col_str(m_fitsFile, col, row, 1, 1, nullptr, tmpArr, nullptr, &status)) printError(status); if (!noDataSource) { QString str = QString::fromLatin1(array); if (str.isEmpty()) { if (columnNumericTypes.at(col - 1)) static_cast*>(numericDataPointers[numericixd++])->push_back(0); else stringDataPointers[stringidx++]->append(QLatin1String("NULL")); } else { if (columnNumericTypes.at(col - 1)) static_cast*>(numericDataPointers[numericixd++])->push_back(str.toDouble()); else { if (!stringDataPointers.isEmpty()) stringDataPointers[stringidx++]->operator<<(str.simplified()); } } } else { QString tmpColstr = QString::fromLatin1(array); tmpColstr = tmpColstr.simplified(); if (tmpColstr.isEmpty()) line << QLatin1String("NULL"); else line << tmpColstr; } } dataStrings << line; } if (!noDataSource) dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); fits_close_file(m_fitsFile, &status); return dataStrings; } else qDebug() << "Incorrect header type"; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(fileName) Q_UNUSED(dataSource) Q_UNUSED(importMode) Q_UNUSED(okToMatrix) Q_UNUSED(lines) #endif return dataStrings; } /*! * \brief Export from data source \a dataSource to file \a fileName * \param fileName the name of the file to be exported to * \param dataSource the datasource whose data is exported */ void FITSFilterPrivate::writeCHDU(const QString &fileName, AbstractDataSource *dataSource) { #ifdef HAVE_FITS if (!fileName.endsWith(QLatin1String(".fits"))) return; int status = 0; bool existed = false; if (!QFile::exists(fileName)) { if (fits_create_file(&m_fitsFile, fileName.toLatin1(), &status)) { printError(status); qDebug() << fileName; return; } } else { if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } else existed = true; } Matrix* const matrix = dynamic_cast(dataSource); if (matrix) { //FITS image if (exportTo == 0) { long naxes[2] = { matrix->columnCount(), matrix->rowCount() }; if (fits_create_img(m_fitsFile, FLOAT_IMG, 2, naxes, &status)) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); return; } const long nelem = naxes[0] * naxes[1]; double* const array = new double[nelem]; const QVector >* const data = static_cast>*>(matrix->data()); for (int col = 0; col < naxes[0]; ++col) for (int row = 0; row < naxes[1]; ++row) array[row * naxes[0] + col] = data->at(row).at(col); if (fits_write_img(m_fitsFile, TDOUBLE, 1, nelem, array, &status )) { printError(status); status = 0; } fits_close_file(m_fitsFile, &status); delete[] array; //FITS table } else { const int nrows = matrix->rowCount(); const int tfields = matrix->columnCount(); QVector columnNames; columnNames.resize(tfields); columnNames.squeeze(); QVector tform; tform.resize(tfields); tform.squeeze(); //TODO: mode const QVector>* const matrixData = static_cast>*>(matrix->data()); const MatrixModel* matrixModel = static_cast(matrix->view())->model(); const int precision = matrix->precision(); for (int i = 0; i < tfields; ++i) { const QString& columnName = matrixModel->headerData(i, Qt::Horizontal).toString(); columnNames[i] = new char[columnName.size() + 1]; strcpy(columnNames[i], columnName.toLatin1().constData()); int maxSize = -1; for (int row = 0; row < nrows; ++row) { if (matrix->text(row, i).size() > maxSize) maxSize = matrix->text(row, i).size(); } QString tformn; if (precision > 0) { tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".") + QString::number(precision); } else tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".0"); tform[i] = new char[tformn.size() + 1]; strcpy(tform[i], tformn.toLatin1().constData()); } //TODO extension name containing[] ? int r = fits_create_tbl(m_fitsFile, ASCII_TBL, nrows, tfields, columnNames.data(), tform.data(), nullptr, matrix->name().toLatin1().constData(), &status); for (int i = 0; i < tfields; ++i) { delete[] tform[i]; delete[] columnNames[i]; } if (r) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } double* columnNumeric = new double[nrows]; for (int col = 1; col <= tfields; ++col) { const QVector& column = matrixData->at(col-1); for (int r = 0; r < column.size(); ++r) columnNumeric[r] = column.at(r); fits_write_col(m_fitsFile, TDOUBLE, col, 1, 1, nrows, columnNumeric, &status); if (status) { printError(status); delete[] columnNumeric; status = 0; if (!existed) { QFile file(fileName); file.remove(); } fits_close_file(m_fitsFile, &status); return; } } delete[] columnNumeric; fits_close_file(m_fitsFile, &status); } return; } auto* const spreadsheet = dynamic_cast(dataSource); if (spreadsheet) { //FITS image if (exportTo == 0) { int maxRowIdx = -1; //don't export lots of empty lines if all of those contain nans // TODO: option? for (int c = 0; c < spreadsheet->columnCount(); ++c) { const Column* const col = spreadsheet->column(c); int currMaxRoxIdx = -1; for (int r = col->rowCount(); r >= 0; --r) { if (col->isValid(r)) { currMaxRoxIdx = r; break; } } if (currMaxRoxIdx > maxRowIdx) { maxRowIdx = currMaxRoxIdx; } } long naxes[2] = { spreadsheet->columnCount(), maxRowIdx + 1}; if (fits_create_img(m_fitsFile, FLOAT_IMG, 2, naxes, &status)) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } const long nelem = naxes[0] * naxes[1]; double* array = new double[nelem]; for (int row = 0; row < naxes[1]; ++row) { for (int col = 0; col < naxes[0]; ++col) array[row * naxes[0] + col] = spreadsheet->column(col)->valueAt(row); } if (fits_write_img(m_fitsFile, TDOUBLE, 1, nelem, array, &status )) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } fits_close_file(m_fitsFile, &status); delete[] array; } else { const int nrows = spreadsheet->rowCount(); const int tfields = spreadsheet->columnCount(); QVector columnNames; columnNames.resize(tfields); columnNames.squeeze(); QVector tform; tform.resize(tfields); tform.squeeze(); QVector tunit; tunit.resize(tfields); tunit.squeeze(); for (int i = 0; i < tfields; ++i) { const Column* const column = spreadsheet->column(i); columnNames[i] = new char[column->name().size() + 1]; strcpy(columnNames[i], column->name().toLatin1().constData()); if (commentsAsUnits) { tunit[i] = new char[column->comment().size() + 1]; strcpy(tunit[i], column->comment().toLatin1().constData()); } else { tunit[i] = new char[1]; tunit[i][0] = '\0'; } switch (column->columnMode()) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { int maxSize = -1; for (int row = 0; row < nrows; ++row) { if (QString::number(column->valueAt(row)).size() > maxSize) maxSize = QString::number(column->valueAt(row)).size(); } const Double2StringFilter* const filter = static_cast(column->outputFilter()); bool decimals = false; for (int ii = 0; ii < nrows; ++ii) { bool ok; QString cell = column->asStringColumn()->textAt(ii); double val = cell.toDouble(&ok); if (cell.size() > QString::number(val).size() + 1) { decimals = true; break; } } QString tformn; if (decimals) { int maxStringSize = -1; for (int row = 0; row < nrows; ++row) { if (column->asStringColumn()->textAt(row).size() > maxStringSize) maxStringSize = column->asStringColumn()->textAt(row).size(); } const int diff = abs(maxSize - maxStringSize); maxSize+= diff; tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".") + QString::number(filter->numDigits()); } else tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".0"); tform[i] = new char[tformn.size()]; strcpy(tform[i], tformn.toLatin1().data()); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { int maxSize = -1; for (int row = 0; row < nrows; ++row) { if (column->textAt(row).size() > maxSize) maxSize = column->textAt(row).size(); } const QString& tformn = QLatin1String("A") + QString::number(maxSize); tform[i] = new char[tformn.size()]; strcpy(tform[i], tformn.toLatin1().data()); break; } - case AbstractColumn::Integer: //TODO - case AbstractColumn::BigInt: - case AbstractColumn::DateTime: - case AbstractColumn::Day: - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Integer: //TODO + case AbstractColumn::ColumnMode::BigInt: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: break; } } //TODO extension name containing[] ? int r = fits_create_tbl(m_fitsFile, ASCII_TBL, nrows, tfields, columnNames.data(), tform.data(), tunit.data(), spreadsheet->name().toLatin1().constData(), &status); for (int i = 0; i < tfields; ++i) { delete[] tform[i]; delete[] tunit[i]; delete[] columnNames[i]; } if (r) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } QVector column; column.resize(nrows); column.squeeze(); double* columnNumeric = new double[nrows]; for (int col = 1; col <= tfields; ++col) { const Column* c = spreadsheet->column(col-1); - AbstractColumn::ColumnMode columnMode = c->columnMode(); + auto columnMode = c->columnMode(); - if (columnMode == AbstractColumn::Numeric) { + if (columnMode == AbstractColumn::ColumnMode::Numeric) { for (int row = 0; row < nrows; ++row) columnNumeric[row] = c->valueAt(row); fits_write_col(m_fitsFile, TDOUBLE, col, 1, 1, nrows, columnNumeric, &status); if (status) { printError(status); delete[] columnNumeric; status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } } else { for (int row = 0; row < nrows; ++row) { column[row] = new char[c->textAt(row).size() + 1]; strcpy(column[row], c->textAt(row).toLatin1().constData()); } fits_write_col(m_fitsFile, TSTRING, col, 1, 1, nrows, column.data(), &status); for (int row = 0; row < nrows; ++row) delete[] column[row]; if (status) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); delete[] columnNumeric; return; } } } delete[] columnNumeric; status = 0; fits_close_file(m_fitsFile, &status); } } #else Q_UNUSED(fileName) Q_UNUSED(dataSource) #endif } /*! * \brief Return a map of the available extensions names in file \a filename * The keys of the map are the extension types, the values are the names * \param fileName the name of the FITS file to be analyzed */ QMultiMap FITSFilterPrivate::extensionNames(const QString& fileName) { DEBUG("FITSFilterPrivate::extensionNames() file name = " << STDSTRING(fileName)); #ifdef HAVE_FITS QMultiMap extensions; int status = 0; fitsfile* fitsFile = nullptr; if (fits_open_file(&fitsFile, fileName.toLatin1(), READONLY, &status )) return QMultiMap(); int hduCount; if (fits_get_num_hdus(fitsFile, &hduCount, &status)) return QMultiMap(); int imageCount = 0; int asciiTableCount = 0; int binaryTableCount = 0; for (int currentHDU = 1; (currentHDU <= hduCount) && !status; ++currentHDU) { int hduType; status = 0; fits_get_hdu_type(fitsFile, &hduType, &status); switch (hduType) { case IMAGE_HDU: imageCount++; break; case ASCII_TBL: asciiTableCount++; break; case BINARY_TBL: binaryTableCount++; break; } char* keyVal = new char[FLEN_VALUE]; QString extName; if (!fits_read_keyword(fitsFile,"EXTNAME", keyVal, nullptr, &status)) { extName = QLatin1String(keyVal); extName = extName.mid(1, extName.length() -2).simplified(); } else { status = 0; if (!fits_read_keyword(fitsFile,"HDUNAME", keyVal, nullptr, &status)) { extName = QLatin1String(keyVal); extName = extName.mid(1, extName.length() -2).simplified(); } else { status = 0; switch (hduType) { case IMAGE_HDU: if (imageCount == 1) extName = i18n("Primary header"); else extName = i18n("IMAGE #%1", imageCount); break; case ASCII_TBL: extName = i18n("ASCII_TBL #%1", asciiTableCount); break; case BINARY_TBL: extName = i18n("BINARY_TBL #%1", binaryTableCount); break; } } } delete[] keyVal; status = 0; extName = extName.trimmed(); switch (hduType) { case IMAGE_HDU: extensions.insert(QLatin1String("IMAGES"), extName); break; case ASCII_TBL: extensions.insert(QLatin1String("TABLES"), extName); break; case BINARY_TBL: extensions.insert(QLatin1String("TABLES"), extName); break; } fits_movrel_hdu(fitsFile, 1, nullptr, &status); } if (status == END_OF_FILE) status = 0; fits_close_file(fitsFile, &status); return extensions; #else Q_UNUSED(fileName) return QMultiMap(); #endif } /*! * \brief Prints the error text corresponding to the status code \a status * \param status the status code of the error */ void FITSFilterPrivate::printError(int status) const { #ifdef HAVE_FITS if (status) { char errorText[FLEN_ERRMSG]; fits_get_errstatus(status, errorText ); qDebug() << QLatin1String(errorText); } #else Q_UNUSED(status) #endif } /*! * \brief Add the keywords \a keywords to the current header unit * \param keywords the keywords to be added * \param fileName the name of the FITS file (extension) in which the keywords are added */ void FITSFilterPrivate::addNewKeyword(const QString& fileName, const QList& keywords) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } for (const FITSFilter::Keyword& keyword: keywords) { status = 0; if (!keyword.key.compare(QLatin1String("COMMENT"))) { if (fits_write_comment(m_fitsFile, keyword.value.toLatin1(), &status)) printError(status); } else if (!keyword.key.compare(QLatin1String("HISTORY"))) { if (fits_write_history(m_fitsFile, keyword.value.toLatin1(), &status)) printError(status); } else if (!keyword.key.compare(QLatin1String("DATE"))) { if (fits_write_date(m_fitsFile, &status)) printError(status); } else { int ok = 0; if (keyword.key.length() <= FLEN_KEYWORD) { ok++; if (keyword.value.length() <= FLEN_VALUE) { ok++; if (keyword.comment.length() <= FLEN_COMMENT) ok++; } } if (ok == 3) { bool ok; double val = keyword.value.toDouble(&ok); if (ok) { if (fits_write_key(m_fitsFile, TDOUBLE, keyword.key.toLatin1().data(), &val, keyword.comment.toLatin1().data(), &status)) printError(status); } else { if (fits_write_key(m_fitsFile, TSTRING, keyword.key.toLatin1().data(), keyword.value.toLatin1().data(), keyword.comment.toLatin1().data(), &status)) printError(status); } } else if ( ok == 2) { //comment too long } else if ( ok == 1) { //value too long } else { //keyword too long } } } status = 0; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(keywords) Q_UNUSED(fileName) #endif } /*! * \brief Update keywords in the current header unit * \param fileName The name of the FITS file (extension) in which the keywords will be updated * \param originals The original keywords of the FITS file (extension) * \param updates The keywords that contain the updated values */ void FITSFilterPrivate::updateKeywords(const QString& fileName, const QList& originals, const QVector& updates) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } FITSFilter::Keyword updatedKeyword; FITSFilter::Keyword originalKeyword; FITSFilter::KeywordUpdate keywordUpdate; for (int i = 0; i < updates.size(); ++i) { updatedKeyword = updates.at(i); originalKeyword = originals.at(i); keywordUpdate = originals.at(i).updates; if (keywordUpdate.keyUpdated && keywordUpdate.valueUpdated && keywordUpdate.commentUpdated) { if (updatedKeyword.isEmpty()) { if (fits_delete_key(m_fitsFile, originalKeyword.key.toLatin1(), &status)) { printError(status); status = 0; } continue; } } if (!updatedKeyword.key.isEmpty()) { if (fits_modify_name(m_fitsFile, originalKeyword.key.toLatin1(), updatedKeyword.key.toLatin1(), &status )) { printError(status); status = 0; } } if (!updatedKeyword.value.isEmpty()) { bool ok; int intValue; double doubleValue; bool updated = false; doubleValue = updatedKeyword.value.toDouble(&ok); if (ok) { if (fits_update_key(m_fitsFile,TDOUBLE, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), &doubleValue, nullptr, &status)) printError(status); else updated = true; } if (!updated) { intValue = updatedKeyword.value.toInt(&ok); if (ok) { if (fits_update_key(m_fitsFile,TINT, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), &intValue, nullptr, &status)) printError(status); else updated = true; } } if (!updated) { if (fits_update_key(m_fitsFile,TSTRING, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), updatedKeyword.value.toLatin1().data(), nullptr, &status)) printError(status); } } else { if (keywordUpdate.valueUpdated) { if (fits_update_key_null(m_fitsFile, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), nullptr, &status)) { printError(status); status = 0; } } } if (!updatedKeyword.comment.isEmpty()) { if (fits_modify_comment(m_fitsFile, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), updatedKeyword.comment.toLatin1().data(), &status)) { printError(status); status = 0; } } else { if (keywordUpdate.commentUpdated) { if (fits_modify_comment(m_fitsFile, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), QByteArray().constData(), &status)) { printError(status); status = 0; } } } } status = 0; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(fileName) Q_UNUSED(originals) Q_UNUSED(updates) #endif } /*! * \brief Delete the keywords \a keywords from the current header unit * \param fileName the name of the FITS file (extension) in which the keywords will be deleted. * \param keywords the keywords to deleted */ void FITSFilterPrivate::deleteKeyword(const QString& fileName, const QList &keywords) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } for (const auto& keyword : keywords) { if (!keyword.key.isEmpty()) { status = 0; if (fits_delete_key(m_fitsFile, keyword.key.toLatin1(), &status)) printError(status); } } status = 0; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(keywords) Q_UNUSED(fileName) #endif } /*! * \brief FITSFilterPrivate::addKeywordUnit * \param fileName the FITS file (extension) in which the keyword units are updated/added * \param keywords the keywords whose units were modified/added */ void FITSFilterPrivate::addKeywordUnit(const QString &fileName, const QList &keywords) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } for (const FITSFilter::Keyword& keyword : keywords) { if (keyword.updates.unitUpdated) { if (fits_write_key_unit(m_fitsFile, keyword.key.toLatin1(), keyword.unit.toLatin1().data(), &status)) { printError(status); status = 0; } } } status = 0; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(fileName) Q_UNUSED(keywords) #endif } /*! * \brief Remove extensions from a FITS file * \param extensions The extensions to be removed from the FITS file */ void FITSFilterPrivate::removeExtensions(const QStringList &extensions) { #ifdef HAVE_FITS int status = 0; for (const auto& ext : extensions) { status = 0; if (fits_open_file(&m_fitsFile, ext.toLatin1(), READWRITE, &status )) { printError(status); continue; } if (fits_delete_hdu(m_fitsFile, nullptr, &status)) printError(status); status = 0; fits_close_file(m_fitsFile, &status); } #else Q_UNUSED(extensions) #endif } /*! * \brief Returns a list of keywords in the current header of \a fileName * \param fileName the name of the FITS file (extension) to be opened * \return A list of keywords */ QList FITSFilterPrivate::chduKeywords(const QString& fileName) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READONLY, &status )) { printError(status); return QList (); } int numberOfKeys; if (fits_get_hdrspace(m_fitsFile, &numberOfKeys, nullptr, &status)) { printError(status); return QList (); } QList keywords; keywords.reserve(numberOfKeys); char* key = new char[FLEN_KEYWORD]; char* value = new char[FLEN_VALUE]; char* comment = new char[FLEN_COMMENT]; char* unit = new char[FLEN_VALUE]; for (int i = 1; i <= numberOfKeys; ++i) { QStringList recordValues; FITSFilter::Keyword keyword; if (fits_read_keyn(m_fitsFile, i, key, value, comment, &status)) { printError(status); status = 0; continue; } fits_read_key_unit(m_fitsFile, key, unit, &status); recordValues << QLatin1String(key) << QLatin1String(value) << QLatin1String(comment) << QLatin1String(unit); keyword.key = recordValues[0].simplified(); keyword.value = recordValues[1].simplified(); keyword.comment = recordValues[2].simplified(); keyword.unit = recordValues[3].simplified(); keywords.append(keyword); } delete[] key; delete[] value; delete[] comment; delete[] unit; fits_close_file(m_fitsFile, &status); return keywords; #else Q_UNUSED(fileName) return QList(); #endif } /*! * \brief Builds the table \a headerEditTable from the keywords \a keys * \param fileName The name of the FITS file from which the keys are read if \a readKeys is \c true * \param headerEditTable The table to be built * \param readKeys It's used to determine whether the keywords are provided or they should be read from * file \a fileName * \param keys The keywords that are provided if the keywords were read already. */ void FITSFilterPrivate::parseHeader(const QString &fileName, QTableWidget *headerEditTable, bool readKeys, const QList& keys) { #ifdef HAVE_FITS QList keywords; if (readKeys) keywords = chduKeywords(fileName); else keywords = keys; headerEditTable->setRowCount(keywords.size()); QTableWidgetItem* item; for (int i = 0; i < keywords.size(); ++i) { const FITSFilter::Keyword& keyword = keywords.at(i); const bool mandatory = FITSFilter::mandatoryImageExtensionKeywords().contains(keyword.key) || FITSFilter::mandatoryTableExtensionKeywords().contains(keyword.key); item = new QTableWidgetItem(keyword.key); const QString& itemText = item->text(); const bool notEditableKey = mandatory || itemText.contains(QLatin1String("TFORM")) || itemText.contains(QLatin1String("TTYPE")) || itemText.contains(QLatin1String("TUNIT")) || itemText.contains(QLatin1String("TDISP")) || itemText.contains(QLatin1String("TBCOL")) || itemText.contains(QLatin1String("TZERO")); const bool notEditableValue = mandatory || itemText.contains(QLatin1String("TFORM")) || itemText.contains(QLatin1String("TDISP")) || itemText.contains(QLatin1String("TBCOL")) || itemText.contains(QLatin1String("TZERO")); if (notEditableKey) item->setFlags(item->flags() & ~Qt::ItemIsEditable); else item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); headerEditTable->setItem(i, 0, item ); item = new QTableWidgetItem(keyword.value); if (notEditableValue) item->setFlags(item->flags() & ~Qt::ItemIsEditable); else item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); headerEditTable->setItem(i, 1, item ); QString commentFieldText; if (!keyword.unit.isEmpty()) { if (keyword.updates.unitUpdated) { const QString& comment = keyword.comment.right(keyword.comment.size() - keyword.comment.indexOf(QChar(']'))-1); commentFieldText = QLatin1String("[") + keyword.unit + QLatin1String("] ") + comment; } else { if (keyword.comment.at(0) == QLatin1Char('[')) commentFieldText = keyword.comment; else commentFieldText = QLatin1String("[") + keyword.unit + QLatin1String("] ") + keyword.comment; } } else commentFieldText = keyword.comment; item = new QTableWidgetItem(commentFieldText); item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); headerEditTable->setItem(i, 2, item ); } headerEditTable->resizeColumnsToContents(); #else Q_UNUSED(fileName) Q_UNUSED(headerEditTable) Q_UNUSED(readKeys) Q_UNUSED(keys) #endif } /*! * \brief Helper function to return the value of the key \a key * \param fileName The name of the FITS file (extension) in which the keyword with key \a key should exist * \param key The key of the keyword whose value it's returned * \return The value of the keyword as a string */ const QString FITSFilterPrivate::valueOf(const QString& fileName, const char *key) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READONLY, &status )) { printError(status); return QString (); } char* keyVal = new char[FLEN_VALUE]; QString keyValue; if (!fits_read_keyword(m_fitsFile, key, keyVal, nullptr, &status)) { keyValue = QLatin1String(keyVal); keyValue = keyValue.simplified(); } else { printError(status); delete[] keyVal; fits_close_file(m_fitsFile, &status); return QString(); } delete[] keyVal; status = 0; fits_close_file(m_fitsFile, &status); return keyValue; #else Q_UNUSED(fileName) Q_UNUSED(key) return QString(); #endif } /*! * \brief Build the extensions tree from FITS file (extension) \a fileName * \param fileName The name of the FITS file to be opened * \param tw The QTreeWidget to be built * \param checkPrimary Used to determine whether the tree will be used for import or the header edit, * if it's \c true and if the primary array it's empty, then the item won't be added to the tree */ void FITSFilterPrivate::parseExtensions(const QString &fileName, QTreeWidget *tw, bool checkPrimary) { DEBUG("FITSFilterPrivate::parseExtensions()"); #ifdef HAVE_FITS const QMultiMap& extensions = extensionNames(fileName); const QStringList& imageExtensions = extensions.values(QLatin1String("IMAGES")); const QStringList& tableExtensions = extensions.values(QLatin1String("TABLES")); QTreeWidgetItem* root = tw->invisibleRootItem(); //TODO: fileName may contain any data type: check if it's a FITS file QTreeWidgetItem* treeNameItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << fileName); root->addChild(treeNameItem); treeNameItem->setExpanded(true); QTreeWidgetItem* imageExtensionItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << i18n("Images")); imageExtensionItem->setFlags(imageExtensionItem->flags() & ~Qt::ItemIsSelectable ); QString primaryHeaderNaxis = valueOf(fileName, "NAXIS"); const int naxis = primaryHeaderNaxis.toInt(); bool noImage = false; for (const QString& ext : imageExtensions) { QTreeWidgetItem* treeItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << ext); if (ext == i18n("Primary header")) { if (checkPrimary && naxis == 0) { delete treeItem; continue; } } imageExtensionItem->addChild(treeItem); } if (imageExtensionItem->childCount() > 0) { treeNameItem->addChild(imageExtensionItem); imageExtensionItem->setIcon(0,QIcon::fromTheme("view-preview")); imageExtensionItem->setExpanded(true); imageExtensionItem->child(0)->setSelected(true); tw->setCurrentItem(imageExtensionItem->child(0)); } else noImage = true; if (tableExtensions.size() > 0) { QTreeWidgetItem* tableExtensionItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << i18n("Tables")); tableExtensionItem->setFlags(tableExtensionItem->flags() & ~Qt::ItemIsSelectable ); for (const QString& ext : tableExtensions) { QTreeWidgetItem* treeItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << ext); tableExtensionItem->addChild(treeItem); } if (tableExtensionItem->childCount() > 0) { treeNameItem->addChild(tableExtensionItem); tableExtensionItem->setIcon(0,QIcon::fromTheme("x-office-spreadsheet")); tableExtensionItem->setExpanded(true); if (noImage) { tableExtensionItem->child(0)->setSelected(true); tw->setCurrentItem(tableExtensionItem->child(0)); } } } #else Q_UNUSED(fileName) Q_UNUSED(tw) Q_UNUSED(checkPrimary) #endif DEBUG("FITSFilterPrivate::parseExtensions() DONE"); } /*! * \brief FITSFilterPrivate::~FITSFilterPrivate */ FITSFilterPrivate::~FITSFilterPrivate() = default; //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void FITSFilter::save(QXmlStreamWriter * writer) const { Q_UNUSED(writer) } /*! Loads from XML. */ bool FITSFilter::load(XmlStreamReader * loader) { Q_UNUSED(loader) return false; } diff --git a/src/backend/datasources/filters/JsonFilter.cpp b/src/backend/datasources/filters/JsonFilter.cpp index 62b274dd7..0c52ab65c 100644 --- a/src/backend/datasources/filters/JsonFilter.cpp +++ b/src/backend/datasources/filters/JsonFilter.cpp @@ -1,854 +1,854 @@ /*************************************************************************** File : JsonFilter.cpp Project : LabPlot Description : JSON I/O-filter. -------------------------------------------------------------------- -------------------------------------------------------------------- Copyright : (C) 2018 Andrey Cygankov (craftplace.ms@gmail.com) Copyright : (C) 2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2018-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/datasources/filters/JsonFilter.h" #include "backend/datasources/filters/JsonFilterPrivate.h" #include "backend/datasources/AbstractDataSource.h" #include "backend/core/column/Column.h" #include "backend/spreadsheet/Spreadsheet.h" #include #include #include #include #include #include #include /*! \class JsonFilter \brief Manages the import/export of data from/to a file formatted using JSON. \ingroup datasources */ JsonFilter::JsonFilter() : AbstractFileFilter(JSON), d(new JsonFilterPrivate(this)) {} JsonFilter::~JsonFilter() = default; /*! reads the content of the device \c device. */ void JsonFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromDevice(device, dataSource, importMode, lines); } /*! reads the content of the file \c fileName. */ void JsonFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } QVector JsonFilter::preview(const QString& fileName) { return d->preview(fileName); } QVector JsonFilter::preview(QIODevice& device) { return d->preview(device); } QVector JsonFilter::preview(const QJsonDocument& doc) { return d->preview(doc); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void JsonFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); } /////////////////////////////////////////////////////////////////////// /*! loads the predefined filter settings for \c filterName */ void JsonFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void JsonFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /*! returns the list of all predefined data types. */ QStringList JsonFilter::dataTypes() { const QMetaObject& mo = AbstractColumn::staticMetaObject; const QMetaEnum& me = mo.enumerator(mo.indexOfEnumerator("ColumnMode")); QStringList list; for (int i = 0; i <= 100; ++i) // me.keyCount() does not work because we have holes in enum if (me.valueToKey(i)) list << me.valueToKey(i); return list; } /*! returns the list of all predefined data row types. */ QStringList JsonFilter::dataRowTypes() { return (QStringList() << "Array" << "Object"); } void JsonFilter::setDataRowType(QJsonValue::Type type) { d->rowType = type; } QJsonValue::Type JsonFilter::dataRowType() const { return d->rowType; } void JsonFilter::setModelRows(const QVector& rows) { d->modelRows = rows; } QVector JsonFilter::modelRows() const { return d->modelRows; } void JsonFilter::setDateTimeFormat(const QString &f) { d->dateTimeFormat = f; } QString JsonFilter::dateTimeFormat() const { return d->dateTimeFormat; } void JsonFilter::setNumberFormat(QLocale::Language lang) { d->numberFormat = lang; } QLocale::Language JsonFilter::numberFormat() const { return d->numberFormat; } void JsonFilter::setNaNValueToZero(bool b) { if (b) d->nanValue = 0; else d->nanValue = NAN; } bool JsonFilter::NaNValueToZeroEnabled() const { if (d->nanValue == 0) return true; return false; } void JsonFilter::setCreateIndexEnabled(bool b) { d->createIndexEnabled = b; } void JsonFilter::setImportObjectNames(bool b) { d->importObjectNames = b; } QStringList JsonFilter::vectorNames() const { return d->vectorNames; } QVector JsonFilter::columnModes() { return d->columnModes; } void JsonFilter::setStartRow(const int r) { d->startRow = r; } int JsonFilter::startRow() const { return d->startRow; } void JsonFilter::setEndRow(const int r) { d->endRow = r; } int JsonFilter::endRow() const { return d->endRow; } void JsonFilter::setStartColumn(const int c) { d->startColumn = c; } int JsonFilter::startColumn() const { return d->startColumn; } void JsonFilter::setEndColumn(const int c) { d->endColumn = c; } int JsonFilter::endColumn() const { return d->endColumn; } QString JsonFilter::fileInfoString(const QString& fileName) { DEBUG("JsonFilter::fileInfoString()"); KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) return i18n("Open device failed"); if (device.atEnd() && !device.isSequential()) return i18n("Empty file"); QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(device.readAll(), &err); if (err.error != QJsonParseError::NoError || doc.isEmpty()) return i18n("Parse error: %1 at offset %2", err.errorString(), err.offset); QString info; info += i18n("Valid JSON document"); //TODO: get number of object, etc. //if (prepareDocumentToRead(doc) != 0) // return info; // reset to start of file if (!device.isSequential()) device.seek(0); return info; } //##################################################################### //################### Private implementation ########################## //##################################################################### JsonFilterPrivate::JsonFilterPrivate(JsonFilter* owner) : q(owner), model(new QJsonModel()) {} //TODO: delete model from memory /*! returns 1 if row is invalid and 0 otherwise. */ int JsonFilterPrivate::checkRow(QJsonValueRef value, int& countCols) { switch (rowType) { //TODO: implement other value types case QJsonValue::Array: { QJsonArray row = value.toArray(); if (row.isEmpty()) return 1; countCols = (countCols == -1 || countCols > row.count()) ? row.count() : countCols; break; } case QJsonValue::Object: { QJsonObject row = value.toObject(); if (row.isEmpty()) return 1; countCols = (countCols == -1 || countCols > row.count()) ? row.count() : countCols; break; } case QJsonValue::Double: case QJsonValue::String: case QJsonValue::Bool: case QJsonValue::Null: case QJsonValue::Undefined: return 1; } return 0; } /*! returns -1 if a parse error has occurred, 1 if the current row type not supported and 0 otherwise. */ int JsonFilterPrivate::parseColumnModes(const QJsonValue& row, const QString& rowName) { columnModes.clear(); vectorNames.clear(); //add index column if required if (createIndexEnabled) { - columnModes << AbstractColumn::Integer; + columnModes << AbstractColumn::ColumnMode::Integer; vectorNames << i18n("index"); } //add column for object names if required if (importObjectNames) { - const AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(rowName, dateTimeFormat, numberFormat); + const auto mode = AbstractFileFilter::columnMode(rowName, dateTimeFormat, numberFormat); columnModes << mode; - if (mode == AbstractColumn::DateTime) + if (mode == AbstractColumn::ColumnMode::DateTime) vectorNames << i18n("timestamp"); - else if (mode == AbstractColumn::Month) + else if (mode == AbstractColumn::ColumnMode::Month) vectorNames << i18n("month"); - else if (mode == AbstractColumn::Day) + else if (mode == AbstractColumn::ColumnMode::Day) vectorNames << i18n("day"); else vectorNames << i18n("name"); } //determine the column modes and names for (int i = startColumn - 1; i < endColumn; ++i) { QJsonValue columnValue; switch (rowType) { case QJsonValue::Array: { columnValue = *(row.toArray().begin() + i); vectorNames << i18n("Column %1", QString::number(i + 1)); break; } case QJsonValue::Object: { QString key = row.toObject().keys().at(i); vectorNames << key; columnValue = row.toObject().value(key); break; } //TODO: implement other value types case QJsonValue::Double: case QJsonValue::String: case QJsonValue::Bool: case QJsonValue::Null: case QJsonValue::Undefined: return 1; } switch (columnValue.type()) { case QJsonValue::Double: - columnModes << AbstractColumn::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; break; case QJsonValue::String: columnModes << AbstractFileFilter::columnMode(columnValue.toString(), dateTimeFormat, numberFormat); break; case QJsonValue::Array: case QJsonValue::Object: case QJsonValue::Bool: case QJsonValue::Null: case QJsonValue::Undefined: return -1; } } return 0; } void JsonFilterPrivate::setEmptyValue(int column, int row) { switch (columnModes[column]) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: static_cast*>(m_dataContainer[column])->operator[](row) = nanValue; break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: static_cast*>(m_dataContainer[column])->operator[](row) = 0; break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: static_cast*>(m_dataContainer[column])->operator[](row) = 0; break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: static_cast*>(m_dataContainer[column])->operator[](row) = QDateTime(); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: static_cast*>(m_dataContainer[column])->operator[](row) = QString(); break; - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } void JsonFilterPrivate::setValueFromString(int column, int row, const QString& valueString) { QLocale locale(numberFormat); switch (columnModes[column]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[column])->operator[](row) = isNumber ? value : nanValue; break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[column])->operator[](row) = isNumber ? value : 0; break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); static_cast*>(m_dataContainer[column])->operator[](row) = isNumber ? value : 0; break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); static_cast*>(m_dataContainer[column])->operator[](row) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: static_cast*>(m_dataContainer[column])->operator[](row) = valueString; break; - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } } /*! returns -1 if the device couldn't be opened, 1 if the current read position in the device is at the end */ int JsonFilterPrivate::prepareDeviceToRead(QIODevice& device) { DEBUG("device is sequential = " << device.isSequential()); if (!device.open(QIODevice::ReadOnly)) return -1; if (device.atEnd() && !device.isSequential()) // empty file return 1; QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(device.readAll(), &err); if (err.error != QJsonParseError::NoError || doc.isEmpty()) return 1; if (prepareDocumentToRead(doc) != 0) return 2; // reset to start of file if (!device.isSequential()) device.seek(0); return 0; } /*! returns 2 if a parse error has occurred and 0 otherwise. */ int JsonFilterPrivate::prepareDocumentToRead(const QJsonDocument& doc) { model->loadJson(doc); if (modelRows.isEmpty()) m_preparedDoc = doc; else { QModelIndex index; for (auto& it : modelRows) index = model->index(it, 0, index); m_preparedDoc = model->genJsonByIndex(index); } if (!m_preparedDoc.isEmpty()) { if (m_preparedDoc.isArray()) containerType = JsonFilter::Array; else if (m_preparedDoc.isObject()) containerType = JsonFilter::Object; else return 2; } else return 2; int countRows = 0; int countCols = -1; QJsonValue firstRow; QString firstRowName; importObjectNames = (importObjectNames && (rowType == QJsonValue::Object)); switch (containerType) { case JsonFilter::Array: { QJsonArray arr = m_preparedDoc.array(); if (arr.count() < startRow) return 2; int endRowOffset = (endRow == -1 || endRow > arr.count()) ? arr.count() : endRow; firstRow = *(arr.begin() + (startRow - 1)); for (QJsonArray::iterator it = arr.begin() + (startRow - 1); it != arr.begin() + endRowOffset; ++it) { if (checkRow(*it, countCols) != 0) return 2; countRows++; } break; } case JsonFilter::Object: { QJsonObject obj = m_preparedDoc.object(); if (obj.count() < startRow) return 2; int startRowOffset = startRow - 1; int endRowOffset = (endRow == -1 || endRow > obj.count()) ? obj.count() : endRow; firstRow = *(obj.begin() + startRowOffset); firstRowName = (obj.begin() + startRowOffset).key(); for (QJsonObject::iterator it = obj.begin() + startRowOffset; it != obj.begin() + endRowOffset; ++it) { if (checkRow(*it, countCols) != 0) return 2; countRows++; } break; } } if (endColumn == -1 || endColumn > countCols) endColumn = countCols; m_actualRows = countRows; m_actualCols = endColumn - startColumn + 1 + createIndexEnabled + importObjectNames; if (parseColumnModes(firstRow, firstRowName) != 0) return 2; DEBUG("start/end column: = " << startColumn << ' ' << endColumn); DEBUG("start/end rows = " << startRow << ' ' << endRow); DEBUG("actual cols/rows = " << m_actualCols << ' ' << m_actualRows); return 0; } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void JsonFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { KFilterDev device(fileName); readDataFromDevice(device, dataSource, importMode); } /*! reads the content of device \c device to the data source \c dataSource. Uses the settings defined in the data source. */ void JsonFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { if (!m_prepared) { const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG("Device error = " << deviceError); return; } //TODO: support other modes and vector names m_prepared = true; } importData(dataSource, importMode, lines); } /*! reads the content of document \c doc to the data source \c dataSource. Uses the settings defined in the data source. */ void JsonFilterPrivate::readDataFromDocument(const QJsonDocument& doc, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { if (!m_prepared) { const int docError = prepareDocumentToRead(doc); if (docError != 0) { DEBUG("Document parse error = " << docError); return; } //TODO: support other modes and vector names m_prepared = true; } importData(dataSource, importMode, lines); } /*! import the content of document \c m_preparedDoc to the data source \c dataSource. Uses the settings defined in the data source. */ void JsonFilterPrivate::importData(AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { Q_UNUSED(lines) m_columnOffset = dataSource->prepareImport(m_dataContainer, importMode, m_actualRows, m_actualCols, vectorNames, columnModes); int rowOffset = startRow - 1; int colOffset = (int)createIndexEnabled + (int)importObjectNames; DEBUG("reading " << m_actualRows << " lines"); DEBUG("reading " << m_actualCols << " columns"); for (int i = 0; i < m_actualRows; ++i) { if (createIndexEnabled) static_cast*>(m_dataContainer[0])->operator[](i) = i + 1; QJsonValue row; switch (containerType) { case JsonFilter::Array: row = *(m_preparedDoc.array().begin() + rowOffset + i); break; case JsonFilter::Object: if (importObjectNames) { const QString& rowName = (m_preparedDoc.object().begin() + rowOffset + i).key(); setValueFromString((int)createIndexEnabled, i, rowName); } row = *(m_preparedDoc.object().begin() + rowOffset + i); break; } for (int n = 0; n < m_actualCols - colOffset; ++n) { QJsonValue value; switch (rowType) { case QJsonValue::Array: value = *(row.toArray().begin() + n + startColumn -1); break; case QJsonValue::Object: value = *(row.toObject().begin() + n + startColumn - 1); break; //TODO: implement other value types case QJsonValue::Double: case QJsonValue::String: case QJsonValue::Bool: case QJsonValue::Null: case QJsonValue::Undefined: break; } switch (value.type()) { case QJsonValue::Double: - if (columnModes[colOffset + n] == AbstractColumn::Numeric) + if (columnModes[colOffset + n] == AbstractColumn::ColumnMode::Numeric) static_cast*>(m_dataContainer[colOffset + n])->operator[](i) = value.toDouble(); else setEmptyValue(colOffset + n, i + startRow - 1); break; case QJsonValue::String: setValueFromString(colOffset + n, i, value.toString()); break; case QJsonValue::Array: case QJsonValue::Object: case QJsonValue::Bool: case QJsonValue::Null: case QJsonValue::Undefined: setEmptyValue(colOffset + n, i + startRow - 1); break; } } emit q->completed(100 * i/m_actualRows); } //set the plot designation to 'X' for index and name columns, if available Spreadsheet* spreadsheet = dynamic_cast(dataSource); if (spreadsheet) { if (createIndexEnabled) spreadsheet->column(m_columnOffset )->setPlotDesignation(AbstractColumn::PlotDesignation::X); if (importObjectNames) spreadsheet->column(m_columnOffset + (int)createIndexEnabled)->setPlotDesignation(AbstractColumn::PlotDesignation::X); } dataSource->finalizeImport(m_columnOffset, startColumn, startColumn + m_actualCols - 1, dateTimeFormat, importMode); } /*! generates the preview for the file \c fileName. */ QVector JsonFilterPrivate::preview(const QString& fileName) { KFilterDev device(fileName); return preview(device); } /*! generates the preview for device \c device. */ QVector JsonFilterPrivate::preview(QIODevice& device) { const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG("Device error = " << deviceError); return QVector(); } return preview(); } /*! generates the preview for document \c doc. */ QVector JsonFilterPrivate::preview(const QJsonDocument& doc) { if (prepareDocumentToRead(doc) != 0) return QVector(); return preview(); } /*! generates the preview for document \c m_preparedDoc. */ QVector JsonFilterPrivate::preview() { QVector dataStrings; const int rowOffset = startRow - 1; DEBUG("reading " << m_actualRows << " lines"); for (int i = 0; i < m_actualRows; ++i) { QString rowName; QJsonValue row; switch (containerType) { case JsonFilter::Object: rowName = (m_preparedDoc.object().begin() + rowOffset + i).key(); row = *(m_preparedDoc.object().begin() + rowOffset + i); break; case JsonFilter::Array: row = *(m_preparedDoc.array().begin() + rowOffset + i); break; } QStringList lineString; if (createIndexEnabled) lineString += QString::number(i + 1); if (importObjectNames) lineString += rowName; for (int n = startColumn - 1; n < endColumn; ++n) { QJsonValue value; switch (rowType) { case QJsonValue::Object: value = *(row.toObject().begin() + n); break; case QJsonValue::Array: value = *(row.toArray().begin() + n); break; //TODO: implement other value types case QJsonValue::Double: case QJsonValue::String: case QJsonValue::Bool: case QJsonValue::Null: case QJsonValue::Undefined: break; } switch (value.type()) { case QJsonValue::Double: - if (columnModes[n] == AbstractColumn::Numeric) + if (columnModes[n] == AbstractColumn::ColumnMode::Numeric) lineString += QString::number(value.toDouble(), 'g', 16); else lineString += lineString += QString(); break; case QJsonValue::String: //TODO: add parsing string before appending lineString += value.toString(); break; case QJsonValue::Array: case QJsonValue::Object: case QJsonValue::Bool: case QJsonValue::Null: case QJsonValue::Undefined: lineString += QString(); break; } } dataStrings << lineString; emit q->completed(100 * i/m_actualRows); } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void JsonFilterPrivate::write(const QString& fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: saving data to json file not supported yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void JsonFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("jsonFilter"); writer->writeAttribute("rowType", QString::number(d->rowType)); writer->writeAttribute("dateTimeFormat", d->dateTimeFormat); writer->writeAttribute("numberFormat", QString::number(d->numberFormat)); writer->writeAttribute("createIndex", QString::number(d->createIndexEnabled)); writer->writeAttribute("importObjectNames", QString::number(d->importObjectNames)); writer->writeAttribute("nanValue", QString::number(d->nanValue)); writer->writeAttribute("startRow", QString::number(d->startRow)); writer->writeAttribute("endRow", QString::number(d->endRow)); writer->writeAttribute("startColumn", QString::number(d->startColumn)); writer->writeAttribute("endColumn", QString::number(d->endColumn)); QStringList list; for (auto& it : modelRows()) list.append(QString::number(it)); writer->writeAttribute("modelRows", list.join(';')); writer->writeEndElement(); DEBUG("JsonFilter save params"); } /*! Loads from XML. */ bool JsonFilter::load(XmlStreamReader* reader) { QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value("rowType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rowType'")); else d->rowType = static_cast(str.toInt()); str = attribs.value("dateTimeFormat").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'dateTimeFormat'")); else d->dateTimeFormat = str; str = attribs.value("numberFormat").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'numberFormat'")); else d->numberFormat = static_cast(str.toInt()); str = attribs.value("createIndex").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'createIndex'")); else d->createIndexEnabled = str.toInt(); str = attribs.value("importObjectNames").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'importObjectNames'")); else d->importObjectNames = str.toInt(); str = attribs.value("nanValue").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'nanValue'")); else d->nanValue = str.toDouble(); str = attribs.value("startRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'startRow'")); else d->startRow = str.toInt(); str = attribs.value("endRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'endRow'")); else d->endRow = str.toInt(); str = attribs.value("startColumn").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'startColumn'")); else d->startColumn = str.toInt(); str = attribs.value("endColumn").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'endColumn'")); else d->endColumn = str.toInt(); QStringList list = attribs.value("modelRows").toString().split(';'); if (list.isEmpty()) reader->raiseWarning(attributeWarning.arg("'modelRows'")); else { d->modelRows = QVector(); for (auto& it : list) d->modelRows.append(it.toInt()); } DEBUG("JsonFilter load params"); return true; } diff --git a/src/backend/datasources/filters/NetCDFFilter.cpp b/src/backend/datasources/filters/NetCDFFilter.cpp index ba0eef5fe..fb43e5373 100644 --- a/src/backend/datasources/filters/NetCDFFilter.cpp +++ b/src/backend/datasources/filters/NetCDFFilter.cpp @@ -1,1001 +1,1001 @@ /*************************************************************************** File : NetCDFFilter.cpp Project : LabPlot Description : NetCDF I/O-filter -------------------------------------------------------------------- Copyright : (C) 2015-2019 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 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/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/NetCDFFilterPrivate.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/column/Column.h" #include #include ///////////// macros /////////////////////////////////////////////// #define NC_GET_ATT(type, ftype) \ auto* value = (type*)malloc(len*sizeof(type)); \ m_status = nc_get_att_ ##ftype(ncid, varid, name, value); \ handleError(m_status, "nc_get_att_" #ftype); \ for (unsigned int l = 0; l < len; l++) \ valueString << QString::number(value[l]); \ free(value); #define NC_SCAN_VAR(type, ftype) \ type data; \ m_status = nc_get_var_ ##ftype(ncid, i, &data); \ handleError(m_status, "nc_get_var_" #ftype); \ rowStrings << QString::number(data); #define NC_READ_VAR(type, ftype, dtype) \ type data; \ m_status = nc_get_var_ ##ftype(ncid, varid, &data); \ handleError(m_status, "nc_get_var_" #ftype); \ \ if (dataSource) { \ dtype *sourceData = static_cast*>(dataContainer[0])->data(); \ sourceData[0] = (dtype)data; \ } else { /* preview */ \ dataStrings << (QStringList() << QString::number(data)); \ } #define NC_READ_AVAR(type, ftype, dtype) \ auto* data = new type[(unsigned int)actualRows]; \ \ size_t start = (size_t)(startRow - 1), count = (size_t)actualRows; \ m_status = nc_get_vara_ ##ftype(ncid, varid, &start, &count, data); \ handleError(m_status, "nc_get_vara_" #ftype); \ \ if (dataSource) { \ dtype *sourceData = static_cast*>(dataContainer[0])->data(); \ for (int i = 0; i < actualRows; i++) \ sourceData[i] = (dtype)data[i]; \ } else { /* preview */ \ for (int i = 0; i < qMin(actualRows, lines); i++) \ dataStrings << (QStringList() << QString::number(data[i])); \ } \ delete[] data; // for native types (atm: int, double) #define NC_READ_AVAR_NATIVE(type) \ type* data = nullptr; \ if (dataSource) \ data = static_cast*>(dataContainer[0])->data(); \ else \ data = new type[(unsigned int)actualRows]; \ \ size_t start = (size_t)(startRow - 1), count = (size_t)actualRows; \ m_status = nc_get_vara_ ##type(ncid, varid, &start, &count, data); \ handleError(m_status, "nc_get_vara_" #type); \ \ if (!dataSource) { /* preview */ \ for (int i = 0; i < qMin(actualRows, lines); i++) \ dataStrings << (QStringList() << QString::number(data[i])); \ delete[] data; \ } #define NC_READ_VAR2(type, ftype, dtype) \ auto** data = (type**) malloc(rows * sizeof(type*)); \ data[0] = (type*)malloc(cols * rows * sizeof(type)); \ for (unsigned int i = 1; i < rows; i++) \ data[i] = data[0] + i*cols; \ \ m_status = nc_get_var_ ##ftype(ncid, varid, &data[0][0]); \ handleError(m_status, "nc_get_var_" #ftype); \ \ if (m_status == NC_NOERR) { \ for (int i = 0; i < qMin((int)rows, lines); i++) { \ QStringList line; \ for (size_t j = 0; j < cols; j++) { \ if (dataSource && dataContainer[0]) \ static_cast*>(dataContainer[(int)(j-(size_t)startColumn+1)])->operator[](i-startRow+1) = data[i][(int)j]; \ else \ line << QString::number(data[i][j]); \ } \ dataStrings << line; \ emit q->completed(100*i/actualRows); \ } \ } \ free(data[0]); \ free(data); ////////////////////////////////////////////////////////////////////// /*! \class NetCDFFilter \brief Manages the import/export of data from/to a NetCDF file. \ingroup datasources */ NetCDFFilter::NetCDFFilter():AbstractFileFilter(NETCDF), d(new NetCDFFilterPrivate(this)) {} NetCDFFilter::~NetCDFFilter() = default; /*! parses the content of the file \c ileName. */ void NetCDFFilter::parse(const QString & fileName, QTreeWidgetItem* rootItem) { d->parse(fileName, rootItem); } /*! reads the content of the selected attribute from file \c fileName. */ QString NetCDFFilter::readAttribute(const QString & fileName, const QString & name, const QString & varName) { return d->readAttribute(fileName, name, varName); } /*! reads the content of the current variable from file \c fileName. */ QVector NetCDFFilter::readCurrentVar(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { return d->readCurrentVar(fileName, dataSource, importMode, lines); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ void NetCDFFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode) { d->readDataFromFile(fileName, dataSource, mode); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void NetCDFFilter::write(const QString & fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /////////////////////////////////////////////////////////////////////// /*! loads the predefined filter settings for \c filterName */ void NetCDFFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void NetCDFFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /////////////////////////////////////////////////////////////////////// void NetCDFFilter::setCurrentVarName(const QString& ds) { d->currentVarName = ds; } const QString NetCDFFilter::currentVarName() const { return d->currentVarName; } void NetCDFFilter::setStartRow(const int s) { d->startRow = s; } int NetCDFFilter::startRow() const { return d->startRow; } void NetCDFFilter::setEndRow(const int e) { d->endRow = e; } int NetCDFFilter::endRow() const { return d->endRow; } void NetCDFFilter::setStartColumn(const int c) { d->startColumn = c; } int NetCDFFilter::startColumn() const { return d->startColumn; } void NetCDFFilter::setEndColumn(const int c) { d->endColumn = c; } int NetCDFFilter::endColumn() const { return d->endColumn; } QString NetCDFFilter::fileInfoString(const QString& fileName) { DEBUG("NetCDFFilter::fileInfoString()"); QByteArray bafileName = fileName.toLatin1(); DEBUG("fileName = " << bafileName.data()); QString info; #ifdef HAVE_NETCDF int ncid, status; status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); NetCDFFilterPrivate::handleError(status, "nc_open"); if (status != NC_NOERR) { DEBUG(" File error. Giving up"); return i18n("Error opening file"); } int ndims, nvars, nattr, uldid; status = nc_inq(ncid, &ndims, &nvars, &nattr, &uldid); NetCDFFilterPrivate::handleError(status, "nc_inq"); DEBUG(" nattr/ndims/nvars = " << nattr << ' ' << ndims << ' ' << nvars); if (status == NC_NOERR) { info += i18n("Number of global attributes: %1", QString::number(nattr)); info += QLatin1String("
"); info += i18n("Number of dimensions: %1", QString::number(ndims)); info += QLatin1String("
"); info += i18n("Number of variables: %1", QString::number(nvars)); info += QLatin1String("
"); int format; status = nc_inq_format(ncid, &format); if (status == NC_NOERR) info += i18n("Format version: %1", NetCDFFilterPrivate::translateFormat(format)); info += QLatin1String("
"); info += i18n("Using library version %1", QString(nc_inq_libvers())); } else { info += i18n("Error getting file info"); } status = ncclose(ncid); NetCDFFilterPrivate::handleError(status, "nc_close"); #endif return info; } /*! * Get file content in CDL (Common Data form Language) format * uses "ncdump" */ QString NetCDFFilter::fileCDLString(const QString& fileName) { DEBUG("NetCDFFilter::fileCDLString()"); QString CDLString; #ifdef Q_OS_LINUX auto* proc = new QProcess(); QStringList args; args << "-cs" << fileName; proc->start( "ncdump", args); if (proc->waitForReadyRead(1000) == false) CDLString += i18n("Reading from file %1 failed.", fileName); else { CDLString += proc->readAll(); CDLString.replace('\n', "
\n"); CDLString.replace("\t","    "); //DEBUG(" CDL string: " << STDSTRING(CDLString)); } #else //TODO: ncdump on Win, Mac Q_UNUSED(fileName) #endif return CDLString; } //##################################################################### //################### Private implementation ########################## //##################################################################### NetCDFFilterPrivate::NetCDFFilterPrivate(NetCDFFilter* owner) : q(owner) { #ifdef HAVE_NETCDF m_status = 0; #endif } #ifdef HAVE_NETCDF void NetCDFFilterPrivate::handleError(int err, const QString& function) { if (err != NC_NOERR) { DEBUG("NETCDF ERROR:" << STDSTRING(function) << "() - " << nc_strerror(err)); return; } Q_UNUSED(function); } QString NetCDFFilterPrivate::translateDataType(nc_type type) { QString typeString; switch (type) { case NC_BYTE: typeString = "BYTE"; break; case NC_UBYTE: typeString = "UBYTE"; break; case NC_CHAR: typeString = "CHAR"; break; case NC_SHORT: typeString = "SHORT"; break; case NC_USHORT: typeString = "USHORT"; break; case NC_INT: typeString = "INT"; break; case NC_UINT: typeString = "UINT"; break; case NC_INT64: typeString = "INT64"; break; case NC_UINT64: typeString = "UINT64"; break; case NC_FLOAT: typeString = "FLOAT"; break; case NC_DOUBLE: typeString = "DOUBLE"; break; case NC_STRING: typeString = "STRING"; break; default: typeString = "UNKNOWN"; } return typeString; } QString NetCDFFilterPrivate::translateFormat(int format) { QString formatString; switch (format) { case NC_FORMAT_CLASSIC: formatString = "NC_FORMAT_CLASSIC"; break; case NC_FORMAT_64BIT: formatString = "NC_FORMAT_64BIT"; break; case NC_FORMAT_NETCDF4: formatString = "NC_FORMAT_NETCDF4"; break; case NC_FORMAT_NETCDF4_CLASSIC: formatString = "NC_FORMAT_NETCDF4_CLASSIC"; break; } return formatString; } QString NetCDFFilterPrivate::scanAttrs(int ncid, int varid, int attid, QTreeWidgetItem* parentItem) { char name[NC_MAX_NAME + 1]; int nattr, nstart = 0; if (attid == -1) { m_status = nc_inq_varnatts(ncid, varid, &nattr); handleError(m_status, "nc_inq_varnatts"); } else { nstart = attid; nattr = attid + 1; } nc_type type; size_t len; QStringList valueString; for (int i = nstart; i < nattr; i++) { valueString.clear(); m_status = nc_inq_attname(ncid, varid, i, name); handleError(m_status, "nc_inq_attname"); m_status = nc_inq_att(ncid, varid, name, &type, &len); handleError(m_status, "nc_inq_att"); QDEBUG(" attr" << i+1 << "name/type/len =" << name << translateDataType(type) << len); //read attribute switch (type) { case NC_BYTE: { NC_GET_ATT(signed char, schar); break; } case NC_UBYTE: { NC_GET_ATT(unsigned char, uchar); break; } case NC_CHAR: { // not number char *value = (char *)malloc((len+1)*sizeof(char)); m_status = nc_get_att_text(ncid, varid, name, value); handleError(m_status, "nc_get_att_text"); value[len] = 0; valueString << value; free(value); break; } case NC_SHORT: { NC_GET_ATT(short, short); break; } case NC_USHORT: { NC_GET_ATT(unsigned short, ushort); break; } case NC_INT: { NC_GET_ATT(int, int); break; } case NC_UINT: { NC_GET_ATT(unsigned int, uint); break; } case NC_INT64: { NC_GET_ATT(long long, longlong); break; } case NC_UINT64: { NC_GET_ATT(unsigned long long, ulonglong); break; } case NC_FLOAT: { NC_GET_ATT(float, float); break; } case NC_DOUBLE: { NC_GET_ATT(double, double); break; } //TODO: NC_STRING default: valueString << "not supported"; } if (parentItem != nullptr) { QString typeName; if (varid == NC_GLOBAL) typeName = i18n("global attribute"); else { char varName[NC_MAX_NAME + 1]; m_status = nc_inq_varname(ncid, varid, varName); typeName = i18n("%1 attribute", QString(varName)); } QStringList props; props << translateDataType(type) << " (" << QString::number(len) << ")"; QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(name) << typeName << props.join(QString()) << valueString.join(", ")); attrItem->setIcon(0, QIcon::fromTheme("accessories-calculator")); attrItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(attrItem); } } return valueString.join("\n"); } void NetCDFFilterPrivate::scanDims(int ncid, int ndims, QTreeWidgetItem* parentItem) { int ulid; m_status = nc_inq_unlimdim(ncid, &ulid); handleError(m_status, "nc_inq_att"); char name[NC_MAX_NAME + 1]; size_t len; for (int i = 0; i < ndims; i++) { m_status = nc_inq_dim(ncid, i, name, &len); handleError(m_status, "nc_inq_att"); DEBUG(" dim" << i+1 << ": name/len =" << name << len); QStringList props; props<setIcon(0, QIcon::fromTheme("accessories-calculator")); attrItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(attrItem); } } void NetCDFFilterPrivate::scanVars(int ncid, int nvars, QTreeWidgetItem* parentItem) { char name[NC_MAX_NAME + 1]; nc_type type; int ndims, nattrs; int dimids[NC_MAX_VAR_DIMS]; for (int i = 0; i < nvars; i++) { m_status = nc_inq_var(ncid, i, name, &type, &ndims, dimids, &nattrs); handleError(m_status, "nc_inq_att"); QDEBUG(" var" << i+1 << ": name/type=" << name << translateDataType(type)); DEBUG(" ndims/nattr" << ndims << nattrs); QStringList props; // properties column props << translateDataType(type); char dname[NC_MAX_NAME + 1]; size_t dlen; props << "("; if (ndims == 0) props << QString::number(0); for (int j = 0; j < ndims; j++) { m_status = nc_inq_dim(ncid, dimids[j], dname, &dlen); if (j != 0) props << "x"; props << QString::number(dlen); } props << ")"; QStringList rowStrings; rowStrings << QString(name) << i18n("variable") << props.join(QString()); if (ndims == 0) {// get value of zero dim var switch (type) { case NC_BYTE: { NC_SCAN_VAR(signed char, schar); break; } case NC_UBYTE: { NC_SCAN_VAR(unsigned char, uchar); break; } case NC_CHAR: { // not number char data; m_status = nc_get_var_text(ncid, i, &data); handleError(m_status, "nc_get_var_text"); rowStrings << QString(data); break; } case NC_SHORT: { NC_SCAN_VAR(short, short); break; } case NC_USHORT: { NC_SCAN_VAR(unsigned short, ushort); break; } case NC_INT: { NC_SCAN_VAR(int, int); break; } case NC_UINT: { NC_SCAN_VAR(unsigned int, uint); break; } case NC_INT64: { NC_SCAN_VAR(long long, longlong); break; } case NC_UINT64: { NC_SCAN_VAR(unsigned long long, ulonglong); break; } case NC_DOUBLE: { NC_SCAN_VAR(double, double); break; } case NC_FLOAT: { NC_SCAN_VAR(float, float); break; } } } else { rowStrings << QString(); } auto* varItem = new QTreeWidgetItem(rowStrings); varItem->setIcon(0, QIcon::fromTheme("x-office-spreadsheet")); varItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); // highlight item for (int c = 0; c < varItem->columnCount(); c++) { varItem->setBackground(c, QColor(192, 255, 192)); varItem->setForeground(c, Qt::black); } parentItem->addChild(varItem); scanAttrs(ncid, i, -1, varItem); } } #endif /*! parses the content of the file \c fileName and fill the tree using rootItem. */ void NetCDFFilterPrivate::parse(const QString & fileName, QTreeWidgetItem* rootItem) { DEBUG("NetCDFFilterPrivate::parse()"); #ifdef HAVE_NETCDF QByteArray bafileName = fileName.toLatin1(); DEBUG("fileName = " << bafileName.data()); int ncid; m_status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); handleError(m_status, "nc_open"); if (m_status != NC_NOERR) { DEBUG(" Giving up"); return; } int ndims, nvars, nattr, uldid; m_status = nc_inq(ncid, &ndims, &nvars, &nattr, &uldid); handleError(m_status, "nc_inq"); DEBUG(" nattr/ndims/nvars = " << nattr << ' ' << ndims << ' ' << nvars); QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(i18n("Attributes"))); attrItem->setIcon(0,QIcon::fromTheme("folder")); attrItem->setFlags(Qt::ItemIsEnabled); rootItem->addChild(attrItem); scanAttrs(ncid, NC_GLOBAL, -1, attrItem); QTreeWidgetItem *dimItem = new QTreeWidgetItem(QStringList() << QString(i18n("Dimensions"))); dimItem->setIcon(0, QIcon::fromTheme("folder")); dimItem->setFlags(Qt::ItemIsEnabled); rootItem->addChild(dimItem); scanDims(ncid, ndims, dimItem); QTreeWidgetItem *varItem = new QTreeWidgetItem(QStringList() << QString(i18n("Variables"))); varItem->setIcon(0, QIcon::fromTheme("folder")); varItem->setFlags(Qt::ItemIsEnabled); rootItem->addChild(varItem); scanVars(ncid, nvars, varItem); m_status = ncclose(ncid); handleError(m_status, "nc_close"); #else Q_UNUSED(fileName) Q_UNUSED(rootItem) #endif } QString NetCDFFilterPrivate::readAttribute(const QString & fileName, const QString & name, const QString & varName) { #ifdef HAVE_NETCDF int ncid; QByteArray bafileName = fileName.toLatin1(); m_status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); handleError(m_status, "nc_open"); if (m_status != NC_NOERR) { DEBUG(" Giving up"); return QString(); } // get varid int varid; if (varName == "global") varid = NC_GLOBAL; else { QByteArray bavarName = varName.toLatin1(); m_status = nc_inq_varid(ncid, bavarName.data(), &varid); handleError(m_status, "nc_inq_varid"); } // attribute 'name' int attid; QByteArray baName = name.toLatin1(); m_status = nc_inq_attid(ncid, varid, baName.data(), &attid); handleError(m_status, "nc_inq_attid"); QString nameString = scanAttrs(ncid, varid, attid); m_status = ncclose(ncid); handleError(m_status, "nc_close"); return nameString; #else Q_UNUSED(fileName) Q_UNUSED(name) Q_UNUSED(varName) return QString(); #endif } /*! reads the content of the variable in the file \c fileName to a string (for preview) or to the data source. */ QVector NetCDFFilterPrivate::readCurrentVar(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode, int lines) { QVector dataStrings; if (currentVarName.isEmpty()) return dataStrings << (QStringList() << i18n("No variable selected")); DEBUG(" current variable = " << STDSTRING(currentVarName)); #ifdef HAVE_NETCDF int ncid; QByteArray bafileName = fileName.toLatin1(); m_status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); handleError(m_status, "nc_open"); if (m_status != NC_NOERR) { DEBUG(" Giving up"); return dataStrings; } int varid; QByteArray baVarName = currentVarName.toLatin1(); m_status = nc_inq_varid(ncid, baVarName.data(), &varid); handleError(m_status, "nc_inq_varid"); int ndims; nc_type type; m_status = nc_inq_varndims(ncid, varid, &ndims); handleError(m_status, "nc_inq_varndims"); m_status = nc_inq_vartype(ncid, varid, &type); handleError(m_status, "nc_inq_type"); int* dimids = (int *) malloc(ndims * sizeof(int)); m_status = nc_inq_vardimid(ncid, varid, dimids); handleError(m_status, "nc_inq_vardimid"); int actualRows = 0, actualCols = 0; int columnOffset = 0; std::vector dataContainer; switch (ndims) { case 0: { DEBUG(" zero dimensions"); actualRows = actualCols = 1; // only one value QVector columnModes; columnModes.resize(actualCols); switch (type) { case NC_BYTE: case NC_UBYTE: case NC_SHORT: case NC_USHORT: case NC_INT: - columnModes[0] = AbstractColumn::Integer; + columnModes[0] = AbstractColumn::ColumnMode::Integer; break; case NC_UINT: // converted to double (int is too small) case NC_INT64: - columnModes[0] = AbstractColumn::BigInt; + columnModes[0] = AbstractColumn::ColumnMode::BigInt; break; case NC_UINT64: // converted to double (int is too small) case NC_DOUBLE: case NC_FLOAT: - columnModes[0] = AbstractColumn::Numeric; + columnModes[0] = AbstractColumn::ColumnMode::Numeric; break; case NC_CHAR: - columnModes[0] = AbstractColumn::Text; + columnModes[0] = AbstractColumn::ColumnMode::Text; break; //TODO: NC_STRING } //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); DEBUG(" Reading data of type " << STDSTRING(translateDataType(type))); switch (type) { case NC_BYTE: { NC_READ_VAR(signed char, schar, int); break; } case NC_UBYTE: { NC_READ_VAR(unsigned char, uchar, int); break; } case NC_CHAR: { // no number char data; m_status = nc_get_var_text(ncid, varid, &data); handleError(m_status, "nc_get_var_text"); if (dataSource) { QString *sourceData = static_cast*>(dataContainer[0])->data(); sourceData[0] = QString(data); } else { // preview dataStrings << (QStringList() << QString(data)); } break; } case NC_SHORT: { NC_READ_VAR(short, short, int); break; } case NC_USHORT: { NC_READ_VAR(unsigned short, ushort, int); break; } case NC_INT: { NC_READ_VAR(int, int, int); break; } case NC_UINT: { NC_READ_VAR(unsigned int, uint, double); break; } // converted to double (int is too small) case NC_INT64: { NC_READ_VAR(long long, longlong, double); break; } // converted to double (int is too small) case NC_UINT64: { NC_READ_VAR(unsigned long long, ulonglong, double); break; } // converted to double (int is too small) case NC_DOUBLE: { NC_READ_VAR(double, double, double); break; } case NC_FLOAT: { NC_READ_VAR(float, float, double); break; } } break; } case 1: { size_t size; m_status = nc_inq_dimlen(ncid, dimids[0], &size); handleError(m_status, "nc_inq_dimlen"); if (endRow == -1) endRow = (int)size; if (lines == -1) lines = endRow; actualRows = endRow - startRow + 1; actualCols = 1; // only one column DEBUG("start/end row: " << startRow << ' ' << endRow); DEBUG("act rows/cols: " << actualRows << ' ' << actualCols); QVector columnModes; columnModes.resize(actualCols); switch (type) { case NC_BYTE: case NC_UBYTE: case NC_SHORT: case NC_USHORT: case NC_INT: - columnModes[0] = AbstractColumn::Integer; + columnModes[0] = AbstractColumn::ColumnMode::Integer; break; case NC_UINT: // converted to double (int is too small) case NC_INT64: - columnModes[0] = AbstractColumn::BigInt; + columnModes[0] = AbstractColumn::ColumnMode::BigInt; break; case NC_UINT64: // converted to double (int is too small) case NC_DOUBLE: case NC_FLOAT: - columnModes[0] = AbstractColumn::Numeric; + columnModes[0] = AbstractColumn::ColumnMode::Numeric; break; case NC_CHAR: - columnModes[0] = AbstractColumn::Text; + columnModes[0] = AbstractColumn::ColumnMode::Text; break; //TODO: NC_STRING } //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); DEBUG(" Reading data of type " << STDSTRING(translateDataType(type))); switch (type) { case NC_BYTE: { NC_READ_AVAR(signed char, schar, int); break; } case NC_UBYTE: { NC_READ_AVAR(unsigned char, uchar, int); break; } case NC_CHAR: { // not number char* data = new char[(unsigned int)actualRows]; size_t start = (size_t)(startRow - 1), count = (size_t)actualRows; m_status = nc_get_vara_text(ncid, varid, &start, &count, data); handleError(m_status, "nc_get_vara_text"); if (dataSource) { QString *sourceData = static_cast*>(dataContainer[0])->data(); for (int i = 0; i < actualRows; i++) sourceData[i] = QString(data[i]); } else { // preview for (int i = 0; i < qMin(actualRows, lines); i++) dataStrings << (QStringList() << QString(data[i])); } delete[] data; break; } case NC_SHORT: { NC_READ_AVAR(short, short, int); break; } case NC_USHORT: { NC_READ_AVAR(unsigned short, ushort, int); break; } case NC_INT: { NC_READ_AVAR_NATIVE(int); break; } case NC_UINT: { NC_READ_AVAR(unsigned int, uint, double); break; } // converted to double (int is too small) case NC_INT64: { NC_READ_AVAR(long long, longlong, double); break; } // converted to double (int is too small) case NC_UINT64: { NC_READ_AVAR(unsigned long long, ulonglong, double); break; } // converted to double (int is too small) case NC_DOUBLE: { NC_READ_AVAR_NATIVE(double); break; } case NC_FLOAT: { NC_READ_AVAR(float, float, double); break; } //TODO: NC_STRING default: DEBUG(" data type not supported yet"); } break; } case 2: { size_t rows, cols; m_status = nc_inq_dimlen(ncid, dimids[0], &rows); handleError(m_status, "nc_inq_dimlen"); m_status = nc_inq_dimlen(ncid, dimids[1], &cols); handleError(m_status, "nc_inq_dimlen"); if (endRow == -1) endRow = (int)rows; if (lines == -1) lines = endRow; if (endColumn == -1) endColumn = (int)cols; actualRows = endRow-startRow+1; actualCols = endColumn-startColumn+1; DEBUG("dim = " << rows << "x" << cols); DEBUG("startRow/endRow: " << startRow << ' ' << endRow); DEBUG("startColumn/endColumn: " << startColumn << ' ' << endColumn); DEBUG("actual rows/cols: " << actualRows << ' ' << actualCols); DEBUG("lines: " << lines); QVector columnModes; columnModes.resize(actualCols); switch (type) { case NC_BYTE: case NC_UBYTE: case NC_SHORT: case NC_USHORT: case NC_INT: for (int i = 0; i < actualCols; i++) - columnModes[i] = AbstractColumn::Integer; + columnModes[i] = AbstractColumn::ColumnMode::Integer; break; case NC_UINT: // converted to double (int is too small) case NC_INT64: for (int i = 0; i < actualCols; i++) - columnModes[i] = AbstractColumn::BigInt; + columnModes[i] = AbstractColumn::ColumnMode::BigInt; break; case NC_UINT64: // converted to double (int is too small) case NC_DOUBLE: case NC_FLOAT: for (int i = 0; i < actualCols; i++) - columnModes[i] = AbstractColumn::Numeric; + columnModes[i] = AbstractColumn::ColumnMode::Numeric; break; case NC_CHAR: for (int i = 0; i < actualCols; i++) - columnModes[i] = AbstractColumn::Text; + columnModes[i] = AbstractColumn::ColumnMode::Text; break; //TODO: NC_STRING } //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); switch (type) { case NC_BYTE: { NC_READ_VAR2(signed char, schar, int); break; } case NC_UBYTE: { NC_READ_VAR2(unsigned char, uchar, int); break; } case NC_CHAR: { // no number char** data = (char**) malloc(rows * sizeof(char*)); data[0] = (char*)malloc(cols * rows * sizeof(char)); for (unsigned int i = 1; i < rows; i++) data[i] = data[0] + i*cols; m_status = nc_get_var_text(ncid, varid, &data[0][0]); handleError(m_status, "nc_get_var_text"); if (m_status == NC_NOERR) { for (int i = 0; i < qMin((int)rows, lines); i++) { QStringList line; for (size_t j = 0; j < cols; j++) { if (dataSource && dataContainer[0]) static_cast*>(dataContainer[(int)(j-(size_t)startColumn+1)])->operator[](i-startRow+1) = data[i][(int)j]; else line << QString(data[i][j]); } dataStrings << line; emit q->completed(100*i/actualRows); } } free(data[0]); free(data); break; } case NC_SHORT: { NC_READ_VAR2(short, short, int); break; } case NC_USHORT: { NC_READ_VAR2(unsigned short, ushort, int); break; } case NC_INT: { NC_READ_VAR2(int, int, int); break; } case NC_UINT: { NC_READ_VAR2(unsigned int, uint, double); break; } // converted to double (int is too small) case NC_INT64: { NC_READ_VAR2(long long, longlong, double); break; } // converted to double (int is too small) case NC_UINT64: { NC_READ_VAR2(unsigned long long, ulonglong, double); break; } // converted to double (int is too small) case NC_FLOAT: { NC_READ_VAR2(float, float, double); break; } case NC_DOUBLE: { NC_READ_VAR2(double, double, double); break; } //TODO: NC_STRING default: DEBUG(" data type not supported yet"); } break; } default: dataStrings << (QStringList() << i18n("%1 dimensional data of type %2 not supported yet", ndims, translateDataType(type))); QDEBUG(dataStrings); } free(dimids); m_status = ncclose(ncid); handleError(m_status, "nc_close"); if (dataSource) dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), mode); #else Q_UNUSED(fileName) Q_UNUSED(dataSource) Q_UNUSED(mode) Q_UNUSED(lines) #endif return dataStrings; } /*! reads the content of the current selected variable from file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ QVector NetCDFFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode) { QVector dataStrings; if (currentVarName.isEmpty()) { DEBUG(" No variable selected"); return dataStrings; } return readCurrentVar(fileName, dataSource, mode); } /*! writes the content of \c dataSource to the file \c fileName. */ void NetCDFFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: write NetCDF files not implemented yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void NetCDFFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("netcdfFilter"); writer->writeEndElement(); } /*! Loads from XML. */ bool NetCDFFilter::load(XmlStreamReader* reader) { Q_UNUSED(reader); // KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); // QXmlStreamAttributes attribs = reader->attributes(); return true; } diff --git a/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp b/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp index a21670622..40a9d4e6f 100644 --- a/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp +++ b/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp @@ -1,386 +1,386 @@ /*************************************************************************** File : NgspiceRawAsciiFilter.cpp Project : LabPlot Description : Ngspice RAW ASCII filter -------------------------------------------------------------------- Copyright : (C) 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 * * * ***************************************************************************/ #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/NgspiceRawAsciiFilter.h" #include "backend/datasources/filters/NgspiceRawAsciiFilterPrivate.h" #include "backend/lib/trace.h" #include /*! \class NgspiceRawAsciiFilter \brief Import of data stored in Ngspice's raw formant, ASCCI version of it. \ingroup datasources */ NgspiceRawAsciiFilter::NgspiceRawAsciiFilter() : AbstractFileFilter(NgspiceRawAscii), d(new NgspiceRawAsciiFilterPrivate(this)) {} NgspiceRawAsciiFilter::~NgspiceRawAsciiFilter() = default; bool NgspiceRawAsciiFilter::isNgspiceAsciiFile(const QString& fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { DEBUG("Failed to open the file " << STDSTRING(fileName)); return false; } QString line = file.readLine(); if (!line.startsWith(QLatin1String("Title:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Date:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Plotname:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Flags:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("No. Variables:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("No. Points:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Variables:"))) return false; return true; } QString NgspiceRawAsciiFilter::fileInfoString(const QString& fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return QString(); QString info; while (!file.atEnd()) { QString line = file.readLine(); if (line.simplified() == QLatin1String("Values:")) break; if (!info.isEmpty()) info += QLatin1String("
"); info += line; } return info; } /*! reads the content of the file \c fileName. */ void NgspiceRawAsciiFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } QVector NgspiceRawAsciiFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void NgspiceRawAsciiFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); } /*! loads the predefined filter settings for \c filterName */ void NgspiceRawAsciiFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void NgspiceRawAsciiFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } void NgspiceRawAsciiFilter::setStartRow(const int r) { d->startRow = r; } int NgspiceRawAsciiFilter::startRow() const { return d->startRow; } void NgspiceRawAsciiFilter::setEndRow(const int r) { d->endRow = r; } int NgspiceRawAsciiFilter::endRow() const { return d->endRow; } QStringList NgspiceRawAsciiFilter::vectorNames() const { return d->vectorNames; } QVector NgspiceRawAsciiFilter::columnModes() { return d->columnModes; } //##################################################################### //################### Private implementation ########################## //##################################################################### NgspiceRawAsciiFilterPrivate::NgspiceRawAsciiFilterPrivate(NgspiceRawAsciiFilter* owner) : q(owner) { } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void NgspiceRawAsciiFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { DEBUG("NgspiceRawAsciiFilterPrivate::readDataFromFile(): fileName = \'" << STDSTRING(fileName) << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode)); QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { DEBUG("Failed to open the file " << STDSTRING(fileName)); return; } //skip the first three lines in the header file.readLine(); //"Title" file.readLine(); //"Date" file.readLine(); //"Plotname" //evaluate the "Flags" line to check whether we have complex numbers QString line = file.readLine(); bool hasComplexValues = line.endsWith(QLatin1String("complex\n")); //number of variables line = file.readLine(); const int vars = line.rightRef(line.length() - 15).toInt(); //remove the "No. Variables: " sub-string //number of points line = file.readLine(); const int points = line.rightRef(line.length() - 12).toInt(); //remove the "No. Points: " sub-string //add names of the variables vectorNames.clear(); columnModes.clear(); file.readLine(); for (int i = 0; i < vars; ++i) { line = file.readLine(); QStringList tokens = line.split('\t'); //skip lines that don't contain the proper number of tokens (wrong format, corrupted file) if (tokens.size() < 4) continue; QString name = tokens.at(2) + QLatin1String(", ") + tokens.at(3).simplified(); if (hasComplexValues) { vectorNames << name + QLatin1String(" REAL"); vectorNames << name + QLatin1String(" IMAGINARY"); - columnModes << AbstractColumn::Numeric; - columnModes << AbstractColumn::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; } else { vectorNames << name; - columnModes << AbstractColumn::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; } } file.readLine(); //skip the line with "Values" //prepare the data container const int actualEndRow = (endRow == -1 || endRow > points) ? points : endRow; const int actualRows = actualEndRow - startRow + 1; const int actualCols = hasComplexValues ? vars*2 : vars; const int columnOffset = dataSource->prepareImport(m_dataContainer, importMode, actualRows, actualCols, vectorNames, columnModes); //skip data lines, if required DEBUG(" Skipping " << startRow - 1 << " lines"); for (int i = 0; i < startRow - 1; ++i) { for (int j = 0; j < vars; ++j) file.readLine(); file.readLine(); //skip the empty line after each value block } //read the data points QStringList lineString; int currentRow = 0; // indexes the position in the vector(column) QLocale locale(QLocale::C); bool isNumber(false); for (int i = 0; i < actualRows; ++i) { lineString.clear(); for (int j = 0; j < vars; ++j) { line = file.readLine(); QStringList tokens = line.split(QLatin1Char('\t')); //skip lines that don't contain the proper number of tokens (wrong format, corrupted file) if (tokens.size() < 2) continue; QString valueString = tokens.at(1).simplified(); //string containing the value(s) if (hasComplexValues) { QStringList realImgTokens = valueString.split(QLatin1Char(',')); if (realImgTokens.size() == 2) { //sanity check to make sure we really have both parts //real part double value = locale.toDouble(realImgTokens.at(0), &isNumber); static_cast*>(m_dataContainer[2*j])->operator[](currentRow) = (isNumber ? value : NAN); //imaginary part value = locale.toDouble(realImgTokens.at(1), &isNumber); static_cast*>(m_dataContainer[2*j+1])->operator[](currentRow) = (isNumber ? value : NAN); } } else { const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[j])->operator[](currentRow) = (isNumber ? value : NAN); } } file.readLine(); //skip the empty line after each value block currentRow++; emit q->completed(100 * currentRow/actualRows); } dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); } /*! * generates the preview for the file \c fileName reading the provided number of \c lines. */ QVector NgspiceRawAsciiFilterPrivate::preview(const QString& fileName, int lines) { QVector dataStrings; QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { DEBUG("Failed to open the file " << STDSTRING(fileName)); return dataStrings; } //skip the first three lines in the header file.readLine(); //"Title" file.readLine(); //"Date" file.readLine(); //"Plotname" //evaluate the "Flags" line to check whether we have complex numbers QString line = file.readLine(); bool hasComplexValues = line.endsWith(QLatin1String("complex\n")); //number of variables line = file.readLine(); const int vars = line.rightRef(line.length() - 15).toInt(); //remove the "No. Variables: " sub-string //number of points line = file.readLine(); const int points = line.rightRef(line.length() - 12).toInt(); //remove the "No. Points: " sub-string //add names of the variables vectorNames.clear(); columnModes.clear(); file.readLine(); for (int i = 0; i < vars; ++i) { line = file.readLine(); QStringList tokens = line.split('\t'); //skip lines that don't contain the proper number of tokens (wrong format, corrupted file) if (tokens.size() < 4) continue; QString name = tokens.at(2) + QLatin1String(", ") + tokens.at(3).simplified(); if (hasComplexValues) { vectorNames << name + QLatin1String(" REAL"); vectorNames << name + QLatin1String(" IMAGINARY"); - columnModes << AbstractColumn::Numeric; - columnModes << AbstractColumn::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; } else { vectorNames << name; - columnModes << AbstractColumn::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; } } file.readLine(); //skip the line with "Values" //read the data points QStringList lineString; for (int i = 0; i < qMin(lines, points); ++i) { lineString.clear(); for (int j = 0; j < vars; ++j) { line = file.readLine(); QStringList tokens = line.split(QLatin1Char('\t')); //skip lines that don't contain the proper number of tokens (wrong format, corrupted file) if (tokens.size() < 2) continue; QString value = tokens.at(1).simplified(); //string containing the value(s) if (hasComplexValues) { QStringList realImgTokens = value.split(QLatin1Char(',')); if (realImgTokens.size() == 2) { //sanity check to make sure we really have both parts lineString << realImgTokens.at(0); //real part lineString << realImgTokens.at(1); //imaginary part } } else lineString << value; } dataStrings << lineString; file.readLine(); //skip the empty line after each value block } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void NgspiceRawAsciiFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: not implemented yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void NgspiceRawAsciiFilter::save(QXmlStreamWriter* writer) const { Q_UNUSED(writer); } /*! Loads from XML. */ bool NgspiceRawAsciiFilter::load(XmlStreamReader* reader) { Q_UNUSED(reader); return true; } diff --git a/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp b/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp index 28fa1b1cb..45bb03123 100644 --- a/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp +++ b/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp @@ -1,358 +1,358 @@ /*************************************************************************** File : NgspiceRawBinaryFilter.cpp Project : LabPlot Description : Ngspice RAW Binary filter -------------------------------------------------------------------- Copyright : (C) 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 * * * ***************************************************************************/ #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/NgspiceRawBinaryFilter.h" #include "backend/datasources/filters/NgspiceRawBinaryFilterPrivate.h" #include "backend/lib/trace.h" #include /*! \class NgspiceRawBinaryFilter \brief Import of data stored in Ngspice's raw formant, ASCCI version of it. \ingroup datasources */ NgspiceRawBinaryFilter::NgspiceRawBinaryFilter() : AbstractFileFilter(NgspiceRawBinary), d(new NgspiceRawBinaryFilterPrivate(this)) {} NgspiceRawBinaryFilter::~NgspiceRawBinaryFilter() = default; bool NgspiceRawBinaryFilter::isNgspiceBinaryFile(const QString& fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { DEBUG("Failed to open the file " << STDSTRING(fileName)); return false; } QString line = file.readLine(); if (!line.startsWith(QLatin1String("Title:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Date:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Plotname:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Flags:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("No. Variables:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("No. Points:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Variables:"))) return false; return true; } QString NgspiceRawBinaryFilter::fileInfoString(const QString& fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return QString(); QString info; while (!file.atEnd()) { QString line = file.readLine(); if (line.simplified() == QLatin1String("Binary:")) break; if (!info.isEmpty()) info += QLatin1String("
"); info += line; } return info; } /*! reads the content of the file \c fileName. */ void NgspiceRawBinaryFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } QVector NgspiceRawBinaryFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void NgspiceRawBinaryFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); } /*! loads the predefined filter settings for \c filterName */ void NgspiceRawBinaryFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void NgspiceRawBinaryFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } void NgspiceRawBinaryFilter::setStartRow(const int r) { d->startRow = r; } int NgspiceRawBinaryFilter::startRow() const { return d->startRow; } void NgspiceRawBinaryFilter::setEndRow(const int r) { d->endRow = r; } int NgspiceRawBinaryFilter::endRow() const { return d->endRow; } QStringList NgspiceRawBinaryFilter::vectorNames() const { return d->vectorNames; } QVector NgspiceRawBinaryFilter::columnModes() { return d->columnModes; } //##################################################################### //################### Private implementation ########################## //##################################################################### NgspiceRawBinaryFilterPrivate::NgspiceRawBinaryFilterPrivate(NgspiceRawBinaryFilter* owner) : q(owner) { } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void NgspiceRawBinaryFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { DEBUG("NgspiceRawBinaryFilterPrivate::readDataFromFile(): fileName = \'" << STDSTRING(fileName) << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode)); QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { DEBUG("Failed to open the file " << STDSTRING(fileName)); return; } //skip the first three lines in the header file.readLine(); //"Title" file.readLine(); //"Date" file.readLine(); //"Plotname" //evaluate the "Flags" line to check whether we have complex numbers QString line = file.readLine(); bool hasComplexValues = line.endsWith(QLatin1String("complex\n")); //number of variables line = file.readLine(); const int vars = line.rightRef(line.length() - 15).toInt(); //remove the "No. Variables: " sub-string //number of points line = file.readLine(); const int points = line.rightRef(line.length() - 12).toInt(); //remove the "No. Points: " sub-string //add names of the variables vectorNames.clear(); columnModes.clear(); file.readLine(); for (int i = 0; i points) ? points : endRow; const int actualRows = actualEndRow - startRow + 1; const int actualCols = hasComplexValues ? 2 * vars : vars; const int columnOffset = dataSource->prepareImport(m_dataContainer, importMode, actualRows, actualCols, vectorNames, columnModes); //skip data lines, if required const int skip = hasComplexValues ? 2 * vars * (startRow - 1) : vars * (startRow - 1); if (skip > 0) { DEBUG(" Skipping " << startRow - 1 << " lines"); file.read(BYTE_SIZE * skip); } //read the data points int currentRow = 0; // indexes the position in the vector(column) for (int i = 0; i < actualRows; ++i) { for (int j = 0; j < vars; ++j) { double value; QDataStream s(file.read(BYTE_SIZE)); s.setByteOrder(QDataStream::LittleEndian); s >> value; if (hasComplexValues) { //real part static_cast*>(m_dataContainer[2*j])->operator[](currentRow) = value; //imaginary part QDataStream sim(file.read(BYTE_SIZE)); sim.setByteOrder(QDataStream::LittleEndian); sim >> value; static_cast*>(m_dataContainer[2*j+1])->operator[](currentRow) = value; } else static_cast*>(m_dataContainer[j])->operator[](currentRow) = value; } currentRow++; emit q->completed(100 * currentRow/actualRows); } dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); } /*! * generates the preview for the file \c fileName reading the provided number of \c lines. */ QVector NgspiceRawBinaryFilterPrivate::preview(const QString& fileName, int lines) { DEBUG("NgspiceRawBinaryFilterPrivate::preview()"); QVector dataStrings; QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { DEBUG("Failed to open the file " << STDSTRING(fileName)); return dataStrings; } //skip the first three lines in the header file.readLine(); //"Title" file.readLine(); //"Date" file.readLine(); //"Plotname" //evaluate the "Flags" line to check whether we have complex numbers QString line = file.readLine(); bool hasComplexValues = line.endsWith(QLatin1String("complex\n")); //number of variables line = file.readLine(); const int vars = line.rightRef(line.length() - 15).toInt(); //remove the "No. Variables: " sub-string DEBUG(" vars = " << vars); //number of points line = file.readLine(); const int points = line.rightRef(line.length() - 12).toInt(); //remove the "No. Points: " sub-string DEBUG(" points = " << points); //add names of the variables vectorNames.clear(); columnModes.clear(); file.readLine(); for (int i = 0; i < vars; ++i) { line = file.readLine(); QStringList tokens = line.split('\t'); QString name = tokens.at(2) + QLatin1String(", ") + tokens.at(3).simplified(); if (hasComplexValues) { vectorNames << name + QLatin1String(" REAL"); vectorNames << name + QLatin1String(" IMAGINARY"); - columnModes << AbstractColumn::Numeric; - columnModes << AbstractColumn::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; } else { vectorNames << name; - columnModes << AbstractColumn::Numeric; + columnModes << AbstractColumn::ColumnMode::Numeric; } } file.readLine(); //skip the line with "Binary" //read the binary data file.setTextModeEnabled(false); QStringList lineString; for (int i = 0; i < qMin(lines, points); ++i) { lineString.clear(); for (int j = 0; j < vars; ++j) { double v; QDataStream s(file.read(BYTE_SIZE)); s.setByteOrder(QDataStream::LittleEndian); s >> v; lineString << QString::number(v, 'e', 15); //real part if (hasComplexValues) { QDataStream sim(file.read(BYTE_SIZE)); sim.setByteOrder(QDataStream::LittleEndian); sim >> v; lineString << QString::number(v, 'e', 15); //imaginary part } } dataStrings << lineString; } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void NgspiceRawBinaryFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void NgspiceRawBinaryFilter::save(QXmlStreamWriter* writer) const { Q_UNUSED(writer); } /*! Loads from XML. */ bool NgspiceRawBinaryFilter::load(XmlStreamReader* reader) { Q_UNUSED(reader); return true; } diff --git a/src/backend/datasources/filters/ROOTFilter.cpp b/src/backend/datasources/filters/ROOTFilter.cpp index b22c3e0ec..fd3331896 100644 --- a/src/backend/datasources/filters/ROOTFilter.cpp +++ b/src/backend/datasources/filters/ROOTFilter.cpp @@ -1,1501 +1,1501 @@ /*************************************************************************** File : ROOTFilter.cpp Project : LabPlot Description : ROOT(CERN) I/O-filter -------------------------------------------------------------------- Copyright : (C) 2018 by Christoph Roick (chrisito@gmx.de) Copyright : (C) 2018 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 "backend/datasources/filters/ROOTFilter.h" #include "backend/datasources/filters/ROOTFilterPrivate.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/column/Column.h" #include #include #include #include #include #ifdef HAVE_ZIP #include #include #endif #include #include #include #include #include #include ROOTFilter::ROOTFilter():AbstractFileFilter(ROOT), d(new ROOTFilterPrivate) {} ROOTFilter::~ROOTFilter() = default; void ROOTFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } void ROOTFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); } void ROOTFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } void ROOTFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } void ROOTFilter::setCurrentObject(const QString& object) { d->currentObject = object; } const QString ROOTFilter::currentObject() const { return d->currentObject; } ROOTFilter::Directory ROOTFilter::listHistograms(const QString& fileName) const { return d->listHistograms(fileName); } ROOTFilter::Directory ROOTFilter::listTrees(const QString& fileName) const { return d->listTrees(fileName); } QVector ROOTFilter::listLeaves(const QString& fileName, qint64 pos) const { return d->listLeaves(fileName, pos); } QVector ROOTFilter::previewCurrentObject(const QString& fileName, int first, int last) const { return d->previewCurrentObject(fileName, first, last); } int ROOTFilter::rowsInCurrentObject(const QString& fileName) const { return d->rowsInCurrentObject(fileName); } void ROOTFilter::setStartRow(const int s) { d->startRow = s; } int ROOTFilter::startRow() const { return d->startRow; } void ROOTFilter::setEndRow(const int e) { d->endRow = e; } int ROOTFilter::endRow() const { return d->endRow; } void ROOTFilter::setColumns(const QVector& columns) { d->columns = columns; } QVector ROOTFilter::columns() const { return d->columns; } void ROOTFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("rootFilter"); writer->writeAttribute("object", d->currentObject); writer->writeAttribute("startRow", QString::number(d->startRow)); writer->writeAttribute("endRow", QString::number(d->endRow)); for (const auto & c : d->columns) { writer->writeStartElement("column"); for (const auto & s : c) writer->writeTextElement("id", s); writer->writeEndElement(); } writer->writeEndElement(); } bool ROOTFilter::load(XmlStreamReader* reader) { QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); // read attributes d->currentObject = attribs.value("object").toString(); if (d->currentObject.isEmpty()) reader->raiseWarning(attributeWarning.arg("object")); QString str = attribs.value("startRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("startRow")); else d->startRow = str.toInt(); str = attribs.value("endRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("endRow")); else d->endRow = str.toInt(); d->columns.clear(); while (reader->readNextStartElement()) { if (reader->name() == "column") { QStringList c; while (reader->readNextStartElement()) { if (reader->name() == "id") c << reader->readElementText(); else reader->skipCurrentElement(); } if (!c.empty()) d->columns << c; } else reader->skipCurrentElement(); } if (d->columns.empty()) reader->raiseWarning(i18n("No column available")); return true; } /**************** ROOTFilterPrivate implementation *******************/ ROOTFilterPrivate::ROOTFilterPrivate() = default; ROOTFilterPrivate::FileType ROOTFilterPrivate::currentObjectPosition(const QString& fileName, long int& pos) { QStringList typeobject = currentObject.split(':'); if (typeobject.size() < 2) return Invalid; FileType type; if (typeobject.first() == QStringLiteral("Hist")) type = Hist; else if (typeobject.first() == QStringLiteral("Tree")) type = Tree; else return Invalid; typeobject.removeFirst(); QStringList path = typeobject.join(':').split('/'); ROOTFilter::Directory dir = type == Hist ? listHistograms(fileName) : listTrees(fileName); const ROOTFilter::Directory* node = &dir; while (path.size() > 1) { bool next = false; for (const auto& child : node->children) { if (child.name == path.first()) { node = &child; path.pop_front(); next = true; break; } } if (!next) return Invalid; } for (const auto& child : node->content) { if (child.first == path.first()) { pos = child.second; break; } } return type; } void ROOTFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { DEBUG("ROOTFilterPrivate::readDataFromFile()"); long int pos = 0; auto type = currentObjectPosition(fileName, pos); if (pos == 0) return; if (type == Hist) { auto bins = readHistogram(pos); const int nbins = static_cast(bins.size()); // skip underflow and overflow bins by default int first = qMax(qAbs(startRow), 0); int last = endRow < 0 ? nbins - 1 : qMax(first - 1, qMin(endRow, nbins - 1)); QStringList headers; for (const auto& l : columns) { headers << l.last(); } std::vector dataContainer; const int columnOffset = dataSource->prepareImport(dataContainer, importMode, last - first + 1, columns.size(), - headers, QVector(columns.size(), AbstractColumn::Numeric)); + headers, QVector(columns.size(), AbstractColumn::ColumnMode::Numeric)); // read data DEBUG(" reading " << first - last + 1 << " lines"); int c = 0; Spreadsheet* spreadsheet = dynamic_cast(dataSource); for (const auto& l : columns) { QVector& container = *static_cast*>(dataContainer[c]); if (l.first() == QStringLiteral("center")) { if (spreadsheet) spreadsheet->column(columnOffset + c)->setPlotDesignation(AbstractColumn::PlotDesignation::X); for (int i = first; i <= last; ++i) container[i - first] = (i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge) : i == 0 ? bins.front().lowedge // -infinity : -bins.front().lowedge; // +infinity } else if (l.first() == QStringLiteral("low")) { if (spreadsheet) spreadsheet->column(columnOffset + c)->setPlotDesignation(AbstractColumn::PlotDesignation::X); for (int i = first; i <= last; ++i) container[i - first] = bins[i].lowedge; } else if (l.first() == QStringLiteral("content")) { if (spreadsheet) spreadsheet->column(columnOffset + c)->setPlotDesignation(AbstractColumn::PlotDesignation::Y); for (int i = first; i <= last; ++i) container[i - first] = bins[i].content; } else if (l.first() == QStringLiteral("error")) { if (spreadsheet) spreadsheet->column(columnOffset + c)->setPlotDesignation(AbstractColumn::PlotDesignation::YError); for (int i = first; i <= last; ++i) container[i - first] = std::sqrt(bins[i].sumw2); } ++c; } dataSource->finalizeImport(columnOffset, 0, columns.size() - 1, QString(), importMode); } else if (type == Tree) { const int nentries = static_cast(currentROOTData->treeEntries(pos)); int first = qMax(qAbs(startRow), 0); int last = qMax(first - 1, qMin(endRow, nentries - 1)); QStringList headers; for (const auto& l : columns) { QString lastelement = l.back(); bool isArray = false; if (lastelement.at(0) == '[' && lastelement.at(lastelement.size() - 1) == ']') { lastelement.midRef(1, lastelement.length() - 2).toUInt(&isArray); } if (!isArray || l.count() == 2) headers << l.join(isArray ? QString() : QString(':')); else headers << l.first() + QChar(':') + l.at(1) + l.back(); } std::vector dataContainer; const int columnOffset = dataSource->prepareImport(dataContainer, importMode, last - first + 1, columns.size(), - headers, QVector(columns.size(), AbstractColumn::Numeric)); + headers, QVector(columns.size(), AbstractColumn::ColumnMode::Numeric)); int c = 0; for (const auto& l : columns) { unsigned int element = 0; QString lastelement = l.back(), leaf = l.front(); bool isArray = false; if (lastelement.at(0) == '[' && lastelement.at(lastelement.size() - 1) == ']') { element = lastelement.midRef(1, lastelement.length() - 2).toUInt(&isArray); if (!isArray) element = 0; if (l.count() > 2) leaf = l.at(1); } else if (l.count() > 1) leaf = l.at(1); QVector& container = *static_cast*>(dataContainer[c++]); auto data = readTree(pos, l.first(), leaf, (int)element, last); for (int i = first; i <= last; ++i) container[i - first] = data[i]; } dataSource->finalizeImport(columnOffset, 0, columns.size() - 1, QString(), importMode); } } void ROOTFilterPrivate::write(const QString& fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); } ROOTFilter::Directory ROOTFilterPrivate::listContent(const std::map& dataContent, std::string (ROOTData::*nameFunc)(long int)) { ROOTFilter::Directory dirs; QHash::type::value_type*, ROOTFilter::Directory*> filledDirs; for (const auto& path : dataContent) { if (!path.second.content.empty()) { QStack addpath; auto pos = &path; ROOTFilter::Directory* currentdir = &dirs; while (true) { auto it = filledDirs.find(pos); if (it != filledDirs.end()) { currentdir = it.value(); break; } auto jt = dataContent.find(pos->second.parent); if (jt != dataContent.end()) { addpath.push(pos); pos = &(*jt); } else break; } while (!addpath.empty()) { auto pos = addpath.pop(); ROOTFilter::Directory dir; dir.name = QString::fromStdString(pos->second.name); currentdir->children << dir; currentdir = ¤tdir->children.last(); filledDirs[pos] = currentdir; } for (auto hist : path.second.content) { auto name = ((*currentROOTData).*nameFunc)(hist); if (!name.empty()) currentdir->content << qMakePair(QString::fromStdString(name), hist); } } } return dirs; } ROOTFilter::Directory ROOTFilterPrivate::listHistograms(const QString& fileName) { if (setFile(fileName)) return listContent(currentROOTData->listHistograms(), &ROOTData::histogramName); else return ROOTFilter::Directory{}; } ROOTFilter::Directory ROOTFilterPrivate::listTrees(const QString& fileName) { if (setFile(fileName)) return listContent(currentROOTData->listTrees(), &ROOTData::treeName); else return ROOTFilter::Directory{}; } QVector ROOTFilterPrivate::listLeaves(const QString& fileName, quint64 pos) { QVector leafList; if (setFile(fileName)) { for (const auto& leaf : currentROOTData->listLeaves(pos)) { leafList << QStringList(QString::fromStdString(leaf.branch)); if (leaf.branch != leaf.leaf) leafList.last() << QString::fromStdString(leaf.leaf); if (leaf.elements > 1) leafList.last() << QString("[%1]").arg(leaf.elements); } } return leafList; } QVector ROOTFilterPrivate::previewCurrentObject(const QString& fileName, int first, int last) { DEBUG("ROOTFilterPrivate::previewCurrentObject()"); long int pos = 0; auto type = currentObjectPosition(fileName, pos); if (pos == 0) return QVector(1, QStringList()); if (type == Hist) { auto bins = readHistogram(pos); const int nbins = static_cast(bins.size()); last = qMin(nbins - 1, last); QVector preview(qMax(last - first + 2, 1)); DEBUG(" reading " << preview.size() - 1 << " lines"); // set headers for (const auto& l : columns) { preview.last() << l.last(); } // read data for (const auto& l : columns) { if (l.first() == QStringLiteral("center")) { for (int i = first; i <= last; ++i) preview[i - first] << QString::number( (i > 0 && i < nbins - 1) ? 0.5 * (bins[i].lowedge + bins[i + 1].lowedge) : i == 0 ? bins.front().lowedge // -infinity : -bins.front().lowedge); // +infinity } else if (l.first() == QStringLiteral("low")) { for (int i = first; i <= last; ++i) preview[i - first] << QString::number(bins[i].lowedge); } else if (l.first() == QStringLiteral("content")) { for (int i = first; i <= last; ++i) preview[i - first] << QString::number(bins[i].content); } else if (l.first() == QStringLiteral("error")) { for (int i = first; i <= last; ++i) preview[i - first] << QString::number(std::sqrt(bins[i].sumw2)); } } return preview; } else if (type == Tree) { last = qMin(last, currentROOTData->treeEntries(pos) - 1); QVector preview(qMax(last - first + 2, 1)); DEBUG(" reading " << preview.size() - 1 << " lines"); // read data leaf by leaf and set headers for (const auto& l : columns) { unsigned int element = 0; QString lastelement = l.back(), leaf = l.front(); bool isArray = false; if (lastelement.at(0) == '[' && lastelement.at(lastelement.size() - 1) == ']') { element = lastelement.midRef(1, lastelement.length() - 2).toUInt(&isArray); if (!isArray) element = 0; if (l.count() > 2) leaf = l.at(1); } else if (l.count() > 1) leaf = l.at(1); auto data = readTree(pos, l.first(), leaf, (int)element, last); for (int i = first; i <= last; ++i) preview[i - first] << QString::number(data[i]); if (!isArray || l.count() == 2) preview.last() << l.join(isArray ? QString() : QString(':')); else preview.last() << l.first() + QChar(':') + l.at(1) + l.back(); } return preview; } else return QVector(1, QStringList()); } int ROOTFilterPrivate::rowsInCurrentObject(const QString& fileName) { long int pos = 0; auto type = currentObjectPosition(fileName, pos); if (pos == 0) return 0; switch (type) { case Hist: return currentROOTData->histogramBins(pos); case Tree: return currentROOTData->treeEntries(pos); case Invalid: default: return 0; } } bool ROOTFilterPrivate::setFile(const QString& fileName) { QFileInfo file(fileName); if (!file.exists()) { currentObject.clear(); columns.clear(); currentROOTData.reset(); return false; } QDateTime modified = file.lastModified(); qint64 size = file.size(); if (!currentROOTData || fileName != currentFile.name || modified != currentFile.modified || size != currentFile.size) { currentFile.name = fileName; currentFile.modified = modified; currentFile.size = size; currentROOTData.reset(new ROOTData(fileName.toStdString())); } return true; } std::vector ROOTFilterPrivate::readHistogram(quint64 pos) { return currentROOTData->readHistogram(pos); } std::vector ROOTFilterPrivate::readTree(quint64 pos, const QString& branchName, const QString& leafName, int element, int last) { return currentROOTData->listEntries(pos, branchName.toStdString(), leafName.toStdString(), element, last + 1); } /******************** ROOTData implementation ************************/ namespace ROOTDataHelpers { /// Read value from stream template T read(std::ifstream& is) { union { T val; char buf[sizeof(T)]; } r; for (size_t i = 0; i < sizeof(T); ++i) is.get(r.buf[sizeof(T) - i - 1]); return r.val; } /// Read value from buffer template T read(char*& s) { union { T val; char buf[sizeof(T)]; } r; for (size_t i = 0; i < sizeof(T); ++i) r.buf[sizeof(T) - i - 1] = *(s++); return r.val; } /// Read value from buffer and cast to U template U readcast(char*& s) { return static_cast(read(s)); } /// Get version of ROOT object, obtain number of bytes in object short Version(char*& buffer, size_t& count) { // root/io/io/src/TBufferFile.cxx -> ReadVersion count = read(buffer); short version = (count & 0x40000000) ? read(buffer) : read(buffer -= 4); count = (count & 0x40000000) ? (count & ~0x40000000) - 2 : 2; return version; } /// Get version of ROOT object short Version(char*& buffer) { size_t c; return Version(buffer, c); } /// Skip ROOT object void Skip(char*& buffer, size_t n) { for (size_t i = 0; i < n; ++i) { size_t count; Version(buffer, count); buffer += count; } } /// Skip TObject header void SkipObject(char*& buffer) { Version(buffer); buffer += 8; } /// Get TString std::string String(char*& buffer) { // root/io/io/src/TBufferFile.cxx -> ReadTString size_t s = *(buffer++); if (s == 0) return std::string(); else { if (s == 0xFF) s = read(buffer); buffer += s; return std::string(buffer - s, buffer); } } /// Get the header of an object in TObjArray std::string readObject(char*& buf, char* const buf0, std::map& tags) { // root/io/io/src/TBufferFile.cxx -> ReadObjectAny std::string clname; unsigned int tag = read(buf); if (tag & 0x40000000) { tag = read(buf); if (tag == 0xFFFFFFFF) { tags[buf - buf0 - 2] = clname = buf; buf += clname.size() + 1; } else { clname = tags[tag & ~0x80000000]; } } return clname; } } using namespace ROOTDataHelpers; ROOTData::ROOTData(const std::string& filename) : filename(filename) { // The file structure is described in root/io/io/src/TFile.cxx std::ifstream is(filename, std::ifstream::binary); std::string root(4, 0); is.read(const_cast(root.data()), 4); if (root != "root") return; int fileVersion = read(is); long int pos = read(is); histdirs.emplace(pos, Directory{}); treedirs.emplace(pos, Directory{}); long int endpos = fileVersion < 1000000 ? read(is) : read(is); is.seekg(33); int compression = read(is); compression = compression > 0 ? compression : 0; while (is.good() && pos < endpos) { is.seekg(pos); int lcdata = read(is); if (lcdata == 0) { break; } if (lcdata < 0) { pos -= lcdata; continue; } short version = read(is); size_t ldata = read(is); is.seekg(4, is.cur); // skip the date size_t lkey = read(is); short cycle = read(is); long int pseek; if (version > 1000) { is.seekg(8, is.cur); pseek = read(is); } else { is.seekg(4, is.cur); pseek = read(is); } std::string cname(read(is), 0); is.read(&cname[0], cname.size()); std::string name(read(is), 0); is.read(&name[0], name.size()); std::string title(read(is), 0); is.read(&title[0], title.size()); ContentType type = Invalid; if (cname.size() == 4 && cname.substr(0, 3) == "TH1") { type = histType(cname[3]); } else if (cname == "TTree") type = Tree; else if (cname.substr(0, 7) == "TNtuple") type = NTuple; else if (cname == "TBasket") type = Basket; else if (cname == "TList" && name == "StreamerInfo") type = Streamer; else if (cname == "TDirectory") { auto it = histdirs.find(pseek); if (it == histdirs.end()) it = histdirs.begin(); histdirs.emplace(pos, Directory{name, it->first}); it = treedirs.find(pseek); if (it == treedirs.end()) it = treedirs.begin(); treedirs.emplace(pos, Directory{name, it->first}); } if (type) { if (type == Basket) is.seekg(19, std::ifstream::cur); // TODO read info instead? KeyBuffer buffer; buffer.type = Invalid; // see root/io/io/src/TKey.cxx for reference int complib = 0; if (compression) { // Default: compression level // ZLIB: 100 + compression level // LZ4: 400 + compression level // do not rely on this, but read the header std::string lib(2, 0); is.read(&lib[0], 2); complib = lib == "ZL" ? 1 : lib == "XZ" ? 2 : lib == "CS" ? 3 : lib == "L4" ? 4 : 0; } if (complib > 0) { # ifdef HAVE_ZIP // see root/core/zip/src/RZip.cxx -> R__unzip const int method = is.get(); size_t chcdata = is.get(); chcdata |= (is.get() << 8); chcdata |= (is.get() << 16); size_t chdata = is.get(); chdata |= (is.get() << 8); chdata |= (is.get() << 16); if (chcdata == lcdata - lkey - 9 && chdata == ldata) { if (complib == 1 && method == Z_DEFLATED) { buffer = KeyBuffer{type, name, title, cycle, lkey, KeyBuffer::zlib, pos + lkey + 9, chcdata, chdata, 0}; } else if (complib == 4 && method == LZ4_versionNumber() / 10000) { buffer = KeyBuffer{type, name, title, cycle, lkey, KeyBuffer::lz4, pos + lkey + 9 + 8, chcdata - 8, chdata, 0}; } } # endif } else { buffer = KeyBuffer{type, name, title, cycle, lkey, KeyBuffer::none, pos + lkey, ldata, ldata, 0}; } switch (buffer.type) { case Basket: basketkeys.emplace(pos, buffer); break; case Tree: case NTuple: { auto it = treedirs.find(pseek); if (it == treedirs.end()) it = treedirs.begin(); bool keyreplaced = false; for (auto & tpos : it->second.content) { auto jt = treekeys.find(tpos); if (jt != treekeys.end() && jt->second.name == buffer.name && jt->second.cycle < buffer.cycle) { // override key with lower cylce number tpos = pos; treekeys.erase(jt); keyreplaced = true; break; } } if (!keyreplaced) it->second.content.push_back(pos); treekeys.emplace(pos, buffer); break; } case Streamer: readStreamerInfo(buffer); break; case Double: case Float: case Int: case Short: case Byte: { auto it = histdirs.find(pseek); if (it == histdirs.end()) it = histdirs.begin(); it->second.content.push_back(pos); histkeys.emplace(pos, buffer); break; } case Invalid: case Long: case Bool: case CString: break; } } pos += lcdata; } // Create default object structures if no StreamerInfo was found. // Obtained by running the following in ROOT with a file passed as an argument: // // _file0->GetStreamerInfoList()->Print() // // auto l = (TStreamerInfo*)_file0->GetStreamerInfoList()->At(ENTRYNUMBER); // l->Print(); // for (int i = 0; i < l->GetNelement(); ++i) { // auto e = l->GetElement(i); // e->Print(); // cout << e->GetFullName() << " " << e->GetTypeName() << " " << e->GetSize() << endl; // } static const StreamerInfo dummyobject{"Object", 0, std::string(), false, false}; if (!treekeys.empty()) { if (!streamerInfo.count("TTree")) { streamerInfo["TTree"] = {dummyobject, dummyobject, dummyobject, dummyobject, StreamerInfo{"fEntries", 8, std::string(), false, false}, StreamerInfo{std::string(), 5 * 8 + 4 * 4, std::string(), false, false}, StreamerInfo{"fNClusterRange", 4, std::string(), true, false}, StreamerInfo{std::string(), 6 * 8, std::string(), false, false}, StreamerInfo{"fNClusterRangeEnd", 8, "fNClusterRange", false, true}, StreamerInfo{"fNClusterSize", 8, "fNClusterRange", false, true}, StreamerInfo{"fBranches", 0, std::string(), false, false} }; } if (!streamerInfo.count("TBranch")) { streamerInfo["TBranch"] = {StreamerInfo{"TNamed", 0, std::string(), false, false}, dummyobject, StreamerInfo{std::string(), 3 * 4, std::string(), false, false}, StreamerInfo{"fWriteBasket", 4, std::string(), false, false}, StreamerInfo{std::string(), 8 + 4, std::string(), false, false}, StreamerInfo{"fMaxBaskets", 4, std::string(), true, false}, StreamerInfo{std::string(), 4 + 4 * 8, std::string(), false, false}, StreamerInfo{"fBranches", 0, std::string(), false, false}, StreamerInfo{"fLeaves", 0, std::string(), false, false}, StreamerInfo{"fBaskets", 0, std::string(), false, false}, StreamerInfo{"fBasketBytes", 4, "fMaxBaskets", false, true}, StreamerInfo{"fBasketEntry", 8, "fMaxBaskets", false, true}, StreamerInfo{"fBasketSeek", 8, "fMaxBaskets", false, true} }; } } if (!histkeys.empty()) { if (!streamerInfo.count("TH1")) { streamerInfo["TH1"] = {dummyobject, dummyobject, dummyobject, dummyobject, StreamerInfo{"fNcells", 4, std::string(), false, false}, StreamerInfo{"fXaxis", 0, std::string(), false, false}, StreamerInfo{"fYaxis", 0, std::string(), false, false}, StreamerInfo{"fZaxis", 0, std::string(), false, false}, StreamerInfo{std::string(), 2 * 2 + 8 * 8, std::string(), false, false}, dummyobject, StreamerInfo{"fSumw2", 0, std::string(), false, false}}; } if (!streamerInfo.count("TAxis")) { streamerInfo["TAxis"] = {dummyobject, dummyobject, StreamerInfo{"fNbins", 4, std::string(), false, false}, StreamerInfo{"fXmin", 8, std::string(), false, false}, StreamerInfo{"fXmax", 8, std::string(), false, false}, StreamerInfo{"fXbins", 0, std::string(), false, false}}; } } for (auto& tree : treekeys) readNEntries(tree.second); for (auto& hist : histkeys) readNBins(hist.second); } void ROOTData::readNBins(ROOTData::KeyBuffer& kbuffer) { std::string buffer = data(kbuffer); if (!buffer.empty()) { char* buf = &buffer[0]; std::map counts; Version(buf); // TH1(D/F/I/S/C) Version(buf); // TH1 advanceTo(buf, streamerInfo.find("TH1")->second, std::string(), "fNcells", counts); kbuffer.nrows = read(buf); // fNcells } } std::string ROOTData::histogramName(long int pos) { auto it = histkeys.find(pos); if (it != histkeys.end()) return it->second.name + ';' + std::to_string(it->second.cycle); return std::string(); } int ROOTData::histogramBins(long int pos) { auto it = histkeys.find(pos); if (it != histkeys.end()) return it->second.nrows; return 0; } std::vector ROOTData::readHistogram(long int pos) { auto it = histkeys.find(pos); if (it == histkeys.end()) return std::vector(); std::string buffer = data(it->second); if (!buffer.empty()) { char* buf = &buffer[0]; std::map counts; auto& streamerTH1 = streamerInfo.find("TH1")->second; auto& streamerTAxis = streamerInfo.find("TAxis")->second; size_t count; Version(buf); // TH1(D/F/I/S/C) Version(buf, count); // TH1 char* const dbuf = buf + count; advanceTo(buf, streamerTH1, std::string(), "fNcells", counts); std::vector r(read(buf)); // fNcells if (r.size() < 3) return std::vector(); r.front().lowedge = -std::numeric_limits::infinity(); advanceTo(buf, streamerTH1, "fNcells", "fXaxis", counts); // x-Axis Version(buf, count); // TAxis char* const nbuf = buf + count; advanceTo(buf, streamerTAxis, std::string(), "fNbins", counts); const int nbins = read(buf); advanceTo(buf, streamerTAxis, "fNbins", "fXmin", counts); const double xmin = read(buf); advanceTo(buf, streamerTAxis, "fXmin", "fXmax", counts); const double xmax = read(buf); advanceTo(buf, streamerTAxis, "fXmax", "fXbins", counts); const size_t nborders = read(buf); // TArrayD // root/core/cont/src/TArrayD.cxx -> Streamer if (nborders == r.size() - 1) { for (size_t i = 0; i < nborders; ++i) { r[i + 1].lowedge = read(buf); } } else { //UNUSED: buf += sizeof(double) * nbins; const double scale = (xmax - xmin) / static_cast(nbins); for (size_t i = 0; i < r.size() - 1; ++i) { r[i + 1].lowedge = static_cast(i) * scale + xmin; } } buf = nbuf; // go beyond x-Axis advanceTo(buf, streamerTH1, "fXaxis", "fSumw2", counts); if (static_cast(read(buf)) == r.size()) { // TArrayD for (auto& b : r) b.sumw2 = read(buf); // always double } buf = dbuf; // skip to contents of TH1(D/F/I/S/C) if (static_cast(read(buf)) == r.size()) { auto readf = readType(it->second.type); for (auto& b : r) b.content = readf(buf); } return r; } else return std::vector(); } void ROOTData::readNEntries(ROOTData::KeyBuffer& kbuffer) { std::string buffer = data(kbuffer); if (!buffer.empty()) { char* buf = &buffer[0]; std::map counts; if (kbuffer.type == NTuple) Version(buf); // TNtuple(D) Version(buf); // TTree advanceTo(buf, streamerInfo.find("TTree")->second, std::string(), "fEntries", counts); kbuffer.nrows = read(buf); // fEntries } } std::string ROOTData::treeName(long int pos) { auto it = treekeys.find(pos); if (it != treekeys.end()) return it->second.name; return std::string(); } int ROOTData::treeEntries(long int pos) { auto it = treekeys.find(pos); if (it != treekeys.end()) return it->second.nrows; else return 0; } std::vector ROOTData::listLeaves(long int pos) const { std::vector leaves; auto it = treekeys.find(pos); if (it == treekeys.end()) return leaves; std::ifstream is(filename, std::ifstream::binary); std::string datastring = data(it->second, is); if (datastring.empty()) return leaves; char* buf = &datastring[0]; char* const buf0 = buf - it->second.keylength; std::map counts; auto& streamerTBranch = streamerInfo.find("TBranch")->second; if (it->second.type == NTuple) Version(buf); // TNtuple(D) Version(buf); // TTree advanceTo(buf, streamerInfo.find("TTree")->second, std::string(), "fBranches", counts); // read the list of branches Version(buf); // TObjArray SkipObject(buf); String(buf); const size_t nbranches = read(buf); const size_t lowb = read(buf); std::map tags; for (size_t i = 0; i < nbranches; ++i) { std::string clname = readObject(buf, buf0, tags); size_t count; Version(buf, count); // TBranch or TBranchElement char* const nbuf = buf + count; if (i >= lowb) { if (clname == "TBranchElement") { Version(buf); // TBranch } advanceTo(buf, streamerTBranch, std::string(), "TNamed", counts); Version(buf); // TNamed SkipObject(buf); const std::string branch = String(buf); String(buf); // TODO add reading of nested branches (fBranches) advanceTo(buf, streamerTBranch, "TNamed", "fLeaves", counts); // fLeaves Version(buf); // TObjArray SkipObject(buf); String(buf); const size_t nleaves = read(buf); const size_t lowb = read(buf); for (size_t i = 0; i < nleaves; ++i) { std::string clname = readObject(buf, buf0, tags); Version(buf, count); // TLeaf(D/F/B/S/I/L/C/O) char* nbuf = buf + count; if (i >= lowb && clname.size() == 6 && clname.compare(0, 5, "TLeaf") == 0) { Version(buf); // TLeaf Version(buf); // TNamed SkipObject(buf); const std::string leafname = String(buf); String(buf); // title size_t elements = read(buf); int bytes = read(buf); if ((leafType(clname.back()) & 0xF) != bytes) qDebug() << "ROOTData: type " << clname.back() << " does not match its size!"; buf += 5; leaves.emplace_back(LeafInfo{branch, leafname, leafType(clname.back()), !read(buf), elements}); } buf = nbuf; } } buf = nbuf; } return leaves; } template std::vector ROOTData::listEntries(long int pos, const std::string& branchname, const std::string& leafname, const size_t element, const size_t nentries) const { std::vector entries; auto it = treekeys.find(pos); if (it == treekeys.end()) return entries; std::ifstream is(filename, std::ifstream::binary); std::string datastring = data(it->second, is); if (datastring.empty()) return entries; char* buf = &datastring[0]; char* const buf0 = buf - it->second.keylength; std::map counts; auto& streamerTTree = streamerInfo.find("TTree")->second; auto& streamerTBranch = streamerInfo.find("TBranch")->second; if (it->second.type == NTuple) Version(buf); // TNtuple(D) Version(buf); // TTree advanceTo(buf, streamerTTree, std::string(), "fEntries", counts); entries.reserve(std::min(static_cast(read(buf)), nentries)); // reserve space (maximum for number of entries) advanceTo(buf, streamerTTree, "fEntries", "fBranches", counts); // read the list of branches Version(buf); // TObjArray SkipObject(buf); String(buf); const size_t nbranches = read(buf); const size_t lowb = read(buf); std::map tags; for (size_t i = 0; i < nbranches; ++i) { std::string clname = readObject(buf, buf0, tags); size_t count; Version(buf, count); // TBranch or TBranchElement char* const nbuf = buf + count; if (i >= lowb) { if (clname == "TBranchElement") { Version(buf); } Version(buf); // TNamed SkipObject(buf); const std::string currentbranch = String(buf); String(buf); advanceTo(buf, streamerTBranch, "TNamed", "fWriteBasket", counts); int fWriteBasket = read(buf); // TODO add reading of nested branches (fBranches) advanceTo(buf, streamerTBranch, "fWriteBasket", "fLeaves", counts); // fLeaves Version(buf); // TObjArray SkipObject(buf); String(buf); const size_t nleaves = read(buf); const size_t lowb = read(buf); int leafoffset = 0, leafcount = 0, leafcontent = 0, leafsize = 0; bool leafsign = false; ContentType leaftype = Invalid; for (size_t i = 0; i < nleaves; ++i) { std::string clname = readObject(buf, buf0, tags); Version(buf, count); // TLeaf(D/F/L/I/S/B/O/C/Element) char* nbuf = buf + count; if (currentbranch == branchname) { if (i >= lowb && clname.size() >= 5 && clname.compare(0, 5, "TLeaf") == 0) { Version(buf); // TLeaf Version(buf); // TNamed SkipObject(buf); const bool istheleaf = (clname.size() == 6 && leafname == String(buf)); String(buf); const int len = read(buf); const int size = read(buf); if (istheleaf) { leafoffset = leafcount; leafsize = size; leaftype = leafType(clname.back()); } leafcount += len * size; if (istheleaf) { leafcontent = leafcount - leafoffset; buf += 1; leafsign = !read(buf); } } } buf = nbuf; } if (leafcontent == 0) { buf = nbuf; continue; } if (static_cast(element) * leafsize >= leafcontent) { qDebug() << "ROOTData: " << leafname.c_str() << " only contains " << leafcontent / leafsize << " elements."; break; } advanceTo(buf, streamerTBranch, "fLeaves", "fBaskets", counts); // fBaskets (probably empty) Version(buf, count); // TObjArray char* const basketsbuf = buf += count + 1; // TODO there is one byte to be skipped in fBaskets, why is that? advanceTo(buf, streamerTBranch, "fBaskets", "fBasketEntry", counts); for (int i = 0; i <= fWriteBasket; ++i) { if (static_cast(read(buf)) > nentries) { fWriteBasket = i; break; } } // rewind to the end of fBaskets and look for the fBasketSeek array advanceTo(buf = basketsbuf, streamerTBranch, "fBaskets", "fBasketSeek", counts); auto readf = readType(leaftype, leafsign); for (int i = 0; i < fWriteBasket; ++i) { long int pos = read(buf); auto it = basketkeys.find(pos); if (it != basketkeys.end()) { std::string basketbuffer = data(it->second); if (!basketbuffer.empty()) { char* bbuf = &basketbuffer[0]; char* const bufend = bbuf + basketbuffer.size(); while (bbuf + leafcount <= bufend && entries.size() < nentries) { bbuf += leafoffset + leafsize * element; entries.emplace_back(readf(bbuf)); bbuf += leafcount - leafsize * (element + 1) - leafoffset; } } } else { qDebug() << "ROOTData: fBasketSeek(" << i << "): " << pos << " (not available)"; } } } buf = nbuf; } return entries; } ROOTData::ContentType ROOTData::histType(const char type) { switch (type) { case 'D': return Double; case 'F': return Float; case 'I': return Int; case 'S': return Short; case 'C': return Byte; default: return Invalid; } } ROOTData::ContentType ROOTData::leafType(const char type) { switch (type) { case 'D': return Double; case 'F': return Float; case 'L': return Long; case 'I': return Int; case 'S': return Short; case 'B': return Byte; case 'O': return Bool; case 'C': return CString; default: return Invalid; } } template T (*ROOTData::readType(ROOTData::ContentType type, bool sign) const)(char*&) { switch (type) { case Double: return readcast; case Float: return readcast; case Long: return sign ? readcast : readcast; case Int: return sign ? readcast : readcast; case Short: return sign ? readcast : readcast; case Byte: return sign ? readcast : readcast; case Bool: return readcast; case CString: case Tree: case NTuple: case Basket: case Streamer: case Invalid: break; } return readcast; } std::string ROOTData::data(const ROOTData::KeyBuffer& buffer) const { std::ifstream is(filename, std::ifstream::binary); return data(buffer, is); } std::string ROOTData::data(const ROOTData::KeyBuffer& buffer, std::ifstream& is) const { std::string data(buffer.count, 0); is.seekg(buffer.start); if (buffer.compression == KeyBuffer::none) { is.read(&data[0], buffer.count); return data; #ifdef HAVE_ZIP } else if (buffer.compression == KeyBuffer::zlib) { std::string cdata(buffer.compressed_count, 0); is.read(&cdata[0], buffer.compressed_count); uLongf luncomp = buffer.count; if (uncompress((Bytef *)data.data(), &luncomp, (Bytef *)cdata.data(), cdata.size()) == Z_OK && data.size() == luncomp) return data; } else { std::string cdata(buffer.compressed_count, 0); is.read(&cdata[0], buffer.compressed_count); if (LZ4_decompress_safe(cdata.data(), const_cast(data.data()), buffer.compressed_count, buffer.count) == static_cast(buffer.count)) return data; #endif } return std::string(); } void ROOTData::readStreamerInfo(const ROOTData::KeyBuffer& buffer) { std::ifstream is(filename, std::ifstream::binary); std::string datastring = data(buffer, is); if (!datastring.empty()) { char* buf = &datastring[0]; char* const buf0 = buf - buffer.keylength; Version(buf); SkipObject(buf); // TCollection String(buf); const int nobj = read(buf); std::map tags; for (int i = 0; i < nobj; ++i) { std::string clname = readObject(buf, buf0, tags); size_t count; Version(buf, count); char* const nbuf = buf + count; if (clname == "TStreamerInfo") { Version(buf); SkipObject(buf); std::vector& sinfo = streamerInfo[String(buf)]; String(buf); buf += 8; // skip check sum and version clname = readObject(buf, buf0, tags); Version(buf, count); if (clname != "TObjArray") { buf += count; continue; } SkipObject(buf); // TObjArray String(buf); const int nobj = read(buf); const int lowb = read(buf); for (int i = 0; i < nobj; ++i) { std::string clname = readObject(buf, buf0, tags); Version(buf, count); char* const nbuf = buf + count; const bool isbasicpointer = clname == "TStreamerBasicPointer"; const bool ispointer = isbasicpointer || clname == "TStreamerObjectPointer"; if (i >= lowb) { if (ispointer || clname == "TStreamerBase" || clname == "TStreamerBasicType" || clname == "TStreamerObject" || clname == "TStreamerObjectAny" || clname == "TStreamerString" || clname == "TStreamerSTL") { Version(buf); // TStreamerXXX Version(buf); // TStreamerElement SkipObject(buf); const std::string name = String(buf); const std::string title = String(buf); int type = read(buf); size_t size = read(buf); if (clname.compare(0, 15, "TStreamerObject") == 0) size = 0; std::string counter; bool iscounter = false; if (ispointer) { if (!title.empty() && title.front() == '[') { const size_t endref = title.find(']', 1); if (endref != title.npos) { counter = title.substr(1, endref - 1); } } if (isbasicpointer) { // see root/io/io/inc/TStreamerInfo.h -> TStreamerInfo::EReadWrite switch (type - 40) { case 1: // char case 11: // unsigned char size = 1; break; case 2: // short case 12: // unsigned short case 19: // float16 size = 2; break; case 3: // int case 5: // float case 9: // double32 case 13: // unsigned int size = 4; break; case 4: // long case 8: // double case 14: // unsigned long case 16: // long case 17: // unsigned long size = 8; break; } } } else if (clname == "TStreamerBasicType") { iscounter = type == 6; // see root/io/io/inc/TStreamerInfo.h -> TStreamerInfo::EReadWrite } sinfo.emplace_back(StreamerInfo{name, size, counter, iscounter, ispointer}); } } buf = nbuf; } } else buf = nbuf; buf += 1; // trailing zero of TObjArray* } } else DEBUG("ROOTData: Inflation failed!") } bool ROOTData::advanceTo(char*& buf, const std::vector& objects, const std::string& current, const std::string& target, std::map& counts) { // The object structure can be retrieved from TFile::GetStreamerInfoList(). // Every ROOT object contains a version number which may include the byte count // for the object. The latter is currently assumed to be present to skip unused // objects. No checks are performed. The corresponding ROOT code is quite nested // but the actual readout is straight forward. auto it = objects.begin(); if (!current.empty()) { for (; it != objects.end(); ++it) { if (it->name == target) { return false; // target lies before current buffer position } else if (it->name == current) { ++it; break; } } } for (; it != objects.end(); ++it) { if (it->name == target) return true; if (it->size == 0) Skip(buf, 1); else if (it->iscounter) counts[it->name] = read(buf); else if (it->ispointer) { if (it->counter.empty()) buf += it->size + 1; else buf += it->size * counts[it->counter] + 1; } else buf += it->size; } return false; } // needs to be after ROOTDataHelpers namespace declaration QString ROOTFilter::fileInfoString(const QString& fileName) { DEBUG("ROOTFilter::fileInfoString()"); QString info; // The file structure is described in root/io/io/src/TFile.cxx std::ifstream is(fileName.toStdString(), std::ifstream::binary); std::string root(4, 0); is.read(const_cast(root.data()), 4); if (root != "root") { DEBUG(" Not a ROOT file. root = " << root); return i18n("Not a ROOT file"); } int version = read(is); info += i18n("File format version: %1", QString::number(version)); info += QLatin1String("
"); is.seekg(20); int freeBytes = read(is); int freeRecords = read(is); int namedBytes = read(is); char pointerBytes = read(is); info += i18n("FREE data record size: %1 bytes", QString::number(freeBytes)); info += QLatin1String("
"); info += i18n("Number of free data records: %1", QString::number(freeRecords)); info += QLatin1String("
"); info += i18n("TNamed size: %1 bytes", QString::number(namedBytes)); info += QLatin1String("
"); info += i18n("Size of file pointers: %1 bytes", QString::number(pointerBytes)); info += QLatin1String("
"); int compression = read(is); compression = compression > 0 ? compression : 0; info += i18n("Compression level and algorithm: %1", QString::number(compression)); info += QLatin1String("
"); is.seekg(41); int infoBytes = read(is); info += i18n("Size of TStreamerInfo record: %1 bytes", QString::number(infoBytes)); info += QLatin1String("
"); Q_UNUSED(fileName); return info; } diff --git a/src/backend/datasources/projects/OriginProjectParser.cpp b/src/backend/datasources/projects/OriginProjectParser.cpp index 6c23d4232..03d1f4666 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::Text); + col->setColumnMode(AbstractColumn::ColumnMode::Text); break; } } } - if (col->columnMode() == AbstractColumn::Numeric) { + 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::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::DateTime); + 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::DateTime); + 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::Month); + 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::Day); + 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; 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)); 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)); 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)); axis->setLinePen(pen); axis->setMajorTicksLength( Worksheet::convertToSceneUnits(axisFormat.majorTickLength, Worksheet::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.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.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)); //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/lib/macros.h b/src/backend/lib/macros.h index 98c7dc8e3..3878413cd 100644 --- a/src/backend/lib/macros.h +++ b/src/backend/lib/macros.h @@ -1,494 +1,494 @@ /*************************************************************************** File : macros.h Project : LabPlot Description : Various preprocessor macros -------------------------------------------------------------------- Copyright : (C) 2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2013-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 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 MACROS_H #define MACROS_H #include #include // C++ style warning (works on Windows) #include #define WARN(x) std::cout << x << std::endl; #ifndef NDEBUG #include #define QDEBUG(x) qDebug() << x; // C++ style debugging (works on Windows) #include #define DEBUG(x) std::cout << x << std::endl; #else #define QDEBUG(x) {} #define DEBUG(x) {} #endif #define UTF8_QSTRING(str) QString::fromUtf8(str) #define STDSTRING(qstr) qstr.toUtf8().constData() #define ENUM_TO_STRING(class, enum, value) \ - (class::staticMetaObject.enumerator(class::staticMetaObject.indexOfEnumerator(#enum)).valueToKey(value)) + (class::staticMetaObject.enumerator(class::staticMetaObject.indexOfEnumerator(#enum)).valueToKey(static_cast(value))) #define ENUM_COUNT(class, enum) \ (class::staticMetaObject.enumerator(class::staticMetaObject.indexOfEnumerator(#enum)).keyCount()) #define BASIC_ACCESSOR(type, var, method, Method) \ type method() const { return var; }; \ void set ## Method(const type value) { var = value; } #define CLASS_ACCESSOR(type, var, method, Method) \ type method() const { return var; }; \ void set ## Method(const type & value) { var = value; } #define BASIC_D_ACCESSOR_DECL(type, method, Method) \ type method() const; \ void set ## Method(const type value); #define BASIC_D_ACCESSOR_IMPL(classname, type, method, Method, var) \ void classname::set ## Method(const type value) \ { \ d->var = value; \ } \ type classname::method() const \ { \ return d->var; \ } #define BASIC_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ return d->var; \ } #define BASIC_SHARED_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ Q_D(const classname); \ return d->var; \ } #define CLASS_D_ACCESSOR_DECL(type, method, Method) \ type method() const; \ void set ## Method(const type & value); #define CLASS_D_ACCESSOR_IMPL(classname, type, method, Method, var) \ void classname::set ## Method(const type & value) \ { \ d->var = value; \ } \ type classname::method() const \ { \ return d->var; \ } #define CLASS_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ return d->var; \ } #define CLASS_SHARED_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ Q_D(const classname); \ return d->var; \ } #define POINTER_D_ACCESSOR_DECL(type, method, Method) \ type *method() const; \ void set ## Method(type *ptr); #define FLAG_D_ACCESSOR_DECL(Method) \ bool is ## Method() const; \ bool has ## Method() const; \ void set ## Method(const bool value = true); \ void enable ## Method(const bool value = true); #define FLAG_D_ACCESSOR_IMPL(classname, Method, var) \ void classname::set ## Method(const bool value) \ { \ d->var = value; \ } \ void classname::enable ## Method(const bool value) \ { \ d->var = value; \ } \ bool classname::is ## Method() const \ { \ return d->var; \ } \ bool classname::has ## Method() const \ { \ return d->var; \ } #define WAIT_CURSOR QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)) #define RESET_CURSOR QApplication::restoreOverrideCursor() #define STD_SETTER_CMD_IMPL(class_name, cmd_name, value_type, field_name) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ }; #define STD_SETTER_CMD_IMPL_F(class_name, cmd_name, value_type, field_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() override { m_target->finalize_method(); } \ }; // setter class with finalize() and signal emitting. #define STD_SETTER_CMD_IMPL_S(class_name, cmd_name, value_type, field_name) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() override { emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; #define STD_SETTER_CMD_IMPL_F_S(class_name, cmd_name, value_type, field_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() override { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; // setter class with finalize() and signal emitting for changing several properties in one single step (embedded in beginMacro/endMacro) #define STD_SETTER_CMD_IMPL_M_F_S(class_name, cmd_name, value_type, field_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardMacroSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardMacroSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() override { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ virtual void finalizeUndo() override { emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; #define STD_SETTER_CMD_IMPL_I(class_name, cmd_name, value_type, field_name, init_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ }; #define STD_SETTER_CMD_IMPL_IF(class_name, cmd_name, value_type, field_name, init_method, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ virtual void finalize() { m_target->finalize_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL(class_name, cmd_name, value_type, method_name) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_F(class_name, cmd_name, value_type, method_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ virtual void finalize() override { m_target->finalize_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_I(class_name, cmd_name, value_type, method_name, init_method) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_IF(class_name, cmd_name, value_type, method_name, init_method, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ virtual void finalize() { m_target->finalize_method(); } \ }; //xml-serialization/deserialization //QColor #define WRITE_QCOLOR(color) \ do { \ writer->writeAttribute( "color_r", QString::number(color.red()) ); \ writer->writeAttribute( "color_g", QString::number(color.green()) ); \ writer->writeAttribute( "color_b", QString::number(color.blue()) ); \ } while (0) #define READ_QCOLOR(color) \ do { \ str = attribs.value("color_r").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_r").toString()); \ else \ color.setRed( str.toInt() ); \ \ str = attribs.value("color_g").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_g").toString()); \ else \ color.setGreen( str.toInt() ); \ \ str = attribs.value("color_b").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_b").toString()); \ else \ color.setBlue( str.toInt() ); \ } while(0) //QPen #define WRITE_QPEN(pen) \ do { \ writer->writeAttribute( "style", QString::number(pen.style()) ); \ writer->writeAttribute( "color_r", QString::number(pen.color().red()) ); \ writer->writeAttribute( "color_g", QString::number(pen.color().green()) ); \ writer->writeAttribute( "color_b", QString::number(pen.color().blue()) ); \ writer->writeAttribute( "width", QString::number(pen.widthF()) ); \ } while (0) #define READ_QPEN(pen) \ do { \ str = attribs.value("style").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("style").toString()); \ else \ pen.setStyle( (Qt::PenStyle)str.toInt() ); \ \ QColor color; \ str = attribs.value("color_r").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_r").toString()); \ else \ color.setRed( str.toInt() ); \ \ str = attribs.value("color_g").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_g").toString()); \ else \ color.setGreen( str.toInt() ); \ \ str = attribs.value("color_b").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_b").toString()); \ else \ color.setBlue( str.toInt() ); \ \ pen.setColor(color); \ \ str = attribs.value("width").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("width").toString()); \ else \ pen.setWidthF( str.toDouble() ); \ } while(0) //QFont #define WRITE_QFONT(font) \ do { \ writer->writeAttribute( "fontFamily", font.family() ); \ writer->writeAttribute( "fontSize", QString::number(font.pixelSize()) ); \ writer->writeAttribute( "fontPointSize", QString::number(font.pointSize()));\ writer->writeAttribute( "fontWeight", QString::number(font.weight()) ); \ writer->writeAttribute( "fontItalic", QString::number(font.italic()) ); \ } while(0) #define READ_QFONT(font) \ do { \ str = attribs.value("fontFamily").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontFamily").toString()); \ else \ font.setFamily( str ); \ \ str = attribs.value("fontSize").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontSize").toString()); \ else { \ int size = str.toInt(); \ if (size != -1) \ font.setPixelSize(size); \ } \ \ str = attribs.value("fontPointSize").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontPointSize").toString()); \ else { \ int size = str.toInt(); \ if (size != -1) \ font.setPointSize(size); \ } \ \ str = attribs.value("fontWeight").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontWeight").toString()); \ else \ font.setWeight( str.toInt() ); \ \ str = attribs.value("fontItalic").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontItalic").toString()); \ else \ font.setItalic( str.toInt() ); \ } while(0) //QBrush #define WRITE_QBRUSH(brush) \ do { \ writer->writeAttribute("brush_style", QString::number(brush.style()) ); \ writer->writeAttribute("brush_color_r", QString::number(brush.color().red())); \ writer->writeAttribute("brush_color_g", QString::number(brush.color().green()));\ writer->writeAttribute("brush_color_b", QString::number(brush.color().blue())); \ } while(0) #define READ_QBRUSH(brush) \ do { \ str = attribs.value("brush_style").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("brush_style").toString()); \ else \ brush.setStyle( (Qt::BrushStyle)str.toInt() ); \ \ QColor color; \ str = attribs.value("brush_color_r").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("brush_color_r").toString()); \ else \ color.setRed( str.toInt() ); \ \ str = attribs.value("brush_color_g").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("brush_color_g").toString()); \ else \ color.setGreen( str.toInt() ); \ \ str = attribs.value("brush_color_b").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("brush_color_b").toString()); \ else \ color.setBlue( str.toInt() ); \ \ brush.setColor(color); \ } while(0) //Column #define WRITE_COLUMN(column, columnName) \ do { \ if (column){ \ writer->writeAttribute( #columnName, column->path() ); \ } else { \ writer->writeAttribute( #columnName, QString() ); \ } \ } while(0) //column names can be empty in case no columns were used before save //the actual pointers to the x- and y-columns are restored in Project::load() #define READ_COLUMN(columnName) \ do { \ str = attribs.value(#columnName).toString(); \ d->columnName ##Path = str; \ } while(0) #define READ_INT_VALUE(name, var, type) \ str = attribs.value(name).toString(); \ if (str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs(name).toString()); \ else \ d->var = (type)str.toInt(); #define READ_DOUBLE_VALUE(name, var) \ str = attribs.value(name).toString(); \ if (str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs(name).toString()); \ else \ d->var = str.toDouble(); #define READ_STRING_VALUE(name, var) \ str = attribs.value(name).toString(); \ if (str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs(name).toString()); \ else \ d->var = str; //used in Project::load() #define RESTORE_COLUMN_POINTER(obj, col, Col) \ do { \ if (!obj->col ##Path().isEmpty()) { \ for (Column* column : columns) { \ if (!column) continue; \ if (column->path() == obj->col ##Path()) { \ obj->set## Col(column); \ break; \ } \ } \ } \ } while(0) #define WRITE_PATH(obj, name) \ do { \ if (obj){ \ writer->writeAttribute( #name, obj->path() ); \ } else { \ writer->writeAttribute( #name, QString() ); \ } \ } while(0) #define READ_PATH(name) \ do { \ str = attribs.value(#name).toString(); \ d->name ##Path = str; \ } while(0) #define RESTORE_POINTER(obj, name, Name, Type, list) \ do { \ if (!obj->name ##Path().isEmpty()) { \ for (AbstractAspect* aspect : list) { \ if (aspect->path() == obj->name ##Path()) { \ auto a = dynamic_cast(aspect); \ if (!a) continue; \ obj->set## Name(a); \ break; \ } \ } \ } \ } while(0) #endif // MACROS_H diff --git a/src/backend/matrix/Matrix.cpp b/src/backend/matrix/Matrix.cpp index 62d40c036..ed5e1c754 100644 --- a/src/backend/matrix/Matrix.cpp +++ b/src/backend/matrix/Matrix.cpp @@ -1,1382 +1,1382 @@ /*************************************************************************** File : Matrix.cpp Project : Matrix Description : Spreadsheet with a MxN matrix data model -------------------------------------------------------------------- Copyright : (C) 2008-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2015-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-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 "Matrix.h" #include "MatrixPrivate.h" #include "matrixcommands.h" #include "backend/matrix/MatrixModel.h" #include "backend/core/Folder.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "commonfrontend/matrix/MatrixView.h" #include "kdefrontend/spreadsheet/ExportSpreadsheetDialog.h" #include #include #include #include #include #include #include /*! This class manages matrix based data (i.e., mathematically a MxN matrix with M rows, N columns). This data is typically used to for 3D plots. The values of the matrix are stored as generic values. Each column of the matrix is stored in a QVector objects. \ingroup backend */ Matrix::Matrix(int rows, int cols, const QString& name, const AbstractColumn::ColumnMode mode) : AbstractDataSource(name, AspectType::Matrix), d(new MatrixPrivate(this, mode)) { //set initial number of rows and columns appendColumns(cols); appendRows(rows); init(); } Matrix::Matrix(const QString& name, bool loading, const AbstractColumn::ColumnMode mode) : AbstractDataSource(name, AspectType::Matrix), d(new MatrixPrivate(this, mode)) { if (!loading) init(); } Matrix::~Matrix() { delete d; } void Matrix::init() { KConfig config; KConfigGroup group = config.group("Matrix"); //matrix dimension int rows = group.readEntry("RowCount", 10); int cols = group.readEntry("ColumnCount", 10); appendRows(rows); appendColumns(cols); //mapping to logical x- and y-coordinates d->xStart = group.readEntry("XStart", 0.0); d->xEnd = group.readEntry("XEnd", 1.0); d->yStart = group.readEntry("YStart", 0.0); d->yEnd = group.readEntry("YEnd", 1.0); //format QByteArray formatba = group.readEntry("NumericFormat", "f").toLatin1(); d->numericFormat = *formatba.data(); d->precision = group.readEntry("Precision", 3); d->headerFormat = (Matrix::HeaderFormat)group.readEntry("HeaderFormat", (int)Matrix::HeaderRowsColumns); } /*! Returns an icon to be used for decorating my views. */ QIcon Matrix::icon() const { return QIcon::fromTheme("labplot-matrix"); } /*! Returns a new context menu. The caller takes ownership of the menu. */ QMenu* Matrix::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); emit requestProjectContextMenu(menu); return menu; } QWidget* Matrix::view() const { if (!m_partView) { m_view = new MatrixView(const_cast(this)); m_partView = m_view; m_model = m_view->model(); } return m_partView; } bool Matrix::exportView() const { auto* dlg = new ExportSpreadsheetDialog(m_view); dlg->setFileName(name()); dlg->setMatrixMode(true); //TODO FITS filter to decide if it can be exported to both dlg->setExportTo(QStringList() << i18n("FITS image") << i18n("FITS table")); if (m_view->selectedColumnCount() == 0) { dlg->setExportSelection(false); } bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { const QString path = dlg->path(); WAIT_CURSOR; if (dlg->format() == ExportSpreadsheetDialog::LaTeX) { const bool verticalHeader = dlg->matrixVerticalHeader(); const bool horizontalHeader = dlg->matrixHorizontalHeader(); const bool latexHeader = dlg->exportHeader(); const bool gridLines = dlg->gridLines(); const bool entire = dlg->entireSpreadheet(); const bool captions = dlg->captions(); m_view->exportToLaTeX(path, verticalHeader, horizontalHeader, latexHeader, gridLines, entire, captions); } else if (dlg->format() == ExportSpreadsheetDialog::FITS) { const int exportTo = dlg->exportToFits(); m_view->exportToFits(path, exportTo ); } else { const QString separator = dlg->separator(); const QLocale::Language format = dlg->numberFormat(); m_view->exportToFile(path, separator, format); } RESET_CURSOR; } delete dlg; return ret; } bool Matrix::printView() { QPrinter printer; auto* dlg = new QPrintDialog(&printer, m_view); bool ret; dlg->setWindowTitle(i18nc("@title:window", "Print Matrix")); if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool Matrix::printPreview() const { QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &MatrixView::print); return dlg->exec(); } //############################################################################## //########################## getter methods ################################## //############################################################################## void* Matrix::data() const { return d->data; } BASIC_D_READER_IMPL(Matrix, AbstractColumn::ColumnMode, mode, mode) BASIC_D_READER_IMPL(Matrix, int, rowCount, rowCount) BASIC_D_READER_IMPL(Matrix, int, columnCount, columnCount) BASIC_D_READER_IMPL(Matrix, double, xStart, xStart) BASIC_D_READER_IMPL(Matrix, double, xEnd, xEnd) BASIC_D_READER_IMPL(Matrix, double, yStart, yStart) BASIC_D_READER_IMPL(Matrix, double, yEnd, yEnd) BASIC_D_READER_IMPL(Matrix, char, numericFormat, numericFormat) BASIC_D_READER_IMPL(Matrix, int, precision, precision) BASIC_D_READER_IMPL(Matrix, Matrix::HeaderFormat, headerFormat, headerFormat) CLASS_D_READER_IMPL(Matrix, QString, formula, formula) void Matrix::setSuppressDataChangedSignal(bool b) { if (m_model) m_model->setSuppressDataChangedSignal(b); } void Matrix::setChanged() { if (m_model) m_model->setChanged(); } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## void Matrix::setRowCount(int count) { if (count == d->rowCount) return; const int diff = count - d->rowCount; if (diff > 0) appendRows(diff); else if (diff < 0) removeRows(rowCount() + diff, -diff); } void Matrix::setColumnCount(int count) { if (count == d->columnCount) return; const int diff = count - columnCount(); if (diff > 0) appendColumns(diff); else if (diff < 0) removeColumns(columnCount() + diff, -diff); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetXStart, double, xStart, updateViewHeader) void Matrix::setXStart(double xStart) { if (xStart != d->xStart) exec(new MatrixSetXStartCmd(d, xStart, ki18n("%1: x-start changed"))); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetXEnd, double, xEnd, updateViewHeader) void Matrix::setXEnd(double xEnd) { if (xEnd != d->xEnd) exec(new MatrixSetXEndCmd(d, xEnd, ki18n("%1: x-end changed"))); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetYStart, double, yStart, updateViewHeader) void Matrix::setYStart(double yStart) { if (yStart != d->yStart) exec(new MatrixSetYStartCmd(d, yStart, ki18n("%1: y-start changed"))); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetYEnd, double, yEnd, updateViewHeader) void Matrix::setYEnd(double yEnd) { if (yEnd != d->yEnd) exec(new MatrixSetYEndCmd(d, yEnd, ki18n("%1: y-end changed"))); } STD_SETTER_CMD_IMPL_S(Matrix, SetNumericFormat, char, numericFormat) void Matrix::setNumericFormat(char format) { if (format != d->numericFormat) exec(new MatrixSetNumericFormatCmd(d, format, ki18n("%1: numeric format changed"))); } STD_SETTER_CMD_IMPL_S(Matrix, SetPrecision, int, precision) void Matrix::setPrecision(int precision) { if (precision != d->precision) exec(new MatrixSetPrecisionCmd(d, precision, ki18n("%1: precision changed"))); } //TODO: make this undoable? void Matrix::setHeaderFormat(Matrix::HeaderFormat format) { d->headerFormat = format; m_model->updateHeader(); if (m_view) m_view->resizeHeaders(); emit headerFormatChanged(format); } //columns void Matrix::insertColumns(int before, int count) { if (count < 1 || before < 0 || before > columnCount()) return; WAIT_CURSOR; exec(new MatrixInsertColumnsCmd(d, before, count)); RESET_CURSOR; } void Matrix::appendColumns(int count) { insertColumns(columnCount(), count); } void Matrix::removeColumns(int first, int count) { if (count < 1 || first < 0 || first+count > columnCount()) return; WAIT_CURSOR; switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: exec(new MatrixRemoveColumnsCmd(d, first, count)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: exec(new MatrixRemoveColumnsCmd(d, first, count)); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: exec(new MatrixRemoveColumnsCmd(d, first, count)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: exec(new MatrixRemoveColumnsCmd(d, first, count)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: exec(new MatrixRemoveColumnsCmd(d, first, count)); break; } RESET_CURSOR; } void Matrix::clearColumn(int c) { WAIT_CURSOR; switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: exec(new MatrixClearColumnCmd(d, c)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: exec(new MatrixClearColumnCmd(d, c)); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: exec(new MatrixClearColumnCmd(d, c)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: exec(new MatrixClearColumnCmd(d, c)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: exec(new MatrixClearColumnCmd(d, c)); break; } RESET_CURSOR; } //rows void Matrix::insertRows(int before, int count) { if (count < 1 || before < 0 || before > rowCount()) return; WAIT_CURSOR; exec(new MatrixInsertRowsCmd(d, before, count)); RESET_CURSOR; } void Matrix::appendRows(int count) { insertRows(rowCount(), count); } void Matrix::removeRows(int first, int count) { if (count < 1 || first < 0 || first+count > rowCount()) return; WAIT_CURSOR; switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: exec(new MatrixRemoveRowsCmd(d, first, count)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: exec(new MatrixRemoveRowsCmd(d, first, count)); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: exec(new MatrixRemoveRowsCmd(d, first, count)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: exec(new MatrixRemoveRowsCmd(d, first, count)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: exec(new MatrixRemoveRowsCmd(d, first, count)); break; } RESET_CURSOR; } void Matrix::clearRow(int r) { switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, 0.0)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, QString())); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, 0)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, 0)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, QDateTime())); break; } } //! Return the value in the given cell (needs explicit instantiation) template T Matrix::cell(int row, int col) const { return d->cell(row, col); } template double Matrix::cell(int row, int col) const; template int Matrix::cell(int row, int col) const; template qint64 Matrix::cell(int row, int col) const; template QDateTime Matrix::cell(int row, int col) const; template QString Matrix::cell(int row, int col) const; //! Return the text displayed in the given cell (needs explicit instantiation) template QString Matrix::text(int row, int col) { return QLocale().toString(cell(row,col)); } template <> QString Matrix::text(int row, int col) { return QLocale().toString(cell(row,col), d->numericFormat, d->precision); } template <> QString Matrix::text(int row, int col) { return cell(row,col); } template QString Matrix::text(int row, int col); template QString Matrix::text(int row, int col); template QString Matrix::text(int row, int col); //! Set the value of the cell (needs explicit instantiation) template void Matrix::setCell(int row, int col, T value) { if (row < 0 || row >= rowCount()) return; if (col < 0 || col >= columnCount()) return; exec(new MatrixSetCellValueCmd(d, row, col, value)); } template void Matrix::setCell(int row, int col, double value); template void Matrix::setCell(int row, int col, int value); template void Matrix::setCell(int row, int col, qint64 value); template void Matrix::setCell(int row, int col, QString value); template void Matrix::setCell(int row, int col, QDateTime value); void Matrix::clearCell(int row, int col) { switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: exec(new MatrixSetCellValueCmd(d, row, col, 0.0)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: exec(new MatrixSetCellValueCmd(d, row, col, QString())); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: exec(new MatrixSetCellValueCmd(d, row, col, 0)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: exec(new MatrixSetCellValueCmd(d, row, col, 0)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: exec(new MatrixSetCellValueCmd(d, row, col, QDateTime())); break; } } void Matrix::setDimensions(int rows, int cols) { if ( (rows < 0) || (cols < 0 ) || (rows == rowCount() && cols == columnCount()) ) return; WAIT_CURSOR; beginMacro(i18n("%1: set matrix size to %2x%3", name(), rows, cols)); int col_diff = cols - columnCount(); if (col_diff > 0) insertColumns(columnCount(), col_diff); else if (col_diff < 0) removeColumns(columnCount() + col_diff, -col_diff); int row_diff = rows - rowCount(); if (row_diff > 0) appendRows(row_diff); else if (row_diff < 0) removeRows(rowCount() + row_diff, -row_diff); endMacro(); RESET_CURSOR; } void Matrix::copy(Matrix* other) { WAIT_CURSOR; beginMacro(i18n("%1: copy %2", name(), other->name())); int rows = other->rowCount(); int columns = other->columnCount(); setDimensions(rows, columns); for (int i = 0; i < rows; i++) setRowHeight(i, other->rowHeight(i)); for (int i = 0; i < columns; i++) setColumnWidth(i, other->columnWidth(i)); d->suppressDataChange = true; switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; } setCoordinates(other->xStart(), other->xEnd(), other->yStart(), other->yEnd()); setNumericFormat(other->numericFormat()); setPrecision(other->precision()); d->formula = other->formula(); d->suppressDataChange = false; emit dataChanged(0, 0, rows-1, columns-1); if (m_view) m_view->adjustHeaders(); endMacro(); RESET_CURSOR; } //! Duplicate the matrix inside its folder void Matrix::duplicate() { Matrix* matrix = new Matrix(rowCount(), columnCount(), name()); matrix->copy(this); if (folder()) folder()->addChild(matrix); } void Matrix::addRows() { if (!m_view) return; WAIT_CURSOR; int count = m_view->selectedRowCount(false); beginMacro(i18np("%1: add %2 rows", "%1: add %2 rows", name(), count)); exec(new MatrixInsertRowsCmd(d, rowCount(), count)); endMacro(); RESET_CURSOR; } void Matrix::addColumns() { if (!m_view) return; WAIT_CURSOR; int count = m_view->selectedRowCount(false); beginMacro(i18np("%1: add %2 column", "%1: add %2 columns", name(), count)); exec(new MatrixInsertColumnsCmd(d, columnCount(), count)); endMacro(); RESET_CURSOR; } void Matrix::setCoordinates(double x1, double x2, double y1, double y2) { exec(new MatrixSetCoordinatesCmd(d, x1, x2, y1, y2)); } void Matrix::setFormula(const QString& formula) { exec(new MatrixSetFormulaCmd(d, formula)); } //! This method should only be called by the view. /** This method does not change the view, it only changes the * values that are saved when the matrix is saved. The view * has to take care of reading and applying these values */ void Matrix::setRowHeight(int row, int height) { d->setRowHeight(row, height); } //! This method should only be called by the view. /** This method does not change the view, it only changes the * values that are saved when the matrix is saved. The view * has to take care of reading and applying these values */ void Matrix::setColumnWidth(int col, int width) { d->setColumnWidth(col, width); } int Matrix::rowHeight(int row) const { return d->rowHeight(row); } int Matrix::columnWidth(int col) const { return d->columnWidth(col); } //! Return the values in the given cells as vector template QVector Matrix::columnCells(int col, int first_row, int last_row) { return d->columnCells(col, first_row, last_row); } //! Set the values in the given cells from a type T vector template void Matrix::setColumnCells(int col, int first_row, int last_row, const QVector& values) { WAIT_CURSOR; exec(new MatrixSetColumnCellsCmd(d, col, first_row, last_row, values)); RESET_CURSOR; } //! Return the values in the given cells as vector (needs explicit instantiation) template QVector Matrix::rowCells(int row, int first_column, int last_column) { return d->rowCells(row, first_column, last_column); } template QVector Matrix::rowCells(int row, int first_column, int last_column); template QVector Matrix::rowCells(int row, int first_column, int last_column); template QVector Matrix::rowCells(int row, int first_column, int last_column); template QVector Matrix::rowCells(int row, int first_column, int last_column); //! Set the values in the given cells from a type T vector template void Matrix::setRowCells(int row, int first_column, int last_column, const QVector& values) { WAIT_CURSOR; exec(new MatrixSetRowCellsCmd(d, row, first_column, last_column, values)); RESET_CURSOR; } void Matrix::setData(void* data) { bool isEmpty = false; switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; } if (!isEmpty) exec(new MatrixReplaceValuesCmd(d, data)); } //############################################################################## //######################### Public slots ##################################### //############################################################################## //! Clear the whole matrix (i.e. reset all cells) void Matrix::clear() { WAIT_CURSOR; beginMacro(i18n("%1: clear", name())); switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: exec(new MatrixClearCmd(d)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: exec(new MatrixClearCmd(d)); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: exec(new MatrixClearCmd(d)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: exec(new MatrixClearCmd(d)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: exec(new MatrixClearCmd(d)); break; } endMacro(); RESET_CURSOR; } void Matrix::transpose() { WAIT_CURSOR; switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: exec(new MatrixTransposeCmd(d)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: exec(new MatrixTransposeCmd(d)); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: exec(new MatrixTransposeCmd(d)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: exec(new MatrixTransposeCmd(d)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: exec(new MatrixTransposeCmd(d)); break; } RESET_CURSOR; } void Matrix::mirrorHorizontally() { WAIT_CURSOR; switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: exec(new MatrixMirrorHorizontallyCmd(d)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: exec(new MatrixMirrorHorizontallyCmd(d)); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: exec(new MatrixMirrorHorizontallyCmd(d)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: exec(new MatrixMirrorHorizontallyCmd(d)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: exec(new MatrixMirrorHorizontallyCmd(d)); break; } RESET_CURSOR; } void Matrix::mirrorVertically() { WAIT_CURSOR; switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: exec(new MatrixMirrorVerticallyCmd(d)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: exec(new MatrixMirrorVerticallyCmd(d)); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: exec(new MatrixMirrorVerticallyCmd(d)); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: exec(new MatrixMirrorVerticallyCmd(d)); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: exec(new MatrixMirrorVerticallyCmd(d)); break; } RESET_CURSOR; } //############################################################################## //###################### Private implementation ############################### //############################################################################## MatrixPrivate::MatrixPrivate(Matrix* owner, const AbstractColumn::ColumnMode m) : q(owner), data(nullptr), mode(m), rowCount(0), columnCount(0), suppressDataChange(false) { switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: data = new QVector>(); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: data = new QVector>(); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: data = new QVector>(); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: data = new QVector>(); break; - case AbstractColumn::Month: - case AbstractColumn::Day: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::DateTime: data = new QVector>(); break; } } MatrixPrivate::~MatrixPrivate() { if (data) { switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: delete static_cast>*>(data); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: delete static_cast>*>(data); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: delete static_cast>*>(data); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: delete static_cast>*>(data); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: delete static_cast>*>(data); break; } } } void MatrixPrivate::updateViewHeader() { q->m_view->model()->updateHeader(); } /*! Insert \p count columns before column number \c before */ void MatrixPrivate::insertColumns(int before, int count) { Q_ASSERT(before >= 0); Q_ASSERT(before <= columnCount); emit q->columnsAboutToBeInserted(before, count); switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; } columnCount += count; emit q->columnsInserted(before, count); } /*! Remove \c count columns starting with column index \c first */ void MatrixPrivate::removeColumns(int first, int count) { emit q->columnsAboutToBeRemoved(first, count); Q_ASSERT(first >= 0); Q_ASSERT(first + count <= columnCount); switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: (static_cast>*>(data))->remove(first, count); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: (static_cast>*>(data))->remove(first, count); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: (static_cast>*>(data))->remove(first, count); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: (static_cast>*>(data))->remove(first, count); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: (static_cast>*>(data))->remove(first, count); break; } for (int i = 0; i < count; i++) columnWidths.remove(first); columnCount -= count; emit q->columnsRemoved(first, count); } /*! Insert \c count rows before row with the index \c before */ void MatrixPrivate::insertRows(int before, int count) { emit q->rowsAboutToBeInserted(before, count); Q_ASSERT(before >= 0); Q_ASSERT(before <= rowCount); switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, 0.0); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, QString()); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, 0); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, 0); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, QDateTime()); } for (int i = 0; i < count; i++) rowHeights.insert(before+i, 0); rowCount += count; emit q->rowsInserted(before, count); } /*! Remove \c count columns starting from the column with index \c first */ void MatrixPrivate::removeRows(int first, int count) { emit q->rowsAboutToBeRemoved(first, count); Q_ASSERT(first >= 0); Q_ASSERT(first+count <= rowCount); switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; } for (int i = 0; i < count; i++) rowHeights.remove(first); rowCount -= count; emit q->rowsRemoved(first, count); } //! Fill column with zeroes void MatrixPrivate::clearColumn(int col) { switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: static_cast>*>(data)->operator[](col).fill(0.0); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: static_cast>*>(data)->operator[](col).fill(QString()); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: static_cast>*>(data)->operator[](col).fill(0); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: static_cast>*>(data)->operator[](col).fill(0); break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: static_cast>*>(data)->operator[](col).fill(QDateTime()); break; } if (!suppressDataChange) emit q->dataChanged(0, col, rowCount-1, col); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## void Matrix::save(QXmlStreamWriter* writer) const { DEBUG("Matrix::save()"); writer->writeStartElement("matrix"); writeBasicAttributes(writer); writeCommentElement(writer); //formula writer->writeStartElement("formula"); writer->writeCharacters(d->formula); writer->writeEndElement(); //format writer->writeStartElement("format"); - writer->writeAttribute("mode", QString::number(d->mode)); + writer->writeAttribute("mode", QString::number(static_cast(d->mode))); writer->writeAttribute("headerFormat", QString::number(d->headerFormat)); writer->writeAttribute("numericFormat", QString(QChar(d->numericFormat))); writer->writeAttribute("precision", QString::number(d->precision)); writer->writeEndElement(); //dimensions writer->writeStartElement("dimension"); writer->writeAttribute("columns", QString::number(d->columnCount)); writer->writeAttribute("rows", QString::number(d->rowCount)); writer->writeAttribute("x_start", QString::number(d->xStart)); writer->writeAttribute("x_end", QString::number(d->xEnd)); writer->writeAttribute("y_start", QString::number(d->yStart)); writer->writeAttribute("y_end", QString::number(d->yEnd)); writer->writeEndElement(); //vector with row heights writer->writeStartElement("row_heights"); const char* data = reinterpret_cast(d->rowHeights.constData()); int size = d->rowHeights.size() * sizeof(int); writer->writeCharacters(QByteArray::fromRawData(data,size).toBase64()); writer->writeEndElement(); //vector with column widths writer->writeStartElement("column_widths"); data = reinterpret_cast(d->columnWidths.constData()); size = d->columnWidths.size()*sizeof(int); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); //columns - DEBUG(" mode = " << d->mode); + DEBUG(" mode = " << static_cast(d->mode)); switch (d->mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: size = d->rowCount*sizeof(double); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: size = d->rowCount*sizeof(QString); for (int i = 0; i < d->columnCount; ++i) { QDEBUG(" string: " << static_cast>*>(d->data)->at(i)); data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: size = d->rowCount*sizeof(int); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: size = d->rowCount*sizeof(qint64); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: size = d->rowCount*sizeof(QDateTime); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; } writer->writeEndElement(); // "matrix" } bool Matrix::load(XmlStreamReader* reader, bool preview) { DEBUG("Matrix::load()"); if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; // read child elements while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "matrix") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "formula") { d->formula = reader->text().toString().trimmed(); } else if (!preview && reader->name() == "format") { attribs = reader->attributes(); str = attribs.value("mode").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("mode").toString()); else d->mode = AbstractColumn::ColumnMode(str.toInt()); str = attribs.value("headerFormat").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("headerFormat").toString()); else d->headerFormat = Matrix::HeaderFormat(str.toInt()); str = attribs.value("numericFormat").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("numericFormat").toString()); else { QByteArray formatba = str.toLatin1(); d->numericFormat = *formatba.data(); } str = attribs.value("precision").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("precision").toString()); else d->precision = str.toInt(); } else if (!preview && reader->name() == "dimension") { attribs = reader->attributes(); str = attribs.value("columns").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("columns").toString()); else d->columnCount = str.toInt(); str = attribs.value("rows").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("rows").toString()); else d->rowCount = str.toInt(); str = attribs.value("x_start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x_start").toString()); else d->xStart = str.toDouble(); str = attribs.value("x_end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x_end").toString()); else d->xEnd = str.toDouble(); str = attribs.value("y_start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y_start").toString()); else d->yStart = str.toDouble(); str = attribs.value("y_end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y_end").toString()); else d->yEnd = str.toDouble(); } else if (!preview && reader->name() == "row_heights") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray bytes = QByteArray::fromBase64(content.toLatin1()); int count = bytes.size()/sizeof(int); d->rowHeights.resize(count); memcpy(d->rowHeights.data(), bytes.data(), count*sizeof(int)); } else if (!preview && reader->name() == "column_widths") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray bytes = QByteArray::fromBase64(content.toLatin1()); int count = bytes.size()/sizeof(int); d->columnWidths.resize(count); memcpy(d->columnWidths.data(), bytes.data(), count*sizeof(int)); } else if (!preview && reader->name() == "column") { //TODO: parallelize reading of columns? reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray bytes = QByteArray::fromBase64(content.toLatin1()); switch (d->mode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { int count = bytes.size()/sizeof(double); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(double)); static_cast>*>(d->data)->append(column); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { int count = bytes.size()/sizeof(char); QVector column; column.resize(count); //TODO: warning (GCC8): writing to an object of type 'class QString' with no trivial copy-assignment; use copy-assignment or copy-initialization instead //memcpy(column.data(), bytes.data(), count*sizeof(QString)); //QDEBUG(" string: " << column.data()); static_cast>*>(d->data)->append(column); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { int count = bytes.size()/sizeof(int); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(int)); static_cast>*>(d->data)->append(column); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { int count = bytes.size()/sizeof(qint64); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(qint64)); static_cast>*>(d->data)->append(column); break; } - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: { int count = bytes.size()/sizeof(QDateTime); QVector column; column.resize(count); //TODO: warning (GCC8): writing to an object of type 'class QDateTime' with no trivial copy-assignment; use copy-assignment or copy-initialization instead //memcpy(column.data(), bytes.data(), count*sizeof(QDateTime)); static_cast>*>(d->data)->append(column); break; } } } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } //############################################################################## //######################## Data Import ####################################### //############################################################################## int Matrix::prepareImport(std::vector& dataContainer, AbstractFileFilter::ImportMode mode, int actualRows, int actualCols, QStringList colNameList, QVector columnMode) { QDEBUG("prepareImport() rows =" << actualRows << " cols =" << actualCols); - QDEBUG(" column modes = " << columnMode); + //QDEBUG(" column modes = " << columnMode); Q_UNUSED(colNameList); int columnOffset = 0; setUndoAware(false); setSuppressDataChangedSignal(true); // resize the matrix if (mode == AbstractFileFilter::Replace) { clear(); setDimensions(actualRows, actualCols); } else { if (rowCount() < actualRows) setDimensions(actualRows, actualCols); else setDimensions(rowCount(), actualCols); } // data() returns a void* which is a pointer to a matrix of any data type (see ColumnPrivate.cpp) dataContainer.resize(actualCols); switch (columnMode[0]) { // only columnMode[0] is used - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } - d->mode = AbstractColumn::Numeric; + d->mode = AbstractColumn::ColumnMode::Numeric; break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } - d->mode = AbstractColumn::Integer; + d->mode = AbstractColumn::ColumnMode::Integer; break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } - d->mode = AbstractColumn::BigInt; + d->mode = AbstractColumn::ColumnMode::BigInt; break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } - d->mode = AbstractColumn::Text; + d->mode = AbstractColumn::ColumnMode::Text; break; - case AbstractColumn::Day: - case AbstractColumn::Month: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::DateTime: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } - d->mode = AbstractColumn::DateTime; + d->mode = AbstractColumn::ColumnMode::DateTime; break; } return columnOffset; } void Matrix::finalizeImport(int columnOffset, int startColumn, int endColumn, const QString& dateTimeFormat, AbstractFileFilter::ImportMode importMode) { DEBUG("Matrix::finalizeImport()"); Q_UNUSED(columnOffset); Q_UNUSED(startColumn); Q_UNUSED(endColumn); Q_UNUSED(dateTimeFormat); Q_UNUSED(importMode); setSuppressDataChangedSignal(false); setChanged(); setUndoAware(true); DEBUG("Matrix::finalizeImport() DONE"); } diff --git a/src/backend/matrix/Matrix.h b/src/backend/matrix/Matrix.h index 6f3652447..3c1d262a0 100644 --- a/src/backend/matrix/Matrix.h +++ b/src/backend/matrix/Matrix.h @@ -1,165 +1,165 @@ /*************************************************************************** File : Matrix.h Project : Matrix Description : Spreadsheet with a MxN matrix data model -------------------------------------------------------------------- Copyright : (C) 2008-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2015-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 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 MATRIX_H #define MATRIX_H #include "backend/datasources/AbstractDataSource.h" #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/lib/macros.h" class MatrixPrivate; class MatrixModel; class MatrixView; class Matrix : public AbstractDataSource { Q_OBJECT Q_ENUMS(HeaderFormat) public: enum HeaderFormat {HeaderRowsColumns, HeaderValues, HeaderRowsColumnsValues}; explicit Matrix(const QString& name, bool loading = false, - const AbstractColumn::ColumnMode = AbstractColumn::Numeric); + const AbstractColumn::ColumnMode = AbstractColumn::ColumnMode::Numeric); Matrix(int rows, int cols, const QString& name, - const AbstractColumn::ColumnMode = AbstractColumn::Numeric); + const AbstractColumn::ColumnMode = AbstractColumn::ColumnMode::Numeric); ~Matrix() override; QIcon icon() const override; QMenu* createContextMenu() override; QWidget* view() const override; bool exportView() const override; bool printView() override; bool printPreview() const override; void* data() const; void setData(void*); BASIC_D_ACCESSOR_DECL(AbstractColumn::ColumnMode, mode, Mode) BASIC_D_ACCESSOR_DECL(int, rowCount, RowCount) BASIC_D_ACCESSOR_DECL(int, columnCount, ColumnCount) BASIC_D_ACCESSOR_DECL(char, numericFormat, NumericFormat) BASIC_D_ACCESSOR_DECL(int, precision, Precision) BASIC_D_ACCESSOR_DECL(HeaderFormat, headerFormat, HeaderFormat) BASIC_D_ACCESSOR_DECL(double, xStart, XStart) BASIC_D_ACCESSOR_DECL(double, xEnd, XEnd) BASIC_D_ACCESSOR_DECL(double, yStart, YStart) BASIC_D_ACCESSOR_DECL(double, yEnd, YEnd) CLASS_D_ACCESSOR_DECL(QString, formula, Formula) void setSuppressDataChangedSignal(bool); void setChanged(); int rowHeight(int row) const; void setRowHeight(int row, int height); int columnWidth(int col) const; void setColumnWidth(int col, int width); void setDimensions(int rows, int cols); void setCoordinates(double x1, double x2, double y1, double y2); void insertColumns(int before, int count); void appendColumns(int count); void removeColumns(int first, int count); void clearColumn(int); void insertRows(int before, int count); void appendRows(int count); void removeRows(int first, int count); void clearRow(int); template T cell(int row, int col) const; template QString text(int row, int col); template void setCell(int row, int col, T value); void clearCell(int row, int col); template QVector columnCells(int col, int first_row, int last_row); template void setColumnCells(int col, int first_row, int last_row, const QVector& values); template QVector rowCells(int row, int first_column, int last_column); template void setRowCells(int row, int first_column, int last_column, const QVector& values); void copy(Matrix* other); void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; int prepareImport(std::vector& dataContainer, AbstractFileFilter::ImportMode, int rows, int cols, QStringList colNameList, QVector) override; void finalizeImport(int columnOffset, int startColumn, int endColumn, const QString& dateTimeFormat, AbstractFileFilter::ImportMode) override; typedef MatrixPrivate Private; public slots: void clear(); void transpose(); void mirrorVertically(); void mirrorHorizontally(); void addColumns(); void addRows(); void duplicate(); signals: void requestProjectContextMenu(QMenu*); void columnsAboutToBeInserted(int before, int count); void columnsInserted(int first, int count); void columnsAboutToBeRemoved(int first, int count); void columnsRemoved(int first, int count); void rowsAboutToBeInserted(int before, int count); void rowsInserted(int first, int count); void rowsAboutToBeRemoved(int first, int count); void rowsRemoved(int first, int count); void dataChanged(int top, int left, int bottom, int right); void coordinatesChanged(); void rowCountChanged(int); void columnCountChanged(int); void xStartChanged(double); void xEndChanged(double); void yStartChanged(double); void yEndChanged(double); void numericFormatChanged(char); void precisionChanged(int); void headerFormatChanged(Matrix::HeaderFormat); private: void init(); MatrixPrivate* const d; mutable MatrixModel* m_model{nullptr}; mutable MatrixView* m_view{nullptr}; friend class MatrixPrivate; }; #endif diff --git a/src/backend/matrix/MatrixModel.cpp b/src/backend/matrix/MatrixModel.cpp index e331246d4..6544f132b 100644 --- a/src/backend/matrix/MatrixModel.cpp +++ b/src/backend/matrix/MatrixModel.cpp @@ -1,308 +1,308 @@ /*************************************************************************** File : MatrixModel.cpp Project : LabPlot Description : Matrix data model -------------------------------------------------------------------- Copyright : (C) 2015-2016 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2008-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2018-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/matrix/MatrixModel.h" #include "backend/matrix/Matrix.h" #include #include /*! \class MatrixModel \brief Model for the access to data of a Matrix-object. This is a model in the sense of Qt4 model/view framework which is used to access a Matrix object from any of Qt4s view classes, typically a QMatrixView. Its main purposes are translating Matrix signals into QAbstractItemModel signals and translating calls to the QAbstractItemModel read/write API into calls in the public API of Matrix. \ingroup backend */ MatrixModel::MatrixModel(Matrix* matrix) : QAbstractItemModel(nullptr), m_matrix(matrix) { connect(m_matrix, &Matrix::columnsAboutToBeInserted, this, &MatrixModel::handleColumnsAboutToBeInserted); connect(m_matrix, &Matrix::columnsInserted, this, &MatrixModel::handleColumnsInserted); connect(m_matrix, &Matrix::columnsAboutToBeRemoved, this, &MatrixModel::handleColumnsAboutToBeRemoved); connect(m_matrix, &Matrix::columnsRemoved, this, &MatrixModel::handleColumnsRemoved); connect(m_matrix, &Matrix::rowsAboutToBeInserted, this, &MatrixModel::handleRowsAboutToBeInserted); connect(m_matrix, &Matrix::rowsInserted, this, &MatrixModel::handleRowsInserted); connect(m_matrix, &Matrix::rowsAboutToBeRemoved, this, &MatrixModel::handleRowsAboutToBeRemoved); connect(m_matrix, &Matrix::rowsRemoved, this, &MatrixModel::handleRowsRemoved); connect(m_matrix, &Matrix::dataChanged, this, &MatrixModel::handleDataChanged); connect(m_matrix, &Matrix::coordinatesChanged, this, &MatrixModel::handleCoordinatesChanged); connect(m_matrix, &Matrix::numericFormatChanged, this, &MatrixModel::handleFormatChanged); connect(m_matrix, &Matrix::precisionChanged, this, &MatrixModel::handleFormatChanged); } void MatrixModel::setSuppressDataChangedSignal(bool b) { m_suppressDataChangedSignal = b; } void MatrixModel::setChanged() { emit changed(); } Qt::ItemFlags MatrixModel::flags(const QModelIndex& index) const { if (index.isValid()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; else return Qt::ItemIsEnabled; } QVariant MatrixModel::data(const QModelIndex& index, int role) const { if ( !index.isValid() ) return QVariant(); int row = index.row(); int col = index.column(); switch (role) { case Qt::ToolTipRole: case Qt::EditRole: case Qt::DisplayRole: { - AbstractColumn::ColumnMode mode = m_matrix->mode(); + auto mode = m_matrix->mode(); //DEBUG("MatrixModel::data() DisplayRole, mode = " << mode); switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: return QVariant(m_matrix->text(row, col)); - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: return QVariant(m_matrix->text(row, col)); - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: return QVariant(m_matrix->text(row, col)); - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: return QVariant(m_matrix->text(row, col)); - case AbstractColumn::Text: // should not happen + case AbstractColumn::ColumnMode::Text: // should not happen return QVariant(m_matrix->text(row, col)); default: - DEBUG(" unknown column mode " << mode << " found"); + DEBUG(" unknown column mode " << static_cast(mode) << " found"); break; } break; } case Qt::BackgroundRole: //use bluish background color to distinguish Matrix from Spreadsheet return QVariant(QBrush(QColor(192,255,255))); case Qt::ForegroundRole: //ignore current theme settings and always use black foreground color so Matrix is usable with dark themes, too. return QVariant(QBrush(QColor(Qt::black))); } return QVariant(); } QVariant MatrixModel::headerData(int section, Qt::Orientation orientation, int role) const { QString result; Matrix::HeaderFormat headerFormat = m_matrix->headerFormat(); switch (orientation) { case Qt::Horizontal: switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: if (headerFormat == Matrix::HeaderRowsColumns) { result = QString::number(section+1); } else if (headerFormat == Matrix::HeaderValues) { double diff = m_matrix->xEnd() - m_matrix->xStart(); double step = 0.0; if (m_matrix->columnCount() > 1) step = diff/double(m_matrix->columnCount()-1); result = QLocale().toString(m_matrix->xStart()+double(section)*step, m_matrix->numericFormat(), m_matrix->precision()); } else { result = QString::number(section+1) + QLatin1String(" ("); double diff = m_matrix->xEnd() - m_matrix->xStart(); double step = 0.0; if (m_matrix->columnCount() > 1) step = diff/double(m_matrix->columnCount()-1); result += QLocale().toString(m_matrix->xStart()+double(section)*step, m_matrix->numericFormat(), m_matrix->precision()); result += ')'; } return QVariant(result); } break; case Qt::Vertical: switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: if (headerFormat == Matrix::HeaderRowsColumns) { result = QString::number(section+1); } else if (headerFormat == Matrix::HeaderValues) { double diff = m_matrix->yEnd() - m_matrix->yStart(); double step = 0.0; if (m_matrix->rowCount() > 1) step = diff/double(m_matrix->rowCount()-1); // TODO: implement decent double == 0 check // if (diff < 1e-10) // result += QLocale().toString(m_matrix->yStart(), // m_matrix->numericFormat(), m_matrix->displayedDigits()); result += QLocale().toString(m_matrix->yStart()+double(section)*step, m_matrix->numericFormat(), m_matrix->precision()); } else { result = QString::number(section+1) + QString(" ("); double diff = m_matrix->yEnd() - m_matrix->yStart(); double step = 0.0; if (m_matrix->rowCount() > 1) step = diff/double(m_matrix->rowCount()-1); result += QLocale().toString(m_matrix->yStart()+double(section)*step, m_matrix->numericFormat(), m_matrix->precision()); result += ')'; } return QVariant(result); } } return QVariant(); } int MatrixModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent) return m_matrix->rowCount(); } int MatrixModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return m_matrix->columnCount(); } bool MatrixModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; int row = index.row(); int column = index.column(); if (role == Qt::EditRole) { - const AbstractColumn::ColumnMode mode = m_matrix->mode(); + const auto mode = m_matrix->mode(); switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: m_matrix->setCell(row, column, value.toDouble()); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: m_matrix->setCell(row, column, value.toInt()); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: m_matrix->setCell(row, column, value.toLongLong()); break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: DEBUG(" WARNING: DateTime format not supported yet"); // should not happen //TODO: m_matrix->setCell(row, column, value.toDateTime()); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: DEBUG(" WARNING: Text format not supported yet"); // should not happen m_matrix->setCell(row, column, value.toString()); break; default: - DEBUG(" Unsupported column mode " << mode); + DEBUG(" Unsupported column mode " << static_cast(mode)); break; } if (!m_suppressDataChangedSignal) emit changed(); return true; } return false; } QModelIndex MatrixModel::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent) return createIndex(row, column); } QModelIndex MatrixModel::parent(const QModelIndex& child) const { Q_UNUSED(child) return QModelIndex{}; } void MatrixModel::updateHeader() { emit headerDataChanged(Qt::Horizontal, 0, m_matrix->columnCount()); emit headerDataChanged(Qt::Vertical, 0, m_matrix->rowCount()); } void MatrixModel::handleColumnsAboutToBeInserted(int before, int count) { beginInsertColumns(QModelIndex(), before, before+count-1); } void MatrixModel::handleColumnsInserted(int first, int count) { Q_UNUSED(first) Q_UNUSED(count) endInsertColumns(); if (!m_suppressDataChangedSignal) emit changed(); } void MatrixModel::handleColumnsAboutToBeRemoved(int first, int count) { beginRemoveColumns(QModelIndex(), first, first+count-1); } void MatrixModel::handleColumnsRemoved(int first, int count) { Q_UNUSED(first) Q_UNUSED(count) endRemoveColumns(); if (!m_suppressDataChangedSignal) emit changed(); } void MatrixModel::handleRowsAboutToBeInserted(int before, int count) { beginInsertRows(QModelIndex(), before, before+count-1); } void MatrixModel::handleRowsInserted(int first, int count) { Q_UNUSED(first) Q_UNUSED(count) endInsertRows(); if (!m_suppressDataChangedSignal) emit changed(); } void MatrixModel::handleRowsAboutToBeRemoved(int first, int count) { beginRemoveRows(QModelIndex(), first, first+count-1); } void MatrixModel::handleRowsRemoved(int first, int count) { Q_UNUSED(first) Q_UNUSED(count) endRemoveRows(); if (!m_suppressDataChangedSignal) emit changed(); } void MatrixModel::handleDataChanged(int top, int left, int bottom, int right) { emit dataChanged(index(top, left), index(bottom, right)); if (!m_suppressDataChangedSignal) emit changed(); } void MatrixModel::handleCoordinatesChanged() { emit headerDataChanged(Qt::Horizontal, 0, columnCount()-1); emit headerDataChanged(Qt::Vertical, 0, rowCount()-1); } void MatrixModel::handleFormatChanged() { handleCoordinatesChanged(); handleDataChanged(0, 0, rowCount()-1, columnCount()-1); } diff --git a/src/backend/spreadsheet/Spreadsheet.cpp b/src/backend/spreadsheet/Spreadsheet.cpp index e837a1a43..5cd726840 100644 --- a/src/backend/spreadsheet/Spreadsheet.cpp +++ b/src/backend/spreadsheet/Spreadsheet.cpp @@ -1,1060 +1,1060 @@ /*************************************************************************** File : Spreadsheet.cpp Project : LabPlot Description : Aspect providing a spreadsheet table with column logic -------------------------------------------------------------------- Copyright : (C) 2006-2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2006-2009 Knut Franke (knut.franke@gmx.de) Copyright : (C) 2012-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-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 "Spreadsheet.h" #include "SpreadsheetModel.h" #include "backend/core/AspectPrivate.h" #include "backend/core/AbstractAspect.h" #include "backend/core/column/ColumnStringIO.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include #include #include #include #include /*! \class Spreadsheet \brief Aspect providing a spreadsheet table with column logic. Spreadsheet is a container object for columns with no data of its own. By definition, it's columns are all of its children inheriting from class Column. Thus, the basic API is already defined by AbstractAspect (managing the list of columns, notification of column insertion/removal) and Column (changing and monitoring state of the actual data). Spreadsheet stores a pointer to its primary view of class SpreadsheetView. SpreadsheetView calls the Spreadsheet API but Spreadsheet only notifies SpreadsheetView by signals without calling its API directly. This ensures a maximum independence of UI and backend. SpreadsheetView can be easily replaced by a different class. User interaction is completely handled in SpreadsheetView and translated into Spreadsheet API calls (e.g., when a user edits a cell this will be handled by the delegate of SpreadsheetView and Spreadsheet will not know whether a script or a user changed the data.). All actions, menus etc. for the user interaction are handled SpreadsheetView, e.g., via a context menu. Selections are also handled by SpreadsheetView. The view itself is created by the first call to view(); \ingroup backend */ Spreadsheet::Spreadsheet(const QString& name, bool loading, AspectType type) : AbstractDataSource(name, type) { if (!loading) init(); } /*! initializes the spreadsheet with the default number of columns and rows */ void Spreadsheet::init() { KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet")); const int columns = group.readEntry(QLatin1String("ColumnCount"), 2); const int rows = group.readEntry(QLatin1String("RowCount"), 100); for (int i = 0; i < columns; i++) { - Column* new_col = new Column(QString::number(i+1), AbstractColumn::Numeric); + Column* new_col = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric); new_col->setPlotDesignation(i == 0 ? AbstractColumn::PlotDesignation::X : AbstractColumn::PlotDesignation::Y); addChild(new_col); } setRowCount(rows); } void Spreadsheet::setModel(SpreadsheetModel* model) { m_model = model; } SpreadsheetModel* Spreadsheet::model() { return m_model; } /*! Constructs 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* Spreadsheet::view() const { if (!m_partView) { bool readOnly = (this->parentAspect()->type() == AspectType::DatapickerCurve); m_view = new SpreadsheetView(const_cast(this), readOnly); m_partView = m_view; } return m_partView; } bool Spreadsheet::exportView() const { return m_view->exportView(); } bool Spreadsheet::printView() { return m_view->printView(); } bool Spreadsheet::printPreview() const { return m_view->printPreview(); } /*! Returns the maximum number of rows in the spreadsheet. */ int Spreadsheet::rowCount() const { int result = 0; for (auto* col : children()) { const int col_rows = col->rowCount(); if ( col_rows > result) result = col_rows; } return result; } void Spreadsheet::removeRows(int first, int count) { if ( count < 1 || first < 0 || first+count > rowCount()) return; WAIT_CURSOR; beginMacro( i18np("%1: remove 1 row", "%1: remove %2 rows", name(), count) ); for (auto* col : children()) col->removeRows(first, count); endMacro(); RESET_CURSOR; } void Spreadsheet::insertRows(int before, int count) { if ( count < 1 || before < 0 || before > rowCount()) return; WAIT_CURSOR; beginMacro( i18np("%1: insert 1 row", "%1: insert %2 rows", name(), count) ); for (auto* col : children()) col->insertRows(before, count); endMacro(); RESET_CURSOR; } void Spreadsheet::appendRows(int count) { insertRows(rowCount(), count); } void Spreadsheet::appendRow() { insertRows(rowCount(), 1); } void Spreadsheet::appendColumns(int count) { insertColumns(columnCount(), count); } void Spreadsheet::appendColumn() { insertColumns(columnCount(), 1); } void Spreadsheet::prependColumns(int count) { insertColumns(0, count); } /*! Sets the number of rows of the spreadsheet to \c new_size */ void Spreadsheet::setRowCount(int new_size) { int current_size = rowCount(); if (new_size > current_size) insertRows(current_size, new_size-current_size); if (new_size < current_size && new_size >= 0) removeRows(new_size, current_size-new_size); } /*! Returns the column with the number \c index. Shallow wrapper around \sa AbstractAspect::child() - see there for caveat. */ Column* Spreadsheet::column(int index) const { return child(index); } /*! Returns the column with the name \c name. */ Column* Spreadsheet::column(const QString &name) const { return child(name); } /*! Returns the total number of columns in the spreadsheet. */ int Spreadsheet::columnCount() const { return childCount(); } /*! Returns the number of columns matching the given designation. */ int Spreadsheet::columnCount(AbstractColumn::PlotDesignation pd) const { int count = 0; for (auto* col : children()) if (col->plotDesignation() == pd) count++; return count; } void Spreadsheet::removeColumns(int first, int count) { if ( count < 1 || first < 0 || first+count > columnCount()) return; WAIT_CURSOR; beginMacro( i18np("%1: remove 1 column", "%1: remove %2 columns", name(), count) ); for (int i = 0; i < count; i++) child(first)->remove(); endMacro(); RESET_CURSOR; } void Spreadsheet::insertColumns(int before, int count) { WAIT_CURSOR; beginMacro( i18np("%1: insert 1 column", "%1: insert %2 columns", name(), count) ); Column * before_col = column(before); int rows = rowCount(); for (int i = 0; i < count; i++) { - Column * new_col = new Column(QString::number(i+1), AbstractColumn::Numeric); + Column * new_col = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric); new_col->setPlotDesignation(AbstractColumn::PlotDesignation::Y); new_col->insertRows(0, rows); insertChildBefore(new_col, before_col); } endMacro(); RESET_CURSOR; } /*! Sets the number of columns to \c new_size */ void Spreadsheet::setColumnCount(int new_size) { int old_size = columnCount(); if ( old_size == new_size || new_size < 0 ) return; if (new_size < old_size) removeColumns(new_size, old_size-new_size); else insertColumns(old_size, new_size-old_size); } /*! Clears the whole spreadsheet. */ void Spreadsheet::clear() { WAIT_CURSOR; beginMacro(i18n("%1: clear", name())); for (auto* col : children()) col->clear(); endMacro(); RESET_CURSOR; } /*! Clears all mask in the spreadsheet. */ void Spreadsheet::clearMasks() { WAIT_CURSOR; beginMacro(i18n("%1: clear all masks", name())); for (auto* col : children()) col->clearMasks(); endMacro(); RESET_CURSOR; } /*! Returns a new context menu. The caller takes ownership of the menu. */ QMenu* Spreadsheet::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); Q_ASSERT(menu); emit requestProjectContextMenu(menu); return menu; } void Spreadsheet::moveColumn(int from, int to) { Column* col = child(from); beginMacro(i18n("%1: move column %2 from position %3 to %4.", name(), col->name(), from+1, to+1)); col->remove(); insertChildBefore(col, child(to)); endMacro(); } void Spreadsheet::copy(Spreadsheet* other) { WAIT_CURSOR; beginMacro(i18n("%1: copy %2", name(), other->name())); for (auto* col : children()) col->remove(); for (auto* src_col : other->children()) { Column * new_col = new Column(src_col->name(), src_col->columnMode()); new_col->copy(src_col); new_col->setPlotDesignation(src_col->plotDesignation()); QVector< Interval > masks = src_col->maskedIntervals(); for (const auto& iv : masks) new_col->setMasked(iv); QVector< Interval > formulas = src_col->formulaIntervals(); for (const auto& iv : formulas) new_col->setFormula(iv, src_col->formula(iv.start())); new_col->setWidth(src_col->width()); addChild(new_col); } setComment(other->comment()); endMacro(); RESET_CURSOR; } // FIXME: replace index-based API with Column*-based one /*! Determines the corresponding X column. */ int Spreadsheet::colX(int col) { for (int i = col-1; i >= 0; i--) { if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::X) return i; } int cols = columnCount(); for (int i = col+1; i < cols; i++) { if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::X) return i; } return -1; } /*! Determines the corresponding Y column. */ int Spreadsheet::colY(int col) { int cols = columnCount(); if (column(col)->plotDesignation() == AbstractColumn::PlotDesignation::XError || column(col)->plotDesignation() == AbstractColumn::PlotDesignation::YError) { // look to the left first for (int i = col-1; i >= 0; i--) { if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y) return i; } for (int i = col+1; i < cols; i++) { if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y) return i; } } else { // look to the right first for (int i = col+1; i < cols; i++) { if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y) return i; } for (int i = col-1; i >= 0; i--) { if (column(i)->plotDesignation() == AbstractColumn::PlotDesignation::Y) return i; } } return -1; } /*! Sorts the given list of column. If 'leading' is a null pointer, each column is sorted separately. */ void Spreadsheet::sortColumns(Column* leading, const QVector& cols, bool ascending) { if (cols.isEmpty()) return; // the normal QPair comparison does not work properly with descending sorting // therefore we use our own compare functions class CompareFunctions { public: static bool doubleLess(QPair a, QPair b) { return a.first < b.first; } static bool doubleGreater(QPair a, QPair b) { return a.first > b.first; } static bool integerLess(QPair a, QPair b) { return a.first < b.first; } static bool integerGreater(QPair a, QPair b) { return a.first > b.first; } static bool bigIntLess(QPair a, QPair b) { return a.first < b.first; } static bool bigIntGreater(QPair a, QPair b) { return a.first > b.first; } static bool QStringLess(const QPair& a, const QPair& b) { return a < b; } static bool QStringGreater(const QPair& a, const QPair& b) { return a > b; } static bool QDateTimeLess(const QPair& a, const QPair& b) { return a < b; } static bool QDateTimeGreater(const QPair& a, const QPair& b) { return a > b; } }; WAIT_CURSOR; beginMacro(i18n("%1: sort columns", name())); if (leading == nullptr) { // sort separately for (auto* col : cols) { switch (col->columnMode()) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { int rows = col->rowCount(); QVector< QPair > map; for (int j = 0; j < rows; j++) map.append(QPair(col->valueAt(j), j)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleGreater); QVectorIterator< QPair > it(map); Column *temp_col = new Column("temp", col->columnMode()); int k = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, k, 1); temp_col->setMasked(col->isMasked(it.next().second)); k++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { int rows = col->rowCount(); QVector< QPair > map; for (int j = 0; j < rows; j++) map.append(QPair(col->valueAt(j), j)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleGreater); QVectorIterator> it(map); Column* temp_col = new Column("temp", col->columnMode()); int k = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, k, 1); temp_col->setMasked(col->isMasked(it.next().second)); k++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { int rows = col->rowCount(); QVector< QPair > map; for (int j = 0; j < rows; j++) map.append(QPair(col->valueAt(j), j)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleGreater); QVectorIterator> it(map); Column* temp_col = new Column("temp", col->columnMode()); int k = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, k, 1); temp_col->setMasked(col->isMasked(it.next().second)); k++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { int rows = col->rowCount(); QVector> map; for (int j = 0; j < rows; j++) map.append(QPair(col->textAt(j), j)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringGreater); QVectorIterator< QPair > it(map); Column* temp_col = new Column("temp", col->columnMode()); int k = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, k, 1); temp_col->setMasked(col->isMasked(it.next().second)); k++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; break; } - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: { + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: { int rows = col->rowCount(); QVector< QPair > map; for (int j = 0; j < rows; j++) map.append(QPair(col->dateTimeAt(j), j)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeGreater); QVectorIterator< QPair > it(map); Column *temp_col = new Column("temp", col->columnMode()); int k = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, k, 1); temp_col->setMasked(col->isMasked(it.next().second)); k++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; break; } } } } else { // sort with leading column switch (leading->columnMode()) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector> map; int rows = leading->rowCount(); for (int i = 0; i < rows; i++) map.append(QPair(leading->valueAt(i), i)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::doubleGreater); QVectorIterator> it(map); for (auto* col : cols) { Column *temp_col = new Column("temp", col->columnMode()); it.toFront(); int j = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, j, 1); temp_col->setMasked(col->isMasked(it.next().second)); j++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; } break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector> map; int rows = leading->rowCount(); for (int i = 0; i < rows; i++) map.append(QPair(leading->valueAt(i), i)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::integerLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::integerGreater); QVectorIterator> it(map); for (auto* col : cols) { Column *temp_col = new Column("temp", col->columnMode()); it.toFront(); int j = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, j, 1); temp_col->setMasked(col->isMasked(it.next().second)); j++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; } break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector> map; int rows = leading->rowCount(); for (int i = 0; i < rows; i++) map.append(QPair(leading->valueAt(i), i)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::bigIntGreater); QVectorIterator> it(map); for (auto* col : cols) { Column *temp_col = new Column("temp", col->columnMode()); it.toFront(); int j = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, j, 1); temp_col->setMasked(col->isMasked(it.next().second)); j++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; } break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector> map; int rows = leading->rowCount(); for (int i = 0; i < rows; i++) map.append(QPair(leading->textAt(i), i)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::QStringGreater); QVectorIterator> it(map); for (auto* col : cols) { Column *temp_col = new Column("temp", col->columnMode()); it.toFront(); int j = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, j, 1); temp_col->setMasked(col->isMasked(it.next().second)); j++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; } break; } - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: { + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: { QVector> map; int rows = leading->rowCount(); for (int i = 0; i < rows; i++) map.append(QPair(leading->dateTimeAt(i), i)); if (ascending) std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeLess); else std::stable_sort(map.begin(), map.end(), CompareFunctions::QDateTimeGreater); QVectorIterator> it(map); for (auto* col : cols) { Column *temp_col = new Column("temp", col->columnMode()); it.toFront(); int j = 0; // put the values in the right order into temp_col while (it.hasNext()) { temp_col->copy(col, it.peekNext().second, j, 1); temp_col->setMasked(col->isMasked(it.next().second)); j++; } // copy the sorted column col->copy(temp_col, 0, 0, rows); delete temp_col; } break; } } } endMacro(); RESET_CURSOR; } // end of sortColumns() /*! Returns an icon to be used for decorating my views. */ QIcon Spreadsheet::icon() const { return QIcon::fromTheme("labplot-spreadsheet"); } /*! Returns the text displayed in the given cell. */ QString Spreadsheet::text(int row, int col) const { Column* c = column(col); if (!c) return QString(); return c->asStringColumn()->textAt(row); } /*! * This slot is, indirectly, called when a child of \c Spreadsheet (i.e. column) was selected in \c ProjectExplorer. * Emits the signal \c columnSelected that is handled in \c SpreadsheetView. */ void Spreadsheet::childSelected(const AbstractAspect* aspect) { const Column* column = qobject_cast(aspect); if (column) { int index = indexOfChild(column); emit columnSelected(index); } } /*! * This slot is, indirectly, called when a child of \c Spreadsheet (i.e. column) was deselected in \c ProjectExplorer. * Emits the signal \c columnDeselected that is handled in \c SpreadsheetView. */ void Spreadsheet::childDeselected(const AbstractAspect* aspect) { const Column* column = qobject_cast(aspect); if (column) { int index = indexOfChild(column); emit columnDeselected(index); } } /*! * Emits the signal to select or to deselect the column number \c index 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 SpreadsheetView upon selection changes. */ void Spreadsheet::setColumnSelectedInView(int index, bool selected) { if (selected) { emit childAspectSelectedInView(child(index)); //deselect the spreadsheet in the project explorer, if a child (column) was selected //and also all possible parents like folder, workbook, datapicker curve, datapicker //to prevents unwanted multiple selection in the project explorer //if one of the parents of the selected column was also selected before. AbstractAspect* parent = this; while (parent) { emit childAspectDeselectedInView(parent); parent = parent->parentAspect(); } } else emit childAspectDeselectedInView(child(index)); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void Spreadsheet::save(QXmlStreamWriter* writer) const { writer->writeStartElement("spreadsheet"); writeBasicAttributes(writer); writeCommentElement(writer); //columns for (auto* col : children(ChildIndexFlag::IncludeHidden)) col->save(writer); writer->writeEndElement(); // "spreadsheet" } /*! Loads from XML. */ bool Spreadsheet::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; // read child elements while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement()) break; if (reader->isStartElement()) { if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "column") { Column* column = new Column(QString()); if (!column->load(reader, preview)) { delete column; setColumnCount(0); return false; } addChildFast(column); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } } return !reader->hasError(); } //############################################################################## //######################## Data Import ####################################### //############################################################################## int Spreadsheet::prepareImport(std::vector& dataContainer, AbstractFileFilter::ImportMode importMode, int actualRows, int actualCols, QStringList colNameList, QVector columnMode) { DEBUG("Spreadsheet::prepareImport()") DEBUG(" resize spreadsheet to rows = " << actualRows << " and cols = " << actualCols) QDEBUG(" column name list = " << colNameList) int columnOffset = 0; setUndoAware(false); if (m_model != nullptr) m_model->suppressSignals(true); //make the available columns undo unaware before we resize and rename them below, //the same will be done for new columns in this->resize(). for (int i = 0; i < childCount(); i++) child(i)->setUndoAware(false); columnOffset = this->resize(importMode, colNameList, actualCols); // resize the spreadsheet if (importMode == AbstractFileFilter::Replace) { clear(); setRowCount(actualRows); } else { if (rowCount() < actualRows) setRowCount(actualRows); } if (columnMode.size() < actualCols) { qWarning("columnMode[] size is too small! Giving up."); return -1; } dataContainer.resize(actualCols); for (int n = 0; n < actualCols; n++) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) Column* column = this->child(columnOffset+n); - DEBUG(" column " << n << " columnMode = " << columnMode[n]); + DEBUG(" column " << n << " columnMode = " << static_cast(columnMode[n])); column->setColumnModeFast(columnMode[n]); //in the most cases the first imported column is meant to be used as x-data. //Other columns provide mostly y-data or errors. //TODO: this has to be configurable for the user in the import widget, //it should be possible to specify x-error plot designation, etc. - AbstractColumn::PlotDesignation desig = (n == 0) ? AbstractColumn::PlotDesignation::X : AbstractColumn::PlotDesignation::Y; + auto desig = (n == 0) ? AbstractColumn::PlotDesignation::X : AbstractColumn::PlotDesignation::Y; column->setPlotDesignation(desig); switch (columnMode[n]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { auto* vector = static_cast*>(column->data()); vector->resize(actualRows); dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { auto* vector = static_cast*>(column->data()); vector->resize(actualRows); dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { auto* vector = static_cast*>(column->data()); vector->resize(actualRows); dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { auto* vector = static_cast*>(column->data()); vector->resize(actualRows); dataContainer[n] = static_cast(vector); break; } - case AbstractColumn::Month: - case AbstractColumn::Day: - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::DateTime: { auto* vector = static_cast* >(column->data()); vector->resize(actualRows); dataContainer[n] = static_cast(vector); break; } } } // QDEBUG("dataPointers =" << dataPointers); DEBUG("Spreadsheet::prepareImport() DONE"); return columnOffset; } /*! resize data source to cols columns returns column offset depending on import mode */ int Spreadsheet::resize(AbstractFileFilter::ImportMode mode, QStringList colNameList, int cols) { DEBUG("Spreadsheet::resize()") QDEBUG(" column name list = " << colNameList) // name additional columns for (int k = colNameList.size(); k < cols; k++ ) colNameList.append( "Column " + QString::number(k+1) ); int columnOffset = 0; //indexes the "start column" in the spreadsheet. Starting from this column the data will be imported. Column* newColumn = nullptr; if (mode == AbstractFileFilter::Append) { columnOffset = childCount(); for (int n = 0; n < cols; n++ ) { - newColumn = new Column(colNameList.at(n), AbstractColumn::Numeric); + newColumn = new Column(colNameList.at(n), AbstractColumn::ColumnMode::Numeric); newColumn->setUndoAware(false); addChildFast(newColumn); } } else if (mode == AbstractFileFilter::Prepend) { Column* firstColumn = child(0); for (int n = 0; n < cols; n++ ) { - newColumn = new Column(colNameList.at(n), AbstractColumn::Numeric); + newColumn = new Column(colNameList.at(n), AbstractColumn::ColumnMode::Numeric); newColumn->setUndoAware(false); insertChildBeforeFast(newColumn, firstColumn); } } else if (mode == AbstractFileFilter::Replace) { //replace completely the previous content of the data source with the content to be imported. int columns = childCount(); if (columns > cols) { //there're more columns in the data source then required -> remove the superfluous columns for (int i = 0; i < columns-cols; i++) removeChild(child(0)); } else { //create additional columns if needed for (int i = columns; i < cols; i++) { - newColumn = new Column(colNameList.at(i), AbstractColumn::Numeric); + newColumn = new Column(colNameList.at(i), AbstractColumn::ColumnMode::Numeric); newColumn->setUndoAware(false); addChildFast(newColumn); } } // 1. rename the columns that were already available // 2. suppress the dataChanged signal for all columns // 3. send aspectDescriptionChanged because otherwise the column // will not be connected again to the curves (project.cpp, descriptionChanged) for (int i = 0; i < childCount(); i++) { child(i)->setSuppressDataChangedSignal(true); emit child(i)->reset(child(i)); child(i)->setName(colNameList.at(i)); child(i)->aspectDescriptionChanged(child(i)); } } return columnOffset; } void Spreadsheet::finalizeImport(int columnOffset, int startColumn, int endColumn, const QString& dateTimeFormat, AbstractFileFilter::ImportMode importMode) { DEBUG("Spreadsheet::finalizeImport()"); //determine the dependent plots QVector plots; if (importMode == AbstractFileFilter::Replace) { for (int n = startColumn; n <= endColumn; n++) { Column* column = this->column(columnOffset + n - startColumn); column->addUsedInPlots(plots); } //suppress retransform in the dependent plots for (auto* plot : plots) plot->setSuppressDataChangedSignal(true); } // set the comments for each of the columns if datasource is a spreadsheet const int rows = rowCount(); for (int n = startColumn; n <= endColumn; n++) { Column* column = this->column(columnOffset + n - startColumn); - DEBUG(" column " << n << " of type " << column->columnMode()); + DEBUG(" column " << n << " of type " << static_cast(column->columnMode())); QString comment; switch (column->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: comment = i18np("numerical data, %1 element", "numerical data, %1 elements", rows); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: comment = i18np("integer data, %1 element", "integer data, %1 elements", rows); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: comment = i18np("big integer data, %1 element", "big integer data, %1 elements", rows); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: comment = i18np("text data, %1 element", "text data, %1 elements", rows); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: comment = i18np("month data, %1 element", "month data, %1 elements", rows); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: comment = i18np("day data, %1 element", "day data, %1 elements", rows); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: comment = i18np("date and time data, %1 element", "date and time data, %1 elements", rows); // set same datetime format in column auto* filter = static_cast(column->outputFilter()); filter->setFormat(dateTimeFormat); } column->setComment(comment); if (importMode == AbstractFileFilter::Replace) { column->setSuppressDataChangedSignal(false); column->setChanged(); } } if (importMode == AbstractFileFilter::Replace) { //retransform the dependent plots for (auto* plot : plots) { plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } } //make the spreadsheet and all its children undo aware again setUndoAware(true); for (int i = 0; i < childCount(); i++) child(i)->setUndoAware(true); if (m_model != nullptr) m_model->suppressSignals(false); if (m_partView != nullptr && m_view != nullptr) m_view->resizeHeader(); DEBUG("Spreadsheet::finalizeImport() DONE"); } diff --git a/src/backend/spreadsheet/SpreadsheetModel.cpp b/src/backend/spreadsheet/SpreadsheetModel.cpp index 47597ed18..1ef214b1e 100644 --- a/src/backend/spreadsheet/SpreadsheetModel.cpp +++ b/src/backend/spreadsheet/SpreadsheetModel.cpp @@ -1,523 +1,523 @@ /*************************************************************************** File : SpreadsheetModel.cpp Project : LabPlot Description : Model for the access to a Spreadsheet -------------------------------------------------------------------- Copyright : (C) 2007 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2009 Knut Franke (knut.franke@gmx.de) Copyright : (C) 2013-2017 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/spreadsheet/Spreadsheet.h" #include "backend/spreadsheet/SpreadsheetModel.h" #include "backend/core/datatypes/Double2StringFilter.h" #include #include #include #include /*! \class SpreadsheetModel \brief Model for the access to a Spreadsheet This is a model in the sense of Qt4 model/view framework which is used to access a Spreadsheet object from any of Qt4s view classes, typically a QTableView. Its main purposes are translating Spreadsheet signals into QAbstractItemModel signals and translating calls to the QAbstractItemModel read/write API into calls in the public API of Spreadsheet. In many cases a pointer to the addressed column is obtained by calling Spreadsheet::column() and the manipulation is done using the public API of column. \ingroup backend */ SpreadsheetModel::SpreadsheetModel(Spreadsheet* spreadsheet) : QAbstractItemModel(nullptr), m_spreadsheet(spreadsheet), m_rowCount(spreadsheet->rowCount()), m_columnCount(spreadsheet->columnCount()) { updateVerticalHeader(); updateHorizontalHeader(); connect(m_spreadsheet, &Spreadsheet::aspectAdded, this, &SpreadsheetModel::handleAspectAdded); connect(m_spreadsheet, &Spreadsheet::aspectAboutToBeRemoved, this, &SpreadsheetModel::handleAspectAboutToBeRemoved); connect(m_spreadsheet, &Spreadsheet::aspectRemoved, this, &SpreadsheetModel::handleAspectRemoved); connect(m_spreadsheet, &Spreadsheet::aspectDescriptionChanged, this, &SpreadsheetModel::handleDescriptionChange); for (int i = 0; i < spreadsheet->columnCount(); ++i) { beginInsertColumns(QModelIndex(), i, i); handleAspectAdded(spreadsheet->column(i)); } m_spreadsheet->setModel(this); } void SpreadsheetModel::suppressSignals(bool value) { m_suppressSignals = value; //update the headers after all the data was added to the model //and we start listening to signals again if (!m_suppressSignals) { m_rowCount = m_spreadsheet->rowCount(); m_columnCount = m_spreadsheet->columnCount(); m_spreadsheet->emitColumnCountChanged(); updateVerticalHeader(); updateHorizontalHeader(); beginResetModel(); endResetModel(); } } Qt::ItemFlags SpreadsheetModel::flags(const QModelIndex& index) const { if (index.isValid()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; else return Qt::ItemIsEnabled; } QVariant SpreadsheetModel::data(const QModelIndex& index, int role) const { if ( !index.isValid() ) return QVariant(); const int row = index.row(); const int col = index.column(); const Column* col_ptr = m_spreadsheet->column(col); if (!col_ptr) return QVariant(); switch (role) { case Qt::ToolTipRole: if (col_ptr->isValid(row)) { if (col_ptr->isMasked(row)) return QVariant(i18n("%1, masked (ignored in all operations)", col_ptr->asStringColumn()->textAt(row))); else return QVariant(col_ptr->asStringColumn()->textAt(row)); } else { if (col_ptr->isMasked(row)) return QVariant(i18n("invalid cell, masked (ignored in all operations)")); else return QVariant(i18n("invalid cell (ignored in all operations)")); } case Qt::EditRole: - if (col_ptr->columnMode() == AbstractColumn::Numeric) { + if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Numeric) { double value = col_ptr->valueAt(row); if (std::isnan(value)) return QVariant("-"); else if (std::isinf(value)) return QVariant(QLatin1String("inf")); else return QVariant(col_ptr->asStringColumn()->textAt(row)); } if (col_ptr->isValid(row)) return QVariant(col_ptr->asStringColumn()->textAt(row)); //m_formula_mode is not used at the moment //if (m_formula_mode) // return QVariant(col_ptr->formula(row)); break; case Qt::DisplayRole: - if (col_ptr->columnMode() == AbstractColumn::Numeric) { + if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Numeric) { double value = col_ptr->valueAt(row); if (std::isnan(value)) return QVariant("-"); else if (std::isinf(value)) return QVariant(UTF8_QSTRING("∞")); else return QVariant(col_ptr->asStringColumn()->textAt(row)); } if (!col_ptr->isValid(row)) return QVariant("-"); //m_formula_mode is not used at the moment //if (m_formula_mode) // return QVariant(col_ptr->formula(row)); return QVariant(col_ptr->asStringColumn()->textAt(row)); case Qt::ForegroundRole: if (!col_ptr->isValid(row)) return QVariant(QBrush(Qt::red)); break; case MaskingRole: return QVariant(col_ptr->isMasked(row)); case FormulaRole: return QVariant(col_ptr->formula(row)); // case Qt::DecorationRole: // if (m_formula_mode) // return QIcon(QPixmap(":/equals.png")); //TODO } return QVariant(); } QVariant SpreadsheetModel::headerData(int section, Qt::Orientation orientation, int role) const { if ( (orientation == Qt::Horizontal && section > m_columnCount-1) || (orientation == Qt::Vertical && section > m_rowCount-1) ) return QVariant(); switch (orientation) { case Qt::Horizontal: switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: case Qt::EditRole: return m_horizontal_header_data.at(section); case Qt::DecorationRole: return m_spreadsheet->child(section)->icon(); case SpreadsheetModel::CommentRole: return m_spreadsheet->child(section)->comment(); } break; case Qt::Vertical: switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: return m_vertical_header_data.at(section); } } return QVariant(); } int SpreadsheetModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent) return m_rowCount; } int SpreadsheetModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return m_columnCount; } bool SpreadsheetModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; int row = index.row(); Column* column = m_spreadsheet->column(index.column()); //don't do anything if no new value was provided - if (column->columnMode() == AbstractColumn::Numeric) { + if (column->columnMode() == AbstractColumn::ColumnMode::Numeric) { bool ok; QLocale locale; double new_value = locale.toDouble(value.toString(), &ok); if (ok) { if (column->valueAt(row) == new_value ) return false; } else { //an empty (non-numeric value) was provided if (std::isnan(column->valueAt(row))) return false; } } else { if (column->asStringColumn()->textAt(row) == value.toString()) return false; } switch (role) { case Qt::EditRole: { // remark: the validity of the cell is determined by the input filter if (m_formula_mode) column->setFormula(row, value.toString()); else column->asStringColumn()->setTextAt(row, value.toString()); return true; } case MaskingRole: { m_spreadsheet->column(index.column())->setMasked(row, value.toBool()); return true; } case FormulaRole: { m_spreadsheet->column(index.column())->setFormula(row, value.toString()); return true; } } return false; } QModelIndex SpreadsheetModel::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent) return createIndex(row, column); } QModelIndex SpreadsheetModel::parent(const QModelIndex& child) const { Q_UNUSED(child) return QModelIndex{}; } bool SpreadsheetModel::hasChildren(const QModelIndex& parent) const { Q_UNUSED(parent) return false; } void SpreadsheetModel::handleAspectAdded(const AbstractAspect* aspect) { const Column* col = dynamic_cast(aspect); if (!col || aspect->parentAspect() != m_spreadsheet) return; connect(col, &Column::plotDesignationChanged, this, &SpreadsheetModel::handlePlotDesignationChange); connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleDataChange); connect(col, &Column::dataChanged, this, &SpreadsheetModel::handleDataChange); connect(col, &Column::formatChanged, this, &SpreadsheetModel::handleDataChange); connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleModeChange); connect(col, &Column::rowsInserted, this, &SpreadsheetModel::handleRowsInserted); connect(col, &Column::rowsRemoved, this, &SpreadsheetModel::handleRowsRemoved); connect(col, &Column::maskingChanged, this, &SpreadsheetModel::handleDataChange); connect(col->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &SpreadsheetModel::handleDigitsChange); if (!m_suppressSignals) { beginResetModel(); updateVerticalHeader(); updateHorizontalHeader(); endResetModel(); m_columnCount = m_spreadsheet->columnCount(); m_spreadsheet->emitColumnCountChanged(); emit headerDataChanged(Qt::Horizontal, 0, m_columnCount-1); } } void SpreadsheetModel::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { if (m_suppressSignals) return; const Column* col = dynamic_cast(aspect); if (!col || aspect->parentAspect() != m_spreadsheet) return; beginResetModel(); disconnect(col, nullptr, this, nullptr); } void SpreadsheetModel::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(before) const Column* col = dynamic_cast(child); if (!col || parent != m_spreadsheet) return; updateVerticalHeader(); updateHorizontalHeader(); m_columnCount = m_spreadsheet->columnCount(); m_spreadsheet->emitColumnCountChanged(); endResetModel(); } void SpreadsheetModel::handleDescriptionChange(const AbstractAspect* aspect) { if (m_suppressSignals) return; const Column* col = dynamic_cast(aspect); if (!col || aspect->parentAspect() != m_spreadsheet) return; if (!m_suppressSignals) { updateHorizontalHeader(); int index = m_spreadsheet->indexOfChild(col); emit headerDataChanged(Qt::Horizontal, index, index); } } void SpreadsheetModel::handleModeChange(const AbstractColumn* col) { if (m_suppressSignals) return; updateHorizontalHeader(); int index = m_spreadsheet->indexOfChild(col); emit headerDataChanged(Qt::Horizontal, index, index); handleDataChange(col); //output filter was changed after the mode change, update the signal-slot connection disconnect(nullptr, SIGNAL(digitsChanged()), this, SLOT(handledigitsChange())); connect(static_cast(col)->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &SpreadsheetModel::handleDigitsChange); } void SpreadsheetModel::handleDigitsChange() { if (m_suppressSignals) return; const auto* filter = dynamic_cast(QObject::sender()); if (!filter) return; const AbstractColumn* col = filter->output(0); handleDataChange(col); } void SpreadsheetModel::handlePlotDesignationChange(const AbstractColumn* col) { if (m_suppressSignals) return; updateHorizontalHeader(); int index = m_spreadsheet->indexOfChild(col); emit headerDataChanged(Qt::Horizontal, index, m_columnCount-1); } void SpreadsheetModel::handleDataChange(const AbstractColumn* col) { if (m_suppressSignals) return; int i = m_spreadsheet->indexOfChild(col); emit dataChanged(index(0, i), index(m_rowCount-1, i)); } void SpreadsheetModel::handleRowsInserted(const AbstractColumn* col, int before, int count) { if (m_suppressSignals) return; Q_UNUSED(before) Q_UNUSED(count) updateVerticalHeader(); int i = m_spreadsheet->indexOfChild(col); m_rowCount = col->rowCount(); emit dataChanged(index(0, i), index(m_rowCount-1, i)); m_spreadsheet->emitRowCountChanged(); } void SpreadsheetModel::handleRowsRemoved(const AbstractColumn* col, int first, int count) { if (m_suppressSignals) return; Q_UNUSED(first) Q_UNUSED(count) updateVerticalHeader(); int i = m_spreadsheet->indexOfChild(col); m_rowCount = col->rowCount(); emit dataChanged(index(0, i), index(m_rowCount-1, i)); m_spreadsheet->emitRowCountChanged(); } void SpreadsheetModel::updateVerticalHeader() { int old_rows = m_vertical_header_data.size(); int new_rows = m_rowCount; if (new_rows > old_rows) { beginInsertRows(QModelIndex(), old_rows, new_rows-1); for (int i = old_rows+1; i <= new_rows; i++) m_vertical_header_data << i; endInsertRows(); } else if (new_rows < old_rows) { beginRemoveRows(QModelIndex(), new_rows, old_rows-1); while (m_vertical_header_data.size() > new_rows) m_vertical_header_data.removeLast(); endRemoveRows(); } } void SpreadsheetModel::updateHorizontalHeader() { int column_count = m_spreadsheet->childCount(); while (m_horizontal_header_data.size() < column_count) m_horizontal_header_data << QString(); while (m_horizontal_header_data.size() > column_count) m_horizontal_header_data.removeLast(); for (int i = 0; i < column_count; i++) { Column* col = m_spreadsheet->child(i); QString type; switch (col->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: type = QLatin1String(" {") + i18n("Numeric") + QLatin1Char('}'); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: type = QLatin1String(" {") + i18n("Integer") + QLatin1Char('}'); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: type = QLatin1String(" {") + i18n("Big Integer") + QLatin1Char('}'); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: type = QLatin1String(" {") + i18n("Text") + QLatin1Char('}'); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: type = QLatin1String(" {") + i18n("Month Names") + QLatin1Char('}'); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: type = QLatin1String(" {") + i18n("Day Names") + QLatin1Char('}'); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: type = QLatin1String(" {") + i18n("Date and Time") + QLatin1Char('}'); break; } QString designation; switch (col->plotDesignation()) { case AbstractColumn::PlotDesignation::NoDesignation: break; case AbstractColumn::PlotDesignation::X: designation = QLatin1String(" [X]"); break; case AbstractColumn::PlotDesignation::Y: designation = QLatin1String(" [Y]"); break; case AbstractColumn::PlotDesignation::Z: designation = QLatin1String(" [Z]"); break; case AbstractColumn::PlotDesignation::XError: designation = QLatin1String(" [") + i18n("X-error") + QLatin1Char(']'); break; case AbstractColumn::PlotDesignation::XErrorPlus: designation = QLatin1String(" [") + i18n("X-error +") + QLatin1Char(']'); break; case AbstractColumn::PlotDesignation::XErrorMinus: designation = QLatin1String(" [") + i18n("X-error -") + QLatin1Char(']'); break; case AbstractColumn::PlotDesignation::YError: designation = QLatin1String(" [") + i18n("Y-error") + QLatin1Char(']'); break; case AbstractColumn::PlotDesignation::YErrorPlus: designation = QLatin1String(" [") + i18n("Y-error +") + QLatin1Char(']'); break; case AbstractColumn::PlotDesignation::YErrorMinus: designation = QLatin1String(" [") + i18n("Y-error -") + QLatin1Char(']'); break; } m_horizontal_header_data.replace(i, col->name() + type + designation); } } Column* SpreadsheetModel::column(int index) { return m_spreadsheet->column(index); } void SpreadsheetModel::activateFormulaMode(bool on) { if (m_formula_mode == on) return; m_formula_mode = on; if (m_rowCount > 0 && m_columnCount > 0) emit dataChanged(index(0,0), index(m_rowCount - 1, m_columnCount - 1)); } bool SpreadsheetModel::formulaModeActive() const { return m_formula_mode; } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp index 15010c9f5..2bd0abcbc 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::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->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); 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); 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); //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::DateTime) { + 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::DateTime) { + 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::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { + 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::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { + 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) 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); 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) { 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/Histogram.cpp b/src/backend/worksheet/plots/cartesian/Histogram.cpp index 77bf1f1f3..8b614eb57 100644 --- a/src/backend/worksheet/plots/cartesian/Histogram.cpp +++ b/src/backend/worksheet/plots/cartesian/Histogram.cpp @@ -1,1839 +1,1839 @@ /*************************************************************************** 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->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->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->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->valuesRotationAngle = group.readEntry("ValuesRotation", 0.0); d->valuesOpacity = group.readEntry("ValuesOpacity", 1.0); 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->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->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->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, 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"))); } //TODO: Format, Precision 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 AbstractColumn::ColumnMode xColMode = valuesColumn->columnMode(); + 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::Numeric: + case AbstractColumn::ColumnMode::Numeric: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix; break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix; - case AbstractColumn::Integer: - case AbstractColumn::BigInt: - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: //TODO 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/XYAnalysisCurve.cpp b/src/backend/worksheet/plots/cartesian/XYAnalysisCurve.cpp index be1e6799e..86343929d 100644 --- a/src/backend/worksheet/plots/cartesian/XYAnalysisCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYAnalysisCurve.cpp @@ -1,354 +1,354 @@ /*************************************************************************** File : XYAnalysisCurve.h Project : LabPlot Description : Base class for all analysis curves -------------------------------------------------------------------- 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 * * * ***************************************************************************/ /*! \class XYAnalysisCurve \brief Base class for all analysis curves \ingroup worksheet */ #include "XYAnalysisCurve.h" #include "XYAnalysisCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include #include XYAnalysisCurve::XYAnalysisCurve(const QString& name, AspectType type) : XYCurve(name, new XYAnalysisCurvePrivate(this), type) { init(); } XYAnalysisCurve::XYAnalysisCurve(const QString& name, XYAnalysisCurvePrivate* dd, AspectType type) : XYCurve(name, dd, type) { init(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYAnalysisCurve::~XYAnalysisCurve() = default; void XYAnalysisCurve::init() { Q_D(XYAnalysisCurve); d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYAnalysisCurve::copyData(QVector& xData, QVector& yData, const AbstractColumn* xDataColumn, const AbstractColumn* yDataColumn, double xMin, double xMax) { int rowCount = qMin(xDataColumn->rowCount(), yDataColumn->rowCount()); for (int row = 0; row < rowCount; ++row) { if (!xDataColumn->isValid(row) || xDataColumn->isMasked(row) || !yDataColumn->isValid(row) || yDataColumn->isMasked(row)) continue; double x = NAN; switch (xDataColumn->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: x = xDataColumn->valueAt(row); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: x = xDataColumn->integerAt(row); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: x = xDataColumn->bigIntAt(row); break; - case AbstractColumn::Text: // invalid + case AbstractColumn::ColumnMode::Text: // invalid break; - case AbstractColumn::DateTime: - case AbstractColumn::Day: - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: x = xDataColumn->dateTimeAt(row).toMSecsSinceEpoch(); } double y = NAN; switch (yDataColumn->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: y = yDataColumn->valueAt(row); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: y = yDataColumn->integerAt(row); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: y = yDataColumn->bigIntAt(row); break; - case AbstractColumn::Text: // invalid + case AbstractColumn::ColumnMode::Text: // invalid break; - case AbstractColumn::DateTime: - case AbstractColumn::Day: - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: y = yDataColumn->dateTimeAt(row).toMSecsSinceEpoch(); } // only when inside given range if (x >= xMin && x <= xMax) { xData.append(x); yData.append(y); } } } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, XYAnalysisCurve::DataSourceType, dataSourceType, dataSourceType) BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const XYCurve*, dataSourceCurve, dataSourceCurve) const QString& XYAnalysisCurve::dataSourceCurvePath() const { return d_ptr->dataSourceCurvePath; } BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, yDataColumn, yDataColumn) BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, y2DataColumn, y2DataColumn) CLASS_SHARED_D_READER_IMPL(XYAnalysisCurve, QString, xDataColumnPath, xDataColumnPath) CLASS_SHARED_D_READER_IMPL(XYAnalysisCurve, QString, yDataColumnPath, yDataColumnPath) CLASS_SHARED_D_READER_IMPL(XYAnalysisCurve, QString, y2DataColumnPath, y2DataColumnPath) //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetDataSourceType, XYAnalysisCurve::DataSourceType, dataSourceType) void XYAnalysisCurve::setDataSourceType(DataSourceType type) { Q_D(XYAnalysisCurve); if (type != d->dataSourceType) exec(new XYAnalysisCurveSetDataSourceTypeCmd(d, type, ki18n("%1: data source type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYAnalysisCurve, SetDataSourceCurve, const XYCurve*, dataSourceCurve, retransform) void XYAnalysisCurve::setDataSourceCurve(const XYCurve* curve) { Q_D(XYAnalysisCurve); if (curve != d->dataSourceCurve) { exec(new XYAnalysisCurveSetDataSourceCurveCmd(d, curve, ki18n("%1: data source curve changed"))); handleSourceDataChanged(); //handle the changes when different columns were provided for the source curve connect(curve, SIGNAL(xColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); connect(curve, SIGNAL(yColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO? connect(curve, SIGNAL(y2ColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //handle the changes when the data inside of the source curve columns connect(curve, &XYCurve::xDataChanged, this, &XYAnalysisCurve::handleSourceDataChanged); connect(curve, &XYCurve::yDataChanged, this, &XYAnalysisCurve::handleSourceDataChanged); //TODO: add disconnect in the undo-function } } STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYAnalysisCurve::setXDataColumn(const AbstractColumn* column) { DEBUG("XYAnalysisCurve::setXDataColumn()"); Q_D(XYAnalysisCurve); if (column != d->xDataColumn) { exec(new XYAnalysisCurveSetXDataColumnCmd(d, column, ki18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { setXDataColumnPath(column->path()); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &XYAnalysisCurve::xDataColumnAboutToBeRemoved); connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); connect(column, &AbstractAspect::aspectDescriptionChanged, this, &XYAnalysisCurve::xDataColumnNameChanged); //TODO disconnect on undo } else setXDataColumnPath(""); } } STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYAnalysisCurve::setYDataColumn(const AbstractColumn* column) { DEBUG("XYAnalysisCurve::setYDataColumn()"); Q_D(XYAnalysisCurve); if (column != d->yDataColumn) { exec(new XYAnalysisCurveSetYDataColumnCmd(d, column, ki18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { setYDataColumnPath(column->path()); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &XYAnalysisCurve::yDataColumnAboutToBeRemoved); connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); connect(column, &AbstractAspect::aspectDescriptionChanged, this, &XYAnalysisCurve::yDataColumnNameChanged); //TODO disconnect on undo } else setXDataColumnPath(""); } } STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetY2DataColumn, const AbstractColumn*, y2DataColumn) void XYAnalysisCurve::setY2DataColumn(const AbstractColumn* column) { DEBUG("XYAnalysisCurve::setY2DataColumn()"); Q_D(XYAnalysisCurve); if (column != d->y2DataColumn) { exec(new XYAnalysisCurveSetY2DataColumnCmd(d, column, ki18n("%1: assign second y-data"))); handleSourceDataChanged(); if (column) { setY2DataColumnPath(column->path()); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &XYAnalysisCurve::y2DataColumnAboutToBeRemoved); connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); connect(column, &AbstractAspect::aspectDescriptionChanged, this, &XYAnalysisCurve::y2DataColumnNameChanged); //TODO disconnect on undo } else setXDataColumnPath(""); } } void XYAnalysisCurve::setXDataColumnPath(const QString& path) { Q_D(XYAnalysisCurve); d->xDataColumnPath = path; } void XYAnalysisCurve::setYDataColumnPath(const QString& path) { Q_D(XYAnalysisCurve); d->yDataColumnPath = path; } void XYAnalysisCurve::setY2DataColumnPath(const QString& path) { Q_D(XYAnalysisCurve); d->y2DataColumnPath = path; } //############################################################################## //################################# SLOTS #################################### //############################################################################## void XYAnalysisCurve::handleSourceDataChanged() { Q_D(XYAnalysisCurve); d->sourceDataChangedSinceLastRecalc = true; emit sourceDataChanged(); } void XYAnalysisCurve::xDataColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYAnalysisCurve); if (aspect == d->xDataColumn) { d->xDataColumn = nullptr; d->retransform(); } } void XYAnalysisCurve::yDataColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYAnalysisCurve); if (aspect == d->yDataColumn) { d->yDataColumn = nullptr; d->retransform(); } } void XYAnalysisCurve::y2DataColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYAnalysisCurve); if (aspect == d->y2DataColumn) { d->y2DataColumn = nullptr; d->retransform(); } } void XYAnalysisCurve::xDataColumnNameChanged() { Q_D(XYAnalysisCurve); setXDataColumnPath(d->xDataColumn->path()); } void XYAnalysisCurve::yDataColumnNameChanged() { Q_D(XYAnalysisCurve); setYDataColumnPath(d->yDataColumn->path()); } void XYAnalysisCurve::y2DataColumnNameChanged() { Q_D(XYAnalysisCurve); setYDataColumnPath(d->y2DataColumn->path()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYAnalysisCurvePrivate::XYAnalysisCurvePrivate(XYAnalysisCurve* owner) : XYCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYAnalysisCurvePrivate::~XYAnalysisCurvePrivate() = default; //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYAnalysisCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYAnalysisCurve); writer->writeStartElement("xyAnalysisCurve"); //write xy-curve information XYCurve::save(writer); //write data source specific information writer->writeStartElement("dataSource"); writer->writeAttribute("type", QString::number(d->dataSourceType)); WRITE_PATH(d->dataSourceCurve, dataSourceCurve); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); WRITE_COLUMN(d->y2DataColumn, y2DataColumn); writer->writeEndElement(); writer->writeEndElement(); //"xyAnalysisCurve" } //! Load from XML bool XYAnalysisCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYAnalysisCurve); 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() == "xyAnalysisCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (reader->name() == "dataSource") { attribs = reader->attributes(); READ_INT_VALUE("type", dataSourceType, XYAnalysisCurve::DataSourceType); READ_PATH(dataSourceCurve); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_COLUMN(y2DataColumn); } } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYConvolutionCurve.cpp b/src/backend/worksheet/plots/cartesian/XYConvolutionCurve.cpp index f28a9d151..4207199c8 100644 --- a/src/backend/worksheet/plots/cartesian/XYConvolutionCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYConvolutionCurve.cpp @@ -1,399 +1,399 @@ /*************************************************************************** File : XYConvolutionCurve.cpp Project : LabPlot Description : A xy-curve defined by a convolution -------------------------------------------------------------------- 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 * * * ***************************************************************************/ /*! \class XYConvolutionCurve \brief A xy-curve defined by a convolution \ingroup worksheet */ #include "XYConvolutionCurve.h" #include "XYConvolutionCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include #include #include #include extern "C" { #include } XYConvolutionCurve::XYConvolutionCurve(const QString& name) : XYAnalysisCurve(name, new XYConvolutionCurvePrivate(this), AspectType::XYConvolution) { } XYConvolutionCurve::XYConvolutionCurve(const QString& name, XYConvolutionCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYConvolution) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYConvolutionCurve::~XYConvolutionCurve() = default; void XYConvolutionCurve::recalculate() { Q_D(XYConvolutionCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYConvolutionCurve::icon() const { return QIcon::fromTheme("labplot-xy-convolution-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYConvolutionCurve, XYConvolutionCurve::ConvolutionData, convolutionData, convolutionData) const XYConvolutionCurve::ConvolutionResult& XYConvolutionCurve::convolutionResult() const { Q_D(const XYConvolutionCurve); return d->convolutionResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYConvolutionCurve, SetConvolutionData, XYConvolutionCurve::ConvolutionData, convolutionData, recalculate); void XYConvolutionCurve::setConvolutionData(const XYConvolutionCurve::ConvolutionData& convolutionData) { Q_D(XYConvolutionCurve); exec(new XYConvolutionCurveSetConvolutionDataCmd(d, convolutionData, ki18n("%1: set options and perform the convolution"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYConvolutionCurvePrivate::XYConvolutionCurvePrivate(XYConvolutionCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYConvolutionCurvePrivate::~XYConvolutionCurvePrivate() = default; void XYConvolutionCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create convolution result columns if not available yet, clear them otherwise if (!xColumn) { - xColumn = new Column("x", AbstractColumn::Numeric); - yColumn = new Column("y", AbstractColumn::Numeric); + xColumn = new Column("x", AbstractColumn::ColumnMode::Numeric); + yColumn = new Column("y", AbstractColumn::ColumnMode::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } //clear the previous result convolutionResult = XYConvolutionCurve::ConvolutionResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; const AbstractColumn* tmpY2DataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; tmpY2DataColumn = y2DataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); // no y2 column: use standard kernel } if (tmpYDataColumn == nullptr) { recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the convolution to temporary vectors QVector xdataVector; QVector ydataVector; QVector y2dataVector; double xmin, xmax; if (tmpXDataColumn != nullptr && convolutionData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = convolutionData.xRange.first(); xmax = convolutionData.xRange.last(); } //only copy those data where values are valid and in range if (tmpXDataColumn != nullptr) { // x-axis present (with possible range) for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) { if (tmpXDataColumn->isValid(row) && !tmpXDataColumn->isMasked(row) && tmpYDataColumn->isValid(row) && !tmpYDataColumn->isMasked(row)) { if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } } else { // no x-axis: take all valid values for (int row = 0; row < tmpYDataColumn->rowCount(); ++row) if (tmpYDataColumn->isValid(row) && !tmpYDataColumn->isMasked(row)) ydataVector.append(tmpYDataColumn->valueAt(row)); } const nsl_conv_kernel_type kernel = convolutionData.kernel; const size_t kernelSize = convolutionData.kernelSize; if (tmpY2DataColumn != nullptr) { for (int row = 0; row < tmpY2DataColumn->rowCount(); ++row) if (tmpY2DataColumn->isValid(row) && !tmpY2DataColumn->isMasked(row)) y2dataVector.append(tmpY2DataColumn->valueAt(row)); DEBUG("kernel = given response"); } else { DEBUG("kernel = " << nsl_conv_kernel_name[kernel] << ", size = " << kernelSize); double* k = new double[kernelSize]; nsl_conv_standard_kernel(k, kernelSize, kernel); for (size_t i = 0; i < kernelSize; i++) y2dataVector.append(k[i]); delete[] k; } const size_t n = (size_t)ydataVector.size(); // number of points for signal const size_t m = (size_t)y2dataVector.size(); // number of points for response if (n < 1 || m < 1) { convolutionResult.available = true; convolutionResult.valid = false; convolutionResult.status = i18n("Not enough data points available."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* y2data = y2dataVector.data(); // convolution settings const double samplingInterval = convolutionData.samplingInterval; const nsl_conv_direction_type direction = convolutionData.direction; const nsl_conv_type_type type = convolutionData.type; const nsl_conv_method_type method = convolutionData.method; const nsl_conv_norm_type norm = convolutionData.normalize; const nsl_conv_wrap_type wrap = convolutionData.wrap; DEBUG("signal n = " << n << ", response m = " << m); DEBUG("sampling interval = " << samplingInterval); DEBUG("direction = " << nsl_conv_direction_name[direction]); DEBUG("type = " << nsl_conv_type_name[type]); DEBUG("method = " << nsl_conv_method_name[method]); DEBUG("norm = " << nsl_conv_norm_name[norm]); DEBUG("wrap = " << nsl_conv_wrap_name[wrap]); /////////////////////////////////////////////////////////// size_t np; if (type == nsl_conv_type_linear) np = n + m - 1; else np = GSL_MAX(n, m); double* out = (double*)malloc(np * sizeof(double)); int status = nsl_conv_convolution_direction(ydata, n, y2data, m, direction, type, method, norm, wrap, out); if (direction == nsl_conv_direction_backward) if (type == nsl_conv_type_linear) np = abs((int)(n - m)) + 1; xVector->resize((int)np); yVector->resize((int)np); // take given x-axis values or use index if (tmpXDataColumn != nullptr) { int size = GSL_MIN(xdataVector.size(), (int)np); memcpy(xVector->data(), xdata, size * sizeof(double)); double sampleInterval = (xVector->data()[size-1] - xVector->data()[0])/(xdataVector.size()-1); DEBUG("xdata size = " << xdataVector.size() << ", np = " << np << ", sample interval = " << sampleInterval); for (int i = size; i < (int)np; i++) // fill missing values xVector->data()[i] = xVector->data()[size-1] + (i-size+1) * sampleInterval; } else { // fill with index (starting with 0) for (size_t i = 0; i < np; i++) xVector->data()[i] = i * samplingInterval; } memcpy(yVector->data(), out, np * sizeof(double)); free(out); /////////////////////////////////////////////////////////// //write the result convolutionResult.available = true; convolutionResult.valid = true; convolutionResult.status = QString::number(status); convolutionResult.elapsedTime = timer.elapsed(); //redraw the curve recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYConvolutionCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYConvolutionCurve); writer->writeStartElement("xyConvolutionCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-convolution-curve specific information // convolution data writer->writeStartElement("convolutionData"); writer->writeAttribute( "samplingInterval", QString::number(d->convolutionData.samplingInterval) ); writer->writeAttribute( "kernel", QString::number(d->convolutionData.kernel) ); writer->writeAttribute( "kernelSize", QString::number(d->convolutionData.kernelSize) ); writer->writeAttribute( "autoRange", QString::number(d->convolutionData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->convolutionData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->convolutionData.xRange.last()) ); writer->writeAttribute( "direction", QString::number(d->convolutionData.direction) ); writer->writeAttribute( "type", QString::number(d->convolutionData.type) ); writer->writeAttribute( "method", QString::number(d->convolutionData.method) ); writer->writeAttribute( "normalize", QString::number(d->convolutionData.normalize) ); writer->writeAttribute( "wrap", QString::number(d->convolutionData.wrap) ); writer->writeEndElement();// convolutionData // convolution results (generated columns) writer->writeStartElement("convolutionResult"); writer->writeAttribute( "available", QString::number(d->convolutionResult.available) ); writer->writeAttribute( "valid", QString::number(d->convolutionResult.valid) ); writer->writeAttribute( "status", d->convolutionResult.status ); writer->writeAttribute( "time", QString::number(d->convolutionResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"convolutionResult" writer->writeEndElement(); //"xyConvolutionCurve" } //! Load from XML bool XYConvolutionCurve::load(XmlStreamReader* reader, bool preview) { DEBUG("XYConvolutionCurve::load()"); Q_D(XYConvolutionCurve); 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() == "xyConvolutionCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "convolutionData") { attribs = reader->attributes(); READ_DOUBLE_VALUE("samplingInterval", convolutionData.samplingInterval); READ_INT_VALUE("kernel", convolutionData.kernel, nsl_conv_kernel_type); READ_INT_VALUE("kernelSize", convolutionData.kernelSize, size_t); READ_INT_VALUE("autoRange", convolutionData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", convolutionData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", convolutionData.xRange.last()); READ_INT_VALUE("direction", convolutionData.direction, nsl_conv_direction_type); READ_INT_VALUE("type", convolutionData.type, nsl_conv_type_type); READ_INT_VALUE("method", convolutionData.method, nsl_conv_method_type); READ_INT_VALUE("normalize", convolutionData.normalize, nsl_conv_norm_type); READ_INT_VALUE("wrap", convolutionData.wrap, nsl_conv_wrap_type); } else if (!preview && reader->name() == "convolutionResult") { attribs = reader->attributes(); READ_INT_VALUE("available", convolutionResult.available, int); READ_INT_VALUE("valid", convolutionResult.valid, int); READ_STRING_VALUE("status", convolutionResult.status); READ_INT_VALUE("time", convolutionResult.elapsedTime, int); } else if (!preview && reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYCorrelationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCorrelationCurve.cpp index 00d787184..da28ea1f6 100644 --- a/src/backend/worksheet/plots/cartesian/XYCorrelationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCorrelationCurve.cpp @@ -1,372 +1,372 @@ /*************************************************************************** File : XYCorrelationCurve.cpp Project : LabPlot Description : A xy-curve defined by a correlation -------------------------------------------------------------------- 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 * * * ***************************************************************************/ /*! \class XYCorrelationCurve \brief A xy-curve defined by a correlation \ingroup worksheet */ #include "XYCorrelationCurve.h" #include "XYCorrelationCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include #include #include #include extern "C" { #include } XYCorrelationCurve::XYCorrelationCurve(const QString& name) : XYAnalysisCurve(name, new XYCorrelationCurvePrivate(this), AspectType::XYCorrelationCurve) { } XYCorrelationCurve::XYCorrelationCurve(const QString& name, XYCorrelationCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYCorrelationCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYCorrelationCurve::~XYCorrelationCurve() = default; void XYCorrelationCurve::recalculate() { Q_D(XYCorrelationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYCorrelationCurve::icon() const { return QIcon::fromTheme("labplot-xy-correlation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYCorrelationCurve, XYCorrelationCurve::CorrelationData, correlationData, correlationData) const XYCorrelationCurve::CorrelationResult& XYCorrelationCurve::correlationResult() const { Q_D(const XYCorrelationCurve); return d->correlationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYCorrelationCurve, SetCorrelationData, XYCorrelationCurve::CorrelationData, correlationData, recalculate); void XYCorrelationCurve::setCorrelationData(const XYCorrelationCurve::CorrelationData& correlationData) { Q_D(XYCorrelationCurve); exec(new XYCorrelationCurveSetCorrelationDataCmd(d, correlationData, ki18n("%1: set options and perform the correlation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYCorrelationCurvePrivate::XYCorrelationCurvePrivate(XYCorrelationCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYCorrelationCurvePrivate::~XYCorrelationCurvePrivate() = default; void XYCorrelationCurvePrivate::recalculate() { DEBUG("XYCorrelationCurvePrivate::recalculate()"); QElapsedTimer timer; timer.start(); //create correlation result columns if not available yet, clear them otherwise if (!xColumn) { - xColumn = new Column("x", AbstractColumn::Numeric); - yColumn = new Column("y", AbstractColumn::Numeric); + xColumn = new Column("x", AbstractColumn::ColumnMode::Numeric); + yColumn = new Column("y", AbstractColumn::ColumnMode::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } //clear the previous result correlationResult = XYCorrelationCurve::CorrelationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; const AbstractColumn* tmpY2DataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; tmpY2DataColumn = y2DataColumn; } else { //curve columns as data source (autocorrelation) tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); tmpY2DataColumn = dataSourceCurve->yColumn(); } if (tmpYDataColumn == nullptr || tmpY2DataColumn == nullptr) { recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the correlation to temporary vectors QVector xdataVector; QVector ydataVector; QVector y2dataVector; double xmin, xmax; if (tmpXDataColumn != nullptr && correlationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = correlationData.xRange.first(); xmax = correlationData.xRange.last(); } //only copy those data where values are valid and in range if (tmpXDataColumn != nullptr) { // x-axis present (with possible range) for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) { if (tmpXDataColumn->isValid(row) && !tmpXDataColumn->isMasked(row) && tmpYDataColumn->isValid(row) && !tmpYDataColumn->isMasked(row)) { if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } } else { // no x-axis: take all valid values for (int row = 0; row < tmpYDataColumn->rowCount(); ++row) if (tmpYDataColumn->isValid(row) && !tmpYDataColumn->isMasked(row)) ydataVector.append(tmpYDataColumn->valueAt(row)); } if (tmpY2DataColumn != nullptr) { for (int row = 0; row < tmpY2DataColumn->rowCount(); ++row) if (tmpY2DataColumn->isValid(row) && !tmpY2DataColumn->isMasked(row)) y2dataVector.append(tmpY2DataColumn->valueAt(row)); } const size_t n = (size_t)ydataVector.size(); // number of points for signal const size_t m = (size_t)y2dataVector.size(); // number of points for response if (n < 1 || m < 1) { correlationResult.available = true; correlationResult.valid = false; correlationResult.status = i18n("Not enough data points available."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* y2data = y2dataVector.data(); // correlation settings const double samplingInterval = correlationData.samplingInterval; const nsl_corr_type_type type = correlationData.type; const nsl_corr_norm_type norm = correlationData.normalize; DEBUG("signal_1 n = " << n << ", signal_2 n = " << m); DEBUG("sampling interval = " << samplingInterval); DEBUG("type = " << nsl_corr_type_name[type]); DEBUG("norm = " << nsl_corr_norm_name[norm]); /////////////////////////////////////////////////////////// size_t np = GSL_MAX(n, m); if (type == nsl_corr_type_linear) np = 2 * np - 1; double* out = (double*)malloc(np * sizeof(double)); int status = nsl_corr_correlation(ydata, n, y2data, m, type, norm, out); xVector->resize((int)np); yVector->resize((int)np); // take given x-axis values or use index if (tmpXDataColumn != nullptr) { int size = GSL_MIN(xdataVector.size(), (int)np); memcpy(xVector->data(), xdata, size * sizeof(double)); double sampleInterval = (xVector->data()[size-1] - xVector->data()[0])/(xdataVector.size()-1); DEBUG("xdata size = " << xdataVector.size() << ", np = " << np << ", sample interval = " << sampleInterval); for (int i = size; i < (int)np; i++) // fill missing values xVector->data()[i] = xVector->data()[size-1] + (i-size+1) * sampleInterval; } else { // fill with index (starting with 0) if (type == nsl_corr_type_linear) for (size_t i = 0; i < np; i++) xVector->data()[i] = (int)(i-np/2) * samplingInterval; else for (size_t i = 0; i < np; i++) xVector->data()[i] = (int)i * samplingInterval; } memcpy(yVector->data(), out, np * sizeof(double)); free(out); /////////////////////////////////////////////////////////// //write the result correlationResult.available = true; correlationResult.valid = true; correlationResult.status = QString::number(status); correlationResult.elapsedTime = timer.elapsed(); //redraw the curve recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYCorrelationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYCorrelationCurve); writer->writeStartElement("xyCorrelationCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-correlation-curve specific information // correlation data writer->writeStartElement("correlationData"); writer->writeAttribute( "samplingInterval", QString::number(d->correlationData.samplingInterval) ); writer->writeAttribute( "autoRange", QString::number(d->correlationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->correlationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->correlationData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->correlationData.type) ); writer->writeAttribute( "normalize", QString::number(d->correlationData.normalize) ); writer->writeEndElement();// correlationData // correlation results (generated columns) writer->writeStartElement("correlationResult"); writer->writeAttribute( "available", QString::number(d->correlationResult.available) ); writer->writeAttribute( "valid", QString::number(d->correlationResult.valid) ); writer->writeAttribute( "status", d->correlationResult.status ); writer->writeAttribute( "time", QString::number(d->correlationResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"correlationResult" writer->writeEndElement(); //"xyCorrelationCurve" } //! Load from XML bool XYCorrelationCurve::load(XmlStreamReader* reader, bool preview) { DEBUG("XYCorrelationCurve::load()"); Q_D(XYCorrelationCurve); 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() == "xyCorrelationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "correlationData") { attribs = reader->attributes(); READ_DOUBLE_VALUE("samplingInterval", correlationData.samplingInterval); READ_INT_VALUE("autoRange", correlationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", correlationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", correlationData.xRange.last()); READ_INT_VALUE("type", correlationData.type, nsl_corr_type_type); READ_INT_VALUE("normalize", correlationData.normalize, nsl_corr_norm_type); } else if (!preview && reader->name() == "correlationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", correlationResult.available, int); READ_INT_VALUE("valid", correlationResult.valid, int); READ_STRING_VALUE("status", correlationResult.status); READ_INT_VALUE("time", correlationResult.elapsedTime, int); } else if (!preview && reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCurve.cpp index 362e1b2fc..7a8fe4414 100644 --- a/src/backend/worksheet/plots/cartesian/XYCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCurve.cpp @@ -1,3334 +1,3334 @@ /*************************************************************************** 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->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->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->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->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->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->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->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->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); 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; - AbstractColumn::ColumnMode xColMode = xColumn->columnMode(); - AbstractColumn::ColumnMode yColMode = yColumn->columnMode(); + 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::Numeric: - case AbstractColumn::Integer: - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::Numeric: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: tempPoint.setX(xColumn->valueAt(row)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: tempPoint.setX(xColumn->dateTimeAt(row).toMSecsSinceEpoch()); break; - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } switch (yColMode) { - case AbstractColumn::Numeric: - case AbstractColumn::Integer: - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::Numeric: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: tempPoint.setY(yColumn->valueAt(row)); break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: tempPoint.setY(yColumn->dateTimeAt(row).toMSecsSinceEpoch()); break; - case AbstractColumn::Month: - case AbstractColumn::Day: + 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 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 - AbstractColumn::Properties columnProperties = q->xColumn()->properties(); + 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::Integer || xColumn->columnMode() == AbstractColumn::BigInt) + 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::Integer || yColumn->columnMode() == AbstractColumn::BigInt) + 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::Integer || xColumn->columnMode() == AbstractColumn::BigInt) + if (xColumn->columnMode() == AbstractColumn::ColumnMode::Integer || xColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) xPrecision = 0; int yPrecision = valuesPrecision; - if (yColumn->columnMode() == AbstractColumn::Integer || yColumn->columnMode() == AbstractColumn::BigInt) + 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; } int endRow; if (symbolPointsLogical.size()>valuesColumn->rowCount()) endRow = valuesColumn->rowCount(); else endRow = symbolPointsLogical.size(); - AbstractColumn::ColumnMode xColMode = valuesColumn->columnMode(); + 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::Numeric: - case AbstractColumn::Integer: - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::Numeric: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix; break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix; break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + 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; } - AbstractColumn::ColumnMode yColumnMode = yColumn()->columnMode(); + 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(); } - AbstractColumn::ColumnMode yColumnMode = yColumn()->columnMode(); + 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.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.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))); 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.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/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp index 19327570b..681543b10 100644 --- a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp @@ -1,394 +1,394 @@ /*************************************************************************** File : XYDataReductionCurve.cpp Project : LabPlot Description : A xy-curve defined by a data reduction -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 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 XYDataReductionCurve \brief A xy-curve defined by a data reduction \ingroup worksheet */ #include "XYDataReductionCurve.h" #include "XYDataReductionCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include #include #include #include XYDataReductionCurve::XYDataReductionCurve(const QString& name) : XYAnalysisCurve(name, new XYDataReductionCurvePrivate(this), AspectType::XYDataReductionCurve) { } XYDataReductionCurve::XYDataReductionCurve(const QString& name, XYDataReductionCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYDataReductionCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYDataReductionCurve::~XYDataReductionCurve() = default; void XYDataReductionCurve::recalculate() { Q_D(XYDataReductionCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYDataReductionCurve::icon() const { return QIcon::fromTheme("labplot-xy-data-reduction-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYDataReductionCurve, XYDataReductionCurve::DataReductionData, dataReductionData, dataReductionData) const XYDataReductionCurve::DataReductionResult& XYDataReductionCurve::dataReductionResult() const { Q_D(const XYDataReductionCurve); return d->dataReductionResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYDataReductionCurve, SetDataReductionData, XYDataReductionCurve::DataReductionData, dataReductionData, recalculate); void XYDataReductionCurve::setDataReductionData(const XYDataReductionCurve::DataReductionData& reductionData) { Q_D(XYDataReductionCurve); exec(new XYDataReductionCurveSetDataReductionDataCmd(d, reductionData, ki18n("%1: set options and perform the data reduction"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYDataReductionCurvePrivate::XYDataReductionCurvePrivate(XYDataReductionCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYDataReductionCurvePrivate::~XYDataReductionCurvePrivate() = default; void XYDataReductionCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create dataReduction result columns if not available yet, clear them otherwise if (!xColumn) { - xColumn = new Column("x", AbstractColumn::Numeric); - yColumn = new Column("y", AbstractColumn::Numeric); + xColumn = new Column("x", AbstractColumn::ColumnMode::Numeric); + yColumn = new Column("y", AbstractColumn::ColumnMode::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result dataReductionResult = XYDataReductionCurve::DataReductionResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the data reduction to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (dataReductionData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = dataReductionData.xRange.first(); xmax = dataReductionData.xRange.last(); } XYAnalysisCurve::copyData(xdataVector, ydataVector, tmpXDataColumn, tmpYDataColumn, xmin, xmax); //number of data points to use const size_t n = (size_t)xdataVector.size(); if (n < 2) { dataReductionResult.available = true; dataReductionResult.valid = false; dataReductionResult.status = i18n("Not enough data points available."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // dataReduction settings const nsl_geom_linesim_type type = dataReductionData.type; const double tol = dataReductionData.tolerance; const double tol2 = dataReductionData.tolerance2; DEBUG("n =" << n); DEBUG("type:" << nsl_geom_linesim_type_name[type]); DEBUG("tolerance/step:" << tol); DEBUG("tolerance2/repeat/maxtol/region:" << tol2); /////////////////////////////////////////////////////////// emit q->completed(10); size_t npoints = 0; double calcTolerance = 0; // calculated tolerance from Douglas-Peucker variant size_t *index = (size_t *) malloc(n*sizeof(size_t)); switch (type) { case nsl_geom_linesim_type_douglas_peucker_variant: // tol used as number of points npoints = tol; calcTolerance = nsl_geom_linesim_douglas_peucker_variant(xdata, ydata, n, npoints, index); break; case nsl_geom_linesim_type_douglas_peucker: npoints = nsl_geom_linesim_douglas_peucker(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_nthpoint: // tol used as step npoints = nsl_geom_linesim_nthpoint(n, (int)tol, index); break; case nsl_geom_linesim_type_raddist: npoints = nsl_geom_linesim_raddist(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_perpdist: // tol2 used as repeat npoints = nsl_geom_linesim_perpdist_repeat(xdata, ydata, n, tol, tol2, index); break; case nsl_geom_linesim_type_interp: npoints = nsl_geom_linesim_interp(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_visvalingam_whyatt: npoints = nsl_geom_linesim_visvalingam_whyatt(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_reumann_witkam: npoints = nsl_geom_linesim_reumann_witkam(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_opheim: npoints = nsl_geom_linesim_opheim(xdata, ydata, n, tol, tol2, index); break; case nsl_geom_linesim_type_lang: // tol2 used as region npoints = nsl_geom_linesim_opheim(xdata, ydata, n, tol, tol2, index); break; } DEBUG("npoints =" << npoints); if (type == nsl_geom_linesim_type_douglas_peucker_variant) DEBUG("calculated tolerance =" << calcTolerance) else Q_UNUSED(calcTolerance); emit q->completed(80); xVector->resize((int)npoints); yVector->resize((int)npoints); for (int i = 0; i < (int)npoints; i++) { (*xVector)[i] = xdata[index[i]]; (*yVector)[i] = ydata[index[i]]; } emit q->completed(90); const double posError = nsl_geom_linesim_positional_squared_error(xdata, ydata, n, index); const double areaError = nsl_geom_linesim_area_error(xdata, ydata, n, index); free(index); /////////////////////////////////////////////////////////// //write the result dataReductionResult.available = true; dataReductionResult.valid = true; if (npoints > 0) dataReductionResult.status = QString("OK"); else dataReductionResult.status = QString("FAILURE"); dataReductionResult.elapsedTime = timer.elapsed(); dataReductionResult.npoints = npoints; dataReductionResult.posError = posError; dataReductionResult.areaError = areaError; //redraw the curve recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; emit q->completed(100); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYDataReductionCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYDataReductionCurve); writer->writeStartElement("xyDataReductionCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-dataReduction-curve specific information // dataReduction data writer->writeStartElement("dataReductionData"); writer->writeAttribute( "autoRange", QString::number(d->dataReductionData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->dataReductionData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->dataReductionData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->dataReductionData.type) ); writer->writeAttribute( "autoTolerance", QString::number(d->dataReductionData.autoTolerance) ); writer->writeAttribute( "tolerance", QString::number(d->dataReductionData.tolerance) ); writer->writeAttribute( "autoTolerance2", QString::number(d->dataReductionData.autoTolerance2) ); writer->writeAttribute( "tolerance2", QString::number(d->dataReductionData.tolerance2) ); writer->writeEndElement();// dataReductionData // dataReduction results (generated columns) writer->writeStartElement("dataReductionResult"); writer->writeAttribute( "available", QString::number(d->dataReductionResult.available) ); writer->writeAttribute( "valid", QString::number(d->dataReductionResult.valid) ); writer->writeAttribute( "status", d->dataReductionResult.status ); writer->writeAttribute( "time", QString::number(d->dataReductionResult.elapsedTime) ); writer->writeAttribute( "npoints", QString::number(d->dataReductionResult.npoints) ); writer->writeAttribute( "posError", QString::number(d->dataReductionResult.posError) ); writer->writeAttribute( "areaError", QString::number(d->dataReductionResult.areaError) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"dataReductionResult" writer->writeEndElement(); //"xyDataReductionCurve" } //! Load from XML bool XYDataReductionCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYDataReductionCurve); 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() == "xyDataReductionCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "dataReductionData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", dataReductionData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", dataReductionData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", dataReductionData.xRange.last()); READ_INT_VALUE("type", dataReductionData.type, nsl_geom_linesim_type); READ_INT_VALUE("autoTolerance", dataReductionData.autoTolerance, int); READ_DOUBLE_VALUE("tolerance", dataReductionData.tolerance); READ_INT_VALUE("autoTolerance2", dataReductionData.autoTolerance2, int); READ_DOUBLE_VALUE("tolerance2", dataReductionData.tolerance2); } else if (!preview && reader->name() == "dataReductionResult") { attribs = reader->attributes(); READ_INT_VALUE("available", dataReductionResult.available, int); READ_INT_VALUE("valid", dataReductionResult.valid, int); READ_STRING_VALUE("status", dataReductionResult.status); READ_INT_VALUE("time", dataReductionResult.elapsedTime, int); READ_INT_VALUE("npoints", dataReductionResult.npoints, size_t); READ_DOUBLE_VALUE("posError", dataReductionResult.posError); READ_DOUBLE_VALUE("areaError", dataReductionResult.areaError); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp index 3933ac02b..8a7bfebb1 100644 --- a/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp @@ -1,341 +1,341 @@ /*************************************************************************** File : XYDifferentiationCurve.cpp Project : LabPlot Description : A xy-curve defined by an differentiation -------------------------------------------------------------------- Copyright : (C) 2016 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 XYDifferentiationCurve \brief A xy-curve defined by an differentiation \ingroup worksheet */ #include "XYDifferentiationCurve.h" #include "XYDifferentiationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" extern "C" { #include } #include #include #include #include XYDifferentiationCurve::XYDifferentiationCurve(const QString& name) : XYAnalysisCurve(name, new XYDifferentiationCurvePrivate(this), AspectType::XYDifferentiationCurve) { } XYDifferentiationCurve::XYDifferentiationCurve(const QString& name, XYDifferentiationCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYDifferentiationCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYDifferentiationCurve::~XYDifferentiationCurve() = default; void XYDifferentiationCurve::recalculate() { Q_D(XYDifferentiationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYDifferentiationCurve::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYDifferentiationCurve, XYDifferentiationCurve::DifferentiationData, differentiationData, differentiationData) const XYDifferentiationCurve::DifferentiationResult& XYDifferentiationCurve::differentiationResult() const { Q_D(const XYDifferentiationCurve); return d->differentiationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYDifferentiationCurve, SetDifferentiationData, XYDifferentiationCurve::DifferentiationData, differentiationData, recalculate); void XYDifferentiationCurve::setDifferentiationData(const XYDifferentiationCurve::DifferentiationData& differentiationData) { Q_D(XYDifferentiationCurve); exec(new XYDifferentiationCurveSetDifferentiationDataCmd(d, differentiationData, ki18n("%1: set options and perform the differentiation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYDifferentiationCurvePrivate::XYDifferentiationCurvePrivate(XYDifferentiationCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYDifferentiationCurvePrivate::~XYDifferentiationCurvePrivate() = default; // ... // see XYFitCurvePrivate void XYDifferentiationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create differentiation result columns if not available yet, clear them otherwise if (!xColumn) { - xColumn = new Column("x", AbstractColumn::Numeric); - yColumn = new Column("y", AbstractColumn::Numeric); + xColumn = new Column("x", AbstractColumn::ColumnMode::Numeric); + yColumn = new Column("y", AbstractColumn::ColumnMode::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result differentiationResult = XYDifferentiationCurve::DifferentiationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the differentiation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (differentiationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = differentiationData.xRange.first(); xmax = differentiationData.xRange.last(); } XYAnalysisCurve::copyData(xdataVector, ydataVector, tmpXDataColumn, tmpYDataColumn, xmin, xmax); //number of data points to differentiate const size_t n = (size_t)xdataVector.size(); if (n < 3) { differentiationResult.available = true; differentiationResult.valid = false; differentiationResult.status = i18n("Not enough data points available."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // differentiation settings const nsl_diff_deriv_order_type derivOrder = differentiationData.derivOrder; const int accOrder = differentiationData.accOrder; DEBUG(nsl_diff_deriv_order_name[derivOrder] << "derivative"); DEBUG("accuracy order:" << accOrder); /////////////////////////////////////////////////////////// int status = 0; switch (derivOrder) { case nsl_diff_deriv_order_first: status = nsl_diff_first_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_second: status = nsl_diff_second_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_third: status = nsl_diff_third_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_fourth: status = nsl_diff_fourth_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_fifth: status = nsl_diff_fifth_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_sixth: status = nsl_diff_sixth_deriv(xdata, ydata, n, accOrder); break; } xVector->resize((int)n); yVector->resize((int)n); memcpy(xVector->data(), xdata, n * sizeof(double)); memcpy(yVector->data(), ydata, n * sizeof(double)); /////////////////////////////////////////////////////////// //write the result differentiationResult.available = true; differentiationResult.valid = true; differentiationResult.status = QString::number(status); differentiationResult.elapsedTime = timer.elapsed(); //redraw the curve recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYDifferentiationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYDifferentiationCurve); writer->writeStartElement("xyDifferentiationCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-differentiation-curve specific information // differentiation data writer->writeStartElement("differentiationData"); writer->writeAttribute( "derivOrder", QString::number(d->differentiationData.derivOrder) ); writer->writeAttribute( "accOrder", QString::number(d->differentiationData.accOrder) ); writer->writeAttribute( "autoRange", QString::number(d->differentiationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->differentiationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->differentiationData.xRange.last()) ); writer->writeEndElement();// differentiationData // differentiation results (generated columns) writer->writeStartElement("differentiationResult"); writer->writeAttribute( "available", QString::number(d->differentiationResult.available) ); writer->writeAttribute( "valid", QString::number(d->differentiationResult.valid) ); writer->writeAttribute( "status", d->differentiationResult.status ); writer->writeAttribute( "time", QString::number(d->differentiationResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"differentiationResult" writer->writeEndElement(); //"xyDifferentiationCurve" } //! Load from XML bool XYDifferentiationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYDifferentiationCurve); 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() == "xyDifferentiationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "differentiationData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", differentiationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", differentiationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", differentiationData.xRange.last()); READ_INT_VALUE("derivOrder", differentiationData.derivOrder, nsl_diff_deriv_order_type); READ_INT_VALUE("accOrder", differentiationData.accOrder, int); } else if (!preview && reader->name() == "differentiationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", differentiationResult.available, int); READ_INT_VALUE("valid", differentiationResult.valid, int); READ_STRING_VALUE("status", differentiationResult.status); READ_INT_VALUE("time", differentiationResult.elapsedTime, int); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp index 199fe3f10..f5324ad0d 100644 --- a/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp @@ -1,229 +1,229 @@ /*************************************************************************** File : XYEquationCurve.cpp Project : LabPlot Description : A xy-curve defined by a mathematical equation -------------------------------------------------------------------- Copyright : (C) 2014-2017 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 XYEquationCurve \brief A xy-curve defined by a mathematical equation \ingroup worksheet */ #include "XYEquationCurve.h" #include "XYEquationCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/gsl/ExpressionParser.h" #include #include XYEquationCurve::XYEquationCurve(const QString& name) : XYCurve(name, new XYEquationCurvePrivate(this), AspectType::XYEquationCurve) { init(); } XYEquationCurve::XYEquationCurve(const QString& name, XYEquationCurvePrivate* dd) : XYCurve(name, dd, AspectType::XYEquationCurve) { init(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYEquationCurve::~XYEquationCurve() = default; void XYEquationCurve::init() { Q_D(XYEquationCurve); d->xColumn->setHidden(true); addChildFast(d->xColumn); d->yColumn->setHidden(true); addChildFast(d->yColumn); //TODO: read from the saved settings for XYEquationCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; setUndoAware(false); suppressRetransform(true); setXColumn(d->xColumn); setYColumn(d->yColumn); suppressRetransform(false); setUndoAware(true); } void XYEquationCurve::recalculate() { Q_D(XYEquationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYEquationCurve::icon() const { return QIcon::fromTheme("labplot-xy-equation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYEquationCurve, XYEquationCurve::EquationData, equationData, equationData) //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYEquationCurve, SetEquationData, XYEquationCurve::EquationData, equationData, recalculate); void XYEquationCurve::setEquationData(const XYEquationCurve::EquationData& equationData) { Q_D(XYEquationCurve); if ( (equationData.expression1 != d->equationData.expression1) || (equationData.expression2 != d->equationData.expression2) || (equationData.min != d->equationData.min) || (equationData.max != d->equationData.max) || (equationData.count != d->equationData.count) ) exec(new XYEquationCurveSetEquationDataCmd(d, equationData, ki18n("%1: set equation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYEquationCurvePrivate::XYEquationCurvePrivate(XYEquationCurve* owner) : XYCurvePrivate(owner), - xColumn(new Column("x", AbstractColumn::Numeric)), - yColumn(new Column("y", AbstractColumn::Numeric)), + xColumn(new Column("x", AbstractColumn::ColumnMode::Numeric)), + yColumn(new Column("y", AbstractColumn::ColumnMode::Numeric)), xVector(static_cast* >(xColumn->data())), yVector(static_cast* >(yColumn->data())), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYEquationCurvePrivate::~XYEquationCurvePrivate() = default; void XYEquationCurvePrivate::recalculate() { //resize the vector if a new number of point to calculate was provided if (equationData.count != xVector->size()) { if (equationData.count >= 1) { xVector->resize(equationData.count); yVector->resize(equationData.count); } else { //invalid number of points provided xVector->clear(); yVector->clear(); recalcLogicalPoints(); emit q->dataChanged(); return; } } else { if (equationData.count < 1) return; } ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = false; if (equationData.type == XYEquationCurve::Cartesian) { rc = parser->evaluateCartesian( equationData.expression1, equationData.min, equationData.max, equationData.count, xVector, yVector ); } else if (equationData.type == XYEquationCurve::Polar) { rc = parser->evaluatePolar( equationData.expression1, equationData.min, equationData.max, equationData.count, xVector, yVector ); } else if (equationData.type == XYEquationCurve::Parametric) { rc = parser->evaluateParametric(equationData.expression1, equationData.expression2, equationData.min, equationData.max, equationData.count, xVector, yVector); } if (!rc) { xVector->clear(); yVector->clear(); } recalcLogicalPoints(); emit q->dataChanged(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYEquationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYEquationCurve); writer->writeStartElement("xyEquationCurve"); //write xy-curve information XYCurve::save(writer); //write xy-equationCurve specific information writer->writeStartElement("equationData"); writer->writeAttribute( "type", QString::number(d->equationData.type) ); writer->writeAttribute( "expression1", d->equationData.expression1 ); writer->writeAttribute( "expression2", d->equationData.expression2 ); writer->writeAttribute( "min", d->equationData.min); writer->writeAttribute( "max", d->equationData.max ); writer->writeAttribute( "count", QString::number(d->equationData.count) ); writer->writeEndElement(); writer->writeEndElement(); } //! Load from XML bool XYEquationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYEquationCurve); 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() == "xyEquationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "equationData") { attribs = reader->attributes(); READ_INT_VALUE("type", equationData.type, XYEquationCurve::EquationType); READ_STRING_VALUE("expression1", equationData.expression1); READ_STRING_VALUE("expression2", equationData.expression2); READ_STRING_VALUE("min", equationData.min); READ_STRING_VALUE("max", equationData.max); READ_INT_VALUE("count", equationData.count, int); } } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp index 7d318ee2d..5cecc5a0c 100644 --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp @@ -1,2470 +1,2470 @@ /*************************************************************************** File : XYFitCurve.cpp Project : LabPlot Description : A xy-curve defined by a fit model -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) - Copyright : (C) 2016-2018 Stefan Gerlach (stefan.gerlach@uni.kn) + Copyright : (C) 2016-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 XYFitCurve \brief A xy-curve defined by a fit model \ingroup worksheet */ #include "XYFitCurve.h" #include "XYFitCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" #include "backend/gsl/ExpressionParser.h" extern "C" { #include #include #include #include #include #include #include "backend/gsl/parser.h" #include "backend/nsl/nsl_sf_stats.h" #include "backend/nsl/nsl_stats.h" } #include #include #include #include XYFitCurve::XYFitCurve(const QString& name) : XYAnalysisCurve(name, new XYFitCurvePrivate(this), AspectType::XYFitCurve) { } XYFitCurve::XYFitCurve(const QString& name, XYFitCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYFitCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYFitCurve::~XYFitCurve() = default; void XYFitCurve::recalculate() { Q_D(XYFitCurve); d->recalculate(); } void XYFitCurve::evaluate(bool preview) { Q_D(XYFitCurve); d->evaluate(preview); } void XYFitCurve::initStartValues(const XYCurve* curve) { Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; initStartValues(fitData, curve); } void XYFitCurve::initStartValues(XYFitCurve::FitData& fitData, const XYCurve* curve) { DEBUG("XYFitCurve::initStartValues()"); if (!curve) { DEBUG(" no curve given"); return; } const Column* tmpXDataColumn = dynamic_cast(curve->xColumn()); const Column* tmpYDataColumn = dynamic_cast(curve->yColumn()); if (!tmpXDataColumn || !tmpYDataColumn) { DEBUG(" data columns not available"); return; } DEBUG(" x data rows = " << tmpXDataColumn->rowCount()); nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; int degree = fitData.degree; DEBUG(" fit model type = " << modelType << ", degree = " << degree); QVector& paramStartValues = fitData.paramStartValues; //QVector* xVector = static_cast* >(tmpXDataColumn->data()); //double xmean = gsl_stats_mean(xVector->constData(), 1, tmpXDataColumn->rowCount()); double xmin = tmpXDataColumn->minimum(); double xmax = tmpXDataColumn->maximum(); //double ymin = tmpYDataColumn->minimum(); //double ymax = tmpYDataColumn->maximum(); double xrange = xmax - xmin; //double yrange = ymax-ymin; DEBUG(" x min/max = " << xmin << ' ' << xmax); //DEBUG(" y min/max = " << ymin << ' ' << ymax); // guess start values for parameter switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // not needed (works anyway) break; //TODO: handle basic models case nsl_fit_model_power: case nsl_fit_model_exponential: case nsl_fit_model_inverse_exponential: case nsl_fit_model_fourier: break; } break; case nsl_fit_model_peak: // use equidistant mu's and (xmax-xmin)/(10*degree) as sigma(, gamma) switch (modelType) { case nsl_fit_model_gaussian: case nsl_fit_model_lorentz: case nsl_fit_model_sech: case nsl_fit_model_logistic: for (int d = 0; d < degree; d++) { paramStartValues[3*d+2] = xmin + (d+1.)*xrange/(degree+1.); // mu paramStartValues[3*d+1] = xrange/(10.*degree); // sigma } break; case nsl_fit_model_voigt: for (int d = 0; d < degree; d++) { paramStartValues[4*d+1] = xmin + (d+1.)*xrange/(degree+1.); // mu paramStartValues[4*d+2] = xrange/(10.*degree); // sigma paramStartValues[4*d+3] = xrange/(10.*degree); // gamma } break; case nsl_fit_model_pseudovoigt1: for (int d = 0; d < degree; d++) { paramStartValues[4*d+1] = 0.5; // eta paramStartValues[4*d+2] = xrange/(10.*degree); // sigma paramStartValues[4*d+3] = xmin + (d+1.)*xrange/(degree+1.); // mu } break; } break; case nsl_fit_model_growth: switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: case nsl_fit_model_sigmoid: // use (xmax+xmin)/2 as mu and (xmax-xmin)/10 as sigma paramStartValues[1] = (xmax+xmin)/2.; paramStartValues[2] = xrange/10.; break; case nsl_fit_model_hill: paramStartValues[2] = xrange/10.; break; case nsl_fit_model_gompertz: //TODO break; } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: // use (xmax+xmin)/2 as mu and (xmax-xmin)/10 as sigma paramStartValues[2] = (xmin+xmax)/2.; paramStartValues[1] = xrange/10.; break; //TODO: other types default: break; } break; case nsl_fit_model_custom: // not possible break; } } /*! * sets the parameter names for given model category, model type and degree in \c fitData for given action */ void XYFitCurve::initFitData(PlotDataDialog::AnalysisAction action) { if (!action) return; Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; if (action == PlotDataDialog::FitLinear) { //Linear fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_polynomial; fitData.degree = 1; } else if (action == PlotDataDialog::FitPower) { //Power fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_power; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp1) { //Exponential (degree 1) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_exponential; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp2) { //Exponential (degree 2) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_exponential; fitData.degree = 2; } else if (action == PlotDataDialog::FitInvExp) { //Inverse exponential fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_inverse_exponential; } else if (action == PlotDataDialog::FitGauss) { //Gauss fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = (int)nsl_fit_model_gaussian; fitData.degree = 1; } else if (action == PlotDataDialog::FitCauchyLorentz) { //Cauchy-Lorentz fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = (int)nsl_fit_model_lorentz; fitData.degree = 1; } else if (action == PlotDataDialog::FitTan) { //Arc tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_atan; } else if (action == PlotDataDialog::FitTanh) { //Hyperbolic tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_tanh; } else if (action == PlotDataDialog::FitErrFunc) { //Error function fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_erf; } else { //Custom fitData.modelCategory = nsl_fit_model_custom; fitData.modelType = 0; } XYFitCurve::initFitData(fitData); } /*! * sets the model expression and the parameter names for given model category, model type and degree in \c fitData */ void XYFitCurve::initFitData(XYFitCurve::FitData& fitData) { nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; QString& model = fitData.model; QStringList& paramNames = fitData.paramNames; QStringList& paramNamesUtf8 = fitData.paramNamesUtf8; int degree = fitData.degree; QVector& paramStartValues = fitData.paramStartValues; QVector& paramLowerLimits = fitData.paramLowerLimits; QVector& paramUpperLimits = fitData.paramUpperLimits; QVector& paramFixed = fitData.paramFixed; if (modelCategory != nsl_fit_model_custom) { DEBUG("XYFitCurve::initFitData() for model category = " << nsl_fit_model_category_name[modelCategory] << ", model type = " << modelType << ", degree = " << degree); paramNames.clear(); } else { DEBUG("XYFitCurve::initFitData() for model category = nsl_fit_model_custom, model type = " << modelType << ", degree = " << degree); } paramNamesUtf8.clear(); // 10 indices used in multi degree models QStringList indices = { UTF8_QSTRING("₁"), UTF8_QSTRING("₂"), UTF8_QSTRING("₃"), UTF8_QSTRING("₄"), UTF8_QSTRING("₅"), UTF8_QSTRING("₆"), UTF8_QSTRING("₇"), UTF8_QSTRING("₈"), UTF8_QSTRING("₉"), UTF8_QSTRING("₁₀")}; switch (modelCategory) { case nsl_fit_model_basic: model = nsl_fit_model_basic_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_polynomial: paramNames << "c0" << "c1"; paramNamesUtf8 << UTF8_QSTRING("c₀") << UTF8_QSTRING("c₁"); if (degree == 2) { model += " + c2*x^2"; paramNames << "c2"; paramNamesUtf8 << UTF8_QSTRING("c₂"); } else if (degree > 2) { for (int i = 2; i <= degree; ++i) { QString numStr = QString::number(i); model += "+c" + numStr + "*x^" + numStr; paramNames << 'c' + numStr; paramNamesUtf8 << 'c' + indices[i-1]; } } break; case nsl_fit_model_power: if (degree == 1) { paramNames << "a" << "b"; } else { paramNames << "a" << "b" << "c"; model = "a + b*x^c"; } break; case nsl_fit_model_exponential: if (degree == 1) { paramNames << "a" << "b"; } else { for (int i = 1; i <= degree; i++) { QString numStr = QString::number(i); if (i == 1) model = "a1*exp(b1*x)"; else model += " + a" + numStr + "*exp(b" + numStr + "*x)"; paramNames << 'a' + numStr << 'b' + numStr; paramNamesUtf8 << 'a' + indices[i-1] << 'b' + indices[i-1]; } } break; case nsl_fit_model_inverse_exponential: paramNames << "a" << "b" << "c"; break; case nsl_fit_model_fourier: paramNames << "w" << "a0" << "a1" << "b1"; paramNamesUtf8 << UTF8_QSTRING("ω") << UTF8_QSTRING("a₀") << UTF8_QSTRING("a₁") << UTF8_QSTRING("b₁"); if (degree > 1) { for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); model += "+ (a" + numStr + "*cos(" + numStr + "*w*x) + b" + numStr + "*sin(" + numStr + "*w*x))"; paramNames << 'a' + numStr << 'b' + numStr; paramNamesUtf8 << 'a' + indices[i-1] << 'b' + indices[i-1]; } } break; } break; case nsl_fit_model_peak: model = nsl_fit_model_peak_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_gaussian: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1./sqrt(2*pi) * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* exp(-((x-mu" + numStr + ")/s" + numStr + ")^2/2)"; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_lorentz: switch (degree) { case 1: paramNames << "a" << "g" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("μ"); break; default: model = "1./pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + " * g" + numStr + "/(g" + numStr + "^2+(x-mu" + numStr + ")^2)"; paramNames << 'a' + numStr << 'g' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("γ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_sech: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1/pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/s" + numStr + ')'; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_logistic: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1/4 * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/2/s" + numStr + ")**2"; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_voigt: switch (degree) { case 1: paramNames << "a" << "mu" << "s" << "g"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << UTF8_QSTRING("σ") << UTF8_QSTRING("γ"); break; default: model.clear(); for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "*voigt(x-mu" + numStr + ",s" + numStr + ",g" + numStr + ')'; paramNames << 'a' + numStr << "mu" + numStr << 's' + numStr << 'g' + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("γ") + indices[i-1]; } } break; case nsl_fit_model_pseudovoigt1: switch (degree) { case 1: paramNames << "a" << "et" << "w" << "mu"; // eta function exists! paramNamesUtf8 << "A" << UTF8_QSTRING("η") << "w" << UTF8_QSTRING("μ"); break; default: model.clear(); for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "*pseudovoigt1(x-mu" + numStr + ",eta" + numStr + ",w" + numStr + ')'; paramNames << 'a' + numStr << "eta" + numStr << 'w' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("η") + indices[i-1] << 'w' + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } } break; } break; case nsl_fit_model_growth: model = nsl_fit_model_growth_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: paramNames << "a" << "mu" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << UTF8_QSTRING("σ"); break; case nsl_fit_model_sigmoid: paramNames << "a" << "mu" << "k"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << "k"; break; case nsl_fit_model_hill: paramNames << "a" << "n" << "a"; paramNamesUtf8 << "A" << "n" << UTF8_QSTRING("σ"); break; case nsl_fit_model_gompertz: paramNames << "a" << "b" << "c"; break; } break; case nsl_fit_model_distribution: model = nsl_sf_stats_distribution_equation[fitData.modelType]; switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_gaussian_tail: paramNames << "A" << "s" << "a" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << "a" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_exponential: paramNames << "a" << "l" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("λ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_exponential_power: paramNames << "a" << "s" << "b" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << "b" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: paramNames << "a" << "g" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_rayleigh: paramNames << "a" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ"); break; case nsl_sf_stats_landau: paramNames << "a"; paramNamesUtf8 << "A"; break; case nsl_sf_stats_levy_alpha_stable: // unused distributions case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; case nsl_sf_stats_gamma: paramNames << "a" << "k" << "t"; paramNamesUtf8 << "A"<< "k" << UTF8_QSTRING("θ"); break; case nsl_sf_stats_flat: paramNames << "A" << "b" << "a"; break; case nsl_sf_stats_chi_squared: paramNames << "a" << "n"; paramNamesUtf8 << "A" << "n"; break; case nsl_sf_stats_fdist: paramNames << "a" << "n1" << "n2"; paramNamesUtf8 << "A" << UTF8_QSTRING("ν₁") << UTF8_QSTRING("ν₂"); break; case nsl_sf_stats_tdist: paramNames << "a" << "n"; paramNamesUtf8 << "A" << UTF8_QSTRING("ν"); break; case nsl_sf_stats_beta: case nsl_sf_stats_pareto: paramNames << "A" << "a" << "b"; break; case nsl_sf_stats_weibull: paramNames << "a" << "k" << "l" << "mu"; paramNamesUtf8 << "A" << "k" << UTF8_QSTRING("λ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_gumbel1: paramNames << "a" << "s" << "mu" << "b"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << UTF8_QSTRING("β"); break; case nsl_sf_stats_gumbel2: paramNames << "A" << "a" << "b" << "mu"; paramNamesUtf8 << "A" << "a" << "b" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_poisson: paramNames << "a" << "l"; paramNamesUtf8 << "A" << UTF8_QSTRING("λ"); break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: paramNames << "a" << "p" << "n"; paramNamesUtf8 << "A" << "p" << "n"; break; case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: paramNames << "a" << "p"; paramNamesUtf8 << "A" << "p"; break; case nsl_sf_stats_hypergeometric: paramNames << "a" << "n1" << "n2" << "t"; paramNamesUtf8 << "A" << UTF8_QSTRING("n₁") << UTF8_QSTRING("n₂") << "t"; break; case nsl_sf_stats_maxwell_boltzmann: paramNames << "a" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ"); break; case nsl_sf_stats_frechet: paramNames << "a" << "g" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; } break; case nsl_fit_model_custom: break; } DEBUG("model: " << STDSTRING(model)); if (paramNamesUtf8.isEmpty()) paramNamesUtf8 << paramNames; //resize the vector for the start values and set the elements to 1.0 //in case a custom model is used, do nothing, we take over the previous values if (modelCategory != nsl_fit_model_custom) { const int np = paramNames.size(); paramStartValues.resize(np); paramFixed.resize(np); paramLowerLimits.resize(np); paramUpperLimits.resize(np); for (int i = 0; i < np; ++i) { paramStartValues[i] = 1.0; paramFixed[i] = false; paramLowerLimits[i] = -std::numeric_limits::max(); paramUpperLimits[i] = std::numeric_limits::max(); } // set some model-dependent start values // TODO: see initStartValues() if (modelCategory == nsl_fit_model_distribution) { if (modelType == (int)nsl_sf_stats_flat) paramStartValues[2] = -1.0; else if (modelType == (int)nsl_sf_stats_levy) paramStartValues[2] = 0.0; else if (modelType == (int)nsl_sf_stats_exponential_power || modelType == (int)nsl_sf_stats_weibull || modelType == (int)nsl_sf_stats_gumbel2 || modelType == (int)nsl_sf_stats_frechet) paramStartValues[3] = 0.0; else if (modelType == (int)nsl_sf_stats_binomial || modelType == (int)nsl_sf_stats_negative_binomial || modelType == (int)nsl_sf_stats_pascal || modelType == (int)nsl_sf_stats_geometric || modelType == (int)nsl_sf_stats_logarithmic) paramStartValues[1] = 0.5; } } } /*! Returns an icon to be used in the project explorer. */ QIcon XYFitCurve::icon() const { return QIcon::fromTheme("labplot-xy-fit-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xErrorColumn, xErrorColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yErrorColumn, yErrorColumn) const QString& XYFitCurve::xErrorColumnPath() const { Q_D(const XYFitCurve); return d->xErrorColumnPath; } const QString& XYFitCurve::yErrorColumnPath() const { Q_D(const XYFitCurve); return d->yErrorColumnPath; } BASIC_SHARED_D_READER_IMPL(XYFitCurve, XYFitCurve::FitData, fitData, fitData) const XYFitCurve::FitResult& XYFitCurve::fitResult() const { Q_D(const XYFitCurve); return d->fitResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYFitCurve, SetXErrorColumn, const AbstractColumn*, xErrorColumn) void XYFitCurve::setXErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xErrorColumn) { exec(new XYFitCurveSetXErrorColumnCmd(d, column, ki18n("%1: assign x-error"))); handleSourceDataChanged(); if (column) { connect(column, &AbstractColumn::dataChanged, this, [=](){ handleSourceDataChanged(); }); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetYErrorColumn, const AbstractColumn*, yErrorColumn) void XYFitCurve::setYErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yErrorColumn) { exec(new XYFitCurveSetYErrorColumnCmd(d, column, ki18n("%1: assign y-error"))); handleSourceDataChanged(); if (column) { connect(column, &AbstractColumn::dataChanged, this, [=](){ handleSourceDataChanged(); }); //TODO disconnect on undo } } } // do not recalculate (allow preview) //STD_SETTER_CMD_IMPL_F_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData, recalculate) STD_SETTER_CMD_IMPL_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData) void XYFitCurve::setFitData(const XYFitCurve::FitData& fitData) { Q_D(XYFitCurve); exec(new XYFitCurveSetFitDataCmd(d, fitData, ki18n("%1: set fit options and perform the fit"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFitCurvePrivate::XYFitCurvePrivate(XYFitCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) {} //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYFitCurvePrivate::~XYFitCurvePrivate() = default; // data structure to pass parameter to fit functions struct data { size_t n; //number of data points double* x; //pointer to the vector with x-data values double* y; //pointer to the vector with y-data values double* weight; //pointer to the vector with weight values nsl_fit_model_category modelCategory; int modelType; int degree; QString* func; // string containing the definition of the model/function QStringList* paramNames; double* paramMin; // lower parameter limits double* paramMax; // upper parameter limits bool* paramFixed; // parameter fixed? }; /*! * \param paramValues vector containing current values of the fit parameters * \param params * \param f vector with the weighted residuals weight[i]*(Yi - y[i]) */ int func_f(const gsl_vector* paramValues, void* params, gsl_vector* f) { //DEBUG("func_f"); size_t n = ((struct data*)params)->n; double* x = ((struct data*)params)->x; double* y = ((struct data*)params)->y; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; QByteArray funcba = ((struct data*)params)->func->toLatin1(); // a local byte array is needed! const char *func = funcba.constData(); // function to evaluate QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; // set current values of the parameters for (int i = 0; i < paramNames->size(); i++) { double v = gsl_vector_get(paramValues, (size_t)i); // bound values if limits are set QByteArray paramnameba = paramNames->at(i).toLatin1(); assign_variable(paramnameba.constData(), nsl_fit_map_bound(v, min[i], max[i])); QDEBUG("Parameter"<n; double* xVector = ((struct data*)params)->x; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; unsigned int degree = ((struct data*)params)->degree; QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; bool *fixed = ((struct data*)params)->paramFixed; // calculate the Jacobian matrix: // Jacobian matrix J(i,j) = df_i / dx_j // where f_i = w_i*(Y_i - y_i), // Y_i = model and the x_j are the parameters double x; switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // Y(x) = c0 + c1*x + ... + cn*x^n for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < (unsigned int)paramNames->size(); ++j) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_polynomial_param_deriv(x, j, weight[i])); } } break; case nsl_fit_model_power: // Y(x) = a*x^b or Y(x) = a + b*x^c. if (degree == 1) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power1_param_deriv(j, x, a, b, weight[i])); } } } else if (degree == 2) { const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double c = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power2_param_deriv(j, x, b, c, weight[i])); } } } break; case nsl_fit_model_exponential: { // Y(x) = a*exp(b*x) + c*exp(d*x) + ... double *p = new double[2*degree]; for (unsigned int i = 0; i < 2*degree; i++) p[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, i), min[i], max[i]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2*degree; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponentialn_param_deriv(j, x, p, weight[i])); } } delete[] p; break; } case nsl_fit_model_inverse_exponential: { // Y(x) = a*(1-exp(b*x))+c const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_inverse_exponential_param_deriv(j, x, a, b, weight[i])); } } break; } case nsl_fit_model_fourier: { // Y(x) = a0 + (a1*cos(w*x) + b1*sin(w*x)) + ... + (an*cos(n*w*x) + bn*sin(n*w*x) //parameters: w, a0, a1, b1, ... an, bn double* a = new double[degree]; double* b = new double[degree]; double w = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); a[0] = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); b[0] = 0; for (unsigned int i = 1; i < degree; ++i) { a[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i), min[2*i], max[2*i]); b[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i+1), min[2*i+1], max[2*i+1]); } for (size_t i = 0; i < n; i++) { x = xVector[i]; double wd = 0; //first derivative with respect to the w parameter for (unsigned int j = 1; j < degree; ++j) { wd += -a[j]*j*x*sin(j*w*x) + b[j]*j*x*cos(j*w*x); } gsl_matrix_set(J, i, 0, weight[i]*wd); gsl_matrix_set(J, i, 1, weight[i]); for (unsigned int j = 1; j <= degree; ++j) { gsl_matrix_set(J, (size_t)i, (size_t)(2*j), nsl_fit_model_fourier_param_deriv(0, j, x, w, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(2*j+1), nsl_fit_model_fourier_param_deriv(1, j, x, w, weight[i])); } for (unsigned int j = 0; j <= 2*degree+1; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } delete[] a; delete[] b; break; } } break; case nsl_fit_model_peak: switch (modelType) { case nsl_fit_model_gaussian: case nsl_fit_model_lorentz: case nsl_fit_model_sech: case nsl_fit_model_logistic: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j), min[3*j], max[3*j]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+1), min[3*j+1], max[3*j+1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+2), min[3*j+2], max[3*j+2]); switch (modelType) { case nsl_fit_model_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_gaussian_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_gaussian_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_gaussian_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_lorentz: // a,s,t gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_lorentz_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_lorentz_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_lorentz_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_sech: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_sech_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_sech_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_sech_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_logistic: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_logistic_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_logistic_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_logistic_param_deriv(2, x, a, s, mu, weight[i])); break; } } for (unsigned int j = 0; j < 3*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; case nsl_fit_model_voigt: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j), min[4*j], max[4*j]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+1), min[4*j+1], max[4*j+1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+2), min[4*j+2], max[4*j+2]); const double g = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+3), min[4*j+3], max[4*j+3]); gsl_matrix_set(J, (size_t)i, (size_t)(4*j), nsl_fit_model_voigt_param_deriv(0, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+1), nsl_fit_model_voigt_param_deriv(1, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+2), nsl_fit_model_voigt_param_deriv(2, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+3), nsl_fit_model_voigt_param_deriv(3, x, a, mu, s, g, weight[i])); } for (unsigned int j = 0; j < 4*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; case nsl_fit_model_pseudovoigt1: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j), min[4*j], max[4*j]); const double eta = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+1), min[4*j+1], max[4*j+1]); const double w = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+2), min[4*j+2], max[4*j+2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+3), min[4*j+3], max[4*j+3]); gsl_matrix_set(J, (size_t)i, (size_t)(4*j), nsl_fit_model_voigt_param_deriv(0, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+1), nsl_fit_model_voigt_param_deriv(1, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+2), nsl_fit_model_voigt_param_deriv(2, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+3), nsl_fit_model_voigt_param_deriv(3, x, a, eta, w, mu, weight[i])); } for (unsigned int j = 0; j < 4*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; } break; case nsl_fit_model_growth: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) { gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } else { switch (modelType) { case nsl_fit_model_atan: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_atan_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_tanh: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_tanh_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_algebraic_sigmoid: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_algebraic_sigmoid_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_sigmoid: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sigmoid_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_erf: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_erf_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_hill: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hill_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_gompertz: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gompertz_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_gudermann: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gudermann_param_deriv(j, x, a, mu, s, weight[i])); break; } } } } } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_exponential: case nsl_sf_stats_laplace: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_levy: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_exponential: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponential_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_laplace: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_laplace_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_cauchy_lorentz: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lorentz_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_rayleigh_tail: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_tail_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_lognormal: if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lognormal_param_deriv(j, x, a, s, mu, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); break; case nsl_sf_stats_logistic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logistic_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_sech: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sech_dist_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_levy: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_levy_param_deriv(j, x, a, s, mu, weight[i])); break; } } } } break; } case nsl_sf_stats_gaussian_tail: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_tail_param_deriv(j, x, A, s, a, mu, weight[i])); } } break; } case nsl_sf_stats_exponential_power: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exp_pow_param_deriv(j, x, a, s, b, mu, weight[i])); } } break; } case nsl_sf_stats_rayleigh: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_param_deriv(j, x, a, s, weight[i])); } } break; } case nsl_sf_stats_gamma: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gamma_param_deriv(j, x, a, k, t, weight[i])); } } break; } case nsl_sf_stats_flat: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_flat_param_deriv(j, x, A, b, a, weight[i])); } } break; } case nsl_sf_stats_chi_squared: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_chi_square_param_deriv(j, x, a, nu, weight[i])); } } break; } case nsl_sf_stats_tdist: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_students_t_param_deriv(j, x, a, nu, weight[i])); } } break; } case nsl_sf_stats_fdist: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_fdist_param_deriv(j, x, a, n1, n2, weight[i])); } } break; } case nsl_sf_stats_beta: case nsl_sf_stats_pareto: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_beta: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_beta_param_deriv(j, x, A, a, b, weight[i])); break; case nsl_sf_stats_pareto: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pareto_param_deriv(j, x, A, a, b, weight[i])); break; } } } } break; } case nsl_sf_stats_weibull: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_weibull_param_deriv(j, x, a, k, l, mu, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } } } break; } case nsl_sf_stats_gumbel1: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel1_param_deriv(j, x, a, s, mu, b, weight[i])); } } break; } case nsl_sf_stats_gumbel2: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel2_param_deriv(j, x, A, a, b, mu, weight[i])); } } break; } case nsl_sf_stats_poisson: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_poisson_param_deriv(j, x, a, l, weight[i])); } } break; } case nsl_sf_stats_maxwell_boltzmann: { // Y(x) = a*sqrt(2/pi) * x^2/s^3 * exp(-(x/s)^2/2) const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_maxwell_param_deriv(j, x, a, s, weight[i])); } } break; } case nsl_sf_stats_frechet: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double g = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_frechet_param_deriv(j, x, a, g, s, mu, weight[i])); } } break; } case nsl_sf_stats_landau: { // const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); for (size_t i = 0; i < n; i++) { x = xVector[i]; if (fixed[0]) gsl_matrix_set(J, (size_t)i, 0, 0.); else gsl_matrix_set(J, (size_t)i, 0, nsl_fit_model_landau_param_deriv(0, x, weight[i])); } break; } case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double N = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_binomial_param_deriv(j, x, a, p, N, weight[i])); break; case nsl_sf_stats_negative_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_negative_binomial_param_deriv(j, x, a, p, N, weight[i])); break; case nsl_sf_stats_pascal: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pascal_param_deriv(j, x, a, p, N, weight[i])); break; } } } } break; } case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_geometric: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_geometric_param_deriv(j, x, a, p, weight[i])); break; case nsl_sf_stats_logarithmic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logarithmic_param_deriv(j, x, a, p, weight[i])); break; } } } } break; } case nsl_sf_stats_hypergeometric: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hypergeometric_param_deriv(j, x, a, n1, n2, t, weight[i])); } } break; } // unused distributions case nsl_sf_stats_levy_alpha_stable: case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; } break; case nsl_fit_model_custom: QByteArray funcba = ((struct data*)params)->func->toLatin1(); const char* func = funcba.data(); QByteArray nameba; double value; const unsigned int np = paramNames->size(); for (size_t i = 0; i < n; i++) { x = xVector[i]; assign_variable("x", x); for (unsigned int j = 0; j < np; j++) { for (unsigned int k = 0; k < np; k++) { if (k != j) { nameba = paramNames->at(k).toLatin1(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, k), min[k], max[k]); assign_variable(nameba.data(), value); } } nameba = paramNames->at(j).toLatin1(); const char *name = nameba.data(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, j), min[j], max[j]); assign_variable(name, value); const double f_p = parse(func); double eps = 1.e-9; if (std::abs(f_p) > 0) eps *= std::abs(f_p); // scale step size with function value value += eps; assign_variable(name, value); const double f_pdp = parse(func); // DEBUG("evaluate deriv"<* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); residualsVector = static_cast* >(residualsColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->addChild(residualsColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { DEBUG(" Clear columns") xVector->clear(); yVector->clear(); //TODO: residualsVector->clear(); } DEBUG("XYFitCurvePrivate::prepareResultColumns() DONE") } void XYFitCurvePrivate::recalculate() { DEBUG("XYFitCurvePrivate::recalculate()"); QElapsedTimer timer; timer.start(); // prepare source data columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { DEBUG(" spreadsheet columns as data source"); tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { DEBUG(" curve columns as data source"); tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } // clear the previous result fitResult = XYFitCurve::FitResult(); if (!tmpXDataColumn || !tmpYDataColumn) { DEBUG(" ERROR: Preparing source data columns failed!"); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } prepareResultColumns(); //fit settings const unsigned int maxIters = fitData.maxIterations; //maximal number of iterations const double delta = fitData.eps; //fit tolerance const unsigned int np = fitData.paramNames.size(); //number of fit parameters if (np == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Model has no parameters."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (yErrorColumn) { if (yErrorColumn->rowCount() < tmpXDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Not sufficient weight data points provided."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } } //copy all valid data point for the fit to temporary vectors QVector xdataVector; QVector ydataVector; QVector xerrorVector; QVector yerrorVector; double xmin, xmax; if (fitData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = fitData.fitRange.first(); xmax = fitData.fitRange.last(); } DEBUG(" fit range = " << xmin << " .. " << xmax); //logic from XYAnalysisCurve::copyData(), extended by the handling of error columns. //TODO: decide how to deal with non-numerical error columns int rowCount = qMin(tmpXDataColumn->rowCount(), tmpYDataColumn->rowCount()); for (int row = 0; row < rowCount; ++row) { // omit invalid data if (!tmpXDataColumn->isValid(row) || tmpXDataColumn->isMasked(row) || !tmpYDataColumn->isValid(row) || tmpYDataColumn->isMasked(row)) continue; double x = NAN; switch (tmpXDataColumn->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: x = tmpXDataColumn->valueAt(row); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: x = tmpXDataColumn->integerAt(row); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: x = tmpXDataColumn->bigIntAt(row); break; - case AbstractColumn::Text: // not valid + case AbstractColumn::ColumnMode::Text: // not valid break; - case AbstractColumn::DateTime: - case AbstractColumn::Day: - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: x = tmpXDataColumn->dateTimeAt(row).toMSecsSinceEpoch(); } double y = NAN; switch (tmpYDataColumn->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: y = tmpYDataColumn->valueAt(row); break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: y = tmpYDataColumn->integerAt(row); break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: y = tmpYDataColumn->bigIntAt(row); break; - case AbstractColumn::Text: // not valid + case AbstractColumn::ColumnMode::Text: // not valid break; - case AbstractColumn::DateTime: - case AbstractColumn::Day: - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: y = tmpYDataColumn->dateTimeAt(row).toMSecsSinceEpoch(); } // only when inside given range if (x >= xmin && x <= xmax) { if ((!xErrorColumn && !yErrorColumn) || !fitData.useDataErrors) { // x-y xdataVector.append(x); ydataVector.append(y); } else if (!xErrorColumn && yErrorColumn) { // x-y-dy if (!std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(x); ydataVector.append(y); yerrorVector.append(yErrorColumn->valueAt(row)); } } else if (xErrorColumn && yErrorColumn) { // x-y-dx-dy if (!std::isnan(xErrorColumn->valueAt(row)) && !std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(x); ydataVector.append(y); xerrorVector.append(xErrorColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } } } //number of data points to fit const size_t n = xdataVector.size(); DEBUG(" number of data points: " << n); if (n == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("No data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (n < np) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("The number of data points (%1) must be greater than or equal to the number of parameters (%2).", n, np); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (fitData.model.simplified().isEmpty()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Fit model not specified."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* xerror = xerrorVector.data(); // size may be 0 double* yerror = yerrorVector.data(); // size may be 0 DEBUG(" x error vector size: " << xerrorVector.size()); DEBUG(" y error vector size: " << yerrorVector.size()); double* weight = new double[n]; for (size_t i = 0; i < n; i++) weight[i] = 1.; const double minError = 1.e-199; // minimum error for weighting switch (fitData.yWeightsType) { case nsl_fit_weight_no: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative_fit: break; case nsl_fit_weight_instrumental: // yerror are sigmas for (int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./gsl_pow_2(qMax(yerror[i], qMax(sqrt(minError), fabs(ydata[i]) * 1.e-15))); break; case nsl_fit_weight_direct: // yerror are weights for (int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = yerror[i]; break; case nsl_fit_weight_inverse: // yerror are inverse weights for (int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./qMax(yerror[i], qMax(minError, fabs(ydata[i]) * 1.e-15)); break; case nsl_fit_weight_statistical: for (int i = 0; i < (int)n; i++) weight[i] = 1./qMax(ydata[i], minError); break; case nsl_fit_weight_relative: for (int i = 0; i < (int)n; i++) weight[i] = 1./qMax(gsl_pow_2(ydata[i]), minError); break; } /////////////////////// GSL >= 2 has a complete new interface! But the old one is still supported. /////////////////////////// // GSL >= 2 : "the 'fdf' field of gsl_multifit_function_fdf is now deprecated and does not need to be specified for nonlinear least squares problems" unsigned int nf = 0; // number of fixed parameter for (unsigned int i = 0; i < np; i++) { const bool fixed = fitData.paramFixed.data()[i]; if (fixed) nf++; DEBUG(" parameter " << i << " fixed: " << fixed); } //function to fit gsl_multifit_function_fdf f; DEBUG(" model = " << STDSTRING(fitData.model)); struct data params = {n, xdata, ydata, weight, fitData.modelCategory, fitData.modelType, fitData.degree, &fitData.model, &fitData.paramNames, fitData.paramLowerLimits.data(), fitData.paramUpperLimits.data(), fitData.paramFixed.data()}; f.f = &func_f; f.df = &func_df; f.fdf = &func_fdf; f.n = n; f.p = np; f.params = ¶ms; DEBUG(" initialize the derivative solver (using Levenberg-Marquardt robust solver)"); const gsl_multifit_fdfsolver_type* T = gsl_multifit_fdfsolver_lmsder; gsl_multifit_fdfsolver* s = gsl_multifit_fdfsolver_alloc(T, n, np); DEBUG(" set start values"); double* x_init = fitData.paramStartValues.data(); double* x_min = fitData.paramLowerLimits.data(); double* x_max = fitData.paramUpperLimits.data(); DEBUG(" scale start values if limits are set"); for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_unbound(x_init[i], x_min[i], x_max[i]); DEBUG(" DONE"); gsl_vector_view x = gsl_vector_view_array(x_init, np); DEBUG(" Turning off GSL error handler to avoid overflow/underflow"); gsl_set_error_handler_off(); DEBUG(" Initialize solver with function f and initial guess x"); gsl_multifit_fdfsolver_set(s, &f, &x.vector); DEBUG(" Iterate ..."); int status; unsigned int iter = 0; fitResult.solverOutput.clear(); writeSolverState(s); do { iter++; DEBUG(" iter " << iter); // update weights for Y-depending weights (using function values from residuals) if (fitData.yWeightsType == nsl_fit_weight_statistical_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i)/sqrt(weight[i]) + ydata[i]); // 1/Y_i } else if (fitData.yWeightsType == nsl_fit_weight_relative_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(gsl_vector_get(s->f, i)/sqrt(weight[i]) + ydata[i]); // 1/Y_i^2 } DEBUG(" run fdfsolver_iterate"); status = gsl_multifit_fdfsolver_iterate(s); DEBUG(" fdfsolver_iterate DONE"); writeSolverState(s); if (status) { DEBUG(" iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); DEBUG(" iter " << iter << ", test status = " << status); } while (status == GSL_CONTINUE && iter < maxIters); // second run for x-error fitting if (xerrorVector.size() > 0) { DEBUG(" Rerun fit with x errors"); unsigned int iter2 = 0; double chisq = 0, chisqOld = 0; double *fun = new double[n]; do { iter2++; chisqOld = chisq; //printf("iter2 = %d\n", iter2); // calculate function from residuals for (size_t i = 0; i < n; i++) fun[i] = gsl_vector_get(s->f, i) * 1./sqrt(weight[i]) + ydata[i]; // calculate weight[i] for (size_t i = 0; i < n; i++) { // calculate df[i] size_t index = i-1; if (i == 0) index = i; if (i == n-1) index = i-2; double df = (fun[index+1] - fun[index])/(xdata[index+1] - xdata[index]); //printf("df = %g\n", df); double sigmasq = 1.; switch (fitData.xWeightsType) { // x-error type: f'(x)^2*s_x^2 = f'(x)/w_x case nsl_fit_weight_no: break; case nsl_fit_weight_direct: // xerror = w_x sigmasq = df*df/qMax(xerror[i], minError); break; case nsl_fit_weight_instrumental: // xerror = s_x sigmasq = df*df*xerror[i]*xerror[i]; break; case nsl_fit_weight_inverse: // xerror = 1/w_x = s_x^2 sigmasq = df*df*xerror[i]; break; case nsl_fit_weight_statistical: // s_x^2 = 1/w_x = x sigmasq = xdata[i]; break; case nsl_fit_weight_relative: // s_x^2 = 1/w_x = x^2 sigmasq = xdata[i]*xdata[i]; break; case nsl_fit_weight_statistical_fit: // unused case nsl_fit_weight_relative_fit: break; } if (yerrorVector.size() > 0) { switch (fitData.yWeightsType) { // y-error types: s_y^2 = 1/w_y case nsl_fit_weight_no: break; case nsl_fit_weight_direct: // yerror = w_y sigmasq += 1./qMax(yerror[i], minError); break; case nsl_fit_weight_instrumental: // yerror = s_y sigmasq += yerror[i]*yerror[i]; break; case nsl_fit_weight_inverse: // yerror = 1/w_y sigmasq += yerror[i]; break; case nsl_fit_weight_statistical: // unused case nsl_fit_weight_relative: break; case nsl_fit_weight_statistical_fit: // s_y^2 = 1/w_y = Y_i sigmasq += fun[i]; break; case nsl_fit_weight_relative_fit: // s_y^2 = 1/w_y = Y_i^2 sigmasq += fun[i]*fun[i]; break; } } //printf ("sigma[%d] = %g\n", i, sqrt(sigmasq)); weight[i] = 1./qMax(sigmasq, minError); } // update weights gsl_multifit_fdfsolver_set(s, &f, &x.vector); do { // fit iter++; writeSolverState(s); status = gsl_multifit_fdfsolver_iterate(s); //printf ("status = %s\n", gsl_strerror (status)); if (status) { DEBUG(" iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); chisq = gsl_blas_dnrm2(s->f); } while (iter2 < maxIters && fabs(chisq-chisqOld) > fitData.eps); delete[] fun; } delete[] weight; // unscale start parameter for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_bound(x_init[i], x_min[i], x_max[i]); //get the covariance matrix //TODO: scale the Jacobian when limits are used before constructing the covar matrix? gsl_matrix* covar = gsl_matrix_alloc(np, np); #if GSL_MAJOR_VERSION >= 2 // the Jacobian is not part of the solver anymore gsl_matrix *J = gsl_matrix_alloc(s->fdf->n, s->fdf->p); gsl_multifit_fdfsolver_jac(s, J); gsl_multifit_covar(J, 0.0, covar); #else gsl_multifit_covar(s->J, 0.0, covar); #endif //write the result fitResult.available = true; fitResult.valid = true; fitResult.status = gslErrorToString(status); fitResult.iterations = iter; fitResult.dof = n - (np - nf); // samples - (parameter - fixed parameter) //gsl_blas_dnrm2() - computes the Euclidian norm (||r||_2 = \sqrt {\sum r_i^2}) of the vector with the elements weight[i]*(Yi - y[i]) //gsl_blas_dasum() - computes the absolute sum \sum |r_i| of the elements of the vector with the elements weight[i]*(Yi - y[i]) fitResult.sse = gsl_pow_2(gsl_blas_dnrm2(s->f)); if (fitResult.dof != 0) { fitResult.rms = fitResult.sse/fitResult.dof; fitResult.rsd = sqrt(fitResult.rms); } fitResult.mse = fitResult.sse/n; fitResult.rmse = sqrt(fitResult.mse); fitResult.mae = gsl_blas_dasum(s->f)/n; // SST needed for coefficient of determination, R-squared fitResult.sst = gsl_stats_tss(ydata, 1, n); // for a linear model without intercept R-squared is calculated differently // see https://cran.r-project.org/doc/FAQ/R-FAQ.html#Why-does-summary_0028_0029-report-strange-results-for-the-R_005e2-estimate-when-I-fit-a-linear-model-with-no-intercept_003f if (fitData.modelCategory == nsl_fit_model_basic && fitData.modelType == nsl_fit_model_polynomial && fitData.degree == 1 && x_init[0] == 0) { DEBUG(" Using alternative R^2 for linear model without intercept"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } if (fitResult.sst < fitResult.sse) { DEBUG(" Using alternative R^2 since R^2 would be negative (probably custom model without intercept)"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } fitResult.rsquare = nsl_stats_rsquare(fitResult.sse, fitResult.sst); fitResult.rsquareAdj = nsl_stats_rsquareAdj(fitResult.rsquare, np, fitResult.dof, 1); fitResult.chisq_p = nsl_stats_chisq_p(fitResult.sse, fitResult.dof); fitResult.fdist_F = nsl_stats_fdist_F(fitResult.sst, fitResult.rms, np, 1); fitResult.fdist_p = nsl_stats_fdist_p(fitResult.fdist_F, np, fitResult.dof); fitResult.logLik = nsl_stats_logLik(fitResult.sse, n); fitResult.aic = nsl_stats_aic(fitResult.sse, n, np, 1); fitResult.bic = nsl_stats_bic(fitResult.sse, n, np, 1); //parameter values // GSL: const double c = GSL_MAX_DBL(1., sqrt(fitResult.rms)); // increase error for poor fit // NIST: const double c = sqrt(fitResult.rms); // increase error for poor fit, decrease for good fit const double c = sqrt(fitResult.rms); fitResult.paramValues.resize(np); fitResult.errorValues.resize(np); fitResult.tdist_tValues.resize(np); fitResult.tdist_pValues.resize(np); fitResult.tdist_marginValues.resize(np); for (unsigned int i = 0; i < np; i++) { // scale resulting values if they are bounded fitResult.paramValues[i] = nsl_fit_map_bound(gsl_vector_get(s->x, i), x_min[i], x_max[i]); // use results as start values if desired if (fitData.useResults) { fitData.paramStartValues.data()[i] = fitResult.paramValues[i]; DEBUG(" saving parameter " << i << ": " << fitResult.paramValues[i] << ' ' << fitData.paramStartValues.data()[i]); } fitResult.errorValues[i] = c*sqrt(gsl_matrix_get(covar, i, i)); fitResult.tdist_tValues[i] = nsl_stats_tdist_t(fitResult.paramValues.at(i), fitResult.errorValues.at(i)); fitResult.tdist_pValues[i] = nsl_stats_tdist_p(fitResult.tdist_tValues.at(i), fitResult.dof); fitResult.tdist_marginValues[i] = nsl_stats_tdist_margin(0.05, fitResult.dof, fitResult.errorValues.at(i)); } // fill residuals vector. To get residuals on the correct x values, fill the rest with zeros. residualsVector->resize(tmpXDataColumn->rowCount()); DEBUG(" Residual vector size: " << residualsVector->size()) if (fitData.autoRange) { // evaluate full range of residuals xVector->resize(tmpXDataColumn->rowCount()); auto mode = tmpXDataColumn->columnMode(); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) - if (mode == AbstractColumn::Numeric) + if (mode == AbstractColumn::ColumnMode::Numeric) (*xVector)[i] = tmpXDataColumn->valueAt(i); - else if (mode == AbstractColumn::Integer) + else if (mode == AbstractColumn::ColumnMode::Integer) (*xVector)[i] = tmpXDataColumn->integerAt(i); - else if (mode == AbstractColumn::BigInt) + else if (mode == AbstractColumn::ColumnMode::BigInt) (*xVector)[i] = tmpXDataColumn->bigIntAt(i); - else if (mode == AbstractColumn::DateTime) + else if (mode == AbstractColumn::ColumnMode::DateTime) (*xVector)[i] = tmpXDataColumn->dateTimeAt(i).toMSecsSinceEpoch(); ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = parser->evaluateCartesian(fitData.model, xVector, residualsVector, fitData.paramNames, fitResult.paramValues); if (rc) { for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*residualsVector)[i] = tmpYDataColumn->valueAt(i) - (*residualsVector)[i]; } else { DEBUG(" ERROR: Failed parsing residuals") residualsVector->clear(); } } else { // only selected range size_t j = 0; for (int i = 0; i < tmpXDataColumn->rowCount(); i++) { if (tmpXDataColumn->valueAt(i) >= xmin && tmpXDataColumn->valueAt(i) <= xmax) residualsVector->data()[i] = - gsl_vector_get(s->f, j++); else // outside range residualsVector->data()[i] = 0; } } residualsColumn->setChanged(); //free resources gsl_multifit_fdfsolver_free(s); gsl_matrix_free(covar); //calculate the fit function (vectors) evaluate(); fitResult.elapsedTime = timer.elapsed(); sourceDataChangedSinceLastRecalc = false; DEBUG("XYFitCurvePrivate::recalculate() DONE"); } /* evaluate fit function (preview == true: use start values, default: false) */ void XYFitCurvePrivate::evaluate(bool preview) { DEBUG("XYFitCurvePrivate::evaluate() preview = " << preview); // prepare source data columns const AbstractColumn* tmpXDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { DEBUG(" spreadsheet columns as data source"); tmpXDataColumn = xDataColumn; } else { DEBUG(" curve columns as data source"); if (dataSourceCurve) tmpXDataColumn = dataSourceCurve->xColumn(); } if (!tmpXDataColumn) { DEBUG(" ERROR: Preparing source data column failed!"); recalcLogicalPoints(); emit q->dataChanged(); return; } //only needed for preview (else we have all columns) // should not harm even if not in preview now that residuals are not cleared if (preview) prepareResultColumns(); if (!xVector || !yVector) { DEBUG(" xVector or yVector not defined!"); recalcLogicalPoints(); emit q->dataChanged(); return; } if (fitData.model.simplified().isEmpty()) { DEBUG(" no fit-model specified."); recalcLogicalPoints(); emit q->dataChanged(); return; } ExpressionParser* parser = ExpressionParser::getInstance(); double xmin, xmax; if (fitData.autoEvalRange) { // evaluate fit on full data range xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { // use given range for evaluation xmin = fitData.evalRange.first(); xmax = fitData.evalRange.last(); } DEBUG(" eval range = " << xmin << " .. " << xmax); xVector->resize((int)fitData.evaluatedPoints); yVector->resize((int)fitData.evaluatedPoints); DEBUG(" vector size = " << xVector->size()); QVector paramValues = fitResult.paramValues; if (preview) // results not available yet paramValues = fitData.paramStartValues; bool rc = parser->evaluateCartesian(fitData.model, QString::number(xmin), QString::number(xmax), (int)fitData.evaluatedPoints, xVector, yVector, fitData.paramNames, paramValues); if (!rc) { DEBUG(" ERROR: Parsing fit function failed") xVector->clear(); yVector->clear(); residualsVector->clear(); } recalcLogicalPoints(); emit q->dataChanged(); DEBUG("XYFitCurvePrivate::evaluate() DONE"); } /*! * writes out the current state of the solver \c s */ void XYFitCurvePrivate::writeSolverState(gsl_multifit_fdfsolver* s) { QString state; //current parameter values, semicolon separated double* min = fitData.paramLowerLimits.data(); double* max = fitData.paramUpperLimits.data(); for (int i = 0; i < fitData.paramNames.size(); ++i) { const double x = gsl_vector_get(s->x, i); // map parameter if bounded state += QString::number(nsl_fit_map_bound(x, min[i], max[i])) + '\t'; } //current value of the chi2-function state += QString::number(gsl_pow_2(gsl_blas_dnrm2(s->f))); state += ';'; DEBUG(" chi = " << gsl_pow_2(gsl_blas_dnrm2(s->f))); fitResult.solverOutput += state; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFitCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFitCurve); writer->writeStartElement("xyFitCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fit-curve specific information //fit data - only save model expression and parameter names for custom model, otherwise they are set in XYFitCurve::initFitData() writer->writeStartElement("fitData"); WRITE_COLUMN(d->xErrorColumn, xErrorColumn); WRITE_COLUMN(d->yErrorColumn, yErrorColumn); writer->writeAttribute("autoRange", QString::number(d->fitData.autoRange)); writer->writeAttribute("fitRangeMin", QString::number(d->fitData.fitRange.first(), 'g', 15)); writer->writeAttribute("fitRangeMax", QString::number(d->fitData.fitRange.last(), 'g', 15)); writer->writeAttribute("modelCategory", QString::number(d->fitData.modelCategory)); writer->writeAttribute("modelType", QString::number(d->fitData.modelType)); writer->writeAttribute("xWeightsType", QString::number(d->fitData.xWeightsType)); writer->writeAttribute("weightsType", QString::number(d->fitData.yWeightsType)); writer->writeAttribute("degree", QString::number(d->fitData.degree)); if (d->fitData.modelCategory == nsl_fit_model_custom) writer->writeAttribute("model", d->fitData.model); writer->writeAttribute("maxIterations", QString::number(d->fitData.maxIterations)); writer->writeAttribute("eps", QString::number(d->fitData.eps, 'g', 15)); writer->writeAttribute("evaluatedPoints", QString::number(d->fitData.evaluatedPoints)); writer->writeAttribute("autoEvalRange", QString::number(d->fitData.autoEvalRange)); writer->writeAttribute("useDataErrors", QString::number(d->fitData.useDataErrors)); writer->writeAttribute("useResults", QString::number(d->fitData.useResults)); writer->writeAttribute("previewEnabled", QString::number(d->fitData.previewEnabled)); if (d->fitData.modelCategory == nsl_fit_model_custom) { writer->writeStartElement("paramNames"); foreach (const QString &name, d->fitData.paramNames) writer->writeTextElement("name", name); writer->writeEndElement(); } writer->writeStartElement("paramStartValues"); foreach (const double &value, d->fitData.paramStartValues) writer->writeTextElement("startValue", QString::number(value, 'g', 15)); writer->writeEndElement(); // use 16 digits to handle -DBL_MAX writer->writeStartElement("paramLowerLimits"); foreach (const double &limit, d->fitData.paramLowerLimits) writer->writeTextElement("lowerLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); // use 16 digits to handle DBL_MAX writer->writeStartElement("paramUpperLimits"); foreach (const double &limit, d->fitData.paramUpperLimits) writer->writeTextElement("upperLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); writer->writeStartElement("paramFixed"); foreach (const double &fixed, d->fitData.paramFixed) writer->writeTextElement("fixed", QString::number(fixed)); writer->writeEndElement(); writer->writeEndElement(); //"fitData" //fit results (generated columns and goodness of the fit) writer->writeStartElement("fitResult"); writer->writeAttribute("available", QString::number(d->fitResult.available)); writer->writeAttribute("valid", QString::number(d->fitResult.valid)); writer->writeAttribute("status", d->fitResult.status); writer->writeAttribute("iterations", QString::number(d->fitResult.iterations)); writer->writeAttribute("time", QString::number(d->fitResult.elapsedTime)); writer->writeAttribute("dof", QString::number(d->fitResult.dof)); writer->writeAttribute("sse", QString::number(d->fitResult.sse, 'g', 15)); writer->writeAttribute("sst", QString::number(d->fitResult.sst, 'g', 15)); writer->writeAttribute("rms", QString::number(d->fitResult.rms, 'g', 15)); writer->writeAttribute("rsd", QString::number(d->fitResult.rsd, 'g', 15)); writer->writeAttribute("mse", QString::number(d->fitResult.mse, 'g', 15)); writer->writeAttribute("rmse", QString::number(d->fitResult.rmse, 'g', 15)); writer->writeAttribute("mae", QString::number(d->fitResult.mae, 'g', 15)); writer->writeAttribute("rsquare", QString::number(d->fitResult.rsquare, 'g', 15)); writer->writeAttribute("rsquareAdj", QString::number(d->fitResult.rsquareAdj, 'g', 15)); writer->writeAttribute("chisq_p", QString::number(d->fitResult.chisq_p, 'g', 15)); writer->writeAttribute("fdist_F", QString::number(d->fitResult.fdist_F, 'g', 15)); writer->writeAttribute("fdist_p", QString::number(d->fitResult.fdist_p, 'g', 15)); writer->writeAttribute("aic", QString::number(d->fitResult.aic, 'g', 15)); writer->writeAttribute("bic", QString::number(d->fitResult.bic, 'g', 15)); writer->writeAttribute("solverOutput", d->fitResult.solverOutput); writer->writeStartElement("paramValues"); foreach (const double &value, d->fitResult.paramValues) writer->writeTextElement("value", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("errorValues"); foreach (const double &value, d->fitResult.errorValues) writer->writeTextElement("error", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_tValues"); foreach (const double &value, d->fitResult.tdist_tValues) writer->writeTextElement("tdist_t", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_pValues"); foreach (const double &value, d->fitResult.tdist_pValues) writer->writeTextElement("tdist_p", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_marginValues"); foreach (const double &value, d->fitResult.tdist_marginValues) writer->writeTextElement("tdist_margin", QString::number(value, 'g', 15)); writer->writeEndElement(); //save calculated columns if available if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->save(writer); d->yColumn->save(writer); d->residualsColumn->save(writer); } writer->writeEndElement(); //"fitResult" writer->writeEndElement(); //"xyFitCurve" } //! Load from XML bool XYFitCurve::load(XmlStreamReader* reader, bool preview) { DEBUG("XYFitCurve::load()"); Q_D(XYFitCurve); 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() == "xyFitCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "fitData") { attribs = reader->attributes(); READ_COLUMN(xErrorColumn); READ_COLUMN(yErrorColumn); READ_INT_VALUE("autoRange", fitData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", fitData.fitRange.first()); // old name READ_DOUBLE_VALUE("xRangeMax", fitData.fitRange.last()); // old name READ_DOUBLE_VALUE("fitRangeMin", fitData.fitRange.first()); READ_DOUBLE_VALUE("fitRangeMax", fitData.fitRange.last()); READ_INT_VALUE("modelCategory", fitData.modelCategory, nsl_fit_model_category); READ_INT_VALUE("modelType", fitData.modelType, unsigned int); READ_INT_VALUE("xWeightsType", fitData.xWeightsType, nsl_fit_weight_type); READ_INT_VALUE("weightsType", fitData.yWeightsType, nsl_fit_weight_type); READ_INT_VALUE("degree", fitData.degree, int); if (d->fitData.modelCategory == nsl_fit_model_custom) { READ_STRING_VALUE("model", fitData.model); DEBUG("read model = " << STDSTRING(d->fitData.model)); } READ_INT_VALUE("maxIterations", fitData.maxIterations, int); READ_DOUBLE_VALUE("eps", fitData.eps); READ_INT_VALUE("fittedPoints", fitData.evaluatedPoints, size_t); // old name READ_INT_VALUE("evaluatedPoints", fitData.evaluatedPoints, size_t); READ_INT_VALUE("evaluateFullRange", fitData.autoEvalRange, bool); // old name READ_INT_VALUE("autoEvalRange", fitData.autoEvalRange, bool); READ_INT_VALUE("useDataErrors", fitData.useDataErrors, bool); READ_INT_VALUE("useResults", fitData.useResults, bool); READ_INT_VALUE("previewEnabled", fitData.previewEnabled, bool); //set the model expression and the parameter names (can be derived from the saved values for category, type and degree) XYFitCurve::initFitData(d->fitData); // remove default names and start values d->fitData.paramStartValues.clear(); } else if (!preview && reader->name() == "name") { // needed for custom model d->fitData.paramNames << reader->readElementText(); } else if (!preview && reader->name() == "startValue") { d->fitData.paramStartValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fixed") { d->fitData.paramFixed << (bool)reader->readElementText().toInt(); } else if (!preview && reader->name() == "lowerLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // -DBL_MAX results in conversion error d->fitData.paramLowerLimits << x; else d->fitData.paramLowerLimits << -std::numeric_limits::max(); } else if (!preview && reader->name() == "upperLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // DBL_MAX results in conversion error d->fitData.paramUpperLimits << x; else d->fitData.paramUpperLimits << std::numeric_limits::max(); } else if (!preview && reader->name() == "value") { d->fitResult.paramValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "error") { d->fitResult.errorValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_t") { d->fitResult.tdist_tValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_p") { d->fitResult.tdist_pValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_margin") { d->fitResult.tdist_marginValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fitResult") { attribs = reader->attributes(); READ_INT_VALUE("available", fitResult.available, int); READ_INT_VALUE("valid", fitResult.valid, int); READ_STRING_VALUE("status", fitResult.status); READ_INT_VALUE("iterations", fitResult.iterations, int); READ_INT_VALUE("time", fitResult.elapsedTime, int); READ_DOUBLE_VALUE("dof", fitResult.dof); READ_DOUBLE_VALUE("sse", fitResult.sse); READ_DOUBLE_VALUE("sst", fitResult.sst); READ_DOUBLE_VALUE("rms", fitResult.rms); READ_DOUBLE_VALUE("rsd", fitResult.rsd); READ_DOUBLE_VALUE("mse", fitResult.mse); READ_DOUBLE_VALUE("rmse", fitResult.rmse); READ_DOUBLE_VALUE("mae", fitResult.mae); READ_DOUBLE_VALUE("rsquare", fitResult.rsquare); READ_DOUBLE_VALUE("rsquareAdj", fitResult.rsquareAdj); READ_DOUBLE_VALUE("chisq_p", fitResult.chisq_p); READ_DOUBLE_VALUE("fdist_F", fitResult.fdist_F); READ_DOUBLE_VALUE("fdist_p", fitResult.fdist_p); READ_DOUBLE_VALUE("aic", fitResult.aic); READ_DOUBLE_VALUE("bic", fitResult.bic); READ_STRING_VALUE("solverOutput", fitResult.solverOutput); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } DEBUG("############################ reading column " << STDSTRING(column->name())) if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; else if (column->name() == "residuals") d->residualsColumn = column; } } // older model save the param names also for non-custom models: remove them while (d->fitData.paramNames.size() > d->fitData.paramStartValues.size()) d->fitData.paramNames.removeLast(); if (d->fitData.paramNamesUtf8.isEmpty()) d->fitData.paramNamesUtf8 << d->fitData.paramNames; DEBUG("# params = " << d->fitData.paramNames.size()); if (preview) return true; // new fit model style (reset model type of old projects) if (d->fitData.modelCategory == nsl_fit_model_basic && d->fitData.modelType >= NSL_FIT_MODEL_BASIC_COUNT) { DEBUG("RESET old fit model"); d->fitData.modelType = 0; d->fitData.degree = 1; d->fitData.paramNames.clear(); d->fitData.paramNamesUtf8.clear(); // reset size of fields not touched by initFitData() d->fitData.paramStartValues.resize(2); d->fitData.paramFixed.resize(2); d->fitResult.paramValues.resize(2); d->fitResult.errorValues.resize(2); d->fitResult.tdist_tValues.resize(2); d->fitResult.tdist_pValues.resize(2); d->fitResult.tdist_marginValues.resize(2); } // not present in old projects int np = d->fitResult.paramValues.size(); if (d->fitResult.tdist_tValues.size() == 0) d->fitResult.tdist_tValues.resize(np); if (d->fitResult.tdist_pValues.size() == 0) d->fitResult.tdist_pValues.resize(np); if (d->fitResult.tdist_marginValues.size() == 0) d->fitResult.tdist_marginValues.resize(np); DEBUG("# start values = " << d->fitData.paramStartValues.size()); // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); addChild(d->residualsColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); d->residualsVector = static_cast* >(d->residualsColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp index ce7731f17..2e22bef8b 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp @@ -1,389 +1,389 @@ /*************************************************************************** File : XYFourierFilterCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier filter -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 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 XYFourierFilterCurve \brief A xy-curve defined by a Fourier filter \ingroup worksheet */ #include "XYFourierFilterCurve.h" #include "XYFourierFilterCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" extern "C" { #include #ifdef HAVE_FFTW3 #include #endif #include "backend/nsl/nsl_sf_poly.h" } #include #include #include #include // qWarning() XYFourierFilterCurve::XYFourierFilterCurve(const QString& name) : XYAnalysisCurve(name, new XYFourierFilterCurvePrivate(this), AspectType::XYFourierFilterCurve) { } XYFourierFilterCurve::XYFourierFilterCurve(const QString& name, XYFourierFilterCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYFourierFilterCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYFourierFilterCurve::~XYFourierFilterCurve() = default; void XYFourierFilterCurve::recalculate() { Q_D(XYFourierFilterCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYFourierFilterCurve::icon() const { return QIcon::fromTheme("labplot-xy-fourier-filter-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFourierFilterCurve, XYFourierFilterCurve::FilterData, filterData, filterData) const XYFourierFilterCurve::FilterResult& XYFourierFilterCurve::filterResult() const { Q_D(const XYFourierFilterCurve); return d->filterResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYFourierFilterCurve, SetFilterData, XYFourierFilterCurve::FilterData, filterData, recalculate); void XYFourierFilterCurve::setFilterData(const XYFourierFilterCurve::FilterData& filterData) { Q_D(XYFourierFilterCurve); exec(new XYFourierFilterCurveSetFilterDataCmd(d, filterData, ki18n("%1: set filter options and perform the Fourier filter"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierFilterCurvePrivate::XYFourierFilterCurvePrivate(XYFourierFilterCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYFourierFilterCurvePrivate::~XYFourierFilterCurvePrivate() = default; void XYFourierFilterCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create filter result columns if not available yet, clear them otherwise if (!xColumn) { - xColumn = new Column("x", AbstractColumn::Numeric); - yColumn = new Column("y", AbstractColumn::Numeric); + xColumn = new Column("x", AbstractColumn::ColumnMode::Numeric); + yColumn = new Column("y", AbstractColumn::ColumnMode::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result filterResult = XYFourierFilterCurve::FilterResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the differentiation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (filterData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = filterData.xRange.first(); xmax = filterData.xRange.last(); } int rowCount = qMin(tmpXDataColumn->rowCount(), tmpYDataColumn->rowCount()); for (int row = 0; row < rowCount; ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (std::isnan(tmpXDataColumn->valueAt(row)) || std::isnan(tmpYDataColumn->valueAt(row)) || tmpXDataColumn->isMasked(row) || tmpYDataColumn->isMasked(row)) continue; // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } //number of data points to filter const size_t n = (size_t)xdataVector.size(); if (n == 0) { filterResult.available = true; filterResult.valid = false; filterResult.status = i18n("No data points available."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // filter settings const nsl_filter_type type = filterData.type; const nsl_filter_form form = filterData.form; const int order = filterData.order; const double cutoff = filterData.cutoff, cutoff2 = filterData.cutoff2; const nsl_filter_cutoff_unit unit = filterData.unit, unit2 = filterData.unit2; DEBUG("n ="< 0. Giving up."; return; } DEBUG("cut off @" << cutindex << cutindex2); DEBUG("bandwidth =" << bandwidth); // run filter int status = nsl_filter_fourier(ydata, n, type, form, order, cutindex, bandwidth); xVector->resize((int)n); yVector->resize((int)n); memcpy(xVector->data(), xdataVector.data(), n*sizeof(double)); memcpy(yVector->data(), ydata, n*sizeof(double)); /////////////////////////////////////////////////////////// //write the result filterResult.available = true; filterResult.valid = true; filterResult.status = gslErrorToString(status); filterResult.elapsedTime = timer.elapsed(); //redraw the curve recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFourierFilterCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFourierFilterCurve); writer->writeStartElement("xyFourierFilterCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fourier_filter-curve specific information //filter data writer->writeStartElement("filterData"); writer->writeAttribute( "autoRange", QString::number(d->filterData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->filterData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->filterData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->filterData.type) ); writer->writeAttribute( "form", QString::number(d->filterData.form) ); writer->writeAttribute( "order", QString::number(d->filterData.order) ); writer->writeAttribute( "cutoff", QString::number(d->filterData.cutoff) ); writer->writeAttribute( "unit", QString::number(d->filterData.unit) ); writer->writeAttribute( "cutoff2", QString::number(d->filterData.cutoff2) ); writer->writeAttribute( "unit2", QString::number(d->filterData.unit2) ); writer->writeEndElement();// filterData //filter results (generated columns) writer->writeStartElement("filterResult"); writer->writeAttribute( "available", QString::number(d->filterResult.available) ); writer->writeAttribute( "valid", QString::number(d->filterResult.valid) ); writer->writeAttribute( "status", d->filterResult.status ); writer->writeAttribute( "time", QString::number(d->filterResult.elapsedTime) ); //save calculated columns if available if (d->xColumn && d->yColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"filterResult" writer->writeEndElement(); //"xyFourierFilterCurve" } //! Load from XML bool XYFourierFilterCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFourierFilterCurve); 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() == "xyFourierFilterCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "filterData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", filterData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", filterData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", filterData.xRange.last()); READ_INT_VALUE("type", filterData.type, nsl_filter_type); READ_INT_VALUE("form", filterData.form, nsl_filter_form); READ_INT_VALUE("order", filterData.order, int); READ_DOUBLE_VALUE("cutoff", filterData.cutoff); READ_INT_VALUE("unit", filterData.unit, nsl_filter_cutoff_unit); READ_DOUBLE_VALUE("cutoff2", filterData.cutoff2); READ_INT_VALUE("unit2", filterData.unit2, nsl_filter_cutoff_unit); } else if (!preview && reader->name() == "filterResult") { attribs = reader->attributes(); READ_INT_VALUE("available", filterResult.available, int); READ_INT_VALUE("valid", filterResult.valid, int); READ_STRING_VALUE("status", filterResult.status); READ_INT_VALUE("time", filterResult.elapsedTime, int); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp index 814df2a47..40241979b 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp @@ -1,375 +1,375 @@ /*************************************************************************** File : XYFourierTransformCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier transform -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 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 XYFourierTransformCurve \brief A xy-curve defined by a Fourier transform \ingroup worksheet */ #include "XYFourierTransformCurve.h" #include "XYFourierTransformCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" extern "C" { #include "backend/nsl/nsl_sf_poly.h" } #include #include #include #include #include // qWarning() XYFourierTransformCurve::XYFourierTransformCurve(const QString& name) : XYAnalysisCurve(name, new XYFourierTransformCurvePrivate(this), AspectType::XYFourierTransformCurve) { } XYFourierTransformCurve::XYFourierTransformCurve(const QString& name, XYFourierTransformCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYFourierTransformCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYFourierTransformCurve::~XYFourierTransformCurve() = default; void XYFourierTransformCurve::recalculate() { Q_D(XYFourierTransformCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYFourierTransformCurve::icon() const { return QIcon::fromTheme("labplot-xy-fourier-transform-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFourierTransformCurve, XYFourierTransformCurve::TransformData, transformData, transformData) const XYFourierTransformCurve::TransformResult& XYFourierTransformCurve::transformResult() const { Q_D(const XYFourierTransformCurve); return d->transformResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYFourierTransformCurve, SetTransformData, XYFourierTransformCurve::TransformData, transformData, recalculate); void XYFourierTransformCurve::setTransformData(const XYFourierTransformCurve::TransformData& transformData) { Q_D(XYFourierTransformCurve); exec(new XYFourierTransformCurveSetTransformDataCmd(d, transformData, ki18n("%1: set transform options and perform the Fourier transform"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierTransformCurvePrivate::XYFourierTransformCurvePrivate(XYFourierTransformCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYFourierTransformCurvePrivate::~XYFourierTransformCurvePrivate() = default; void XYFourierTransformCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create transform result columns if not available yet, clear them otherwise if (!xColumn) { - xColumn = new Column("x", AbstractColumn::Numeric); - yColumn = new Column("y", AbstractColumn::Numeric); + xColumn = new Column("x", AbstractColumn::ColumnMode::Numeric); + yColumn = new Column("y", AbstractColumn::ColumnMode::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result transformResult = XYFourierTransformCurve::TransformResult(); if (!xDataColumn || !yDataColumn) { recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the transform to temporary vectors QVector xdataVector; QVector ydataVector; const double xmin = transformData.xRange.first(); const double xmax = transformData.xRange.last(); int rowCount = qMin(xDataColumn->rowCount(), yDataColumn->rowCount()); for (int row = 0; row < rowCount; ++row) { // only copy those data where _all_ values (for x and y, if given) are valid if (std::isnan(xDataColumn->valueAt(row)) || std::isnan(yDataColumn->valueAt(row)) || xDataColumn->isMasked(row) || yDataColumn->isMasked(row)) continue; // only when inside given range if (xDataColumn->valueAt(row) >= xmin && xDataColumn->valueAt(row) <= xmax) { xdataVector.append(xDataColumn->valueAt(row)); ydataVector.append(yDataColumn->valueAt(row)); } } //number of data points to transform unsigned int n = (unsigned int)ydataVector.size(); if (n == 0) { transformResult.available = true; transformResult.valid = false; transformResult.status = i18n("No data points available."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // transform settings const nsl_sf_window_type windowType = transformData.windowType; const nsl_dft_result_type type = transformData.type; const bool twoSided = transformData.twoSided; const bool shifted = transformData.shifted; const nsl_dft_xscale xScale = transformData.xScale; DEBUG("n =" << n); DEBUG("window type:" << nsl_sf_window_type_name[windowType]); DEBUG("type:" << nsl_dft_result_type_name[type]); DEBUG("scale:" << nsl_dft_xscale_name[xScale]); DEBUG("two sided:" << twoSided); DEBUG("shifted:" << shifted); #ifndef NDEBUG QDebug out = qDebug(); for (unsigned int i = 0; i < n; i++) out<= n/2 && shifted) xdata[i] = (n-1)/(xmax-xmin)*(i/(double)n-1.); else xdata[i] = (n-1)*i/(xmax-xmin)/n; } break; case nsl_dft_xscale_index: for (unsigned int i = 0; i < N; i++) { if (i >= n/2 && shifted) xdata[i] = (int)i-(int) N; else xdata[i] = i; } break; case nsl_dft_xscale_period: { double f0 = (n-1)/(xmax-xmin)/n; for (unsigned int i = 0; i < N; i++) { double f = (n-1)*i/(xmax-xmin)/n; xdata[i] = 1/(f+f0); } break; } } #ifndef NDEBUG out = qDebug(); for (unsigned int i = 0; i < N; i++) out << ydata[i] << '(' << xdata[i] << ')'; #endif xVector->resize((int)N); yVector->resize((int)N); if (shifted) { memcpy(xVector->data(), &xdata[n/2], n/2*sizeof(double)); memcpy(&xVector->data()[n/2], xdata, n/2*sizeof(double)); memcpy(yVector->data(), &ydata[n/2], n/2*sizeof(double)); memcpy(&yVector->data()[n/2], ydata, n/2*sizeof(double)); } else { memcpy(xVector->data(), xdata, N*sizeof(double)); memcpy(yVector->data(), ydata, N*sizeof(double)); } /////////////////////////////////////////////////////////// //write the result transformResult.available = true; transformResult.valid = true; transformResult.status = gslErrorToString(status); transformResult.elapsedTime = timer.elapsed(); //redraw the curve recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFourierTransformCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFourierTransformCurve); writer->writeStartElement("xyFourierTransformCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fourier_transform-curve specific information //transform data writer->writeStartElement("transformData"); writer->writeAttribute( "autoRange", QString::number(d->transformData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->transformData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->transformData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->transformData.type) ); writer->writeAttribute( "twoSided", QString::number(d->transformData.twoSided) ); writer->writeAttribute( "shifted", QString::number(d->transformData.shifted) ); writer->writeAttribute( "xScale", QString::number(d->transformData.xScale) ); writer->writeAttribute( "windowType", QString::number(d->transformData.windowType) ); writer->writeEndElement();// transformData //transform results (generated columns) writer->writeStartElement("transformResult"); writer->writeAttribute( "available", QString::number(d->transformResult.available) ); writer->writeAttribute( "valid", QString::number(d->transformResult.valid) ); writer->writeAttribute( "status", d->transformResult.status ); writer->writeAttribute( "time", QString::number(d->transformResult.elapsedTime) ); //save calculated columns if available if (d->xColumn && d->yColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"transformResult" writer->writeEndElement(); //"xyFourierTransformCurve" } //! Load from XML bool XYFourierTransformCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFourierTransformCurve); 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() == "xyFourierTransformCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "transformData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", transformData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", transformData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", transformData.xRange.last()); READ_INT_VALUE("type", transformData.type, nsl_dft_result_type); READ_INT_VALUE("twoSided", transformData.twoSided, bool); READ_INT_VALUE("shifted", transformData.shifted, bool); READ_INT_VALUE("xScale", transformData.xScale, nsl_dft_xscale); READ_INT_VALUE("windowType", transformData.windowType, nsl_sf_window_type); } else if (!preview && reader->name() == "transformResult") { attribs = reader->attributes(); READ_INT_VALUE("available", transformResult.available, int); READ_INT_VALUE("valid", transformResult.valid, int); READ_STRING_VALUE("status", transformResult.status); READ_INT_VALUE("time", transformResult.elapsedTime, int); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp index 854a6bf48..50e39fc66 100644 --- a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp @@ -1,338 +1,338 @@ /*************************************************************************** File : XYIntegrationCurve.cpp Project : LabPlot Description : A xy-curve defined by an integration -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 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 XYIntegrationCurve \brief A xy-curve defined by an integration \ingroup worksheet */ #include "XYIntegrationCurve.h" #include "XYIntegrationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" extern "C" { #include } #include #include #include #include XYIntegrationCurve::XYIntegrationCurve(const QString& name) : XYAnalysisCurve(name, new XYIntegrationCurvePrivate(this), AspectType::XYIntegrationCurve) { } XYIntegrationCurve::XYIntegrationCurve(const QString& name, XYIntegrationCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYIntegrationCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYIntegrationCurve::~XYIntegrationCurve() = default; void XYIntegrationCurve::recalculate() { Q_D(XYIntegrationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYIntegrationCurve::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYIntegrationCurve, XYIntegrationCurve::IntegrationData, integrationData, integrationData) const XYIntegrationCurve::IntegrationResult& XYIntegrationCurve::integrationResult() const { Q_D(const XYIntegrationCurve); return d->integrationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYIntegrationCurve, SetIntegrationData, XYIntegrationCurve::IntegrationData, integrationData, recalculate); void XYIntegrationCurve::setIntegrationData(const XYIntegrationCurve::IntegrationData& integrationData) { Q_D(XYIntegrationCurve); exec(new XYIntegrationCurveSetIntegrationDataCmd(d, integrationData, ki18n("%1: set options and perform the integration"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYIntegrationCurvePrivate::XYIntegrationCurvePrivate(XYIntegrationCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYIntegrationCurvePrivate::~XYIntegrationCurvePrivate() = default; void XYIntegrationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create integration result columns if not available yet, clear them otherwise if (!xColumn) { - xColumn = new Column("x", AbstractColumn::Numeric); - yColumn = new Column("y", AbstractColumn::Numeric); + xColumn = new Column("x", AbstractColumn::ColumnMode::Numeric); + yColumn = new Column("y", AbstractColumn::ColumnMode::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result integrationResult = XYIntegrationCurve::IntegrationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the integration to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (integrationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = integrationData.xRange.first(); xmax = integrationData.xRange.last(); } XYAnalysisCurve::copyData(xdataVector, ydataVector, tmpXDataColumn, tmpYDataColumn, xmin, xmax); const size_t n = (size_t)xdataVector.size(); // number of data points to integrate if (n < 2) { integrationResult.available = true; integrationResult.valid = false; integrationResult.status = i18n("Not enough data points available."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // integration settings const nsl_int_method_type method = integrationData.method; const bool absolute = integrationData.absolute; DEBUG("method:"<resize((int)np); yVector->resize((int)np); memcpy(xVector->data(), xdata, np * sizeof(double)); memcpy(yVector->data(), ydata, np * sizeof(double)); /////////////////////////////////////////////////////////// //write the result integrationResult.available = true; integrationResult.valid = true; integrationResult.status = QString::number(status); integrationResult.elapsedTime = timer.elapsed(); integrationResult.value = ydata[np-1]; //redraw the curve recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYIntegrationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYIntegrationCurve); writer->writeStartElement("xyIntegrationCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-integration-curve specific information // integration data writer->writeStartElement("integrationData"); writer->writeAttribute( "autoRange", QString::number(d->integrationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->integrationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->integrationData.xRange.last()) ); writer->writeAttribute( "method", QString::number(d->integrationData.method) ); writer->writeAttribute( "absolute", QString::number(d->integrationData.absolute) ); writer->writeEndElement();// integrationData // integration results (generated columns) writer->writeStartElement("integrationResult"); writer->writeAttribute( "available", QString::number(d->integrationResult.available) ); writer->writeAttribute( "valid", QString::number(d->integrationResult.valid) ); writer->writeAttribute( "status", d->integrationResult.status ); writer->writeAttribute( "time", QString::number(d->integrationResult.elapsedTime) ); writer->writeAttribute( "value", QString::number(d->integrationResult.value) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"integrationResult" writer->writeEndElement(); //"xyIntegrationCurve" } //! Load from XML bool XYIntegrationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYIntegrationCurve); 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() == "xyIntegrationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "integrationData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", integrationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", integrationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", integrationData.xRange.last()); READ_INT_VALUE("method", integrationData.method, nsl_int_method_type); READ_INT_VALUE("absolute", integrationData.absolute, bool); } else if (!preview && reader->name() == "integrationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", integrationResult.available, int); READ_INT_VALUE("valid", integrationResult.valid, int); READ_STRING_VALUE("status", integrationResult.status); READ_INT_VALUE("time", integrationResult.elapsedTime, int); READ_DOUBLE_VALUE("value", integrationResult.value); } else if (!preview && reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp index d05ec0510..25aa4db93 100644 --- a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp @@ -1,542 +1,542 @@ /*************************************************************************** File : XYInterpolationCurve.cpp Project : LabPlot Description : A xy-curve defined by an interpolation -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2016-2017 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 XYInterpolationCurve \brief A xy-curve defined by an interpolation \ingroup worksheet */ #include "XYInterpolationCurve.h" #include "XYInterpolationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" extern "C" { #include #include #include "backend/nsl/nsl_diff.h" #include "backend/nsl/nsl_int.h" } #include #include #include XYInterpolationCurve::XYInterpolationCurve(const QString& name) : XYAnalysisCurve(name, new XYInterpolationCurvePrivate(this), AspectType::XYInterpolationCurve) { } XYInterpolationCurve::XYInterpolationCurve(const QString& name, XYInterpolationCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYInterpolationCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYInterpolationCurve::~XYInterpolationCurve() = default; void XYInterpolationCurve::recalculate() { Q_D(XYInterpolationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYInterpolationCurve::icon() const { return QIcon::fromTheme("labplot-xy-interpolation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYInterpolationCurve, XYInterpolationCurve::InterpolationData, interpolationData, interpolationData) const XYInterpolationCurve::InterpolationResult& XYInterpolationCurve::interpolationResult() const { Q_D(const XYInterpolationCurve); return d->interpolationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYInterpolationCurve, SetInterpolationData, XYInterpolationCurve::InterpolationData, interpolationData, recalculate); void XYInterpolationCurve::setInterpolationData(const XYInterpolationCurve::InterpolationData& interpolationData) { Q_D(XYInterpolationCurve); exec(new XYInterpolationCurveSetInterpolationDataCmd(d, interpolationData, ki18n("%1: set options and perform the interpolation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYInterpolationCurvePrivate::XYInterpolationCurvePrivate(XYInterpolationCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYInterpolationCurvePrivate::~XYInterpolationCurvePrivate() = default; void XYInterpolationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create interpolation result columns if not available yet, clear them otherwise if (!xColumn) { - xColumn = new Column("x", AbstractColumn::Numeric); - yColumn = new Column("y", AbstractColumn::Numeric); + xColumn = new Column("x", AbstractColumn::ColumnMode::Numeric); + yColumn = new Column("y", AbstractColumn::ColumnMode::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result interpolationResult = XYInterpolationCurve::InterpolationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { interpolationResult.available = true; interpolationResult.valid = false; interpolationResult.status = i18n("Number of x and y data points must be equal."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the interpolation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (interpolationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = interpolationData.xRange.first(); xmax = interpolationData.xRange.last(); } XYAnalysisCurve::copyData(xdataVector, ydataVector, tmpXDataColumn, tmpYDataColumn, xmin, xmax); //number of data points to interpolate const size_t n = (size_t)xdataVector.size(); if (n < 2) { interpolationResult.available = true; interpolationResult.valid = false; interpolationResult.status = i18n("Not enough data points available."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // interpolation settings const nsl_interp_type type = interpolationData.type; const nsl_interp_pch_variant variant = interpolationData.variant; const double tension = interpolationData.tension; const double continuity = interpolationData.continuity; const double bias = interpolationData.bias; const nsl_interp_evaluate evaluate = interpolationData.evaluate; const size_t npoints = interpolationData.npoints; DEBUG("type:"<data(), yVector->data(), npoints); break; case nsl_interp_evaluate_second_derivative: nsl_diff_second_deriv_second_order(xVector->data(), yVector->data(), npoints); break; case nsl_interp_evaluate_integral: nsl_int_trapezoid(xVector->data(), yVector->data(), npoints, 0); break; } } // check values for (int i = 0; i < (int)npoints; i++) { if ((*yVector)[i] > std::numeric_limits::max()) (*yVector)[i] = std::numeric_limits::max(); else if ((*yVector)[i] < std::numeric_limits::lowest()) (*yVector)[i] = std::numeric_limits::lowest(); } gsl_spline_free(spline); gsl_interp_accel_free(acc); /////////////////////////////////////////////////////////// //write the result interpolationResult.available = true; interpolationResult.valid = true; interpolationResult.status = gslErrorToString(status); interpolationResult.elapsedTime = timer.elapsed(); //redraw the curve recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYInterpolationCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYInterpolationCurve); writer->writeStartElement("xyInterpolationCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-interpolation-curve specific information // interpolation data writer->writeStartElement("interpolationData"); writer->writeAttribute( "autoRange", QString::number(d->interpolationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->interpolationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->interpolationData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->interpolationData.type) ); writer->writeAttribute( "variant", QString::number(d->interpolationData.variant) ); writer->writeAttribute( "tension", QString::number(d->interpolationData.tension) ); writer->writeAttribute( "continuity", QString::number(d->interpolationData.continuity) ); writer->writeAttribute( "bias", QString::number(d->interpolationData.bias) ); writer->writeAttribute( "npoints", QString::number(d->interpolationData.npoints) ); writer->writeAttribute( "pointsMode", QString::number(d->interpolationData.pointsMode) ); writer->writeAttribute( "evaluate", QString::number(d->interpolationData.evaluate) ); writer->writeEndElement();// interpolationData // interpolation results (generated columns) writer->writeStartElement("interpolationResult"); writer->writeAttribute( "available", QString::number(d->interpolationResult.available) ); writer->writeAttribute( "valid", QString::number(d->interpolationResult.valid) ); writer->writeAttribute( "status", d->interpolationResult.status ); writer->writeAttribute( "time", QString::number(d->interpolationResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"interpolationResult" writer->writeEndElement(); //"xyInterpolationCurve" } //! Load from XML bool XYInterpolationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYInterpolationCurve); 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() == "xyInterpolationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "interpolationData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", interpolationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", interpolationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", interpolationData.xRange.last()); READ_INT_VALUE("type", interpolationData.type, nsl_interp_type); READ_INT_VALUE("variant", interpolationData.variant, nsl_interp_pch_variant); READ_DOUBLE_VALUE("tension", interpolationData.tension); READ_DOUBLE_VALUE("continuity", interpolationData.continuity); READ_DOUBLE_VALUE("bias", interpolationData.bias); READ_INT_VALUE("npoints", interpolationData.npoints, size_t); READ_INT_VALUE("pointsMode", interpolationData.pointsMode, XYInterpolationCurve::PointsMode); READ_INT_VALUE("evaluate", interpolationData.evaluate, nsl_interp_evaluate); } else if (!preview && reader->name() == "interpolationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", interpolationResult.available, int); READ_INT_VALUE("valid", interpolationResult.valid, int); READ_STRING_VALUE("status", interpolationResult.status); READ_INT_VALUE("time", interpolationResult.elapsedTime, int); } else if (reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp b/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp index cae81de36..1f04a8093 100644 --- a/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp @@ -1,402 +1,402 @@ /*************************************************************************** File : XYSmoothCurve.cpp Project : LabPlot Description : A xy-curve defined by a smooth -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 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 * * * ***************************************************************************/ /*! \class XYSmoothCurve \brief A xy-curve defined by a smooth \ingroup worksheet */ #include "XYSmoothCurve.h" #include "XYSmoothCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include #include #include #include extern "C" { #include // gsl_pow_* #include "backend/nsl/nsl_stats.h" #include "backend/nsl/nsl_sf_kernel.h" } XYSmoothCurve::XYSmoothCurve(const QString& name) : XYAnalysisCurve(name, new XYSmoothCurvePrivate(this), AspectType::XYSmoothCurve) { } XYSmoothCurve::XYSmoothCurve(const QString& name, XYSmoothCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYSmoothCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYSmoothCurve::~XYSmoothCurve() = default; void XYSmoothCurve::recalculate() { Q_D(XYSmoothCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYSmoothCurve::icon() const { return QIcon::fromTheme("labplot-xy-smoothing-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYSmoothCurve, XYSmoothCurve::SmoothData, smoothData, smoothData) const XYSmoothCurve::SmoothResult& XYSmoothCurve::smoothResult() const { Q_D(const XYSmoothCurve); return d->smoothResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYSmoothCurve, SetSmoothData, XYSmoothCurve::SmoothData, smoothData, recalculate); void XYSmoothCurve::setSmoothData(const XYSmoothCurve::SmoothData& smoothData) { Q_D(XYSmoothCurve); exec(new XYSmoothCurveSetSmoothDataCmd(d, smoothData, ki18n("%1: set options and perform the smooth"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYSmoothCurvePrivate::XYSmoothCurvePrivate(XYSmoothCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYSmoothCurvePrivate::~XYSmoothCurvePrivate() = default; void XYSmoothCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create smooth result columns if not available yet, clear them otherwise if (!xColumn) { - xColumn = new Column("x", AbstractColumn::Numeric); - yColumn = new Column("y", AbstractColumn::Numeric); + xColumn = new Column("x", AbstractColumn::ColumnMode::Numeric); + yColumn = new Column("y", AbstractColumn::ColumnMode::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); if (roughVector) roughVector->clear(); } if (!roughColumn) { - roughColumn = new Column("rough", AbstractColumn::Numeric); + roughColumn = new Column("rough", AbstractColumn::ColumnMode::Numeric); roughVector = static_cast* >(roughColumn->data()); q->addChild(roughColumn); } // clear the previous result smoothResult = XYSmoothCurve::SmoothResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { smoothResult.available = true; smoothResult.valid = false; smoothResult.status = i18n("Number of x and y data points must be equal."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the smooth to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (smoothData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = smoothData.xRange.first(); xmax = smoothData.xRange.last(); } XYAnalysisCurve::copyData(xdataVector, ydataVector, tmpXDataColumn, tmpYDataColumn, xmin, xmax); //number of data points to smooth const size_t n = (size_t)xdataVector.size(); if (n < 2) { smoothResult.available = true; smoothResult.valid = false; smoothResult.status = i18n("Not enough data points available."); recalcLogicalPoints(); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* ydataOriginal = new double[n]; memcpy(ydataOriginal, ydata, n*sizeof(double)); // smooth settings const nsl_smooth_type type = smoothData.type; const size_t points = smoothData.points; const nsl_smooth_weight_type weight = smoothData.weight; const double percentile = smoothData.percentile; const int order = smoothData.order; const nsl_smooth_pad_mode mode = smoothData.mode; const double lvalue = smoothData.lvalue; const double rvalue = smoothData.rvalue; DEBUG("type:"<writeStartElement("smoothData"); writer->writeAttribute( "autoRange", QString::number(d->smoothData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->smoothData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->smoothData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->smoothData.type) ); writer->writeAttribute( "points", QString::number(d->smoothData.points) ); writer->writeAttribute( "weight", QString::number(d->smoothData.weight) ); writer->writeAttribute( "percentile", QString::number(d->smoothData.percentile) ); writer->writeAttribute( "order", QString::number(d->smoothData.order) ); writer->writeAttribute( "mode", QString::number(d->smoothData.mode) ); writer->writeAttribute( "lvalue", QString::number(d->smoothData.lvalue) ); writer->writeAttribute( "rvalue", QString::number(d->smoothData.rvalue) ); writer->writeEndElement();// smoothData // smooth results (generated columns) writer->writeStartElement("smoothResult"); writer->writeAttribute( "available", QString::number(d->smoothResult.available) ); writer->writeAttribute( "valid", QString::number(d->smoothResult.valid) ); writer->writeAttribute( "status", d->smoothResult.status ); writer->writeAttribute( "time", QString::number(d->smoothResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } if (d->roughColumn) d->roughColumn->save(writer); writer->writeEndElement(); //"smoothResult" writer->writeEndElement(); //"xySmoothCurve" } //! Load from XML bool XYSmoothCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYSmoothCurve); 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() == "xySmoothCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "smoothData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", smoothData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", smoothData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", smoothData.xRange.last()); READ_INT_VALUE("type", smoothData.type, nsl_smooth_type); READ_INT_VALUE("points", smoothData.points, size_t); READ_INT_VALUE("weight", smoothData.weight, nsl_smooth_weight_type); READ_DOUBLE_VALUE("percentile", smoothData.percentile); READ_INT_VALUE("order", smoothData.order, int); READ_INT_VALUE("mode", smoothData.mode, nsl_smooth_pad_mode); READ_DOUBLE_VALUE("lvalue", smoothData.lvalue); READ_DOUBLE_VALUE("rvalue", smoothData.rvalue); } else if (!preview && reader->name() == "smoothResult") { attribs = reader->attributes(); READ_INT_VALUE("available", smoothResult.available, int); READ_INT_VALUE("valid", smoothResult.valid, int); READ_STRING_VALUE("status", smoothResult.status); READ_INT_VALUE("time", smoothResult.elapsedTime, int); } else if (!preview && reader->name() == "column") { - Column* column = new Column(QString(), AbstractColumn::Numeric); + Column* column = new Column(QString(), AbstractColumn::ColumnMode::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; else d->roughColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } if (d->roughColumn) { addChild(d->roughColumn); d->roughVector = static_cast* >(d->roughColumn->data()); } return true; } diff --git a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp index e05871030..3cc2e0b1e 100644 --- a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp +++ b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp @@ -1,3552 +1,3552 @@ /*************************************************************************** File : SpreadsheetView.cpp Project : LabPlot Description : View class for Spreadsheet -------------------------------------------------------------------- Copyright : (C) 2011-2020 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016 by Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2020 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 "SpreadsheetView.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/spreadsheet/SpreadsheetModel.h" #include "backend/spreadsheet/Spreadsheet.h" #include "commonfrontend/spreadsheet/SpreadsheetItemDelegate.h" #include "commonfrontend/spreadsheet/SpreadsheetHeaderView.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "backend/core/column/Column.h" #include "backend/core/column/ColumnPrivate.h" #include "backend/core/datatypes/SimpleCopyThroughFilter.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/String2DoubleFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/core/datatypes/String2DateTimeFilter.h" #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 "kdefrontend/spreadsheet/ExportSpreadsheetDialog.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include "kdefrontend/spreadsheet/AddSubtractValueDialog.h" #include "kdefrontend/spreadsheet/DropValuesDialog.h" #include "kdefrontend/spreadsheet/RescaleDialog.h" #include "kdefrontend/spreadsheet/SortDialog.h" #include "kdefrontend/spreadsheet/RandomValuesDialog.h" #include "kdefrontend/spreadsheet/EquidistantValuesDialog.h" #include "kdefrontend/spreadsheet/FunctionValuesDialog.h" #include "kdefrontend/spreadsheet/StatisticsDialog.h" #include //for std::reverse #ifdef Q_OS_MAC #include "3rdparty/kdmactouchbar/src/kdmactouchbar.h" #endif enum NormalizationMethod {DivideBySum, DivideByMin, DivideByMax, DivideByCount, DivideByMean, DivideByMedian, DivideByMode, DivideByRange, DivideBySD, DivideByMAD, DivideByIQR, ZScoreSD, ZScoreMAD, ZScoreIQR, Rescale}; /*! \class SpreadsheetView \brief View class for Spreadsheet \ingroup commonfrontend */ SpreadsheetView::SpreadsheetView(Spreadsheet* spreadsheet, bool readOnly) : QWidget(), m_tableView(new QTableView(this)), m_spreadsheet(spreadsheet), m_model(new SpreadsheetModel(spreadsheet)), m_readOnly(readOnly) { auto* layout = new QHBoxLayout(this); layout->setContentsMargins(0,0,0,0); layout->addWidget(m_tableView); if (m_readOnly) m_tableView->setEditTriggers(QTableView::NoEditTriggers); init(); //resize the view to show alls columns and the first 10 rows. //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_spreadsheet->isLoading()) { int w = m_tableView->verticalHeader()->width(); int h = m_horizontalHeader->height(); for (int i = 0; i < m_horizontalHeader->count(); ++i) w += m_horizontalHeader->sectionSize(i); if (m_tableView->verticalHeader()->count() <= 10) h += m_tableView->verticalHeader()->sectionSize(0)*m_tableView->verticalHeader()->count(); else h += m_tableView->verticalHeader()->sectionSize(0)*11; resize(w+50, h); } KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet")); showComments(group.readEntry(QLatin1String("ShowComments"), false)); } SpreadsheetView::~SpreadsheetView() { delete m_model; } void SpreadsheetView::init() { initActions(); initMenus(); m_tableView->setModel(m_model); m_tableView->setItemDelegate(new SpreadsheetItemDelegate(this)); m_tableView->setSelectionMode(QAbstractItemView::ExtendedSelection); //horizontal header m_horizontalHeader = new SpreadsheetHeaderView(this); m_horizontalHeader->setSectionsClickable(true); m_horizontalHeader->setHighlightSections(true); m_tableView->setHorizontalHeader(m_horizontalHeader); m_horizontalHeader->setSectionsMovable(true); m_horizontalHeader->installEventFilter(this); resizeHeader(); connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionMoved, this, &SpreadsheetView::handleHorizontalSectionMoved); connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionDoubleClicked, this, &SpreadsheetView::handleHorizontalHeaderDoubleClicked); connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionResized, this, &SpreadsheetView::handleHorizontalSectionResized); connect(m_horizontalHeader, &SpreadsheetHeaderView::sectionClicked, this, &SpreadsheetView::columnClicked); // vertical header QHeaderView* v_header = m_tableView->verticalHeader(); v_header->setSectionResizeMode(QHeaderView::Fixed); v_header->setSectionsMovable(false); v_header->installEventFilter(this); setFocusPolicy(Qt::StrongFocus); setFocus(); installEventFilter(this); connectActions(); showComments(false); connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::updateHeaderGeometry); connect(m_model, &SpreadsheetModel::headerDataChanged, this, &SpreadsheetView::handleHeaderDataChanged); connect(m_spreadsheet, &Spreadsheet::aspectAdded, this, &SpreadsheetView::handleAspectAdded); connect(m_spreadsheet, &Spreadsheet::aspectAboutToBeRemoved,this, &SpreadsheetView::handleAspectAboutToBeRemoved); connect(m_spreadsheet, &Spreadsheet::requestProjectContextMenu, this, &SpreadsheetView::createContextMenu); for (auto* column : m_spreadsheet->children()) connect(column, &Column::requestProjectContextMenu, this, &SpreadsheetView::createColumnContextMenu); //selection relevant connections QItemSelectionModel* sel_model = m_tableView->selectionModel(); connect(sel_model, &QItemSelectionModel::currentColumnChanged, this, &SpreadsheetView::currentColumnChanged); connect(sel_model, &QItemSelectionModel::selectionChanged, this, &SpreadsheetView::selectionChanged); connect(sel_model, &QItemSelectionModel::selectionChanged, this, &SpreadsheetView::selectionChanged); connect(m_spreadsheet, &Spreadsheet::columnSelected, this, &SpreadsheetView::selectColumn); connect(m_spreadsheet, &Spreadsheet::columnDeselected, this, &SpreadsheetView::deselectColumn); } /*! set the column sizes to the saved values or resize to content if no size was saved yet */ void SpreadsheetView::resizeHeader() { DEBUG("SpreadsheetView::resizeHeader()"); const auto columns = m_spreadsheet->children(); for (int i = 0; i < columns.size(); ++i) { const Column* col = columns.at(i); if (col->width() == 0) m_tableView->resizeColumnToContents(i); else m_tableView->setColumnWidth(i, col->width()); } } void SpreadsheetView::initActions() { // selection related actions action_cut_selection = new QAction(QIcon::fromTheme("edit-cut"), i18n("Cu&t"), this); action_copy_selection = new QAction(QIcon::fromTheme("edit-copy"), i18n("&Copy"), this); action_paste_into_selection = new QAction(QIcon::fromTheme("edit-paste"), i18n("Past&e"), this); action_mask_selection = new QAction(QIcon::fromTheme("edit-node"), i18n("&Mask Selection"), this); action_unmask_selection = new QAction(QIcon::fromTheme("format-remove-node"), i18n("&Unmask Selection"), this); action_clear_selection = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Selection"), this); action_select_all = new QAction(QIcon::fromTheme("edit-select-all"), i18n("Select All"), this); // action_set_formula = new QAction(QIcon::fromTheme(QString()), i18n("Assign &Formula"), this); // action_recalculate = new QAction(QIcon::fromTheme(QString()), i18n("Recalculate"), this); action_fill_sel_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this); action_fill_row_numbers = new QAction(QIcon::fromTheme(QString()), i18n("Row Numbers"), this); action_fill_random = new QAction(QIcon::fromTheme(QString()), i18n("Uniform Random Values"), this); action_fill_random_nonuniform = new QAction(QIcon::fromTheme(QString()), i18n("Random Values"), this); action_fill_equidistant = new QAction(QIcon::fromTheme(QString()), i18n("Equidistant Values"), this); action_fill_function = new QAction(QIcon::fromTheme(QString()), i18n("Function Values"), this); action_fill_const = new QAction(QIcon::fromTheme(QString()), i18n("Const Values"), this); //spreadsheet related actions action_toggle_comments = new QAction(QIcon::fromTheme("document-properties"), i18n("Show Comments"), this); action_clear_spreadsheet = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clear Spreadsheet"), this); action_clear_masks = new QAction(QIcon::fromTheme("format-remove-node"), i18n("Clear Masks"), this); action_sort_spreadsheet = new QAction(QIcon::fromTheme("view-sort-ascending"), i18n("&Sort Spreadsheet"), this); action_go_to_cell = new QAction(QIcon::fromTheme("go-jump"), i18n("&Go to Cell"), this); action_statistics_all_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Statisti&cs"), this ); // column related actions action_insert_column_left = new QAction(QIcon::fromTheme("edit-table-insert-column-left"), i18n("Insert Column Left"), this); action_insert_column_right = new QAction(QIcon::fromTheme("edit-table-insert-column-right"), i18n("Insert Column Right"), this); action_insert_columns_left = new QAction(QIcon::fromTheme("edit-table-insert-column-left"), i18n("Insert Multiple Columns Left"), this); action_insert_columns_right = new QAction(QIcon::fromTheme("edit-table-insert-column-right"), i18n("Insert Multiple Columns Right"), this); action_remove_columns = new QAction(QIcon::fromTheme("edit-table-delete-column"), i18n("Remove Selected Columns"), this); action_clear_columns = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clear Selected Columns"), this); action_set_as_none = new QAction(i18n("None"), this); action_set_as_none->setData(static_cast(AbstractColumn::PlotDesignation::NoDesignation)); action_set_as_x = new QAction("X", this); action_set_as_x->setData(static_cast(AbstractColumn::PlotDesignation::X)); action_set_as_y = new QAction("Y", this); action_set_as_y->setData(static_cast(AbstractColumn::PlotDesignation::Y)); action_set_as_z = new QAction("Z", this); action_set_as_z->setData(static_cast(AbstractColumn::PlotDesignation::Z)); action_set_as_xerr = new QAction(i18n("X-error"), this); action_set_as_xerr->setData(static_cast(AbstractColumn::PlotDesignation::XError)); action_set_as_xerr_minus = new QAction(i18n("X-error minus"), this); action_set_as_xerr_minus->setData(static_cast(AbstractColumn::PlotDesignation::XErrorMinus)); action_set_as_xerr_plus = new QAction(i18n("X-error plus"), this); action_set_as_xerr_plus->setData(static_cast(AbstractColumn::PlotDesignation::XErrorPlus)); action_set_as_yerr = new QAction(i18n("Y-error"), this); action_set_as_yerr->setData(static_cast(AbstractColumn::PlotDesignation::YError)); action_set_as_yerr_minus = new QAction(i18n("Y-error minus"), this); action_set_as_yerr_minus->setData(static_cast(AbstractColumn::PlotDesignation::YErrorMinus)); action_set_as_yerr_plus = new QAction(i18n("Y-error plus"), this); action_set_as_yerr_plus->setData(static_cast(AbstractColumn::PlotDesignation::YErrorPlus)); //data manipulation action_add_value = new QAction(i18n("Add Value"), this); action_add_value->setData(AddSubtractValueDialog::Add); action_subtract_value = new QAction(i18n("Subtract Value"), this); action_subtract_value->setData(AddSubtractValueDialog::Subtract); action_multiply_value = new QAction(i18n("Multiply by Value"), this); action_multiply_value->setData(AddSubtractValueDialog::Multiply); action_divide_value = new QAction(i18n("Divide by Value"), this); action_divide_value->setData(AddSubtractValueDialog::Divide); action_drop_values = new QAction(QIcon::fromTheme(QString()), i18n("Drop Values"), this); action_mask_values = new QAction(QIcon::fromTheme(QString()), i18n("Mask Values"), this); action_reverse_columns = new QAction(QIcon::fromTheme(QString()), i18n("Reverse"), this); // action_join_columns = new QAction(QIcon::fromTheme(QString()), i18n("Join"), this); normalizeColumnActionGroup = new QActionGroup(this); QAction* normalizeAction = new QAction(i18n("Divide by Sum"), normalizeColumnActionGroup); normalizeAction->setData(DivideBySum); normalizeAction = new QAction(i18n("Divide by Min"), normalizeColumnActionGroup); normalizeAction->setData(DivideByMin); normalizeAction = new QAction(i18n("Divide by Max"), normalizeColumnActionGroup); normalizeAction->setData(DivideByMax); normalizeAction = new QAction(i18n("Divide by Count"), normalizeColumnActionGroup); normalizeAction->setData(DivideByCount); normalizeAction = new QAction(i18n("Divide by Mean"), normalizeColumnActionGroup); normalizeAction->setData(DivideByMean); normalizeAction = new QAction(i18n("Divide by Median"), normalizeColumnActionGroup); normalizeAction->setData(DivideByMedian); normalizeAction = new QAction(i18n("Divide by Mode"), normalizeColumnActionGroup); normalizeAction->setData(DivideByMode); normalizeAction = new QAction(i18n("Divide by Range"), normalizeColumnActionGroup); normalizeAction->setData(DivideByRange); normalizeAction = new QAction(i18n("Divide by SD"), normalizeColumnActionGroup); normalizeAction->setData(DivideBySD); normalizeAction = new QAction(i18n("Divide by MAD"), normalizeColumnActionGroup); normalizeAction->setData(DivideByMAD); normalizeAction = new QAction(i18n("Divide by IQR"), normalizeColumnActionGroup); normalizeAction->setData(DivideByIQR); normalizeAction = new QAction(QLatin1String("(x-Mean)/SD"), normalizeColumnActionGroup); normalizeAction->setData(ZScoreSD); normalizeAction = new QAction(QLatin1String("(x-Median)/MAD"), normalizeColumnActionGroup); normalizeAction->setData(ZScoreMAD); normalizeAction = new QAction(QLatin1String("(x-Median)/IQR"), normalizeColumnActionGroup); normalizeAction->setData(ZScoreIQR); normalizeAction = new QAction(QLatin1String("Rescale to [a, b]"), normalizeColumnActionGroup); normalizeAction->setData(Rescale); action_normalize_selection = new QAction(QIcon::fromTheme(QString()), i18n("&Normalize Selection"), this); //sort and statistics action_sort_columns = new QAction(QIcon::fromTheme(QString()), i18n("&Selected Columns"), this); action_sort_asc_column = new QAction(QIcon::fromTheme("view-sort-ascending"), i18n("&Ascending"), this); action_sort_desc_column = new QAction(QIcon::fromTheme("view-sort-descending"), i18n("&Descending"), this); action_statistics_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Column Statisti&cs"), this); // row related actions action_insert_row_above = new QAction(QIcon::fromTheme("edit-table-insert-row-above") ,i18n("Insert Row Above"), this); action_insert_row_below = new QAction(QIcon::fromTheme("edit-table-insert-row-below"), i18n("Insert Row Below"), this); action_insert_rows_above = new QAction(QIcon::fromTheme("edit-table-insert-row-above") ,i18n("Insert Multiple Rows Above"), this); action_insert_rows_below = new QAction(QIcon::fromTheme("edit-table-insert-row-below"), i18n("Insert Multiple Rows Below"), this); action_remove_rows = new QAction(QIcon::fromTheme("edit-table-delete-row"), i18n("Remo&ve Selected Rows"), this); action_clear_rows = new QAction(QIcon::fromTheme("edit-clear"), i18n("Clea&r Selected Rows"), this); action_statistics_rows = new QAction(QIcon::fromTheme("view-statistics"), i18n("Row Statisti&cs"), this); //plot data action action_plot_data_xycurve = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-Curve"), this); action_plot_data_xycurve->setData(PlotDataDialog::PlotXYCurve); action_plot_data_histogram = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), this); action_plot_data_histogram->setData(PlotDataDialog::PlotHistogram); //Analyze and plot menu actions addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Reduce Data"), this); // addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-data-reduction-curve"), i18n("Reduce Data"), this); addDataReductionAction->setData(PlotDataDialog::DataReduction); addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiate"), this); // addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-differentiation-curve"), i18n("Differentiate"), this); addDifferentiationAction->setData(PlotDataDialog::Differentiation); addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integrate"), this); // addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-integration-curve"), i18n("Integrate"), this); addIntegrationAction->setData(PlotDataDialog::Integration); addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolate"), this); addInterpolationAction->setData(PlotDataDialog::Interpolation); addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this); addSmoothAction->setData(PlotDataDialog::Smoothing); QAction* fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Linear"), this); fitAction->setData(PlotDataDialog::FitLinear); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Power"), this); fitAction->setData(PlotDataDialog::FitPower); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Exponential (degree 1)"), this); fitAction->setData(PlotDataDialog::FitExp1); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Exponential (degree 2)"), this); fitAction->setData(PlotDataDialog::FitExp2); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Inverse Exponential"), this); fitAction->setData(PlotDataDialog::FitInvExp); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Gauss"), this); fitAction->setData(PlotDataDialog::FitGauss); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Cauchy-Lorentz"), this); fitAction->setData(PlotDataDialog::FitCauchyLorentz); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Arc Tangent"), this); fitAction->setData(PlotDataDialog::FitTan); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Hyperbolic Tangent"), this); fitAction->setData(PlotDataDialog::FitTanh); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Error Function"), this); fitAction->setData(PlotDataDialog::FitErrFunc); addFitAction.append(fitAction); fitAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Custom"), this); fitAction->setData(PlotDataDialog::FitCustom); addFitAction.append(fitAction); addFourierFilterAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this); addFourierFilterAction->setData(PlotDataDialog::FourierFilter); } void SpreadsheetView::initMenus() { //Selection menu m_selectionMenu = new QMenu(i18n("Selection"), this); m_selectionMenu->setIcon(QIcon::fromTheme("selection")); QMenu* submenu = nullptr; if (!m_readOnly) { submenu = new QMenu(i18n("Fi&ll Selection With"), this); submenu->setIcon(QIcon::fromTheme("select-rectangle")); submenu->addAction(action_fill_sel_row_numbers); submenu->addAction(action_fill_const); m_selectionMenu->addMenu(submenu); m_selectionMenu->addSeparator(); m_selectionMenu->addAction(action_cut_selection); } m_selectionMenu->addAction(action_copy_selection); if (!m_readOnly) { m_selectionMenu->addAction(action_paste_into_selection); m_selectionMenu->addAction(action_clear_selection); m_selectionMenu->addSeparator(); m_selectionMenu->addAction(action_mask_selection); m_selectionMenu->addAction(action_unmask_selection); m_selectionMenu->addSeparator(); m_selectionMenu->addAction(action_normalize_selection); } //plot data menu m_plotDataMenu = new QMenu(i18n("Plot Data"), this); m_plotDataMenu->addAction(action_plot_data_xycurve); m_plotDataMenu->addAction(action_plot_data_histogram); // Column menu m_columnMenu = new QMenu(this); m_columnMenu->addMenu(m_plotDataMenu); // Data fit sub-menu QMenu* dataFitMenu = new QMenu(i18n("Fit"), this); 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)); //analyze and plot data menu m_analyzePlotMenu = new QMenu(i18n("Analyze and Plot Data"), this); m_analyzePlotMenu->addMenu(dataFitMenu); m_analyzePlotMenu->addSeparator(); m_analyzePlotMenu->addAction(addDifferentiationAction); m_analyzePlotMenu->addAction(addIntegrationAction); m_analyzePlotMenu->addSeparator(); m_analyzePlotMenu->addAction(addInterpolationAction); m_analyzePlotMenu->addAction(addSmoothAction); m_analyzePlotMenu->addSeparator(); m_analyzePlotMenu->addAction(addFourierFilterAction); m_analyzePlotMenu->addSeparator(); m_analyzePlotMenu->addAction(addDataReductionAction); m_columnMenu->addMenu(m_analyzePlotMenu); m_columnSetAsMenu = new QMenu(i18n("Set Column As"), this); m_columnMenu->addSeparator(); m_columnSetAsMenu->addAction(action_set_as_x); m_columnSetAsMenu->addAction(action_set_as_y); m_columnSetAsMenu->addAction(action_set_as_z); m_columnSetAsMenu->addSeparator(); m_columnSetAsMenu->addAction(action_set_as_xerr); m_columnSetAsMenu->addAction(action_set_as_xerr_minus); m_columnSetAsMenu->addAction(action_set_as_xerr_plus); m_columnSetAsMenu->addSeparator(); m_columnSetAsMenu->addAction(action_set_as_yerr); m_columnSetAsMenu->addAction(action_set_as_yerr_minus); m_columnSetAsMenu->addAction(action_set_as_yerr_plus); m_columnSetAsMenu->addSeparator(); m_columnSetAsMenu->addAction(action_set_as_none); m_columnMenu->addMenu(m_columnSetAsMenu); if (!m_readOnly) { m_columnGenerateDataMenu = new QMenu(i18n("Generate Data"), this); m_columnGenerateDataMenu->addAction(action_fill_row_numbers); m_columnGenerateDataMenu->addAction(action_fill_const); m_columnGenerateDataMenu->addAction(action_fill_equidistant); m_columnGenerateDataMenu->addAction(action_fill_random_nonuniform); m_columnGenerateDataMenu->addAction(action_fill_function); m_columnMenu->addSeparator(); m_columnMenu->addMenu(m_columnGenerateDataMenu); m_columnMenu->addSeparator(); m_columnManipulateDataMenu = new QMenu(i18n("Manipulate Data"), this); m_columnManipulateDataMenu->addAction(action_add_value); m_columnManipulateDataMenu->addAction(action_subtract_value); m_columnManipulateDataMenu->addAction(action_multiply_value); m_columnManipulateDataMenu->addAction(action_divide_value); m_columnManipulateDataMenu->addSeparator(); m_columnManipulateDataMenu->addAction(action_reverse_columns); m_columnManipulateDataMenu->addSeparator(); m_columnManipulateDataMenu->addAction(action_drop_values); m_columnManipulateDataMenu->addAction(action_mask_values); m_columnManipulateDataMenu->addSeparator(); // m_columnManipulateDataMenu->addAction(action_join_columns); //normalization menu with the following structure //Divide by Sum //Divide by Min //Divide by Max //Divide by Count //-------------- //Divide by Mean //Divide by Median //Divide by Mode //--------------- //Divide by Range //Divide by SD //Divide by MAD //Divide by IQR //-------------- //(x-Mean)/SD //(x-Median)/MAD //(x-Median)/IQR //-------------- //Rescale to [a, b] m_columnNormalizeMenu = new QMenu(i18n("Normalize"), this); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(0)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(1)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(2)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(3)); m_columnNormalizeMenu->addSeparator(); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(4)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(5)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(6)); m_columnNormalizeMenu->addSeparator(); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(7)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(8)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(9)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(10)); m_columnNormalizeMenu->addSeparator(); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(11)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(12)); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(13)); m_columnNormalizeMenu->addSeparator(); m_columnNormalizeMenu->addAction(normalizeColumnActionGroup->actions().at(14)); m_columnManipulateDataMenu->addMenu(m_columnNormalizeMenu); m_columnMenu->addMenu(m_columnManipulateDataMenu); m_columnMenu->addSeparator(); m_columnSortMenu = new QMenu(i18n("Sort"), this); m_columnSortMenu->setIcon(QIcon::fromTheme("view-sort-ascending")); m_columnSortMenu->addAction(action_sort_asc_column); m_columnSortMenu->addAction(action_sort_desc_column); m_columnSortMenu->addAction(action_sort_columns); m_columnMenu->addSeparator(); m_columnMenu->addMenu(m_columnSortMenu); m_columnMenu->addSeparator(); m_columnMenu->addAction(action_insert_column_left); m_columnMenu->addAction(action_insert_column_right); m_columnMenu->addSeparator(); m_columnMenu->addAction(action_insert_columns_left); m_columnMenu->addAction(action_insert_columns_right); m_columnMenu->addSeparator(); m_columnMenu->addAction(action_remove_columns); m_columnMenu->addAction(action_clear_columns); } m_columnMenu->addSeparator(); m_columnMenu->addAction(action_toggle_comments); m_columnMenu->addSeparator(); m_columnMenu->addAction(action_statistics_columns); //Spreadsheet menu m_spreadsheetMenu = new QMenu(this); m_spreadsheetMenu->addMenu(m_plotDataMenu); m_spreadsheetMenu->addMenu(m_analyzePlotMenu); m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addMenu(m_selectionMenu); m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addAction(action_select_all); if (!m_readOnly) { m_spreadsheetMenu->addAction(action_clear_spreadsheet); m_spreadsheetMenu->addAction(action_clear_masks); m_spreadsheetMenu->addAction(action_sort_spreadsheet); } m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addAction(action_go_to_cell); m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addAction(action_toggle_comments); m_spreadsheetMenu->addSeparator(); m_spreadsheetMenu->addAction(action_statistics_all_columns); //Row menu m_rowMenu = new QMenu(this); if (!m_readOnly) { submenu = new QMenu(i18n("Fi&ll Selection With"), this); submenu->addAction(action_fill_sel_row_numbers); submenu->addAction(action_fill_const); m_rowMenu->addMenu(submenu); m_rowMenu->addSeparator(); m_rowMenu->addAction(action_insert_row_above); m_rowMenu->addAction(action_insert_row_below); m_rowMenu->addSeparator(); m_rowMenu->addAction(action_insert_rows_above); m_rowMenu->addAction(action_insert_rows_below); m_rowMenu->addSeparator(); m_rowMenu->addAction(action_remove_rows); m_rowMenu->addAction(action_clear_rows); } m_rowMenu->addSeparator(); m_rowMenu->addAction(action_statistics_rows); action_statistics_rows->setVisible(false); } void SpreadsheetView::connectActions() { connect(action_cut_selection, &QAction::triggered, this, &SpreadsheetView::cutSelection); connect(action_copy_selection, &QAction::triggered, this, &SpreadsheetView::copySelection); connect(action_paste_into_selection, &QAction::triggered, this, &SpreadsheetView::pasteIntoSelection); connect(action_mask_selection, &QAction::triggered, this, &SpreadsheetView::maskSelection); connect(action_unmask_selection, &QAction::triggered, this, &SpreadsheetView::unmaskSelection); connect(action_clear_selection, &QAction::triggered, this, &SpreadsheetView::clearSelectedCells); // connect(action_recalculate, &QAction::triggered, this, &SpreadsheetView::recalculateSelectedCells); connect(action_fill_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillWithRowNumbers); connect(action_fill_sel_row_numbers, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRowNumbers); // connect(action_fill_random, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithRandomNumbers); connect(action_fill_random_nonuniform, &QAction::triggered, this, &SpreadsheetView::fillWithRandomValues); connect(action_fill_equidistant, &QAction::triggered, this, &SpreadsheetView::fillWithEquidistantValues); connect(action_fill_function, &QAction::triggered, this, &SpreadsheetView::fillWithFunctionValues); connect(action_fill_const, &QAction::triggered, this, &SpreadsheetView::fillSelectedCellsWithConstValues); connect(action_select_all, &QAction::triggered, m_tableView, &QTableView::selectAll); connect(action_clear_spreadsheet, &QAction::triggered, m_spreadsheet, &Spreadsheet::clear); connect(action_clear_masks, &QAction::triggered, m_spreadsheet, &Spreadsheet::clearMasks); connect(action_sort_spreadsheet, &QAction::triggered, this, &SpreadsheetView::sortSpreadsheet); connect(action_go_to_cell, &QAction::triggered, this, static_cast(&SpreadsheetView::goToCell)); connect(action_insert_column_left, &QAction::triggered, this, &SpreadsheetView::insertColumnLeft); connect(action_insert_column_right, &QAction::triggered, this, &SpreadsheetView::insertColumnRight); connect(action_insert_columns_left, &QAction::triggered, this, static_cast(&SpreadsheetView::insertColumnsLeft)); connect(action_insert_columns_right, &QAction::triggered, this, static_cast(&SpreadsheetView::insertColumnsRight)); connect(action_remove_columns, &QAction::triggered, this, &SpreadsheetView::removeSelectedColumns); connect(action_clear_columns, &QAction::triggered, this, &SpreadsheetView::clearSelectedColumns); connect(action_set_as_none, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_x, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_y, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_z, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_xerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_xerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_xerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_yerr, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_yerr_minus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); connect(action_set_as_yerr_plus, &QAction::triggered, this, &SpreadsheetView::setSelectionAs); //data manipulation connect(action_add_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); connect(action_subtract_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); connect(action_multiply_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); connect(action_divide_value, &QAction::triggered, this, &SpreadsheetView::modifyValues); connect(action_reverse_columns, &QAction::triggered, this, &SpreadsheetView::reverseColumns); connect(action_drop_values, &QAction::triggered, this, &SpreadsheetView::dropColumnValues); connect(action_mask_values, &QAction::triggered, this, &SpreadsheetView::maskColumnValues); // connect(action_join_columns, &QAction::triggered, this, &SpreadsheetView::joinColumns); connect(normalizeColumnActionGroup, &QActionGroup::triggered, this, &SpreadsheetView::normalizeSelectedColumns); connect(action_normalize_selection, &QAction::triggered, this, &SpreadsheetView::normalizeSelection); //sort connect(action_sort_columns, &QAction::triggered, this, &SpreadsheetView::sortSelectedColumns); connect(action_sort_asc_column, &QAction::triggered, this, &SpreadsheetView::sortColumnAscending); connect(action_sort_desc_column, &QAction::triggered, this, &SpreadsheetView::sortColumnDescending); //statistics connect(action_statistics_columns, &QAction::triggered, this, &SpreadsheetView::showColumnStatistics); connect(action_statistics_all_columns, &QAction::triggered, this, &SpreadsheetView::showAllColumnsStatistics); connect(action_insert_row_above, &QAction::triggered, this, &SpreadsheetView::insertRowAbove); connect(action_insert_row_below, &QAction::triggered, this, &SpreadsheetView::insertRowBelow); connect(action_insert_rows_above, &QAction::triggered, this, static_cast(&SpreadsheetView::insertRowsAbove)); connect(action_insert_rows_below, &QAction::triggered, this, static_cast(&SpreadsheetView::insertRowsBelow)); connect(action_remove_rows, &QAction::triggered, this, &SpreadsheetView::removeSelectedRows); connect(action_clear_rows, &QAction::triggered, this, &SpreadsheetView::clearSelectedRows); connect(action_statistics_rows, &QAction::triggered, this, &SpreadsheetView::showRowStatistics); connect(action_toggle_comments, &QAction::triggered, this, &SpreadsheetView::toggleComments); connect(action_plot_data_xycurve, &QAction::triggered, this, &SpreadsheetView::plotData); connect(action_plot_data_histogram, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addDataReductionAction, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addDifferentiationAction, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addIntegrationAction, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addInterpolationAction, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addSmoothAction, &QAction::triggered, this, &SpreadsheetView::plotData); for (const auto& action : addFitAction) connect(action, &QAction::triggered, this, &SpreadsheetView::plotData); connect(addFourierFilterAction, &QAction::triggered,this, &SpreadsheetView::plotData); } void SpreadsheetView::fillToolBar(QToolBar* toolBar) { if (!m_readOnly) { toolBar->addAction(action_insert_row_above); toolBar->addAction(action_insert_row_below); toolBar->addAction(action_remove_rows); } toolBar->addAction(action_statistics_rows); toolBar->addSeparator(); if (!m_readOnly) { toolBar->addAction(action_insert_column_left); toolBar->addAction(action_insert_column_right); toolBar->addAction(action_remove_columns); } toolBar->addAction(action_statistics_columns); if (!m_readOnly) { toolBar->addSeparator(); toolBar->addAction(action_sort_asc_column); toolBar->addAction(action_sort_desc_column); } } #ifdef Q_OS_MAC void SpreadsheetView::fillTouchBar(KDMacTouchBar* touchBar){ //touchBar->addAction(action_insert_column_right); } #endif /*! * Populates the menu \c menu with the spreadsheet and spreadsheet view relevant actions. * The menu is used * - as the context menu in SpreadsheetView * - as the "spreadsheet menu" in the main menu-bar (called form MainWin) * - as a part of the spreadsheet context menu in project explorer */ void SpreadsheetView::createContextMenu(QMenu* menu) { Q_ASSERT(menu); checkSpreadsheetMenu(); 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); if (m_spreadsheet->columnCount() > 0 && m_spreadsheet->rowCount() > 0) { menu->insertMenu(firstAction, m_plotDataMenu); menu->insertSeparator(firstAction); } menu->insertMenu(firstAction, m_selectionMenu); menu->insertSeparator(firstAction); menu->insertAction(firstAction, action_select_all); if (!m_readOnly) { menu->insertAction(firstAction, action_clear_spreadsheet); menu->insertAction(firstAction, action_clear_masks); menu->insertAction(firstAction, action_sort_spreadsheet); menu->insertSeparator(firstAction); } menu->insertAction(firstAction, action_go_to_cell); menu->insertSeparator(firstAction); menu->insertAction(firstAction, action_toggle_comments); menu->insertSeparator(firstAction); menu->insertAction(firstAction, action_statistics_all_columns); menu->insertSeparator(firstAction); } /*! * adds column specific actions in SpreadsheetView to the context menu shown in the project explorer. */ void SpreadsheetView::createColumnContextMenu(QMenu* menu) { const Column* column = dynamic_cast(QObject::sender()); if (!column) return; //should never happen, since the sender is always a Column QAction* firstAction = menu->actions().at(1); //TODO: add these menus and synchronize the behavior with the context menu creation //on the spreadsheet header in eventFilter(), // menu->insertMenu(firstAction, m_plotDataMenu); // menu->insertMenu(firstAction, m_analyzePlotMenu); // menu->insertSeparator(firstAction); const bool hasValues = column->hasValues(); const bool numeric = column->isNumeric(); if (numeric) menu->insertMenu(firstAction, m_columnSetAsMenu); if (!m_readOnly) { if (numeric) { menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_columnGenerateDataMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_columnManipulateDataMenu); menu->insertSeparator(firstAction); } menu->insertMenu(firstAction, m_columnSortMenu); action_sort_asc_column->setVisible(true); action_sort_desc_column->setVisible(true); action_sort_columns->setVisible(false); //in case no cells are available, deactivate the actions that only make sense in the presence of cells const bool hasCells = m_spreadsheet->rowCount() > 0; m_columnGenerateDataMenu->setEnabled(numeric && hasCells); //in case no valid numerical values are available, deactivate the actions that only make sense in the presence of values m_columnManipulateDataMenu->setEnabled(numeric && hasValues); m_columnSortMenu->setEnabled(hasValues); } menu->insertSeparator(firstAction); menu->insertAction(firstAction, action_statistics_columns); action_statistics_columns->setEnabled(numeric && hasValues); } //SLOTS void SpreadsheetView::handleAspectAdded(const AbstractAspect* aspect) { const Column* col = dynamic_cast(aspect); if (!col || col->parentAspect() != m_spreadsheet) return; int index = m_spreadsheet->indexOfChild(col); if (col->width() == 0) m_tableView->resizeColumnToContents(index); else m_tableView->setColumnWidth(index, col->width()); goToCell(0, index); connect(col, &Column::requestProjectContextMenu, this, &SpreadsheetView::createColumnContextMenu); } void SpreadsheetView::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { const Column* col = dynamic_cast(aspect); if (!col || col->parentAspect() != m_spreadsheet) return; disconnect(col, nullptr, this, nullptr); } void SpreadsheetView::handleHorizontalSectionResized(int logicalIndex, int oldSize, int newSize) { Q_UNUSED(logicalIndex); Q_UNUSED(oldSize); //save the new size in the column Column* col = m_spreadsheet->child(logicalIndex); col->setWidth(newSize); } void SpreadsheetView::goToCell(int row, int col) { QModelIndex index = m_model->index(row, col); m_tableView->scrollTo(index); m_tableView->setCurrentIndex(index); } void SpreadsheetView::handleHorizontalSectionMoved(int index, int from, int to) { Q_UNUSED(index); static bool inside = false; if (inside) return; Q_ASSERT(index == from); inside = true; m_tableView->horizontalHeader()->moveSection(to, from); inside = false; m_spreadsheet->moveColumn(from, to); } //TODO Implement the "change of the column name"-mode upon a double click void SpreadsheetView::handleHorizontalHeaderDoubleClicked(int index) { Q_UNUSED(index); } /*! Returns whether comments are shown currently or not */ bool SpreadsheetView::areCommentsShown() const { return m_horizontalHeader->areCommentsShown(); } /*! toggles the column comment in the horizontal header */ void SpreadsheetView::toggleComments() { showComments(!areCommentsShown()); //TODO if (areCommentsShown()) action_toggle_comments->setText(i18n("Hide Comments")); else action_toggle_comments->setText(i18n("Show Comments")); } //! Shows (\c on=true) or hides (\c on=false) the column comments in the horizontal header void SpreadsheetView::showComments(bool on) { m_horizontalHeader->showComments(on); } void SpreadsheetView::currentColumnChanged(const QModelIndex & current, const QModelIndex & previous) { Q_UNUSED(previous); int col = current.column(); if (col < 0 || col >= m_spreadsheet->columnCount()) return; } //TODO void SpreadsheetView::handleHeaderDataChanged(Qt::Orientation orientation, int first, int last) { if (orientation != Qt::Horizontal) return; QItemSelectionModel * sel_model = m_tableView->selectionModel(); int col = sel_model->currentIndex().column(); if (col < first || col > last) return; } /*! Returns the number of selected columns. If \c full is \c true, this function only returns the number of fully selected columns. */ int SpreadsheetView::selectedColumnCount(bool full) const { int count = 0; const int cols = m_spreadsheet->columnCount(); for (int i = 0; i < cols; i++) if (isColumnSelected(i, full)) count++; return count; } /*! Returns the number of (at least partly) selected columns with the plot designation \param pd . */ int SpreadsheetView::selectedColumnCount(AbstractColumn::PlotDesignation pd) const{ int count = 0; const int cols = m_spreadsheet->columnCount(); for (int i = 0; i < cols; i++) if ( isColumnSelected(i, false) && (m_spreadsheet->column(i)->plotDesignation() == pd) ) count++; return count; } /*! Returns \c true if column \param col is selected, otherwise returns \c false. If \param full is \c true, this function only returns true if the whole column is selected. */ bool SpreadsheetView::isColumnSelected(int col, bool full) const { if (full) return m_tableView->selectionModel()->isColumnSelected(col, QModelIndex()); else return m_tableView->selectionModel()->columnIntersectsSelection(col, QModelIndex()); } /*! Returns all selected columns. If \param full is true, this function only returns a column if the whole column is selected. */ QVector SpreadsheetView::selectedColumns(bool full) const { QVector columns; const int cols = m_spreadsheet->columnCount(); for (int i = 0; i < cols; i++) if (isColumnSelected(i, full)) columns << m_spreadsheet->column(i); return columns; } /*! Returns \c true if row \param row is selected; otherwise returns \c false If \param full is \c true, this function only returns \c true if the whole row is selected. */ bool SpreadsheetView::isRowSelected(int row, bool full) const { if (full) return m_tableView->selectionModel()->isRowSelected(row, QModelIndex()); else return m_tableView->selectionModel()->rowIntersectsSelection(row, QModelIndex()); } /*! Return the index of the first selected column. If \param full is \c true, this function only looks for fully selected columns. */ int SpreadsheetView::firstSelectedColumn(bool full) const { const int cols = m_spreadsheet->columnCount(); for (int i = 0; i < cols; i++) { if (isColumnSelected(i, full)) return i; } return -1; } /*! Return the index of the last selected column. If \param full is \c true, this function only looks for fully selected columns. */ int SpreadsheetView::lastSelectedColumn(bool full) const { const int cols = m_spreadsheet->columnCount(); for (int i = cols - 1; i >= 0; i--) if (isColumnSelected(i, full)) return i; return -2; } /*! Return the index of the first selected row. If \param full is \c true, this function only looks for fully selected rows. */ int SpreadsheetView::firstSelectedRow(bool full) const{ QModelIndexList indexes; if (!full) indexes = m_tableView->selectionModel()->selectedIndexes(); else indexes = m_tableView->selectionModel()->selectedRows(); if (!indexes.empty()) return indexes.first().row(); else return -1; } /*! Return the index of the last selected row. If \param full is \c true, this function only looks for fully selected rows. */ int SpreadsheetView::lastSelectedRow(bool full) const { QModelIndexList indexes; if (!full) indexes = m_tableView->selectionModel()->selectedIndexes(); else indexes = m_tableView->selectionModel()->selectedRows(); if (!indexes.empty()) return indexes.last().row(); else return -2; } /*! Return whether a cell is selected */ bool SpreadsheetView::isCellSelected(int row, int col) const { if (row < 0 || col < 0 || row >= m_spreadsheet->rowCount() || col >= m_spreadsheet->columnCount()) return false; return m_tableView->selectionModel()->isSelected(m_model->index(row, col)); } /*! Get the complete set of selected rows. */ IntervalAttribute SpreadsheetView::selectedRows(bool full) const { IntervalAttribute result; const int rows = m_spreadsheet->rowCount(); for (int i = 0; i < rows; i++) if (isRowSelected(i, full)) result.setValue(i, true); return result; } /*! Select/Deselect a cell. */ void SpreadsheetView::setCellSelected(int row, int col, bool select) { m_tableView->selectionModel()->select(m_model->index(row, col), select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); } /*! Select/Deselect a range of cells. */ void SpreadsheetView::setCellsSelected(int first_row, int first_col, int last_row, int last_col, bool select) { QModelIndex top_left = m_model->index(first_row, first_col); QModelIndex bottom_right = m_model->index(last_row, last_col); m_tableView->selectionModel()->select(QItemSelection(top_left, bottom_right), select ? QItemSelectionModel::SelectCurrent : QItemSelectionModel::Deselect); } /*! Determine the current cell (-1 if no cell is designated as the current). */ void SpreadsheetView::getCurrentCell(int* row, int* col) const { QModelIndex index = m_tableView->selectionModel()->currentIndex(); if (index.isValid()) { *row = index.row(); *col = index.column(); } else { *row = -1; *col = -1; } } bool SpreadsheetView::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::ContextMenu) { auto* cm_event = static_cast(event); const QPoint global_pos = cm_event->globalPos(); if (watched == m_tableView->verticalHeader()) { bool onlyNumeric = true; for (int i = 0; i < m_spreadsheet->columnCount(); ++i) { - if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::Numeric) { + if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::ColumnMode::Numeric) { onlyNumeric = false; break; } } action_statistics_rows->setVisible(onlyNumeric); m_rowMenu->exec(global_pos); } else if (watched == m_horizontalHeader) { const int col = m_horizontalHeader->logicalIndexAt(cm_event->pos()); if (!isColumnSelected(col, true)) { QItemSelectionModel* sel_model = m_tableView->selectionModel(); sel_model->clearSelection(); sel_model->select(QItemSelection(m_model->index(0, col, QModelIndex()), m_model->index(m_model->rowCount()-1, col, QModelIndex())), QItemSelectionModel::Select); } if (selectedColumns().size() == 1) { action_sort_columns->setVisible(false); action_sort_asc_column->setVisible(true); action_sort_desc_column->setVisible(true); } else { action_sort_columns->setVisible(true); action_sort_asc_column->setVisible(false); action_sort_desc_column->setVisible(false); } //check whether we have non-numeric columns selected and deactivate actions for numeric columns bool numeric = true; bool plottable = true; bool datetime = false; bool hasValues = false; for (const Column* col : selectedColumns()) { - if ( !(col->columnMode() == AbstractColumn::Numeric || col->columnMode() == AbstractColumn::Integer || - col->columnMode() == AbstractColumn::BigInt) ) { - datetime = (col->columnMode() == AbstractColumn::DateTime); + if ( !(col->columnMode() == AbstractColumn::ColumnMode::Numeric || col->columnMode() == AbstractColumn::ColumnMode::Integer || + col->columnMode() == AbstractColumn::ColumnMode::BigInt) ) { + datetime = (col->columnMode() == AbstractColumn::ColumnMode::DateTime); if (!datetime) plottable = false; numeric = false; break; } } for (const Column* col : selectedColumns()) { if (col->hasValues()) { hasValues = true; break; } } m_plotDataMenu->setEnabled(plottable); m_analyzePlotMenu->setEnabled(numeric); m_columnSetAsMenu->setEnabled(numeric); action_statistics_columns->setEnabled(numeric && hasValues); if (!m_readOnly) { m_columnGenerateDataMenu->setEnabled(numeric); m_columnManipulateDataMenu->setEnabled(numeric || datetime); m_columnSortMenu->setEnabled(numeric); //in case no cells are available, deactivate the actions that only make sense in the presence of cells const bool hasCells = m_spreadsheet->rowCount() > 0; m_columnGenerateDataMenu->setEnabled(numeric && hasCells); //in case no valid numerical values are available, deactivate the actions that only make sense in the presence of values m_columnManipulateDataMenu->setEnabled(numeric && hasValues); m_columnSortMenu->setEnabled(hasValues); } m_columnMenu->exec(global_pos); } else if (watched == this) { checkSpreadsheetMenu(); m_spreadsheetMenu->exec(global_pos); } return true; } else if (event->type() == QEvent::KeyPress) { auto* key_event = static_cast(event); if (key_event->matches(QKeySequence::Copy)) copySelection(); else if (key_event->matches(QKeySequence::Paste)) pasteIntoSelection(); else if (key_event->key() == Qt::Key_Backspace || key_event->matches(QKeySequence::Delete)) clearSelectedCells(); } return QWidget::eventFilter(watched, event); } /*! * disables cell data relevant actions in the spreadsheet menu if there're no cells available */ void SpreadsheetView::checkSpreadsheetMenu() { const bool cellsAvail = m_spreadsheet->columnCount()>0 && m_spreadsheet->rowCount()>0; m_plotDataMenu->setEnabled(cellsAvail); m_selectionMenu->setEnabled(cellsAvail); action_select_all->setEnabled(cellsAvail); action_clear_spreadsheet->setEnabled(cellsAvail); action_clear_masks->setEnabled(cellsAvail); action_sort_spreadsheet->setEnabled(cellsAvail); action_go_to_cell->setEnabled(cellsAvail); action_statistics_all_columns->setEnabled(cellsAvail); //deactivate mask/unmask actions if there are no unmasked/masked cells //in the current selection QModelIndexList indexes = m_tableView->selectionModel()->selectedIndexes(); bool hasMasked = false; bool hasUnmasked = false; for (auto index : indexes) { int row = index.row(); int col = index.column(); const auto* column = m_spreadsheet->column(col); //TODO: the null pointer check shouldn't be actually required here //but when deleting the columns the selection model in the view //and the aspect model sometimes get out of sync and we crash... if (column && column->isMasked(row)) { hasMasked = true; break; } } for (auto index : indexes) { int row = index.row(); int col = index.column(); const auto* column = m_spreadsheet->column(col); if (column && !column->isMasked(row)) { hasUnmasked = true; break; } } action_mask_selection->setEnabled(hasUnmasked); action_unmask_selection->setEnabled(hasMasked); } bool SpreadsheetView::formulaModeActive() const { return m_model->formulaModeActive(); } void SpreadsheetView::activateFormulaMode(bool on) { m_model->activateFormulaMode(on); } void SpreadsheetView::goToNextColumn() { if (m_spreadsheet->columnCount() == 0) return; QModelIndex idx = m_tableView->currentIndex(); int col = idx.column()+1; if (col >= m_spreadsheet->columnCount()) col = 0; m_tableView->setCurrentIndex(idx.sibling(idx.row(), col)); } void SpreadsheetView::goToPreviousColumn() { if (m_spreadsheet->columnCount() == 0) return; QModelIndex idx = m_tableView->currentIndex(); int col = idx.column()-1; if (col < 0) col = m_spreadsheet->columnCount()-1; m_tableView->setCurrentIndex(idx.sibling(idx.row(), col)); } void SpreadsheetView::cutSelection() { int first = firstSelectedRow(); if ( first < 0 ) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: cut selected cells", m_spreadsheet->name())); copySelection(); clearSelectedCells(); m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::copySelection() { PERFTRACE("copy selected cells"); const int first_col = firstSelectedColumn(); if (first_col == -1) return; const int last_col = lastSelectedColumn(); if (last_col == -2) return; const int first_row = firstSelectedRow(); if (first_row == -1) return; const int last_row = lastSelectedRow(); if (last_row == -2) return; const int cols = last_col - first_col + 1; const int rows = last_row - first_row + 1; WAIT_CURSOR; QString output_str; QVector columns; QVector formats; for (int c = 0; c < cols; c++) { Column* col = m_spreadsheet->column(first_col + c); columns << col; const Double2StringFilter* out_fltr = static_cast(col->outputFilter()); formats << out_fltr->numericFormat(); } QLocale locale; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { const Column* col_ptr = columns.at(c); if (isCellSelected(first_row + r, first_col + c)) { // if (formulaModeActive()) // output_str += col_ptr->formula(first_row + r); // else - if (col_ptr->columnMode() == AbstractColumn::Numeric) + if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Numeric) output_str += locale.toString(col_ptr->valueAt(first_row + r), formats.at(c), 16); // copy with max. precision - else if (col_ptr->columnMode() == AbstractColumn::Integer || col_ptr->columnMode() == AbstractColumn::BigInt) + else if (col_ptr->columnMode() == AbstractColumn::ColumnMode::Integer || col_ptr->columnMode() == AbstractColumn::ColumnMode::BigInt) output_str += QString::number(col_ptr->valueAt(first_row + r)); else output_str += col_ptr->asStringColumn()->textAt(first_row + r); } if (c < cols-1) output_str += '\t'; } if (r < rows-1) output_str += '\n'; } QApplication::clipboard()->setText(output_str); RESET_CURSOR; } /* bool determineLocale(const QString& value, QLocale& locale) { int pointIndex = value.indexOf(QLatin1Char('.')); int commaIndex = value.indexOf(QLatin1Char('.')); if (pointIndex != -1 && commaIndex != -1) { } return false; }*/ void SpreadsheetView::pasteIntoSelection() { if (m_spreadsheet->columnCount() < 1 || m_spreadsheet->rowCount() < 1) return; const QMimeData* mime_data = QApplication::clipboard()->mimeData(); if (!mime_data->hasFormat("text/plain")) return; PERFTRACE("paste selected cells"); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: paste from clipboard", m_spreadsheet->name())); int first_col = firstSelectedColumn(); int last_col = lastSelectedColumn(); int first_row = firstSelectedRow(); int last_row = lastSelectedRow(); int input_row_count = 0; int input_col_count = 0; QString input_str = QString(mime_data->data("text/plain")).trimmed(); QVector cellTexts; QString separator; if (input_str.indexOf(QLatin1String("\r\n")) != -1) separator = QLatin1String("\r\n"); else separator = QLatin1Char('\n'); QStringList input_rows(input_str.split(separator)); input_row_count = input_rows.count(); input_col_count = 0; bool hasTabs = false; if (input_row_count > 0 && input_rows.constFirst().indexOf(QLatin1Char('\t')) != -1) hasTabs = true; for (int i = 0; i < input_row_count; i++) { if (hasTabs) cellTexts.append(input_rows.at(i).split(QLatin1Char('\t'))); else cellTexts.append(input_rows.at(i).split(QRegularExpression(QStringLiteral("\\s+")))); if (cellTexts.at(i).count() > input_col_count) input_col_count = cellTexts.at(i).count(); } QLocale locale; // bool localeDetermined = false; if ( (first_col == -1 || first_row == -1) || (last_row == first_row && last_col == first_col) ) { // if there is no selection or only one cell selected, the // selection will be expanded to the needed size from the current cell int current_row, current_col; getCurrentCell(¤t_row, ¤t_col); if (current_row == -1) current_row = 0; if (current_col == -1) current_col = 0; setCellSelected(current_row, current_col); first_col = current_col; first_row = current_row; last_row = first_row + input_row_count -1; last_col = first_col + input_col_count -1; const int columnCount = m_spreadsheet->columnCount(); //if the target columns that are already available don't have any values yet, //convert their mode to the mode of the data to be pasted for (int c = first_col; c <= last_col && c < columnCount; ++c) { Column* col = m_spreadsheet->column(c); if (col->hasValues() ) continue; //first non-empty value in the column to paste determines the column mode/type of the new column to be added const int curCol = c - first_col; QString nonEmptyValue; for (auto r : cellTexts) { if (curCol < r.count() && !r.at(curCol).isEmpty()) { nonEmptyValue = r.at(curCol); break; } } // if (!localeDetermined) // localeDetermined = determineLocale(nonEmptyValue, locale); - const AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(nonEmptyValue, - QLatin1String("yyyy-dd-MM hh:mm:ss:zzz")); + const auto mode = AbstractFileFilter::columnMode(nonEmptyValue, QLatin1String("yyyy-dd-MM hh:mm:ss:zzz")); col->setColumnMode(mode); } //add columns if necessary if (last_col >= columnCount) { for (int c = 0; c < last_col - (columnCount - 1); ++c) { const int curCol = columnCount - first_col + c; //first non-empty value in the column to paste determines the column mode/type of the new column to be added QString nonEmptyValue; for (auto r : cellTexts) { if (curCol < r.count() && !r.at(curCol).isEmpty()) { nonEmptyValue = r.at(curCol); break; } } // if (!localeDetermined) // localeDetermined = determineLocale(nonEmptyValue, locale); - const AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(nonEmptyValue, - QLatin1String("yyyy-dd-MM hh:mm:ss:zzz")); + const auto mode = AbstractFileFilter::columnMode(nonEmptyValue, QLatin1String("yyyy-dd-MM hh:mm:ss:zzz")); Column* new_col = new Column(QString::number(curCol), mode); new_col->setPlotDesignation(AbstractColumn::PlotDesignation::Y); new_col->insertRows(0, m_spreadsheet->rowCount()); m_spreadsheet->addChild(new_col); } } //add rows if necessary if (last_row >= m_spreadsheet->rowCount()) m_spreadsheet->appendRows(last_row + 1 - m_spreadsheet->rowCount()); // select the rectangle to be pasted in setCellsSelected(first_row, first_col, last_row, last_col); } const int rows = last_row - first_row + 1; const int cols = last_col - first_col + 1; for (int c = 0; c < cols && c < input_col_count; c++) { Column* col = m_spreadsheet->column(first_col + c); col->setSuppressDataChangedSignal(true); - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { if (rows == m_spreadsheet->rowCount() && rows <= cellTexts.size()) { QVector new_data(rows); for (int r = 0; r < rows; ++r) { if (c < cellTexts.at(r).count()) new_data[r] = locale.toDouble(cellTexts.at(r).at(c)); } col->replaceValues(0, new_data); } else { for (int r = 0; r < rows && r < input_row_count; r++) { if ( isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count()) ) { if (!cellTexts.at(r).at(c).isEmpty()) col->setValueAt(first_row + r, locale.toDouble(cellTexts.at(r).at(c))); else col->setValueAt(first_row + r, std::numeric_limits::quiet_NaN()); } } } - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { if (rows == m_spreadsheet->rowCount() && rows <= cellTexts.size()) { QVector new_data(rows); for (int r = 0; r < rows; ++r) { if (c < cellTexts.at(r).count()) new_data[r] = locale.toInt(cellTexts.at(r).at(c)); } col->replaceInteger(0, new_data); } else { for (int r = 0; r < rows && r < input_row_count; r++) { if ( isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count()) ) { if (!cellTexts.at(r).at(c).isEmpty()) col->setIntegerAt(first_row + r, locale.toInt(cellTexts.at(r).at(c))); else col->setIntegerAt(first_row + r, 0); } } } - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { if (rows == m_spreadsheet->rowCount() && rows <= cellTexts.size()) { QVector new_data(rows); for (int r = 0; r < rows; ++r) new_data[r] = locale.toLongLong(cellTexts.at(r).at(c)); col->replaceBigInt(0, new_data); } else { for (int r = 0; r < rows && r < input_row_count; r++) { if ( isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count()) ) { if (!cellTexts.at(r).at(c).isEmpty()) col->setBigIntAt(first_row + r, locale.toLongLong(cellTexts.at(r).at(c))); else col->setBigIntAt(first_row + r, 0); } } } } else { for (int r = 0; r < rows && r < input_row_count; r++) { if (isCellSelected(first_row + r, first_col + c) && (c < cellTexts.at(r).count()) ) { // if (formulaModeActive()) // col->setFormula(first_row + r, cellTexts.at(r).at(c)); // else col->asStringColumn()->setTextAt(first_row + r, cellTexts.at(r).at(c)); } } } col->setSuppressDataChangedSignal(false); col->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::maskSelection() { int first = firstSelectedRow(); if (first < 0) return; int last = lastSelectedRow(); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: mask selected cells", m_spreadsheet->name())); QVector plots; //determine the dependent plots for (auto* column : selectedColumns()) column->addUsedInPlots(plots); //suppress retransform in the dependent plots for (auto* plot : plots) plot->setSuppressDataChangedSignal(true); //mask the selected cells for (auto* column : selectedColumns()) { int col = m_spreadsheet->indexOfChild(column); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) column->setMasked(row); } //retransform the dependent plots for (auto* plot : plots) { plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::unmaskSelection() { int first = firstSelectedRow(); if (first < 0) return; int last = lastSelectedRow(); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: unmask selected cells", m_spreadsheet->name())); QVector plots; //determine the dependent plots for (auto* column : selectedColumns()) column->addUsedInPlots(plots); //suppress retransform in the dependent plots for (auto* plot : plots) plot->setSuppressDataChangedSignal(true); //unmask the selected cells for (auto* column : selectedColumns()) { int col = m_spreadsheet->indexOfChild(column); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) column->setMasked(row, false); } //retransform the dependent plots for (auto* plot : plots) { plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::plotData() { const QAction* action = dynamic_cast(QObject::sender()); PlotDataDialog::PlotType type = PlotDataDialog::PlotXYCurve; if (action == action_plot_data_xycurve || action == action_plot_data_histogram) type = (PlotDataDialog::PlotType)action->data().toInt(); auto* dlg = new PlotDataDialog(m_spreadsheet, type); if (action != action_plot_data_xycurve && action != action_plot_data_histogram) { PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt(); dlg->setAnalysisAction(type); } dlg->exec(); } void SpreadsheetView::fillSelectedCellsWithRowNumbers() { if (selectedColumnCount() < 1) return; int first = firstSelectedRow(); if (first < 0) return; int last = lastSelectedRow(); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: fill cells with row numbers", m_spreadsheet->name())); for (auto* col_ptr : selectedColumns()) { int col = m_spreadsheet->indexOfChild(col_ptr); col_ptr->setSuppressDataChangedSignal(true); switch (col_ptr->columnMode()) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = row + 1; else results[row-first] = col_ptr->valueAt(row); col_ptr->replaceValues(first, results); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = row + 1; else results[row-first] = col_ptr->integerAt(row); col_ptr->replaceInteger(first, results); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = row + 1; else results[row-first] = col_ptr->bigIntAt(row); col_ptr->replaceBigInt(first, results); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { QVector results; for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results << QString::number(row+1); else results << col_ptr->textAt(row); col_ptr->replaceTexts(first, results); break; } //TODO: handle other modes - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } col_ptr->setSuppressDataChangedSignal(false); col_ptr->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::fillWithRowNumbers() { if (selectedColumnCount() < 1) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: fill column with row numbers", "%1: fill columns with row numbers", m_spreadsheet->name(), selectedColumnCount())); const int rows = m_spreadsheet->rowCount(); QVector int_data(rows); for (int i = 0; i < rows; ++i) int_data[i] = i + 1; for (auto* col : selectedColumns()) { switch (col->columnMode()) { - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: col->replaceInteger(0, int_data); break; - case AbstractColumn::Numeric: - case AbstractColumn::BigInt: - col->setColumnMode(AbstractColumn::Integer); + case AbstractColumn::ColumnMode::Numeric: + case AbstractColumn::ColumnMode::BigInt: + col->setColumnMode(AbstractColumn::ColumnMode::Integer); col->replaceInteger(0, int_data); break; - case AbstractColumn::Text: - case AbstractColumn::DateTime: - case AbstractColumn::Day: - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Text: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: break; } } m_spreadsheet->endMacro(); RESET_CURSOR; } //TODO: this function is not used currently. void SpreadsheetView::fillSelectedCellsWithRandomNumbers() { if (selectedColumnCount() < 1) return; int first = firstSelectedRow(); int last = lastSelectedRow(); if (first < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: fill cells with random values", m_spreadsheet->name())); qsrand(QTime::currentTime().msec()); for (auto* col_ptr : selectedColumns()) { int col = m_spreadsheet->indexOfChild(col_ptr); col_ptr->setSuppressDataChangedSignal(true); switch (col_ptr->columnMode()) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { //TODO qrand() is obsolete. Use QRandomGenerator instead QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = double(qrand())/double(RAND_MAX); else results[row-first] = col_ptr->valueAt(row); col_ptr->replaceValues(first, results); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { //TODO qrand() is obsolete. Use QRandomGenerator instead QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = qrand(); else results[row-first] = col_ptr->integerAt(row); col_ptr->replaceInteger(first, results); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { //TODO qrand() is obsolete. Use QRandomGenerator instead QVector results(last-first+1); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results[row-first] = qrand(); else results[row-first] = col_ptr->bigIntAt(row); col_ptr->replaceBigInt(first, results); break; } - case AbstractColumn::Text: { + case AbstractColumn::ColumnMode::Text: { //TODO qrand() is obsolete. Use QRandomGenerator instead QVector results; for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results << QString::number(double(qrand())/double(RAND_MAX)); else results << col_ptr->textAt(row); col_ptr->replaceTexts(first, results); break; } - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: { + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: { //TODO qrand() is obsolete. Use QRandomGenerator instead QVector results; QDate earliestDate(1, 1, 1); QDate latestDate(2999, 12, 31); QTime midnight(0, 0, 0, 0); for (int row = first; row <= last; row++) if (isCellSelected(row, col)) results << QDateTime( earliestDate.addDays(((double)qrand())*((double)earliestDate.daysTo(latestDate))/((double)RAND_MAX)), midnight.addMSecs(((qint64)qrand())*1000*60*60*24/RAND_MAX)); else results << col_ptr->dateTimeAt(row); col_ptr->replaceDateTimes(first, results); break; } } col_ptr->setSuppressDataChangedSignal(false); col_ptr->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::fillWithRandomValues() { if (selectedColumnCount() < 1) return; auto* dlg = new RandomValuesDialog(m_spreadsheet); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::fillWithEquidistantValues() { if (selectedColumnCount() < 1) return; auto* dlg = new EquidistantValuesDialog(m_spreadsheet); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::fillWithFunctionValues() { if (selectedColumnCount() < 1) return; auto* dlg = new FunctionValuesDialog(m_spreadsheet); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::fillSelectedCellsWithConstValues() { if (selectedColumnCount() < 1) return; int first = firstSelectedRow(); int last = lastSelectedRow(); if (first < 0) return; bool doubleOk = false; bool intOk = false; bool bigIntOk = false; bool stringOk = false; double doubleValue = 0; int intValue = 0; qint64 bigIntValue = 0; QString stringValue; m_spreadsheet->beginMacro(i18n("%1: fill cells with const values", m_spreadsheet->name())); for (auto* col_ptr : selectedColumns()) { int col = m_spreadsheet->indexOfChild(col_ptr); col_ptr->setSuppressDataChangedSignal(true); switch (col_ptr->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: if (!doubleOk) doubleValue = QInputDialog::getDouble(this, i18n("Fill the selection with constant value"), i18n("Value"), 0, -std::numeric_limits::max(), std::numeric_limits::max(), 6, &doubleOk); if (doubleOk) { WAIT_CURSOR; QVector results(last-first+1); for (int row = first; row <= last; row++) { if (isCellSelected(row, col)) results[row-first] = doubleValue; else results[row-first] = col_ptr->valueAt(row); } col_ptr->replaceValues(first, results); RESET_CURSOR; } break; - case AbstractColumn::Integer: + case AbstractColumn::ColumnMode::Integer: if (!intOk) intValue = QInputDialog::getInt(this, i18n("Fill the selection with constant value"), i18n("Value"), 0, -2147483647, 2147483647, 1, &intOk); if (intOk) { WAIT_CURSOR; QVector results(last-first+1); for (int row = first; row <= last; row++) { if (isCellSelected(row, col)) results[row-first] = intValue; else results[row-first] = col_ptr->integerAt(row); } col_ptr->replaceInteger(first, results); RESET_CURSOR; } break; - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::BigInt: //TODO: getBigInt() if (!bigIntOk) bigIntValue = QInputDialog::getInt(this, i18n("Fill the selection with constant value"), i18n("Value"), 0, -2147483647, 2147483647, 1, &bigIntOk); if (bigIntOk) { WAIT_CURSOR; QVector results(last-first+1); for (int row = first; row <= last; row++) { if (isCellSelected(row, col)) results[row-first] = bigIntValue; else results[row-first] = col_ptr->bigIntAt(row); } col_ptr->replaceBigInt(first, results); RESET_CURSOR; } break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: if (!stringOk) stringValue = QInputDialog::getText(this, i18n("Fill the selection with constant value"), i18n("Value"), QLineEdit::Normal, nullptr, &stringOk); if (stringOk && !stringValue.isEmpty()) { WAIT_CURSOR; QVector results; for (int row = first; row <= last; row++) { if (isCellSelected(row, col)) results << stringValue; else results << col_ptr->textAt(row); } col_ptr->replaceTexts(first, results); RESET_CURSOR; } break; //TODO: handle other modes - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: break; } col_ptr->setSuppressDataChangedSignal(false); col_ptr->setChanged(); } m_spreadsheet->endMacro(); } /*! Open the sort dialog for all columns. */ void SpreadsheetView::sortSpreadsheet() { sortDialog(m_spreadsheet->children()); } /*! Insert an empty column left to the first selected column */ void SpreadsheetView::insertColumnLeft() { insertColumnsLeft(1); } /*! Insert multiple empty columns left to the firt selected column */ void SpreadsheetView::insertColumnsLeft() { bool ok = false; int count = QInputDialog::getInt(nullptr, i18n("Insert empty columns"), i18n("Enter the number of columns to insert"), 1/*value*/, 1/*min*/, 1000/*max*/, 1/*step*/, &ok); if (!ok) return; insertColumnsLeft(count); } /*! * private helper function doing the actual insertion of columns to the left */ void SpreadsheetView::insertColumnsLeft(int count) { WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: insert empty column", "%1: insert empty columns", m_spreadsheet->name(), count )); const int first = firstSelectedColumn(); if (first >= 0) { //determine the first selected column Column* firstCol = m_spreadsheet->child(first); for (int i = 0; i < count; ++i) { - Column* newCol = new Column(QString::number(i + 1), AbstractColumn::Numeric); + Column* newCol = new Column(QString::number(i + 1), AbstractColumn::ColumnMode::Numeric); newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); //resize the new column and insert it before the first selected column newCol->insertRows(0, m_spreadsheet->rowCount()); m_spreadsheet->insertChildBefore(newCol, firstCol); } } else { if (m_spreadsheet->columnCount()>0) { //columns available but no columns selected -> prepend the new column at the very beginning Column* firstCol = m_spreadsheet->child(0); for (int i = 0; i < count; ++i) { - Column* newCol = new Column(QString::number(i + 1), AbstractColumn::Numeric); + Column* newCol = new Column(QString::number(i + 1), AbstractColumn::ColumnMode::Numeric); newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); newCol->insertRows(0, m_spreadsheet->rowCount()); m_spreadsheet->insertChildBefore(newCol, firstCol); } } else { //no columns available anymore -> resize the spreadsheet and the new column to the default size KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet")); const int rows = group.readEntry(QLatin1String("RowCount"), 100); m_spreadsheet->setRowCount(rows); for (int i = 0; i < count; ++i) { - Column* newCol = new Column(QString::number(i + 1), AbstractColumn::Numeric); + Column* newCol = new Column(QString::number(i + 1), AbstractColumn::ColumnMode::Numeric); (i == 0) ? newCol->setPlotDesignation(AbstractColumn::PlotDesignation::X) : newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); newCol->insertRows(0, rows); //add/append a new column m_spreadsheet->addChild(newCol); } } } m_spreadsheet->endMacro(); RESET_CURSOR; } /*! Insert an empty column right to the last selected column */ void SpreadsheetView::insertColumnRight() { insertColumnsRight(1); } /*! Insert multiple empty columns right to the last selected column */ void SpreadsheetView::insertColumnsRight() { bool ok = false; int count = QInputDialog::getInt(nullptr, i18n("Insert empty columns"), i18n("Enter the number of columns to insert"), 1/*value*/, 1/*min*/, 1000/*max*/, 1/*step*/, &ok); if (!ok) return; insertColumnsRight(count); } /*! * private helper function doing the actual insertion of columns to the right */ void SpreadsheetView::insertColumnsRight(int count) { WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: insert empty column", "%1: insert empty columns", m_spreadsheet->name(), count )); const int last = lastSelectedColumn(); if (last >= 0) { if (last < m_spreadsheet->columnCount() - 1) { //determine the column next to the last selected column Column* nextCol = m_spreadsheet->child(last + 1); for (int i = 0; i < count; ++i) { - Column* newCol = new Column(QString::number(i+1), AbstractColumn::Numeric); + Column* newCol = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric); newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); newCol->insertRows(0, m_spreadsheet->rowCount()); //insert the new column before the column next to the last selected column m_spreadsheet->insertChildBefore(newCol, nextCol); } } else { for (int i = 0; i < count; ++i) { - Column* newCol = new Column(QString::number(i+1), AbstractColumn::Numeric); + Column* newCol = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric); newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); newCol->insertRows(0, m_spreadsheet->rowCount()); //last column selected, no next column available -> add/append a new column m_spreadsheet->addChild(newCol); } } } else { if (m_spreadsheet->columnCount()>0) { for (int i = 0; i < count; ++i) { - Column* newCol = new Column(QString::number(i+1), AbstractColumn::Numeric); + Column* newCol = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric); newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); newCol->insertRows(0, m_spreadsheet->rowCount()); //columns available but no columns selected -> append the new column at the very end m_spreadsheet->addChild(newCol); } } else { //no columns available anymore -> resize the spreadsheet and the new column to the default size KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Spreadsheet")); const int rows = group.readEntry(QLatin1String("RowCount"), 100); m_spreadsheet->setRowCount(rows); for (int i = 0; i < count; ++i) { - Column* newCol = new Column(QString::number(i+1), AbstractColumn::Numeric); + Column* newCol = new Column(QString::number(i+1), AbstractColumn::ColumnMode::Numeric); (i == 0) ? newCol->setPlotDesignation(AbstractColumn::PlotDesignation::X) : newCol->setPlotDesignation(AbstractColumn::PlotDesignation::Y); newCol->insertRows(0, rows); //add/append a new column m_spreadsheet->addChild(newCol); } } } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::removeSelectedColumns() { WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: remove selected columns", m_spreadsheet->name())); for (auto* column : selectedColumns()) m_spreadsheet->removeChild(column); m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::clearSelectedColumns() { WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: clear selected columns", m_spreadsheet->name())); if (formulaModeActive()) { for (auto* col : selectedColumns()) { col->setSuppressDataChangedSignal(true); col->clearFormulas(); col->setSuppressDataChangedSignal(false); col->setChanged(); } } else { for (auto* col : selectedColumns()) { col->setSuppressDataChangedSignal(true); col->clear(); col->setSuppressDataChangedSignal(false); col->setChanged(); } } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::setSelectionAs() { QVector columns = selectedColumns(); if (!columns.size()) return; m_spreadsheet->beginMacro(i18n("%1: set plot designation", m_spreadsheet->name())); QAction* action = dynamic_cast(QObject::sender()); if (!action) return; - AbstractColumn::PlotDesignation pd = (AbstractColumn::PlotDesignation)action->data().toInt(); + auto pd = (AbstractColumn::PlotDesignation)action->data().toInt(); for (auto* col : columns) col->setPlotDesignation(pd); m_spreadsheet->endMacro(); } /*! * add, subtract, multiply, divide */ void SpreadsheetView::modifyValues() { if (selectedColumnCount() < 1) return; const QAction* action = dynamic_cast(QObject::sender()); - AddSubtractValueDialog::Operation op = (AddSubtractValueDialog::Operation)action->data().toInt(); + auto op = (AddSubtractValueDialog::Operation)action->data().toInt(); auto* dlg = new AddSubtractValueDialog(m_spreadsheet, op); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::reverseColumns() { WAIT_CURSOR; QVector cols = selectedColumns(); m_spreadsheet->beginMacro(i18np("%1: reverse column", "%1: reverse columns", m_spreadsheet->name(), cols.size())); for (auto* col : cols) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { //determine the last row containing a valid value, //ignore all following empty rows when doing the reverse auto* data = static_cast* >(col->data()); QVector new_data(*data); auto itEnd = new_data.begin(); auto it = new_data.begin(); while (it != new_data.end()) { if (!std::isnan(*it)) itEnd = it; ++it; } ++itEnd; std::reverse(new_data.begin(), itEnd); col->replaceValues(0, new_data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { auto* data = static_cast* >(col->data()); QVector new_data(*data); std::reverse(new_data.begin(), new_data.end()); col->replaceInteger(0, new_data); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { auto* data = static_cast* >(col->data()); QVector new_data(*data); std::reverse(new_data.begin(), new_data.end()); col->replaceBigInt(0, new_data); } } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::dropColumnValues() { if (selectedColumnCount() < 1) return; auto* dlg = new DropValuesDialog(m_spreadsheet); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::maskColumnValues() { if (selectedColumnCount() < 1) return; auto* dlg = new DropValuesDialog(m_spreadsheet, true); dlg->setColumns(selectedColumns()); dlg->exec(); } void SpreadsheetView::joinColumns() { //TODO } void SpreadsheetView::normalizeSelectedColumns(QAction* action) { auto columns = selectedColumns(); if (columns.isEmpty()) return; auto method = static_cast(action->data().toInt()); double rescaleIntervalMin = 0.0; double rescaleIntervalMax = 0.0; if (method == Rescale) { auto* dlg = new RescaleDialog(this); dlg->setColumns(columns); int rc = dlg->exec(); if (rc != QDialog::Accepted) return; rescaleIntervalMin = dlg->min(); rescaleIntervalMax = dlg->max(); delete dlg; } WAIT_CURSOR; QStringList messages; QString message = i18n("Normalization of the column %1 was not possible because of %2."); m_spreadsheet->beginMacro(i18n("%1: normalize columns", m_spreadsheet->name())); for (auto* col : columns) { - if (col->columnMode() != AbstractColumn::Numeric - && col->columnMode() != AbstractColumn::Integer - && col->columnMode() != AbstractColumn::BigInt) + if (col->columnMode() != AbstractColumn::ColumnMode::Numeric + && col->columnMode() != AbstractColumn::ColumnMode::Integer + && col->columnMode() != AbstractColumn::ColumnMode::BigInt) continue; - if (col->columnMode() == AbstractColumn::Integer - || col->columnMode() == AbstractColumn::BigInt) - col->setColumnMode(AbstractColumn::Numeric); + if (col->columnMode() == AbstractColumn::ColumnMode::Integer + || col->columnMode() == AbstractColumn::ColumnMode::BigInt) + col->setColumnMode(AbstractColumn::ColumnMode::Numeric); auto* data = static_cast* >(col->data()); QVector new_data(col->rowCount()); switch (method) { case DivideBySum: { double sum = std::accumulate(data->begin(), data->end(), 0); if (sum != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / sum; } else { messages << message.arg(col->name()).arg(QLatin1String("Sum = 0")); continue; } break; } case DivideByMin: { double min = col->minimum(); if (min != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / min; } else { messages << message.arg(col->name()).arg(QLatin1String("Min = 0")); continue; } break; } case DivideByMax: { double max = col->maximum(); if (max != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / max; } else { messages << message.arg(col->name()).arg(QLatin1String("Max = 0")); continue; } break; } case DivideByCount: { int count = data->size(); if (count != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / count; } else { messages << message.arg(col->name()).arg(QLatin1String("Count = 0")); continue; } break; } case DivideByMean: { double mean = col->statistics().arithmeticMean; if (mean != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / mean; } else { messages << message.arg(col->name()).arg(QLatin1String("Mean = 0")); continue; } break; } case DivideByMedian: { double median = col->statistics().median; if (median != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / median; } else { messages << message.arg(col->name()).arg(QLatin1String("Median = 0")); continue; } break; } case DivideByMode: { double mode = col->statistics().mode; if (mode != 0.0 && !std::isnan(mode)) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / mode; } else { if (mode == 0.0) messages << message.arg(col->name()).arg(QLatin1String("Mode = 0")); else messages << message.arg(col->name()).arg(i18n("'Mode not defined'")); continue; } break; } case DivideByRange: { double range = col->statistics().maximum - col->statistics().minimum; if (range != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / range; } else { messages << message.arg(col->name()).arg(QLatin1String("Range = 0")); continue; } break; } case DivideBySD: { double std = col->statistics().standardDeviation; if (std != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / std; } else { messages << message.arg(col->name()).arg(QLatin1String("SD = 0")); continue; } break; } case DivideByMAD: { double mad = col->statistics().medianDeviation; if (mad != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / mad; } else { messages << message.arg(col->name()).arg(QLatin1String("MAD = 0")); continue; } break; } case DivideByIQR: { double iqr = col->statistics().iqr; if (iqr != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = data->operator[](i) / iqr; } else { messages << message.arg(col->name()).arg(QLatin1String("IQR = 0")); continue; } break; } case ZScoreSD: { double mean = col->statistics().arithmeticMean; double std = col->statistics().standardDeviation; if (std != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = (data->operator[](i) - mean) / std; } else { messages << message.arg(col->name()).arg(QLatin1String("SD = 0")); continue; } break; } case ZScoreMAD: { double median = col->statistics().median; double mad = col->statistics().medianDeviation; if (mad != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = (data->operator[](i) - median) / mad; } else { messages << message.arg(col->name()).arg(QLatin1String("MAD = 0")); continue; } break; } case ZScoreIQR: { double median = col->statistics().median; double iqr = col->statistics().thirdQuartile - col->statistics().firstQuartile; if (iqr != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = (data->operator[](i) - median) / iqr; } else { messages << message.arg(col->name()).arg(QLatin1String("IQR = 0")); continue; } break; } case Rescale: { double min = col->statistics().minimum; double max = col->statistics().maximum; if (max - min != 0.0) { for (int i = 0; i < col->rowCount(); ++i) new_data[i] = rescaleIntervalMin + (data->operator[](i) - min)/(max - min)*(rescaleIntervalMax - rescaleIntervalMin); } else { messages << message.arg(col->name()).arg(QLatin1String("Max - Min = 0")); continue; } break; } } col->replaceValues(0, new_data); } m_spreadsheet->endMacro(); RESET_CURSOR; if (!messages.isEmpty()) { QString info; for (const QString& message : messages) { if (!info.isEmpty()) info += QLatin1String("

"); info += message; } QMessageBox::warning(this, i18n("Normalization not possible"), info); } } void SpreadsheetView::normalizeSelection() { WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: normalize selection", m_spreadsheet->name())); double max = 0.0; for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++) - if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::Numeric) + if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::ColumnMode::Numeric) for (int row = 0; row < m_spreadsheet->rowCount(); row++) { if (isCellSelected(row, col) && m_spreadsheet->column(col)->valueAt(row) > max) max = m_spreadsheet->column(col)->valueAt(row); } if (max != 0.0) { // avoid division by zero //TODO setSuppressDataChangedSignal for (int col = firstSelectedColumn(); col <= lastSelectedColumn(); col++) - if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::Numeric) + if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::ColumnMode::Numeric) for (int row = 0; row < m_spreadsheet->rowCount(); row++) { if (isCellSelected(row, col)) m_spreadsheet->column(col)->setValueAt(row, m_spreadsheet->column(col)->valueAt(row) / max); } } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::sortSelectedColumns() { sortDialog(selectedColumns()); } void SpreadsheetView::showAllColumnsStatistics() { showColumnStatistics(true); } void SpreadsheetView::showColumnStatistics(bool forAll) { QString dlgTitle(m_spreadsheet->name() + " column statistics"); auto* dlg = new StatisticsDialog(dlgTitle); QVector columns; if (!forAll) dlg->setColumns(selectedColumns()); else if (forAll) { for (int col = 0; col < m_spreadsheet->columnCount(); ++col) { - if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::Numeric) + if (m_spreadsheet->column(col)->columnMode() == AbstractColumn::ColumnMode::Numeric) columns << m_spreadsheet->column(col); } dlg->setColumns(columns); } if (dlg->exec() == QDialog::Accepted) { if (forAll) columns.clear(); } } void SpreadsheetView::showRowStatistics() { QString dlgTitle(m_spreadsheet->name() + " row statistics"); auto* dlg = new StatisticsDialog(dlgTitle); QVector columns; for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { if (isRowSelected(i)) { QVector rowValues; for (int j = 0; j < m_spreadsheet->columnCount(); ++j) rowValues << m_spreadsheet->column(j)->valueAt(i); columns << new Column(QString::number(i+1), rowValues); } } dlg->setColumns(columns); if (dlg->exec() == QDialog::Accepted) { qDeleteAll(columns); columns.clear(); } } /*! Insert an empty row above(=before) the first selected row */ void SpreadsheetView::insertRowAbove() { insertRowsAbove(1); } /*! Insert multiple empty rows above(=before) the first selected row */ void SpreadsheetView::insertRowsAbove() { bool ok = false; int count = QInputDialog::getInt(nullptr, i18n("Insert multiple rows"), i18n("Enter the number of rows to insert"), 1/*value*/, 1/*min*/, 1000000/*max*/, 1/*step*/, &ok); if (ok) insertRowsAbove(count); } /*! * private helper function doing the actual insertion of rows above */ void SpreadsheetView::insertRowsAbove(int count) { int first = firstSelectedRow(); if (first < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: insert empty row", "%1: insert empty rows", m_spreadsheet->name(), count )); m_spreadsheet->insertRows(first, count); m_spreadsheet->endMacro(); RESET_CURSOR; } /*! Insert an empty row below the last selected row */ void SpreadsheetView::insertRowBelow() { insertRowsBelow(1); } /*! Insert an empty row below the last selected row */ void SpreadsheetView::insertRowsBelow() { bool ok = false; int count = QInputDialog::getInt(nullptr, i18n("Insert multiple rows"), i18n("Enter the number of rows to insert"), 1/*value*/, 1/*min*/, 1000000/*max*/, 1/*step*/, &ok); if (ok) insertRowsBelow(count); } /*! * private helper function doing the actual insertion of rows below */ void SpreadsheetView::insertRowsBelow(int count) { int last = lastSelectedRow(); if (last < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: insert empty row", "%1: insert empty rows", m_spreadsheet->name(), count )); if (last < m_spreadsheet->rowCount() - 1) m_spreadsheet->insertRows(last + 1, count); //insert before the next to the last selected row else m_spreadsheet->appendRows(count); //append new rows at the end m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::removeSelectedRows() { if (firstSelectedRow() < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: remove selected rows", m_spreadsheet->name())); //TODO setSuppressDataChangedSignal for (const auto& i : selectedRows().intervals()) m_spreadsheet->removeRows(i.start(), i.size()); m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::clearSelectedRows() { if (firstSelectedRow() < 0) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: clear selected rows", m_spreadsheet->name())); for (auto* col : selectedColumns()) { col->setSuppressDataChangedSignal(true); if (formulaModeActive()) { for (const auto& i : selectedRows().intervals()) col->setFormula(i, QString()); } else { for (const auto& i : selectedRows().intervals()) { if (i.end() == col->rowCount()-1) col->removeRows(i.start(), i.size()); else { QVector empties; for (int j = 0; j < i.size(); j++) empties << QString(); col->asStringColumn()->replaceTexts(i.start(), empties); } } } col->setSuppressDataChangedSignal(false); col->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; //selected rows were deleted but the view selection is still in place -> reset the selection in the view m_tableView->clearSelection(); } void SpreadsheetView::clearSelectedCells() { int first = firstSelectedRow(); int last = lastSelectedRow(); if (first < 0) return; //don't try to clear values if the selected cells don't have any values at all bool empty = true; for (auto* column : selectedColumns()) { for (int row = last; row >= first; row--) { if (column->isValid(row)) { empty = false; break; } } if (!empty) break; } if (empty) return; WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: clear selected cells", m_spreadsheet->name())); for (auto* column : selectedColumns()) { column->setSuppressDataChangedSignal(true); if (formulaModeActive()) { int col = m_spreadsheet->indexOfChild(column); for (int row = last; row >= first; row--) if (isCellSelected(row, col)) column->setFormula(row, QString()); } else { int col = m_spreadsheet->indexOfChild(column); for (int row = last; row >= first; row--) if (isCellSelected(row, col)) { if (row < column->rowCount()) column->asStringColumn()->setTextAt(row, QString()); } } column->setSuppressDataChangedSignal(false); column->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; } void SpreadsheetView::goToCell() { bool ok; int col = QInputDialog::getInt(nullptr, i18n("Go to Cell"), i18n("Enter column"), 1, 1, m_spreadsheet->columnCount(), 1, &ok); if (!ok) return; int row = QInputDialog::getInt(nullptr, i18n("Go to Cell"), i18n("Enter row"), 1, 1, m_spreadsheet->rowCount(), 1, &ok); if (!ok) return; goToCell(row-1, col-1); } //! Open the sort dialog for the given columns void SpreadsheetView::sortDialog(const QVector& cols) { if (cols.isEmpty()) return; for (auto* col : cols) col->setSuppressDataChangedSignal(true); auto* dlg = new SortDialog(); connect(dlg, SIGNAL(sort(Column*,QVector,bool)), m_spreadsheet, SLOT(sortColumns(Column*,QVector,bool))); dlg->setColumns(cols); int rc = dlg->exec(); for (auto* col : cols) { col->setSuppressDataChangedSignal(false); if (rc == QDialog::Accepted) col->setChanged(); } } void SpreadsheetView::sortColumnAscending() { QVector cols = selectedColumns(); for (auto* col : cols) col->setSuppressDataChangedSignal(true); m_spreadsheet->sortColumns(cols.first(), cols, true); for (auto* col : cols) { col->setSuppressDataChangedSignal(false); col->setChanged(); } } void SpreadsheetView::sortColumnDescending() { QVector cols = selectedColumns(); for (auto* col : cols) col->setSuppressDataChangedSignal(true); m_spreadsheet->sortColumns(cols.first(), cols, false); for (auto* col : cols) { col->setSuppressDataChangedSignal(false); col->setChanged(); } } /*! Cause a repaint of the header. */ void SpreadsheetView::updateHeaderGeometry(Qt::Orientation o, int first, int last) { Q_UNUSED(first) Q_UNUSED(last) //TODO if (o != Qt::Horizontal) return; m_tableView->horizontalHeader()->setStretchLastSection(true); // ugly hack (flaw in Qt? Does anyone know a better way?) m_tableView->horizontalHeader()->updateGeometry(); m_tableView->horizontalHeader()->setStretchLastSection(false); // ugly hack part 2 } /*! selects the column \c column in the speadsheet view . */ void SpreadsheetView::selectColumn(int column) { QItemSelection selection(m_model->index(0, column), m_model->index(m_spreadsheet->rowCount()-1, column) ); m_suppressSelectionChangedEvent = true; m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select); m_suppressSelectionChangedEvent = false; } /*! deselects the column \c column in the speadsheet view . */ void SpreadsheetView::deselectColumn(int column) { QItemSelection selection(m_model->index(0, column), m_model->index(m_spreadsheet->rowCount()-1, column) ); m_suppressSelectionChangedEvent = true; m_tableView->selectionModel()->select(selection, QItemSelectionModel::Deselect); m_suppressSelectionChangedEvent = false; } /*! called when a column in the speadsheet view was clicked (click in the header). Propagates the selection of the column to the \c Spreadsheet object (a click in the header always selects the column). */ void SpreadsheetView::columnClicked(int column) { m_spreadsheet->setColumnSelectedInView(column, true); } /*! called on selections changes. Propagates the selection/deselection of columns to the \c Spreadsheet object. */ void SpreadsheetView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(selected); Q_UNUSED(deselected); if (m_suppressSelectionChangedEvent) return; QItemSelectionModel* selModel = m_tableView->selectionModel(); for (int i = 0; i < m_spreadsheet->columnCount(); i++) m_spreadsheet->setColumnSelectedInView(i, selModel->isColumnSelected(i, QModelIndex())); } bool SpreadsheetView::exportView() { auto* dlg = new ExportSpreadsheetDialog(this); dlg->setFileName(m_spreadsheet->name()); dlg->setExportTo(QStringList() << i18n("FITS image") << i18n("FITS table")); for (int i = 0; i < m_spreadsheet->columnCount(); ++i) { - if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::Numeric) { + if (m_spreadsheet->column(i)->columnMode() != AbstractColumn::ColumnMode::Numeric) { dlg->setExportToImage(false); break; } } if (selectedColumnCount() == 0) dlg->setExportSelection(false); bool ret; if ((ret = dlg->exec()) == QDialog::Accepted) { const QString path = dlg->path(); const bool exportHeader = dlg->exportHeader(); WAIT_CURSOR; switch (dlg->format()) { case ExportSpreadsheetDialog::ASCII: { const QString separator = dlg->separator(); const QLocale::Language format = dlg->numberFormat(); exportToFile(path, exportHeader, separator, format); break; } case ExportSpreadsheetDialog::Binary: break; case ExportSpreadsheetDialog::LaTeX: { const bool exportLatexHeader = dlg->exportLatexHeader(); const bool gridLines = dlg->gridLines(); const bool captions = dlg->captions(); const bool skipEmptyRows = dlg->skipEmptyRows(); const bool exportEntire = dlg->entireSpreadheet(); exportToLaTeX(path, exportHeader, gridLines, captions, exportLatexHeader, skipEmptyRows, exportEntire); break; } case ExportSpreadsheetDialog::FITS: { const int exportTo = dlg->exportToFits(); const bool commentsAsUnits = dlg->commentsAsUnitsFits(); exportToFits(path, exportTo, commentsAsUnits); break; } case ExportSpreadsheetDialog::SQLite: exportToSQLite(path); break; } RESET_CURSOR; } delete dlg; return ret; } bool SpreadsheetView::printView() { QPrinter printer; auto* dlg = new QPrintDialog(&printer, this); dlg->setWindowTitle(i18nc("@title:window", "Print Spreadsheet")); bool ret; if ((ret = dlg->exec()) == QDialog::Accepted) { print(&printer); } delete dlg; return ret; } bool SpreadsheetView::printPreview() { QPrintPreviewDialog* dlg = new QPrintPreviewDialog(this); connect(dlg, &QPrintPreviewDialog::paintRequested, this, &SpreadsheetView::print); return dlg->exec(); } /*! prints the complete spreadsheet to \c printer. */ void SpreadsheetView::print(QPrinter* printer) const { WAIT_CURSOR; QPainter painter (printer); const int dpiy = printer->logicalDpiY(); const int margin = (int) ( (1/2.54)*dpiy ); // 1 cm margins QHeaderView *hHeader = m_tableView->horizontalHeader(); QHeaderView *vHeader = m_tableView->verticalHeader(); const int rows = m_spreadsheet->rowCount(); const int cols = m_spreadsheet->columnCount(); int height = margin; const int vertHeaderWidth = vHeader->width(); int columnsPerTable = 0; int headerStringWidth = 0; int firstRowStringWidth = 0; bool tablesNeeded = false; for (int col = 0; col < cols; ++col) { headerStringWidth += m_tableView->columnWidth(col); firstRowStringWidth += m_spreadsheet->column(col)->asStringColumn()->textAt(0).length(); if ((headerStringWidth >= printer->pageRect().width() -2*margin) || (firstRowStringWidth >= printer->pageRect().width() - 2*margin)) { tablesNeeded = true; break; } columnsPerTable++; } int tablesCount = (columnsPerTable != 0) ? cols/columnsPerTable : 0; const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols; if (!tablesNeeded) { tablesCount = 1; columnsPerTable = cols; } if (remainingColumns > 0) tablesCount++; //Paint the horizontal header first for (int table = 0; table < tablesCount; ++table) { int right = margin + vertHeaderWidth; painter.setFont(hHeader->font()); QString headerString = m_tableView->model()->headerData(0, Qt::Horizontal).toString(); QRect br; br = painter.boundingRect(br, Qt::AlignCenter, headerString); QRect tr(br); if (table != 0) height += tr.height(); painter.drawLine(right, height, right, height+br.height()); int i = table * columnsPerTable; int toI = table * columnsPerTable + columnsPerTable; if ((remainingColumns > 0) && (table == tablesCount-1)) { i = (tablesCount-1)*columnsPerTable; toI = (tablesCount-1)* columnsPerTable + remainingColumns; } for (; imodel()->headerData(i, Qt::Horizontal).toString(); const int w = m_tableView->columnWidth(i); tr.setTopLeft(QPoint(right,height)); tr.setWidth(w); tr.setHeight(br.height()); painter.drawText(tr, Qt::AlignCenter, headerString); right += w; painter.drawLine(right, height, right, height+tr.height()); } painter.drawLine(margin + vertHeaderWidth, height, right-1, height);//first horizontal line height += tr.height(); painter.drawLine(margin, height, right-1, height); // print table values QString cellText; for (i = 0; i < rows; ++i) { right = margin; cellText = m_tableView->model()->headerData(i, Qt::Vertical).toString()+'\t'; tr = painter.boundingRect(tr, Qt::AlignCenter, cellText); painter.drawLine(right, height, right, height+tr.height()); br.setTopLeft(QPoint(right,height)); br.setWidth(vertHeaderWidth); br.setHeight(tr.height()); painter.drawText(br, Qt::AlignCenter, cellText); right += vertHeaderWidth; painter.drawLine(right, height, right, height+tr.height()); int j = table * columnsPerTable; int toJ = table * columnsPerTable + columnsPerTable; if ((remainingColumns > 0) && (table == tablesCount-1)) { j = (tablesCount-1)*columnsPerTable; toJ = (tablesCount-1)* columnsPerTable + remainingColumns; } for (; j < toJ; j++) { int w = m_tableView->columnWidth(j); cellText = m_spreadsheet->column(j)->isValid(i) ? m_spreadsheet->text(i,j)+'\t': QLatin1String("- \t"); tr = painter.boundingRect(tr,Qt::AlignCenter,cellText); br.setTopLeft(QPoint(right,height)); br.setWidth(w); br.setHeight(tr.height()); painter.drawText(br, Qt::AlignCenter, cellText); right += w; painter.drawLine(right, height, right, height+tr.height()); } height += br.height(); painter.drawLine(margin, height, right-1, height); if (height >= printer->height() - margin) { printer->newPage(); height = margin; painter.drawLine(margin, height, right, height); } } } RESET_CURSOR; } /*! * the spreadsheet can have empty rows at the end full with NaNs. * for the export we only need to export valid (non-empty) rows. * this functions determines the maximal row to export, or -1 * if the spreadsheet doesn't have any data yet. */ int SpreadsheetView::maxRowToExport() const { int maxRow = -1; for (int j = 0; j < m_spreadsheet->columnCount(); ++j) { Column* col = m_spreadsheet->column(j); - if (col->columnMode() == AbstractColumn::Numeric) { + auto mode = col->columnMode(); + if (mode == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { if (!std::isnan(col->valueAt(i)) && i > maxRow) maxRow = i; } - } if (col->columnMode() == AbstractColumn::Integer) { + } + if (mode == AbstractColumn::ColumnMode::Integer || mode == AbstractColumn::ColumnMode::BigInt) { //TODO: //integer column found. Since empty integer cells are equal to 0 //at the moment, we need to export the whole column. //this logic needs to be adjusted once we're able to descriminate //between empty and 0 values for integer columns maxRow = m_spreadsheet->rowCount(); break; - } else if (col->columnMode() == AbstractColumn::DateTime) { + } else if (mode == AbstractColumn::ColumnMode::DateTime) { for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { if (col->dateTimeAt(i).isValid() && i > maxRow) maxRow = i; } - } else if (col->columnMode() == AbstractColumn::Text) { + } else if (mode == AbstractColumn::ColumnMode::Text) { for (int i = 0; i < m_spreadsheet->rowCount(); ++i) { if (!col->textAt(i).isEmpty() && i > maxRow) maxRow = i; } } } return maxRow; } void SpreadsheetView::exportToFile(const QString& path, const bool exportHeader, const QString& separator, QLocale::Language language) const { QFile file(path); if (!file.open(QFile::WriteOnly | QFile::Truncate)) { RESET_CURSOR; QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path)); return; } PERFTRACE("export spreadsheet to file"); QTextStream out(&file); int maxRow = maxRowToExport(); if (maxRow < 0) return; const int cols = m_spreadsheet->columnCount(); QString sep = separator; sep = sep.replace(QLatin1String("TAB"), QLatin1String("\t"), Qt::CaseInsensitive); sep = sep.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); //export header (column names) if (exportHeader) { for (int j = 0; j < cols; ++j) { out << '"' << m_spreadsheet->column(j)->name() <<'"'; if (j != cols - 1) out << sep; } out << '\n'; } //export values QLocale locale(language); for (int i = 0; i <= maxRow; ++i) { for (int j = 0; j < cols; ++j) { Column* col = m_spreadsheet->column(j); - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { const Double2StringFilter* out_fltr = static_cast(col->outputFilter()); out << locale.toString(col->valueAt(i), out_fltr->numericFormat(), 16); // export with max. precision } else out << col->asStringColumn()->textAt(i); if (j != cols - 1) out << sep; } out << '\n'; } } void SpreadsheetView::exportToLaTeX(const QString & path, const bool exportHeaders, const bool gridLines, const bool captions, const bool latexHeaders, const bool skipEmptyRows, const bool exportEntire) const { QFile file(path); if (!file.open(QFile::WriteOnly | QFile::Truncate)) { RESET_CURSOR; QMessageBox::critical(nullptr, i18n("Failed to export"), i18n("Failed to write to '%1'. Please check the path.", path)); return; } QList toExport; int cols; int totalRowCount = 0; if (exportEntire) { cols = const_cast(this)->m_spreadsheet->columnCount(); totalRowCount = m_spreadsheet->rowCount(); for (int col = 0; col < cols; ++col) toExport << m_spreadsheet->column(col); } else { cols = const_cast(this)->selectedColumnCount(); const int firtsSelectedCol = const_cast(this)->firstSelectedColumn(); bool rowsCalculated = false; for (int col = firtsSelectedCol; col < firtsSelectedCol + cols; ++col) { QVector textData; for (int row = 0; row < m_spreadsheet->rowCount(); ++row) { if (const_cast(this)->isRowSelected(row)) { textData << m_spreadsheet->column(col)->asStringColumn()->textAt(row); if (!rowsCalculated) totalRowCount++; } } if (!rowsCalculated) rowsCalculated = true; Column* column = new Column(m_spreadsheet->column(col)->name(), textData); toExport << column; } } int columnsStringSize = 0; int columnsPerTable = 0; for (int i = 0; i < cols; ++i) { int maxSize = -1; for (int j = 0; j < toExport.at(i)->asStringColumn()->rowCount(); ++j) { if (toExport.at(i)->asStringColumn()->textAt(j).size() > maxSize) maxSize = toExport.at(i)->asStringColumn()->textAt(j).size(); } columnsStringSize += maxSize; if (!toExport.at(i)->isValid(0)) columnsStringSize += 3; if (columnsStringSize > 65) break; ++columnsPerTable; } const int tablesCount = (columnsPerTable != 0) ? cols/columnsPerTable : 0; const int remainingColumns = (columnsPerTable != 0) ? cols % columnsPerTable : cols; bool columnsSeparating = (cols > columnsPerTable); QTextStream out(&file); QProcess tex; tex.start("latex", QStringList() << "--version", QProcess::ReadOnly); tex.waitForFinished(500); QString texVersionOutput = QString(tex.readAllStandardOutput()); texVersionOutput = texVersionOutput.split('\n')[0]; int yearidx = -1; for (int i = texVersionOutput.size() - 1; i >= 0; --i) { if (texVersionOutput.at(i) == QChar('2')) { yearidx = i; break; } } if (texVersionOutput.at(yearidx+1) == QChar('/')) yearidx -= 3; bool ok; texVersionOutput.midRef(yearidx, 4).toInt(&ok); int version = -1; if (ok) version = texVersionOutput.midRef(yearidx, 4).toInt(&ok); if (latexHeaders) { out << QLatin1String("\\documentclass[11pt,a4paper]{article} \n"); out << QLatin1String("\\usepackage{geometry} \n"); out << QLatin1String("\\usepackage{xcolor,colortbl} \n"); if (version >= 2015) out << QLatin1String("\\extrafloats{1280} \n"); out << QLatin1String("\\definecolor{HeaderBgColor}{rgb}{0.81,0.81,0.81} \n"); out << QLatin1String("\\geometry{ \n"); out << QLatin1String("a4paper, \n"); out << QLatin1String("total={170mm,257mm}, \n"); out << QLatin1String("left=10mm, \n"); out << QLatin1String("top=10mm } \n"); out << QLatin1String("\\begin{document} \n"); out << QLatin1String("\\title{LabPlot Spreadsheet Export to \\LaTeX{} } \n"); out << QLatin1String("\\author{LabPlot} \n"); out << QLatin1String("\\date{\\today} \n"); } QString endTabularTable ("\\end{tabular} \n \\end{table} \n"); QString tableCaption ("\\caption{"+ m_spreadsheet->name()+ "} \n"); QString beginTable ("\\begin{table}[ht] \n"); int rowCount = 0; const int maxRows = 45; bool captionRemoved = false; if (columnsSeparating) { QVector emptyRowIndices; for (int table = 0; table < tablesCount; ++table) { QStringList textable; captionRemoved = false; textable << beginTable; if (captions) textable << tableCaption; textable << QLatin1String("\\centering \n"); textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); for (int i = 0; i < columnsPerTable; ++i) textable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") ); textable << QLatin1String("} \n"); if (gridLines) textable << QLatin1String("\\hline \n"); if (exportHeaders) { if (latexHeaders) textable << QLatin1String("\\rowcolor{HeaderBgColor} \n"); for (int col = table*columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col) { textable << toExport.at(col)->name(); if (col != ((table * columnsPerTable)+ columnsPerTable)-1) textable << QLatin1String(" & "); } textable << QLatin1String("\\\\ \n"); if (gridLines) textable << QLatin1String("\\hline \n"); } for (const auto& s : textable) out << s; QStringList values; for (int row = 0; row < totalRowCount; ++row) { values.clear(); bool notEmpty = false; for (int col = table*columnsPerTable; col < (table * columnsPerTable) + columnsPerTable; ++col ) { if (toExport.at(col)->isValid(row)) { notEmpty = true; values << toExport.at(col)->asStringColumn()->textAt(row); } else values << QLatin1String("-"); if (col != ((table * columnsPerTable)+ columnsPerTable)-1) values << QLatin1String(" & "); } if (!notEmpty && skipEmptyRows) { if (!emptyRowIndices.contains(row)) emptyRowIndices << row; } if (emptyRowIndices.contains(row) && notEmpty) emptyRowIndices.remove(emptyRowIndices.indexOf(row)); if (notEmpty || !skipEmptyRows) { for (const auto& s : values) out << s; out << QLatin1String("\\\\ \n"); if (gridLines) out << QLatin1String("\\hline \n"); rowCount++; if (rowCount == maxRows) { out << endTabularTable; out << QLatin1String("\\newpage \n"); if (captions) if (!captionRemoved) textable.removeAt(1); for (const auto& s : textable) out << s; rowCount = 0; if (!captionRemoved) captionRemoved = true; } } } out << endTabularTable; } //new table for the remaining columns QStringList remainingTable; remainingTable << beginTable; if (captions) remainingTable << tableCaption; remainingTable << QLatin1String("\\centering \n"); remainingTable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); for (int c = 0; c < remainingColumns; ++c) remainingTable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") ); remainingTable << QLatin1String("} \n"); if (gridLines) remainingTable << QLatin1String("\\hline \n"); if (exportHeaders) { if (latexHeaders) remainingTable << QLatin1String("\\rowcolor{HeaderBgColor} \n"); for (int col = 0; col < remainingColumns; ++col) { remainingTable << toExport.at(col + (tablesCount * columnsPerTable))->name(); if (col != remainingColumns-1) remainingTable << QLatin1String(" & "); } remainingTable << QLatin1String("\\\\ \n"); if (gridLines) remainingTable << QLatin1String("\\hline \n"); } for (const auto& s : remainingTable) out << s; QStringList values; captionRemoved = false; for (int row = 0; row < totalRowCount; ++row) { values.clear(); bool notEmpty = false; for (int col = 0; col < remainingColumns; ++col ) { if (toExport.at(col + (tablesCount * columnsPerTable))->isValid(row)) { notEmpty = true; values << toExport.at(col + (tablesCount * columnsPerTable))->asStringColumn()->textAt(row); } else values << QLatin1String("-"); if (col != remainingColumns-1) values << QLatin1String(" & "); } if (!emptyRowIndices.contains(row) && !notEmpty) notEmpty = true; if (notEmpty || !skipEmptyRows) { for (const auto& s : values) out << s; out << QLatin1String("\\\\ \n"); if (gridLines) out << QLatin1String("\\hline \n"); rowCount++; if (rowCount == maxRows) { out << endTabularTable; out << QLatin1String("\\pagebreak[4] \n"); if (captions) if (!captionRemoved) remainingTable.removeAt(1); for (const auto& s : remainingTable) out << s; rowCount = 0; if (!captionRemoved) captionRemoved = true; } } } out << endTabularTable; } else { QStringList textable; textable << beginTable; if (captions) textable << tableCaption; textable << QLatin1String("\\centering \n"); textable << QLatin1String("\\begin{tabular}{") << (gridLines ? QStringLiteral("|") : QString()); for (int c = 0; c < cols; ++c) textable << ( gridLines ? QLatin1String(" c |") : QLatin1String(" c ") ); textable << QLatin1String("} \n"); if (gridLines) textable << QLatin1String("\\hline \n"); if (exportHeaders) { if (latexHeaders) textable << QLatin1String("\\rowcolor{HeaderBgColor} \n"); for (int col = 0; col < cols; ++col) { textable << toExport.at(col)->name(); if (col != cols-1) textable << QLatin1String(" & "); } textable << QLatin1String("\\\\ \n"); if (gridLines) textable << QLatin1String("\\hline \n"); } for (const auto& s : textable) out << s; QStringList values; captionRemoved = false; for (int row = 0; row < totalRowCount; ++row) { values.clear(); bool notEmpty = false; for (int col = 0; col < cols; ++col ) { if (toExport.at(col)->isValid(row)) { notEmpty = true; values << toExport.at(col)->asStringColumn()->textAt(row); } else values << "-"; if (col != cols-1) values << " & "; } if (notEmpty || !skipEmptyRows) { for (const auto& s : values) out << s; out << QLatin1String("\\\\ \n"); if (gridLines) out << QLatin1String("\\hline \n"); rowCount++; if (rowCount == maxRows) { out << endTabularTable; out << QLatin1String("\\newpage \n"); if (captions) if (!captionRemoved) textable.removeAt(1); for (const auto& s : textable) out << s; rowCount = 0; if (!captionRemoved) captionRemoved = true; } } } out << endTabularTable; } if (latexHeaders) out << QLatin1String("\\end{document} \n"); if (!exportEntire) { qDeleteAll(toExport); toExport.clear(); } else toExport.clear(); } void SpreadsheetView::exportToFits(const QString &fileName, const int exportTo, const bool commentsAsUnits) const { auto* filter = new FITSFilter; filter->setExportTo(exportTo); filter->setCommentsAsUnits(commentsAsUnits); filter->write(fileName, m_spreadsheet); delete filter; } void SpreadsheetView::exportToSQLite(const QString& path) const { QFile file(path); if (!file.open(QFile::WriteOnly | QFile::Truncate)) return; PERFTRACE("export spreadsheet to SQLite database"); QApplication::processEvents(QEventLoop::AllEvents, 0); //create database const QStringList& drivers = QSqlDatabase::drivers(); QString driver; if (drivers.contains(QLatin1String("QSQLITE3"))) driver = QLatin1String("QSQLITE3"); else driver = QLatin1String("QSQLITE"); QSqlDatabase db = QSqlDatabase::addDatabase(driver); db.setDatabaseName(path); if (!db.open()) { RESET_CURSOR; KMessageBox::error(nullptr, i18n("Couldn't create the SQLite database %1.", path)); } //create table const int cols = m_spreadsheet->columnCount(); QString query = QLatin1String("create table ") + m_spreadsheet->name() + QLatin1String(" ("); for (int i = 0; i < cols; ++i) { Column* col = m_spreadsheet->column(i); if (i != 0) query += QLatin1String(", "); query += QLatin1String("\"") + col->name() + QLatin1String("\" "); switch (col->columnMode()) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: query += QLatin1String("REAL"); break; - case AbstractColumn::Integer: - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: query += QLatin1String("INTEGER"); break; - case AbstractColumn::Text: - case AbstractColumn::Month: - case AbstractColumn::Day: - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::Text: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::DateTime: query += QLatin1String("TEXT"); break; } } query += QLatin1Char(')'); QSqlQuery q; if (!q.exec(query)) { RESET_CURSOR; KMessageBox::error(nullptr, i18n("Failed to create table in the SQLite database %1.", path) + '\n' + q.lastError().databaseText()); db.close(); return; } int maxRow = maxRowToExport(); if (maxRow < 0) { db.close(); return; } //create bulk insert statement { PERFTRACE("Create the bulk insert statement"); q.exec(QLatin1String("BEGIN TRANSACTION;")); query = "INSERT INTO '" + m_spreadsheet->name() + "' ("; for (int i = 0; i < cols; ++i) { if (i != 0) query += QLatin1String(", "); query += QLatin1Char('\'') + m_spreadsheet->column(i)->name() + QLatin1Char('\''); } query += QLatin1String(") VALUES "); for (int i = 0; i <= maxRow; ++i) { if (i != 0) query += QLatin1String(","); query += QLatin1Char('('); for (int j = 0; j < cols; ++j) { Column* col = m_spreadsheet->column(j); if (j != 0) query += QLatin1String(", "); query += QLatin1Char('\'') + col->asStringColumn()->textAt(i) + QLatin1Char('\''); } query += QLatin1String(")"); } query += QLatin1Char(';'); } //insert values if (!q.exec(query)) { RESET_CURSOR; KMessageBox::error(nullptr, i18n("Failed to insert values into the table.")); QDEBUG("bulk insert error " << q.lastError().databaseText()); } else q.exec(QLatin1String("COMMIT TRANSACTION;")); //close the database db.close(); } diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index ee77adc22..f6274085f 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.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.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType", (int)LiveDataSource::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()); 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) { 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) { 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) { 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->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); 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::Numeric); + 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); 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) { //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); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) ui.cbReadingType->setCurrentIndex(LiveDataSource::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); 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/ImportSQLDatabaseWidget.cpp b/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp index b2ee7f74b..ed0560416 100644 --- a/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp +++ b/src/kdefrontend/datasources/ImportSQLDatabaseWidget.cpp @@ -1,495 +1,495 @@ /*************************************************************************** File : ImportSQLDatabaseWidget.cpp Project : LabPlot Description : Datapicker -------------------------------------------------------------------- Copyright : (C) 2016 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2016-2017 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 "ImportSQLDatabaseWidget.h" #include "DatabaseManagerDialog.h" #include "DatabaseManagerWidget.h" #include "backend/datasources/AbstractDataSource.h" #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/lib/macros.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING #include #include #include #endif ImportSQLDatabaseWidget::ImportSQLDatabaseWidget(QWidget* parent) : QWidget(parent) { ui.setupUi(this); ui.cbImportFrom->addItem(i18n("Table")); ui.cbImportFrom->addItem(i18n("Custom query")); ui.bDatabaseManager->setIcon(QIcon::fromTheme("network-server-database")); ui.bDatabaseManager->setToolTip(i18n("Manage connections")); ui.twPreview->setEditTriggers(QAbstractItemView::NoEditTriggers); ui.cbNumberFormat->addItems(AbstractFileFilter::numberFormats()); ui.cbDateTimeFormat->addItems(AbstractColumn::dateTimeFormats()); #ifdef HAVE_KF5_SYNTAX_HIGHLIGHTING m_highlighter = new KSyntaxHighlighting::SyntaxHighlighter(ui.teQuery->document()); m_highlighter->setDefinition(m_repository.definitionForName("SQL")); m_highlighter->setTheme( (palette().color(QPalette::Base).lightness() < 128) ? m_repository.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme) : m_repository.defaultTheme(KSyntaxHighlighting::Repository::LightTheme) ); #endif m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + "sql_connections"; connect( ui.cbConnection, SIGNAL(currentIndexChanged(int)), SLOT(connectionChanged()) ); connect( ui.cbImportFrom, SIGNAL(currentIndexChanged(int)), SLOT(importFromChanged(int)) ); connect( ui.bDatabaseManager, SIGNAL(clicked()), this, SLOT(showDatabaseManager()) ); connect( ui.lwTables, SIGNAL(currentRowChanged(int)), this, SLOT(refreshPreview()) ); connect( ui.bRefreshPreview, SIGNAL(clicked()), this, SLOT(refreshPreview()) ); //defer the loading of settings a bit in order to show the dialog prior to blocking the GUI in refreshPreview() QTimer::singleShot( 100, this, SLOT(loadSettings()) ); } void ImportSQLDatabaseWidget::loadSettings() { m_initializing = true; //read available connections readConnections(); //load last used connection and other settings KConfigGroup config(KSharedConfig::openConfig(), "ImportSQLDatabaseWidget"); ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(config.readEntry("Connection", ""))); ui.cbImportFrom->setCurrentIndex(config.readEntry("ImportFrom", 0)); importFromChanged(ui.cbImportFrom->currentIndex()); ui.cbNumberFormat->setCurrentIndex(config.readEntry("NumberFormat", (int)QLocale::AnyLanguage)); ui.cbDateTimeFormat->setCurrentItem(config.readEntry("DateTimeFormat", "yyyy-dd-MM hh:mm:ss:zzz")); QList defaultSizes; defaultSizes << 100 << 100; ui.splitterMain->setSizes(config.readEntry("SplitterMainSizes", defaultSizes)); ui.splitterPreview->setSizes(config.readEntry("SplitterPreviewSizes", defaultSizes)); //TODO m_initializing = false; //all settings loaded -> trigger the selection of the last used connection in order to get the data preview connectionChanged(); } ImportSQLDatabaseWidget::~ImportSQLDatabaseWidget() { // save current settings KConfigGroup config(KSharedConfig::openConfig(), "ImportSQLDatabaseWidget"); config.writeEntry("Connection", ui.cbConnection->currentText()); config.writeEntry("ImportFrom", ui.cbImportFrom->currentIndex()); config.writeEntry("NumberFormat", ui.cbNumberFormat->currentIndex()); config.writeEntry("DateTimeFormat", ui.cbDateTimeFormat->currentText()); config.writeEntry("SplitterMainSizes", ui.splitterMain->sizes()); config.writeEntry("SplitterPreviewSizes", ui.splitterPreview->sizes()); //TODO } /*! * in case the import from a table is selected, returns the currently selected database table. * returns empty string otherwise. */ QString ImportSQLDatabaseWidget::selectedTable() const { if (ui.cbImportFrom->currentIndex() == 0) { if (ui.lwTables->currentItem()) return ui.lwTables->currentItem()->text(); } return QString(); } /*! returns \c true if a working connections was selected and a table (or custom query) is provided and ready to be imported. returns \c false otherwise. */ bool ImportSQLDatabaseWidget::isValid() const { return m_valid; } /*! returns \c true if the selected table or the result of a custom query contains numeric data only. returns \c false otherwise. */ bool ImportSQLDatabaseWidget::isNumericData() const { return m_numeric; } /*! loads all available saved connections */ void ImportSQLDatabaseWidget::readConnections() { DEBUG("ImportSQLDatabaseWidget: reading available connections"); KConfig config(m_configPath, KConfig::SimpleConfig); for (const auto& name : config.groupList()) ui.cbConnection->addItem(name); } void ImportSQLDatabaseWidget::connectionChanged() { if (m_initializing) return; QDEBUG("ImportSQLDatabaseWidget: connecting to " + ui.cbConnection->currentText()); //clear the previously shown content ui.teQuery->clear(); ui.lwTables->clear(); ui.twPreview->clear(); ui.twPreview->setColumnCount(0); ui.twPreview->setRowCount(0); if (ui.cbConnection->currentIndex() == -1) return; //connection name was changed, determine the current connections settings KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); //close and remove the previous connection, if available if (m_db.isOpen()) { m_db.close(); QSqlDatabase::removeDatabase(m_db.driverName()); } //open the selected connection const QString& driver = group.readEntry("Driver"); m_db = QSqlDatabase::addDatabase(driver); const QString& dbName = group.readEntry("DatabaseName"); if (DatabaseManagerWidget::isFileDB(driver)) { if (!QFile::exists(dbName)) { KMessageBox::error(this, i18n("Couldn't find the database file '%1'. Please check the connection settings.", dbName), i18n("Connection Failed")); setInvalid(); return; } else m_db.setDatabaseName(dbName); } else if (DatabaseManagerWidget::isODBC(driver)) { if (group.readEntry("CustomConnectionEnabled", false)) m_db.setDatabaseName(group.readEntry("CustomConnectionString")); else m_db.setDatabaseName(dbName); } else { m_db.setDatabaseName(dbName); m_db.setHostName( group.readEntry("HostName") ); m_db.setPort( group.readEntry("Port", 0) ); m_db.setUserName( group.readEntry("UserName") ); m_db.setPassword( group.readEntry("Password") ); } WAIT_CURSOR; if (!m_db.open()) { RESET_CURSOR; KMessageBox::error(this, i18n("Failed to connect to the database '%1'. Please check the connection settings.", ui.cbConnection->currentText()) + QLatin1String("\n\n") + m_db.lastError().databaseText(), i18n("Connection Failed")); setInvalid(); return; } //show all available database tables if (m_db.tables().size()) { ui.lwTables->addItems(m_db.tables()); ui.lwTables->setCurrentRow(0); for (int i = 0; i < ui.lwTables->count(); ++i) ui.lwTables->item(i)->setIcon(QIcon::fromTheme("view-form-table")); } else setInvalid(); RESET_CURSOR; } void ImportSQLDatabaseWidget::refreshPreview() { if (!ui.lwTables->currentItem()) { setInvalid(); return; } WAIT_CURSOR; ui.twPreview->clear(); //execute the current query (select on a table or a custom query) const QString& query = currentQuery(true); if (query.isEmpty()) { RESET_CURSOR; setInvalid(); return; } QSqlQuery q; q.prepare(currentQuery(true)); q.setForwardOnly(true); q.exec(); if (!q.isActive() || !q.next()) { // check if query was successful and got to first record RESET_CURSOR; if (!q.lastError().databaseText().isEmpty()) KMessageBox::error(this, q.lastError().databaseText(), i18n("Unable to Execute Query")); setInvalid(); return; } //resize the table to the number of columns (=number of fields in the result set) m_cols = q.record().count(); ui.twPreview->setColumnCount(m_cols); //determine the names and the data type (column modes) of the table columns. //check whether we have numerical data only by checking the data types of the first record. m_columnNames.clear(); m_columnModes.clear(); bool numeric = true; const auto numberFormat = (QLocale::Language)ui.cbNumberFormat->currentIndex(); const QString& dateTimeFormat = ui.cbDateTimeFormat->currentText(); // ui.twPreview->setRowCount(1); //add the first row for the check boxes for (int i = 0; i < m_cols; ++i) { //name m_columnNames << q.record().fieldName(i); //value and type const QString valueString = q.record().value(i).toString(); AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); m_columnModes << mode; - if (mode != AbstractColumn::Numeric) + if (mode != AbstractColumn::ColumnMode::Numeric) numeric = false; //header item - QTableWidgetItem* item = new QTableWidgetItem(m_columnNames[i] + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, mode) + QLatin1String("}")); + QTableWidgetItem* item = new QTableWidgetItem(m_columnNames[i] + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, AbstractColumn::ColumnMode, mode) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(mode)); ui.twPreview->setHorizontalHeaderItem(i, item); //create checked items // QTableWidgetItem* itemChecked = new QTableWidgetItem(); // itemChecked->setCheckState(Qt::Checked); // ui.twPreview->setItem(0, i, itemChecked); } //preview the data const bool customQuery = (ui.cbImportFrom->currentIndex() != 0); int row = 0; do { for (int col = 0; col < m_cols; ++col) { ui.twPreview->setRowCount(row+1); ui.twPreview->setItem(row, col, new QTableWidgetItem(q.value(col).toString()) ); } row++; //in case a custom query is executed, check whether the row number limit is reached if (customQuery && row >= ui.sbPreviewLines->value()) break; } while (q.next()); ui.twPreview->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); setValid(); if (numeric != m_numeric) { m_numeric = numeric; emit stateChanged(); } RESET_CURSOR; } void ImportSQLDatabaseWidget::importFromChanged(int index) { if (index == 0) { //import from a table ui.gbQuery->hide(); ui.lwTables->show(); } else { //import the result set of a custom query ui.gbQuery->show(); ui.lwTables->hide(); ui.twPreview->clear(); } refreshPreview(); } void ImportSQLDatabaseWidget::read(AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { if (!dataSource) return; WAIT_CURSOR; //execute the current query (select on a table or a custom query) QSqlQuery q; // q.setForwardOnly(true); //TODO: crashes most probably because of q.last() and q.first() below q.prepare(currentQuery()); if (!q.exec() || !q.isActive()) { RESET_CURSOR; if (!q.lastError().databaseText().isEmpty()) KMessageBox::error(this, q.lastError().databaseText(), i18n("Unable to Execute Query")); setInvalid(); return; } //determine the number of rows/records to read q.last(); const int rows = q.at()+1; q.first(); // pointers to the actual data containers //columnOffset indexes the "start column" in the datasource. Data will be imported starting from this column. std::vector dataContainer; int columnOffset = dataSource->prepareImport(dataContainer, importMode, rows, m_cols, m_columnNames, m_columnModes); //number and DateTime formatting const QString& dateTimeFormat = ui.cbDateTimeFormat->currentText(); const QLocale numberFormat = QLocale((QLocale::Language)ui.cbNumberFormat->currentIndex()); //read the data int row = 0; do { for (int col = 0; col < m_cols; ++col) { const QString valueString = q.record().value(col).toString(); // set value depending on data type switch (m_columnModes[col]) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { bool isNumber; const double value = numberFormat.toDouble(valueString, &isNumber); static_cast*>(dataContainer[col])->operator[](row) = (isNumber ? value : NAN); break; } - case AbstractColumn::Integer: { + case AbstractColumn::ColumnMode::Integer: { bool isNumber; const int value = numberFormat.toInt(valueString, &isNumber); static_cast*>(dataContainer[col])->operator[](row) = (isNumber ? value : NAN); break; } - case AbstractColumn::BigInt: { + case AbstractColumn::ColumnMode::BigInt: { bool isNumber; const qint64 value = numberFormat.toLongLong(valueString, &isNumber); static_cast*>(dataContainer[col])->operator[](row) = (isNumber ? value : NAN); break; } - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); static_cast*>(dataContainer[col])->operator[](row) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: static_cast*>(dataContainer[col])->operator[](row) = valueString; break; - case AbstractColumn::Month: // never happens - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: // never happens + case AbstractColumn::ColumnMode::Day: break; } } row++; emit completed(100 * row/rows); } while (q.next()); DEBUG(" Read " << row << " rows"); dataSource->finalizeImport(columnOffset, 1, m_cols, dateTimeFormat, importMode); RESET_CURSOR; } QString ImportSQLDatabaseWidget::currentQuery(bool preview) { QString query; const bool customQuery = (ui.cbImportFrom->currentIndex() != 0); if ( !customQuery ) { const QString& tableName = ui.lwTables->currentItem()->text(); if (!preview) { query = QLatin1String("SELECT * FROM ") + tableName; } else { //preview the content of the currently selected table const QString& driver = m_db.driverName(); const QString& limit = QString::number(ui.sbPreviewLines->value()); if ( (driver == QLatin1String("QSQLITE3")) || (driver == QLatin1String("QSQLITE")) || (driver == QLatin1String("QMYSQL3")) || (driver == QLatin1String("QMYSQL")) || (driver == QLatin1String("QPSQL")) ) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" LIMIT ") + limit; else if (driver == QLatin1String("QOCI")) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" ROWNUM<=") + limit; else if (driver == QLatin1String("QDB2")) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" FETCH FIRST ") + limit + QLatin1String(" ROWS ONLY"); else if (driver == QLatin1String("QIBASE")) query = QLatin1String("SELECT * FROM ") + tableName + QLatin1String(" ROWS ") + limit; else //for ODBC the DBMS is not known and it's not clear what syntax to use -> select all rows query = QLatin1String("SELECT * FROM ") + tableName; } } else { //preview the result of a custom query query = ui.teQuery->toPlainText().simplified(); } return query; } /*! shows the database manager where the connections are created and edited. The selected connection is selected in the connection combo box in this widget. **/ void ImportSQLDatabaseWidget::showDatabaseManager() { DatabaseManagerDialog* dlg = new DatabaseManagerDialog(this, ui.cbConnection->currentText()); if (dlg->exec() == QDialog::Accepted) { //re-read the available connections to be in sync with the changes in DatabaseManager m_initializing = true; ui.cbConnection->clear(); readConnections(); //select the connection the user has selected in DatabaseManager const QString& conn = dlg->connection(); ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conn)); m_initializing = false; connectionChanged(); } delete dlg; } void ImportSQLDatabaseWidget::setInvalid() { if (m_valid) { ui.twPreview->setColumnCount(0); ui.twPreview->setRowCount(0); m_valid = false; emit stateChanged(); } } void ImportSQLDatabaseWidget::setValid() { if (!m_valid) { m_valid = true; emit stateChanged(); } } diff --git a/src/kdefrontend/dockwidgets/ColumnDock.cpp b/src/kdefrontend/dockwidgets/ColumnDock.cpp index 6861d6c4f..e2ca54830 100644 --- a/src/kdefrontend/dockwidgets/ColumnDock.cpp +++ b/src/kdefrontend/dockwidgets/ColumnDock.cpp @@ -1,434 +1,434 @@ /*************************************************************************** File : ColumnDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2011-2019 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013-2017 by Stefan Gerlach (stefan.gerlach@uni.kn) Description : widget for column 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 "ColumnDock.h" #include "backend/core/AbstractFilter.h" #include "backend/core/datatypes/SimpleCopyThroughFilter.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/String2DoubleFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/core/datatypes/String2DateTimeFilter.h" #include "backend/datapicker/DatapickerCurve.h" #include "backend/datasources/LiveDataSource.h" #include "backend/spreadsheet/Spreadsheet.h" #include /*! \class ColumnDock \brief Provides a widget for editing the properties of the spreadsheet columns currently selected in the project explorer. \ingroup kdefrontend */ ColumnDock::ColumnDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; connect(ui.leName, &QLineEdit::textChanged, this, &ColumnDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &ColumnDock::commentChanged); connect(ui.cbType, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int))); connect(ui.cbFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(formatChanged(int))); connect(ui.sbPrecision, SIGNAL(valueChanged(int)), this, SLOT(precisionChanged(int)) ); connect(ui.cbPlotDesignation, SIGNAL(currentIndexChanged(int)), this, SLOT(plotDesignationChanged(int))); retranslateUi(); } void ColumnDock::setColumns(QList list) { m_initializing = true; m_columnsList = list; m_column = list.first(); m_aspect = list.first(); //check whether we have non-editable columns: //1. columns in a LiveDataSource //2. columns in the spreadsheet of a datapicker curve //3. columns for residuals calculated in XYFitCurve) bool nonEditable = false; for (auto* col : m_columnsList) { auto* s = dynamic_cast(col->parentAspect()); if (s) { if (dynamic_cast(s) || dynamic_cast(s->parentAspect())) { nonEditable = true; break; } } else { nonEditable = true; break; } } if (list.size() == 1) { //names and comments of non-editable columns in a file data source can be changed. if (!nonEditable && dynamic_cast(m_column->parentAspect()) != nullptr) { ui.leName->setEnabled(false); ui.leComment->setEnabled(false); } else { ui.leName->setEnabled(true); ui.leComment->setEnabled(true); } ui.leName->setText(m_column->name()); ui.leComment->setText(m_column->comment()); } else { ui.leName->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 column - AbstractColumn::ColumnMode columnMode = m_column->columnMode(); + auto columnMode = m_column->columnMode(); this->updateFormatWidgets(columnMode); this->updateTypeWidgets(columnMode); ui.cbPlotDesignation->setCurrentIndex( int(m_column->plotDesignation()) ); // slots connect(m_column, &AbstractColumn::aspectDescriptionChanged, this, &ColumnDock::columnDescriptionChanged); connect(m_column, &AbstractColumn::modeChanged, this, &ColumnDock::columnModeChanged); connect(m_column->outputFilter(), &AbstractSimpleFilter::formatChanged, this, &ColumnDock::columnFormatChanged); connect(m_column->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &ColumnDock::columnPrecisionChanged); connect(m_column, &AbstractColumn::plotDesignationChanged, this, &ColumnDock::columnPlotDesignationChanged); //don't allow to change the column type at least one non-editable column ui.cbType->setEnabled(!nonEditable); m_initializing = false; } void ColumnDock::updateTypeWidgets(AbstractColumn::ColumnMode mode) { ui.cbType->setCurrentIndex(ui.cbType->findData((int)mode)); switch (mode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { auto* filter = static_cast(m_column->outputFilter()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->numericFormat())); ui.sbPrecision->setValue(filter->numDigits()); break; } - case AbstractColumn::Month: - case AbstractColumn::Day: - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::DateTime: { auto* filter = static_cast(m_column->outputFilter()); // DEBUG(" set column format: " << STDSTRING(filter->format())); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->format())); break; } - case AbstractColumn::Integer: // nothing to set - case AbstractColumn::BigInt: - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Integer: // nothing to set + case AbstractColumn::ColumnMode::BigInt: + case AbstractColumn::ColumnMode::Text: break; } } /*! depending on the currently selected column type (column mode) updates the widgets for the column format, shows/hides the allowed widgets, fills the corresponding combobox with the possible entries. Called when the type (column mode) is changed. */ void ColumnDock::updateFormatWidgets(AbstractColumn::ColumnMode mode) { const Lock lock(m_initializing); ui.cbFormat->clear(); switch (mode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: ui.cbFormat->addItem(i18n("Decimal"), QVariant('f')); ui.cbFormat->addItem(i18n("Scientific (e)"), QVariant('e')); ui.cbFormat->addItem(i18n("Scientific (E)"), QVariant('E')); ui.cbFormat->addItem(i18n("Automatic (g)"), QVariant('g')); ui.cbFormat->addItem(i18n("Automatic (G)"), QVariant('G')); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: ui.cbFormat->addItem(i18n("Number without Leading Zero"), QVariant("M")); ui.cbFormat->addItem(i18n("Number with Leading Zero"), QVariant("MM")); ui.cbFormat->addItem(i18n("Abbreviated Month Name"), QVariant("MMM")); ui.cbFormat->addItem(i18n("Full Month Name"), QVariant("MMMM")); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: ui.cbFormat->addItem(i18n("Number without Leading Zero"), QVariant("d")); ui.cbFormat->addItem(i18n("Number with Leading Zero"), QVariant("dd")); ui.cbFormat->addItem(i18n("Abbreviated Day Name"), QVariant("ddd")); ui.cbFormat->addItem(i18n("Full Day Name"), QVariant("dddd")); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: for (const auto& s : AbstractColumn::dateTimeFormats()) ui.cbFormat->addItem(s, QVariant(s)); break; - case AbstractColumn::Integer: - case AbstractColumn::BigInt: - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: + case AbstractColumn::ColumnMode::Text: break; } - if (mode == AbstractColumn::Numeric) { + if (mode == AbstractColumn::ColumnMode::Numeric) { ui.lPrecision->show(); ui.sbPrecision->show(); } else { ui.lPrecision->hide(); ui.sbPrecision->hide(); } - if (mode == AbstractColumn::Text || mode == AbstractColumn::Integer || mode == AbstractColumn::BigInt) { + if (mode == AbstractColumn::ColumnMode::Text || mode == AbstractColumn::ColumnMode::Integer || mode == AbstractColumn::ColumnMode::BigInt) { ui.lFormat->hide(); ui.cbFormat->hide(); } else { ui.lFormat->show(); ui.cbFormat->show(); } - if (mode == AbstractColumn::DateTime) { + if (mode == AbstractColumn::ColumnMode::DateTime) { ui.cbFormat->setEditable(true); ui.cbFormat->setCurrentItem("yyyy-MM-dd hh:mm:ss.zzz"); } else { ui.cbFormat->setEditable(false); ui.cbFormat->setCurrentIndex(0); } } //************************************************************* //******** SLOTs for changes triggered in ColumnDock ********** //************************************************************* void ColumnDock::retranslateUi() { m_initializing = true; ui.cbType->clear(); - ui.cbType->addItem(i18n("Numeric"), QVariant(int(AbstractColumn::Numeric))); - ui.cbType->addItem(i18n("Integer"), QVariant(int(AbstractColumn::Integer))); - ui.cbType->addItem(i18n("Big Integer"), QVariant(int(AbstractColumn::BigInt))); - ui.cbType->addItem(i18n("Text"), QVariant(int(AbstractColumn::Text))); - ui.cbType->addItem(i18n("Month Names"), QVariant(int(AbstractColumn::Month))); - ui.cbType->addItem(i18n("Day Names"), QVariant(int(AbstractColumn::Day))); - ui.cbType->addItem(i18n("Date and Time"), QVariant(int(AbstractColumn::DateTime))); + ui.cbType->addItem(i18n("Numeric"), QVariant(static_cast(AbstractColumn::ColumnMode::Numeric))); + ui.cbType->addItem(i18n("Integer"), QVariant(static_cast(AbstractColumn::ColumnMode::Integer))); + ui.cbType->addItem(i18n("Big Integer"), QVariant(static_cast(AbstractColumn::ColumnMode::BigInt))); + ui.cbType->addItem(i18n("Text"), QVariant(static_cast(AbstractColumn::ColumnMode::Text))); + ui.cbType->addItem(i18n("Month Names"), QVariant(static_cast(AbstractColumn::ColumnMode::Month))); + ui.cbType->addItem(i18n("Day Names"), QVariant(static_cast(AbstractColumn::ColumnMode::Day))); + ui.cbType->addItem(i18n("Date and Time"), QVariant(static_cast(AbstractColumn::ColumnMode::DateTime))); ui.cbPlotDesignation->clear(); ui.cbPlotDesignation->addItem(i18n("None")); ui.cbPlotDesignation->addItem(i18n("X")); ui.cbPlotDesignation->addItem(i18n("Y")); ui.cbPlotDesignation->addItem(i18n("Z")); ui.cbPlotDesignation->addItem(i18n("X-error")); ui.cbPlotDesignation->addItem(i18n("X-error -")); ui.cbPlotDesignation->addItem(i18n("X-error +")); ui.cbPlotDesignation->addItem(i18n("Y-error")); ui.cbPlotDesignation->addItem(i18n("Y-error -")); ui.cbPlotDesignation->addItem(i18n("Y-error +")); m_initializing = false; } /*! called when the type (column mode - numeric, text etc.) of the column was changed. */ void ColumnDock::typeChanged(int index) { DEBUG("ColumnDock::typeChanged()"); if (m_initializing) return; - AbstractColumn::ColumnMode columnMode = (AbstractColumn::ColumnMode)ui.cbType->itemData(index).toInt(); + auto columnMode = (AbstractColumn::ColumnMode)ui.cbType->itemData(index).toInt(); switch (columnMode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { int digits = ui.sbPrecision->value(); for (auto* col : m_columnsList) { col->beginMacro(i18n("%1: change column type", col->name())); col->setColumnMode(columnMode); auto* filter = static_cast(col->outputFilter()); //TODO: using //char format = ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toChar().toLatin1(); //outside of the for-loop and //filter->setNumericFormat(format); //inseide the loop leads to wrong results when converting from integer to numeric -> 'f' is set instead of 'e' filter->setNumericFormat(ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toChar().toLatin1()); filter->setNumDigits(digits); col->endMacro(); } break; } - case AbstractColumn::Integer: - case AbstractColumn::BigInt: - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: + case AbstractColumn::ColumnMode::Text: for (auto* col : m_columnsList) col->setColumnMode(columnMode); break; - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: for (auto* col : m_columnsList) { col->beginMacro(i18n("%1: change column type", col->name())); // the format is saved as item data QString format = ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toString(); col->setColumnMode(columnMode); auto* filter = static_cast(col->outputFilter()); filter->setFormat(format); col->endMacro(); } break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: for (auto* col : m_columnsList) { col->beginMacro(i18n("%1: change column type", col->name())); // the format is the current text QString format = ui.cbFormat->currentText(); col->setColumnMode(columnMode); auto* filter = static_cast(col->outputFilter()); filter->setFormat(format); col->endMacro(); } break; } const Lock lock(m_initializing); this->updateFormatWidgets(columnMode); this->updateTypeWidgets(columnMode); DEBUG("ColumnDock::typeChanged() DONE"); } /*! called when the format for the current type (column mode) was changed. */ void ColumnDock::formatChanged(int index) { DEBUG("ColumnDock::formatChanged()"); if (m_initializing) return; - AbstractColumn::ColumnMode mode = (AbstractColumn::ColumnMode)ui.cbType->itemData(ui.cbType->currentIndex()).toInt(); + auto mode = (AbstractColumn::ColumnMode)ui.cbType->itemData(ui.cbType->currentIndex()).toInt(); switch (mode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { char format = ui.cbFormat->itemData(index).toChar().toLatin1(); for (auto* col : m_columnsList) { auto* filter = static_cast(col->outputFilter()); filter->setNumericFormat(format); } break; } - case AbstractColumn::Integer: - case AbstractColumn::BigInt: - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: + case AbstractColumn::ColumnMode::Text: break; - case AbstractColumn::Month: - case AbstractColumn::Day: - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::DateTime: { QString format = ui.cbFormat->itemData(index).toString(); for (auto* col : m_columnsList) { auto* filter = static_cast(col->outputFilter()); filter->setFormat(format); } break; } } DEBUG("ColumnDock::formatChanged() DONE"); } void ColumnDock::precisionChanged(int digits) { if (m_initializing) return; for (auto* col : m_columnsList) { auto* filter = static_cast(col->outputFilter()); filter->setNumDigits(digits); } } void ColumnDock::plotDesignationChanged(int index) { if (m_initializing) return; auto pd = AbstractColumn::PlotDesignation(index); for (auto* col : m_columnsList) col->setPlotDesignation(pd); } //************************************************************* //********* SLOTs for changes triggered in Column ************* //************************************************************* void ColumnDock::columnDescriptionChanged(const AbstractAspect* aspect) { if (m_column != 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 ColumnDock::columnModeChanged(const AbstractAspect* aspect) { if (m_column != aspect) return; m_initializing = true; - AbstractColumn::ColumnMode columnMode = m_column->columnMode(); + auto columnMode = m_column->columnMode(); this->updateFormatWidgets(columnMode); this->updateTypeWidgets(columnMode); m_initializing = false; } void ColumnDock::columnFormatChanged() { DEBUG("ColumnDock::columnFormatChanged()"); m_initializing = true; - AbstractColumn::ColumnMode columnMode = m_column->columnMode(); + auto columnMode = m_column->columnMode(); switch (columnMode) { - case AbstractColumn::Numeric: { + case AbstractColumn::ColumnMode::Numeric: { auto* filter = static_cast(m_column->outputFilter()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->numericFormat())); break; } - case AbstractColumn::Integer: - case AbstractColumn::BigInt: - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: + case AbstractColumn::ColumnMode::Text: break; - case AbstractColumn::Month: - case AbstractColumn::Day: - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::DateTime: { auto* filter = static_cast(m_column->outputFilter()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->format())); break; } } m_initializing = false; } void ColumnDock::columnPrecisionChanged() { m_initializing = true; auto* filter = static_cast(m_column->outputFilter()); ui.sbPrecision->setValue(filter->numDigits()); m_initializing = false; } void ColumnDock::columnPlotDesignationChanged(const AbstractColumn* col) { m_initializing = true; - ui.cbPlotDesignation->setCurrentIndex( int(col->plotDesignation()) ); + ui.cbPlotDesignation->setCurrentIndex(int(col->plotDesignation())); m_initializing = false; } diff --git a/src/kdefrontend/dockwidgets/HistogramDock.cpp b/src/kdefrontend/dockwidgets/HistogramDock.cpp index 294891f2f..25efb6592 100644 --- a/src/kdefrontend/dockwidgets/HistogramDock.cpp +++ b/src/kdefrontend/dockwidgets/HistogramDock.cpp @@ -1,1728 +1,1728 @@ /*************************************************************************** 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); //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)) ); //TODO connect( ui.cbValuesFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesColumnFormatChanged(int)) ); 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("Bin Entries Number"); 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::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) ); 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) ); } 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) ); 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) { auto valuesType = Histogram::ValuesType(index); if (valuesType == Histogram::NoValues) { //no values are to paint -> deactivate all the pertinent widgets ui.cbValuesPosition->setEnabled(false); ui.lValuesColumn->hide(); cbValuesColumn->hide(); ui.sbValuesDistance->setEnabled(false); ui.sbValuesRotation->setEnabled(false); ui.sbValuesOpacity->setEnabled(false); ui.cbValuesFormat->setEnabled(false); ui.cbValuesFormat->setEnabled(false); ui.sbValuesPrecision->setEnabled(false); ui.leValuesPrefix->setEnabled(false); ui.leValuesSuffix->setEnabled(false); ui.kfrValuesFont->setEnabled(false); ui.kcbValuesColor->setEnabled(false); } else { ui.cbValuesPosition->setEnabled(true); ui.sbValuesDistance->setEnabled(true); ui.sbValuesRotation->setEnabled(true); ui.sbValuesOpacity->setEnabled(true); ui.cbValuesFormat->setEnabled(true); ui.sbValuesPrecision->setEnabled(true); ui.leValuesPrefix->setEnabled(true); ui.leValuesSuffix->setEnabled(true); ui.kfrValuesFont->setEnabled(true); ui.kcbValuesColor->setEnabled(true); const Column* column; if (valuesType == Histogram::ValuesCustomColumn) { ui.lValuesColumn->show(); cbValuesColumn->show(); column = static_cast(cbValuesColumn->currentModelIndex().internalPointer()); } else { ui.lValuesColumn->hide(); cbValuesColumn->hide(); column = static_cast(m_curve->dataColumn()); } this->showValuesColumnFormat(column); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesType(valuesType); } //TODO: very similar to ColumnDock void HistogramDock::showValuesColumnFormat(const Column* column) { if (!column) { // no valid column is available // -> hide all the format properties widgets (equivalent to showing the properties of the column mode "Text") - this->updateValuesFormatWidgets(AbstractColumn::Text); + this->updateValuesFormatWidgets(AbstractColumn::ColumnMode::Text); } else { AbstractColumn::ColumnMode columnMode = column->columnMode(); //update the format widgets for the new column mode this->updateValuesFormatWidgets(columnMode); //show the actual formatting properties switch (columnMode) { - case AbstractColumn::Numeric:{ + case AbstractColumn::ColumnMode::Numeric: { auto* filter = static_cast(column->outputFilter()); ui.cbValuesFormat->setCurrentIndex(ui.cbValuesFormat->findData(filter->numericFormat())); ui.sbValuesPrecision->setValue(filter->numDigits()); break; } - case AbstractColumn::Text: - case AbstractColumn::Integer: - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::Text: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: break; - case AbstractColumn::Month: - case AbstractColumn::Day: - case AbstractColumn::DateTime: { + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::DateTime: { auto* filter = static_cast(column->outputFilter()); ui.cbValuesFormat->setCurrentIndex(ui.cbValuesFormat->findData(filter->format())); break; } } } } //TODO: very similar to ColumnDock void HistogramDock::updateValuesFormatWidgets(const AbstractColumn::ColumnMode columnMode) { ui.cbValuesFormat->clear(); switch (columnMode) { - case AbstractColumn::Numeric: + case AbstractColumn::ColumnMode::Numeric: ui.cbValuesFormat->addItem(i18n("Decimal"), QVariant('f')); ui.cbValuesFormat->addItem(i18n("Scientific (e)"), QVariant('e')); ui.cbValuesFormat->addItem(i18n("Scientific (E)"), QVariant('E')); ui.cbValuesFormat->addItem(i18n("Automatic (e)"), QVariant('g')); ui.cbValuesFormat->addItem(i18n("Automatic (E)"), QVariant('G')); break; - case AbstractColumn::Integer: - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: ui.cbValuesFormat->addItem(i18n("Text"), QVariant()); break; - case AbstractColumn::Month: + case AbstractColumn::ColumnMode::Month: ui.cbValuesFormat->addItem(i18n("Number without Leading Zero"), QVariant("M")); ui.cbValuesFormat->addItem(i18n("Number with Leading Zero"), QVariant("MM")); ui.cbValuesFormat->addItem(i18n("Abbreviated Month Name"), QVariant("MMM")); ui.cbValuesFormat->addItem(i18n("Full Month Name"), QVariant("MMMM")); break; - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::Day: ui.cbValuesFormat->addItem(i18n("Number without Leading Zero"), QVariant("d")); ui.cbValuesFormat->addItem(i18n("Number with Leading Zero"), QVariant("dd")); ui.cbValuesFormat->addItem(i18n("Abbreviated Day Name"), QVariant("ddd")); ui.cbValuesFormat->addItem(i18n("Full Day Name"), QVariant("dddd")); break; - case AbstractColumn::DateTime: + case AbstractColumn::ColumnMode::DateTime: for (const auto& s : AbstractColumn::dateFormats()) ui.cbValuesFormat->addItem(s, QVariant(s)); for (const auto& s : AbstractColumn::timeFormats()) ui.cbValuesFormat->addItem(s, QVariant(s)); for (const auto& s1 : AbstractColumn::dateFormats()) for (const auto& s2 : AbstractColumn::timeFormats()) ui.cbValuesFormat->addItem(s1 + ' ' + s2, QVariant(s1 + ' ' + s2)); break; } ui.cbValuesFormat->setCurrentIndex(0); - if (columnMode == AbstractColumn::Numeric) { + if (columnMode == AbstractColumn::ColumnMode::Numeric) { ui.lValuesPrecision->show(); ui.sbValuesPrecision->show(); } else { ui.lValuesPrecision->hide(); ui.sbValuesPrecision->hide(); } - if (columnMode == AbstractColumn::Text) { + if (columnMode == AbstractColumn::ColumnMode::Text) { ui.lValuesFormatTop->hide(); ui.lValuesFormat->hide(); ui.cbValuesFormat->hide(); } else { ui.lValuesFormatTop->show(); ui.lValuesFormat->show(); ui.cbValuesFormat->show(); ui.cbValuesFormat->setCurrentIndex(0); } - if (columnMode == AbstractColumn::DateTime) { + if (columnMode == AbstractColumn::ColumnMode::DateTime) { ui.cbValuesFormat->setEditable(true); } else { ui.cbValuesFormat->setEditable(false); } } /*! called when the custom column for the values was changed. */ void HistogramDock::valuesColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* column = static_cast(index.internalPointer()); this->showValuesColumnFormat(column); for (auto* curve : m_curvesList) { //TODO save also the format of the currently selected column for the values (precision etc.) 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) ); } 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::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) ); 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); 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) ); 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) ); 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) ); 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)); 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); m_initializing = false; } void HistogramDock::curveValuesDistanceChanged(qreal distance) { m_initializing = true; ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(distance, Worksheet::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::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)) ); 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) ); 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) ); 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.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.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) ); //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.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)) ); 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.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.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("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("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/XYCurveDock.cpp b/src/kdefrontend/dockwidgets/XYCurveDock.cpp index e7c507208..4113e1382 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.cbValuesNumericFormat->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) ); 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) ); 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) ); } 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) ); 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::Numeric) + if (column->columnMode() == AbstractColumn::ColumnMode::Numeric) hasNumeric = true; - else if (column->columnMode() == AbstractColumn::Integer || column->columnMode() == AbstractColumn::BigInt) + else if (column->columnMode() == AbstractColumn::ColumnMode::Integer || column->columnMode() == AbstractColumn::ColumnMode::BigInt) hasInteger = true; - else if (column->columnMode() == AbstractColumn::DateTime) + 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::Integer || xColumn->columnMode() == AbstractColumn::Integer)) - || (yColumn && (yColumn->columnMode() == AbstractColumn::Integer || yColumn->columnMode() == AbstractColumn::Integer)); + 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::Numeric) - || (yColumn && yColumn->columnMode() == AbstractColumn::Numeric); + hasNumeric = (xColumn && xColumn->columnMode() == AbstractColumn::ColumnMode::Numeric) + || (yColumn && yColumn->columnMode() == AbstractColumn::ColumnMode::Numeric); - hasDateTime = (xColumn && xColumn->columnMode() == AbstractColumn::DateTime) - || (yColumn && yColumn->columnMode() == AbstractColumn::DateTime); + 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) ); } 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) ); 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); 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) ); 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) ); 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) ); 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) ); 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)); 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) ); 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)) ); 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) ); 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) ); 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.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.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.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) ); //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.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)) ); 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.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.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.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.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.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) ); //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.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)) ); 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.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.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("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("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("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)); //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("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("ErrorBarsStyle", ui.cbErrorBarsStyle->currentIndex()); group.writeEntry("ErrorBarsColor", ui.kcbErrorBarsColor->color()); group.writeEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(ui.sbErrorBarsWidth->value(),Worksheet::Point) ); group.writeEntry("ErrorBarsOpacity", ui.sbErrorBarsOpacity->value()/100.0); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp b/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp index 0176cc597..51020bc93 100644 --- a/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYSmoothCurveDock.cpp @@ -1,642 +1,642 @@ /*************************************************************************** File : XYSmoothCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017-2020 Alexander Semke (alexander.semke@web.de) Description : widget for editing properties of smooth curves ***************************************************************************/ /*************************************************************************** * * * 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 "XYSmoothCurveDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/XYSmoothCurve.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include /*! \class XYSmoothCurveDock \brief Provides a widget for editing the properties of the XYSmoothCurves (2D-curves defined by an smooth) currently selected in the project explorer. If more then 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 */ XYSmoothCurveDock::XYSmoothCurveDock(QWidget* parent) : XYCurveDock(parent) { //hide the line connection type ui.cbLineType->setDisabled(true); //remove the tab "Error bars" ui.tabWidget->removeTab(5); } /*! * // Tab "General" */ void XYSmoothCurveDock::setupGeneral() { DEBUG("XYSmoothCurveDock::setupGeneral()"); QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); m_leName = uiGeneralTab.leName; m_leComment = uiGeneralTab.leComment; auto* gridLayout = static_cast(generalTab->layout()); gridLayout->setContentsMargins(2,2,2,2); gridLayout->setHorizontalSpacing(2); gridLayout->setVerticalSpacing(2); uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); cbDataSourceCurve = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbDataSourceCurve, 5, 2, 1, 3); cbXDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXDataColumn, 6, 2, 1, 3); cbYDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYDataColumn, 7, 2, 1, 3); for (int i = 0; i < NSL_SMOOTH_TYPE_COUNT; i++) uiGeneralTab.cbType->addItem(i18n(nsl_smooth_type_name[i])); for (int i = 0; i < NSL_SMOOTH_WEIGHT_TYPE_COUNT; i++) uiGeneralTab.cbWeight->addItem(i18n(nsl_smooth_weight_type_name[i])); for (int i = 0; i < NSL_SMOOTH_PAD_MODE_COUNT; i++) uiGeneralTab.cbMode->addItem(i18n(nsl_smooth_pad_mode_name[i])); uiGeneralTab.sbMin->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); uiGeneralTab.sbMax->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build")); auto* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); //Slots connect(uiGeneralTab.leName, &QLineEdit::textChanged, this, &XYSmoothCurveDock::nameChanged ); connect(uiGeneralTab.leComment, &QLineEdit::textChanged, this, &XYSmoothCurveDock::commentChanged ); connect(uiGeneralTab.chkVisible, &QCheckBox::clicked, this, &XYSmoothCurveDock::visibilityChanged); connect(uiGeneralTab.cbDataSourceType, QOverload::of(&QComboBox::currentIndexChanged), this, &XYSmoothCurveDock::dataSourceTypeChanged); connect(uiGeneralTab.cbAutoRange, &QCheckBox::clicked, this, &XYSmoothCurveDock::autoRangeChanged); connect(uiGeneralTab.sbMin, QOverload::of(&QDoubleSpinBox::valueChanged), this, &XYSmoothCurveDock::xRangeMinChanged); connect(uiGeneralTab.sbMax, QOverload::of(&QDoubleSpinBox::valueChanged), this, &XYSmoothCurveDock::xRangeMaxChanged); connect(uiGeneralTab.dateTimeEditMin, &QDateTimeEdit::dateTimeChanged, this, &XYSmoothCurveDock::xRangeMinDateTimeChanged); connect(uiGeneralTab.dateTimeEditMax, &QDateTimeEdit::dateTimeChanged, this, &XYSmoothCurveDock::xRangeMaxDateTimeChanged); connect(uiGeneralTab.cbType, QOverload::of(&QComboBox::currentIndexChanged), this, &XYSmoothCurveDock::typeChanged); connect(uiGeneralTab.sbPoints, QOverload::of(&QSpinBox::valueChanged), this, &XYSmoothCurveDock::pointsChanged); connect(uiGeneralTab.cbWeight, QOverload::of(&QComboBox::currentIndexChanged), this, &XYSmoothCurveDock::weightChanged); connect(uiGeneralTab.sbPercentile, QOverload::of(&QDoubleSpinBox::valueChanged), this, &XYSmoothCurveDock::percentileChanged); connect(uiGeneralTab.sbOrder, QOverload::of(&QSpinBox::valueChanged), this, &XYSmoothCurveDock::orderChanged); connect(uiGeneralTab.cbMode, QOverload::of(&QComboBox::currentIndexChanged), this, &XYSmoothCurveDock::modeChanged); connect(uiGeneralTab.sbLeftValue, QOverload::of(&QDoubleSpinBox::valueChanged), this, &XYSmoothCurveDock::valueChanged); connect(uiGeneralTab.sbRightValue, QOverload::of(&QDoubleSpinBox::valueChanged), this, &XYSmoothCurveDock::valueChanged); connect(uiGeneralTab.pbRecalculate, &QPushButton::clicked, this, &XYSmoothCurveDock::recalculateClicked); connect(cbDataSourceCurve, &TreeViewComboBox::currentModelIndexChanged, this, &XYSmoothCurveDock::dataSourceCurveChanged); connect(cbXDataColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYSmoothCurveDock::xDataColumnChanged); connect(cbYDataColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYSmoothCurveDock::yDataColumnChanged); } void XYSmoothCurveDock::initGeneralTab() { DEBUG("XYSmoothCurveDock::initGeneralTab()"); //if there are more then one curve in the list, disable 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.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.leName->setText(QString()); uiGeneralTab.leComment->setText(QString()); } //show the properties of the first curve m_smoothCurve = static_cast(m_curve); checkColumnAvailability(cbXDataColumn, m_smoothCurve->xDataColumn(), m_smoothCurve->xDataColumnPath()); checkColumnAvailability(cbYDataColumn, m_smoothCurve->yDataColumn(), m_smoothCurve->yDataColumnPath()); //data source uiGeneralTab.cbDataSourceType->setCurrentIndex(m_smoothCurve->dataSourceType()); this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_smoothCurve->dataSourceCurve()); XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_smoothCurve->xDataColumn()); XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_smoothCurve->yDataColumn()); //range widgets const auto* plot = static_cast(m_smoothCurve->parentAspect()); m_dateTimeRange = (plot->xRangeFormat() != CartesianPlot::Numeric); if (!m_dateTimeRange) { uiGeneralTab.sbMin->setValue(m_smoothData.xRange.first()); uiGeneralTab.sbMax->setValue(m_smoothData.xRange.last()); } else { uiGeneralTab.dateTimeEditMin->setDateTime( QDateTime::fromMSecsSinceEpoch(m_smoothData.xRange.first()) ); uiGeneralTab.dateTimeEditMax->setDateTime( QDateTime::fromMSecsSinceEpoch(m_smoothData.xRange.last()) ); } uiGeneralTab.lMin->setVisible(!m_dateTimeRange); uiGeneralTab.sbMin->setVisible(!m_dateTimeRange); uiGeneralTab.lMax->setVisible(!m_dateTimeRange); uiGeneralTab.sbMax->setVisible(!m_dateTimeRange); uiGeneralTab.lMinDateTime->setVisible(m_dateTimeRange); uiGeneralTab.dateTimeEditMin->setVisible(m_dateTimeRange); uiGeneralTab.lMaxDateTime->setVisible(m_dateTimeRange); uiGeneralTab.dateTimeEditMax->setVisible(m_dateTimeRange); //auto range uiGeneralTab.cbAutoRange->setChecked(m_smoothData.autoRange); this->autoRangeChanged(); // update list of selectable types xDataColumnChanged(cbXDataColumn->currentModelIndex()); uiGeneralTab.cbType->setCurrentIndex(m_smoothData.type); typeChanged(uiGeneralTab.cbType->currentIndex()); // needed, when type does not change uiGeneralTab.sbPoints->setValue((int)m_smoothData.points); uiGeneralTab.cbWeight->setCurrentIndex(m_smoothData.weight); uiGeneralTab.sbPercentile->setValue(m_smoothData.percentile); uiGeneralTab.sbOrder->setValue((int)m_smoothData.order); uiGeneralTab.cbMode->setCurrentIndex(m_smoothData.mode); modeChanged(uiGeneralTab.cbMode->currentIndex()); // needed, when mode does not change uiGeneralTab.sbLeftValue->setValue(m_smoothData.lvalue); uiGeneralTab.sbRightValue->setValue(m_smoothData.rvalue); valueChanged(); this->showSmoothResult(); uiGeneralTab.chkVisible->setChecked( m_curve->isVisible() ); //Slots connect(m_smoothCurve, &XYSmoothCurve::aspectDescriptionChanged, this, &XYSmoothCurveDock::curveDescriptionChanged); connect(m_smoothCurve, &XYSmoothCurve::dataSourceTypeChanged, this, &XYSmoothCurveDock::curveDataSourceTypeChanged); connect(m_smoothCurve, &XYSmoothCurve::dataSourceCurveChanged, this, &XYSmoothCurveDock::curveDataSourceCurveChanged); connect(m_smoothCurve, &XYSmoothCurve::xDataColumnChanged, this, &XYSmoothCurveDock::curveXDataColumnChanged); connect(m_smoothCurve, &XYSmoothCurve::yDataColumnChanged, this, &XYSmoothCurveDock::curveYDataColumnChanged); connect(m_smoothCurve, &XYSmoothCurve::smoothDataChanged, this, &XYSmoothCurveDock::curveSmoothDataChanged); connect(m_smoothCurve, &XYSmoothCurve::sourceDataChanged, this, &XYSmoothCurveDock::enableRecalculate); } void XYSmoothCurveDock::setModel() { QList list{AspectType::Folder, AspectType::Datapicker, AspectType::Worksheet, AspectType::CartesianPlot, AspectType::XYCurve}; cbDataSourceCurve->setTopLevelClasses(list); QList hiddenAspects; for (auto* curve : m_curvesList) hiddenAspects << curve; cbDataSourceCurve->setHiddenAspects(hiddenAspects); list = {AspectType::Folder, AspectType::Workbook, AspectType::Datapicker, AspectType::DatapickerCurve, AspectType::Spreadsheet, AspectType::LiveDataSource, AspectType::Column, AspectType::Worksheet, AspectType::CartesianPlot, AspectType::XYFitCurve, AspectType::CantorWorksheet }; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbDataSourceCurve->setModel(m_aspectTreeModel); cbXDataColumn->setModel(m_aspectTreeModel); cbYDataColumn->setModel(m_aspectTreeModel); XYCurveDock::setModel(); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYSmoothCurveDock::setCurves(QList list) { DEBUG("XYSmoothCurveDock::setCurves()"); m_initializing = true; m_curvesList = list; m_curve = list.first(); m_aspect = m_curve; m_smoothCurve = dynamic_cast(m_curve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); this->setModel(); m_smoothData = m_smoothCurve->smoothData(); initGeneralTab(); initTabs(); m_initializing = false; //hide the "skip gaps" option after the curves were set ui.lLineSkipGaps->hide(); ui.chkLineSkipGaps->hide(); } //************************************************************* //**** SLOTs for changes triggered in XYFitCurveDock ***** //************************************************************* void XYSmoothCurveDock::dataSourceTypeChanged(int index) { auto type = (XYAnalysisCurve::DataSourceType)index; if (type == XYAnalysisCurve::DataSourceSpreadsheet) { uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); uiGeneralTab.lXColumn->show(); cbXDataColumn->show(); uiGeneralTab.lYColumn->show(); cbYDataColumn->show(); } else { uiGeneralTab.lDataSourceCurve->show(); cbDataSourceCurve->show(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) dynamic_cast(curve)->setDataSourceType(type); } void XYSmoothCurveDock::dataSourceCurveChanged(const QModelIndex& index) { auto* aspect = static_cast(index.internalPointer()); auto* dataSourceCurve = dynamic_cast(aspect); if (m_initializing) return; for (auto* curve : m_curvesList) dynamic_cast(curve)->setDataSourceCurve(dataSourceCurve); } void XYSmoothCurveDock::xDataColumnChanged(const QModelIndex& index) { auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setXDataColumn(column); // disable types that need more data points if (column != nullptr) { if (uiGeneralTab.cbAutoRange->isChecked()) { uiGeneralTab.sbMin->setValue(column->minimum()); uiGeneralTab.sbMax->setValue(column->maximum()); } unsigned int n = 0; for (int row = 0; row < column->rowCount(); row++) { if (column->isMasked(row)) continue; switch (column->columnMode()) { - case AbstractColumn::Numeric: - case AbstractColumn::Integer: - case AbstractColumn::BigInt: + case AbstractColumn::ColumnMode::Numeric: + case AbstractColumn::ColumnMode::Integer: + case AbstractColumn::ColumnMode::BigInt: if (!std::isnan(column->valueAt(row))) n++; break; - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Day: n++; break; - case AbstractColumn::Text: + case AbstractColumn::ColumnMode::Text: break; } } DEBUG(" Set maximum points to " << n) // set maximum of sbPoints to number of columns uiGeneralTab.sbPoints->setMaximum((int)n); } cbXDataColumn->useCurrentIndexText(true); cbXDataColumn->setInvalid(false); } void XYSmoothCurveDock::yDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setYDataColumn(column); cbYDataColumn->useCurrentIndexText(true); cbYDataColumn->setInvalid(false); } void XYSmoothCurveDock::autoRangeChanged() { bool autoRange = uiGeneralTab.cbAutoRange->isChecked(); m_smoothData.autoRange = autoRange; uiGeneralTab.lMin->setEnabled(!autoRange); uiGeneralTab.sbMin->setEnabled(!autoRange); uiGeneralTab.lMax->setEnabled(!autoRange); uiGeneralTab.sbMax->setEnabled(!autoRange); uiGeneralTab.lMinDateTime->setEnabled(!autoRange); uiGeneralTab.dateTimeEditMin->setEnabled(!autoRange); uiGeneralTab.lMaxDateTime->setEnabled(!autoRange); uiGeneralTab.dateTimeEditMax->setEnabled(!autoRange); if (autoRange) { const AbstractColumn* xDataColumn = nullptr; if (m_smoothCurve->dataSourceType() == XYAnalysisCurve::DataSourceSpreadsheet) xDataColumn = m_smoothCurve->xDataColumn(); else { if (m_smoothCurve->dataSourceCurve()) xDataColumn = m_smoothCurve->dataSourceCurve()->xColumn(); } if (xDataColumn) { if (!m_dateTimeRange) { uiGeneralTab.sbMin->setValue(xDataColumn->minimum()); uiGeneralTab.sbMax->setValue(xDataColumn->maximum()); } else { uiGeneralTab.dateTimeEditMin->setDateTime(QDateTime::fromMSecsSinceEpoch(xDataColumn->minimum())); uiGeneralTab.dateTimeEditMax->setDateTime(QDateTime::fromMSecsSinceEpoch(xDataColumn->maximum())); } } } } void XYSmoothCurveDock::xRangeMinChanged(double value) { m_smoothData.xRange.first() = value; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYSmoothCurveDock::xRangeMaxChanged(double value) { m_smoothData.xRange.last() = value; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYSmoothCurveDock::xRangeMinDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; m_smoothData.xRange.first() = dateTime.toMSecsSinceEpoch(); uiGeneralTab.pbRecalculate->setEnabled(true); } void XYSmoothCurveDock::xRangeMaxDateTimeChanged(const QDateTime& dateTime) { if (m_initializing) return; m_smoothData.xRange.last() = dateTime.toMSecsSinceEpoch(); uiGeneralTab.pbRecalculate->setEnabled(true); } void XYSmoothCurveDock::typeChanged(int index) { auto type = (nsl_smooth_type)index; m_smoothData.type = type; const auto* model = qobject_cast(uiGeneralTab.cbMode->model()); QStandardItem* pad_interp_item = model->item(nsl_smooth_pad_interp); if (type == nsl_smooth_type_moving_average || type == nsl_smooth_type_moving_average_lagged) { uiGeneralTab.lWeight->show(); uiGeneralTab.cbWeight->show(); // disable interp pad model for MA and MAL pad_interp_item->setFlags(pad_interp_item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); } else { uiGeneralTab.lWeight->hide(); uiGeneralTab.cbWeight->hide(); pad_interp_item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); } if (type == nsl_smooth_type_moving_average_lagged) { uiGeneralTab.sbPoints->setSingleStep(1); uiGeneralTab.sbPoints->setMinimum(2); uiGeneralTab.lRightValue->hide(); uiGeneralTab.sbRightValue->hide(); } else { uiGeneralTab.sbPoints->setSingleStep(2); uiGeneralTab.sbPoints->setMinimum(3); if (m_smoothData.mode == nsl_smooth_pad_constant) { uiGeneralTab.lRightValue->show(); uiGeneralTab.sbRightValue->show(); } } if (type == nsl_smooth_type_percentile) { uiGeneralTab.lPercentile->show(); uiGeneralTab.sbPercentile->show(); // disable interp pad model for MA and MAL pad_interp_item->setFlags(pad_interp_item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); } else { uiGeneralTab.lPercentile->hide(); uiGeneralTab.sbPercentile->hide(); } if (type == nsl_smooth_type_savitzky_golay) { uiGeneralTab.lOrder->show(); uiGeneralTab.sbOrder->show(); } else { uiGeneralTab.lOrder->hide(); uiGeneralTab.sbOrder->hide(); } enableRecalculate(); } void XYSmoothCurveDock::pointsChanged(int value) { m_smoothData.points = (unsigned int)value; uiGeneralTab.sbOrder->setMaximum(value - 1); // set maximum order enableRecalculate(); } void XYSmoothCurveDock::weightChanged(int index) { m_smoothData.weight = (nsl_smooth_weight_type)index; enableRecalculate(); } void XYSmoothCurveDock::percentileChanged(double value) { m_smoothData.percentile = value; enableRecalculate(); } void XYSmoothCurveDock::orderChanged(int value) { m_smoothData.order = (unsigned int)value; enableRecalculate(); } void XYSmoothCurveDock::modeChanged(int index) { m_smoothData.mode = (nsl_smooth_pad_mode)index; if (m_smoothData.mode == nsl_smooth_pad_constant) { uiGeneralTab.lLeftValue->show(); uiGeneralTab.sbLeftValue->show(); if (m_smoothData.type == nsl_smooth_type_moving_average_lagged) { uiGeneralTab.lRightValue->hide(); uiGeneralTab.sbRightValue->hide(); } else { uiGeneralTab.lRightValue->show(); uiGeneralTab.sbRightValue->show(); } } else { uiGeneralTab.lLeftValue->hide(); uiGeneralTab.sbLeftValue->hide(); uiGeneralTab.lRightValue->hide(); uiGeneralTab.sbRightValue->hide(); } enableRecalculate(); } void XYSmoothCurveDock::valueChanged() { m_smoothData.lvalue = uiGeneralTab.sbLeftValue->value(); m_smoothData.rvalue = uiGeneralTab.sbRightValue->value(); enableRecalculate(); } void XYSmoothCurveDock::recalculateClicked() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); for (auto* curve : m_curvesList) dynamic_cast(curve)->setSmoothData(m_smoothData); uiGeneralTab.pbRecalculate->setEnabled(false); emit info(i18n("Smoothing status: %1", m_smoothCurve->smoothResult().status)); QApplication::restoreOverrideCursor(); } void XYSmoothCurveDock::enableRecalculate() const { if (m_initializing) return; //no smoothing possible without the x- and y-data bool hasSourceData = false; if (m_smoothCurve->dataSourceType() == XYAnalysisCurve::DataSourceSpreadsheet) { AbstractAspect* aspectX = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); AbstractAspect* aspectY = static_cast(cbYDataColumn->currentModelIndex().internalPointer()); hasSourceData = (aspectX != nullptr && aspectY != nullptr); if (aspectX) { cbXDataColumn->useCurrentIndexText(true); cbXDataColumn->setInvalid(false); } if (aspectY) { cbYDataColumn->useCurrentIndexText(true); cbYDataColumn->setInvalid(false); } } else { hasSourceData = (m_smoothCurve->dataSourceCurve() != nullptr); } uiGeneralTab.pbRecalculate->setEnabled(hasSourceData); } /*! * show the result and details of the smooth */ void XYSmoothCurveDock::showSmoothResult() { const XYSmoothCurve::SmoothResult& smoothResult = m_smoothCurve->smoothResult(); if (!smoothResult.available) { uiGeneralTab.teResult->clear(); return; } //const XYSmoothCurve::SmoothData& smoothData = m_smoothCurve->smoothData(); QString str = i18n("status: %1", smoothResult.status) + "
"; if (!smoothResult.valid) { uiGeneralTab.teResult->setText(str); return; //result is not valid, there was an error which is shown in the status-string, nothing to show more. } if (smoothResult.elapsedTime>1000) str += i18n("calculation time: %1 s", QString::number(smoothResult.elapsedTime/1000)) + "
"; else str += i18n("calculation time: %1 ms", QString::number(smoothResult.elapsedTime)) + "
"; str += "

"; uiGeneralTab.teResult->setText(str); //enable the "recalculate"-button if the source data was changed since the last smooth uiGeneralTab.pbRecalculate->setEnabled(m_smoothCurve->isSourceDataChangedSinceLastRecalc()); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYSmoothCurveDock::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 XYSmoothCurveDock::curveDataSourceTypeChanged(XYAnalysisCurve::DataSourceType type) { m_initializing = true; uiGeneralTab.cbDataSourceType->setCurrentIndex(type); m_initializing = false; } void XYSmoothCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve); m_initializing = false; } void XYSmoothCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column); m_initializing = false; } void XYSmoothCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column); m_initializing = false; } void XYSmoothCurveDock::curveSmoothDataChanged(const XYSmoothCurve::SmoothData& smoothData) { m_initializing = true; m_smoothData = smoothData; uiGeneralTab.cbType->setCurrentIndex(m_smoothData.type); this->showSmoothResult(); m_initializing = false; } void XYSmoothCurveDock::dataChanged() { this->enableRecalculate(); } diff --git a/src/kdefrontend/spreadsheet/AddSubtractValueDialog.cpp b/src/kdefrontend/spreadsheet/AddSubtractValueDialog.cpp index 951811750..8974d4646 100644 --- a/src/kdefrontend/spreadsheet/AddSubtractValueDialog.cpp +++ b/src/kdefrontend/spreadsheet/AddSubtractValueDialog.cpp @@ -1,538 +1,547 @@ /*************************************************************************** File : AddSubtractValueDialog.cpp Project : LabPlot Description : Dialog for adding/subtracting a value from column values -------------------------------------------------------------------- Copyright : (C) 2018 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2020 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 "AddSubtractValueDialog.h" #include "backend/core/column/Column.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include #include #include #include #include #include /*! \class AddSubtractValueDialog \brief Dialog for adding/subtracting a value from column values. \ingroup kdefrontend */ AddSubtractValueDialog::AddSubtractValueDialog(Spreadsheet* s, Operation op, QWidget* parent) : QDialog(parent), m_spreadsheet(s), m_operation(op) { Q_ASSERT(s != nullptr); init(); switch (m_operation) { case Add: m_okButton->setToolTip(i18n("Add the specified value to column values")); break; case Subtract: m_okButton->setToolTip(i18n("Subtract the specified value from column values")); break; case Multiply: m_okButton->setToolTip(i18n("Multiply column values by the specified value")); break; case Divide: m_okButton->setToolTip(i18n("Divide column values by the specified value")); break; } } AddSubtractValueDialog::AddSubtractValueDialog(Matrix* m, Operation op, QWidget* parent) : QDialog(parent), m_matrix(m), m_operation(op) { Q_ASSERT(m != nullptr); init(); switch (m_operation) { case Add: m_okButton->setToolTip(i18n("Add the specified value to matrix values")); break; case Subtract: m_okButton->setToolTip(i18n("Subtract the specified value from matrix values")); break; case Multiply: m_okButton->setToolTip(i18n("Multiply matrix values by the specified value")); break; case Divide: m_okButton->setToolTip(i18n("Divide matrix values by the specified value")); } } void AddSubtractValueDialog::init() { switch (m_operation) { case Add: setWindowTitle(i18nc("@title:window", "Add Value")); break; case Subtract: setWindowTitle(i18nc("@title:window", "Subtract Value")); break; case Multiply: setWindowTitle(i18nc("@title:window", "Multiply by Value")); break; case Divide: setWindowTitle(i18nc("@title:window", "Divide by Value")); break; } ui.setupUi(this); setAttribute(Qt::WA_DeleteOnClose); QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); ui.gridLayout->addWidget(btnBox, 3, 0, 1, 2); m_okButton = btnBox->button(QDialogButtonBox::Ok); switch (m_operation) { case Add: m_okButton->setText(i18n("&Add")); break; case Subtract: m_okButton->setText(i18n("&Subtract")); break; case Multiply: m_okButton->setText(i18n("&Multiply")); break; case Divide: m_okButton->setText(i18n("&Divide")); break; } connect(m_okButton, &QPushButton::clicked, this, &AddSubtractValueDialog::generate); connect(btnBox, &QDialogButtonBox::accepted, this, &AddSubtractValueDialog::accept); connect(btnBox, &QDialogButtonBox::rejected, this, &AddSubtractValueDialog::reject); connect(ui.leValue, &QLineEdit::textChanged, this, [=]() {m_okButton->setEnabled(!ui.leValue->text().isEmpty());}); //restore saved settings if available create(); // ensure there's a window created KConfigGroup conf(KSharedConfig::openConfig(), "AddSubtractValueDialog"); if (conf.exists()) { KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else resize(QSize(300, 0).expandedTo(minimumSize())); } AddSubtractValueDialog::~AddSubtractValueDialog() { KConfigGroup conf(KSharedConfig::openConfig(), "AddSubtractValueDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); } void AddSubtractValueDialog::setColumns(const QVector& columns) { m_columns = columns; //depending on the current column mode, activate/deactivate the corresponding widgets //and show the first valid value in the first selected column as the value to add/subtract const Column* column = m_columns.first(); - AbstractColumn::ColumnMode mode = column->columnMode(); - if (mode == AbstractColumn::Integer) { + const auto mode = column->columnMode(); + //TODO: switch + if (mode == AbstractColumn::ColumnMode::Integer) { ui.lTimeValue->setVisible(false); ui.dateTimeEdit->setVisible(false); ui.leValue->setValidator(new QIntValidator(ui.leValue)); ui.leValue->setText(QString::number(column->integerAt(0))); - } else if (mode == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { ui.lTimeValue->setVisible(false); ui.dateTimeEdit->setVisible(false); //TODO: QLongLongVaildator ui.leValue->setValidator(new QIntValidator(ui.leValue)); ui.leValue->setText(QString::number(column->bigIntAt(0))); - } else if (mode == AbstractColumn::Numeric) { + } else if (mode == AbstractColumn::ColumnMode::Numeric) { ui.lTimeValue->setVisible(false); ui.dateTimeEdit->setVisible(false); ui.leValue->setValidator(new QDoubleValidator(ui.leValue)); for (int row = 0; row < column->rowCount(); ++row) { const double value = column->valueAt(row); if (!std::isnan(value)) { ui.leValue->setText(QString::number(column->valueAt(row), 'g', 16)); break; } } } else { //datetime ui.lValue->setVisible(false); ui.leValue->setVisible(false); auto* filter = static_cast(column->outputFilter()); ui.dateTimeEdit->setDisplayFormat(filter->format()); for (int row = 0; row < column->rowCount(); ++row) { const QDateTime& value = column->dateTimeAt(row); if (value.isValid()) { ui.dateTimeEdit->setDateTime(value); break; } } } } void AddSubtractValueDialog::setMatrices() { - if (m_matrix->mode() == AbstractColumn::Integer) { + const auto mode = m_matrix->mode(); + switch (mode) { + case AbstractColumn::ColumnMode::Integer: ui.lTimeValue->setVisible(false); ui.dateTimeEdit->setVisible(false); ui.leValue->setValidator(new QIntValidator(ui.leValue)); ui.leValue->setText(QString::number(m_matrix->cell(0,0))); - } else if (m_matrix->mode() == AbstractColumn::BigInt) { + break; + case AbstractColumn::ColumnMode::BigInt: ui.lTimeValue->setVisible(false); ui.dateTimeEdit->setVisible(false); // TODO QLongLongValidator ui.leValue->setValidator(new QIntValidator(ui.leValue)); ui.leValue->setText(QString::number(m_matrix->cell(0,0))); - } else if (m_matrix->mode() == AbstractColumn::Numeric) { + break; + case AbstractColumn::ColumnMode::Numeric: ui.lTimeValue->setVisible(false); ui.dateTimeEdit->setVisible(false); ui.leValue->setValidator(new QDoubleValidator(ui.leValue)); ui.leValue->setText(QString::number(m_matrix->cell(0,0))); - } else { //datetime + break; + case AbstractColumn::ColumnMode::DateTime: + case AbstractColumn::ColumnMode::Day: + case AbstractColumn::ColumnMode::Month: + case AbstractColumn::ColumnMode::Text: ui.lValue->setVisible(false); ui.leValue->setVisible(false); } } /*! * generates new values in the selected columns by adding/subtracting the value provided in this dialog * from every column element. */ QString AddSubtractValueDialog::getMessage(const QString& name) { QString msg; QString value = ui.leValue->text(); switch (m_operation) { case Add: msg = i18n("%1: add %2 to column values", name, value); break; case Subtract: msg = i18n("%1: subtract %2 from column values", name, value); break; case Multiply: msg = i18n("%1: multiply column values by %2", name, value); break; case Divide: msg = i18n("%1: divide column values by %2", name, value); break; } return msg; } void AddSubtractValueDialog::generate() { if(m_spreadsheet != nullptr) generateForColumns(); else if(m_matrix != nullptr) generateForMatrices(); } void AddSubtractValueDialog::generateForColumns() { Q_ASSERT(m_spreadsheet); WAIT_CURSOR; QString msg = getMessage(m_spreadsheet->name()); m_spreadsheet->beginMacro(msg); - AbstractColumn::ColumnMode mode = m_columns.first()->columnMode(); + auto mode = m_columns.first()->columnMode(); const int rows = m_spreadsheet->rowCount(); - if (mode == AbstractColumn::Integer) { + if (mode == AbstractColumn::ColumnMode::Integer) { QVector new_data(rows); int value = ui.leValue->text().toInt(); switch (m_operation) { case Subtract: value *= -1; //fall through case Add: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i) + value; col->replaceInteger(0, new_data); } break; case Multiply: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i) * value; col->replaceInteger(0, new_data); } break; case Divide: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i) / value; col->replaceInteger(0, new_data); } break; } - } else if (mode == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { QVector new_data(rows); qint64 value = ui.leValue->text().toLongLong(); switch (m_operation) { case Subtract: value *= -1; //fall through case Add: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i) + value; col->replaceBigInt(0, new_data); } break; case Multiply: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i) * value; col->replaceBigInt(0, new_data); } break; case Divide: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i) / value; col->replaceBigInt(0, new_data); } break; } - } else if (mode == AbstractColumn::Numeric) { + } else if (mode == AbstractColumn::ColumnMode::Numeric) { QVector new_data(rows); double value = ui.leValue->text().toDouble(); switch (m_operation) { case Subtract: value *= -1.; //fall through case Add: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i) + value; col->replaceValues(0, new_data); } break; case Multiply: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i) * value; col->replaceValues(0, new_data); } break; case Divide: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i) / value; col->replaceValues(0, new_data); } break; } } else { //datetime QVector new_data(rows); quint64 value = ui.dateTimeEdit->dateTime().toMSecsSinceEpoch(); switch (m_operation) { case Subtract: value *= -1.; //fall through case Add: for (auto* col : m_columns) { auto* data = static_cast* >(col->data()); for (int i = 0; ioperator[](i).toMSecsSinceEpoch() + value); col->replaceDateTimes(0, new_data); } case Multiply: case Divide: break; } } m_spreadsheet->endMacro(); RESET_CURSOR; } void AddSubtractValueDialog::generateForMatrices() { Q_ASSERT(m_matrix); WAIT_CURSOR; QString msg = getMessage(m_matrix->name()); m_matrix->beginMacro(msg); - AbstractColumn::ColumnMode mode = m_matrix->mode(); + auto mode = m_matrix->mode(); const int rows = m_matrix->rowCount(); const int cols = m_matrix->columnCount(); - if (mode == AbstractColumn::Integer) { + if (mode == AbstractColumn::ColumnMode::Integer) { int new_data; int value = ui.leValue->text().toInt(); switch (m_operation) { case Subtract: value *= -1; //fall through case Add: for (int i = 0; icell(i,j); new_data += value; m_matrix->setCell(i, j, new_data); } break; case Multiply: for (int i = 0; icell(i,j); new_data *= value; m_matrix->setCell(i, j, new_data); } break; case Divide: for (int i = 0; icell(i,j); new_data /= value; m_matrix->setCell(i, j, new_data); } break; } - } else if (mode == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { qint64 new_data; qint64 value = ui.leValue->text().toLongLong(); switch (m_operation) { case Subtract: value *= -1; //fall through case Add: for (int i = 0; icell(i,j); new_data += value; m_matrix->setCell(i, j, new_data); } break; case Multiply: for (int i = 0; icell(i,j); new_data *= value; m_matrix->setCell(i, j, new_data); } break; case Divide: for (int i = 0; icell(i,j); new_data /= value; m_matrix->setCell(i,j,new_data); } break; } - } else if (mode == AbstractColumn::Numeric) { + } else if (mode == AbstractColumn::ColumnMode::Numeric) { double new_data; double value = ui.leValue->text().toDouble(); switch (m_operation) { case Subtract: value *= -1.; //fall through case Add: for (int i = 0; icell(i,j); new_data += value; m_matrix->setCell(i,j,new_data); } break; case Multiply: for (int i = 0; icell(i,j); new_data *= value; m_matrix->setCell(i,j,new_data); } break; case Divide: for (int i = 0; icell(i,j); new_data /= value; m_matrix->setCell(i,j,new_data); } break; } } else { //datetime QDateTime new_data; quint64 value = ui.dateTimeEdit->dateTime().toMSecsSinceEpoch(); switch (m_operation) { case Subtract: value *= -1.; //fall through case Add: for (int i = 0; icell(i,j)).toMSecsSinceEpoch(); new_data = QDateTime::fromMSecsSinceEpoch(data + value); m_matrix->setCell(i,j,new_data); } break; case Multiply: case Divide: break; } } m_matrix->endMacro(); RESET_CURSOR; } diff --git a/src/kdefrontend/spreadsheet/DropValuesDialog.cpp b/src/kdefrontend/spreadsheet/DropValuesDialog.cpp index 37aeaa81b..bcf1d02d7 100644 --- a/src/kdefrontend/spreadsheet/DropValuesDialog.cpp +++ b/src/kdefrontend/spreadsheet/DropValuesDialog.cpp @@ -1,594 +1,596 @@ /*************************************************************************** File : DropValuesDialog.cpp Project : LabPlot Description : Dialog for droping and masking values in columns -------------------------------------------------------------------- Copyright : (C) 2015-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 "DropValuesDialog.h" #include "backend/core/column/Column.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" #include #include #include #include #include #include #include #include #include enum class Operator {EqualTo, BetweenIncl, BetweenExcl, GreaterThan, GreaterThanEqualTo, LessThan, LessThanEqualTo}; /*! \class DropValuesDialog \brief Dialog for generating values from a mathematical function. \ingroup kdefrontend */ DropValuesDialog::DropValuesDialog(Spreadsheet* s, bool mask, QWidget* parent) : QDialog(parent), m_spreadsheet(s), m_mask(mask) { setWindowTitle(i18nc("@title:window", "Drop Values")); ui.setupUi(this); setAttribute(Qt::WA_DeleteOnClose); ui.cbOperator->addItem(i18n("Equal to")); ui.cbOperator->addItem(i18n("Between (Incl. End Points)")); ui.cbOperator->addItem(i18n("Between (Excl. End Points)")); ui.cbOperator->addItem(i18n("Greater than")); ui.cbOperator->addItem(i18n("Greater than or Equal to")); ui.cbOperator->addItem(i18n("Less than")); ui.cbOperator->addItem(i18n("Less than or Equal to")); ui.leValue1->setValidator( new QDoubleValidator(ui.leValue1) ); ui.leValue2->setValidator( new QDoubleValidator(ui.leValue2) ); QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); ui.horizontalLayout->addWidget(btnBox); m_okButton = btnBox->button(QDialogButtonBox::Ok); connect(btnBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &DropValuesDialog::close); if (m_mask) { m_okButton->setText(i18n("&Mask")); m_okButton->setToolTip(i18n("Mask values in the specified region")); ui.lMode->setText(i18n("Mask values")); setWindowTitle(i18nc("@title:window", "Mask Values")); } else { m_okButton->setText(i18n("&Drop")); m_okButton->setToolTip(i18n("Drop values in the specified region")); } connect(ui.cbOperator, static_cast(&QComboBox::currentIndexChanged), this, &DropValuesDialog::operatorChanged ); connect(m_okButton, &QPushButton::clicked, this, &DropValuesDialog::okClicked); connect(btnBox, &QDialogButtonBox::accepted, this, &DropValuesDialog::accept); connect(btnBox, &QDialogButtonBox::rejected, this, &DropValuesDialog::reject); //restore saved settings if available KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("DropValuesDialog")); ui.cbOperator->setCurrentIndex(conf.readEntry("Operator", 0)); operatorChanged(ui.cbOperator->currentIndex()); create(); // ensure there's a window created if (conf.exists()) { KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else resize(QSize(400, 0).expandedTo(minimumSize())); } DropValuesDialog::~DropValuesDialog() { //save the current settings KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("DropValuesDialog")); conf.writeEntry("Operator", ui.cbOperator->currentIndex()); KWindowConfig::saveWindowSize(windowHandle(), conf); } void DropValuesDialog::setColumns(const QVector& columns) { m_columns = columns; } void DropValuesDialog::operatorChanged(int index) const { bool value2 = (index == 1) || (index == 2); ui.lMin->setVisible(value2); ui.lMax->setVisible(value2); ui.lAnd->setVisible(value2); ui.leValue2->setVisible(value2); } void DropValuesDialog::okClicked() const { if (m_mask) maskValues(); else dropValues(); } //TODO: m_column->setMasked() is slow, we need direct access to the masked-container -> redesign //TODO: provide template based solution to avoid code duplication class MaskValuesTask : public QRunnable { public: MaskValuesTask(Column* col, Operator op, double value1, double value2) { m_column = col; m_operator = op; m_value1 = value1; m_value2 = value2; } void run() override { m_column->setSuppressDataChangedSignal(true); bool changed = false; auto* data = static_cast* >(m_column->data()); auto* data_int = static_cast* >(m_column->data()); auto* data_bigint = static_cast* >(m_column->data()); const int rows = m_column->rowCount(); + auto mode = m_column->columnMode(); switch (m_operator) { case Operator::EqualTo: - if (m_column->columnMode() == AbstractColumn::Numeric) { + if (mode == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) { if (data->at(i) == m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::Integer) { + } else if (mode == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) { if (data_int->at(i) == m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) { if (data_bigint->at(i) == m_value1) { m_column->setMasked(i, true); changed = true; } } } break; case Operator::BetweenIncl: - if (m_column->columnMode() == AbstractColumn::Numeric) { + if (mode == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) { if (data->at(i) >= m_value1 && data->at(i) <= m_value2) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::Integer) { + } else if (mode == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) { if (data_int->at(i) >= m_value1 && data_int->at(i) <= m_value2) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) { if (data_bigint->at(i) >= m_value1 && data_bigint->at(i) <= m_value2) { m_column->setMasked(i, true); changed = true; } } } break; case Operator::BetweenExcl: - if (m_column->columnMode() == AbstractColumn::Numeric) { + if (mode == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) { if (data->at(i) > m_value1 && data->at(i) < m_value2) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::Integer) { + } else if (mode == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) { if (data_int->at(i) > m_value1 && data_int->at(i) < m_value2) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) { if (data_bigint->at(i) > m_value1 && data_bigint->at(i) < m_value2) { m_column->setMasked(i, true); changed = true; } } } break; case Operator::GreaterThan: - if (m_column->columnMode() == AbstractColumn::Numeric) { + if (mode == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) { if (data->at(i) > m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::Integer) { + } else if (mode == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) { if (data_int->at(i) > m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) { if (data_bigint->at(i) > m_value1) { m_column->setMasked(i, true); changed = true; } } } break; case Operator::GreaterThanEqualTo: - if (m_column->columnMode() == AbstractColumn::Numeric) { + if (mode == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) { if (data->at(i) >= m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::Integer) { + } else if (mode == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) { if (data_int->at(i) >= m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) { if (data_bigint->at(i) >= m_value1) { m_column->setMasked(i, true); changed = true; } } } break; case Operator::LessThan: - if (m_column->columnMode() == AbstractColumn::Numeric) { + if (mode == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) { if (data->at(i) < m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::Integer) { + } else if (mode == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) { if (data_int->at(i) < m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) { if (data_bigint->at(i) < m_value1) { m_column->setMasked(i, true); changed = true; } } } break; case Operator::LessThanEqualTo: - if (m_column->columnMode() == AbstractColumn::Numeric) { + if (mode == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) { if (data->at(i) <= m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::Integer) { + } else if (mode == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) { if (data_int->at(i) <= m_value1) { m_column->setMasked(i, true); changed = true; } } - } else if (m_column->columnMode() == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) { if (data_bigint->at(i) <= m_value1) { m_column->setMasked(i, true); changed = true; } } } } m_column->setSuppressDataChangedSignal(false); if (changed) m_column->setChanged(); } private: Operator m_operator; double m_value1; double m_value2; Column* m_column; }; //TODO: provide template based solution to avoid code duplication class DropValuesTask : public QRunnable { public: DropValuesTask(Column* col, Operator op, double value1, double value2) { m_column = col; m_operator = op; m_value1 = value1; m_value2 = value2; } void run() override { bool changed = false; - if (m_column->columnMode() == AbstractColumn::Numeric) { + auto mode = m_column->columnMode(); + if (mode == AbstractColumn::ColumnMode::Numeric) { auto* data = static_cast* >(m_column->data()); QVector new_data(*data); switch (m_operator) { case Operator::EqualTo: for (auto& d : new_data) { if (d == m_value1) { d = NAN; changed = true; } } break; case Operator::BetweenIncl: for (auto& d : new_data) { if (d >= m_value1 && d <= m_value2) { d = NAN; changed = true; } } break; case Operator::BetweenExcl: for (auto& d : new_data) { if (d > m_value1 && d < m_value2) { d = NAN; changed = true; } } break; case Operator::GreaterThan: for (auto& d : new_data) { if (d > m_value1) { d = NAN; changed = true; } } break; case Operator::GreaterThanEqualTo: for (auto& d : new_data) { if (d >= m_value1) { d = NAN; changed = true; } } break; case Operator::LessThan: for (auto& d : new_data) { if (d < m_value1) { d = NAN; changed = true; } } break; case Operator::LessThanEqualTo: for (auto& d : new_data) { if (d <= m_value1) { d = NAN; changed = true; } } } if (changed) m_column->replaceValues(0, new_data); - } else if (m_column->columnMode() == AbstractColumn::Integer) { + } else if (mode == AbstractColumn::ColumnMode::Integer) { auto* data = static_cast* >(m_column->data()); QVector new_data(*data); switch (m_operator) { case Operator::EqualTo: for (auto& d : new_data) { if (d == m_value1) { d = 0; changed = true; } } break; case Operator::BetweenIncl: for (auto& d : new_data) { if (d >= m_value1 && d <= m_value2) { d = 0; changed = true; } } break; case Operator::BetweenExcl: for (auto& d : new_data) { if (d > m_value1 && d < m_value2) { d = 0; changed = true; } } break; case Operator::GreaterThan: for (auto& d : new_data) { if (d > m_value1) { d = 0; changed = true; } } break; case Operator::GreaterThanEqualTo: for (auto& d : new_data) { if (d >= m_value1) { d = 0; changed = true; } } break; case Operator::LessThan: for (auto& d : new_data) { if (d < m_value1) { d = 0; changed = true; } } break; case Operator::LessThanEqualTo: for (auto& d : new_data) { if (d <= m_value1) { d = 0; changed = true; } } } if (changed) m_column->replaceInteger(0, new_data); - } else if (m_column->columnMode() == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { auto* data = static_cast* >(m_column->data()); QVector new_data(*data); switch (m_operator) { case Operator::EqualTo: for (auto& d : new_data) { if (d == m_value1) { d = 0; changed = true; } } break; case Operator::BetweenIncl: for (auto& d : new_data) { if (d >= m_value1 && d <= m_value2) { d = 0; changed = true; } } break; case Operator::BetweenExcl: for (auto& d : new_data) { if (d > m_value1 && d < m_value2) { d = 0; changed = true; } } break; case Operator::GreaterThan: for (auto& d : new_data) { if (d > m_value1) { d = 0; changed = true; } } break; case Operator::GreaterThanEqualTo: for (auto& d : new_data) { if (d >= m_value1) { d = 0; changed = true; } } break; case Operator::LessThan: for (auto& d : new_data) { if (d < m_value1) { d = 0; changed = true; } } break; case Operator::LessThanEqualTo: for (auto& d : new_data) { if (d <= m_value1) { d = 0; changed = true; } } } if (changed) m_column->replaceBigInt(0, new_data); } } private: Operator m_operator; double m_value1; double m_value2; Column* m_column; }; void DropValuesDialog::maskValues() const { Q_ASSERT(m_spreadsheet); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: mask values", m_spreadsheet->name())); const Operator op = static_cast(ui.cbOperator->currentIndex()); const double value1 = ui.leValue1->text().toDouble(); const double value2 = ui.leValue2->text().toDouble(); for (Column* col: m_columns) { auto* task = new MaskValuesTask(col, op, value1, value2); task->run(); //TODO: writing to the undo-stack in Column::setMasked() is not tread-safe -> redesign // QThreadPool::globalInstance()->start(task); delete task; } //wait until all columns were processed // QThreadPool::globalInstance()->waitForDone(); m_spreadsheet->endMacro(); RESET_CURSOR; } void DropValuesDialog::dropValues() const { Q_ASSERT(m_spreadsheet); WAIT_CURSOR; m_spreadsheet->beginMacro(i18n("%1: drop values", m_spreadsheet->name())); const Operator op = static_cast(ui.cbOperator->currentIndex()); const double value1 = ui.leValue1->text().toDouble(); const double value2 = ui.leValue2->text().toDouble(); for (Column* col: m_columns) { auto* task = new DropValuesTask(col, op, value1, value2); QThreadPool::globalInstance()->start(task); } //wait until all columns were processed QThreadPool::globalInstance()->waitForDone(); m_spreadsheet->endMacro(); RESET_CURSOR; } diff --git a/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp b/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp index 2ed6d60b5..e091c8baa 100644 --- a/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp +++ b/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp @@ -1,431 +1,431 @@ /*************************************************************************** File : FunctionValuesDialog.cpp Project : LabPlot Description : Dialog for generating values from a mathematical function -------------------------------------------------------------------- Copyright : (C) 2014-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 "FunctionValuesDialog.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/gsl/ExpressionParser.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/widgets/ConstantsWidget.h" #include "kdefrontend/widgets/FunctionsWidget.h" #include #include #include #include #include #include #include #include #include /*! \class FunctionValuesDialog \brief Dialog for generating values from a mathematical function. \ingroup kdefrontend */ FunctionValuesDialog::FunctionValuesDialog(Spreadsheet* s, QWidget* parent) : QDialog(parent), m_spreadsheet(s) { Q_ASSERT(s != nullptr); setWindowTitle(i18nc("@title:window", "Function Values")); ui.setupUi(this); setAttribute(Qt::WA_DeleteOnClose); ui.tbConstants->setIcon( QIcon::fromTheme("labplot-format-text-symbol") ); ui.tbConstants->setIcon( QIcon::fromTheme("format-text-symbol") ); ui.tbFunctions->setIcon( QIcon::fromTheme("preferences-desktop-font") ); ui.teEquation->setMaximumHeight(QLineEdit().sizeHint().height()*2); ui.teEquation->setFocus(); m_topLevelClasses = {AspectType::Folder, AspectType::Workbook, AspectType::Spreadsheet, AspectType::Column }; m_selectableClasses = {AspectType::Column}; // needed for buggy compiler #if __cplusplus < 201103L m_aspectTreeModel = std::auto_ptr(new AspectTreeModel(m_spreadsheet->project())); #else m_aspectTreeModel = std::unique_ptr(new AspectTreeModel(m_spreadsheet->project())); #endif m_aspectTreeModel->setSelectableAspects(m_selectableClasses); m_aspectTreeModel->enableNumericColumnsOnly(true); m_aspectTreeModel->enableNonEmptyNumericColumnsOnly(true); ui.bAddVariable->setIcon(QIcon::fromTheme("list-add")); ui.bAddVariable->setToolTip(i18n("Add new variable")); ui.chkAutoUpdate->setToolTip(i18n("Automatically update the calculated values on changes in the variable columns")); QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); ui.verticalLayout->addWidget(btnBox); m_okButton = btnBox->button(QDialogButtonBox::Ok); connect(btnBox, &QDialogButtonBox::accepted, this, &FunctionValuesDialog::accept); connect(btnBox, &QDialogButtonBox::rejected, this, &FunctionValuesDialog::reject); m_okButton->setText(i18n("&Generate")); m_okButton->setToolTip(i18n("Generate function values")); connect(ui.bAddVariable, &QPushButton::pressed, this, &FunctionValuesDialog::addVariable); connect(ui.teEquation, &ExpressionTextEdit::expressionChanged, this, &FunctionValuesDialog::checkValues); connect(ui.tbConstants, &QToolButton::clicked, this, &FunctionValuesDialog::showConstants); connect(ui.tbFunctions, &QToolButton::clicked, this, &FunctionValuesDialog::showFunctions); connect(m_okButton, &QPushButton::clicked, this, &FunctionValuesDialog::generate); //restore saved settings if available create(); // ensure there's a window created KConfigGroup conf(KSharedConfig::openConfig(), "FunctionValuesDialog"); if (conf.exists()) { KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else resize(QSize(300, 0).expandedTo(minimumSize())); } FunctionValuesDialog::~FunctionValuesDialog() { KConfigGroup conf(KSharedConfig::openConfig(), "FunctionValuesDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); } void FunctionValuesDialog::setColumns(const QVector& columns) { m_columns = columns; //formula expression ui.teEquation->setPlainText(m_columns.first()->formula()); //variables const QStringList& variableNames = m_columns.first()->formulaVariableNames(); if (!variableNames.size()) { //no formula was used for this column -> add the first variable "x" addVariable(); m_variableNames[0]->setText("x"); } else { //formula and variables are available const QVector& variableColumns = m_columns.first()->formulaVariableColumns(); const QStringList columnPaths = m_columns.first()->formulaVariableColumnPaths(); //add all available variables and select the corresponding columns const QVector cols = m_spreadsheet->project()->children(AspectType::Column, AbstractAspect::ChildIndexFlag::Recursive); for (int i = 0; i < variableNames.size(); ++i) { addVariable(); m_variableNames[i]->setText(variableNames.at(i)); bool found = false; for (const auto* col : cols) { if (col != variableColumns.at(i)) continue; const auto* column = dynamic_cast(col); if (column) m_variableDataColumns[i]->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(column)); else m_variableDataColumns[i]->setCurrentModelIndex(QModelIndex()); m_variableDataColumns[i]->useCurrentIndexText(true); m_variableDataColumns[i]->setInvalid(false); found = true; break; } //for the current variable name no column is existing anymore (was deleted) //->highlight the combobox red if (!found) { m_variableDataColumns[i]->setCurrentModelIndex(QModelIndex()); m_variableDataColumns[i]->useCurrentIndexText(false); m_variableDataColumns[i]->setInvalid(true, i18n("The column \"%1\"\nis not available anymore. It will be automatically used once it is created again.", columnPaths[i])); m_variableDataColumns[i]->setText(columnPaths[i].split('/').last()); } } } //auto update ui.chkAutoUpdate->setChecked(m_columns.first()->formulaAutoUpdate()); checkValues(); } bool FunctionValuesDialog::validVariableName(QLineEdit* le) { if (ExpressionParser::getInstance()->constants().indexOf(le->text()) != -1) { le->setStyleSheet("QLineEdit{background: red;}"); le->setToolTip(i18n("Provided variable name is already reserved for a name of a constant. Please use another name.")); return false; } if (ExpressionParser::getInstance()->functions().indexOf(le->text()) != -1) { le->setStyleSheet("QLineEdit{background: red;}"); le->setToolTip(i18n("Provided variable name is already reserved for a name of a function. Please use another name.")); return false; } le->setStyleSheet(QString()); le->setToolTip(""); return true; } /*! check the user input and enables/disables the Ok-button depending on the correctness of the input */ void FunctionValuesDialog::checkValues() { //check whether the formula syntax is correct if (!ui.teEquation->isValid()) { m_okButton->setEnabled(false); return; } //check whether for the variables where a name was provided also a column was selected. for (int i = 0; i < m_variableDataColumns.size(); ++i) { if (m_variableNames.at(i)->text().simplified().isEmpty()) continue; TreeViewComboBox* cb = m_variableDataColumns.at(i); AbstractAspect* aspect = static_cast(cb->currentModelIndex().internalPointer()); if (!aspect) { m_okButton->setEnabled(false); return; } if (!validVariableName(m_variableNames[i])) { m_okButton->setEnabled(false); return; } /* Column* column = dynamic_cast(aspect); DEBUG("row count = " << (static_cast* >(column->data()))->size()); if (!column || column->rowCount() < 1) { m_okButton->setEnabled(false); //Warning: x column is empty return; } */ } m_okButton->setEnabled(true); } void FunctionValuesDialog::showConstants() { QMenu menu; ConstantsWidget constants(&menu); connect(&constants, &ConstantsWidget::constantSelected, this, &FunctionValuesDialog::insertConstant); connect(&constants, &ConstantsWidget::constantSelected, &menu, &QMenu::close); connect(&constants, &ConstantsWidget::canceled, &menu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&constants); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width()+ui.tbConstants->width(),-menu.sizeHint().height()); menu.exec(ui.tbConstants->mapToGlobal(pos)); } void FunctionValuesDialog::showFunctions() { QMenu menu; FunctionsWidget functions(&menu); connect(&functions, &FunctionsWidget::functionSelected, this, &FunctionValuesDialog::insertFunction); connect(&functions, &FunctionsWidget::functionSelected, &menu, &QMenu::close); connect(&functions, &FunctionsWidget::canceled, &menu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&functions); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width()+ui.tbFunctions->width(),-menu.sizeHint().height()); menu.exec(ui.tbFunctions->mapToGlobal(pos)); } void FunctionValuesDialog::insertFunction(const QString& str) { //TODO: not all functions have only one argument ui.teEquation->insertPlainText(str + "(x)"); } void FunctionValuesDialog::insertConstant(const QString& str) { ui.teEquation->insertPlainText(str); } void FunctionValuesDialog::addVariable() { auto* layout = ui.gridLayoutVariables; int row = m_variableNames.size(); //text field for the variable name auto* le = new QLineEdit(); le->setMaximumWidth(30); connect(le, &QLineEdit::textChanged, this, &FunctionValuesDialog::variableNameChanged); layout->addWidget(le, row, 0, 1, 1); m_variableNames << le; //label for the "="-sign auto* l = new QLabel("="); layout->addWidget(l, row, 1, 1, 1); m_variableLabels << l; //combo box for the data column auto* cb = new TreeViewComboBox(); cb->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); connect(cb, &TreeViewComboBox::currentModelIndexChanged, this, &FunctionValuesDialog::variableColumnChanged); layout->addWidget(cb, row, 2, 1, 1); m_variableDataColumns << cb; cb->setTopLevelClasses(m_topLevelClasses); cb->setModel(m_aspectTreeModel.get()); //don't allow to select columns to be calculated as variable columns (avoid circular dependencies) QList aspects; for (auto* col : m_columns) aspects << col; cb->setHiddenAspects(aspects); //for the variable column select the first non-selected column in the spreadsheet for (auto* col : m_spreadsheet->children()) { if (m_columns.indexOf(col) == -1) { cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(col)); break; } } //move the add-button to the next row layout->removeWidget(ui.bAddVariable); layout->addWidget(ui.bAddVariable, row+1,3, 1, 1); //add delete-button for the just added variable if (row != 0) { auto* b = new QToolButton(); b->setIcon(QIcon::fromTheme("list-remove")); b->setToolTip(i18n("Delete variable")); layout->addWidget(b, row, 3, 1, 1); m_variableDeleteButtons<setText(i18n("Variables:")); //TODO: adjust the tab-ordering after new widgets were added } void FunctionValuesDialog::deleteVariable() { QObject* ob = QObject::sender(); int index = m_variableDeleteButtons.indexOf(qobject_cast(ob)) ; delete m_variableNames.takeAt(index+1); delete m_variableLabels.takeAt(index+1); delete m_variableDataColumns.takeAt(index+1); delete m_variableDeleteButtons.takeAt(index); variableNameChanged(); checkValues(); //adjust the layout resize( QSize(width(),0).expandedTo(minimumSize()) ); m_variableNames.size() > 1 ? ui.lVariable->setText(i18n("Variables:")) : ui.lVariable->setText(i18n("Variable:")); //TODO: adjust the tab-ordering after some widgets were deleted } void FunctionValuesDialog::variableNameChanged() { QStringList vars; QString text; for (auto* varName : m_variableNames) { QString name = varName->text().simplified(); if (!name.isEmpty()) { vars << name; if (text.isEmpty()) { text += name; } else { text += ", " + name; } } } if (!text.isEmpty()) text = "f(" + text + ") = "; else text = "f = "; ui.lFunction->setText(text); ui.teEquation->setVariables(vars); checkValues(); } /*! * called if a new column was selected in the comboboxes for the variable columns. */ void FunctionValuesDialog::variableColumnChanged(const QModelIndex& index) { //combobox was potentially red-highlighted because of a missing column //remove the highlighting if we have a valid selection now auto* aspect = static_cast(index.internalPointer()); if (aspect) { auto* cb = dynamic_cast(QObject::sender()); if (cb) cb->setStyleSheet(""); } checkValues(); } void FunctionValuesDialog::generate() { Q_ASSERT(m_spreadsheet); WAIT_CURSOR; m_spreadsheet->beginMacro(i18np("%1: fill column with function values", "%1: fill columns with function values", m_spreadsheet->name(), m_columns.size())); //determine variable names and the data vectors of the specified columns QStringList variableNames; QVector variableColumns; for (int i = 0; i < m_variableNames.size(); ++i) { variableNames << m_variableNames.at(i)->text().simplified(); AbstractAspect* aspect = static_cast(m_variableDataColumns.at(i)->currentModelIndex().internalPointer()); Q_ASSERT(aspect); auto* column = dynamic_cast(aspect); Q_ASSERT(column); variableColumns << column; } //set the new values and store the expression, variable names and the used data columns const QString& expression = ui.teEquation->toPlainText(); bool autoUpdate = (ui.chkAutoUpdate->checkState() == Qt::Checked); for (auto* col : m_columns) { - if (col->columnMode() != AbstractColumn::Numeric) - col->setColumnMode(AbstractColumn::Numeric); + if (col->columnMode() != AbstractColumn::ColumnMode::Numeric) + col->setColumnMode(AbstractColumn::ColumnMode::Numeric); col->setFormula(expression, variableNames, variableColumns, autoUpdate); col->updateFormula(); } m_spreadsheet->endMacro(); RESET_CURSOR; } diff --git a/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp b/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp index 67b1c38f0..2185a79d6 100644 --- a/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp +++ b/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp @@ -1,1045 +1,1046 @@ /*************************************************************************** File : RandomValuesDialog.cpp Project : LabPlot Description : Dialog for generating non-uniformly distributed random numbers -------------------------------------------------------------------- Copyright : (C) 2014-2019 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2020 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 "RandomValuesDialog.h" #include "backend/core/column/Column.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" #include #include #include #include #include #include #include #include #include extern "C" { #include "backend/nsl/nsl_sf_stats.h" #include #include } /*! \class RandomValuesDialog \brief Dialog for generating non-uniform random numbers. \ingroup kdefrontend */ RandomValuesDialog::RandomValuesDialog(Spreadsheet* s, QWidget* parent) : QDialog(parent), m_spreadsheet(s) { setWindowTitle(i18nc("@title:window", "Random Values")); QWidget* mainWidget = new QWidget(this); ui.setupUi(mainWidget); auto* layout = new QVBoxLayout(this); auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_okButton = buttonBox->button(QDialogButtonBox::Ok); m_okButton->setDefault(true); m_okButton->setToolTip(i18n("Generate random values according to the selected distribution")); m_okButton->setText(i18n("&Generate")); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &RandomValuesDialog::close); connect(buttonBox, &QDialogButtonBox::accepted, this, &RandomValuesDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &RandomValuesDialog::reject); layout->addWidget(mainWidget); layout->addWidget(buttonBox); setLayout(layout); setAttribute(Qt::WA_DeleteOnClose); for (int i = 0; i < NSL_SF_STATS_DISTRIBUTION_RNG_COUNT; i++) ui.cbDistribution->addItem(i18n(nsl_sf_stats_distribution_name[i]), i); //use white background in the preview label QPalette p; p.setColor(QPalette::Window, Qt::white); ui.lFuncPic->setAutoFillBackground(true); ui.lFuncPic->setPalette(p); ui.leParameter1->setClearButtonEnabled(true); ui.leParameter2->setClearButtonEnabled(true); ui.leParameter3->setClearButtonEnabled(true); ui.leParameter1->setValidator( new QDoubleValidator(ui.leParameter1) ); ui.leParameter2->setValidator( new QDoubleValidator(ui.leParameter2) ); ui.leParameter3->setValidator( new QDoubleValidator(ui.leParameter3) ); connect(ui.cbDistribution, static_cast(&QComboBox::currentIndexChanged), this, &RandomValuesDialog::distributionChanged); connect(ui.leParameter1, &QLineEdit::textChanged, this, &RandomValuesDialog::checkValues); connect(ui.leParameter2, &QLineEdit::textChanged, this, &RandomValuesDialog::checkValues); connect(ui.leParameter3, &QLineEdit::textChanged, this, &RandomValuesDialog::checkValues); connect(buttonBox, &QDialogButtonBox::accepted, this, &RandomValuesDialog::generate); //restore saved settings if available create(); // ensure there's a window created const KConfigGroup conf(KSharedConfig::openConfig(), "RandomValuesDialog"); if (conf.exists()) { ui.cbDistribution->setCurrentIndex(conf.readEntry("Distribution", 0)); this->distributionChanged(ui.cbDistribution->currentIndex()); //if index=0 no signal is emitted above, call this slot directly here ui.leParameter1->setText(conf.readEntry("Parameter1")); ui.leParameter2->setText(conf.readEntry("Parameter2")); ui.leParameter3->setText(conf.readEntry("Parameter3")); KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else { //Gaussian distribution as default this->distributionChanged(0); resize( QSize(400,0).expandedTo(minimumSize()) ); } } RandomValuesDialog::~RandomValuesDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "RandomValuesDialog"); conf.writeEntry("Distribution", ui.cbDistribution->currentIndex()); conf.writeEntry("Parameter1", ui.leParameter1->text()); conf.writeEntry("Parameter2", ui.leParameter2->text()); conf.writeEntry("Parameter3", ui.leParameter3->text()); KWindowConfig::saveWindowSize(windowHandle(), conf); } void RandomValuesDialog::setColumns(const QVector& columns) { m_columns = columns; } void RandomValuesDialog::distributionChanged(int index) { nsl_sf_stats_distribution dist = (nsl_sf_stats_distribution)ui.cbDistribution->itemData(index).toInt(); // default settings (used by most distributions) ui.lParameter1->show(); ui.leParameter1->show(); ui.lParameter2->show(); ui.leParameter2->show(); ui.lParameter3->hide(); ui.leParameter3->hide(); ui.lFunc->setText("p(x) ="); switch (dist) { case nsl_sf_stats_gaussian: ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_gaussian_tail: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.lParameter3->setText("a ="); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("0.0"); break; case nsl_sf_stats_exponential: ui.lParameter1->setText(UTF8_QSTRING("λ =")); ui.leParameter1->setText("1.0"); ui.lParameter2->setText(UTF8_QSTRING("μ =")); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_laplace: ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_exponential_power: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.lParameter3->setText("b ="); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("1.0"); break; case nsl_sf_stats_cauchy_lorentz: ui.lParameter1->setText(UTF8_QSTRING("γ =")); ui.lParameter2->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_rayleigh: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lParameter1->setText(UTF8_QSTRING("σ =")); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_rayleigh_tail: ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_landau: ui.lParameter1->hide(); ui.leParameter1->hide(); ui.lParameter2->hide(); ui.leParameter2->hide(); break; case nsl_sf_stats_levy_alpha_stable: ui.lParameter1->setText("c ="); ui.lParameter2->setText(UTF8_QSTRING("α =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_levy_skew_alpha_stable: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(UTF8_QSTRING("c =")); ui.lParameter2->setText(UTF8_QSTRING("α =")); ui.lParameter3->setText(UTF8_QSTRING("β =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("1.0"); break; case nsl_sf_stats_flat: ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_gamma: ui.lParameter1->setText(UTF8_QSTRING("θ =")); ui.lParameter2->setText("k ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_weibull: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText("k ="); ui.lParameter2->setText(UTF8_QSTRING("λ =")); ui.lParameter3->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("1.0"); break; case nsl_sf_stats_beta: ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_gumbel1: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(UTF8_QSTRING("σ =")); ui.lParameter2->setText(UTF8_QSTRING("β =")); ui.lParameter3->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("0.0"); break; case nsl_sf_stats_gumbel2: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.lParameter3->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("0.0"); break; case nsl_sf_stats_pareto: ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_lognormal: ui.lParameter1->setText(UTF8_QSTRING("σ =")); ui.lParameter2->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_chi_squared: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lParameter1->setText("n ="); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_fdist: ui.lParameter1->setText(UTF8_QSTRING("ν₁ =")); ui.lParameter2->setText(UTF8_QSTRING("ν₂ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_tdist: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lParameter1->setText(UTF8_QSTRING("ν =")); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_logistic: ui.lParameter1->setText(UTF8_QSTRING("σ =")); ui.lParameter2->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_poisson: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lFunc->setText("p(k) ="); ui.lParameter1->setText(UTF8_QSTRING("λ =")); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_bernoulli: case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: ui.lParameter2->hide(); ui.leParameter2->hide(); if (dist == nsl_sf_stats_bernoulli) ui.lFunc->setText(QString()); else ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("p ="); ui.leParameter1->setText("0.5"); break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("p ="); ui.lParameter2->setText("n ="); ui.leParameter1->setText("0.5"); ui.leParameter2->setText("100"); break; case nsl_sf_stats_hypergeometric: ui.lParameter3->show(); ui.leParameter3->show(); ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("n1 ="); ui.lParameter2->setText("n2 ="); ui.lParameter3->setText("t ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("2.0"); ui.leParameter3->setText("3.0"); break; case nsl_sf_stats_maxwell_boltzmann: // additional non-GSL distros case nsl_sf_stats_sech: case nsl_sf_stats_levy: case nsl_sf_stats_frechet: break; } QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/gsl_distributions/" + QString(nsl_sf_stats_distribution_pic_name[dist]) + ".png"); DEBUG("Distribution pixmap path = " << STDSTRING(file)); ui.lFuncPic->setPixmap(QPixmap(file)); } void RandomValuesDialog::checkValues() { if (ui.leParameter1->text().simplified().isEmpty()) { m_okButton->setEnabled(false); return; } if (ui.leParameter2->isVisible() && ui.leParameter2->text().simplified().isEmpty()) { m_okButton->setEnabled(false); return; } if (ui.leParameter3->isVisible() && ui.leParameter3->text().simplified().isEmpty()) { m_okButton->setEnabled(false); return; } m_okButton->setEnabled(true); return; } void RandomValuesDialog::generate() { Q_ASSERT(m_spreadsheet); //create a generator chosen by the environment variable GSL_RNG_TYPE gsl_rng_env_setup(); const gsl_rng_type* T = gsl_rng_default; gsl_rng* r = gsl_rng_alloc(T); gsl_rng_set(r, QDateTime::currentMSecsSinceEpoch()); WAIT_CURSOR; for (auto* col : m_columns) col->setSuppressDataChangedSignal(true); m_spreadsheet->beginMacro(i18np("%1: fill column with non-uniform random numbers", "%1: fill columns with non-uniform random numbers", m_spreadsheet->name(), m_columns.size())); const int index = ui.cbDistribution->currentIndex(); const nsl_sf_stats_distribution dist = (nsl_sf_stats_distribution)ui.cbDistribution->itemData(index).toInt(); DEBUG("random number distribution: " << nsl_sf_stats_distribution_name[dist]); const int rows = m_spreadsheet->rowCount(); QVector data(rows); QVector data_int(rows); QVector data_bigint(rows); switch (dist) { case nsl_sf_stats_gaussian: { double mu = ui.leParameter1->text().toDouble(); double sigma = ui.leParameter2->text().toDouble(); DEBUG(" mu = " << mu << ", sigma = " << sigma); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + auto mode = col->columnMode(); + if (mode == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gaussian(r, sigma) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (mode == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gaussian(r, sigma) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (mode == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gaussian(r, sigma) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_gaussian_tail: { double mu = ui.leParameter1->text().toDouble(); double sigma = ui.leParameter2->text().toDouble(); double a = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gaussian_tail(r, a, sigma) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gaussian_tail(r, a, sigma) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gaussian_tail(r, a, sigma) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_exponential: { double l = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { //GSL uses the inverse for exp. distrib. for (int i = 0; i < rows; ++i) data[i] = gsl_ran_exponential(r, 1./l) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_exponential(r, 1./l) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_exponential(r, 1./l) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_laplace: { double mu = ui.leParameter1->text().toDouble(); double s = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_laplace(r, s) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_laplace(r, s) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_laplace(r, s) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_exponential_power: { double mu = ui.leParameter1->text().toDouble(); double a = ui.leParameter2->text().toDouble(); double b = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_exppow(r, a, b) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_exppow(r, a, b) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_exppow(r, a, b) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_cauchy_lorentz: { double gamma = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_cauchy(r, gamma) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_cauchy(r, gamma) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_cauchy(r, gamma) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_rayleigh: { double s = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_rayleigh(r, s); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_rayleigh(r, s)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_rayleigh(r, s)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_rayleigh_tail: { double mu = ui.leParameter1->text().toDouble(); double sigma = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_rayleigh_tail(r, mu, sigma); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_rayleigh_tail(r, mu, sigma)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_rayleigh_tail(r, mu, sigma)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_landau: for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_landau(r); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_landau(r)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_landau(r)); col->replaceBigInt(0, data_bigint); } } break; case nsl_sf_stats_levy_alpha_stable: { double c = ui.leParameter1->text().toDouble(); double alpha = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_levy(r, c, alpha); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_levy(r, c, alpha)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_levy(r, c, alpha)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_levy_skew_alpha_stable: { double c = ui.leParameter1->text().toDouble(); double alpha = ui.leParameter2->text().toDouble(); double beta = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_levy_skew(r, c, alpha, beta); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_levy_skew(r, c, alpha, beta)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_levy_skew(r, c, alpha, beta)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_gamma: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gamma(r, a, b); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gamma(r, a, b)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gamma(r, a, b)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_flat: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_flat(r, a, b); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_flat(r, a, b)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_flat(r, a, b)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_lognormal: { double s = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_lognormal(r, mu, s); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_lognormal(r, mu, s)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_lognormal(r, mu, s)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_chi_squared: { double n = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_chisq(r, n); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_chisq(r, n)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_chisq(r, n)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_fdist: { double nu1 = ui.leParameter1->text().toDouble(); double nu2 = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_fdist(r, nu1, nu2); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_fdist(r, nu1, nu2)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_fdist(r, nu1, nu2)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_tdist: { double nu = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_tdist(r, nu); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_tdist(r, nu)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_tdist(r, nu)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_beta: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_beta(r, a, b); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_beta(r, a, b)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_beta(r, a, b)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_logistic: { double s = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_logistic(r, s) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_logistic(r, s) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_logistic(r, s) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_pareto: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_pareto(r, a, b); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_pareto(r, a, b)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_pareto(r, a, b)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_weibull: { double k = ui.leParameter1->text().toDouble(); double l = ui.leParameter2->text().toDouble(); double mu = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_weibull(r, l, k) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_weibull(r, l, k) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_weibull(r, l, k) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_gumbel1: { double s = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); double mu = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gumbel1(r, 1./s, b) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gumbel1(r, 1./s, b) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gumbel1(r, 1./s, b) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_gumbel2: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); double mu = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gumbel2(r, a, b) + mu; col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gumbel2(r, a, b) + mu); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gumbel2(r, a, b) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_poisson: { double l = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_poisson(r, l); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_poisson(r, l)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_poisson(r, l)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_bernoulli: { double p = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_bernoulli(r, p); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_bernoulli(r, p)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_bernoulli(r, p)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_binomial: { double p = ui.leParameter1->text().toDouble(); double n = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_binomial(r, p, n); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_binomial(r, p, n)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_binomial(r, p, n)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_negative_binomial: { double p = ui.leParameter1->text().toDouble(); double n = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_negative_binomial(r, p, n); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_negative_binomial(r, p, n)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_negative_binomial(r, p, n)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_pascal: { double p = ui.leParameter1->text().toDouble(); double n = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_pascal(r, p, n); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_pascal(r, p, n)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_pascal(r, p, n)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_geometric: { double p = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_geometric(r, p); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_geometric(r, p)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_geometric(r, p)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_hypergeometric: { double n1 = ui.leParameter1->text().toDouble(); double n2 = ui.leParameter2->text().toDouble(); double t = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_hypergeometric(r, n1, n2, t); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_hypergeometric(r, n1, n2, t)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_hypergeometric(r, n1, n2, t)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_logarithmic: { double p = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { - if (col->columnMode() == AbstractColumn::Numeric) { + if (col->columnMode() == AbstractColumn::ColumnMode::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_logarithmic(r, p); col->replaceValues(0, data); - } else if (col->columnMode() == AbstractColumn::Integer) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_logarithmic(r, p)); col->replaceInteger(0, data_int); - } else if (col->columnMode() == AbstractColumn::BigInt) { + } else if (col->columnMode() == AbstractColumn::ColumnMode::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_logarithmic(r, p)); col->replaceBigInt(0, data_bigint); } } break; } // additional non-GSL distributions not needed case nsl_sf_stats_maxwell_boltzmann: case nsl_sf_stats_sech: case nsl_sf_stats_levy: case nsl_sf_stats_frechet: break; } for (auto* col : m_columns) { col->setSuppressDataChangedSignal(false); col->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; gsl_rng_free(r); } diff --git a/src/kdefrontend/spreadsheet/StatisticsDialog.cpp b/src/kdefrontend/spreadsheet/StatisticsDialog.cpp index 3071e3128..69dc2cebb 100644 --- a/src/kdefrontend/spreadsheet/StatisticsDialog.cpp +++ b/src/kdefrontend/spreadsheet/StatisticsDialog.cpp @@ -1,292 +1,304 @@ /*************************************************************************** File : StatisticsDialog.cpp Project : LabPlot Description : Dialog showing statistics for column values -------------------------------------------------------------------- Copyright : (C) 2016-2017 by Fabian Kristof (fkristofszabolcs@gmail.com)) Copyright : (C) 2016-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 "StatisticsDialog.h" #include "backend/core/column/Column.h" #include #include #include #include #include #include #include #include #include #include StatisticsDialog::StatisticsDialog(const QString& title, QWidget* parent) : QDialog(parent), m_twStatistics(new QTabWidget) { QDialogButtonBox* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok); QPushButton* btnOk = btnBox->button(QDialogButtonBox::Ok); btnOk->setFocus(); connect(btnOk, &QPushButton::clicked, this, &StatisticsDialog::close); connect(btnBox, &QDialogButtonBox::accepted, this, &StatisticsDialog::accept); auto* layout = new QVBoxLayout; layout->addWidget(m_twStatistics); layout->addWidget(btnBox); setLayout(layout); setWindowTitle(title); setWindowIcon(QIcon::fromTheme("view-statistics")); setAttribute(Qt::WA_DeleteOnClose); const QString htmlColor = (palette().color(QPalette::Base).lightness() < 128) ? QLatin1String("#5f5f5f") : QLatin1String("#D1D1D1"); m_htmlText = QString("" "" "" "" // "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" // "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" // "" "" "" "" "" "" "" "" "" "" "" "" "" "
" + i18n("Location measures")+ "
" + i18n("Size")+ "%1
" + i18n("Minimum")+ "%2
" + i18n("Maximum")+ "%3
" + i18n("Arithmetic mean")+ "%4
" + i18n("Geometric mean")+ "%5
" + i18n("Harmonic mean")+ "%6
" + i18n("Contraharmonic mean")+ "%7
" + i18n("Mode")+ "%8
" + i18n("First Quartile")+ "%9
" + i18n("Median")+ "%10
" + i18n("Third Quartile")+ "%11
" + i18n("Trimean")+ "%12
" + i18n("Dispersion measures")+ "
" + i18n("Variance")+ "%13
" + i18n("Standard deviation")+ "%14
" + i18n("Mean absolute deviation around mean")+ "%15
" + i18n("Mean absolute deviation around median")+ "%16
" + i18n("Median absolute deviation")+ "%17
" + i18n("Interquartile Range")+ "%18
" + i18n("Shape measures")+ "
" + i18n("Skewness")+ "%19
" + i18n("Kurtosis")+ "%20
" + i18n("Entropy")+ "%21
"); connect(m_twStatistics, &QTabWidget::currentChanged, this, &StatisticsDialog::currentTabChanged); //restore saved settings if available create(); // ensure there's a window created KConfigGroup conf(KSharedConfig::openConfig(), "StatisticsDialog"); if (conf.exists()) { KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else resize(QSize(490, 520)); } StatisticsDialog::~StatisticsDialog() { KConfigGroup conf(KSharedConfig::openConfig(), "StatisticsDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); } void StatisticsDialog::setColumns(const QVector& columns) { if (!columns.size()) return; m_columns = columns; for (auto* col : m_columns) { auto* textEdit = new QTextEdit; textEdit->setReadOnly(true); m_twStatistics->addTab(textEdit, col->name()); } currentTabChanged(0); } const QString StatisticsDialog::isNanValue(const double value) { return (std::isnan(value) ? QLatin1String("-") : QString::number(value,'f')); } QString modeValue(Column* column, double value) { if (std::isnan(value)) return QLatin1String("-"); - if (column->columnMode() == AbstractColumn::Integer) + switch (column->columnMode()) { + case AbstractColumn::ColumnMode::Integer: return QString::number((int)value); - else if (column->columnMode() == AbstractColumn::BigInt) + case AbstractColumn::ColumnMode::BigInt: return QString::number((qint64)value); - else + case AbstractColumn::ColumnMode::Text: + //TODO + case AbstractColumn::ColumnMode::DateTime: + //TODO + case AbstractColumn::ColumnMode::Day: + //TODO + case AbstractColumn::ColumnMode::Month: + //TODO + case AbstractColumn::ColumnMode::Numeric: return QString::number(value, 'f'); + } + + return QString(); } void StatisticsDialog::currentTabChanged(int index) { WAIT_CURSOR; const Column::ColumnStatistics& statistics = m_columns[index]->statistics(); RESET_CURSOR; auto* const textEdit = static_cast(m_twStatistics->currentWidget()); textEdit->setHtml(m_htmlText.arg(QString::number(statistics.size), isNanValue(statistics.minimum == INFINITY ? NAN : statistics.minimum), isNanValue(statistics.maximum == -INFINITY ? NAN : statistics.maximum), isNanValue(statistics.arithmeticMean), isNanValue(statistics.geometricMean), isNanValue(statistics.harmonicMean), isNanValue(statistics.contraharmonicMean), modeValue(m_columns[index], statistics.mode), isNanValue(statistics.firstQuartile)). arg(isNanValue(statistics.median), isNanValue(statistics.thirdQuartile), isNanValue(statistics.trimean), isNanValue(statistics.variance), isNanValue(statistics.standardDeviation), isNanValue(statistics.meanDeviation), isNanValue(statistics.meanDeviationAroundMedian), isNanValue(statistics.medianDeviation), isNanValue(statistics.iqr)). arg(isNanValue(statistics.skewness), isNanValue(statistics.kurtosis), isNanValue(statistics.entropy))); }