diff --git a/src/backend/core/column/Column.cpp b/src/backend/core/column/Column.cpp index 50e21ca72..d5d1b4bd7 100644 --- a/src/backend/core/column/Column.cpp +++ b/src/backend/core/column/Column.cpp @@ -1,1297 +1,1308 @@ /*************************************************************************** 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/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/XYAnalysisCurve.h" extern "C" { #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); addChild(d->inputFilter()); addChild(d->outputFilter()); m_suppressDataChangedSignal = false; m_usedInActionGroup = new QActionGroup(this); connect(m_usedInActionGroup, &QActionGroup::triggered, this, &Column::navigateTo); } 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 explicitely 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); //add curves where the column is currently in use QVector curves = project()->children(AbstractAspect::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); } } if (firstAction) menu->insertSeparator(firstAction); menu->insertMenu(firstAction, usedInMenu); menu->insertSeparator(firstAction); return menu; } void Column::navigateTo(QAction* action) { project()->navigateTo(action->data().toString()); } /*! * */ void Column::setSuppressDataChangedSignal(bool b) { m_suppressDataChangedSignal = b; } /** * \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 other pointer to the column to copy * \param src_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 QStringList& Column::formulaVariableColumnPathes() const { - return d->formulaVariableColumnPathes(); +const QVector& Column::formulaVariableColumns() const { + return d->formulaVariableColumns(); } /** * \brief Sets the formula used to generate column values */ -void Column::setFormula(const QString& formula, const QStringList& variableNames, const QStringList& columnPathes) { - exec(new ColumnSetGlobalFormulaCmd(d, formula, variableNames, columnPathes)); +void Column::setFormula(const QString& formula, const QStringList& variableNames, const QVector& columns) { + exec(new ColumnSetGlobalFormulaCmd(d, formula, variableNames, columns)); +} + +/*! + * 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->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()"); d->statisticsAvailable = false; d->propertiesAvailable = false; 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? d->statisticsAvailable = false; d->propertiesAvailable = false; 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) { d->statisticsAvailable = false; d->propertiesAvailable = false; 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) { d->statisticsAvailable = false; d->propertiesAvailable = false; 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) { d->statisticsAvailable = false; d->propertiesAvailable = false; 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()) { d->statisticsAvailable = false; d->propertiesAvailable = false; 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()"); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; 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()) { d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; 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()"); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; 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()) { d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnReplaceIntegersCmd(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 { d->statistics = ColumnStatistics(); ColumnStatistics& statistics = d->statistics; // TODO: support other data types? auto* rowValues = reinterpret_cast*>(data()); size_t 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; rowData.reserve(rowValues->size()); for (int row = 0; row < rowValues->size(); ++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 += pow(val, 2.0); 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() < rowValues->size()) rowData.squeeze(); statistics.arithmeticMean = columnSum / notNanCount; statistics.geometricMean = pow(columnProduct, 1.0 / notNanCount); statistics.harmonicMean = notNanCount / columnSumNeg; statistics.contraharmonicMean = columnSumSquare / columnSum; double columnSumVariance = 0; double columnSumMeanDeviation = 0.0; double columnSumMedianDeviation = 0.0; double sumForCentralMoment_r3 = 0.0; double sumForCentralMoment_r4 = 0.0; 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; QVector absoluteMedianList; absoluteMedianList.reserve((int)notNanCount); absoluteMedianList.resize((int)notNanCount); int idx = 0; for (int row = 0; row < rowValues->size(); ++row) { val = rowValues->value(row); if (std::isnan(val) || isMasked(row) ) continue; columnSumVariance += pow(val - statistics.arithmeticMean, 2.0); sumForCentralMoment_r3 += pow(val - statistics.arithmeticMean, 3.0); sumForCentralMoment_r4 += pow(val - statistics.arithmeticMean, 4.0); columnSumMeanDeviation += fabs( val - statistics.arithmeticMean ); absoluteMedianList[idx] = fabs(val - statistics.median); columnSumMedianDeviation += absoluteMedianList[idx]; idx++; } statistics.meanDeviationAroundMedian = columnSumMedianDeviation / 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); statistics.skewness = centralMoment_r3 / pow(statistics.standardDeviation, 3.0); statistics.kurtosis = (centralMoment_r4 / pow(statistics.standardDeviation, 4.0)) - 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, \false otherwise. */ bool Column::hasValues() const { if (d->hasValuesAvailable) return d->hasValues; bool foundValues = false; if (columnMode() == AbstractColumn::Numeric) { for (int row = 0; row < rowCount(); ++row) { if (!std::isnan(valueAt(row))) { foundValues = true; break; } } } else if (columnMode() == AbstractColumn::Integer) { //integer column has always valid values foundValues = true; } else if (columnMode() == AbstractColumn::DateTime) { for (int row = 0; row < rowCount(); ++row) { if (dateTimeAt(row).isValid()) { foundValues = true; break; } } } d->hasValues = foundValues; d->hasValuesAvailable = true; return d->hasValues; } //TODO: support all data types /** * \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); } /* * 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(plotDesignation())); writer->writeAttribute("mode", QString::number(columnMode())); writer->writeAttribute("width", QString::number(width())); //save the formula used to generate column values, if available if (!formula().isEmpty() ) { writer->writeStartElement("formula"); 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 : formulaVariableColumnPathes()) - writer->writeTextElement("path", path); + for (const auto col : formulaVariableColumns()) + writer->writeTextElement("path", col->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: { 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: { 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::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: 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) { auto* data = new QVector(bytes.size()/(int)sizeof(double)); 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)) { auto* task = new DecodeColumnTask(d, content); QThreadPool::globalInstance()->start(task); } } } return !reader->error(); } /** * \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; 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(); } } } - setFormula(formula, variableNames, columnPathes); + //TODO: +// setFormula(formula, variableNames, columnPathes); + 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: { double value = str.toDouble(&ok); if (!ok) { reader->raiseError(i18n("invalid row value")); return false; } setValueAt(index, value); break; } case AbstractColumn::Integer: { int value = str.toInt(&ok); if (!ok) { reader->raiseError(i18n("invalid row value")); return false; } setIntegerAt(index, value); break; } case AbstractColumn::Text: setTextAt(index, str); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::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 * * 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::rowCount() const { return d->rowCount(); } /** * \brief Return the column plot designation */ AbstractColumn::PlotDesignation Column::plotDesignation() const { return d->plotDesignation(); } 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) { auto* input_filter = static_cast(d->inputFilter()); auto* output_filter = static_cast(d->outputFilter()); DEBUG("change format " << input_filter->format().toStdString() << " to " << output_filter->format().toStdString()); input_filter->setFormat(output_filter->format()); } emit aspectDescriptionChanged(this); // the icon for the type changed if (!m_suppressDataChangedSignal) emit dataChanged(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 \count elements is returned. * for \c count < 0, the minimum of the last \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 { ColumnMode mode = columnMode(); 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(); } switch (mode) { case Numeric: { auto* vec = static_cast*>(data()); for (int row = start; row < end; ++row) { const double val = vec->at(row); if (std::isnan(val)) continue; if (val < min) min = val; } break; } case Integer: { auto* vec = static_cast*>(data()); for (int row = start; row < end; ++row) { const int val = vec->at(row); if (val < min) min = val; } break; } case Text: break; case DateTime: { auto* vec = static_cast*>(data()); for (int row = start; row < end; ++row) { const qint64 val = vec->at(row).toMSecsSinceEpoch(); if (val < min) min = val; } break; } case Day: case Month: 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 \count elements is returned. * for \c count < 0, the maximum of the last \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 { ColumnMode mode = columnMode(); 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(); } switch (mode) { case Numeric: { auto* vec = static_cast*>(data()); for (int row = start; row < end; ++row) { const double val = vec->at(row); if (std::isnan(val)) continue; if (val > max) max = val; } break; } case Integer: { auto* vec = static_cast*>(data()); for (int row = start; row < end; ++row) { const int val = vec->at(row); if (val > max) max = val; } break; } case Text: break; case DateTime: { auto* vec = static_cast*>(data()); for (int row = start; row < end; ++row) { const qint64 val = vec->at(row).toMSecsSinceEpoch(); if (val > max) max = val; } break; } case Day: case Month: default: break; } } return max; } diff --git a/src/backend/core/column/Column.h b/src/backend/core/column/Column.h index 37999a19b..81149b40b 100644 --- a/src/backend/core/column/Column.h +++ b/src/backend/core/column/Column.h @@ -1,150 +1,153 @@ /*************************************************************************** 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 ColumnStringIO; class QActionGroup; class Column : public AbstractColumn { Q_OBJECT public: explicit Column(const QString& name, AbstractColumn::ColumnMode = AbstractColumn::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) : 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; void setPlotDesignation(AbstractColumn::PlotDesignation) override; bool isReadOnly() const override; int rowCount() 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 QStringList& variableColumnPathes); + void setFormula(const QString& formula, const QStringList& variableNames, const QVector& columns); QString formula() const; const QStringList& formulaVariableNames() const; - const QStringList& formulaVariableColumnPathes() const; + const QVector& formulaVariableColumns() 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; Properties properties() const override; double maximum(int count = 0) const override; double minimum(int count = 0) const override; void setChanged(); void setSuppressDataChangedSignal(const bool); void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; +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; QActionGroup* m_usedInActionGroup; ColumnPrivate* d; ColumnStringIO* m_string_io; signals: void requestProjectContextMenu(QMenu*); private slots: void navigateTo(QAction*); void handleFormatChange(); friend class ColumnPrivate; friend class ColumnStringIO; }; #endif diff --git a/src/backend/core/column/ColumnPrivate.cpp b/src/backend/core/column/ColumnPrivate.cpp index 7c4de30cc..f80e4b9cb 100644 --- a/src/backend/core/column/ColumnPrivate.cpp +++ b/src/backend/core/column/ColumnPrivate.cpp @@ -1,1343 +1,1387 @@ /*************************************************************************** File : ColumnPrivate.cpp Project : AbstractColumn Description : Private data class of Column -------------------------------------------------------------------- Copyright : (C) 2007-2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-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 "ColumnPrivate.h" #include "ColumnStringIO.h" #include "Column.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: m_input_filter = new String2DoubleFilter(); m_output_filter = new Double2StringFilter(); m_data = new QVector(); break; case AbstractColumn::Integer: m_input_filter = new String2IntegerFilter(); m_output_filter = new Integer2StringFilter(); m_data = new QVector(); break; case AbstractColumn::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); m_data = new QStringList(); break; case AbstractColumn::DateTime: m_input_filter = new String2DateTimeFilter(); m_output_filter = new DateTime2StringFilter(); m_data = new QVector(); break; case AbstractColumn::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: 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: 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: 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::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); break; case AbstractColumn::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: 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: 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: delete static_cast*>(m_data); break; case AbstractColumn::Integer: delete static_cast*>(m_data); break; case AbstractColumn::Text: delete static_cast*>(m_data); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::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: { disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::Numeric: break; case AbstractColumn::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::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: 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: 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: 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: { disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::Integer: break; case AbstractColumn::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: 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: 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: 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: 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::Text: { switch (mode) { case AbstractColumn::Text: break; case AbstractColumn::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: 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::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: 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: 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: { disconnect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: break; case AbstractColumn::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) filter = new Month2DoubleFilter(); else if (m_column_mode == AbstractColumn::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) filter = new Month2IntegerFilter(); else if (m_column_mode == AbstractColumn::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; } // switch(mode) break; } } // determine the new input and output filters switch (mode) { // new mode case AbstractColumn::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: 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::Text: new_in_filter = new SimpleCopyThroughFilter(); new_out_filter = new SimpleCopyThroughFilter(); break; case AbstractColumn::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: new_in_filter = new String2MonthFilter(); new_out_filter = new DateTime2StringFilter(); static_cast(new_out_filter)->setFormat("MMMM"); DEBUG(" Month out_filter format: " << static_cast(new_out_filter)->format().toStdString()); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::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: disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::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: connect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: connect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::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; 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: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } case AbstractColumn::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: { 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 other pointer to the column to copy * \param src_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: { 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: { 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::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: 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: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } case AbstractColumn::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: 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 other pointer to the column to copy * \param src_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: { 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: { 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::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: for (int i = 0; i *>(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 Return the data vector size * * 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::rowCount() const { switch (m_column_mode) { case AbstractColumn::Numeric: return static_cast*>(m_data)->size(); case AbstractColumn::Integer: return static_cast*>(m_data)->size(); case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: return static_cast*>(m_data)->size(); case AbstractColumn::Text: return static_cast*>(m_data)->size(); } return 0; } /** * \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(); DEBUG("ColumnPrivate::resizeTo() " << old_size << " -> " << new_size); if (new_size == old_size) return; switch (m_column_mode) { case AbstractColumn::Numeric: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, NAN); break; } case AbstractColumn::Integer: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, 0); break; } case AbstractColumn::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: { 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: static_cast*>(m_data)->insert(before, count, NAN); break; case AbstractColumn::Integer: static_cast*>(m_data)->insert(before, count, 0); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < count; ++i) static_cast*>(m_data)->insert(before, QDateTime()); break; case AbstractColumn::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: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::Integer: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < corrected_count; ++i) static_cast*>(m_data)->removeAt(first); break; case AbstractColumn::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; } /** * \brief Sets the formula used to generate column values */ -void ColumnPrivate::setFormula(const QString& formula, const QStringList& variableNames, const QStringList& variableColumnPathes) { +void ColumnPrivate::setFormula(const QString& formula, const QStringList& variableNames, const QVector& variableColumns) { m_formula = formula; m_formulaVariableNames = variableNames; - m_formulaVariableColumnPathes = variableColumnPathes; -} + m_formulaVariableColumns = variableColumns; -/** - * \brief Return the formula associated with row 'row' - */ -QString ColumnPrivate::formula(int row) const { - return m_formulas.value(row); + disconnect(m_owner, &Column::updateFormula, nullptr, nullptr); + QVector columns; + for (auto column : variableColumns) + connect(column, &Column::dataChanged, m_owner, &Column::updateFormula); } const QStringList& ColumnPrivate::formulaVariableNames() const { return m_formulaVariableNames; } -const QStringList& ColumnPrivate::formulaVariableColumnPathes() const { - return m_formulaVariableColumnPathes; +const QVector& ColumnPrivate::formulaVariableColumns() const { + return m_formulaVariableColumns; +} + +/*! + * \sa FunctionValuesDialog::generate() + */ +void ColumnPrivate::updateFormula() { + //determine variable names and the data vectors of the specified columns + QVector*> xVectors; + QVector*> xNewVectors; + + for (auto column : m_formulaVariableColumns) { + if (column->columnMode() == AbstractColumn::Integer) { + //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()); + } + + //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()); + for (auto& d : new_data) + d = 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); + + //delete help vectors created for the conversion from int to double + for (auto* vector : xNewVectors) + delete vector; +} + +/** + * \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(Interval i, QString formula) { m_formulas.setValue(i, formula); } /** * \brief Overloaded function for convenience */ void ColumnPrivate::setFormula(int row, 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(); 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) 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) 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) return QDateTime(); return static_cast*>(m_data)->value(row); } /** * \brief Return the double value in row 'row' for columns with type Numeric and Integer. * This function has to be used everywhere where the exact type (double or int) is not relevant for numerical calculations. * For cases where the integer value is needed without any implicit conversions, \sa intergAt() has to be used. */ double ColumnPrivate::valueAt(int row) const { if (m_column_mode == AbstractColumn::Numeric) return static_cast*>(m_data)->value(row, NAN); else if (m_column_mode == AbstractColumn::Integer) 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; return static_cast*>(m_data)->value(row, 0); } /** * \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; 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; 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) 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) 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) return; 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) return; 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; 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; 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; 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; 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); } /*! * Updates the properties. Will be called, when data in the column changed. * The properies 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 prevValueDatetime = 0; if (m_column_mode == AbstractColumn::Integer) prevValueInt = integerAt(0); else if (m_column_mode == AbstractColumn::Numeric) prevValue = valueAt(0); else if (m_column_mode == AbstractColumn::DateTime || m_column_mode == AbstractColumn::Month || m_column_mode == AbstractColumn::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 valueDateTime; for (int row = 1; row < rowCount(); row++) { if (m_column_mode == AbstractColumn::Integer) { valueInt = integerAt(row); // check monotonic increasing if (valueInt >= prevValueInt && monotonic_increasing < 0) monotonic_increasing = 1; else if (valueInt < prevValueInt && monotonic_increasing >= 0) monotonic_increasing = 0; // else: nothing // check monotonic decreasing if (valueInt <= prevValueInt && monotonic_decreasing < 0) monotonic_decreasing = 1; else if (valueInt > prevValueInt && monotonic_decreasing >= 0) monotonic_decreasing = 0; prevValueInt = valueInt; } else if (m_column_mode == AbstractColumn::Numeric) { value = valueAt(row); // check monotonic increasing if (value >= prevValue && monotonic_increasing < 0) monotonic_increasing = 1; else if (value < prevValue) monotonic_increasing = 0; // else: nothing // check monotonic decreasing if (value <= prevValue && monotonic_decreasing < 0) monotonic_decreasing = 1; else if (value > prevValue) monotonic_decreasing = 0; prevValue = value; } else if (m_column_mode == AbstractColumn::DateTime || m_column_mode == AbstractColumn::Month || m_column_mode == AbstractColumn::Day) { valueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); // check monotonic increasing if (valueDateTime >= prevValueDatetime && monotonic_increasing < 0) monotonic_increasing = 1; else if (valueDateTime < prevValueDatetime) monotonic_increasing = 0; // else: nothing // check monotonic decreasing if (valueDateTime <= prevValueDatetime && monotonic_decreasing < 0) monotonic_decreasing = 1; else if (valueDateTime > prevValueDatetime) monotonic_decreasing = 0; 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/ColumnPrivate.h b/src/backend/core/column/ColumnPrivate.h index f19730a80..23dce681f 100644 --- a/src/backend/core/column/ColumnPrivate.h +++ b/src/backend/core/column/ColumnPrivate.h @@ -1,132 +1,137 @@ /*************************************************************************** File : ColumnPrivate.h Project : LabPlot Description : Private data class of Column -------------------------------------------------------------------- Copyright : (C) 2007,2008 Tilman Benkert (thzs@gmx.net) 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 * * * ***************************************************************************/ #ifndef COLUMNPRIVATE_H #define COLUMNPRIVATE_H #include "backend/core/AbstractColumn.h" #include "backend/lib/IntervalAttribute.h" class Column; class ColumnPrivate : QObject { Q_OBJECT public: ColumnPrivate(Column*, AbstractColumn::ColumnMode); ~ColumnPrivate() override; ColumnPrivate(Column*, AbstractColumn::ColumnMode, void*); AbstractColumn::ColumnMode columnMode() const; void setColumnMode(AbstractColumn::ColumnMode); bool copy(const AbstractColumn*); bool copy(const AbstractColumn*, int source_start, int dest_start, int num_rows); bool copy(const ColumnPrivate*); bool copy(const ColumnPrivate*, int source_start, int dest_start, int num_rows); int rowCount() const; void resizeTo(int); void insertRows(int before, int count); void removeRows(int first, int count); QString name() const; AbstractColumn::PlotDesignation plotDesignation() const; void setPlotDesignation(AbstractColumn::PlotDesignation); int width() const; void setWidth(int); void* data() const; AbstractSimpleFilter* inputFilter() const; AbstractSimpleFilter* outputFilter() const; void replaceModeData(AbstractColumn::ColumnMode, void* data, AbstractSimpleFilter *in, AbstractSimpleFilter *out); void replaceData(void*); IntervalAttribute formulaAttribute() const; void replaceFormulas(const IntervalAttribute& formulas); + //global formula defined for the whole column QString formula() const; const QStringList& formulaVariableNames() const; - const QStringList& formulaVariableColumnPathes() const; - void setFormula(const QString& formula, const QStringList& variableNames, const QStringList& variableColumnPathes); + const QVector& formulaVariableColumns() const; + void setFormula(const QString& formula, const QStringList& variableNames, const QVector& variableColumns); + void updateFormula(); + + //cell formulas QString formula(int row) const; QVector< Interval > formulaIntervals() const; void setFormula(Interval i, QString formula); void setFormula(int row, QString formula); void clearFormulas(); QString textAt(int row) const; void setTextAt(int row, const QString&); void replaceTexts(int first, const QVector&); QDate dateAt(int row) const; void setDateAt(int row, QDate); QTime timeAt(int row) const; void setTimeAt(int row, QTime); QDateTime dateTimeAt(int row) const; void setDateTimeAt(int row, const QDateTime&); void replaceDateTimes(int first, const QVector&); double valueAt(int row) const; void setValueAt(int row, double new_value); void replaceValues(int first, const QVector&); int integerAt(int row) const; void setIntegerAt(int row, int new_value); void replaceInteger(int first, const QVector&); void updateProperties(); mutable AbstractColumn::ColumnStatistics statistics; bool statisticsAvailable{false}; //is 'statistics' already available or needs to be (re-)calculated? bool hasValues{false}; bool hasValuesAvailable{false}; //is 'hasValues' already available or needs to be (re-)calculated? mutable bool propertiesAvailable{false}; //is 'properties' already available (true) or needs to be (re-)calculated (false)? mutable AbstractColumn::Properties properties{AbstractColumn::Properties::No}; // declares the properties of the curve (monotonic increasing/decreasing ...). Speed up algorithms + private: AbstractColumn::ColumnMode m_column_mode; // type of column data void* m_data; //pointer to the data container (QVector) AbstractSimpleFilter* m_input_filter; //input filter for string -> data type conversion AbstractSimpleFilter* m_output_filter; //output filter for data type -> string conversion QString m_formula; QStringList m_formulaVariableNames; - QStringList m_formulaVariableColumnPathes; + QVector m_formulaVariableColumns; IntervalAttribute m_formulas; AbstractColumn::PlotDesignation m_plot_designation{AbstractColumn::NoDesignation}; int m_width{0}; //column width in the view Column* m_owner; }; #endif diff --git a/src/backend/core/column/columncommands.cpp b/src/backend/core/column/columncommands.cpp index cd46b58a8..9e86d5f7a 100644 --- a/src/backend/core/column/columncommands.cpp +++ b/src/backend/core/column/columncommands.cpp @@ -1,1190 +1,1190 @@ /*************************************************************************** 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 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 #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: delete static_cast*>(m_new_data); break; case AbstractColumn::Integer: delete static_cast*>(m_new_data); break; case AbstractColumn::Text: delete static_cast*>(m_new_data); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: delete static_cast*>(m_new_data); break; } } else { if (m_new_data != m_old_data) switch (m_old_mode) { case AbstractColumn::Numeric: delete static_cast*>(m_old_data); break; case AbstractColumn::Integer: delete static_cast*>(m_old_data); break; case AbstractColumn::Text: delete static_cast*>(m_old_data); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::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: delete static_cast*>(m_empty_data); break; case AbstractColumn::Integer: delete static_cast*>(m_empty_data); break; case AbstractColumn::Text: delete static_cast*>(m_empty_data); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: delete static_cast*>(m_empty_data); break; } } else { if (!m_data) return; switch (m_col->columnMode()) { case AbstractColumn::Numeric: delete static_cast*>(m_data); break; case AbstractColumn::Integer: delete static_cast*>(m_data); break; case AbstractColumn::Text: delete static_cast*>(m_data); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::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: { auto* vec = new QVector(rowCount); m_empty_data = vec; for (int i = 0; i < rowCount; ++i) vec->operator[](i) = NAN; break; } case AbstractColumn::Integer: { 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: m_empty_data = new QVector(); for (int i = 0; i < rowCount; ++i) static_cast< QVector*>(m_empty_data)->append(QDateTime()); break; case AbstractColumn::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, QStringList variableColumns) - : QUndoCommand(), m_col(col), m_newFormula(std::move(formula)), m_newVariableNames(std::move(variableNames)), m_newVariableColumnPathes(std::move(variableColumns)) { +ColumnSetGlobalFormulaCmd::ColumnSetGlobalFormulaCmd(ColumnPrivate* col, QString formula, QStringList variableNames, QVector variableColumns) + : QUndoCommand(), m_col(col), m_newFormula(std::move(formula)), m_newVariableNames(std::move(variableNames)), m_newVariableColumns(std::move(variableColumns)) { setText(i18n("%1: set formula", col->name())); } void ColumnSetGlobalFormulaCmd::redo() { if (!m_copied) { m_formula = m_col->formula(); m_variableNames = m_col->formulaVariableNames(); - m_variableColumnPathes = m_col->formulaVariableColumnPathes(); + m_variableColumns = m_col->formulaVariableColumns(); m_copied = true; } - m_col->setFormula(m_newFormula, m_newVariableNames, m_newVariableColumnPathes); + m_col->setFormula(m_newFormula, m_newVariableNames, m_newVariableColumns); } void ColumnSetGlobalFormulaCmd::undo() { - m_col->setFormula(m_formula, m_variableNames, m_variableColumnPathes); + m_col->setFormula(m_formula, m_variableNames, m_variableColumns); } /** *************************************************************************** * \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 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 ColumnReplaceIntegersCmd * \brief Replace a range of integers in a int column ** ***************************************************************************/ ColumnReplaceIntegersCmd::ColumnReplaceIntegersCmd(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 ColumnReplaceIntegersCmd::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 ColumnReplaceIntegersCmd::undo() { m_col->replaceInteger(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 21d618951..616e1780f 100644 --- a/src/backend/core/column/columncommands.h +++ b/src/backend/core/column/columncommands.h @@ -1,331 +1,331 @@ /*************************************************************************** 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::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; }; 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, QStringList variableColumnPathes); + explicit ColumnSetGlobalFormulaCmd(ColumnPrivate* col, QString formula, QStringList variableNames, QVector columns); void redo() override; void undo() override; private: ColumnPrivate* m_col; QString m_formula; QStringList m_variableNames; - QStringList m_variableColumnPathes; + QVector m_variableColumns; QString m_newFormula; QStringList m_newVariableNames; - QStringList m_newVariableColumnPathes; + QVector m_newVariableColumns; 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 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 ColumnReplaceIntegersCmd : public QUndoCommand { public: explicit ColumnReplaceIntegersCmd(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/kdefrontend/spreadsheet/FunctionValuesDialog.cpp b/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp index c94286bb5..0020d0e41 100644 --- a/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp +++ b/src/kdefrontend/spreadsheet/FunctionValuesDialog.cpp @@ -1,389 +1,389 @@ /*************************************************************************** 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/gsl/ExpressionParser.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.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")); 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, SIGNAL(pressed()), this, SLOT(addVariable()) ); connect( ui.teEquation, SIGNAL(expressionChanged()), this, SLOT(checkValues()) ); connect( ui.tbConstants, SIGNAL(clicked()), this, SLOT(showConstants()) ); connect( ui.tbFunctions, SIGNAL(clicked()), this, SLOT(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(0, 0).expandedTo(minimumSize())); } FunctionValuesDialog::~FunctionValuesDialog() { KConfigGroup conf(KSharedConfig::openConfig(), "FunctionValuesDialog"); KWindowConfig::saveWindowSize(windowHandle(), conf); } void FunctionValuesDialog::setColumns(QVector columns) { m_columns = columns; ui.teEquation->setPlainText(m_columns.first()->formula()); 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 QStringList& columnPathes = m_columns.first()->formulaVariableColumnPathes(); + const QVector& variableColumns = m_columns.first()->formulaVariableColumns(); //add all available variables and select the corresponding columns const QVector cols = m_spreadsheet->project()->children(AspectType::Column, AbstractAspect::Recursive); for (int i = 0; i < variableNames.size(); ++i) { addVariable(); m_variableNames[i]->setText(variableNames.at(i)); - for (const auto* aspect : cols) { - if (aspect->path() == columnPathes.at(i)) { - const auto* column = dynamic_cast(aspect); + for (const auto* col : cols) { + if (col == variableColumns.at(i)) { + const auto* column = dynamic_cast(col); if (column) m_variableDataColumns[i]->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(column)); else m_variableDataColumns[i]->setCurrentModelIndex(QModelIndex()); break; } } } } checkValues(); } /*! 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; } /* 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, SIGNAL(constantSelected(QString)), this, SLOT(insertConstant(QString))); connect(&constants, SIGNAL(constantSelected(QString)), &menu, SLOT(close())); connect(&constants, SIGNAL(canceled()), &menu, SLOT(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, SIGNAL(functionSelected(QString)), this, SLOT(insertFunction(QString))); connect(&functions, SIGNAL(functionSelected(QString)), &menu, SLOT(close())); connect(&functions, SIGNAL(canceled()), &menu, SLOT(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 = dynamic_cast(ui.frameVariables->layout()); int row = m_variableNames.size(); //text field for the variable name auto* le = new QLineEdit(); le->setMaximumWidth(30); connect(le, SIGNAL(textChanged(QString)), this, SLOT(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, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(checkValues()) ); layout->addWidget(cb, row, 2, 1, 1); m_variableDataColumns << cb; cb->setTopLevelClasses(m_topLevelClasses); cb->setModel(m_aspectTreeModel.get()); cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(m_spreadsheet->column(0))); //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); } 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; - QStringList columnPathes; + QVector columns; QVector*> xVectors; QVector*> xNewVectors; int maxRowCount = m_spreadsheet->rowCount(); 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); - columnPathes << column->path(); + columns << column; if (column->columnMode() == AbstractColumn::Integer) { //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(); } //resize the spreadsheet if one of the data vectors from other spreadsheet(s) has more elements then the current spreadsheet. if (m_spreadsheet->rowCount() < maxRowCount) m_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(maxRowCount); for (auto& d : new_data) d = NAN; //evaluate the expression for f(x_1, x_2, ...) and write the calculated values into a new vector. ExpressionParser* parser = ExpressionParser::getInstance(); const QString& expression = ui.teEquation->toPlainText(); parser->evaluateCartesian(expression, variableNames, xVectors, &new_data); //set the new values and store the expression, variable names and the used data columns for (auto* col : m_columns) { if (col->columnMode() != AbstractColumn::Numeric) col->setColumnMode(AbstractColumn::Numeric); - col->setFormula(expression, variableNames, columnPathes); + col->setFormula(expression, variableNames, columns); col->replaceValues(0, new_data); } m_spreadsheet->endMacro(); //delete help vectors created for the conversion from int to double for (auto* vector : xNewVectors) delete vector; RESET_CURSOR; }