diff --git a/src/backend/matrix/Matrix.cpp b/src/backend/matrix/Matrix.cpp index a5505cefc..e25e4688f 100644 --- a/src/backend/matrix/Matrix.cpp +++ b/src/backend/matrix/Matrix.cpp @@ -1,1140 +1,1247 @@ /*************************************************************************** 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. + The values of the matrix are stored as generic values. Each column + of the matrix is stored in a QVector objects. \ingroup backend */ Matrix::Matrix(AbstractScriptingEngine* engine, int rows, int cols, const QString& name, const AbstractColumn::ColumnMode mode) : AbstractDataSource(engine, name), d(new MatrixPrivate(this, mode)), m_model(nullptr) { //set initial number of rows and columns appendColumns(cols); appendRows(rows); init(); } Matrix::Matrix(AbstractScriptingEngine* engine, const QString& name, bool loading, const AbstractColumn::ColumnMode mode) : AbstractDataSource(engine, name), d(new MatrixPrivate(this, mode)), 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 ################################## //############################################################################## void* Matrix::data() const { return d->data; } BASIC_D_READER_IMPL(Matrix, AbstractColumn::ColumnMode, mode, mode) BASIC_D_READER_IMPL(Matrix, int, rowCount, rowCount) BASIC_D_READER_IMPL(Matrix, int, columnCount, columnCount) BASIC_D_READER_IMPL(Matrix, double, xStart, xStart) BASIC_D_READER_IMPL(Matrix, double, xEnd, xEnd) BASIC_D_READER_IMPL(Matrix, double, yStart, yStart) BASIC_D_READER_IMPL(Matrix, double, yEnd, yEnd) BASIC_D_READER_IMPL(Matrix, char, numericFormat, numericFormat) BASIC_D_READER_IMPL(Matrix, int, precision, precision) BASIC_D_READER_IMPL(Matrix, Matrix::HeaderFormat, headerFormat, headerFormat) CLASS_D_READER_IMPL(Matrix, QString, formula, formula) void Matrix::setSuppressDataChangedSignal(bool b) { if (m_model) m_model->setSuppressDataChangedSignal(b); } void Matrix::setChanged() { if (m_model) m_model->setChanged(); } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## void Matrix::setRowCount(int count) { if (count == d->rowCount) return; const int diff = count - d->rowCount; if (diff > 0) appendRows(diff); else if (diff < 0) removeRows(rowCount() + diff, -diff); } void Matrix::setColumnCount(int count) { if (count == d->columnCount) return; const int diff = count - columnCount(); if (diff > 0) appendColumns(diff); else if (diff < 0) removeColumns(columnCount() + diff, -diff); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetXStart, double, xStart, updateViewHeader) void Matrix::setXStart(double xStart) { if (xStart != d->xStart) exec(new MatrixSetXStartCmd(d, xStart, 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)); + switch (d->mode) { + case AbstractColumn::Numeric: + exec(new MatrixRemoveColumnsCmd(d, first, count)); + break; + case AbstractColumn::Text: + exec(new MatrixRemoveColumnsCmd(d, first, count)); + break; + case AbstractColumn::Integer: + exec(new MatrixRemoveColumnsCmd(d, first, count)); + break; + case AbstractColumn::Day: + case AbstractColumn::Month: + case AbstractColumn::DateTime: + exec(new MatrixRemoveColumnsCmd(d, first, count)); + break; + } RESET_CURSOR; } void Matrix::clearColumn(int c) { - exec(new MatrixClearColumnCmd(d, c)); + WAIT_CURSOR; + switch (d->mode) { + case AbstractColumn::Numeric: + exec(new MatrixClearColumnCmd(d, c)); + break; + case AbstractColumn::Text: + exec(new MatrixClearColumnCmd(d, c)); + break; + case AbstractColumn::Integer: + exec(new MatrixClearColumnCmd(d, c)); + break; + case AbstractColumn::Day: + case AbstractColumn::Month: + case AbstractColumn::DateTime: + exec(new MatrixClearColumnCmd(d, c)); + break; + } + RESET_CURSOR; } //rows void Matrix::insertRows(int before, int count) { if (count < 1 || before < 0 || before > rowCount()) return; WAIT_CURSOR; exec(new MatrixInsertRowsCmd(d, before, count)); RESET_CURSOR; } void Matrix::appendRows(int count) { insertRows(rowCount(), count); } void Matrix::removeRows(int first, int count) { if (count < 1 || first < 0 || first+count > rowCount()) return; WAIT_CURSOR; - exec(new MatrixRemoveRowsCmd(d, first, count)); + switch (d->mode) { + case AbstractColumn::Numeric: + exec(new MatrixRemoveRowsCmd(d, first, count)); + break; + case AbstractColumn::Text: + exec(new MatrixRemoveRowsCmd(d, first, count)); + break; + case AbstractColumn::Integer: + exec(new MatrixRemoveRowsCmd(d, first, count)); + break; + case AbstractColumn::Day: + case AbstractColumn::Month: + case AbstractColumn::DateTime: + exec(new MatrixRemoveRowsCmd(d, first, count)); + break; + } RESET_CURSOR; } void Matrix::clearRow(int r) { switch (d->mode) { case AbstractColumn::Numeric: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, 0.0)); break; case AbstractColumn::Text: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, QString())); break; case AbstractColumn::Integer: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, 0)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, QDateTime())); break; } } //! Return the value in the given cell (needs explicit instantiation) template T Matrix::cell(int row, int col) const { return d->cell(row, col); } template double Matrix::cell(int row, int col) const; //! Return the text displayed in the given cell (needs explicit instantiation) template QString Matrix::text(int row, int col) { return QLocale().toString(cell(row,col), d->numericFormat, d->precision); } template QString Matrix::text(int row, int col); //! Set the value of the cell (needs explicit instantiation) template void Matrix::setCell(int row, int col, T value) { if(row < 0 || row >= rowCount()) return; if(col < 0 || col >= columnCount()) return; exec(new MatrixSetCellValueCmd(d, row, col, value)); } template void Matrix::setCell(int row, int col, double value); void Matrix::clearCell(int row, int col) { switch (d->mode) { case AbstractColumn::Numeric: exec(new MatrixSetCellValueCmd(d, row, col, 0.0)); break; case AbstractColumn::Text: exec(new MatrixSetCellValueCmd(d, row, col, QString())); break; case AbstractColumn::Integer: exec(new MatrixSetCellValueCmd(d, row, col, 0)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: exec(new MatrixSetCellValueCmd(d, row, col, QDateTime())); break; } } void Matrix::setDimensions(int rows, int cols) { if( (rows < 0) || (cols < 0 ) || (rows == rowCount() && cols == columnCount()) ) return; WAIT_CURSOR; beginMacro(i18n("%1: set matrix size to %2x%3", name(), rows, cols)); int col_diff = cols - columnCount(); if (col_diff > 0) insertColumns(columnCount(), col_diff); else if (col_diff < 0) removeColumns(columnCount() + col_diff, -col_diff); int row_diff = rows - rowCount(); if(row_diff > 0) appendRows(row_diff); else if (row_diff < 0) removeRows(rowCount() + row_diff, -row_diff); endMacro(); RESET_CURSOR; } void Matrix::copy(Matrix* other) { WAIT_CURSOR; beginMacro(i18n("%1: copy %2", name(), other->name())); int rows = other->rowCount(); int columns = other->columnCount(); setDimensions(rows, columns); for (int i=0; irowHeight(i)); for (int i=0; icolumnWidth(i)); d->suppressDataChange = true; switch (d->mode) { case AbstractColumn::Numeric: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; case AbstractColumn::Text: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; case AbstractColumn::Integer: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; } setCoordinates(other->xStart(), other->xEnd(), other->yStart(), other->yEnd()); setNumericFormat(other->numericFormat()); setPrecision(other->precision()); d->formula = other->formula(); d->suppressDataChange = false; emit dataChanged(0, 0, rows-1, columns-1); if (m_view) 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 vector template QVector Matrix::columnCells(int col, int first_row, int last_row) { return d->columnCells(col, first_row, last_row); } //! Set the values in the given cells from a double vector template void Matrix::setColumnCells(int col, int first_row, int last_row, const QVector& values) { WAIT_CURSOR; exec(new MatrixSetColumnCellsCmd(d, col, first_row, last_row, values)); RESET_CURSOR; } //! Return the values in the given cells as vector (needs explicit instantiation) template QVector Matrix::rowCells(int row, int first_column, int last_column) { return d->rowCells(row, first_column, last_column); } template QVector Matrix::rowCells(int row, int first_column, int last_column); template QVector Matrix::rowCells(int row, int first_column, int last_column); template QVector Matrix::rowCells(int row, int first_column, int last_column); template QVector Matrix::rowCells(int row, int first_column, int last_column); //! Set the values in the given cells from a double vector template void Matrix::setRowCells(int row, int first_column, int last_column, const QVector& values) { WAIT_CURSOR; - exec(new MatrixSetRowCellsCmd(d, row, first_column, last_column, values)); + exec(new MatrixSetRowCellsCmd(d, row, first_column, last_column, values)); RESET_CURSOR; } void Matrix::setData(void* data) { bool isEmpty = false; switch (d->mode) { case AbstractColumn::Numeric: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; case AbstractColumn::Text: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; case AbstractColumn::Integer: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; } if (!isEmpty) exec(new MatrixReplaceValuesCmd(d, data)); } //############################################################################## //######################### Public slots ##################################### //############################################################################## -//! Clear the whole matrix (i.e. set all cells to 0.0) +//! Clear the whole matrix (i.e. reset all cells) void Matrix::clear() { WAIT_CURSOR; beginMacro(i18n("%1: clear", name())); - exec(new MatrixClearCmd(d)); + switch (d->mode) { + case AbstractColumn::Numeric: + exec(new MatrixClearCmd(d)); + break; + case AbstractColumn::Text: + exec(new MatrixClearCmd(d)); + break; + case AbstractColumn::Integer: + exec(new MatrixClearCmd(d)); + break; + case AbstractColumn::Day: + case AbstractColumn::Month: + case AbstractColumn::DateTime: + exec(new MatrixClearCmd(d)); + break; + } endMacro(); RESET_CURSOR; } void Matrix::transpose() { WAIT_CURSOR; - exec(new MatrixTransposeCmd(d)); + switch (d->mode) { + case AbstractColumn::Numeric: + exec(new MatrixTransposeCmd(d)); + break; + case AbstractColumn::Text: + exec(new MatrixTransposeCmd(d)); + break; + case AbstractColumn::Integer: + exec(new MatrixTransposeCmd(d)); + break; + case AbstractColumn::Day: + case AbstractColumn::Month: + case AbstractColumn::DateTime: + exec(new MatrixTransposeCmd(d)); + break; + } RESET_CURSOR; } void Matrix::mirrorHorizontally() { WAIT_CURSOR; - exec(new MatrixMirrorHorizontallyCmd(d)); + switch (d->mode) { + case AbstractColumn::Numeric: + exec(new MatrixMirrorHorizontallyCmd(d)); + break; + case AbstractColumn::Text: + exec(new MatrixMirrorHorizontallyCmd(d)); + break; + case AbstractColumn::Integer: + exec(new MatrixMirrorHorizontallyCmd(d)); + break; + case AbstractColumn::Day: + case AbstractColumn::Month: + case AbstractColumn::DateTime: + exec(new MatrixMirrorHorizontallyCmd(d)); + break; + } RESET_CURSOR; } void Matrix::mirrorVertically() { WAIT_CURSOR; - exec(new MatrixMirrorVerticallyCmd(d)); + switch (d->mode) { + case AbstractColumn::Numeric: + exec(new MatrixMirrorVerticallyCmd(d)); + break; + case AbstractColumn::Text: + exec(new MatrixMirrorVerticallyCmd(d)); + break; + case AbstractColumn::Integer: + exec(new MatrixMirrorVerticallyCmd(d)); + break; + case AbstractColumn::Day: + case AbstractColumn::Month: + case AbstractColumn::DateTime: + exec(new MatrixMirrorVerticallyCmd(d)); + break; + } RESET_CURSOR; } //############################################################################## //###################### Private implementation ############################### //############################################################################## MatrixPrivate::MatrixPrivate(Matrix* owner, const AbstractColumn::ColumnMode mode) : q(owner), mode(mode), rowCount(0), columnCount(0), suppressDataChange(false) { switch (mode) { case AbstractColumn::Numeric: data = new QVector>(); break; case AbstractColumn::Text: data = new QVector>(); break; case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: data = new QVector>(); break; case AbstractColumn::Integer: data = new QVector>(); break; } } 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); emit q->columnsAboutToBeInserted(before, count); switch (mode) { case AbstractColumn::Numeric: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; case AbstractColumn::Text: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; case AbstractColumn::Integer: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; } columnCount += count; emit q->columnsInserted(before, count); } /*! Remove \c count columns starting with column index \c first */ void MatrixPrivate::removeColumns(int first, int count) { emit q->columnsAboutToBeRemoved(first, count); Q_ASSERT(first >= 0); Q_ASSERT(first + count <= columnCount); switch (mode) { case AbstractColumn::Numeric: (static_cast>*>(data))->remove(first, count); break; case AbstractColumn::Text: (static_cast>*>(data))->remove(first, count); break; case AbstractColumn::Integer: (static_cast>*>(data))->remove(first, count); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: (static_cast>*>(data))->remove(first, count); break; } for (int i = 0; i < count; i++) columnWidths.remove(first); columnCount -= count; emit q->columnsRemoved(first, count); } /*! Insert \c count rows before row with the index \c before */ void MatrixPrivate::insertRows(int before, int count) { emit q->rowsAboutToBeInserted(before, count); Q_ASSERT(before >= 0); Q_ASSERT(before <= rowCount); switch (mode) { case AbstractColumn::Numeric: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, 0.0); break; case AbstractColumn::Text: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, QString()); break; case AbstractColumn::Integer: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, 0); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, QDateTime()); } for(int i=0; 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); switch (mode) { case AbstractColumn::Numeric: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; case AbstractColumn::Text: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; case AbstractColumn::Integer: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; } for (int i = 0; i < count; i++) rowHeights.remove(first); rowCount -= count; emit q->rowsRemoved(first, count); } //! Fill column with zeroes void MatrixPrivate::clearColumn(int col) { switch (mode) { case AbstractColumn::Numeric: static_cast>*>(data)->operator[](col).fill(0.0); break; case AbstractColumn::Text: static_cast>*>(data)->operator[](col).fill(QString()); break; case AbstractColumn::Integer: static_cast>*>(data)->operator[](col).fill(0); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: static_cast>*>(data)->operator[](col).fill(QDateTime()); break; } if (!suppressDataChange) emit q->dataChanged(0, col, rowCount-1, col); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## void Matrix::save(QXmlStreamWriter* writer) const { writer->writeStartElement("matrix"); writeBasicAttributes(writer); writeCommentElement(writer); //formula writer->writeStartElement("formula"); writer->writeCharacters(d->formula); writer->writeEndElement(); //format writer->writeStartElement("format"); writer->writeAttribute("mode", QString::number(d->mode)); writer->writeAttribute("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 switch (d->mode) { case AbstractColumn::Numeric: size = d->rowCount*sizeof(double); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; case AbstractColumn::Text: size = d->rowCount*sizeof(QString); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; case AbstractColumn::Integer: size = d->rowCount*sizeof(int); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: size = d->rowCount*sizeof(QDateTime); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; } writer->writeEndElement(); // "matrix" } bool Matrix::load(XmlStreamReader* reader) { 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("mode").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'mode'")); else d->mode = AbstractColumn::ColumnMode(str.toInt()); 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()); switch (d->mode) { case AbstractColumn::Numeric: { int count = bytes.size()/sizeof(double); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(double)); static_cast>*>(d->data)->append(column); break; } case AbstractColumn::Text: { int count = bytes.size()/sizeof(QString); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(QString)); static_cast>*>(d->data)->append(column); break; } case AbstractColumn::Integer: { int count = bytes.size()/sizeof(int); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(int)); static_cast>*>(d->data)->append(column); break; } case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: { int count = bytes.size()/sizeof(QDateTime); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(QDateTime)); static_cast>*>(d->data)->append(column); break; } } } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } //############################################################################## //######################## Data Import ####################################### //############################################################################## int Matrix::prepareImport(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); } // 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::Integer: 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::Day: case AbstractColumn::Month: 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; } 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 3af077171..1b8c4e141 100644 --- a/src/backend/matrix/Matrix.h +++ b/src/backend/matrix/Matrix.h @@ -1,175 +1,175 @@ /*************************************************************************** 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, const AbstractColumn::ColumnMode = AbstractColumn::Numeric); Matrix(AbstractScriptingEngine* engine, int rows, int cols, const QString& name, const AbstractColumn::ColumnMode = AbstractColumn::Numeric); ~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; void* data() const; void setData(void*); BASIC_D_ACCESSOR_DECL(AbstractColumn::ColumnMode, mode, Mode) BASIC_D_ACCESSOR_DECL(int, rowCount, RowCount) BASIC_D_ACCESSOR_DECL(int, columnCount, ColumnCount) BASIC_D_ACCESSOR_DECL(char, numericFormat, NumericFormat) BASIC_D_ACCESSOR_DECL(int, precision, Precision) BASIC_D_ACCESSOR_DECL(HeaderFormat, headerFormat, HeaderFormat) BASIC_D_ACCESSOR_DECL(double, xStart, XStart) BASIC_D_ACCESSOR_DECL(double, xEnd, XEnd) BASIC_D_ACCESSOR_DECL(double, yStart, YStart) BASIC_D_ACCESSOR_DECL(double, yEnd, YEnd) CLASS_D_ACCESSOR_DECL(QString, formula, Formula) void setSuppressDataChangedSignal(bool); void setChanged(); int rowHeight(int row) const; void setRowHeight(int row, int height); int columnWidth(int col) const; void setColumnWidth(int col, int width); void setDimensions(int rows, int cols); void setCoordinates(double x1, double x2, double y1, double y2); void insertColumns(int before, int count); void appendColumns(int count); void removeColumns(int first, int count); void clearColumn(int); void insertRows(int before, int count); void appendRows(int count); void removeRows(int first, int count); void clearRow(int); template T cell(int row, int col) const; template QString text(int row, int col); template void setCell(int row, int col, T value); void clearCell(int row, int col); template QVector columnCells(int col, int first_row, int last_row); template void setColumnCells(int col, int first_row, int last_row, const QVector& values); template QVector rowCells(int row, int first_column, int last_column); template void setRowCells(int row, int first_column, int last_column, const QVector& values); void copy(Matrix* other); 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; mutable MatrixModel* m_model; friend class MatrixPrivate; + template friend class MatrixRemoveColumnsCmd; + template friend class MatrixRemoveRowsCmd; }; #endif diff --git a/src/backend/matrix/matrixcommands.cpp b/src/backend/matrix/matrixcommands.cpp index f7ed4751e..e141e13f6 100644 --- a/src/backend/matrix/matrixcommands.cpp +++ b/src/backend/matrix/matrixcommands.cpp @@ -1,319 +1,122 @@ /*************************************************************************** File : matrixcommands.cpp Project : LabPlot Description : Commands used in Matrix (part of the undo/redo framework) -------------------------------------------------------------------- Copyright : (C) 2008 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 * * * ***************************************************************************/ #include "matrixcommands.h" #include "MatrixPrivate.h" //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) -{ + : 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; - //TODO: consider mode - for (int i = 0; i < m_count; i++) - m_backups.append(m_private_obj->columnCells(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; i < m_count; i++) - m_private_obj->setColumnCells(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; - //TODO: consider mode - for (int col = 0; col < m_private_obj->columnCount; 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; col < m_private_obj->columnCount; 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; - //TODO: mode - for (int i = 0; i < m_private_obj->columnCount; i++) - m_backups.append(m_private_obj->columnCells(i, 0, last_row)); - } - for (int i = 0; i < m_private_obj->columnCount; i++) - m_private_obj->clearColumn(i); -} - -void MatrixClearCmd::undo() { - int last_row = m_private_obj->rowCount-1; - for (int i = 0; i < m_private_obj->columnCount; 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() { - //TODO: mode - 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 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) { + : 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 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() { - //TODO: mode - 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); - //TODO: mode - for (int i = 1; i < temp_size; i++) { - QVector 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; - //TODO: mode - 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; - //TODO: mode - for (int i = 0; i < middle; i++) { - QVector 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); } diff --git a/src/backend/matrix/matrixcommands.h b/src/backend/matrix/matrixcommands.h index 5bff334a5..ee8de1354 100644 --- a/src/backend/matrix/matrixcommands.h +++ b/src/backend/matrix/matrixcommands.h @@ -1,265 +1,410 @@ /*************************************************************************** File : matrixcommands.h Project : LabPlot Description : Commands used in Matrix (part of the undo/redo framework) -------------------------------------------------------------------- - Copyright : (C) 2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2008 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 MATRIX_COMMANDS_H #define MATRIX_COMMANDS_H #include #include #include #include "Matrix.h" #include "MatrixPrivate.h" //! Insert columns class MatrixInsertColumnsCmd : public QUndoCommand { public: - MatrixInsertColumnsCmd(MatrixPrivate* private_obj, int before, int count, QUndoCommand* parent = 0); + MatrixInsertColumnsCmd(MatrixPrivate*, int before, int count, QUndoCommand* = 0); virtual void redo(); virtual void undo(); private: MatrixPrivate* m_private_obj; int m_before; //! Column to insert before int m_count; //! The number of new columns }; //! Insert rows class MatrixInsertRowsCmd : public QUndoCommand { public: - MatrixInsertRowsCmd(MatrixPrivate* private_obj, int before, int count, QUndoCommand* parent = 0); + MatrixInsertRowsCmd(MatrixPrivate*, int before, int count, QUndoCommand* = 0); virtual void redo(); virtual void undo(); private: MatrixPrivate* m_private_obj; int m_before; //! Row to insert before int m_count; //! The number of new rows }; //! Remove columns +template class MatrixRemoveColumnsCmd : public QUndoCommand { public: - MatrixRemoveColumnsCmd(MatrixPrivate* private_obj, int first, int count, QUndoCommand* parent = 0); - virtual void redo(); - virtual void undo(); + MatrixRemoveColumnsCmd(MatrixPrivate* private_obj, int first, int count, QUndoCommand* parent = 0) + : 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)); + } + virtual void redo() { + if(m_backups.isEmpty()) { + int last_row = m_private_obj->rowCount-1; + for (int i = 0; i < m_count; i++) + m_backups.append(m_private_obj->columnCells(m_first+i, 0, last_row)); + } + m_private_obj->removeColumns(m_first, m_count); + emit m_private_obj->q->columnCountChanged(m_private_obj->columnCount); + } + virtual void 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; i < m_count; i++) + m_private_obj->setColumnCells(m_first+i, 0, last_row, m_backups.at(i)); + + emit m_private_obj->q->columnCountChanged(m_private_obj->columnCount); + } private: MatrixPrivate* m_private_obj; int m_first; //! First column to remove int m_count; //! The number of columns to remove - QVector< QVector > m_backups; //! Backups of the removed columns + QVector> m_backups; //! Backups of the removed columns }; //! Remove rows +template class MatrixRemoveRowsCmd : public QUndoCommand { public: - MatrixRemoveRowsCmd(MatrixPrivate* private_obj, int first, int count, QUndoCommand* parent = 0); - virtual void redo(); - virtual void undo(); + MatrixRemoveRowsCmd(MatrixPrivate* private_obj, int first, int count, QUndoCommand* parent = 0) + : 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)); + } + virtual void redo() { + if(m_backups.isEmpty()) { + int last_row = m_first+m_count-1; + for (int col = 0; col < m_private_obj->columnCount; 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); + } + virtual void undo() { + m_private_obj->insertRows(m_first, m_count); + int last_row = m_first+m_count-1; + for (int col = 0; col < m_private_obj->columnCount; col++) + m_private_obj->setColumnCells(col, m_first, last_row, m_backups.at(col)); + emit m_private_obj->q->rowCountChanged(m_private_obj->rowCount); + } private: MatrixPrivate* m_private_obj; int m_first; //! First row to remove int m_count; //! The number of rows to remove - QVector< QVector > m_backups; //! Backups of the removed rows + QVector< QVector > m_backups; //! Backups of the removed rows }; //! Clear matrix +template class MatrixClearCmd : public QUndoCommand { public: - explicit MatrixClearCmd(MatrixPrivate* private_obj, QUndoCommand* parent = 0); - virtual void redo(); - virtual void undo(); + explicit MatrixClearCmd(MatrixPrivate* private_obj, QUndoCommand* parent = 0) + : QUndoCommand(parent), m_private_obj(private_obj) { + setText(i18n("%1: clear", m_private_obj->name())); + } + virtual void redo() { + if(m_backups.isEmpty()) { + int last_row = m_private_obj->rowCount-1; + + for (int i = 0; i < m_private_obj->columnCount; i++) + m_backups.append(m_private_obj->columnCells(i, 0, last_row)); + } + + for (int i = 0; i < m_private_obj->columnCount; i++) + m_private_obj->clearColumn(i); + } + virtual void undo() { + int last_row = m_private_obj->rowCount-1; + for (int i = 0; i < m_private_obj->columnCount; i++) + m_private_obj->setColumnCells(i, 0, last_row, m_backups.at(i)); + } private: MatrixPrivate* m_private_obj; - QVector< QVector > m_backups; //! Backups of the cleared cells + QVector> m_backups; //! Backups of the cleared cells }; //! Clear matrix column +template class MatrixClearColumnCmd : public QUndoCommand { public: - MatrixClearColumnCmd(MatrixPrivate* private_obj, int col, QUndoCommand* parent = 0); - virtual void redo(); - virtual void undo(); + MatrixClearColumnCmd(MatrixPrivate* private_obj, int col, QUndoCommand* parent = 0) + : QUndoCommand(parent), m_private_obj(private_obj), m_col(col) { + setText(i18n("%1: clear column %2", m_private_obj->name(), m_col+1)); + } + virtual void 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); + } + virtual void undo() { + m_private_obj->setColumnCells(m_col, 0, m_private_obj->rowCount-1, m_backup); + } private: MatrixPrivate* m_private_obj; int m_col; //! The index of the column - QVector m_backup; //! Backup of the cleared column + QVector m_backup; //! Backup of the cleared column }; // Set cell value template class MatrixSetCellValueCmd : public QUndoCommand { public: MatrixSetCellValueCmd(MatrixPrivate* private_obj, int row, int col, T value, QUndoCommand* parent = 0) : 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())); } virtual void redo() { m_old_value = m_private_obj->cell(m_row, m_col); m_private_obj->setCell(m_row, m_col, m_value); } virtual void undo() { m_private_obj->setCell(m_row, m_col, m_old_value); } private: MatrixPrivate* m_private_obj; int m_row; //! The index of the row int m_col; //! The index of the column T m_value; //! New cell value T m_old_value; //! Backup of the changed value }; // Set matrix coordinates class MatrixSetCoordinatesCmd : public QUndoCommand { public: - MatrixSetCoordinatesCmd(MatrixPrivate* private_obj, double x1, double x2, double y1, double y2, QUndoCommand* parent = 0); + MatrixSetCoordinatesCmd(MatrixPrivate*, double x1, double x2, double y1, double y2, QUndoCommand* = 0); virtual void redo(); virtual void undo(); private: MatrixPrivate* m_private_obj; double m_new_x1; double m_new_x2; double m_new_y1; double m_new_y2; double m_old_x1; double m_old_x2; double m_old_y1; double m_old_y2; }; //! Set matrix formula class MatrixSetFormulaCmd : public QUndoCommand { public: - MatrixSetFormulaCmd(MatrixPrivate* private_obj, QString formula); + MatrixSetFormulaCmd(MatrixPrivate*, QString formula); virtual void redo(); virtual void undo(); private: MatrixPrivate* m_private_obj; QString m_other_formula; }; // Set cell values for (a part of) a column at once template class MatrixSetColumnCellsCmd : public QUndoCommand { public: MatrixSetColumnCellsCmd(MatrixPrivate* private_obj, int col, int first_row, int last_row, const QVector& values, QUndoCommand* parent = 0) : 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())); } - virtual void 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); } virtual void undo() { m_private_obj->setColumnCells(m_col, m_first_row, m_last_row, m_old_values); } private: MatrixPrivate* m_private_obj; int m_col; //! The index of the column int m_first_row; //! The index of the first row int m_last_row; //! The index of the last row QVector m_values; //! New cell values QVector m_old_values; //! Backup of the changed values }; //! Set cell values for (a part of) a row at once +template class MatrixSetRowCellsCmd : public QUndoCommand { public: - MatrixSetRowCellsCmd(MatrixPrivate* private_obj, int row, int first_column, int last_column, const QVector& values, QUndoCommand* parent = 0); - virtual void redo(); - virtual void undo(); + MatrixSetRowCellsCmd(MatrixPrivate* private_obj, int row, int first_column, int last_column, const QVector& values, QUndoCommand* parent = 0) + : 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())); + } + virtual void 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); + } + virtual void undo() { + m_private_obj->setRowCells(m_row, m_first_column, m_last_column, m_old_values); + } private: MatrixPrivate* m_private_obj; int m_row; //! The index of the row int m_first_column; //! The index of the first column int m_last_column; //! The index of the last column - QVector m_values; //! New cell values - QVector m_old_values; //! Backup of the changed values + QVector m_values; //! New cell values + QVector m_old_values; //! Backup of the changed values }; //! Transpose the matrix +template class MatrixTransposeCmd : public QUndoCommand { public: - explicit MatrixTransposeCmd(MatrixPrivate* private_obj, QUndoCommand* parent = 0); - virtual void redo(); - virtual void undo(); + explicit MatrixTransposeCmd(MatrixPrivate* private_obj, QUndoCommand* parent = 0) + : QUndoCommand(parent), m_private_obj(private_obj) { + setText(i18n("%1: transpose", m_private_obj->name())); + } + virtual void 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 < temp_size; i++) { + QVector 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); + } + virtual void undo() { + redo(); + } private: MatrixPrivate* m_private_obj; }; //! Mirror the matrix horizontally +template class MatrixMirrorHorizontallyCmd : public QUndoCommand { public: - explicit MatrixMirrorHorizontallyCmd(MatrixPrivate* private_obj, QUndoCommand* parent = 0); - virtual void redo(); - virtual void undo(); + explicit MatrixMirrorHorizontallyCmd(MatrixPrivate* private_obj, QUndoCommand* parent = 0) + : QUndoCommand(parent), m_private_obj(private_obj) { + setText(i18n("%1: mirror horizontally", m_private_obj->name())); + } + virtual void 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); + } + virtual void undo() { + redo(); + } private: MatrixPrivate* m_private_obj; }; // Mirror the matrix vertically +template class MatrixMirrorVerticallyCmd : public QUndoCommand { public: - explicit MatrixMirrorVerticallyCmd(MatrixPrivate* private_obj, QUndoCommand* parent = 0); - virtual void redo(); - virtual void undo(); + explicit MatrixMirrorVerticallyCmd(MatrixPrivate* private_obj, QUndoCommand* parent = 0) + : QUndoCommand(parent), m_private_obj(private_obj) { + setText(i18n("%1: mirror vertically", m_private_obj->name())); + } + virtual void 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 < middle; i++) { + QVector 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); + } + virtual void undo() { + redo(); + } private: MatrixPrivate* m_private_obj; }; // Replace matrix values class MatrixReplaceValuesCmd : public QUndoCommand { public: - explicit MatrixReplaceValuesCmd(MatrixPrivate* private_obj, void* new_values, QUndoCommand* parent = 0); + explicit MatrixReplaceValuesCmd(MatrixPrivate*, void* new_values, QUndoCommand* = 0); virtual void redo(); virtual void undo(); private: MatrixPrivate* m_private_obj; void* m_old_values; void* m_new_values; }; #endif // MATRIX_COMMANDS_H diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp index 6cdb76e69..aca7b8e8a 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -1,2662 +1,2699 @@ /*************************************************************************** File : CartesianPlot.cpp Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2017 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CartesianPlot.h" #include "CartesianPlotPrivate.h" #include "Axis.h" #include "XYCurve.h" #include "Histogram.h" #include "XYEquationCurve.h" #include "XYDataReductionCurve.h" #include "XYDifferentiationCurve.h" #include "XYIntegrationCurve.h" #include "XYInterpolationCurve.h" #include "XYSmoothCurve.h" #include "XYFitCurve.h" #include "XYFourierFilterCurve.h" #include "XYFourierTransformCurve.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "kdefrontend/ThemeHandler.h" #include "kdefrontend/widgets/ThemesWidget.h" #include #include #include #include #include #include #include #include #include // DBL_MAX /** * \class CartesianPlot * \brief A xy-plot. * * */ CartesianPlot::CartesianPlot(const QString &name):AbstractPlot(name, new CartesianPlotPrivate(this)), m_legend(0), m_zoomFactor(1.2) { init(); } CartesianPlot::CartesianPlot(const QString &name, CartesianPlotPrivate *dd):AbstractPlot(name, dd), m_legend(0), m_zoomFactor(1.2) { init(); } CartesianPlot::~CartesianPlot() { delete m_coordinateSystem; delete addNewMenu; delete zoomMenu; delete themeMenu; //don't need to delete objects added with addChild() //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } /*! initializes all member variables of \c CartesianPlot */ void CartesianPlot::init() { Q_D(CartesianPlot); d->cSystem = new CartesianCoordinateSystem(this); m_coordinateSystem = d->cSystem; + d->rangeType = CartesianPlot::RangeFree; + d->rangeLastValues = 1000; + d->rangeFirstValues = 1000; d->autoScaleX = true; d->autoScaleY = true; d->xScale = ScaleLinear; d->yScale = ScaleLinear; d->xRangeBreakingEnabled = false; d->yRangeBreakingEnabled = false; //the following factor determines the size of the offset between the min/max points of the curves //and the coordinate system ranges, when doing auto scaling //Factor 1 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges. d->autoScaleOffsetFactor = 0.05; //TODO: make this factor optional. //Provide in the UI the possibility to choose between "exact" or 0% offset, 2%, 5% and 10% for the auto fit option m_plotArea = new PlotArea(name() + " plot area"); addChild(m_plotArea); //offset between the plot area and the area defining the coordinate system, in scene units. d->horizontalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); initActions(); initMenus(); connect(this, SIGNAL(aspectAdded(const AbstractAspect*)), this, SLOT(childAdded(const AbstractAspect*))); connect(this, SIGNAL(aspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*)), this, SLOT(childRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*))); graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true); } /*! initializes all children of \c CartesianPlot and setups a default plot of type \c type with a plot title. */ void CartesianPlot::initDefault(Type type) { Q_D(CartesianPlot); switch (type) { case FourAxes: { d->xMin = 0; d->xMax = 1; d->yMin = 0; d->yMax = 1; //Axes - Axis *axis = new Axis("x axis 1", this, Axis::AxisHorizontal); + Axis* axis = new Axis("x axis 1", this, Axis::AxisHorizontal); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); QPen pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis = new Axis("x axis 2", this, Axis::AxisHorizontal); addChild(axis); axis->setPosition(Axis::AxisTop); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis = new Axis("y axis 1", this, Axis::AxisVertical); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis = new Axis("y axis 2", this, Axis::AxisVertical); addChild(axis); axis->setPosition(Axis::AxisRight); axis->setStart(0); axis->setEnd(1); axis->setOffset(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); break; } case TwoAxes: { d->xMin = 0; d->xMax = 1; d->yMin = 0; d->yMax = 1; - Axis *axis = new Axis("x axis 1", this, Axis::AxisHorizontal); + Axis* axis = new Axis("x axis 1", this, Axis::AxisHorizontal); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis = new Axis("y axis 1", this, Axis::AxisVertical); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); break; } case TwoAxesCentered: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); - Axis *axis = new Axis("x axis 1", this, Axis::AxisHorizontal); + Axis* axis = new Axis("x axis 1", this, Axis::AxisHorizontal); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis = new Axis("y axis 1", this, Axis::AxisVertical); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); break; } case TwoAxesCenteredZero: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); - Axis *axis = new Axis("x axis 1", this, Axis::AxisHorizontal); + Axis* axis = new Axis("x axis 1", this, Axis::AxisHorizontal); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis = new Axis("y axis 1", this, Axis::AxisVertical); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); break; } } d->xMinPrev = d->xMin; d->xMaxPrev = d->xMax; d->yMinPrev = d->yMin; d->yMaxPrev = d->yMax; //Plot title m_title = new TextLabel(this->name(), TextLabel::PlotTitle); addChild(m_title); m_title->setHidden(true); m_title->setParentGraphicsItem(m_plotArea->graphicsItem()); //Geometry, specify the plot rect in scene coordinates. //TODO: Use default settings for left, top, width, height and for min/max for the coordinate system float x = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float y = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float w = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); float h = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); //all plot children are initialized -> set the geometry of the plot in scene coordinates. d->rect = QRectF(x,y,w,h); d->retransform(); } void CartesianPlot::initActions() { //"add new" actions addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this); addHistogramPlot = new QAction(QIcon::fromTheme("labplot-xy-fourier_filter-curve"), i18n("Histogram"), this); addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical equation"), this); // no icons yet addDataReductionCurveAction = new QAction(i18n("xy-curve from a data reduction"), this); addDifferentiationCurveAction = new QAction(i18n("xy-curve from a differentiation"), this); addIntegrationCurveAction = new QAction(i18n("xy-curve from an integration"), this); addInterpolationCurveAction = new QAction(i18n("xy-curve from an interpolation"), this); addSmoothCurveAction = new QAction(i18n("xy-curve from a smooth"), this); addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("xy-curve from a fit to data"), this); addFourierFilterCurveAction = new QAction(i18n("xy-curve from a Fourier filter"), this); addFourierTransformCurveAction = new QAction(i18n("xy-curve from a Fourier transform"), this); // addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("xy-curve from an interpolation"), this); // addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smooth-curve"), i18n("xy-curve from a smooth"), this); // addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_filter-curve"), i18n("xy-curve from a Fourier filter"), this); // addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_transform-curve"), i18n("xy-curve from a Fourier transform"), this); addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("legend"), this); addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("horizontal axis"), this); addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("vertical axis"), this); addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("custom point"), this); connect(addCurveAction, SIGNAL(triggered()), SLOT(addCurve())); connect(addHistogramPlot,SIGNAL(triggered()), SLOT(addHistogram())); connect(addEquationCurveAction, SIGNAL(triggered()), SLOT(addEquationCurve())); connect(addDataReductionCurveAction, SIGNAL(triggered()), SLOT(addDataReductionCurve())); connect(addDifferentiationCurveAction, SIGNAL(triggered()), SLOT(addDifferentiationCurve())); connect(addIntegrationCurveAction, SIGNAL(triggered()), SLOT(addIntegrationCurve())); connect(addInterpolationCurveAction, SIGNAL(triggered()), SLOT(addInterpolationCurve())); connect(addSmoothCurveAction, SIGNAL(triggered()), SLOT(addSmoothCurve())); connect(addFitCurveAction, SIGNAL(triggered()), SLOT(addFitCurve())); connect(addFourierFilterCurveAction, SIGNAL(triggered()), SLOT(addFourierFilterCurve())); connect(addFourierTransformCurveAction, SIGNAL(triggered()), SLOT(addFourierTransformCurve())); connect(addLegendAction, SIGNAL(triggered()), SLOT(addLegend())); connect(addHorizontalAxisAction, SIGNAL(triggered()), SLOT(addHorizontalAxis())); connect(addVerticalAxisAction, SIGNAL(triggered()), SLOT(addVerticalAxis())); connect(addCustomPointAction, SIGNAL(triggered()), SLOT(addCustomPoint())); //Analysis menu actions addDataOperationAction = new QAction(i18n("Data operation"), this); addDataReductionAction = new QAction(i18n("Reduce data"), this); addDifferentiationAction = new QAction(i18n("Differentiate"), this); addIntegrationAction = new QAction(i18n("Integrate"), this); addInterpolationAction = new QAction(i18n("Interpolate"), this); addSmoothAction = new QAction(i18n("Smooth"), this); addFitAction.append(new QAction(i18n("Linear"), this)); addFitAction.append(new QAction(i18n("Power"), this)); addFitAction.append(new QAction(i18n("Exponential (degree 1)"), this)); addFitAction.append(new QAction(i18n("Exponential (degree 2)"), this)); addFitAction.append(new QAction(i18n("Inverse exponential"), this)); addFitAction.append(new QAction(i18n("Gauss"), this)); addFitAction.append(new QAction(i18n("Cauchy-Lorentz"), this)); addFitAction.append(new QAction(i18n("Arc Tangent"), this)); addFitAction.append(new QAction(i18n("Hyperbolic tangent"), this)); addFitAction.append(new QAction(i18n("Error function"), this)); addFitAction.append(new QAction(i18n("Custom"), this)); addFourierFilterAction = new QAction(i18n("Fourier filter"), this); connect(addDataReductionAction, SIGNAL(triggered()), SLOT(addDataReductionCurve())); connect(addDifferentiationAction, SIGNAL(triggered()), SLOT(addDifferentiationCurve())); connect(addIntegrationAction, SIGNAL(triggered()), SLOT(addIntegrationCurve())); connect(addInterpolationAction, SIGNAL(triggered()), SLOT(addInterpolationCurve())); connect(addSmoothAction, SIGNAL(triggered()), SLOT(addSmoothCurve())); for (const auto& action: addFitAction) connect(action, SIGNAL(triggered()), SLOT(addFitCurve())); connect(addFourierFilterAction, SIGNAL(triggered()), SLOT(addFourierFilterCurve())); //zoom/navigate actions scaleAutoAction = new QAction(QIcon::fromTheme("labplot-auto-scale-all"), i18n("auto scale"), this); scaleAutoXAction = new QAction(QIcon::fromTheme("labplot-auto-scale-x"), i18n("auto scale X"), this); scaleAutoYAction = new QAction(QIcon::fromTheme("labplot-auto-scale-y"), i18n("auto scale Y"), this); zoomInAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("zoom in"), this); zoomOutAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("zoom out"), this); zoomInXAction = new QAction(QIcon::fromTheme("labplot-zoom-in-x"), i18n("zoom in X"), this); zoomOutXAction = new QAction(QIcon::fromTheme("labplot-zoom-out-x"), i18n("zoom out X"), this); zoomInYAction = new QAction(QIcon::fromTheme("labplot-zoom-in-y"), i18n("zoom in Y"), this); zoomOutYAction = new QAction(QIcon::fromTheme("labplot-zoom-out-y"), i18n("zoom out Y"), this); shiftLeftXAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("shift left X"), this); shiftRightXAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("shift right X"), this); shiftUpYAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("shift up Y"), this); shiftDownYAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("shift down Y"), this); connect(scaleAutoAction, SIGNAL(triggered()), SLOT(scaleAuto())); connect(scaleAutoXAction, SIGNAL(triggered()), SLOT(scaleAutoX())); connect(scaleAutoYAction, SIGNAL(triggered()), SLOT(scaleAutoY())); connect(zoomInAction, SIGNAL(triggered()), SLOT(zoomIn())); connect(zoomOutAction, SIGNAL(triggered()), SLOT(zoomOut())); connect(zoomInXAction, SIGNAL(triggered()), SLOT(zoomInX())); connect(zoomOutXAction, SIGNAL(triggered()), SLOT(zoomOutX())); connect(zoomInYAction, SIGNAL(triggered()), SLOT(zoomInY())); connect(zoomOutYAction, SIGNAL(triggered()), SLOT(zoomOutY())); connect(shiftLeftXAction, SIGNAL(triggered()), SLOT(shiftLeftX())); connect(shiftRightXAction, SIGNAL(triggered()), SLOT(shiftRightX())); connect(shiftUpYAction, SIGNAL(triggered()), SLOT(shiftUpY())); connect(shiftDownYAction, SIGNAL(triggered()), SLOT(shiftDownY())); //visibility action visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, SIGNAL(triggered()), this, SLOT(visibilityChanged())); } void CartesianPlot::initMenus() { addNewMenu = new QMenu(i18n("Add new")); addNewMenu->addAction(addCurveAction); addNewMenu->addAction(addHistogramPlot); addNewMenu->addAction(addEquationCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addDataReductionCurveAction); addNewMenu->addAction(addDifferentiationCurveAction); addNewMenu->addAction(addIntegrationCurveAction); addNewMenu->addAction(addInterpolationCurveAction); addNewMenu->addAction(addSmoothCurveAction); addNewMenu->addAction(addFitCurveAction); addNewMenu->addAction(addFourierFilterCurveAction); addNewMenu->addAction(addFourierTransformCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addLegendAction); addNewMenu->addSeparator(); addNewMenu->addAction(addHorizontalAxisAction); addNewMenu->addAction(addVerticalAxisAction); addNewMenu->addSeparator(); addNewMenu->addAction(addCustomPointAction); zoomMenu = new QMenu(i18n("Zoom")); zoomMenu->addAction(scaleAutoAction); zoomMenu->addAction(scaleAutoXAction); zoomMenu->addAction(scaleAutoYAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInAction); zoomMenu->addAction(zoomOutAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInXAction); zoomMenu->addAction(zoomOutXAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInYAction); zoomMenu->addAction(zoomOutYAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftLeftXAction); zoomMenu->addAction(shiftRightXAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftUpYAction); zoomMenu->addAction(shiftDownYAction); // Data manipulation menu QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation")); dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); dataManipulationMenu->addAction(addDataOperationAction); dataManipulationMenu->addAction(addDataReductionAction); // Data fit menu QMenu* dataFitMenu = new QMenu(i18n("Fit")); dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve")); dataFitMenu->addAction(addFitAction.at(0)); dataFitMenu->addAction(addFitAction.at(1)); dataFitMenu->addAction(addFitAction.at(2)); dataFitMenu->addAction(addFitAction.at(3)); dataFitMenu->addAction(addFitAction.at(4)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(5)); dataFitMenu->addAction(addFitAction.at(6)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(7)); dataFitMenu->addAction(addFitAction.at(8)); dataFitMenu->addAction(addFitAction.at(9)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(10)); //analysis menu dataAnalysisMenu = new QMenu(i18n("Analysis")); dataAnalysisMenu->insertMenu(0, dataManipulationMenu); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addDifferentiationAction); dataAnalysisMenu->addAction(addIntegrationAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addInterpolationAction); dataAnalysisMenu->addAction(addSmoothAction); dataAnalysisMenu->addAction(addFourierFilterAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addMenu(dataFitMenu); //themes menu themeMenu = new QMenu(i18n("Apply Theme")); ThemesWidget* themeWidget = new ThemesWidget(0); // TODO: SLOT: loadTheme(KConfig config) connect(themeWidget, SIGNAL(themeSelected(QString)), this, SLOT(loadTheme(QString))); connect(themeWidget, SIGNAL(themeSelected(QString)), themeMenu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(themeWidget); themeMenu->addAction(widgetAction); } QMenu* CartesianPlot::createContextMenu() { QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); menu->insertMenu(firstAction, addNewMenu); menu->insertMenu(firstAction, zoomMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, themeMenu); menu->insertSeparator(firstAction); return menu; } QMenu* CartesianPlot::analysisMenu() const { return dataAnalysisMenu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlot::icon() const { return QIcon::fromTheme("office-chart-line"); } void CartesianPlot::navigate(CartesianPlot::NavigationOperation op) { if (op == ScaleAuto) scaleAuto(); else if (op == ScaleAutoX) scaleAutoX(); else if (op == ScaleAutoY) scaleAutoY(); else if (op == ZoomIn) zoomIn(); else if (op == ZoomOut) zoomOut(); else if (op == ZoomInX) zoomInX(); else if (op == ZoomOutX) zoomOutX(); else if (op == ZoomInY) zoomInY(); else if (op == ZoomOutY) zoomOutY(); else if (op == ShiftLeftX) shiftLeftX(); else if (op == ShiftRightX) shiftRightX(); else if (op == ShiftUpY) shiftUpY(); else if (op == ShiftDownY) shiftDownY(); } //############################################################################## //################################ getter methods ############################ //############################################################################## +BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeType, rangeType, rangeType) +BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeLastValues, rangeLastValues) +BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeFirstValues, rangeFirstValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleX, autoScaleX) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, xMin, xMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, xMax, xMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, xScale, xScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, xRangeBreakingEnabled, xRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, xRangeBreaks, xRangeBreaks) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleY, autoScaleY) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, yMin, yMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, yMax, yMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, yScale, yScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, yRangeBreakingEnabled, yRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, yRangeBreaks, yRangeBreaks) CLASS_SHARED_D_READER_IMPL(CartesianPlot, QString, theme, theme) /*! return the actual bounding rectangular of the plot (plot's rectangular minus padding) in plot's coordinates */ //TODO: return here a private variable only, update this variable on rect and padding changes. QRectF CartesianPlot::plotRect() { Q_D(const CartesianPlot); QRectF rect = d->mapRectFromScene(d->rect); rect.setX(rect.x() + d->horizontalPadding); rect.setY(rect.y() + d->verticalPadding); rect.setWidth(rect.width() - d->horizontalPadding); rect.setHeight(rect.height()-d->verticalPadding); return rect; } CartesianPlot::MouseMode CartesianPlot::mouseMode() const { Q_D(const CartesianPlot); return d->mouseMode; } //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## /*! set the rectangular, defined in scene coordinates */ class CartesianPlotSetRectCmd : public QUndoCommand { public: CartesianPlotSetRectCmd(CartesianPlotPrivate* private_obj, QRectF rect) : m_private(private_obj), m_rect(rect) { setText(i18n("%1: change geometry rect", m_private->name())); }; virtual void redo() { QRectF tmp = m_private->rect; const double horizontalRatio = m_rect.width() / m_private->rect.width(); const double verticalRatio = m_rect.height() / m_private->rect.height(); m_private->q->handleResize(horizontalRatio, verticalRatio, false); m_private->rect = m_rect; m_rect = tmp; m_private->retransform(); emit m_private->q->rectChanged(m_private->rect); }; virtual void undo() { redo(); } private: CartesianPlotPrivate* m_private; QRectF m_rect; }; void CartesianPlot::setRect(const QRectF& rect) { Q_D(CartesianPlot); if (rect != d->rect) exec(new CartesianPlotSetRectCmd(d, rect)); } +STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, retransformScales); +void CartesianPlot::setRangeType(RangeType type) { + Q_D(CartesianPlot); + if (type != d->rangeType) + exec(new CartesianPlotSetRangeTypeCmd(d, type, i18n("%1: set range type"))); +} + +STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, retransformScales); +void CartesianPlot::setRangeLastValues(int values) { + Q_D(CartesianPlot); + if (values != d->rangeLastValues) + exec(new CartesianPlotSetRangeLastValuesCmd(d, values, i18n("%1: set range"))); +} + +STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, retransformScales); +void CartesianPlot::setRangeFirstValues(int values) { + Q_D(CartesianPlot); + if (values != d->rangeFirstValues) + exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, i18n("%1: set range"))); +} + + class CartesianPlotSetAutoScaleXCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change x-range auto scaling", m_private->name())); }; virtual void redo() { m_autoScaleOld = m_private->autoScaleX; if (m_autoScale) { m_minOld = m_private->xMin; m_maxOld = m_private->xMax; m_private->q->scaleAutoX(); } m_private->autoScaleX = m_autoScale; emit m_private->q->xAutoScaleChanged(m_autoScale); }; virtual void undo() { if (!m_autoScaleOld) { m_private->xMin = m_minOld; m_private->xMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleX = m_autoScaleOld; emit m_private->q->xAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; float m_minOld; float m_maxOld; }; void CartesianPlot::setAutoScaleX(bool autoScaleX) { Q_D(CartesianPlot); if (autoScaleX != d->autoScaleX) exec(new CartesianPlotSetAutoScaleXCmd(d, autoScaleX)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, float, xMin, retransformScales) void CartesianPlot::setXMin(float xMin) { Q_D(CartesianPlot); if (xMin != d->xMin) exec(new CartesianPlotSetXMinCmd(d, xMin, i18n("%1: set min x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, float, xMax, retransformScales); void CartesianPlot::setXMax(float xMax) { Q_D(CartesianPlot); if (xMax != d->xMax) exec(new CartesianPlotSetXMaxCmd(d, xMax, i18n("%1: set max x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales); void CartesianPlot::setXScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->xScale) exec(new CartesianPlotSetXScaleCmd(d, scale, i18n("%1: set x scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreakingEnabled, bool, xRangeBreakingEnabled, retransformScales) void CartesianPlot::setXRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->xRangeBreakingEnabled) exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, i18n("%1: x-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreaks, CartesianPlot::RangeBreaks, xRangeBreaks, retransformScales); void CartesianPlot::setXRangeBreaks(const RangeBreaks& breakings) { Q_D(CartesianPlot); exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, i18n("%1: x-range breaks changed"))); } class CartesianPlotSetAutoScaleYCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change y-range auto scaling", m_private->name())); }; virtual void redo() { m_autoScaleOld = m_private->autoScaleY; if (m_autoScale) { m_minOld = m_private->yMin; m_maxOld = m_private->yMax; m_private->q->scaleAutoY(); } m_private->autoScaleY = m_autoScale; emit m_private->q->yAutoScaleChanged(m_autoScale); }; virtual void undo() { if (!m_autoScaleOld) { m_private->yMin = m_minOld; m_private->yMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleY = m_autoScaleOld; emit m_private->q->yAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; float m_minOld; float m_maxOld; }; void CartesianPlot::setAutoScaleY(bool autoScaleY) { Q_D(CartesianPlot); if (autoScaleY != d->autoScaleY) exec(new CartesianPlotSetAutoScaleYCmd(d, autoScaleY)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMin, float, yMin, retransformScales); void CartesianPlot::setYMin(float yMin) { Q_D(CartesianPlot); if (yMin != d->yMin) exec(new CartesianPlotSetYMinCmd(d, yMin, i18n("%1: set min y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, float, yMax, retransformScales); void CartesianPlot::setYMax(float yMax) { Q_D(CartesianPlot); if (yMax != d->yMax) exec(new CartesianPlotSetYMaxCmd(d, yMax, i18n("%1: set max y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales); void CartesianPlot::setYScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->yScale) exec(new CartesianPlotSetYScaleCmd(d, scale, i18n("%1: set y scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreakingEnabled, bool, yRangeBreakingEnabled, retransformScales) void CartesianPlot::setYRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->yRangeBreakingEnabled) exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, i18n("%1: y-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreaks, CartesianPlot::RangeBreaks, yRangeBreaks, retransformScales); void CartesianPlot::setYRangeBreaks(const RangeBreaks& breaks) { Q_D(CartesianPlot); exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, i18n("%1: y-range breaks changed"))); } STD_SETTER_CMD_IMPL_S(CartesianPlot, SetTheme, QString, theme) void CartesianPlot::setTheme(const QString& theme) { Q_D(CartesianPlot); if (theme != d->theme) { if (!theme.isEmpty()) { beginMacro( i18n("%1: load theme %2", name(), theme) ); exec(new CartesianPlotSetThemeCmd(d, theme, i18n("%1: set theme"))); loadTheme(theme); endMacro(); } else { exec(new CartesianPlotSetThemeCmd(d, theme, i18n("%1: disable theming"))); } } } //################################################################ //########################## Slots ############################### //################################################################ void CartesianPlot::addHorizontalAxis() { Axis* axis = new Axis("x-axis", this, Axis::AxisHorizontal); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(xMin()); axis->setEnd(xMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addVerticalAxis() { Axis* axis = new Axis("y-axis", this, Axis::AxisVertical); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(yMin()); axis->setEnd(yMax()); axis->setUndoAware(true); } addChild(axis); } XYCurve* CartesianPlot::addCurve() { XYCurve* curve = new XYCurve("xy-curve"); this->addChild(curve); this->applyThemeOnNewCurve(curve); return curve; } XYEquationCurve* CartesianPlot::addEquationCurve() { XYEquationCurve* curve = new XYEquationCurve("f(x)"); this->addChild(curve); this->applyThemeOnNewCurve(curve); return curve; } XYDataReductionCurve* CartesianPlot::addDataReductionCurve() { XYDataReductionCurve* curve = new XYDataReductionCurve("Data reduction"); this->addChild(curve); return curve; } /*! * returns the first selected XYCurve in the plot */ const XYCurve* CartesianPlot::currentCurve() const { for (const auto* curve: this->children()) { if (curve->graphicsItem()->isSelected()) return curve; } return 0; } XYDifferentiationCurve* CartesianPlot::addDifferentiationCurve() { XYDifferentiationCurve* curve = new XYDifferentiationCurve("Differentiation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: differentiate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Derivative of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->differentiationDataChanged(curve->differentiationData()); } else { beginMacro(i18n("%1: add differentiation curve", name())); this->addChild(curve); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYIntegrationCurve* CartesianPlot::addIntegrationCurve() { XYIntegrationCurve* curve = new XYIntegrationCurve("Integration"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: integrate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Integral of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->integrationDataChanged(curve->integrationData()); } else { beginMacro(i18n("%1: add differentiation curve", name())); this->addChild(curve); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYInterpolationCurve* CartesianPlot::addInterpolationCurve() { XYInterpolationCurve* curve = new XYInterpolationCurve("Interpolation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: interpolate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Interpolation of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->interpolationDataChanged(curve->interpolationData()); } else { beginMacro(i18n("%1: add interpolation curve", name())); this->addChild(curve); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } Histogram* CartesianPlot::addHistogram(){ Histogram* curve= new Histogram("Histogram"); this->addChild(curve); return curve; } XYSmoothCurve* CartesianPlot::addSmoothCurve() { XYSmoothCurve* curve = new XYSmoothCurve("Smooth"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: smooth '%2'", name(), curCurve->name()) ); curve->setName( i18n("Smoothing of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->smoothDataChanged(curve->smoothData()); } else { beginMacro(i18n("%1: add smoothing curve", name())); this->addChild(curve); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYFitCurve* CartesianPlot::addFitCurve() { XYFitCurve* curve = new XYFitCurve("fit"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: fit to '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fit to '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); //set the fit model category and type const QAction* action = qobject_cast(QObject::sender()); curve->initFitData(action, addFitAction); this->addChild(curve); curve->recalculate(); emit curve->fitDataChanged(curve->fitData()); } else { beginMacro(i18n("%1: add fit curve", name())); this->addChild(curve); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYFourierFilterCurve* CartesianPlot::addFourierFilterCurve() { XYFourierFilterCurve* curve = new XYFourierFilterCurve("Fourier filter"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: Fourier filtering of '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fourier filtering of '%1'", curCurve->name()) ); curve->setDataSourceType(XYCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); } else { beginMacro(i18n("%1: add Fourier filter curve", name())); this->addChild(curve); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYFourierTransformCurve* CartesianPlot::addFourierTransformCurve() { XYFourierTransformCurve* curve = new XYFourierTransformCurve("Fourier transform"); this->addChild(curve); this->applyThemeOnNewCurve(curve); return curve; } void CartesianPlot::addLegend() { //don't do anything if there's already a legend if (m_legend) return; m_legend = new CartesianPlotLegend(this, "legend"); this->addChild(m_legend); m_legend->retransform(); //only one legend is allowed -> disable the action addLegendAction->setEnabled(false); } void CartesianPlot::addCustomPoint() { CustomPoint* point = new CustomPoint(this, "custom point"); this->addChild(point); } void CartesianPlot::childAdded(const AbstractAspect* child) { Q_D(CartesianPlot); const XYCurve* curve = qobject_cast(child); if (curve) { connect(curve, SIGNAL(dataChanged()), this, SLOT(dataChanged())); connect(curve, SIGNAL(xDataChanged()), this, SLOT(xDataChanged())); connect(curve, SIGNAL(yDataChanged()), this, SLOT(yDataChanged())); connect(curve, SIGNAL(visibilityChanged(bool)), this, SLOT(curveVisibilityChanged())); //update the legend on changes of the name, line and symbol styles connect(curve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(updateLegend())); connect(curve, SIGNAL(lineTypeChanged(XYCurve::LineType)), this, SLOT(updateLegend())); connect(curve, SIGNAL(linePenChanged(QPen)), this, SLOT(updateLegend())); connect(curve, SIGNAL(lineOpacityChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsStyleChanged(Symbol::Style)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsSizeChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsRotationAngleChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsOpacityChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsBrushChanged(QBrush)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsPenChanged(QPen)), this, SLOT(updateLegend())); updateLegend(); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; } else { const Histogram* histo = qobject_cast(child); if (histo) { connect(histo, SIGNAL(HistogramdataChanged()), this, SLOT(HistogramdataChanged())); connect(histo, SIGNAL(xHistogramDataChanged()), this, SLOT(xHistogramDataChanged())); connect(histo, SIGNAL(yHistogramDataChanged()), this, SLOT(yHistogramDataChanged())); connect(histo, SIGNAL(visibilityChanged(bool)), this, SLOT(curveVisibilityChanged())); } } //if a theme was selected, apply the theme settings for newly added children, too if (!d->theme.isEmpty() && !isLoading()) { const WorksheetElement* el = dynamic_cast(child); if (el) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(el)->loadThemeConfig(config); } } } void CartesianPlot::childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); if (m_legend == child) { addLegendAction->setEnabled(true); m_legend = nullptr; } else { const XYCurve* curve = qobject_cast(child); if (curve) updateLegend(); } } void CartesianPlot::updateLegend() { if (m_legend) m_legend->retransform(); } /*! called when in one of the curves the data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::dataChanged() { Q_D(CartesianPlot); XYCurve* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else if (d->autoScaleY) this->scaleAutoY(); else curve->retransform(); } void CartesianPlot::HistogramdataChanged(){ Q_D(CartesianPlot); Histogram* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; if (d->autoScaleX) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else curve->retransform(); } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::xDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); XYCurve* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesXMinMaxIsDirty = true; if (d->autoScaleX) this->scaleAutoX(); else curve->retransform(); emit xDataChangedSignal(); } void CartesianPlot::xHistogramDataChanged(){ if (project()->isLoading()) return; Q_D(CartesianPlot); Histogram* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesXMinMaxIsDirty = true; if (d->autoScaleX) { this->scaleAutoX(); this->scaleAutoY(); } else curve->retransform(); } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::yDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); XYCurve* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesYMinMaxIsDirty = true; if (d->autoScaleY) this->scaleAutoY(); else curve->retransform(); } void CartesianPlot::yHistogramDataChanged(){ if (project()->isLoading()) return; Q_D(CartesianPlot); Histogram* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesYMinMaxIsDirty = true; if (d->autoScaleY) this->scaleAutoY(); else curve->retransform(); } void CartesianPlot::curveVisibilityChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; updateLegend(); if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else if (d->autoScaleY) this->scaleAutoY(); } void CartesianPlot::setMouseMode(const MouseMode mouseMode) { Q_D(CartesianPlot); d->mouseMode = mouseMode; d->setHandlesChildEvents(mouseMode != CartesianPlot::SelectionMode); QList items = d->childItems(); if (d->mouseMode == CartesianPlot::SelectionMode) { for (auto* item: items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, false); } else { for (auto* item: items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, true); } //when doing zoom selection, prevent the graphics item from being movable //if it's currently movable (no worksheet layout available) const Worksheet* worksheet = dynamic_cast(parentAspect()); if (worksheet) { if (mouseMode == CartesianPlot::SelectionMode) { if (worksheet->layout() != Worksheet::NoLayout) graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); else graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); } else { //zoom m_selection graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } } } void CartesianPlot::scaleAutoX() { Q_D(CartesianPlot); //loop over all xy-curves and determine the maximum x-value if (d->curvesXMinMaxIsDirty) { d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; if (curve->xColumn()->minimum() != INFINITY) { if (curve->xColumn()->minimum() < d->curvesXMin) d->curvesXMin = curve->xColumn()->minimum(); } if (curve->xColumn()->maximum() != -INFINITY) { if (curve->xColumn()->maximum() > d->curvesXMax) d->curvesXMax = curve->xColumn()->maximum(); } } QList childrenHistogram = this->children(); foreach(const Histogram* curve, childrenHistogram) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; if (curve->xColumn()->minimum() != INFINITY){ if (curve->xColumn()->minimum() < d->curvesXMin) d->curvesXMin = curve->xColumn()->minimum(); } if (curve->xColumn()->maximum() != -INFINITY){ if (curve->xColumn()->maximum() > d->curvesXMax) d->curvesXMax = curve->xColumn()->maximum(); } } d->curvesXMinMaxIsDirty = false; } bool update = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; update = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; update = true; } if (update) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { float offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } d->retransformScales(); } } void CartesianPlot::scaleAutoY() { Q_D(CartesianPlot); //loop over all xy-curves and determine the maximum y-value if (d->curvesYMinMaxIsDirty) { d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->yColumn()) continue; if (curve->yColumn()->minimum() != INFINITY) { if (curve->yColumn()->minimum() < d->curvesYMin) d->curvesYMin = curve->yColumn()->minimum(); } if (curve->yColumn()->maximum() != -INFINITY) { if (curve->yColumn()->maximum() > d->curvesYMax) d->curvesYMax = curve->yColumn()->maximum(); } } d->curvesYMinMaxIsDirty = false; } QList childrenHistogram = this->children(); foreach(const Histogram* curve, childrenHistogram) { if (!curve->isVisible()) continue; d->curvesYMin = 0.0; if (curve->getYMaximum() != -INFINITY) { if ( curve->getYMaximum() > d->curvesYMax) d->curvesYMax = curve->getYMaximum(); } } bool update = false; if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; update = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; update = true; } if (update) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { float offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } d->retransformScales(); } } void CartesianPlot::scaleAuto() { Q_D(CartesianPlot); //loop over all xy-curves and determine the maximum x-value QList children = this->children(); QList childrenHistogram = this->children(); +// switch (d->rangeType) { +// case CartesianPlot::RangeLast: +// case CartesianPlot::RangeFirst: +// //TODO: +// break; +// case CartesianPlot::RangeFree: { if (d->curvesXMinMaxIsDirty) { d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; for (const auto* curve: children) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; if (curve->xColumn()->minimum() != INFINITY) { if (curve->xColumn()->minimum() < d->curvesXMin) d->curvesXMin = curve->xColumn()->minimum(); } if (curve->xColumn()->maximum() != -INFINITY) { if (curve->xColumn()->maximum() > d->curvesXMax) d->curvesXMax = curve->xColumn()->maximum(); } } foreach(const Histogram* curve, childrenHistogram) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; if (curve->xColumn()->minimum() != INFINITY){ if (curve->xColumn()->minimum() < d->curvesXMin) d->curvesXMin = curve->xColumn()->minimum(); } if (curve->xColumn()->maximum() != -INFINITY){ if (curve->xColumn()->maximum() > d->curvesXMax) d->curvesXMax = curve->xColumn()->maximum(); } } d->curvesXMinMaxIsDirty = false; } +// } +// } +//TODO: this function seems to be broken. Maybe this was caused by the last merge from the histogram branch. if (d->curvesYMinMaxIsDirty) { d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; for (const auto* curve: children) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; if (curve->yColumn()->minimum() != INFINITY) { if (curve->yColumn()->minimum() < d->curvesYMin) d->curvesYMin = curve->yColumn()->minimum(); } if (curve->yColumn()->maximum() != -INFINITY) { if (curve->yColumn()->maximum() > d->curvesYMax) d->curvesYMax = curve->yColumn()->maximum(); } } } foreach(const Histogram* curve, childrenHistogram) { if (!curve->isVisible()) continue; d->curvesYMin = 0.0; if (curve->getYMaximum() != -INFINITY){ if ( curve->getYMaximum() > d->curvesYMax) d->curvesYMax = curve->getYMaximum(); } } bool updateX = false; bool updateY = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; updateX = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; updateX = true; } if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; updateY = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; updateY = true; } if (updateX || updateY) { if (updateX) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { float offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } } if (updateY) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { float offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } } d->retransformScales(); } } void CartesianPlot::zoomIn() { DEBUG("CartesianPlot::zoomIn()"); Q_D(CartesianPlot); float oldRange = (d->xMax - d->xMin); float newRange = (d->xMax - d->xMin) / m_zoomFactor; d->xMax = d->xMax + (newRange - oldRange) / 2; d->xMin = d->xMin - (newRange - oldRange) / 2; oldRange = (d->yMax - d->yMin); newRange = (d->yMax - d->yMin) / m_zoomFactor; d->yMax = d->yMax + (newRange - oldRange) / 2; d->yMin = d->yMin - (newRange - oldRange) / 2; d->retransformScales(); } void CartesianPlot::zoomOut() { Q_D(CartesianPlot); float oldRange = (d->xMax-d->xMin); float newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; oldRange = (d->yMax-d->yMin); newRange = (d->yMax-d->yMin)*m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomInX() { Q_D(CartesianPlot); float oldRange = (d->xMax-d->xMin); float newRange = (d->xMax-d->xMin)/m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomOutX() { Q_D(CartesianPlot); float oldRange = (d->xMax-d->xMin); float newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomInY() { Q_D(CartesianPlot); float oldRange = (d->yMax-d->yMin); float newRange = (d->yMax-d->yMin)/m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomOutY() { Q_D(CartesianPlot); float oldRange = (d->yMax-d->yMin); float newRange = (d->yMax-d->yMin)*m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::shiftLeftX() { Q_D(CartesianPlot); float offsetX = (d->xMax-d->xMin)*0.1; d->xMax -= offsetX; d->xMin -= offsetX; d->retransformScales(); } void CartesianPlot::shiftRightX() { Q_D(CartesianPlot); float offsetX = (d->xMax-d->xMin)*0.1; d->xMax += offsetX; d->xMin += offsetX; d->retransformScales(); } void CartesianPlot::shiftUpY() { Q_D(CartesianPlot); float offsetY = (d->yMax-d->yMin)*0.1; d->yMax += offsetY; d->yMin += offsetY; d->retransformScales(); } void CartesianPlot::shiftDownY() { Q_D(CartesianPlot); float offsetY = (d->yMax-d->yMin)*0.1; d->yMax -= offsetY; d->yMin -= offsetY; d->retransformScales(); } void CartesianPlot::setXMinMax(const int xmin, const int xmax) { Q_D(CartesianPlot); if ((xmin != d->xMin) || (xmax != d->xMax)) { d->xMin = xmin; d->xMax = xmax; d->retransformScales(); } } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlot::visibilityChanged() { Q_D(CartesianPlot); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### CartesianPlotPrivate::CartesianPlotPrivate(CartesianPlot* plot) : AbstractPlotPrivate(plot), curvesXMinMaxIsDirty(false), curvesYMinMaxIsDirty(false), curvesXMin(INFINITY), curvesXMax(-INFINITY), curvesYMin(INFINITY), curvesYMax(-INFINITY), q(plot), mouseMode(CartesianPlot::SelectionMode), cSystem(nullptr), m_suppressRetransform(false), // m_printing(false), m_selectionBandIsShown(false) { setData(0, WorksheetElement::NameCartesianPlot); } /*! updates the position of plot rectangular in scene coordinates to \c r and recalculates the scales. The size of the plot corresponds to the size of the plot area, the area which is filled with the background color etc. and which can pose the parent item for several sub-items (like TextLabel). Note, the size of the area used to define the coordinate system doesn't need to be equal to this plot area. Also, the size (=bounding box) of CartesianPlot can be greater than the size of the plot area. */ void CartesianPlotPrivate::retransform() { DEBUG("CartesianPlotPrivate::retransform()"); if (m_suppressRetransform) return; prepareGeometryChange(); setPos( rect.x()+rect.width()/2, rect.y()+rect.height()/2); retransformScales(); //plotArea position is always (0, 0) in parent's coordinates, don't need to update here q->plotArea()->setRect(rect); //call retransform() for the title and the legend (if available) //when a predefined position relative to the (Left, Centered etc.) is used, //the actual position needs to be updated on plot's geometry changes. if (q->title()) q->title()->retransform(); if (q->m_legend) q->m_legend->retransform(); WorksheetElementContainerPrivate::recalcShapeAndBoundingRect(); } void CartesianPlotPrivate::retransformScales() { DEBUG("CartesianPlotPrivate::retransformScales()"); CartesianPlot* plot = dynamic_cast(q); QList scales; //perform the mapping from the scene coordinates to the plot's coordinates here. QRectF itemRect = mapRectFromScene(rect); //check ranges for log-scales if (xScale != CartesianPlot::ScaleLinear) checkXRange(); //check whether we have x-range breaks - the first break, if available, should be valid bool hasValidBreak = (xRangeBreakingEnabled && !xRangeBreaks.list.isEmpty() && xRangeBreaks.list.first().isValid()); static const int breakGap = 20; double sceneStart, sceneEnd, logicalStart, logicalEnd; //create x-scales int plotSceneStart = itemRect.x() + horizontalPadding; int plotSceneEnd = itemRect.x() + itemRect.width() - horizontalPadding; if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = xMin; logicalEnd = xMax; //TODO: how should we handle the case sceneStart == sceneEnd? //(to reproduce, create plots and adjust the spacing/pading to get zero size for the plots) if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = xMin; for (const auto& rb: xRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &xRangeBreaks.list.first()) sceneStart += breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the x-data range) sceneStart = sceneEndLast+breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = xMax; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setXScales(scales); //check ranges for log-scales if (yScale != CartesianPlot::ScaleLinear) checkYRange(); //check whether we have y-range breaks - the first break, if available, should be valid hasValidBreak = (yRangeBreakingEnabled && !yRangeBreaks.list.isEmpty() && yRangeBreaks.list.first().isValid()); //create y-scales scales.clear(); plotSceneStart = itemRect.y()+itemRect.height()-verticalPadding; plotSceneEnd = itemRect.y()+verticalPadding; if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = yMin; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = yMin; for (const auto& rb: yRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &yRangeBreaks.list.first()) sceneStart -= breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the y-data range) sceneStart = sceneEndLast-breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setYScales(scales); //calculate the changes in x and y and save the current values for xMin, xMax, yMin, yMax float deltaXMin = 0; float deltaXMax = 0; float deltaYMin = 0; float deltaYMax = 0; if (xMin != xMinPrev) { deltaXMin = xMin - xMinPrev; emit plot->xMinChanged(xMin); } if (xMax != xMaxPrev) { deltaXMax = xMax - xMaxPrev; emit plot->xMaxChanged(xMax); } if (yMin != yMinPrev) { deltaYMin = yMin - yMinPrev; emit plot->yMinChanged(yMin); } if (yMax!=yMaxPrev) { deltaYMax = yMax - yMaxPrev; emit plot->yMaxChanged(yMax); } xMinPrev = xMin; xMaxPrev = xMax; yMinPrev = yMin; yMaxPrev = yMax; //adjust auto-scale axes for (auto* axis: q->children()) { if (!axis->autoScale()) continue; if (axis->orientation() == Axis::AxisHorizontal) { if (deltaXMax != 0) { axis->setUndoAware(false); axis->setEnd(xMax); axis->setUndoAware(true); } if (deltaXMin != 0) { axis->setUndoAware(false); axis->setStart(xMin); axis->setUndoAware(true); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaYMin != 0) { // axis->setOffset(axis->offset() + deltaYMin, false); // } } else { if (deltaYMax != 0) { axis->setUndoAware(false); axis->setEnd(yMax); axis->setUndoAware(true); } if (deltaYMin != 0) { axis->setUndoAware(false); axis->setStart(yMin); axis->setUndoAware(true); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaXMin != 0) { // axis->setOffset(axis->offset() + deltaXMin, false); // } } } // call retransform() on the parent to trigger the update of all axes and curves q->retransform(); } /*! * don't allow any negative values for the x range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkXRange() { double min = 0.01; if (xMin <= 0.0) { (min < xMax*min) ? xMin = min : xMin = xMax*min; emit q->xMinChanged(xMin); } else if (xMax <= 0.0) { (-min > xMin*min) ? xMax = -min : xMax = xMin*min; emit q->xMaxChanged(xMax); } } /*! * don't allow any negative values for the y range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkYRange() { double min = 0.01; if (yMin <= 0.0) { (min < yMax*min) ? yMin = min : yMin = yMax*min; emit q->yMinChanged(yMin); } else if (yMax <= 0.0) { (-min > yMin*min) ? yMax = -min : yMax = yMin*min; emit q->yMaxChanged(yMax); } } CartesianScale* CartesianPlotPrivate::createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd) { // Interval interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale Interval interval (-1E15, 1E15); // Interval interval (logicalStart, logicalEnd); if (type == CartesianPlot::ScaleLinear) { return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { float base; if (type == CartesianPlot::ScaleLog10) base = 10.0; else if (type == CartesianPlot::ScaleLog2) base = 2.0; else base = M_E; return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, base); } } /*! * Reimplemented from QGraphicsItem. */ QVariant CartesianPlotPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemPositionChange) { const QPointF& itemPos = value.toPointF();//item's center point in parent's coordinates; float x = itemPos.x(); float y = itemPos.y(); //calculate the new rect and forward the changes to the frontend QRectF newRect; float w = rect.width(); float h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); emit q->rectChanged(newRect); } return QGraphicsItem::itemChange(change, value); } void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { if (mouseMode == CartesianPlot::ZoomSelectionMode) { m_selectionStart = event->pos(); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { m_selectionStart.setX(event->pos().x()); m_selectionStart.setY(q->plotRect().height()/2); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionStart.setX(-q->plotRect().width()/2); m_selectionStart.setY(event->pos().y()); } m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } else { QGraphicsItem::mousePressEvent(event); } } void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) { QGraphicsItem::mouseMoveEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { QGraphicsItem::mouseMoveEvent(event); if ( !boundingRect().contains(event->pos()) ) { q->info(""); return; } QString info; QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart); if (mouseMode == CartesianPlot::ZoomSelectionMode) { m_selectionEnd = event->pos(); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()) + QString::fromUtf8(", Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { m_selectionEnd.setX(event->pos().x()); m_selectionEnd.setY(-q->plotRect().height()/2); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionEnd.setX(q->plotRect().width()/2); m_selectionEnd.setY(event->pos().y()); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); } q->info(info); update(); } //TODO: implement the navigation in plot on mouse move events, //calculate the position changes and call shift*()-functions } void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) { const QPointF& itemPos = pos();//item's center point in parent's coordinates; float x = itemPos.x(); float y = itemPos.y(); //calculate the new rect and set it QRectF newRect; float w = rect.width(); float h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); m_suppressRetransform = true; q->setRect(newRect); m_suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { //don't zoom if very small region was selected, avoid occasional/unwanted zooming if ( qAbs(m_selectionEnd.x()-m_selectionStart.x()) < 20 || qAbs(m_selectionEnd.y()-m_selectionStart.y()) < 20 ) { m_selectionBandIsShown = false; return; } //determine the new plot ranges QPointF logicalZoomStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::SuppressPageClipping); QPointF logicalZoomEnd = cSystem->mapSceneToLogical(m_selectionEnd, AbstractCoordinateSystem::SuppressPageClipping); if (m_selectionEnd.x() > m_selectionStart.x()) { xMin = logicalZoomStart.x(); xMax = logicalZoomEnd.x(); } else { xMin = logicalZoomEnd.x(); xMax = logicalZoomStart.x(); } if (m_selectionEnd.y() > m_selectionStart.y()) { yMin = logicalZoomEnd.y(); yMax = logicalZoomStart.y(); } else { yMin = logicalZoomStart.y(); yMax = logicalZoomEnd.y(); } m_selectionBandIsShown = false; retransformScales(); } } void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { //determine first, which axes are selected and zoom only in the corresponding direction. //zoom the entire plot if no axes selected. bool zoomX = false; bool zoomY = false; for (auto* axis: q->children()) { if (!axis->graphicsItem()->isSelected()) continue; if (axis->orientation() == Axis::AxisHorizontal) zoomX = true; else zoomY = true; } if (event->delta() > 0) { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomIn(); } else { if (zoomX) q->zoomInX(); if (zoomY) q->zoomInY(); } } else { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomOut(); } else { if (zoomX) q->zoomOutX(); if (zoomY) q->zoomOutY(); } } } void CartesianPlotPrivate::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { QPointF point = event->pos(); QString info; if (q->plotRect().contains(point)) { QPointF logicalPoint = cSystem->mapSceneToLogical(point); if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) { info = "x=" + QString::number(logicalPoint.x()) + ", y=" + QString::number(logicalPoint.y()); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { QPointF p1(logicalPoint.x(), yMin); QPointF p2(logicalPoint.x(), yMax); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2)); info = "x=" + QString::number(logicalPoint.x()); update(); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { QPointF p1(xMin, logicalPoint.y()); QPointF p2(xMax, logicalPoint.y()); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2)); info = "y=" + QString::number(logicalPoint.y()); update(); } } q->info(info); QGraphicsItem::hoverMoveEvent(event); } void CartesianPlotPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * widget) { // DEBUG("CartesianPlotPrivate::paint()"); if (!isVisible()) return; painter->setPen(QPen(Qt::black, 3)); if ((mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) && (!m_selectionBandIsShown)) { painter->drawLine(m_selectionStartLine); } if (m_selectionBandIsShown) { painter->save(); painter->setPen(QPen(Qt::black, 5)); painter->drawRect(QRectF(m_selectionStart, m_selectionEnd)); painter->setBrush(Qt::blue); painter->setOpacity(0.2); painter->drawRect(QRectF(m_selectionStart, m_selectionEnd)); painter->restore(); } WorksheetElementContainerPrivate::paint(painter, option, widget); // DEBUG("CartesianPlotPrivate::paint() DONE"); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlot::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlot); writer->writeStartElement( "cartesianPlot" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()){ writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->rect.x()) ); writer->writeAttribute( "y", QString::number(d->rect.y()) ); writer->writeAttribute( "width", QString::number(d->rect.width()) ); writer->writeAttribute( "height", QString::number(d->rect.height()) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //coordinate system and padding writer->writeStartElement( "coordinateSystem" ); writer->writeAttribute( "autoScaleX", QString::number(d->autoScaleX) ); writer->writeAttribute( "autoScaleY", QString::number(d->autoScaleY) ); writer->writeAttribute( "xMin", QString::number(d->xMin) ); writer->writeAttribute( "xMax", QString::number(d->xMax) ); writer->writeAttribute( "yMin", QString::number(d->yMin) ); writer->writeAttribute( "yMax", QString::number(d->yMax) ); writer->writeAttribute( "xScale", QString::number(d->xScale) ); writer->writeAttribute( "yScale", QString::number(d->yScale) ); writer->writeAttribute( "horizontalPadding", QString::number(d->horizontalPadding) ); writer->writeAttribute( "verticalPadding", QString::number(d->verticalPadding) ); writer->writeEndElement(); //x-scale breaks if (d->xRangeBreakingEnabled || !d->xRangeBreaks.list.isEmpty()) { writer->writeStartElement("xRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->xRangeBreakingEnabled) ); for (const auto& rb: d->xRangeBreaks.list) { writer->writeStartElement("xRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //y-scale breaks if (d->yRangeBreakingEnabled || !d->yRangeBreaks.list.isEmpty()) { writer->writeStartElement("yRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->yRangeBreakingEnabled) ); for (const auto& rb: d->yRangeBreaks.list) { writer->writeStartElement("yRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //serialize all children (plot area, title text label, axes and curves) for (auto *elem: children(IncludeHidden)) elem->save(writer); writer->writeEndElement(); // close "cartesianPlot" section } //! Load from XML bool CartesianPlot::load(XmlStreamReader* reader) { Q_D(CartesianPlot); if (!reader->isStartElement() || reader->name() != "cartesianPlot") { reader->raiseError(i18n("no cartesianPlot 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; QString tmpTheme; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cartesianPlot") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "theme") { attribs = reader->attributes(); tmpTheme = attribs.value("name").toString(); } else if (reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->rect.setX( str.toDouble() ); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->rect.setY( str.toDouble() ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'width'")); else d->rect.setWidth( str.toDouble() ); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'height'")); else d->rect.setHeight( str.toDouble() ); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (reader->name() == "coordinateSystem") { attribs = reader->attributes(); str = attribs.value("autoScaleX").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoScaleX'")); else d->autoScaleX = bool(str.toInt()); str = attribs.value("autoScaleY").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoScaleY'")); else d->autoScaleY = bool(str.toInt()); str = attribs.value("xMin").toString(); if (str.isEmpty()) { reader->raiseWarning(attributeWarning.arg("'xMin'")); } else { d->xMin = str.toDouble(); d->xMinPrev = d->xMin; } str = attribs.value("xMax").toString(); if (str.isEmpty()) { reader->raiseWarning(attributeWarning.arg("'xMax'")); } else { d->xMax = str.toDouble(); d->xMaxPrev = d->xMax; } str = attribs.value("yMin").toString(); if (str.isEmpty()) { reader->raiseWarning(attributeWarning.arg("'yMin'")); } else { d->yMin = str.toDouble(); d->yMinPrev = d->yMin; } str = attribs.value("yMax").toString(); if (str.isEmpty()) { reader->raiseWarning(attributeWarning.arg("'yMax'")); } else { d->yMax = str.toDouble(); d->yMaxPrev = d->yMax; } str = attribs.value("xScale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'xScale'")); else d->xScale = CartesianPlot::Scale(str.toInt()); str = attribs.value("yScale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'yScale'")); else d->yScale = CartesianPlot::Scale(str.toInt()); str = attribs.value("horizontalPadding").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalPadding'")); else d->horizontalPadding = str.toDouble(); str = attribs.value("verticalPadding").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalPadding'")); else d->verticalPadding = str.toDouble(); } else if (reader->name() == "xRangeBreaks") { //delete default rang break d->xRangeBreaks.list.clear(); attribs = reader->attributes(); str = attribs.value("enabled").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'enabled'")); else d->xRangeBreakingEnabled = str.toInt(); } else if (reader->name() == "xRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'start'")); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'end'")); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'style'")); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->xRangeBreaks.list << b; } else if (reader->name() == "yRangeBreaks") { //delete default rang break d->yRangeBreaks.list.clear(); attribs = reader->attributes(); str = attribs.value("enabled").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'enabled'")); else d->yRangeBreakingEnabled = str.toInt(); } else if (reader->name() == "yRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'start'")); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'end'")); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'style'")); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->yRangeBreaks.list << b; } else if (reader->name() == "textLabel") { m_title = new TextLabel(""); if (!m_title->load(reader)) { delete m_title; m_title=0; return false; } else { addChild(m_title); } } else if (reader->name() == "plotArea") { m_plotArea->load(reader); } else if (reader->name() == "axis") { Axis* axis = new Axis("", this); if (!axis->load(reader)) { delete axis; return false; } else { addChild(axis); } } else if (reader->name() == "xyCurve") { XYCurve* curve = addCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyEquationCurve") { XYEquationCurve* curve = addEquationCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyDataReductionCurve") { XYDataReductionCurve* curve = addDataReductionCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyDifferentiationCurve") { XYDifferentiationCurve* curve = addDifferentiationCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyIntegrationCurve") { XYIntegrationCurve* curve = addIntegrationCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyInterpolationCurve") { XYInterpolationCurve* curve = addInterpolationCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyFitCurve") { XYFitCurve* curve = addFitCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyFourierFilterCurve") { XYFourierFilterCurve* curve = addFourierFilterCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyFourierTransformCurve") { XYFourierTransformCurve* curve = addFourierTransformCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xySmoothCurve") { XYSmoothCurve* curve = addSmoothCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "cartesianPlotLegend") { m_legend = new CartesianPlotLegend(this, ""); if (!m_legend->load(reader)) { delete m_legend; return false; } else { addChild(m_legend); addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action } } else if (reader->name() == "customPoint") { CustomPoint* point = new CustomPoint(this, ""); if (!point->load(reader)) { delete point; return false; } else { addChild(point); } }else if(reader->name() == "Histogram"){ Histogram* curve = addHistogram(); if (!curve->load(reader)){ removeChild(curve); return false; } } else { // unknown element reader->raiseWarning(i18n("unknown cartesianPlot element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } d->retransform();//TODO: This is expensive. why do we need this on load? if (m_title) { m_title->setHidden(true); m_title->graphicsItem()->setParentItem(m_plotArea->graphicsItem()); } //if a theme was used, assign the value to the private member at the very end of load() //so we don't try to load the theme in applyThemeOnNewCurve() when adding curves on project load and calculate the palette if (!tmpTheme.isEmpty()){ KConfig config( ThemeHandler::themeFilePath(tmpTheme), KConfig::SimpleConfig ); //TODO: check whether the theme config really exists d->theme = tmpTheme; this->setColorPalette(config); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void CartesianPlot::loadTheme(const QString& theme) { KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); loadThemeConfig(config); } void CartesianPlot::loadThemeConfig(const KConfig& config) { QString str = config.name(); str = str.right(str.length() - str.lastIndexOf(QDir::separator()) - 1); beginMacro( i18n("%1: Load theme %2.", AbstractAspect::name(), str) ); this->setTheme(str); //load the color palettes for the curves this->setColorPalette(config); //load the theme for all the children for (auto* child: children(AbstractAspect::IncludeHidden)) child->loadThemeConfig(config); Q_D(CartesianPlot); d->update(this->rect()); endMacro(); } void CartesianPlot::saveTheme(KConfig &config) { const QList& axisElements = children(AbstractAspect::IncludeHidden); const QList& plotAreaElements = children(AbstractAspect::IncludeHidden); const QList& textLabelElements = children(AbstractAspect::IncludeHidden); axisElements.at(0)->saveThemeConfig(config); plotAreaElements.at(0)->saveThemeConfig(config); textLabelElements.at(0)->saveThemeConfig(config); for (auto *child: children(AbstractAspect::IncludeHidden)) child->saveThemeConfig(config); } //Generating colors from 5-color theme palette void CartesianPlot::setColorPalette(const KConfig& config) { KConfigGroup group = config.group("Theme"); //read the five colors defining the palette m_themeColorPalette.clear(); m_themeColorPalette.append(group.readEntry("ThemePaletteColor1", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor2", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor3", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor4", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor5", QColor())); //generate 30 additional shades if the color palette contains more than one color if (m_themeColorPalette.at(0) != m_themeColorPalette.at(1)) { QColor c; //3 factors to create shades from theme's palette float fac[3] = {0.25,0.45,0.65}; //Generate 15 lighter shades for (int i = 0; i < 5; i++) { for (int j = 1; j < 4; j++) { c.setRed( m_themeColorPalette.at(i).red()*(1-fac[j-1]) ); c.setGreen( m_themeColorPalette.at(i).green()*(1-fac[j-1]) ); c.setBlue( m_themeColorPalette.at(i).blue()*(1-fac[j-1]) ); m_themeColorPalette.append(c); } } //Generate 15 darker shades for (int i = 0; i < 5; i++) { for (int j = 4; j < 7; j++) { c.setRed( m_themeColorPalette.at(i).red()+((255-m_themeColorPalette.at(i).red())*fac[j-4]) ); c.setGreen( m_themeColorPalette.at(i).green()+((255-m_themeColorPalette.at(i).green())*fac[j-4]) ); c.setBlue( m_themeColorPalette.at(i).blue()+((255-m_themeColorPalette.at(i).blue())*fac[j-4]) ); m_themeColorPalette.append(c); } } } } const QList& CartesianPlot::themeColorPalette() const { return m_themeColorPalette; } void CartesianPlot::applyThemeOnNewCurve(XYCurve* curve) { Q_D(const CartesianPlot); if (!d->theme.isEmpty()) { KConfig config( ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig ); curve->loadThemeConfig(config); } } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.h b/src/backend/worksheet/plots/cartesian/CartesianPlot.h index 6c0ba3991..88d9964b3 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.h @@ -1,273 +1,283 @@ /*************************************************************************** File : CartesianPlot.h Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- - Copyright : (C) 2011-2015 by Alexander Semke (alexander.semke@web.de) + Copyright : (C) 2011-2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef CARTESIANPLOT_H #define CARTESIANPLOT_H #include "backend/worksheet/plots/AbstractPlot.h" #include #include class QToolBar; class CartesianPlotPrivate; class CartesianPlotLegend; class XYCurve; class XYEquationCurve; class XYDataReductionCurve; class XYDifferentiationCurve; class XYIntegrationCurve; class XYInterpolationCurve; class XYSmoothCurve; class XYFitCurve; class XYFourierFilterCurve; class KConfig; class XYFourierTransformCurve; class CartesianPlot:public AbstractPlot { Q_OBJECT public: explicit CartesianPlot(const QString &name); virtual ~CartesianPlot(); enum Scale {ScaleLinear, ScaleLog10, ScaleLog2, ScaleLn, ScaleSqrt, ScaleX2}; enum Type {FourAxes, TwoAxes, TwoAxesCentered, TwoAxesCenteredZero}; + enum RangeType {RangeFree, RangeLast, RangeFirst}; enum RangeBreakStyle {RangeBreakSimple, RangeBreakVertical, RangeBreakSloped}; enum MouseMode {SelectionMode, ZoomSelectionMode, ZoomXSelectionMode, ZoomYSelectionMode}; enum NavigationOperation {ScaleAuto, ScaleAutoX, ScaleAutoY, ZoomIn, ZoomOut, ZoomInX, ZoomOutX, ZoomInY, ZoomOutY, ShiftLeftX, ShiftRightX, ShiftUpY, ShiftDownY }; struct RangeBreak { RangeBreak() : start(NAN), end(NAN), position(0.5), style(RangeBreakSloped) {} bool isValid() const { return (!std::isnan(start) && !std::isnan(end)); } float start; float end; float position; RangeBreakStyle style; }; //simple wrapper for QList in order to get our macros working struct RangeBreaks { RangeBreaks() : lastChanged(-1) { RangeBreak b; list << b; }; QList list; int lastChanged; }; void initDefault(Type = FourAxes); QIcon icon() const; QMenu* createContextMenu(); QMenu* analysisMenu() const; void setRect(const QRectF&); QRectF plotRect(); void setMouseMode(const MouseMode); MouseMode mouseMode() const; void navigate(NavigationOperation); const QList& themeColorPalette() const; virtual void save(QXmlStreamWriter*) const; virtual bool load(XmlStreamReader*); virtual void loadThemeConfig(const KConfig&); void saveTheme(KConfig& config); + BASIC_D_ACCESSOR_DECL(CartesianPlot::RangeType, rangeType, RangeType) + BASIC_D_ACCESSOR_DECL(int, rangeLastValues, RangeLastValues) + BASIC_D_ACCESSOR_DECL(int, rangeFirstValues, RangeFirstValues) BASIC_D_ACCESSOR_DECL(bool, autoScaleX, AutoScaleX) BASIC_D_ACCESSOR_DECL(bool, autoScaleY, AutoScaleY) BASIC_D_ACCESSOR_DECL(float, xMin, XMin) BASIC_D_ACCESSOR_DECL(float, xMax, XMax) BASIC_D_ACCESSOR_DECL(float, yMin, YMin) BASIC_D_ACCESSOR_DECL(float, yMax, YMax) BASIC_D_ACCESSOR_DECL(CartesianPlot::Scale, xScale, XScale) BASIC_D_ACCESSOR_DECL(CartesianPlot::Scale, yScale, YScale) BASIC_D_ACCESSOR_DECL(bool, xRangeBreakingEnabled, XRangeBreakingEnabled) BASIC_D_ACCESSOR_DECL(bool, yRangeBreakingEnabled, YRangeBreakingEnabled) CLASS_D_ACCESSOR_DECL(RangeBreaks, xRangeBreaks, XRangeBreaks) CLASS_D_ACCESSOR_DECL(RangeBreaks, yRangeBreaks, YRangeBreaks) QString theme() const; typedef CartesianPlotPrivate Private; public slots: void setTheme(const QString&); private: void init(); void initActions(); void initMenus(); void setColorPalette(const KConfig&); void applyThemeOnNewCurve(XYCurve* curve); const XYCurve* currentCurve() const; CartesianPlotLegend* m_legend; float m_zoomFactor; QList m_themeColorPalette; QAction* visibilityAction; //"add new" actions QAction* addCurveAction; QAction* addEquationCurveAction; QAction* addHistogramPlot; QAction* addDataReductionCurveAction; QAction* addDifferentiationCurveAction; QAction* addIntegrationCurveAction; QAction* addInterpolationCurveAction; QAction* addSmoothCurveAction; QAction* addFitCurveAction; QAction* addFourierFilterCurveAction; QAction* addFourierTransformCurveAction; QAction* addHorizontalAxisAction; QAction* addVerticalAxisAction; QAction* addLegendAction; QAction* addCustomPointAction; //scaling, zooming, navigation actions QAction* scaleAutoXAction; QAction* scaleAutoYAction; QAction* scaleAutoAction; QAction* zoomInAction; QAction* zoomOutAction; QAction* zoomInXAction; QAction* zoomOutXAction; QAction* zoomInYAction; QAction* zoomOutYAction; QAction* shiftLeftXAction; QAction* shiftRightXAction; QAction* shiftUpYAction; QAction* shiftDownYAction; //analysis menu actions QAction* addDataOperationAction; QAction* addDataReductionAction; QAction* addDifferentiationAction; QAction* addIntegrationAction; QAction* addInterpolationAction; QAction* addSmoothAction; QVector addFitAction; QAction* addFourierFilterAction; QMenu* addNewMenu; QMenu* zoomMenu; QMenu* dataAnalysisMenu; QMenu* themeMenu; Q_DECLARE_PRIVATE(CartesianPlot) public slots: void addHorizontalAxis(); void addVerticalAxis(); XYCurve* addCurve(); Histogram* addHistogram(); XYEquationCurve* addEquationCurve(); XYDataReductionCurve* addDataReductionCurve(); XYDifferentiationCurve* addDifferentiationCurve(); XYIntegrationCurve* addIntegrationCurve(); XYInterpolationCurve* addInterpolationCurve(); XYSmoothCurve* addSmoothCurve(); XYFitCurve* addFitCurve(); XYFourierFilterCurve* addFourierFilterCurve(); XYFourierTransformCurve* addFourierTransformCurve(); void addLegend(); void addCustomPoint(); void scaleAuto(); void scaleAutoX(); void scaleAutoY(); void zoomIn(); void zoomOut(); void zoomInX(); void zoomOutX(); void zoomInY(); void zoomOutY(); void shiftLeftX(); void shiftRightX(); void shiftUpY(); void shiftDownY(); void setXMinMax(const int xmin, const int xmax); private slots: void updateLegend(); void childAdded(const AbstractAspect*); void childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child); void dataChanged(); void xDataChanged(); void yDataChanged(); void HistogramdataChanged(); void xHistogramDataChanged(); void yHistogramDataChanged(); void curveVisibilityChanged(); //SLOTs for changes triggered via QActions in the context menu void visibilityChanged(); void loadTheme(const QString&); protected: CartesianPlot(const QString &name, CartesianPlotPrivate *dd); signals: + friend class CartesianPlotSetCRangeTypeCmd; + friend class CartesianPlotSetCRangeLastValuesCmd; + friend class CartesianPlotSetCRangeFirstValuesCmd; friend class CartesianPlotSetRectCmd; friend class CartesianPlotSetAutoScaleXCmd; friend class CartesianPlotSetXMinCmd; friend class CartesianPlotSetXMaxCmd; friend class CartesianPlotSetXScaleCmd; friend class CartesianPlotSetAutoScaleYCmd; friend class CartesianPlotSetYMinCmd; friend class CartesianPlotSetYMaxCmd; friend class CartesianPlotSetYScaleCmd; friend class CartesianPlotSetXRangeBreakingEnabledCmd; friend class CartesianPlotSetYRangeBreakingEnabledCmd; friend class CartesianPlotSetXRangeBreaksCmd; friend class CartesianPlotSetYRangeBreaksCmd; friend class CartesianPlotSetThemeCmd; + void rangeTypeChanged(CartesianPlot::RangeType); + void rangeLastValuesChanged(int); + void rangeFirstValuesChanged(int); void rectChanged(QRectF&); void xAutoScaleChanged(bool); void xMinChanged(float); void xMaxChanged(float); void xScaleChanged(int); void yAutoScaleChanged(bool); void yMinChanged(float); void yMaxChanged(float); void yScaleChanged(int); void xRangeBreakingEnabledChanged(bool); void xRangeBreaksChanged(const CartesianPlot::RangeBreaks&); void yRangeBreakingEnabledChanged(bool); void yRangeBreaksChanged(const CartesianPlot::RangeBreaks&); void themeChanged(const QString&); void xDataChangedSignal(); }; #endif diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h index 04646c6f8..144b76ff4 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h @@ -1,88 +1,91 @@ /*************************************************************************** File : CartesianPlotPrivate.h Project : LabPlot Description : Private members of CartesianPlot. -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) *******************************************************7*******************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef CARTESIANPLOTPRIVATE_H #define CARTESIANPLOTPRIVATE_H #include "CartesianPlot.h" #include "CartesianCoordinateSystem.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include class CartesianPlotPrivate : public AbstractPlotPrivate { public: explicit CartesianPlotPrivate(CartesianPlot*); void retransform(); void retransformScales(); + CartesianPlot::RangeType rangeType; + int rangeFirstValues; + int rangeLastValues; float xMin, xMax, yMin, yMax; float xMinPrev, xMaxPrev, yMinPrev, yMaxPrev; bool autoScaleX, autoScaleY; float autoScaleOffsetFactor; CartesianPlot::Scale xScale, yScale; bool xRangeBreakingEnabled; bool yRangeBreakingEnabled; CartesianPlot::RangeBreaks xRangeBreaks; CartesianPlot::RangeBreaks yRangeBreaks; QString theme; //cached values of minimum and maximum for all visible curves bool curvesXMinMaxIsDirty, curvesYMinMaxIsDirty; double curvesXMin, curvesXMax, curvesYMin, curvesYMax; CartesianPlot* const q; CartesianPlot::MouseMode mouseMode; CartesianCoordinateSystem* cSystem; private: virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); virtual void mousePressEvent(QGraphicsSceneMouseEvent*); virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*); virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*); virtual void wheelEvent(QGraphicsSceneWheelEvent*); virtual void hoverMoveEvent(QGraphicsSceneHoverEvent*); virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* widget = nullptr); void checkXRange(); void checkYRange(); CartesianScale* createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd); bool m_suppressRetransform; // bool m_printing; bool m_selectionBandIsShown; QPointF m_selectionStart; QPointF m_selectionEnd; QLineF m_selectionStartLine; }; #endif