diff --git a/src/backend/core/AbstractColumn.cpp b/src/backend/core/AbstractColumn.cpp index 4cafdf0c5..430650ee0 100644 --- a/src/backend/core/AbstractColumn.cpp +++ b/src/backend/core/AbstractColumn.cpp @@ -1,696 +1,695 @@ /*************************************************************************** File : AbstractColumn.cpp Project : LabPlot Description : Interface definition for data with column logic -------------------------------------------------------------------- Copyright : (C) 2007,2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/core/AbstractColumn.h" #include "backend/core/AbstractColumnPrivate.h" #include "backend/core/abstractcolumncommands.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/SignallingUndoCommand.h" #include #include #include #include /** * \class AbstractColumn * \brief Interface definition for data with column logic * * This is an abstract base class for column-based data, * i.e. mathematically a vector or technically a 1D array or list. * It only defines the interface but has no data members itself. * * Classes derived from this are typically table columns or outputs * of filters which can be chained between table columns and plots. * From the point of view of the plot functions there will be no difference * between a table column and a filter output since both use this interface. * * Classes derived from this will either store a * vector with entries of one certain data type, e.g. double, QString, * QDateTime, or generate such values on demand. To determine the data * type of a class derived from this, use the columnMode() function. * AbstractColumn defines all access functions for all supported data * types but only those corresponding to the return value of columnMode() * will return a meaningful value. Calling functions not belonging to * the data type of the column is safe, but will do nothing (writing * function) or return some default value (reading functions). * * This class also defines all signals which indicate a data change. * Any class whose output values are subject to change over time must emit * the according signals. These signals notify any object working with the * column before and after a change of the column. * In some cases it will be necessary for a class using * the column to connect aboutToBeDestroyed(), to react * to a column's deletion, e.g. a filter's reaction to a * table deletion. * * All writing functions have a "do nothing" standard implementation to * make deriving a read-only class very easy without bothering about the * writing interface. */ /** * \brief Ctor * * \param name the column name (= aspect name) */ AbstractColumn::AbstractColumn(const QString &name) : AbstractAspect(name), m_abstract_column_private( new AbstractColumnPrivate(this) ) { - } AbstractColumn::~AbstractColumn() { emit aboutToBeDestroyed(this); delete m_abstract_column_private; } QStringList AbstractColumn::dateFormats() { QStringList dates; dates << "yyyy-MM-dd"; dates << "yyyy/MM/dd"; dates << "dd/MM/yyyy"; dates << "dd/MM/yy"; dates << "dd.MM.yyyy"; dates << "dd.MM.yy"; dates << "MM/yyyy"; dates << "dd.MM."; dates << "yyyyMMdd"; return dates; } QStringList AbstractColumn::timeFormats() { QStringList times; times << "hh"; times << "hh ap"; times << "hh:mm"; times << "hh:mm ap"; times << "hh:mm:ss"; times << "hh:mm:ss.zzz"; times << "hh:mm:ss:zzz"; times << "mm:ss.zzz"; times << "hhmmss"; return times; } QStringList AbstractColumn::dateTimeFormats() { QStringList dateTimes; // any combination of date and times for (auto d: dateFormats()) dateTimes << d; for (auto t: timeFormats()) dateTimes << t; for (auto d: dateFormats()) for (auto t: timeFormats()) dateTimes << d + ' ' + t; return dateTimes; } /** * \brief Convenience method for mode-dependent icon */ QIcon AbstractColumn::iconForMode(ColumnMode mode) { switch (mode) { case AbstractColumn::Numeric: - return QIcon::fromTheme("x-shape-text"); + break; case AbstractColumn::Text: return QIcon::fromTheme("draw-text"); - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: - return QIcon::fromTheme("chronometer"); + case AbstractColumn::DateTime: + case AbstractColumn::Month: + case AbstractColumn::Day: + return QIcon::fromTheme("chronometer"); } return QIcon::fromTheme("x-shape-text"); } /** * \fn bool AbstractColumn::isReadOnly() const * \brief Return whether the object is read-only */ /** * \fn AbstractColumn::ColumnMode AbstractColumn::columnMode() const * \brief Return the column mode * * This function is most used by tables but can also be used * by plots. The column mode specifies how to interpret * the values in the column additional to the data type. */ /** * \brief Set the column mode * * This sets the column mode and, if * necessary, converts it to another datatype. */ void AbstractColumn::setColumnMode(AbstractColumn::ColumnMode) {} /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool AbstractColumn::copy(const AbstractColumn *other) { Q_UNUSED(other) return false; } /** * \brief Copies part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param destination_start first row to copy in * \param num_rows the number of rows to copy */ bool AbstractColumn::copy(const AbstractColumn *source, int source_start, int destination_start, int num_rows) { Q_UNUSED(source) Q_UNUSED(source_start) Q_UNUSED(destination_start) Q_UNUSED(num_rows) return false; } /** * \fn int AbstractColumn::rowCount() const * \brief Return the data vector size */ /** * \brief Insert some empty (or initialized with invalid values) rows */ void AbstractColumn::insertRows(int before, int count) { beginMacro( i18np("%1: insert 1 row", "%1: insert %2 rows", name(), count) ); exec(new SignallingUndoCommand("pre-signal", this, "rowsAboutToBeInserted", "rowsRemoved", Q_ARG(const AbstractColumn*,this), Q_ARG(int,before), Q_ARG(int,count))); handleRowInsertion(before, count); exec(new SignallingUndoCommand("post-signal", this, "rowsInserted", "rowsAboutToBeRemoved", Q_ARG(const AbstractColumn*,this), Q_ARG(int,before), Q_ARG(int,count))); endMacro(); } void AbstractColumn::handleRowInsertion(int before, int count) { exec(new AbstractColumnInsertRowsCmd(this, before, count)); } /** * \brief Remove 'count' rows starting from row 'first' */ void AbstractColumn::removeRows(int first, int count) { beginMacro( i18np("%1: remove 1 row", "%1: remove %2 rows", name(), count) ); exec(new SignallingUndoCommand("change signal", this, "rowsAboutToBeRemoved", "rowsInserted", Q_ARG(const AbstractColumn*,this), Q_ARG(int,first), Q_ARG(int,count))); handleRowRemoval(first, count); exec(new SignallingUndoCommand("change signal", this, "rowsRemoved", "rowsAboutToBeInserted", Q_ARG(const AbstractColumn*,this), Q_ARG(int,first), Q_ARG(int,count))); endMacro(); } void AbstractColumn::handleRowRemoval(int first, int count) { exec(new AbstractColumnRemoveRowsCmd(this, first, count)); } /** * \fn AbstractColumn::PlotDesignation AbstractColumn::plotDesignation() const * \brief Return the column plot designation */ /** * \brief Set the column plot designation */ void AbstractColumn::setPlotDesignation(AbstractColumn::PlotDesignation pd) { Q_UNUSED(pd) } /** * \brief Clear the whole column */ void AbstractColumn::clear() {} /** * \brief Convenience method for mode-independent testing of validity */ bool AbstractColumn::isValid(int row) const { switch (columnMode()) { - case AbstractColumn::Numeric: - return !std::isnan(valueAt(row)); - case AbstractColumn::Text: - return !textAt(row).isNull(); - case AbstractColumn::DateTime: - case AbstractColumn::Month: - case AbstractColumn::Day: - return dateTimeAt(row).isValid(); + case AbstractColumn::Numeric: + return !std::isnan(valueAt(row)); + case AbstractColumn::Text: + return !textAt(row).isNull(); + case AbstractColumn::DateTime: + case AbstractColumn::Month: + case AbstractColumn::Day: + return dateTimeAt(row).isValid(); } return false; } //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name IntervalAttribute related functions //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Return whether a certain row is masked */ bool AbstractColumn::isMasked(int row) const { return m_abstract_column_private->m_masking.isSet(row); } /** * \brief Return whether a certain interval of rows is fully masked */ bool AbstractColumn::isMasked(Interval i) const { return m_abstract_column_private->m_masking.isSet(i); } /** * \brief Return all intervals of masked rows */ QList< Interval > AbstractColumn::maskedIntervals() const { return m_abstract_column_private->m_masking.intervals(); } /** * \brief Clear all masking information */ void AbstractColumn::clearMasks() { exec(new AbstractColumnClearMasksCmd(m_abstract_column_private), "maskingAboutToChange", "maskingChanged", Q_ARG(const AbstractColumn*,this)); } /** * \brief Set an interval masked * * \param i the interval * \param mask true: mask, false: unmask */ void AbstractColumn::setMasked(Interval i, bool mask) { exec(new AbstractColumnSetMaskedCmd(m_abstract_column_private, i, mask), "maskingAboutToChange", "maskingChanged", Q_ARG(const AbstractColumn*,this)); } /** * \brief Overloaded function for convenience */ void AbstractColumn::setMasked(int row, bool mask) { setMasked(Interval(row,row), mask); } //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name Formula related functions //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Return the formula associated with row 'row' */ QString AbstractColumn::formula(int row) const { Q_UNUSED(row); return QString(); } /** * \brief Return the intervals that have associated formulas * * This can be used to make a list of formulas with their intervals. * Here is some example code: * * \code * QStringList list; * QList< Interval > intervals = my_column.formulaIntervals(); * foreach(Interval interval, intervals) * list << QString(interval.toString() + ": " + my_column.formula(interval.start())); * \endcode */ QList< Interval > AbstractColumn::formulaIntervals() const { return QList< Interval >(); } /** * \brief Set a formula string for an interval of rows */ void AbstractColumn::setFormula(Interval i, QString formula) { Q_UNUSED(i) Q_UNUSED(formula) } /** * \brief Overloaded function for convenience */ void AbstractColumn::setFormula(int row, QString formula) { Q_UNUSED(row) Q_UNUSED(formula) } /** * \brief Clear all formulas */ void AbstractColumn::clearFormulas() {}; //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name type specific functions //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Return the content of row 'row'. * * Use this only when columnMode() is Text */ QString AbstractColumn::textAt(int row) const { Q_UNUSED(row); return ""; } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Text */ void AbstractColumn::setTextAt(int row, const QString& new_value) { Q_UNUSED(row) Q_UNUSED(new_value) } /** * \brief Replace a range of values * * Use this only when columnMode() is Text */ void AbstractColumn::replaceTexts(int first, const QVector& new_values) { Q_UNUSED(first) Q_UNUSED(new_values) }; /** * \brief Return the date part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDate AbstractColumn::dateAt(int row) const { Q_UNUSED(row); return QDate(); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void AbstractColumn::setDateAt(int row, const QDate& new_value) { Q_UNUSED(row) Q_UNUSED(new_value) }; /** * \brief Return the time part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QTime AbstractColumn::timeAt(int row) const { Q_UNUSED(row); return QTime(); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void AbstractColumn::setTimeAt(int row, const QTime& new_value) { Q_UNUSED(row) Q_UNUSED(new_value) } /** * \brief Return the QDateTime in row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDateTime AbstractColumn::dateTimeAt(int row) const { Q_UNUSED(row); return QDateTime(); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void AbstractColumn::setDateTimeAt(int row, const QDateTime& new_value) { Q_UNUSED(row) Q_UNUSED(new_value) }; /** * \brief Replace a range of values * * Use this only when columnMode() is DateTime, Month or Day */ void AbstractColumn::replaceDateTimes(int first, const QVector& new_values) { Q_UNUSED(first) Q_UNUSED(new_values) }; /** * \brief Return the double value in row 'row' * * Use this only when columnMode() is Numeric */ double AbstractColumn::valueAt(int row) const { Q_UNUSED(row); return NAN; } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Numeric */ void AbstractColumn::setValueAt(int row, double new_value) { Q_UNUSED(row) Q_UNUSED(new_value) }; /** * \brief Replace a range of values * * Use this only when columnMode() is Numeric */ void AbstractColumn::replaceValues(int first, const QVector& new_values) { Q_UNUSED(first) Q_UNUSED(new_values) } double AbstractColumn::minimum() const{ double val; double min = INFINITY; for (int row = 0; row < rowCount(); row++) { val = valueAt(row); if (std::isnan(val)) continue; if (val < min) min = val; } return min; } double AbstractColumn::maximum() const{ double val; double max = -INFINITY; for (int row = 0; row < rowCount(); row++) { val = valueAt(row); if (std::isnan(val)) continue; if (val > max) max = val; } return max; } //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \fn void AbstractColumn::plotDesignationAboutToChange(const AbstractColumn *source) * \brief Column plot designation will be changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::plotDesignationChanged(const AbstractColumn *source) * \brief Column plot designation changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::modeAboutToChange(const AbstractColumn *source) * \brief Column mode (possibly also the data type) will be changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::modeChanged(const AbstractColumn *source) * \brief Column mode (possibly also the data type) changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::dataAboutToChange(const AbstractColumn *source) * \brief Data of the column will be changed * * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::dataChanged(const AbstractColumn *source) * \brief Data of the column has changed * * Important: When data has changed also the number * of rows in the column may have changed without * any other signal emission. * 'source' is always the this pointer of the column that * emitted this signal. This way it's easier to use * one handler for lots of columns. */ /** * \fn void AbstractColumn::rowsAboutToBeInserted(const AbstractColumn *source, int before, int count) * \brief Rows will be inserted * * \param source the column that emitted the signal * \param before the row to insert before * \param count the number of rows to be inserted */ /** * \fn void AbstractColumn::rowsInserted(const AbstractColumn *source, int before, int count) * \brief Rows have been inserted * * \param source the column that emitted the signal * \param before the row to insert before * \param count the number of rows to be inserted */ /** * \fn void AbstractColumn::rowsAboutToBeRemoved(const AbstractColumn *source, int first, int count) * \brief Rows will be deleted * * \param source the column that emitted the signal * \param first the first row to be deleted * \param count the number of rows to be deleted */ /** * \fn void AbstractColumn::rowsRemoved(const AbstractColumn *source, int first, int count) * \brief Rows have been deleted * * \param source the column that emitted the signal * \param first the first row that was deleted * \param count the number of deleted rows */ /** * \fn void AbstractColumn::maskingAboutToChange(const AbstractColumn *source) * \brief Rows are about to be masked or unmasked */ /** * \fn void AbstractColumn::maskingChanged(const AbstractColumn *source) * \brief Rows have been masked or unmasked */ /** * \fn void AbstractColumn::aboutToBeDestroyed(const AbstractColumn *source) * \brief Emitted shortl before this column is deleted * * \param source the object emitting this signal * * This is needed by AbstractFilter. */ /** * \brief Read XML mask element */ bool AbstractColumn::XmlReadMask(XmlStreamReader *reader) { Q_ASSERT(reader->isStartElement() && reader->name() == "mask"); bool ok1, ok2; int start, end; start = reader->readAttributeInt("start_row", &ok1); end = reader->readAttributeInt("end_row", &ok2); if(!ok1 || !ok2) { reader->raiseError(i18n("invalid or missing start or end row")); return false; } setMasked(Interval(start,end)); if (!reader->skipToEndElement()) return false; return true; } /** * \brief Write XML mask element */ void AbstractColumn::XmlWriteMask(QXmlStreamWriter *writer) const { foreach (const Interval& interval, maskedIntervals()) { writer->writeStartElement("mask"); writer->writeAttribute("start_row", QString::number(interval.start())); writer->writeAttribute("end_row", QString::number(interval.end())); writer->writeEndElement(); } } diff --git a/src/backend/core/abstractcolumncommands.h b/src/backend/core/abstractcolumncommands.h index 5bff4848a..c2554c8c7 100644 --- a/src/backend/core/abstractcolumncommands.h +++ b/src/backend/core/abstractcolumncommands.h @@ -1,100 +1,95 @@ /*************************************************************************** File : abstractcolumncommands.h Project : LabPlot Description : Commands to be called by AbstractColumn to modify AbstractColumnPrivate -------------------------------------------------------------------- Copyright : (C) 2007-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2010 Knut Franke (knut.franke@gmx.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 ABSTRACTCOLUMNCOMMANDS_H #define ABSTRACTCOLUMNCOMMANDS_H #include "AbstractColumnPrivate.h" #include class AbstractColumnClearMasksCmd : public QUndoCommand { public: explicit AbstractColumnClearMasksCmd(AbstractColumnPrivate *col, QUndoCommand *parent = 0); ~AbstractColumnClearMasksCmd(); virtual void redo(); virtual void undo(); private: AbstractColumnPrivate *m_col; IntervalAttribute m_masking; bool m_copied; - }; -class AbstractColumnSetMaskedCmd : public QUndoCommand -{ +class AbstractColumnSetMaskedCmd : public QUndoCommand { public: explicit AbstractColumnSetMaskedCmd(AbstractColumnPrivate * col, Interval interval, bool masked, QUndoCommand * parent = 0 ); ~AbstractColumnSetMaskedCmd(); virtual void redo(); virtual void undo(); private: AbstractColumnPrivate * m_col; Interval m_interval; bool m_masked; IntervalAttribute m_masking; bool m_copied; - }; -class AbstractColumnInsertRowsCmd : public QUndoCommand -{ +class AbstractColumnInsertRowsCmd : public QUndoCommand { public: explicit AbstractColumnInsertRowsCmd(AbstractColumn * col, int before, int count, QUndoCommand * parent = 0 ); ~AbstractColumnInsertRowsCmd(); virtual void redo(); virtual void undo(); protected: AbstractColumnPrivate * m_col; int m_before; int m_count; }; -class AbstractColumnRemoveRowsCmd : public QUndoCommand -{ +class AbstractColumnRemoveRowsCmd : public QUndoCommand { public: explicit AbstractColumnRemoveRowsCmd(AbstractColumn * col, int first, int count, QUndoCommand * parent = 0 ); ~AbstractColumnRemoveRowsCmd(); virtual void redo(); virtual void undo(); protected: AbstractColumnPrivate * m_col; int m_first; int m_count; IntervalAttribute m_masking; }; #endif // ifndef ABSTRACTCOLUMNCOMMANDS_H diff --git a/src/backend/core/column/Column.cpp b/src/backend/core/column/Column.cpp index c997426d7..70bb0c60c 100644 --- a/src/backend/core/column/Column.cpp +++ b/src/backend/core/column/Column.cpp @@ -1,975 +1,977 @@ /*************************************************************************** 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" 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, AbstractColumn::ColumnMode mode) : AbstractColumn(name), 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, SIGNAL(triggered(QAction*)), this, SLOT(navigateTo(QAction*))); } /** * \brief Dtor */ Column::~Column() { delete m_string_io; delete d; } QMenu* Column::createContextMenu() { QMenu* menu = AbstractAspect::createContextMenu(); QAction* firstAction = menu->actions().at(1); //add actions available in SpreadsheetView 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 QList curves = project()->children(AbstractAspect::Recursive); for (const auto* curve: curves) { if (curve->dataSourceType() == XYCurve::DataSourceSpreadsheet && (curve->xColumn() == this || curve->yColumn() == this) ) { QAction* action = new QAction(curve->icon(), curve->name(), m_usedInActionGroup); action->setData(curve->path()); usedInMenu->addAction(action); } } 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; + 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(); } /** * \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); setStatisticsAvailable(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); setStatisticsAvailable(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(); } /** * \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)); } /** * \brief Set a formula string for an interval of rows */ void Column::setFormula(Interval i, QString formula) { exec(new ColumnSetFormulaCmd(d, i, formula)); } /** * \brief Overloaded function for convenience */ void Column::setFormula(int row, 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) { setStatisticsAvailable(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) { if (!new_values.isEmpty()) { //TODO: do we really need this check? setStatisticsAvailable(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, const QDate& new_value) { setStatisticsAvailable(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, const QTime& new_value) { setStatisticsAvailable(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) { setStatisticsAvailable(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()) { setStatisticsAvailable(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, double new_value) { setStatisticsAvailable(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) { if (!new_values.isEmpty()) { setStatisticsAvailable(false); exec(new ColumnReplaceValuesCmd(d, first, new_values)); } } void Column::setStatisticsAvailable(bool available) { d->statisticsAvailable = available; } bool Column::statisticsAvailable() const { return d->statisticsAvailable; } const Column::ColumnStatistics& Column::statistics() { if (!statisticsAvailable()) calculateStatistics(); return d->statistics; } void Column::calculateStatistics() { d->statistics = ColumnStatistics(); ColumnStatistics& statistics = d->statistics; // TODO: support other data types? QVector* rowValues = reinterpret_cast*>(data()); int notNanCount = 0; double val; double columnSum = 0.0; double columnProduct = 1.0; double columnSumNeg = 0.0; double columnSumSquare = 0.0; statistics.minimum = INFINITY; statistics.maximum = -INFINITY; QMap frequencyOfValues; QVector rowData; 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) { setStatisticsAvailable(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((notNanCount-1)/2) : (rowData.at((notNanCount-1)/2) + rowData.at(notNanCount/2))/2.0; QVector absoluteMedianList; absoluteMedianList.reserve(notNanCount); absoluteMedianList.resize(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((notNanCount-1)/2) : (absoluteMedianList.at((notNanCount-1)/2) + absoluteMedianList.at(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.values()) { const double frequencyNorm = static_cast(v) / notNanCount; entropy += (frequencyNorm * log2(frequencyNorm)); } statistics.entropy = -entropy; setStatisticsAvailable(true); } ////////////////////////////////////////////////////////////////////////////////////////////// void* Column::data() const { return d->data(); } //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); } /* * 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() { if (!m_suppressDataChangedSignal) emit dataChanged(this); setStatisticsAvailable(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("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 (auto name: formulaVariableNames()) writer->writeTextElement("name", name); writer->writeEndElement(); writer->writeStartElement("columnPathes"); for (auto path: formulaVariableColumnPathes()) writer->writeTextElement("path", path); writer->writeEndElement(); writer->writeEndElement(); } writeCommentElement(writer); writer->writeStartElement("input_filter"); d->inputFilter()->save(writer); writer->writeEndElement(); writer->writeStartElement("output_filter"); d->outputFilter()->save(writer); writer->writeEndElement(); XmlWriteMask(writer); //TODO: formula in cells is not implemented yet // QList< 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()); int size = d->rowCount()*sizeof(double); writer->writeCharacters(QByteArray::fromRawData(data, 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() { QByteArray bytes = QByteArray::fromBase64(m_content.toAscii()); QVector * data = new QVector(bytes.size()/sizeof(double)); 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) { if (reader->isStartElement() && reader->name() == "column") { if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value("designation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'designation'")); else setPlotDesignation( AbstractColumn::PlotDesignation(str.toInt()) ); str = attribs.value("mode").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'mode'")); else setColumnMode( AbstractColumn::ColumnMode(str.toInt()) ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'width'")); else 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; } QString content = reader->text().toString().trimmed(); if (!content.isEmpty() && columnMode() == AbstractColumn::Numeric) { DecodeColumnTask* task = new DecodeColumnTask(d, content); QThreadPool::globalInstance()->start(task); } } } else // no column element reader->raiseError(i18n("no column element found")); return !reader->error(); } /** * \brief Read XML input filter element */ bool Column::XmlReadInputFilter(XmlStreamReader* reader) { Q_ASSERT(reader->isStartElement() && reader->name() == "input_filter"); if (!reader->skipToNextTag()) return false; if (!d->inputFilter()->load(reader)) return false; if (!reader->skipToNextTag()) return false; Q_ASSERT(reader->isEndElement() && reader->name() == "input_filter"); return true; } /** * \brief Read XML output filter element */ bool Column::XmlReadOutputFilter(XmlStreamReader* reader) { Q_ASSERT(reader->isStartElement() && reader->name() == "output_filter"); if (!reader->skipToNextTag()) return false; if (!d->outputFilter()->load(reader)) return false; if (!reader->skipToNextTag()) return false; Q_ASSERT(reader->isEndElement() && 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); 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() && reader->name() == "row"); - QString str; - - QXmlStreamAttributes attribs = reader->attributes(); +// QXmlStreamAttributes attribs = reader->attributes(); bool ok; int index = reader->readAttributeInt("index", &ok); if (!ok) { reader->raiseError(i18n("invalid or missing row index")); return false; } - str = reader->readElementText(); + 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; + double value = str.toDouble(&ok); + if(!ok) { + reader->raiseError(i18n("invalid row value")); + return false; } + setValueAt(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; * QList< Interval > intervals = my_column.formulaIntervals(); * foreach(Interval interval, intervals) * list << QString(interval.toString() + ": " + my_column.formula(interval.start())); * \endcode */ QList< Interval > Column::formulaIntervals() const { return d->formulaIntervals(); } void Column::handleFormatChange() { if (columnMode() == AbstractColumn::DateTime) { auto* input_filter = static_cast(d->inputFilter()); auto* output_filter = static_cast(d->outputFilter()); 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 setStatisticsAvailable(false); } diff --git a/src/backend/datasources/filters/AbstractFileFilter.cpp b/src/backend/datasources/filters/AbstractFileFilter.cpp index d02fa60b7..eb6e1e358 100644 --- a/src/backend/datasources/filters/AbstractFileFilter.cpp +++ b/src/backend/datasources/filters/AbstractFileFilter.cpp @@ -1,64 +1,67 @@ /*************************************************************************** File : AbstractFileFilter.h Project : LabPlot Description : file I/O-filter related interface -------------------------------------------------------------------- Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/lib/macros.h" #include #include #include AbstractColumn::ColumnMode AbstractFileFilter::columnMode(const QString& valueString, const QString& dateTimeFormat, QLocale::Language lang) { AbstractColumn::ColumnMode mode = AbstractColumn::Numeric; + //TODO: check for int first (when supported) + //local.toInt(); -> returns false for floating values + //try to convert to a number first bool isNumber; QLocale locale(lang); locale.toDouble(valueString, &isNumber); - //if not a number, check datetime and string + //if not a number, check datetime. if that fails: string if (!isNumber) { QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); if (valueDateTime.isValid()) mode = AbstractColumn::DateTime; else mode = AbstractColumn::Text; } return mode; } /* returns the list of all supported locales for numeric data */ QStringList AbstractFileFilter::numberFormats() { QStringList formats; for (int l = 0; l < ENUM_COUNT(QLocale, Language); l++) formats << QLocale::languageToString((QLocale::Language)l); return formats; } diff --git a/src/backend/datasources/filters/AsciiFilter.cpp b/src/backend/datasources/filters/AsciiFilter.cpp index e01737163..74d82a7a9 100644 --- a/src/backend/datasources/filters/AsciiFilter.cpp +++ b/src/backend/datasources/filters/AsciiFilter.cpp @@ -1,1151 +1,1142 @@ /*************************************************************************** File : AsciiFilter.cpp Project : LabPlot Description : ASCII I/O-filter -------------------------------------------------------------------- Copyright : (C) 2009-2017 Stefan Gerlach (stefan.gerlach@uni.kn) 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 * * * ***************************************************************************/ #include "backend/datasources/FileDataSource.h" #include "backend/core/column/Column.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/AsciiFilterPrivate.h" #include "backend/lib/macros.h" #include #include #include #include #include #include /*! \class AsciiFilter \brief Manages the import/export of data organized as columns (vectors) from/to an ASCII-file. \ingroup datasources */ AsciiFilter::AsciiFilter() : AbstractFileFilter(), d(new AsciiFilterPrivate(this)) {} AsciiFilter::~AsciiFilter() {} /*! reads the content of the device \c device. */ void AsciiFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromDevice(device, dataSource, importMode, lines); } void AsciiFilter::readFromLiveDeviceNotFile(QIODevice &device, AbstractDataSource * dataSource, AbstractFileFilter::ImportMode) { d->readFromLiveDevice(device, dataSource); } qint64 AsciiFilter::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from, AbstractFileFilter::ImportMode importMode) { return d->readFromLiveDevice(device, dataSource, from, importMode); } /*! reads the content of the file \c fileName. */ QVector AsciiFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromFile(fileName, dataSource, importMode, lines); return QVector(); //TODO: remove this later once all read*-functions in the filter classes don't return any preview strings anymore } QVector AsciiFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ //void AsciiFilter::read(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { // d->read(fileName, dataSource, importMode); //} /*! writes the content of the data source \c dataSource to the file \c fileName. */ void AsciiFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /*! loads the predefined filter settings for \c filterName */ void AsciiFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void AsciiFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /*! returns the list with the names of all saved (system wide or user defined) filter settings. */ QStringList AsciiFilter::predefinedFilters() { return QStringList(); } /*! returns the list of all predefined separator characters. */ QStringList AsciiFilter::separatorCharacters() { return (QStringList() << "auto" << "TAB" << "SPACE" << "," << ";" << ":" << ",TAB" << ";TAB" << ":TAB" << ",SPACE" << ";SPACE" << ":SPACE"); } /*! returns the list of all predefined comment characters. */ QStringList AsciiFilter::commentCharacters() { return (QStringList() << "#" << "!" << "//" << "+" << "c" << ":" << ";"); } /*! returns the list of all predefined data types. */ QStringList AsciiFilter::dataTypes() { const QMetaObject& mo = AbstractColumn::staticMetaObject; const QMetaEnum& me = mo.enumerator(mo.indexOfEnumerator("ColumnMode")); QStringList list; for (int i = 0; i <= 100; i++) // me.keyCount() does not work because we have holes in enum if (me.valueToKey(i)) list << me.valueToKey(i); return list; } /*! returns the number of columns in the file \c fileName. */ int AsciiFilter::columnNumber(const QString& fileName, const QString& separator) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { DEBUG("Could not open file " << fileName.toStdString() << " for determining number of columns"); return -1; } QString line = device.readLine(); line.remove(QRegExp("[\\n\\r]")); QStringList lineStringList; if (separator.length() > 0) lineStringList = line.split(separator); else lineStringList = line.split(QRegExp("\\s+")); DEBUG("number of columns : " << lineStringList.size()); return lineStringList.size(); } size_t AsciiFilter::lineNumber(const QString& fileName) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { DEBUG("Could not open file " << fileName.toStdString() << " for determining number of lines"); return 0; } size_t lineCount = 0; while (!device.atEnd()) { device.readLine(); lineCount++; } //TODO: wc is much faster but not portable /* QElapsedTimer myTimer; myTimer.start(); QProcess wc; wc.start(QString("wc"), QStringList() << "-l" << fileName); size_t lineCount = 0; while (wc.waitForReadyRead()) lineCount = wc.readLine().split(' ')[0].toInt(); lineCount++; // last line not counted DEBUG(" Elapsed time counting lines : " << myTimer.elapsed() << " ms"); */ return lineCount; } /*! returns the number of lines in the device \c device or 0 if not available. resets the position to 0! */ size_t AsciiFilter::lineNumber(QIODevice &device) { // device.hasReadLine() always returns 0 for KFilterDev! if (device.isSequential()) return 0; size_t lineCount = 0; device.seek(0); while (!device.atEnd()) { device.readLine(); lineCount++; } device.seek(0); return lineCount; } void AsciiFilter::setCommentCharacter(const QString& s) { d->commentCharacter = s; } QString AsciiFilter::commentCharacter() const { return d->commentCharacter; } void AsciiFilter::setSeparatingCharacter(const QString& s) { d->separatingCharacter = s; } QString AsciiFilter::separatingCharacter() const { return d->separatingCharacter; } void AsciiFilter::setDateTimeFormat(const QString &f) { d->dateTimeFormat = f; } QString AsciiFilter::dateTimeFormat() const { return d->dateTimeFormat; } void AsciiFilter::setNumberFormat(QLocale::Language lang) { d->numberFormat = lang; } QLocale::Language AsciiFilter::numberFormat() const { return d->numberFormat; } void AsciiFilter::setAutoModeEnabled(const bool b) { d->autoModeEnabled = b; } bool AsciiFilter::isAutoModeEnabled() const { return d->autoModeEnabled; } void AsciiFilter::setHeaderEnabled(const bool b) { d->headerEnabled = b; } bool AsciiFilter::isHeaderEnabled() const { return d->headerEnabled; } void AsciiFilter::setSkipEmptyParts(const bool b) { d->skipEmptyParts = b; } bool AsciiFilter::skipEmptyParts() const { return d->skipEmptyParts; } void AsciiFilter::setCreateIndexEnabled(bool b) { d->createIndexEnabled = b; } void AsciiFilter::setSimplifyWhitespacesEnabled(bool b) { d->simplifyWhitespacesEnabled = b; } bool AsciiFilter::simplifyWhitespacesEnabled() const { return d->simplifyWhitespacesEnabled; } void AsciiFilter::setVectorNames(const QString s) { d->vectorNames = s.simplified().split(' '); } QStringList AsciiFilter::vectorNames() const { return d->vectorNames; } QVector AsciiFilter::columnModes() { return d->columnModes; } void AsciiFilter::setStartRow(const int r) { d->startRow = r; } int AsciiFilter::startRow() const { return d->startRow; } void AsciiFilter::setEndRow(const int r) { d->endRow = r; } int AsciiFilter::endRow() const { return d->endRow; } void AsciiFilter::setStartColumn(const int c) { d->startColumn = c; } int AsciiFilter::startColumn() const { return d->startColumn; } void AsciiFilter::setEndColumn(const int c) { d->endColumn = c; } int AsciiFilter::endColumn() const { return d->endColumn; } //##################################################################### //################### Private implementation ########################## //##################################################################### AsciiFilterPrivate::AsciiFilterPrivate(AsciiFilter* owner) : q(owner), commentCharacter("#"), separatingCharacter("auto"), autoModeEnabled(true), headerEnabled(true), skipEmptyParts(false), simplifyWhitespacesEnabled(true), createIndexEnabled(false), startRow(1), endRow(-1), startColumn(1), endColumn(-1), m_prepared(false), m_columnOffset(0) { } /*! * returns -1 if the device couldn't be opened, 1 if the current read position in the device is at the end and 0 otherwise. */ int AsciiFilterPrivate::prepareDeviceToRead(QIODevice& device) { if (!device.open(QIODevice::ReadOnly)) return -1; if (device.atEnd()) // empty file return 1; // Parse the first line: // Determine the number of columns, create the columns and use (if selected) the first row to name them QString firstLine; do { // skip comment lines firstLine = device.readLine(); if (device.atEnd()) { if (device.isSequential()) break; else return 1; } } while (firstLine.startsWith(commentCharacter)); DEBUG(" device position after first line and comments = " << device.pos()); QString firstLineOriginal = firstLine; firstLine.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) firstLine = firstLine.simplified(); DEBUG("First line: \'" << firstLine.toStdString() << '\''); // determine separator and split first line QStringList firstLineStringList; if (separatingCharacter == "auto") { DEBUG("automatic separator"); QRegExp regExp("(\\s+)|(,\\s+)|(;\\s+)|(:\\s+)"); firstLineStringList = firstLine.split(regExp, QString::SkipEmptyParts); if (!firstLineStringList.isEmpty()) { int length1 = firstLineStringList.at(0).length(); if (firstLineStringList.size() > 1) { int pos2 = firstLine.indexOf(firstLineStringList.at(1), length1); m_separator = firstLine.mid(length1, pos2 - length1); } else { //old: separator = line.right(line.length() - length1); m_separator = ' '; } } } else { // use given separator // replace symbolic "TAB" with '\t' m_separator = separatingCharacter.replace(QLatin1String("TAB"), "\t", Qt::CaseInsensitive); // replace symbolic "SPACE" with ' ' m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); firstLineStringList = firstLine.split(m_separator, QString::SkipEmptyParts); } DEBUG("separator: \'" << m_separator.toStdString() << '\''); DEBUG("number of columns: " << firstLineStringList.size()); DEBUG("headerEnabled = " << headerEnabled); if (headerEnabled) { // use first line to name vectors vectorNames = firstLineStringList; QDEBUG("vector names =" << vectorNames); startRow++; } // set range to read if (endColumn == -1) endColumn = firstLineStringList.size(); // last column if (createIndexEnabled) { vectorNames.prepend("index"); endColumn++; } m_actualCols = endColumn - startColumn + 1; //TEST: readline-seek-readline fails /* qint64 testpos = device.pos(); DEBUG("read data line @ pos " << testpos << " : " << device.readLine().toStdString()); device.seek(testpos); testpos = device.pos(); DEBUG("read data line again @ pos " << testpos << " : " << device.readLine().toStdString()); */ // this also resets position to start of file m_actualRows = AsciiFilter::lineNumber(device); // Find first data line (ignoring comment lines) DEBUG("Skipping " << startRow - 1 << " lines"); for (int i = 0; i < startRow - 1; ++i) { QString line = device.readLine(); if (device.atEnd()) { if (device.isSequential()) break; else return 1; } if (line.startsWith(commentCharacter)) // ignore commented lines i--; } // parse first data line to determine data type for each column if (device.isSequential()) firstLine = firstLineOriginal; else firstLine = device.readLine(); firstLine.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) firstLine = firstLine.simplified(); DEBUG("first data line : \'" << firstLine.toStdString() << '\''); firstLineStringList = firstLine.split(m_separator, QString::SkipEmptyParts); - QDEBUG("first data line, parsed : " << firstLineStringList); + QDEBUG("first data line, parsed: " << firstLineStringList); columnModes.resize(m_actualCols); int col = 0; if (createIndexEnabled) { columnModes[0] = AbstractColumn::Numeric; col = 1; } for (const auto& valueString: firstLineStringList) { // only parse columns available in first data line if (col == m_actualCols) break; columnModes[col++] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); } QDEBUG("column modes = " << columnModes); int actualEndRow = endRow; DEBUG("endRow = " << endRow); if (endRow == -1 || endRow > m_actualRows) actualEndRow = m_actualRows; if (m_actualRows > actualEndRow) m_actualRows = actualEndRow; // reset to start of file if (!device.isSequential()) device.seek(0); DEBUG("start/end column: " << startColumn << ' ' << endColumn); DEBUG("start/end row: " << startRow << ' ' << actualEndRow); DEBUG("actual cols/rows (w/o header incl. start rows): " << m_actualCols << ' ' << m_actualRows); if (m_actualRows == 0 && !device.isSequential()) return 1; return 0; } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("AsciiFilterPrivate::readDataFromFile(): fileName = \'" << fileName.toStdString() << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode) << ", lines = " << lines); KFilterDev device(fileName); readDataFromDevice(device, dataSource, importMode, lines); } qint64 AsciiFilterPrivate::readFromLiveDevice(QIODevice & device, AbstractDataSource * dataSource, qint64 from, AbstractFileFilter::ImportMode importMode) { Q_ASSERT(dataSource != nullptr); FileDataSource* spreadsheet = dynamic_cast(dataSource); if (!m_prepared) { DEBUG("device is sequential = " << device.isSequential()); const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) DEBUG("Device error = " << deviceError); if (deviceError) return 0; ////////// /////////////////////////// prepare import for spreadsheet spreadsheet->setUndoAware(false); //make the available columns undo unaware before we resize and rename them below, //the same will be done for new columns in this->resize(). for (int i = 0; i < spreadsheet->childCount(); i++) spreadsheet->child(i)->setUndoAware(false); qDebug() << "fds resizing!"; spreadsheet->removeColumns(0, 2); if (importMode == AbstractFileFilter::Replace) spreadsheet->clear(); spreadsheet->resize(importMode, vectorNames, m_actualCols); qDebug() << "fds resized to col: " << m_actualCols; qDebug() << "fds rowCount: " << spreadsheet->rowCount(); m_actualRows = 1; //also here we need a cheaper version of this if (!spreadsheet->keepLastValues()) spreadsheet->setRowCount(m_actualRows); else { spreadsheet->setRowCount(spreadsheet->keepNvalues()); m_actualRows = spreadsheet->keepNvalues(); } if (device.isSequential()) m_actualRows = 1; qDebug() << "fds rows resized to: " << m_actualRows; m_dataContainer.resize(m_actualCols); for (int n = 0; n < m_actualCols; n++) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) spreadsheet->child(n)->setColumnMode(columnModes[n]); switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } if (!device.isSequential()) device.seek(device.size()); m_prepared = true; qDebug() << "prepared!"; } qint64 bytesread = 0; // if there's data do be read if (device.bytesAvailable() > 0) { qDebug() << "got new data"; //move to the last read position, from == total bytes read //since the other source types are sequencial we cannot seek on them if (spreadsheet->sourceType() == FileDataSource::SourceType::FileOrPipe) device.seek(from); //count the new lines, increase actualrows on each //now we read all the new lines, if we want to use sample rate //then here we can do it, if we have actually sample rate number of lines :-? int newLinesForSampleRateNotTillEnd = 0; int newLinesTillEnd = 0; QVector newData; if (spreadsheet->readingType() != FileDataSource::ReadingType::TillEnd) { newData.reserve(spreadsheet->sampleRate()); newData.resize(spreadsheet->sampleRate()); } int newDataIdx = 0; while (!device.atEnd()) { if (spreadsheet->readingType() != FileDataSource::ReadingType::TillEnd) newData[newDataIdx++] = device.readLine(); else newData.push_back(device.readLine()); newLinesTillEnd++; if (spreadsheet->readingType() != FileDataSource::ReadingType::TillEnd) { newLinesForSampleRateNotTillEnd++; //for Continous reading and FromEnd we read sample rate number of lines if possible if (newLinesForSampleRateNotTillEnd == spreadsheet->sampleRate()) break; } } //we had less new lines than the sample rate specified if (spreadsheet->readingType() != FileDataSource::ReadingType::TillEnd) { qDebug() << "Removed empty lines: " << newData.removeAll(""); } //increase row count if we don't have a fixed size if (!spreadsheet->keepLastValues()) { if (spreadsheet->readingType() != FileDataSource::ReadingType::TillEnd) m_actualRows += qMin(newData.size(), spreadsheet->sampleRate()); else m_actualRows += newData.size(); } //back to the last read position before counting when reading from files if (spreadsheet->sourceType() == FileDataSource::SourceType::FileOrPipe) device.seek(from); const int spreadsheetRowCountBeforeResize = spreadsheet->rowCount(); int currentRow; // indexes the position in the vector(column) //new rows/resize columns if we don't have a fixed size //TODO if the user changes this value..m_resizedToFixedSize..setResizedToFixedSize if (!spreadsheet->keepLastValues()) { if (spreadsheet->rowCount() < m_actualRows) spreadsheet->setRowCount(m_actualRows); currentRow = spreadsheetRowCountBeforeResize; // indexes the position in the vector(column) // if we have fixed size, we do this only once in preparation, here we can use // m_prepared and we need something to decide whether it has a fixed size or increasing for (int n = 0; n < m_actualCols; n++) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } else { //when we have a fixed size we have to pop sampleRate number of lines if specified //here popping, setting currentRow currentRow = m_actualRows - 1 - qMin(spreadsheet->sampleRate(), newLinesTillEnd); for (int row = 0; row < qMin(spreadsheet->sampleRate(), newLinesTillEnd); ++row) { for (int col = 0; col < m_actualCols; ++col) { switch (columnModes[col]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } } // from the last row we read the new data in the spreadsheet qDebug() << "reading from line: " << currentRow; qDebug() <<"available bytes: " << device.bytesAvailable(); int linesToRead = m_actualRows - spreadsheetRowCountBeforeResize; if (spreadsheet->keepLastValues()) linesToRead = qMin(spreadsheet->sampleRate(), newLinesTillEnd); qDebug() << "Lines to read: " << linesToRead <<" actual rows: " << m_actualRows; if (spreadsheet->readingType() == FileDataSource::ReadingType::FromEnd) { if (newData.size() > spreadsheet->sampleRate()) newDataIdx = newData.size() - spreadsheet->sampleRate() - 1; else newDataIdx = 0; } for (int i = 0; i < /*qMin(lines, m_actualRows)*/linesToRead; ++i) { QString line; if (spreadsheet->readingType() == FileDataSource::ReadingType::FromEnd) line = newData.at(newDataIdx++); else line = newData.at(i); if (spreadsheet->sourceType() == FileDataSource::SourceType::FileOrPipe) bytesread += line.size(); qDebug() << "line bytes: " << line.size() << " line: " << line; qDebug() << "reading in row: " << currentRow; if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; QLocale locale(numberFormat); QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); for (int n = 0; n < m_actualCols; n++) { if (n < lineStringList.size()) { const QString& valueString = lineStringList.at(n); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : NAN); qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueString; break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } else { // missing columns in this line switch (columnModes[n]) { case AbstractColumn::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = NAN; break; case AbstractColumn::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = "NAN"; break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } } currentRow++; } ////////// // set the comments for each of the columns if datasource is a spreadsheet const int rows = spreadsheet->rowCount(); for (int n = 0; n < m_actualCols; ++n) { Column* column = spreadsheet->column(n); QString comment; switch (column->columnMode()) { case AbstractColumn::Numeric: comment = i18np("numerical data, %1 element", "numerical data, %1 elements", rows); break; case AbstractColumn::Text: comment = i18np("text data, %1 element", "text data, %1 elements", rows); break; } column->setComment(comment); if (importMode == AbstractFileFilter::Replace) { column->setSuppressDataChangedSignal(false); column->setChanged(); } } } else qDebug() << "No new data available"; ////////////////// return bytesread; } /*! reads the content of device \c device to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("AsciiFilterPrivate::readDataFromDevice(): dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode) << ", lines = " << lines); Q_ASSERT(dataSource != nullptr); if (!m_prepared) { DEBUG("device is sequential = " << device.isSequential()); const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) DEBUG("Device error = " << deviceError); if (deviceError == 1 && importMode == AbstractFileFilter::Replace && dataSource) dataSource->clear(); if (deviceError) return; // matrix data has only one column mode (which is not text) if (dynamic_cast(dataSource)) { auto mode = columnModes[0]; (mode == AbstractColumn::Text) ? mode = AbstractColumn::Numeric : 0; for (auto& c: columnModes) (c != mode) ? c = mode : 0; } m_columnOffset = dataSource->prepareImport(m_dataContainer, importMode, m_actualRows - startRow + 1, m_actualCols, vectorNames, columnModes); m_prepared = true; } DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); QLocale locale(numberFormat); // Read the data int currentRow = 0; // indexes the position in the vector(column) if (lines == -1) lines = m_actualRows; DEBUG("reading " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(lines, m_actualRows); i++) { QString line = device.readLine(); line.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; if (startRow > 1) { // skip start lines startRow--; continue; } QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); //prepend the index if required //TODO: come up maybe with a solution with adding the index inside of the loop below, //without conversion to string, prepending to the list and then conversion back to integer. if (createIndexEnabled) lineStringList.prepend(QString::number(i+1)); for (int n = 0; n < m_actualCols; n++) { if (n < lineStringList.size()) { const QString& valueString = lineStringList.at(n); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : NAN); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueString; break; - case AbstractColumn::Month: - //TODO - break; + case AbstractColumn::Month: // never happens case AbstractColumn::Day: - //TODO break; } } else { // missing columns in this line switch (columnModes[n]) { case AbstractColumn::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = NAN; break; case AbstractColumn::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = "NAN"; break; - case AbstractColumn::Month: - //TODO - break; + case AbstractColumn::Month: // never happens case AbstractColumn::Day: - //TODO break; } } } currentRow++; emit q->completed(100 * currentRow/m_actualRows); } dataSource->finalizeImport(m_columnOffset, startColumn, endColumn, dateTimeFormat, importMode); } - /*! * generates the preview for the file \c fileName reading the provided number of \c lines. */ QVector AsciiFilterPrivate::preview(const QString& fileName, int lines) { QVector dataStrings; KFilterDev device(fileName); const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG("Device error = " << deviceError); return dataStrings; } //number formatting DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); QLocale locale(numberFormat); // Read the data if (lines == -1) lines = m_actualRows; DEBUG("generating preview for " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(lines, m_actualRows); i++) { QString line = device.readLine(); line.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; if (startRow > 1) { // skip start lines startRow--; continue; } QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); //prepend index if required if (createIndexEnabled) lineStringList.prepend(QString::number(i+1)); QStringList lineString; for (int n = 0; n < m_actualCols; n++) { if (n < lineStringList.size()) { const QString& valueString = lineStringList.at(n); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : NAN); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } case AbstractColumn::Text: lineString += valueString; break; - case AbstractColumn::Month: - //TODO - break; + case AbstractColumn::Month: // never happens case AbstractColumn::Day: - //TODO break; } } else // missing columns in this line lineString += QLatin1String("NAN"); } dataStrings << lineString; } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void AsciiFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); - //TODO + + //TODO: save data to ascii file } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void AsciiFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "asciiFilter"); writer->writeAttribute( "commentCharacter", d->commentCharacter); writer->writeAttribute( "separatingCharacter", d->separatingCharacter); writer->writeAttribute( "autoMode", QString::number(d->autoModeEnabled)); writer->writeAttribute( "createIndex", QString::number(d->createIndexEnabled)); writer->writeAttribute( "header", QString::number(d->headerEnabled)); writer->writeAttribute( "vectorNames", d->vectorNames.join(' ')); writer->writeAttribute( "skipEmptyParts", QString::number(d->skipEmptyParts)); writer->writeAttribute( "simplifyWhitespaces", QString::number(d->simplifyWhitespacesEnabled)); writer->writeAttribute( "startRow", QString::number(d->startRow)); writer->writeAttribute( "endRow", QString::number(d->endRow)); writer->writeAttribute( "startColumn", QString::number(d->startColumn)); writer->writeAttribute( "endColumn", QString::number(d->endColumn)); writer->writeEndElement(); } /*! Loads from XML. */ bool AsciiFilter::load(XmlStreamReader* reader) { if (!reader->isStartElement() || reader->name() != "asciiFilter") { reader->raiseError(i18n("no ascii filter element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value("commentCharacter").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'commentCharacter'")); else d->commentCharacter = str; str = attribs.value("separatingCharacter").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'separatingCharacter'")); else d->separatingCharacter = str; str = attribs.value("createIndex").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'createIndex'")); else d->createIndexEnabled = str.toInt(); str = attribs.value("autoMode").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoMode'")); else d->autoModeEnabled = str.toInt(); str = attribs.value("header").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'header'")); else d->headerEnabled = str.toInt(); str = attribs.value("vectorNames").toString(); d->vectorNames = str.split(' '); //may be empty str = attribs.value("simplifyWhitespaces").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'simplifyWhitespaces'")); else d->simplifyWhitespacesEnabled = str.toInt(); str = attribs.value("skipEmptyParts").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'skipEmptyParts'")); else d->skipEmptyParts = str.toInt(); str = attribs.value("startRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'startRow'")); else d->startRow = str.toInt(); str = attribs.value("endRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'endRow'")); else d->endRow = str.toInt(); str = attribs.value("startColumn").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'startColumn'")); else d->startColumn = str.toInt(); str = attribs.value("endColumn").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'endColumn'")); else d->endColumn = str.toInt(); return true; } diff --git a/src/backend/matrix/Matrix.cpp b/src/backend/matrix/Matrix.cpp index 44ed0fb42..d1c94d0bd 100644 --- a/src/backend/matrix/Matrix.cpp +++ b/src/backend/matrix/Matrix.cpp @@ -1,944 +1,944 @@ /*************************************************************************** File : Matrix.cpp Project : Matrix Description : Spreadsheet with a MxN matrix data model -------------------------------------------------------------------- Copyright : (C) 2008-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2015-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "Matrix.h" #include "MatrixPrivate.h" #include "matrixcommands.h" #include "backend/matrix/MatrixModel.h" #include "backend/core/Folder.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "commonfrontend/matrix/MatrixView.h" #include "kdefrontend/spreadsheet/ExportSpreadsheetDialog.h" #include #include #include #include #include #include /*! This class manages matrix based data (i.e., mathematically a MxN matrix with M rows, N columns). This data is typically used to for 3D plots. The values of the matrix are stored as double precision values. Each column of the matrix is stored in a QVector objects. \ingroup backend */ Matrix::Matrix(AbstractScriptingEngine* engine, int rows, int cols, const QString& name) : AbstractDataSource(engine, name), d(new MatrixPrivate(this)), m_model(nullptr) { //set initial number of rows and columns appendColumns(cols); appendRows(rows); init(); } Matrix::Matrix(AbstractScriptingEngine* engine, const QString& name, bool loading) : AbstractDataSource(engine, name), d(new MatrixPrivate(this)), m_model(nullptr) { if (!loading) init(); } Matrix::~Matrix() { delete d; } void Matrix::init() { KConfig config; KConfigGroup group = config.group("Matrix"); //matrix dimension int rows = group.readEntry("RowCount", 10); int cols = group.readEntry("ColumnCount", 10); appendRows(rows); appendColumns(cols); //mapping to logical x- and y-coordinates d->xStart = group.readEntry("XStart", 0.0); d->xEnd = group.readEntry("XEnd", 1.0); d->yStart = group.readEntry("YStart", 0.0); d->yEnd = group.readEntry("YEnd", 1.0); //format d->numericFormat = *group.readEntry("NumericFormat", "f").toLatin1().data(); d->precision = group.readEntry("Precision", 3); d->headerFormat = (Matrix::HeaderFormat)group.readEntry("HeaderFormat", (int)Matrix::HeaderRowsColumns); } /*! Returns an icon to be used for decorating my views. */ QIcon Matrix::icon() const { return QIcon::fromTheme("labplot-matrix"); } /*! Returns a new context menu. The caller takes ownership of the menu. */ QMenu* Matrix::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); emit requestProjectContextMenu(menu); return menu; } QWidget* Matrix::view() const { if (!m_view) { MatrixView* view = new MatrixView(const_cast(this)); m_model = view->model(); m_view = view; } return m_view; } bool Matrix::exportView() const { ExportSpreadsheetDialog* dlg = new ExportSpreadsheetDialog(m_view); dlg->setFileName(name()); dlg->setMatrixMode(true); //TODO FITS filter to decide if it can be exported to both dlg->setExportTo(QStringList() << i18n("FITS image") << i18n("FITS table")); if (reinterpret_cast(m_view)->selectedColumnCount() == 0) { dlg->setExportSelection(false); } bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { const QString path = dlg->path(); const MatrixView* view = reinterpret_cast(m_view); WAIT_CURSOR; if (dlg->format() == ExportSpreadsheetDialog::LaTeX) { const bool verticalHeader = dlg->matrixVerticalHeader(); const bool horizontalHeader = dlg->matrixHorizontalHeader(); const bool latexHeader = dlg->exportHeader(); const bool gridLines = dlg->gridLines(); const bool entire = dlg->entireSpreadheet(); const bool captions = dlg->captions(); view->exportToLaTeX(path, verticalHeader, horizontalHeader, latexHeader, gridLines, entire, captions); } else if (dlg->format() == ExportSpreadsheetDialog::FITS) { const int exportTo = dlg->exportToFits(); view->exportToFits(path, exportTo ); } else { const QString separator = dlg->separator(); view->exportToFile(path, separator); } RESET_CURSOR; } delete dlg; return ret; } bool Matrix::printView() { QPrinter printer; QPrintDialog* dlg = new QPrintDialog(&printer, m_view); bool ret; dlg->setWindowTitle(i18n("Print Matrix")); if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { const MatrixView* view = reinterpret_cast(m_view); view->print(&printer); } delete dlg; return ret; } bool Matrix::printPreview() const { const MatrixView* view = reinterpret_cast(m_view); QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, SIGNAL(paintRequested(QPrinter*)), view, SLOT(print(QPrinter*))); return dlg->exec(); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_D_READER_IMPL(Matrix, int, columnCount, columnCount) BASIC_D_READER_IMPL(Matrix, int, rowCount, rowCount) BASIC_D_READER_IMPL(Matrix, double, xStart, xStart) BASIC_D_READER_IMPL(Matrix, double, xEnd, xEnd) BASIC_D_READER_IMPL(Matrix, double, yStart, yStart) BASIC_D_READER_IMPL(Matrix, double, yEnd, yEnd) BASIC_D_READER_IMPL(Matrix, char, numericFormat, numericFormat) BASIC_D_READER_IMPL(Matrix, int, precision, precision) BASIC_D_READER_IMPL(Matrix, Matrix::HeaderFormat, headerFormat, headerFormat) CLASS_D_READER_IMPL(Matrix, QString, formula, formula) void* Matrix::data() const { return d->data; } void Matrix::setSuppressDataChangedSignal(bool b) { if (m_model) m_model->setSuppressDataChangedSignal(b); } void Matrix::setChanged() { if (m_model) m_model->setChanged(); } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## void Matrix::setRowCount(int count) { if (count == d->rowCount) return; const int diff = count - d->rowCount; if (diff > 0) appendRows(diff); else if (diff < 0) removeRows(rowCount() + diff, -diff); } void Matrix::setColumnCount(int count) { if (count == d->columnCount) return; const int diff = count - columnCount(); if (diff > 0) appendColumns(diff); else if (diff < 0) removeColumns(columnCount() + diff, -diff); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetXStart, double, xStart, updateViewHeader) void Matrix::setXStart(double xStart) { if (xStart != d->xStart) exec(new MatrixSetXStartCmd(d, xStart, i18n("%1: x-start changed"))); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetXEnd, double, xEnd, updateViewHeader) void Matrix::setXEnd(double xEnd) { if (xEnd != d->xEnd) exec(new MatrixSetXEndCmd(d, xEnd, i18n("%1: x-end changed"))); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetYStart, double, yStart, updateViewHeader) void Matrix::setYStart(double yStart) { if (yStart != d->yStart) exec(new MatrixSetYStartCmd(d, yStart, i18n("%1: y-start changed"))); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetYEnd, double, yEnd, updateViewHeader) void Matrix::setYEnd(double yEnd) { if (yEnd != d->yEnd) exec(new MatrixSetYEndCmd(d, yEnd, i18n("%1: y-end changed"))); } STD_SETTER_CMD_IMPL_S(Matrix, SetNumericFormat, char, numericFormat) void Matrix::setNumericFormat(char format) { if (format != d->numericFormat) exec(new MatrixSetNumericFormatCmd(d, format, i18n("%1: numeric format changed"))); } STD_SETTER_CMD_IMPL_S(Matrix, SetPrecision, int, precision) void Matrix::setPrecision(int precision) { if (precision != d->precision) exec(new MatrixSetPrecisionCmd(d, precision, i18n("%1: precision changed"))); } //TODO: make this undoable? void Matrix::setHeaderFormat(Matrix::HeaderFormat format) { d->headerFormat = format; m_model->updateHeader(); if (m_view) (reinterpret_cast(m_view))->resizeHeaders(); emit headerFormatChanged(format); } //columns void Matrix::insertColumns(int before, int count) { if (count < 1 || before < 0 || before > columnCount()) return; WAIT_CURSOR; exec(new MatrixInsertColumnsCmd(d, before, count)); RESET_CURSOR; } void Matrix::appendColumns(int count) { insertColumns(columnCount(), count); } void Matrix::removeColumns(int first, int count) { if (count < 1 || first < 0 || first+count > columnCount()) return; WAIT_CURSOR; exec(new MatrixRemoveColumnsCmd(d, first, count)); RESET_CURSOR; } void Matrix::clearColumn(int c) { exec(new MatrixClearColumnCmd(d, c)); } //rows void Matrix::insertRows(int before, int count) { if (count < 1 || before < 0 || before > rowCount()) return; WAIT_CURSOR; exec(new MatrixInsertRowsCmd(d, before, count)); RESET_CURSOR; } void Matrix::appendRows(int count) { insertRows(rowCount(), count); } void Matrix::removeRows(int first, int count) { if (count < 1 || first < 0 || first+count > rowCount()) return; WAIT_CURSOR; exec(new MatrixRemoveRowsCmd(d, first, count)); RESET_CURSOR; } + void Matrix::clearRow(int r) { for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, 0.0)); } -//cell double Matrix::cell(int row, int col) const { return d->cell(row, col); } //! Return the text displayed in the given cell QString Matrix::text(int row, int col) { return QLocale().toString(cell(row,col), d->numericFormat, d->precision); } //! Set the value of the cell void Matrix::setCell(int row, int col, double value) { if(row < 0 || row >= rowCount()) return; if(col < 0 || col >= columnCount()) return; exec(new MatrixSetCellValueCmd(d, row, col, value)); } void Matrix::clearCell(int row, int col) { exec(new MatrixSetCellValueCmd(d, row, col, 0.0)); } void Matrix::setDimensions(int rows, int cols) { if( (rows < 0) || (cols < 0 ) || (rows == rowCount() && cols == columnCount()) ) return; WAIT_CURSOR; beginMacro(i18n("%1: set matrix size to %2x%3", name(), rows, cols)); int col_diff = cols - columnCount(); if (col_diff > 0) insertColumns(columnCount(), col_diff); else if (col_diff < 0) removeColumns(columnCount() + col_diff, -col_diff); int row_diff = rows - rowCount(); if(row_diff > 0) appendRows(row_diff); else if (row_diff < 0) removeRows(rowCount() + row_diff, -row_diff); endMacro(); RESET_CURSOR; } void Matrix::copy(Matrix* other) { WAIT_CURSOR; beginMacro(i18n("%1: copy %2", name(), other->name())); int rows = other->rowCount(); int columns = other->columnCount(); setDimensions(rows, columns); for (int i=0; irowHeight(i)); for (int i=0; icolumnWidth(i)); d->suppressDataChange = true; for (int i=0; icolumnCells(i, 0, rows-1)); setCoordinates(other->xStart(), other->xEnd(), other->yStart(), other->yEnd()); setNumericFormat(other->numericFormat()); setPrecision(other->precision()); d->formula = other->formula(); d->suppressDataChange = false; emit dataChanged(0, 0, rows-1, columns-1); if (m_view) reinterpret_cast(m_view)->adjustHeaders(); endMacro(); RESET_CURSOR; } //! Duplicate the matrix inside its folder void Matrix::duplicate() { Matrix* matrix = new Matrix(0, rowCount(), columnCount(), name()); matrix->copy(this); if (folder()) folder()->addChild(matrix); } void Matrix::addRows() { if (!m_view) return; WAIT_CURSOR; int count = reinterpret_cast(m_view)->selectedRowCount(false); beginMacro(i18np("%1: add %2 rows", "%1: add %2 rows", name(), count)); exec(new MatrixInsertRowsCmd(d, rowCount(), count)); endMacro(); RESET_CURSOR; } void Matrix::addColumns() { if (!m_view) return; WAIT_CURSOR; int count = reinterpret_cast(m_view)->selectedRowCount(false); beginMacro(i18np("%1: add %2 column", "%1: add %2 columns", name(), count)); exec(new MatrixInsertColumnsCmd(d, columnCount(), count)); endMacro(); RESET_CURSOR; } void Matrix::setCoordinates(double x1, double x2, double y1, double y2) { exec(new MatrixSetCoordinatesCmd(d, x1, x2, y1, y2)); } void Matrix::setFormula(const QString& formula) { exec(new MatrixSetFormulaCmd(d, formula)); } //! This method should only be called by the view. /** This method does not change the view, it only changes the * values that are saved when the matrix is saved. The view * has to take care of reading and applying these values */ void Matrix::setRowHeight(int row, int height) { d->setRowHeight(row, height); } //! This method should only be called by the view. /** This method does not change the view, it only changes the * values that are saved when the matrix is saved. The view * has to take care of reading and applying these values */ void Matrix::setColumnWidth(int col, int width) { d->setColumnWidth(col, width); } int Matrix::rowHeight(int row) const { return d->rowHeight(row); } int Matrix::columnWidth(int col) const { return d->columnWidth(col); } //! Return the values in the given cells as double vector QVector Matrix::columnCells(int col, int first_row, int last_row) { return d->columnCells(col, first_row, last_row); } //! Set the values in the given cells from a double vector void Matrix::setColumnCells(int col, int first_row, int last_row, const QVector & values) { WAIT_CURSOR; exec(new MatrixSetColumnCellsCmd(d, col, first_row, last_row, values)); RESET_CURSOR; } //! Return the values in the given cells as double vector QVector Matrix::rowCells(int row, int first_column, int last_column) { return d->rowCells(row, first_column, last_column); } //! Set the values in the given cells from a double vector void Matrix::setRowCells(int row, int first_column, int last_column, const QVector & values) { WAIT_CURSOR; exec(new MatrixSetRowCellsCmd(d, row, first_column, last_column, values)); RESET_CURSOR; } void Matrix::setData(void* data) { //TODO: consider columnMode if (!(static_cast>*>(data))->isEmpty()) exec(new MatrixReplaceValuesCmd(d, data)); } //############################################################################## //######################### Public slots ##################################### //############################################################################## //! Clear the whole matrix (i.e. set all cells to 0.0) void Matrix::clear() { WAIT_CURSOR; beginMacro(i18n("%1: clear", name())); exec(new MatrixClearCmd(d)); endMacro(); RESET_CURSOR; } void Matrix::transpose() { WAIT_CURSOR; exec(new MatrixTransposeCmd(d)); RESET_CURSOR; } void Matrix::mirrorHorizontally() { WAIT_CURSOR; exec(new MatrixMirrorHorizontallyCmd(d)); RESET_CURSOR; } void Matrix::mirrorVertically() { WAIT_CURSOR; exec(new MatrixMirrorVerticallyCmd(d)); RESET_CURSOR; } //############################################################################## //###################### Private implementation ############################### //############################################################################## MatrixPrivate::MatrixPrivate(Matrix* owner) : q(owner), columnCount(0), rowCount(0), suppressDataChange(false) { //TODO: consider columnMode data = new QVector>(); } void MatrixPrivate::updateViewHeader() { reinterpret_cast(q->m_view)->model()->updateHeader(); } /*! Insert \count columns before column number \c before */ void MatrixPrivate::insertColumns(int before, int count) { Q_ASSERT(before >= 0); Q_ASSERT(before <= columnCount); //TODO: consider columnMode emit q->columnsAboutToBeInserted(before, count); for(int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } columnCount += count; emit q->columnsInserted(before, count); } /*! Remove \c count columns starting with column index \c first */ void MatrixPrivate::removeColumns(int first, int count) { emit q->columnsAboutToBeRemoved(first, count); Q_ASSERT(first >= 0); Q_ASSERT(first+count <= columnCount); //TODO: consider columnMode (static_cast>*>(data))->remove(first, count); for (int i=0; icolumnsRemoved(first, count); } /*! Insert \c count rows before row with the index \c before */ void MatrixPrivate::insertRows(int before, int count) { emit q->rowsAboutToBeInserted(before, count); Q_ASSERT(before >= 0); Q_ASSERT(before <= rowCount); //TODO: consider columnMode for(int col=0; col>*>(data))->operator[](col).insert(before+i, 0.0); for(int i=0; irowsInserted(before, count); } /*! Remove \c count columns starting from the column with index \c first */ void MatrixPrivate::removeRows(int first, int count) { emit q->rowsAboutToBeRemoved(first, count); Q_ASSERT(first >= 0); Q_ASSERT(first+count <= rowCount); //TODO: consider columnMode for(int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); for (int i = 0; i < count; i++) rowHeights.remove(first); rowCount -= count; emit q->rowsRemoved(first, count); } //! Return the value in the given cell double MatrixPrivate::cell(int row, int col) const { Q_ASSERT(row >= 0 && row < rowCount); Q_ASSERT(col >= 0 && col < columnCount); // if(row < 0 || row >= rowCount() || col < 0 || col >= columnCount()) // return 0.0; //TODO: consider columnMode return (static_cast>*>(data))->at(col).at(row); } void MatrixPrivate::setCell(int row, int col, double value) { Q_ASSERT(row >= 0 && row < rowCount); Q_ASSERT(col >= 0 && col < columnCount); //TODO: consider columnMode static_cast>*>(data)->operator[](col)[row] = value; if (!suppressDataChange) emit q->dataChanged(row, col, row, col); } QVector MatrixPrivate::columnCells(int col, int first_row, int last_row) { Q_ASSERT(first_row >= 0 && first_row < rowCount); Q_ASSERT(last_row >= 0 && last_row < rowCount); //TODO: consider columnMode if (first_row == 0 && last_row == rowCount-1) return (static_cast>*>(data))->at(col); QVector result; for (int i = first_row; i <= last_row; i++) result.append(static_cast>*>(data)->at(col).at(i)); return result; } void MatrixPrivate::setColumnCells(int col, int first_row, int last_row, const QVector & values) { Q_ASSERT(first_row >= 0 && first_row < rowCount); Q_ASSERT(last_row >= 0 && last_row < rowCount); Q_ASSERT(values.count() > last_row - first_row); //TODO: consider columnMode if (first_row == 0 && last_row == rowCount-1) { static_cast>*>(data)->operator[](col) = values; static_cast>*>(data)->operator[](col).resize(rowCount); // values may be larger if (!suppressDataChange) emit q->dataChanged(first_row, col, last_row, col); return; } for (int i = first_row; i <= last_row; i++) static_cast>*>(data)->operator[](col)[i] = values.at(i-first_row); if (!suppressDataChange) emit q->dataChanged(first_row, col, last_row, col); } QVector MatrixPrivate::rowCells(int row, int first_column, int last_column) { Q_ASSERT(first_column >= 0 && first_column < columnCount); Q_ASSERT(last_column >= 0 && last_column < columnCount); //TODO: consider columnMode QVector result; for (int i = first_column; i <= last_column; i++) result.append(static_cast>*>(data)->operator[](i)[row]); return result; } void MatrixPrivate::setRowCells(int row, int first_column, int last_column, const QVector& values) { Q_ASSERT(first_column >= 0 && first_column < columnCount); Q_ASSERT(last_column >= 0 && last_column < columnCount); Q_ASSERT(values.count() > last_column - first_column); //TODO: consider columnMode for(int i = first_column; i <= last_column; i++) static_cast>*>(data)->operator[](i)[row] = values.at(i-first_column); if (!suppressDataChange) emit q->dataChanged(row, first_column, row, last_column); } //! Fill column with zeroes void MatrixPrivate::clearColumn(int col) { //TODO: consider columnMode static_cast>*>(data)->operator[](col).fill(0.0); if (!suppressDataChange) emit q->dataChanged(0, col, rowCount-1, col); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## void Matrix::save(QXmlStreamWriter* writer) const { writer->writeStartElement("matrix"); writeBasicAttributes(writer); writeCommentElement(writer); //formula writer->writeStartElement("formula"); writer->writeCharacters(d->formula); writer->writeEndElement(); //format writer->writeStartElement("format"); writer->writeAttribute("headerFormat", QString::number(d->headerFormat)); writer->writeAttribute("numericFormat", QString(QChar(d->numericFormat))); writer->writeAttribute("precision", QString::number(d->precision)); writer->writeEndElement(); //dimensions writer->writeStartElement("dimension"); writer->writeAttribute("columns", QString::number(d->columnCount)); writer->writeAttribute("rows", QString::number(d->rowCount)); writer->writeAttribute("x_start", QString::number(d->xStart)); writer->writeAttribute("x_end", QString::number(d->xEnd)); writer->writeAttribute("y_start", QString::number(d->yStart)); writer->writeAttribute("y_end", QString::number(d->yEnd)); writer->writeEndElement(); //vector with row heights writer->writeStartElement("row_heights"); const char* data = reinterpret_cast(d->rowHeights.constData()); int size = d->rowHeights.size()*sizeof(int); writer->writeCharacters(QByteArray::fromRawData(data,size).toBase64()); writer->writeEndElement(); //vector with column widths writer->writeStartElement("column_widths"); data = reinterpret_cast(d->columnWidths.constData()); size = d->columnWidths.size()*sizeof(int); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); //columns //TODO: consider columnMode size = d->rowCount*sizeof(double); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data,size).toBase64()); writer->writeEndElement(); } writer->writeEndElement(); // "matrix" } bool Matrix::load(XmlStreamReader* reader) { if(!reader->isStartElement() || reader->name() != "matrix") { reader->raiseError(i18n("no matrix element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; // read child elements while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "matrix") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if(reader->name() == "formula") { d->formula = reader->text().toString().trimmed(); } else if (reader->name() == "format") { attribs = reader->attributes(); str = attribs.value("headerFormat").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'headerFormat'")); else d->headerFormat = Matrix::HeaderFormat(str.toInt()); str = attribs.value("numericFormat").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'numericFormat'")); else d->numericFormat = *str.toLatin1().data(); str = attribs.value("precision").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'precision'")); else d->precision = str.toInt(); } else if (reader->name() == "dimension") { attribs = reader->attributes(); str = attribs.value("columns").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'columns'")); else d->columnCount = str.toInt(); str = attribs.value("rows").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rows'")); else d->rowCount = str.toInt(); str = attribs.value("x_start").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x_start'")); else d->xStart = str.toDouble(); str = attribs.value("x_end").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x_end'")); else d->xEnd = str.toDouble(); str = attribs.value("y_start").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y_start'")); else d->yStart = str.toDouble(); str = attribs.value("y_end").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y_end'")); else d->yEnd = str.toDouble(); } else if (reader->name() == "row_heights") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray bytes = QByteArray::fromBase64(content.toAscii()); int count = bytes.size()/sizeof(int); d->rowHeights.resize(count); memcpy(d->rowHeights.data(), bytes.data(), count*sizeof(int)); } else if (reader->name() == "column_widths") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray bytes = QByteArray::fromBase64(content.toAscii()); int count = bytes.size()/sizeof(int); d->columnWidths.resize(count); memcpy(d->columnWidths.data(), bytes.data(), count*sizeof(int)); } else if (reader->name() == "column") { //TODO: parallelize reading of columns? reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray bytes = QByteArray::fromBase64(content.toAscii()); //TODO: consider columnMode int count = bytes.size()/sizeof(double); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(double)); static_cast>*>(d->data)->append(column); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } //############################################################################## //######################## Data Import ####################################### //############################################################################## int Matrix::prepareImport(QVector& dataContainer, AbstractFileFilter::ImportMode mode, int actualRows, int actualCols, QStringList colNameList, QVector columnMode) { QDEBUG("prepareImport() rows =" << actualRows << " cols =" << actualCols); Q_UNUSED(colNameList); int columnOffset = 0; setUndoAware(false); setSuppressDataChangedSignal(true); // resize the matrix if (mode == AbstractFileFilter::Replace) { clear(); setDimensions(actualRows, actualCols); } else { if (rowCount() < actualRows) setDimensions(actualRows, actualCols); else setDimensions(rowCount(), actualCols); } //TODO: support other numeric types when available (float, int, ...) // data() returns a void* which is a pointer to a matrix of any data type (see ColumnPrivate.cpp) dataContainer.resize(actualCols); switch (columnMode[0]) { // only columnMode[0] is used case AbstractColumn::Numeric: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->reserve(actualRows); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } break; case AbstractColumn::Text: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->reserve(actualRows); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } break; case AbstractColumn::DateTime: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->reserve(actualRows); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } return columnOffset; } void Matrix::finalizeImport(int columnOffset, int startColumn, int endColumn, const QString& dateTimeFormat, AbstractFileFilter::ImportMode importMode) { Q_UNUSED(columnOffset); Q_UNUSED(startColumn); Q_UNUSED(endColumn); Q_UNUSED(dateTimeFormat); Q_UNUSED(importMode); setSuppressDataChangedSignal(false); setChanged(); setUndoAware(true); } diff --git a/src/backend/matrix/Matrix.h b/src/backend/matrix/Matrix.h index 1042a522c..5b69fc9a2 100644 --- a/src/backend/matrix/Matrix.h +++ b/src/backend/matrix/Matrix.h @@ -1,173 +1,174 @@ /*************************************************************************** File : Matrix.h Project : Matrix Description : Spreadsheet with a MxN matrix data model -------------------------------------------------------------------- Copyright : (C) 2008-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2015-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef MATRIX_H #define MATRIX_H #include "backend/datasources/AbstractDataSource.h" #include "backend/datasources/filters/AbstractFileFilter.h" +//#include "backend/matrix/MatrixPrivate.h" #include "backend/lib/macros.h" class MatrixPrivate; class MatrixModel; class MatrixView; class Matrix : public AbstractDataSource { Q_OBJECT Q_ENUMS(HeaderFormat) public: enum HeaderFormat {HeaderRowsColumns, HeaderValues, HeaderRowsColumnsValues}; Matrix(AbstractScriptingEngine* engine, const QString& name, bool loading = false); Matrix(AbstractScriptingEngine* engine, int rows, int cols, const QString& name); ~Matrix(); virtual QIcon icon() const override; virtual QMenu* createContextMenu() override; virtual QWidget* view() const override; virtual bool exportView() const override; virtual bool printView() override; virtual bool printPreview() const override; BASIC_D_ACCESSOR_DECL(int, rowCount, RowCount) BASIC_D_ACCESSOR_DECL(int, columnCount, ColumnCount) BASIC_D_ACCESSOR_DECL(double, xStart, XStart) BASIC_D_ACCESSOR_DECL(double, xEnd, XEnd) BASIC_D_ACCESSOR_DECL(double, yStart, YStart) BASIC_D_ACCESSOR_DECL(double, yEnd, YEnd) BASIC_D_ACCESSOR_DECL(char, numericFormat, NumericFormat) BASIC_D_ACCESSOR_DECL(int, precision, Precision) BASIC_D_ACCESSOR_DECL(HeaderFormat, headerFormat, HeaderFormat) CLASS_D_ACCESSOR_DECL(QString, formula, Formula) -// QVector >& data() const; -// void setData(const QVector >&); void* data() const; void setData(void*); void setSuppressDataChangedSignal(bool); void setChanged(); int rowHeight(int row) const; void setRowHeight(int row, int height); int columnWidth(int col) const; void setColumnWidth(int col, int width); void setDimensions(int rows, int cols); void setCoordinates(double x1, double x2, double y1, double y2); void insertColumns(int before, int count); void appendColumns(int count); void removeColumns(int first, int count); void clearColumn(int); void insertRows(int before, int count); void appendRows(int count); void removeRows(int first, int count); void clearRow(int); + + //TODO: consider columnMode double cell(int row, int col) const; QString text(int row, int col); void setCell(int row, int col, double value); void clearCell(int row, int col); //TODO: consider columnMode QVector columnCells(int col, int first_row, int last_row); void setColumnCells(int col, int first_row, int last_row, const QVector& values); QVector rowCells(int row, int first_column, int last_column); void setRowCells(int row, int first_column, int last_column, const QVector& values); void copy(Matrix* other); virtual void save(QXmlStreamWriter*) const override; virtual bool load(XmlStreamReader*) override; virtual int prepareImport(QVector& dataContainer, AbstractFileFilter::ImportMode, int rows, int cols, QStringList colNameList, QVector) override; virtual void finalizeImport(int columnOffset, int startColumn, int endColumn, const QString& dateTimeFormat, AbstractFileFilter::ImportMode) override; typedef MatrixPrivate Private; public slots: void clear(); void transpose(); void mirrorVertically(); void mirrorHorizontally(); void addColumns(); void addRows(); void duplicate(); signals: void requestProjectContextMenu(QMenu*); void columnsAboutToBeInserted(int before, int count); void columnsInserted(int first, int count); void columnsAboutToBeRemoved(int first, int count); void columnsRemoved(int first, int count); void rowsAboutToBeInserted(int before, int count); void rowsInserted(int first, int count); void rowsAboutToBeRemoved(int first, int count); void rowsRemoved(int first, int count); void dataChanged(int top, int left, int bottom, int right); void coordinatesChanged(); friend class MatrixInsertRowsCmd; friend class MatrixRemoveRowsCmd; friend class MatrixInsertColumnsCmd; friend class MatrixRemoveColumnsCmd; void rowCountChanged(int); void columnCountChanged(int); friend class MatrixSetXStartCmd; friend class MatrixSetXEndCmd; friend class MatrixSetYStartCmd; friend class MatrixSetYEndCmd; void xStartChanged(double); void xEndChanged(double); void yStartChanged(double); void yEndChanged(double); friend class MatrixSetNumericFormatCmd; friend class MatrixSetPrecisionCmd; void numericFormatChanged(char); void precisionChanged(int); void headerFormatChanged(Matrix::HeaderFormat); private: void init(); MatrixPrivate* const d; friend class MatrixPrivate; mutable MatrixModel* m_model; }; #endif diff --git a/src/backend/matrix/MatrixPrivate.h b/src/backend/matrix/MatrixPrivate.h index 0f07cc81a..25d116530 100644 --- a/src/backend/matrix/MatrixPrivate.h +++ b/src/backend/matrix/MatrixPrivate.h @@ -1,82 +1,84 @@ /*************************************************************************** File : MatrixPrivate.h Project : LabPlot Description : Private members of Matrix. -------------------------------------------------------------------- Copyright : (C) 2008-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2015 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 MATRIXPRIVATE_H #define MATRIXPRIVATE_H template class QVector; +//class Matrix; class MatrixPrivate { public: explicit MatrixPrivate(Matrix*); void insertColumns(int before, int count); void removeColumns(int first, int count); void insertRows(int before, int count); void removeRows(int first, int count); QString name() const { return q->name(); } +// template T cell(int row, int col) const; double cell(int row, int col) const; void setCell(int row, int col, double value); QVector columnCells(int col, int first_row, int last_row); void setColumnCells(int col, int first_row, int last_row, const QVector & values); QVector rowCells(int row, int first_column, int last_column); void setRowCells(int row, int first_column, int last_column, const QVector & values); void clearColumn(int col); void setRowHeight(int row, int height) { rowHeights[row] = height; } void setColumnWidth(int col, int width) { columnWidths[col] = width; } int rowHeight(int row) const { return rowHeights.at(row); } int columnWidth(int col) const { return columnWidths.at(col); } void updateViewHeader(); void emitDataChanged(int top, int left, int bottom, int right) { emit q->dataChanged(top, left, bottom, right); } // TODO: anything private here? Matrix* q; int columnCount; int rowCount; void* data; QVector rowHeights;//!< Row widths QVector columnWidths;//!< Columns widths int defaultRowHeight; Matrix::HeaderFormat headerFormat; char numericFormat; //!< Format code for displaying numbers int precision; //!< Number of significant digits QString formula; //! //Insert columns MatrixInsertColumnsCmd::MatrixInsertColumnsCmd( MatrixPrivate * private_obj, int before, int count, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj), m_before(before), m_count(count) { setText(i18np("%1: insert %2 column", "%1: insert %2 columns", m_private_obj->name(), m_count)); } void MatrixInsertColumnsCmd::redo() { m_private_obj->insertColumns(m_before, m_count); emit m_private_obj->q->columnCountChanged(m_private_obj->columnCount); } void MatrixInsertColumnsCmd::undo() { m_private_obj->removeColumns(m_before, m_count); emit m_private_obj->q->columnCountChanged(m_private_obj->columnCount); } //Insert rows MatrixInsertRowsCmd::MatrixInsertRowsCmd( MatrixPrivate * private_obj, int before, int count, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj), m_before(before), m_count(count) { setText(i18np("%1: insert %2 row", "%1: insert %2 rows", m_private_obj->name(), m_count)); } void MatrixInsertRowsCmd::redo() { m_private_obj->insertRows(m_before, m_count); emit m_private_obj->q->rowCountChanged(m_private_obj->rowCount); } void MatrixInsertRowsCmd::undo() { m_private_obj->removeRows(m_before, m_count); emit m_private_obj->q->rowCountChanged(m_private_obj->rowCount); } //Remove columns MatrixRemoveColumnsCmd::MatrixRemoveColumnsCmd( MatrixPrivate * private_obj, int first, int count, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj), m_first(first), m_count(count) { setText(i18np("%1: remove %2 column", "%1: remove %2 columns", m_private_obj->name(), m_count)); } void MatrixRemoveColumnsCmd::redo() { if(m_backups.isEmpty()) { int last_row = m_private_obj->rowCount-1; for(int i=0; icolumnCells(m_first+i, 0, last_row)); } m_private_obj->removeColumns(m_first, m_count); emit m_private_obj->q->columnCountChanged(m_private_obj->columnCount); } void MatrixRemoveColumnsCmd::undo() { m_private_obj->insertColumns(m_first, m_count); int last_row = m_private_obj->rowCount-1; //TODO: use memcopy to copy from the backup vector for(int i=0; isetColumnCells(m_first+i, 0, last_row, m_backups.at(i)); emit m_private_obj->q->columnCountChanged(m_private_obj->columnCount); } //Remove rows MatrixRemoveRowsCmd::MatrixRemoveRowsCmd( MatrixPrivate * private_obj, int first, int count, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj), m_first(first), m_count(count) { setText(i18np("%1: remove %2 row", "%1: remove %2 rows", m_private_obj->name(), m_count)); } void MatrixRemoveRowsCmd::redo() { if(m_backups.isEmpty()) { int last_row = m_first+m_count-1; for(int col=0; colcolumnCount; col++) m_backups.append(m_private_obj->columnCells(col, m_first, last_row)); } m_private_obj->removeRows(m_first, m_count); emit m_private_obj->q->rowCountChanged(m_private_obj->rowCount); } void MatrixRemoveRowsCmd::undo() { m_private_obj->insertRows(m_first, m_count); int last_row = m_first+m_count-1; for(int col=0; colcolumnCount; col++) m_private_obj->setColumnCells(col, m_first, last_row, m_backups.at(col)); emit m_private_obj->q->rowCountChanged(m_private_obj->rowCount); } // clear matrix MatrixClearCmd::MatrixClearCmd( MatrixPrivate * private_obj, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj) { setText(i18n("%1: clear", m_private_obj->name())); } void MatrixClearCmd::redo() { if(m_backups.isEmpty()) { int last_row = m_private_obj->rowCount-1; for(int i=0; icolumnCount; i++) m_backups.append(m_private_obj->columnCells(i, 0, last_row)); } for(int i=0; icolumnCount; i++) m_private_obj->clearColumn(i); } void MatrixClearCmd::undo() { int last_row = m_private_obj->rowCount-1; for(int i=0; icolumnCount; i++) m_private_obj->setColumnCells(i, 0, last_row, m_backups.at(i)); } //clear column MatrixClearColumnCmd::MatrixClearColumnCmd( MatrixPrivate * private_obj, int col, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj), m_col(col) { setText(i18n("%1: clear column %2", m_private_obj->name(), m_col+1)); } void MatrixClearColumnCmd::redo() { if(m_backup.isEmpty()) m_backup = m_private_obj->columnCells(m_col, 0, m_private_obj->rowCount-1); m_private_obj->clearColumn(m_col); } void MatrixClearColumnCmd::undo() { m_private_obj->setColumnCells(m_col, 0, m_private_obj->rowCount-1, m_backup); } //set cell value MatrixSetCellValueCmd::MatrixSetCellValueCmd( MatrixPrivate * private_obj, int row, int col, double value, QUndoCommand * parent) - : QUndoCommand( parent ), m_private_obj(private_obj), m_row(row), m_col(col), m_value(value) + : QUndoCommand(parent), m_private_obj(private_obj), m_row(row), m_col(col), m_value(value) { // remark: don't use many QString::arg() calls in ctors of commands that might be called often, // they use a lot of execution time setText(i18n("%1: set cell value", m_private_obj->name())); } void MatrixSetCellValueCmd::redo() { m_old_value = m_private_obj->cell(m_row, m_col); m_private_obj->setCell(m_row, m_col, m_value); } void MatrixSetCellValueCmd::undo() { m_private_obj->setCell(m_row, m_col, m_old_value); } //set coordinates MatrixSetCoordinatesCmd::MatrixSetCoordinatesCmd( MatrixPrivate * private_obj, double x1, double x2, double y1, double y2, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj), m_new_x1(x1), m_new_x2(x2), m_new_y1(y1), m_new_y2(y2) { setText(i18n("%1: set matrix coordinates", m_private_obj->name())); } void MatrixSetCoordinatesCmd::redo() { m_old_x1 = m_private_obj->xStart; m_old_x2 = m_private_obj->xEnd; m_old_y1 = m_private_obj->yStart; m_old_y2 = m_private_obj->yEnd; m_private_obj->xStart = m_new_x1; m_private_obj->xEnd = m_new_x2; m_private_obj->yStart = m_new_y1; m_private_obj->yEnd = m_new_y2; } void MatrixSetCoordinatesCmd::undo() { m_private_obj->xStart = m_old_x1; m_private_obj->xEnd = m_old_x2; m_private_obj->yStart = m_old_y1; m_private_obj->yEnd = m_old_y2; } //set formula MatrixSetFormulaCmd::MatrixSetFormulaCmd(MatrixPrivate * private_obj, QString formula) : m_private_obj(private_obj), m_other_formula(formula) { setText(i18n("%1: set formula", m_private_obj->name())); } void MatrixSetFormulaCmd::redo() { QString tmp = m_private_obj->formula; m_private_obj->formula = m_other_formula; m_other_formula = tmp; } void MatrixSetFormulaCmd::undo() { redo(); } - //set column cells +//TODO: consider columnMode MatrixSetColumnCellsCmd::MatrixSetColumnCellsCmd( MatrixPrivate * private_obj, int col, int first_row, int last_row, const QVector & values, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj), m_col(col), m_first_row(first_row), m_last_row(last_row), m_values(values) { setText(i18n("%1: set cell values", m_private_obj->name())); } void MatrixSetColumnCellsCmd::redo() { if (m_old_values.isEmpty()) m_old_values = m_private_obj->columnCells(m_col, m_first_row, m_last_row); m_private_obj->setColumnCells(m_col, m_first_row, m_last_row, m_values); } - void MatrixSetColumnCellsCmd::undo() { m_private_obj->setColumnCells(m_col, m_first_row, m_last_row, m_old_values); } - //set row cells MatrixSetRowCellsCmd::MatrixSetRowCellsCmd( MatrixPrivate * private_obj, int row, int first_column, int last_column, const QVector & values, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj), m_row(row), m_first_column(first_column), m_last_column(last_column), m_values(values) { setText(i18n("%1: set cell values", m_private_obj->name())); } void MatrixSetRowCellsCmd::redo() { if (m_old_values.isEmpty()) m_old_values = m_private_obj->rowCells(m_row, m_first_column, m_last_column); m_private_obj->setRowCells(m_row, m_first_column, m_last_column, m_values); } void MatrixSetRowCellsCmd::undo() { m_private_obj->setRowCells(m_row, m_first_column, m_last_column, m_old_values); } //transpose matrix MatrixTransposeCmd::MatrixTransposeCmd( MatrixPrivate * private_obj, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj) { setText(i18n("%1: transpose", m_private_obj->name())); } void MatrixTransposeCmd::redo() { int rows = m_private_obj->rowCount; int cols = m_private_obj->columnCount; int temp_size = qMax(rows, cols); m_private_obj->suppressDataChange = true; if (cols < rows) m_private_obj->insertColumns(cols, temp_size - cols); else if (cols > rows) m_private_obj->insertRows(rows, temp_size - rows); for(int i = 1; i row = m_private_obj->rowCells(i, 0, i-1); QVector col = m_private_obj->columnCells(i, 0, i-1); m_private_obj->setRowCells(i, 0, i-1, col); m_private_obj->setColumnCells(i, 0, i-1, row); } if (cols < rows) m_private_obj->removeRows(cols, temp_size - cols); else if (cols > rows) m_private_obj->removeColumns(rows, temp_size - rows); m_private_obj->suppressDataChange = false; m_private_obj->emitDataChanged(0, 0, m_private_obj->rowCount-1, m_private_obj->columnCount-1); } void MatrixTransposeCmd::undo() { redo(); } //mirror horizontally MatrixMirrorHorizontallyCmd::MatrixMirrorHorizontallyCmd( MatrixPrivate * private_obj, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj) { setText(i18n("%1: mirror horizontally", m_private_obj->name())); } void MatrixMirrorHorizontallyCmd::redo() { int rows = m_private_obj->rowCount; int cols = m_private_obj->columnCount; int middle = cols/2; m_private_obj->suppressDataChange = true; for(int i = 0; i temp = m_private_obj->columnCells(i, 0, rows-1); m_private_obj->setColumnCells(i, 0, rows-1, m_private_obj->columnCells(cols-i-1, 0, rows-1)); m_private_obj->setColumnCells(cols-i-1, 0, rows-1, temp); } m_private_obj->suppressDataChange = false; m_private_obj->emitDataChanged(0, 0, rows-1, cols-1); } void MatrixMirrorHorizontallyCmd::undo() { redo(); } //mirror vertically MatrixMirrorVerticallyCmd::MatrixMirrorVerticallyCmd( MatrixPrivate * private_obj, QUndoCommand * parent) : QUndoCommand( parent ), m_private_obj(private_obj) { setText(i18n("%1: mirror vertically", m_private_obj->name())); } void MatrixMirrorVerticallyCmd::redo() { int rows = m_private_obj->rowCount; int cols = m_private_obj->columnCount; int middle = rows/2; m_private_obj->suppressDataChange = true; for(int i = 0; i temp = m_private_obj->rowCells(i, 0, cols-1); m_private_obj->setRowCells(i, 0, cols-1, m_private_obj->rowCells(rows-i-1, 0, cols-1)); m_private_obj->setRowCells(rows-i-1, 0, cols-1, temp); } m_private_obj->suppressDataChange = false; m_private_obj->emitDataChanged(0, 0, rows-1, cols-1); } void MatrixMirrorVerticallyCmd::undo() { redo(); } //replace values MatrixReplaceValuesCmd::MatrixReplaceValuesCmd(MatrixPrivate* private_obj, void* new_values, QUndoCommand* parent) : QUndoCommand(parent), m_private_obj(private_obj), m_new_values(new_values) { setText(i18n("%1: replace values", m_private_obj->name())); } void MatrixReplaceValuesCmd::redo() { m_old_values = m_private_obj->data; m_private_obj->data = m_new_values; m_private_obj->emitDataChanged(0, 0, m_private_obj->rowCount -1, m_private_obj->columnCount-1); } void MatrixReplaceValuesCmd::undo() { m_new_values = m_private_obj->data; m_private_obj->data = m_old_values; m_private_obj->emitDataChanged(0, 0, m_private_obj->rowCount -1, m_private_obj->columnCount-1); }