diff --git a/src/backend/cantorWorksheet/CantorWorksheet.cpp b/src/backend/cantorWorksheet/CantorWorksheet.cpp index ca793d7d4..dd50c5e6a 100644 --- a/src/backend/cantorWorksheet/CantorWorksheet.cpp +++ b/src/backend/cantorWorksheet/CantorWorksheet.cpp @@ -1,326 +1,326 @@ /*************************************************************************** File : CantorWorksheet.cpp Project : LabPlot Description : Aspect providing a Cantor Worksheets for Multiple backends -------------------------------------------------------------------- Copyright : (C) 2015 Garvit Khatri (garvitdelhi@gmail.com) Copyright : (C) 2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CantorWorksheet.h" #include "VariableParser.h" #include "backend/core/column/Column.h" #include "backend/core/column/ColumnPrivate.h" #include "backend/core/Project.h" #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h" #include #include #include #include #include #include "cantor/cantor_part.h" #include #include #include CantorWorksheet::CantorWorksheet(const QString &name, bool loading) : AbstractPart(name, AspectType::CantorWorksheet), m_backendName(name) { if (!loading) init(); } /*! initializes Cantor's part and plugins */ bool CantorWorksheet::init(QByteArray* content) { KPluginLoader loader(QLatin1String("cantorpart")); KPluginFactory* factory = loader.factory(); if (!factory) { //we can only get to this here if we open a project having Cantor content and Cantor plugins were not found. //return false here, a proper error message will be created in load() and propagated further. WARN("Failed to load Cantor plugin:") - WARN("Cantor Part file name: " << loader.fileName().toStdString()) - WARN(" " << loader.errorString().toStdString()) + WARN("Cantor Part file name: " << STDSTRING(loader.fileName())) + WARN(" " << STDSTRING(loader.errorString())) return false; } else { m_part = factory->create(this, QVariantList() << m_backendName << QLatin1String("--noprogress")); if (!m_part) { DEBUG("Could not create the Cantor Part.") return false; } m_worksheetAccess = m_part->findChild(Cantor::WorksheetAccessInterface::Name); //load worksheet content if available if (content) m_worksheetAccess->loadWorksheetFromByteArray(content); connect(m_worksheetAccess, SIGNAL(modified()), this, SLOT(modified())); //Cantor's session m_session = m_worksheetAccess->session(); connect(m_session, SIGNAL(statusChanged(Cantor::Session::Status)), this, SIGNAL(statusChanged(Cantor::Session::Status))); //variable model #ifndef OLD_CANTORLIBS_VERSION m_variableModel = m_session->variableDataModel(); #else m_variableModel = m_session->variableModel(); #endif connect(m_variableModel, &QAbstractItemModel::dataChanged, this, &CantorWorksheet::dataChanged); connect(m_variableModel, &QAbstractItemModel::rowsInserted, this, &CantorWorksheet::rowsInserted); connect(m_variableModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &CantorWorksheet::rowsAboutToBeRemoved); connect(m_variableModel, &QAbstractItemModel::modelReset, this, &CantorWorksheet::modelReset); //available plugins auto* handler = m_part->findChild(QLatin1String("PanelPluginHandler")); if (!handler) { KMessageBox::error(nullptr, i18n("No PanelPluginHandle found for the Cantor Part.")); return false; } m_plugins = handler->plugins(); } return true; } //SLots void CantorWorksheet::dataChanged(const QModelIndex& index) { const QString& name = m_variableModel->data(m_variableModel->index(index.row(), 0)).toString(); Column* col = child(name); if (col) { // Cantor::DefaultVariableModel::DataRole == 257 QVariant dataValue = m_variableModel->data(m_variableModel->index(index.row(), 1), 257); if (dataValue.isNull()) dataValue = m_variableModel->data(m_variableModel->index(index.row(), 1)); const QString& value = dataValue.toString(); VariableParser parser(m_backendName, value); if (parser.isParsed()) col->replaceValues(0, parser.values()); } } void CantorWorksheet::rowsInserted(const QModelIndex& parent, int first, int last) { Q_UNUSED(parent) for (int i = first; i <= last; ++i) { const QString& name = m_variableModel->data(m_variableModel->index(i, 0)).toString(); QVariant dataValue = m_variableModel->data(m_variableModel->index(i, 1), 257); if (dataValue.isNull()) dataValue = m_variableModel->data(m_variableModel->index(i, 1)); const QString& value = dataValue.toString(); VariableParser parser(m_backendName, value); if (parser.isParsed()) { Column* col = child(name); if (col) { col->replaceValues(0, parser.values()); } else { col = new Column(name, parser.values()); col->setUndoAware(false); addChild(col); //TODO: Cantor currently ignores the order of variables in the worksheets //and adds new variables at the last position in the model. //Fix this in Cantor and switch to insertChildBefore here later. //insertChildBefore(col, child(i)); } } else { //the already existing variable doesn't contain any numerical values -> remove it Column* col = child(name); if (col) removeChild(col); } } project()->setChanged(true); } void CantorWorksheet::modified() { project()->setChanged(true); } void CantorWorksheet::modelReset() { for (int i = 0; i < childCount(); ++i) child(i)->remove(); } void CantorWorksheet::rowsAboutToBeRemoved(const QModelIndex & parent, int first, int last) { Q_UNUSED(parent); #ifndef OLD_CANTORLIBS_VERSION for (int i = first; i <= last; ++i) { const QString& name = m_variableModel->data(m_variableModel->index(first, 0)).toString(); Column* column = child(name); if (column) column->remove(); } #else Q_UNUSED(first); Q_UNUSED(last); //TODO: Old Cantor removes rows from the model even when the variable was changed only. //We don't want this behaviour since this removes the columns from the datasource in the curve. return; #endif } QList CantorWorksheet::getPlugins() { return m_plugins; } KParts::ReadWritePart* CantorWorksheet::part() { return m_part; } QIcon CantorWorksheet::icon() const { if (m_session) return QIcon::fromTheme(m_session->backend()->icon()); return QIcon(); } QWidget* CantorWorksheet::view() const { if (!m_partView) { m_view = new CantorWorksheetView(const_cast(this)); m_view->setBaseSize(1500, 1500); m_partView = m_view; // connect(m_view, SIGNAL(statusInfo(QString)), this, SIGNAL(statusInfo(QString))); } return m_partView; } //! Return a new context menu. /** * The caller takes ownership of the menu. */ QMenu* CantorWorksheet::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); Q_ASSERT(menu); emit requestProjectContextMenu(menu); return menu; } QString CantorWorksheet::backendName() { return this->m_backendName; } //TODO bool CantorWorksheet::exportView() const { return false; } bool CantorWorksheet::printView() { m_part->action("file_print")->trigger(); return true; } bool CantorWorksheet::printPreview() const { m_part->action("file_print_preview")->trigger(); return true; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CantorWorksheet::save(QXmlStreamWriter* writer) const{ writer->writeStartElement("cantorWorksheet"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement( "general" ); writer->writeAttribute( "backend_name", m_backendName); //TODO: save worksheet settings writer->writeEndElement(); //save the content of Cantor's worksheet QByteArray content = m_worksheetAccess->saveWorksheetToByteArray(); writer->writeStartElement("worksheet"); writer->writeAttribute("content", content.toBase64()); writer->writeEndElement(); //save columns(variables) for (auto* col : children(IncludeHidden)) col->save(writer); writer->writeEndElement(); // close "cantorWorksheet" section } //! Load from XML bool CantorWorksheet::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; bool rc = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cantorWorksheet") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); m_backendName = attribs.value("backend_name").toString().trimmed(); if (m_backendName.isEmpty()) reader->raiseWarning(attributeWarning.subs("backend_name").toString()); } else if (!preview && reader->name() == "worksheet") { attribs = reader->attributes(); QString str = attribs.value("content").toString().trimmed(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("content").toString()); QByteArray content = QByteArray::fromBase64(str.toLatin1()); rc = init(&content); if (!rc) { QString msg = i18n("This project has Cantor content but no Cantor plugins were found. Please check your installation. The project will be closed."); reader->raiseError(msg); return false; } } else if (!preview && reader->name() == "column") { Column* column = new Column(QString()); column->setUndoAware(false); if (!column->load(reader, preview)) { delete column; return false; } addChild(column); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } diff --git a/src/backend/core/column/Column.cpp b/src/backend/core/column/Column.cpp index e65ee191b..a4a988d49 100644 --- a/src/backend/core/column/Column.cpp +++ b/src/backend/core/column/Column.cpp @@ -1,2149 +1,2149 @@ /*************************************************************************** File : Column.cpp Project : LabPlot Description : Aspect that manages a column -------------------------------------------------------------------- Copyright : (C) 2007-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2013-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/core/column/Column.h" #include "backend/core/column/ColumnPrivate.h" #include "backend/core/column/ColumnStringIO.h" #include "backend/core/column/columncommands.h" #include "backend/core/Project.h" #include "backend/lib/XmlStreamReader.h" #include "backend/core/datatypes/String2DateTimeFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/Histogram.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/XYAnalysisCurve.h" extern "C" { #include } #include #include #include #include #include #include #include /** * \class Column * \brief Aspect that manages a column * * This class represents a column, i.e., (mathematically) a 1D vector of * values with a header. It provides a public reading and (undo aware) writing * interface as defined in AbstractColumn. A column * can have one of currently three data types: double, QString, or * QDateTime. The string representation of the values can differ depending * on the mode of the column. * * Column inherits from AbstractAspect and is intended to be a child * of the corresponding Spreadsheet in the aspect hierarchy. Columns don't * have a view as they are intended to be displayed inside a spreadsheet. */ Column::Column(const QString& name, ColumnMode mode) : AbstractColumn(name, AspectType::Column), d(new ColumnPrivate(this, mode)) { init(); } /** * \brief Common part of ctors */ void Column::init() { m_string_io = new ColumnStringIO(this); d->inputFilter()->input(0, m_string_io); d->outputFilter()->input(0, this); d->inputFilter()->setHidden(true); d->outputFilter()->setHidden(true); addChildFast(d->inputFilter()); addChildFast(d->outputFilter()); m_suppressDataChangedSignal = false; m_usedInActionGroup = new QActionGroup(this); connect(m_usedInActionGroup, &QActionGroup::triggered, this, &Column::navigateTo); connect(this, &AbstractColumn::maskingChanged, this, [=]{d->propertiesAvailable = false;}); } Column::~Column() { delete m_string_io; delete d; } QMenu* Column::createContextMenu() { QMenu* menu = AbstractAspect::createContextMenu(); QAction* firstAction{nullptr}; //insert after "rename" and "delete" actions, if available. //MQTTTopic columns don't have these actions if (menu->actions().size() > 1) firstAction = menu->actions().at(1); //add actions available in SpreadsheetView //TODO: we don't need to add anything from the view for MQTTTopic columns. //at the moment it's ok to check to the null pointer for firstAction here. //later, once we have some actions in the menu also for MQTT topics we'll //need to explicitly to dynamic_cast for MQTTTopic if (firstAction) emit requestProjectContextMenu(menu); //"Used in" menu containing all curves where the column is used QMenu* usedInMenu = new QMenu(i18n("Used in")); usedInMenu->setIcon(QIcon::fromTheme("go-next-view")); //remove previously added actions for (auto* action : m_usedInActionGroup->actions()) m_usedInActionGroup->removeAction(action); Project* project = this->project(); //add curves where the column is currently in use usedInMenu->addSection(i18n("XY-Curves")); auto curves = project->children(AbstractAspect::Recursive); for (const auto* curve : curves) { bool used = false; const auto* analysisCurve = dynamic_cast(curve); if (analysisCurve) { if (analysisCurve->dataSourceType() == XYAnalysisCurve::DataSourceSpreadsheet && (analysisCurve->xDataColumn() == this || analysisCurve->yDataColumn() == this || analysisCurve->y2DataColumn() == this) ) used = true; } else { if (curve->xColumn() == this || curve->yColumn() == this) used = true; } if (used) { QAction* action = new QAction(curve->icon(), curve->name(), m_usedInActionGroup); action->setData(curve->path()); usedInMenu->addAction(action); } } //add histograms where the column is used usedInMenu->addSection(i18n("Histograms")); auto hists = project->children(AbstractAspect::Recursive); for (const auto* hist : hists) { bool used = (hist->dataColumn() == this); if (used) { QAction* action = new QAction(hist->icon(), hist->name(), m_usedInActionGroup); action->setData(hist->path()); usedInMenu->addAction(action); } } //add calculated columns where the column is used in formula variables usedInMenu->addSection(i18n("Calculated Columns")); QVector columns = project->children(AbstractAspect::Recursive); const QString& path = this->path(); for (const auto* column : columns) { auto paths = column->formulaVariableColumnPaths(); if (paths.indexOf(path) != -1) { QAction* action = new QAction(column->icon(), column->name(), m_usedInActionGroup); action->setData(column->path()); usedInMenu->addAction(action); } } if (firstAction) menu->insertSeparator(firstAction); menu->insertMenu(firstAction, usedInMenu); menu->insertSeparator(firstAction); return menu; } void Column::navigateTo(QAction* action) { project()->navigateTo(action->data().toString()); } /*! * */ void Column::setSuppressDataChangedSignal(bool b) { m_suppressDataChangedSignal = b; } void Column::addUsedInPlots(QVector& plots) { const Project* project = this->project(); //when executing tests we don't create any project, //add a null-pointer check for tests here. if (!project) return; auto curves = project->children(AbstractAspect::Recursive); //determine the plots where the column is consumed for (const auto* curve : curves) { if (curve->xColumn() == this || curve->yColumn() == this || (curve->xErrorType() == XYCurve::SymmetricError && curve->xErrorPlusColumn() == this) || (curve->xErrorType() == XYCurve::AsymmetricError && (curve->xErrorPlusColumn() == this ||curve->xErrorMinusColumn() == this)) || (curve->yErrorType() == XYCurve::SymmetricError && curve->yErrorPlusColumn() == this) || (curve->yErrorType() == XYCurve::AsymmetricError && (curve->yErrorPlusColumn() == this ||curve->yErrorMinusColumn() == this)) ) { auto* plot = static_cast(curve->parentAspect()); if (plots.indexOf(plot) == -1) plots << plot; } } auto hists = project->children(AbstractAspect::Recursive); for (const auto* hist : hists) { if (hist->dataColumn() == this ) { auto* plot = static_cast(hist->parentAspect()); if (plots.indexOf(plot) == -1) plots << plot; } } } /** * \brief Set the column mode * * This sets the column mode and, if * necessary, converts it to another datatype. */ void Column::setColumnMode(AbstractColumn::ColumnMode mode) { if (mode == columnMode()) return; DEBUG("Column::setColumnMode()"); beginMacro(i18n("%1: change column type", name())); auto* old_input_filter = d->inputFilter(); auto* old_output_filter = d->outputFilter(); exec(new ColumnSetModeCmd(d, mode)); if (d->inputFilter() != old_input_filter) { removeChild(old_input_filter); addChild(d->inputFilter()); d->inputFilter()->input(0, m_string_io); } if (d->outputFilter() != old_output_filter) { removeChild(old_output_filter); addChild(d->outputFilter()); d->outputFilter()->input(0, this); } endMacro(); DEBUG("Column::setColumnMode() DONE"); } void Column::setColumnModeFast(AbstractColumn::ColumnMode mode) { if (mode == columnMode()) return; auto* old_input_filter = d->inputFilter(); auto* old_output_filter = d->outputFilter(); exec(new ColumnSetModeCmd(d, mode)); if (d->inputFilter() != old_input_filter) { removeChild(old_input_filter); addChildFast(d->inputFilter()); d->inputFilter()->input(0, m_string_io); } if (d->outputFilter() != old_output_filter) { removeChild(old_output_filter); addChildFast(d->outputFilter()); d->outputFilter()->input(0, this); } } bool Column::isDraggable() const { return true; } QVector Column::dropableOn() const { return QVector{AspectType::CartesianPlot}; } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool Column::copy(const AbstractColumn* other) { Q_CHECK_PTR(other); if (other->columnMode() != columnMode()) return false; exec(new ColumnFullCopyCmd(d, other)); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool Column::copy(const AbstractColumn* source, int source_start, int dest_start, int num_rows) { Q_CHECK_PTR(source); if (source->columnMode() != columnMode()) return false; exec(new ColumnPartialCopyCmd(d, source, source_start, dest_start, num_rows)); return true; } /** * \brief Insert some empty (or initialized with zero) rows */ void Column::handleRowInsertion(int before, int count) { AbstractColumn::handleRowInsertion(before, count); exec(new ColumnInsertRowsCmd(d, before, count)); if (!m_suppressDataChangedSignal) emit dataChanged(this); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; } /** * \brief Remove 'count' rows starting from row 'first' */ void Column::handleRowRemoval(int first, int count) { AbstractColumn::handleRowRemoval(first, count); exec(new ColumnRemoveRowsCmd(d, first, count)); if (!m_suppressDataChangedSignal) emit dataChanged(this); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; } /** * \brief Set the column plot designation */ void Column::setPlotDesignation(AbstractColumn::PlotDesignation pd) { if (pd != plotDesignation()) exec(new ColumnSetPlotDesignationCmd(d, pd)); } /** * \brief Get width */ int Column::width() const { return d->width(); } /** * \brief Set width */ void Column::setWidth(int value) { d->setWidth(value); } /** * \brief Clear the whole column */ void Column::clear() { exec(new ColumnClearCmd(d)); } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //! \name Formula related functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Returns the formula used to generate column values */ QString Column:: formula() const { return d->formula(); } const QStringList& Column::formulaVariableNames() const { return d->formulaVariableNames(); } const QVector& Column::formulaVariableColumns() const { return d->formulaVariableColumns(); } const QStringList& Column::formulaVariableColumnPaths() const { return d->formulaVariableColumnPaths(); } void Column::setformulVariableColumnsPath(int index, const QString& path) { d->setformulVariableColumnsPath(index, path); } void Column::setformulVariableColumn(int index, Column* column) { d->setformulVariableColumn(index, column); } bool Column::formulaAutoUpdate() const { return d->formulaAutoUpdate(); } /** * \brief Sets the formula used to generate column values */ void Column::setFormula(const QString& formula, const QStringList& variableNames, const QVector& columns, bool autoUpdate) { exec(new ColumnSetGlobalFormulaCmd(d, formula, variableNames, columns, autoUpdate)); } /*! * in case the cell values are calculated via a global column formula, * updates the values on data changes in all the dependent changes in the * "variable columns". */ void Column::updateFormula() { d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; d->updateFormula(); } /** * \brief Set a formula string for an interval of rows */ void Column::setFormula(const Interval& i, const QString& formula) { exec(new ColumnSetFormulaCmd(d, i, formula)); } /** * \brief Overloaded function for convenience */ void Column::setFormula(int row, const QString& formula) { setFormula(Interval(row, row), formula); } /** * \brief Clear all formulas */ void Column::clearFormulas() { exec(new ColumnClearFormulasCmd(d)); } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //! \name type specific functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Text */ void Column::setTextAt(int row, const QString& new_value) { DEBUG("Column::setTextAt()"); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnSetTextCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is Text */ void Column::replaceTexts(int first, const QVector& new_values) { DEBUG("Column::replaceTexts()"); if (!new_values.isEmpty()) { //TODO: do we really need this check? d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnReplaceTextsCmd(d, first, new_values)); } } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void Column::setDateAt(int row, QDate new_value) { setDateTimeAt(row, QDateTime(new_value, timeAt(row))); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void Column::setTimeAt(int row, QTime new_value) { setDateTimeAt(row, QDateTime(dateAt(row), new_value)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void Column::setDateTimeAt(int row, const QDateTime& new_value) { d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnSetDateTimeCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is DateTime, Month or Day */ void Column::replaceDateTimes(int first, const QVector& new_values) { if (!new_values.isEmpty()) { d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnReplaceDateTimesCmd(d, first, new_values)); } } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Numeric */ void Column::setValueAt(int row, const double new_value) { // DEBUG("Column::setValueAt()"); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnSetValueCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is Numeric */ void Column::replaceValues(int first, const QVector& new_values) { DEBUG("Column::replaceValues()"); if (!new_values.isEmpty()) { d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnReplaceValuesCmd(d, first, new_values)); } } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Integer */ void Column::setIntegerAt(int row, const int new_value) { DEBUG("Column::setIntegerAt()"); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnSetIntegerCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is Integer */ void Column::replaceInteger(int first, const QVector& new_values) { DEBUG("Column::replaceInteger()"); if (!new_values.isEmpty()) { d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnReplaceIntegerCmd(d, first, new_values)); } } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is BigInt */ void Column::setBigIntAt(int row, const qint64 new_value) { DEBUG("Column::setBigIntAt()"); d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnSetBigIntCmd(d, row, new_value)); } /** * \brief Replace a range of values * * Use this only when columnMode() is BigInt */ void Column::replaceBigInt(int first, const QVector& new_values) { DEBUG("Column::replaceInteger()"); if (!new_values.isEmpty()) { d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; exec(new ColumnReplaceBigIntCmd(d, first, new_values)); } } /*! * \brief Column::properties * Returns the column properties of this curve (monoton increasing, monoton decreasing, ... ) * \see AbstractColumn::properties */ AbstractColumn::Properties Column::properties() const { if (!d->propertiesAvailable) d->updateProperties(); return d->properties; } const Column::ColumnStatistics& Column::statistics() const { if (!d->statisticsAvailable) calculateStatistics(); return d->statistics; } void Column::calculateStatistics() const { if ( (columnMode() != AbstractColumn::Numeric) && (columnMode() != AbstractColumn::Integer) && (columnMode() != AbstractColumn::BigInt) ) return; d->statistics = ColumnStatistics(); ColumnStatistics& statistics = d->statistics; int rowValuesSize = 0; int notNanCount = 0; double val; double columnSum = 0.0; double columnProduct = 1.0; double columnSumNeg = 0.0; double columnSumSquare = 0.0; statistics.minimum = INFINITY; statistics.maximum = -INFINITY; QMap frequencyOfValues; QVector rowData; if (columnMode() == AbstractColumn::Numeric) { auto* rowValues = reinterpret_cast*>(data()); rowValuesSize = rowValues->size(); rowData.reserve(rowValuesSize); for (int row = 0; row < rowValuesSize; ++row) { val = rowValues->value(row); if (std::isnan(val) || isMasked(row)) continue; if (val < statistics.minimum) statistics.minimum = val; if (val > statistics.maximum) statistics.maximum = val; columnSum += val; columnSumNeg += (1.0 / val); columnSumSquare += pow(val, 2.0); columnProduct *= val; if (frequencyOfValues.contains(val)) frequencyOfValues.operator [](val)++; else frequencyOfValues.insert(val, 1); ++notNanCount; rowData.push_back(val); } } else if (columnMode() == AbstractColumn::Integer) { //TODO: code duplication because of the reinterpret_cast... auto* rowValues = reinterpret_cast*>(data()); rowValuesSize = rowValues->size(); rowData.reserve(rowValuesSize); for (int row = 0; row < rowValuesSize; ++row) { val = rowValues->value(row); if (std::isnan(val) || isMasked(row)) continue; if (val < statistics.minimum) statistics.minimum = val; if (val > statistics.maximum) statistics.maximum = val; columnSum += val; columnSumNeg += (1.0 / val); columnSumSquare += pow(val, 2.0); columnProduct *= val; if (frequencyOfValues.contains(val)) frequencyOfValues.operator [](val)++; else frequencyOfValues.insert(val, 1); ++notNanCount; rowData.push_back(val); } } else if (columnMode() == AbstractColumn::BigInt) { //TODO: code duplication because of the reinterpret_cast... auto* rowValues = reinterpret_cast*>(data()); rowValuesSize = rowValues->size(); rowData.reserve(rowValuesSize); for (int row = 0; row < rowValuesSize; ++row) { val = rowValues->value(row); if (std::isnan(val) || isMasked(row)) continue; if (val < statistics.minimum) statistics.minimum = val; if (val > statistics.maximum) statistics.maximum = val; columnSum += val; columnSumNeg += (1.0 / val); columnSumSquare += pow(val, 2.0); columnProduct *= val; if (frequencyOfValues.contains(val)) frequencyOfValues.operator [](val)++; else frequencyOfValues.insert(val, 1); ++notNanCount; rowData.push_back(val); } } if (notNanCount == 0) { d->statisticsAvailable = true; return; } if (rowData.size() < rowValuesSize) rowData.squeeze(); statistics.arithmeticMean = columnSum / notNanCount; statistics.geometricMean = pow(columnProduct, 1.0 / notNanCount); statistics.harmonicMean = notNanCount / columnSumNeg; statistics.contraharmonicMean = columnSumSquare / columnSum; double columnSumVariance = 0; double columnSumMeanDeviation = 0.0; double columnSumMedianDeviation = 0.0; double sumForCentralMoment_r3 = 0.0; double sumForCentralMoment_r4 = 0.0; gsl_sort(rowData.data(), 1, notNanCount); statistics.median = (notNanCount%2) ? rowData.at((int)((notNanCount-1)/2)) : (rowData.at((int)((notNanCount-1)/2)) + rowData.at((int)(notNanCount/2)))/2.0; QVector absoluteMedianList; absoluteMedianList.reserve((int)notNanCount); absoluteMedianList.resize((int)notNanCount); for (int row = 0; row < notNanCount; ++row) { val = rowData.value(row); columnSumVariance += pow(val - statistics.arithmeticMean, 2.0); sumForCentralMoment_r3 += pow(val - statistics.arithmeticMean, 3.0); sumForCentralMoment_r4 += pow(val - statistics.arithmeticMean, 4.0); columnSumMeanDeviation += fabs( val - statistics.arithmeticMean ); absoluteMedianList[row] = fabs(val - statistics.median); columnSumMedianDeviation += absoluteMedianList[row]; } statistics.meanDeviationAroundMedian = columnSumMedianDeviation / notNanCount; statistics.medianDeviation = (notNanCount%2) ? absoluteMedianList.at((int)((notNanCount-1)/2)) : (absoluteMedianList.at((int)((notNanCount-1)/2)) + absoluteMedianList.at((int)(notNanCount/2)))/2.0; const double centralMoment_r3 = sumForCentralMoment_r3 / notNanCount; const double centralMoment_r4 = sumForCentralMoment_r4 / notNanCount; statistics.variance = columnSumVariance / notNanCount; statistics.standardDeviation = sqrt(statistics.variance); statistics.skewness = centralMoment_r3 / pow(statistics.standardDeviation, 3.0); statistics.kurtosis = (centralMoment_r4 / pow(statistics.standardDeviation, 4.0)) - 3.0; statistics.meanDeviation = columnSumMeanDeviation / notNanCount; double entropy = 0.0; for (const auto& v : frequencyOfValues) { const double frequencyNorm = static_cast(v) / notNanCount; entropy += (frequencyNorm * log2(frequencyNorm)); } statistics.entropy = -entropy; d->statisticsAvailable = true; } ////////////////////////////////////////////////////////////////////////////////////////////// void* Column::data() const { return d->data(); } /*! * return \c true if the column has numeric values, \c false otherwise. */ bool Column::hasValues() const { if (d->hasValuesAvailable) return d->hasValues; bool foundValues = false; if (columnMode() == AbstractColumn::Numeric) { for (int row = 0; row < rowCount(); ++row) { if (!std::isnan(valueAt(row))) { foundValues = true; break; } } } else if (columnMode() == AbstractColumn::Integer || columnMode() == AbstractColumn::BigInt) { //integer column has always valid values foundValues = true; } else if (columnMode() == AbstractColumn::DateTime) { for (int row = 0; row < rowCount(); ++row) { if (dateTimeAt(row).isValid()) { foundValues = true; break; } } } d->hasValues = foundValues; d->hasValuesAvailable = true; return d->hasValues; } /** * \brief Return the content of row 'row'. * * Use this only when columnMode() is Text */ QString Column::textAt(int row) const { return d->textAt(row); } /** * \brief Return the date part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDate Column::dateAt(int row) const { return d->dateAt(row); } /** * \brief Return the time part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QTime Column::timeAt(int row) const { return d->timeAt(row); } /** * \brief Return the QDateTime in row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDateTime Column::dateTimeAt(int row) const { return d->dateTimeAt(row); } /** * \brief Return the double value in row 'row' */ double Column::valueAt(int row) const { return d->valueAt(row); } /** * \brief Return the int value in row 'row' */ int Column::integerAt(int row) const { return d->integerAt(row); } /** * \brief Return the bigint value in row 'row' */ qint64 Column::bigIntAt(int row) const { return d->bigIntAt(row); } /* * call this function if the data of the column was changed directly via the data()-pointer * and not via the setValueAt() in order to emit the dataChanged-signal. * This is used e.g. in \c XYFitCurvePrivate::recalculate() */ void Column::setChanged() { d->propertiesAvailable = false; if (!m_suppressDataChangedSignal) emit dataChanged(this); d->statisticsAvailable = false; d->hasValuesAvailable = false; } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// /** * \brief Return an icon to be used for decorating the views and spreadsheet column headers */ QIcon Column::icon() const { return iconForMode(columnMode()); } //////////////////////////////////////////////////////////////////////////////////////////////////// //! \name serialize/deserialize //@{ //////////////////////////////////////////////////////////////////////////////////////////////////// /** * \brief Save the column as XML */ void Column::save(QXmlStreamWriter* writer) const { writer->writeStartElement("column"); writeBasicAttributes(writer); writer->writeAttribute("rows", QString::number(rowCount())); writer->writeAttribute("designation", QString::number(plotDesignation())); writer->writeAttribute("mode", QString::number(columnMode())); writer->writeAttribute("width", QString::number(width())); //save the formula used to generate column values, if available if (!formula().isEmpty() ) { writer->writeStartElement("formula"); writer->writeAttribute("autoUpdate", QString::number(d->formulaAutoUpdate())); writer->writeTextElement("text", formula()); writer->writeStartElement("variableNames"); for (const auto& name : formulaVariableNames()) writer->writeTextElement("name", name); writer->writeEndElement(); writer->writeStartElement("columnPathes"); for (const auto& path : formulaVariableColumnPaths()) writer->writeTextElement("path", path); writer->writeEndElement(); writer->writeEndElement(); } writeCommentElement(writer); writer->writeStartElement("input_filter"); d->inputFilter()->save(writer); writer->writeEndElement(); writer->writeStartElement("output_filter"); d->outputFilter()->save(writer); writer->writeEndElement(); XmlWriteMask(writer); //TODO: formula in cells is not implemented yet // QVector< Interval > formulas = formulaIntervals(); // foreach(const Interval& interval, formulas) { // writer->writeStartElement("formula"); // writer->writeAttribute("start_row", QString::number(interval.start())); // writer->writeAttribute("end_row", QString::number(interval.end())); // writer->writeCharacters(formula(interval.start())); // writer->writeEndElement(); // } int i; switch (columnMode()) { case AbstractColumn::Numeric: { const char* data = reinterpret_cast(static_cast< QVector* >(d->data())->constData()); size_t size = d->rowCount() * sizeof(double); writer->writeCharacters(QByteArray::fromRawData(data, (int)size).toBase64()); break; } case AbstractColumn::Integer: { const char* data = reinterpret_cast(static_cast< QVector* >(d->data())->constData()); size_t size = d->rowCount() * sizeof(int); writer->writeCharacters(QByteArray::fromRawData(data, (int)size).toBase64()); break; } case AbstractColumn::BigInt: { const char* data = reinterpret_cast(static_cast< QVector* >(d->data())->constData()); size_t size = d->rowCount() * sizeof(qint64); writer->writeCharacters(QByteArray::fromRawData(data, (int)size).toBase64()); break; } case AbstractColumn::Text: for (i = 0; i < rowCount(); ++i) { writer->writeStartElement("row"); writer->writeAttribute("index", QString::number(i)); writer->writeCharacters(textAt(i)); writer->writeEndElement(); } break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (i = 0; i < rowCount(); ++i) { writer->writeStartElement("row"); writer->writeAttribute("index", QString::number(i)); writer->writeCharacters(dateTimeAt(i).toString("yyyy-dd-MM hh:mm:ss:zzz")); writer->writeEndElement(); } break; } writer->writeEndElement(); // "column" } //TODO: extra header class DecodeColumnTask : public QRunnable { public: DecodeColumnTask(ColumnPrivate* priv, const QString& content) { m_private = priv; m_content = content; }; void run() override { QByteArray bytes = QByteArray::fromBase64(m_content.toLatin1()); if (m_private->columnMode() == AbstractColumn::Numeric) { auto* data = new QVector(bytes.size()/(int)sizeof(double)); memcpy(data->data(), bytes.data(), bytes.size()); m_private->replaceData(data); } else if (m_private->columnMode() == AbstractColumn::BigInt) { auto* data = new QVector(bytes.size()/(int)sizeof(qint64)); memcpy(data->data(), bytes.data(), bytes.size()); m_private->replaceData(data); } else { auto* data = new QVector(bytes.size()/(int)sizeof(int)); memcpy(data->data(), bytes.data(), bytes.size()); m_private->replaceData(data); } } private: ColumnPrivate* m_private; QString m_content; }; /** * \brief Load the column from XML */ bool Column::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value("rows").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("rows").toString()); else d->resizeTo(str.toInt()); str = attribs.value("designation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("designation").toString()); else d->setPlotDesignation( AbstractColumn::PlotDesignation(str.toInt()) ); str = attribs.value("mode").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("mode").toString()); else setColumnModeFast( AbstractColumn::ColumnMode(str.toInt()) ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else d->setWidth(str.toInt()); // read child elements while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement()) break; if (reader->isStartElement()) { bool ret_val = true; if (reader->name() == "comment") ret_val = readCommentElement(reader); else if (reader->name() == "input_filter") ret_val = XmlReadInputFilter(reader); else if (reader->name() == "output_filter") ret_val = XmlReadOutputFilter(reader); else if (reader->name() == "mask") ret_val = XmlReadMask(reader); else if (reader->name() == "formula") ret_val = XmlReadFormula(reader); else if (reader->name() == "row") ret_val = XmlReadRow(reader); else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } if (!ret_val) return false; } if (!preview) { QString content = reader->text().toString().trimmed(); if (!content.isEmpty() && ( columnMode() == AbstractColumn::Numeric || columnMode() == AbstractColumn::Integer || columnMode() == AbstractColumn::BigInt)) { auto* task = new DecodeColumnTask(d, content); QThreadPool::globalInstance()->start(task); } } } return !reader->error(); } void Column::finalizeLoad() { d->finalizeLoad(); } /** * \brief Read XML input filter element */ bool Column::XmlReadInputFilter(XmlStreamReader* reader) { Q_ASSERT(reader->isStartElement() == true && reader->name() == "input_filter"); if (!reader->skipToNextTag()) return false; if (!d->inputFilter()->load(reader, false)) return false; if (!reader->skipToNextTag()) return false; Q_ASSERT(reader->isEndElement() == true && reader->name() == "input_filter"); return true; } /** * \brief Read XML output filter element */ bool Column::XmlReadOutputFilter(XmlStreamReader* reader) { Q_ASSERT(reader->isStartElement() == true && reader->name() == "output_filter"); if (!reader->skipToNextTag()) return false; if (!d->outputFilter()->load(reader, false)) return false; if (!reader->skipToNextTag()) return false; Q_ASSERT(reader->isEndElement() == true && reader->name() == "output_filter"); return true; } /** * \brief Read XML formula element */ bool Column::XmlReadFormula(XmlStreamReader* reader) { QString formula; QStringList variableNames; QStringList columnPathes; bool autoUpdate = reader->attributes().value("autoUpdate").toInt(); while (reader->readNext()) { if (reader->isEndElement()) break; if (reader->name() == "text") formula = reader->readElementText(); else if (reader->name() == "variableNames") { while (reader->readNext()) { if (reader->name() == "variableNames" && reader->isEndElement()) break; if (reader->isStartElement()) variableNames << reader->readElementText(); } } else if (reader->name() == "columnPathes") { while (reader->readNext()) { if (reader->name() == "columnPathes" && reader->isEndElement()) break; if (reader->isStartElement()) columnPathes << reader->readElementText(); } } } d->setFormula(formula, variableNames, columnPathes, autoUpdate); return true; } //TODO: read cell formula, not implemented yet // bool Column::XmlReadFormula(XmlStreamReader* reader) // { // Q_ASSERT(reader->isStartElement() && reader->name() == "formula"); // // bool ok1, ok2; // int start, end; // start = reader->readAttributeInt("start_row", &ok1); // end = reader->readAttributeInt("end_row", &ok2); // if (!ok1 || !ok2) // { // reader->raiseError(i18n("invalid or missing start or end row")); // return false; // } // setFormula(Interval(start,end), reader->readElementText()); // // return true; // } /** * \brief Read XML row element */ bool Column::XmlReadRow(XmlStreamReader* reader) { Q_ASSERT(reader->isStartElement() == true && reader->name() == "row"); // QXmlStreamAttributes attribs = reader->attributes(); bool ok; int index = reader->readAttributeInt("index", &ok); if (!ok) { reader->raiseError(i18n("invalid or missing row index")); return false; } QString str = reader->readElementText(); switch (columnMode()) { case AbstractColumn::Numeric: { double value = str.toDouble(&ok); if (!ok) { reader->raiseError(i18n("invalid row value")); return false; } setValueAt(index, value); break; } case AbstractColumn::Integer: { int value = str.toInt(&ok); if (!ok) { reader->raiseError(i18n("invalid row value")); return false; } setIntegerAt(index, value); break; } case AbstractColumn::BigInt: { qint64 value = str.toLongLong(&ok); if (!ok) { reader->raiseError(i18n("invalid row value")); return false; } setBigIntAt(index, value); break; } case AbstractColumn::Text: setTextAt(index, str); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: QDateTime date_time = QDateTime::fromString(str,"yyyy-dd-MM hh:mm:ss:zzz"); setDateTimeAt(index, date_time); break; } return true; } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// /** * \brief Return whether the object is read-only */ bool Column::isReadOnly() const { return false; } /** * \brief Return the column mode * * This function is mostly used by spreadsheets but can also be used * by plots. The column mode specifies how to interpret * the values in the column additional to the data type. */ AbstractColumn::ColumnMode Column::columnMode() const { return d->columnMode(); } /** * \brief Return the data vector size * * This returns the number of rows that actually contain data. * Rows beyond this can be masked etc. but should be ignored by filters, * plots etc. */ int Column::rowCount() const { return d->rowCount(); } /** * \brief Return the column plot designation */ AbstractColumn::PlotDesignation Column::plotDesignation() const { return d->plotDesignation(); } QString Column::plotDesignationString() const { switch (plotDesignation()) { case AbstractColumn::NoDesignation: return QString(""); case AbstractColumn::X: return QLatin1String("[X]"); case AbstractColumn::Y: return QLatin1String("[Y]"); case AbstractColumn::Z: return QLatin1String("[Z]"); case AbstractColumn::XError: return QLatin1String("[") + i18n("X-error") + QLatin1Char(']'); case AbstractColumn::XErrorPlus: return QLatin1String("[") + i18n("X-error +") + QLatin1Char(']'); case AbstractColumn::XErrorMinus: return QLatin1String("[") + i18n("X-error -") + QLatin1Char(']'); case AbstractColumn::YError: return QLatin1String("[") + i18n("Y-error") + QLatin1Char(']'); case AbstractColumn::YErrorPlus: return QLatin1String("[") + i18n("Y-error +") + QLatin1Char(']'); case AbstractColumn::YErrorMinus: return QLatin1String("[") + i18n("Y-error -") + QLatin1Char(']'); } return QString(""); } AbstractSimpleFilter* Column::outputFilter() const { return d->outputFilter(); } /** * \brief Return a wrapper column object used for String I/O. */ ColumnStringIO* Column::asStringColumn() const { return m_string_io; } //////////////////////////////////////////////////////////////////////////////// //! \name IntervalAttribute related functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the formula associated with row 'row' */ QString Column::formula(int row) const { return d->formula(row); } /** * \brief Return the intervals that have associated formulas * * This can be used to make a list of formulas with their intervals. * Here is some example code: * * \code * QStringList list; * QVector< Interval > intervals = my_column.formulaIntervals(); * foreach(Interval interval, intervals) * list << QString(interval.toString() + ": " + my_column.formula(interval.start())); * \endcode */ QVector< Interval > Column::formulaIntervals() const { return d->formulaIntervals(); } void Column::handleFormatChange() { DEBUG("Column::handleFormatChange() mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, columnMode())); if (columnMode() == AbstractColumn::DateTime) { auto* input_filter = static_cast(d->inputFilter()); auto* output_filter = static_cast(d->outputFilter()); - DEBUG("change format " << input_filter->format().toStdString() << " to " << output_filter->format().toStdString()); + DEBUG("change format " << STDSTRING(input_filter->format()) << " to " << STDSTRING(output_filter->format())); input_filter->setFormat(output_filter->format()); } emit aspectDescriptionChanged(this); // the icon for the type changed if (!m_suppressDataChangedSignal) emit formatChanged(this); // all cells must be repainted d->statisticsAvailable = false; d->hasValuesAvailable = false; d->propertiesAvailable = false; DEBUG("Column::handleFormatChange() DONE"); } /*! * calculates the minimal value in the column. * for \c count = 0, the minimum of all elements is returned. * for \c count > 0, the minimum of the first \p count elements is returned. * for \c count < 0, the minimum of the last \p count elements is returned. */ double Column::minimum(int count) const { double min = INFINITY; if (count == 0 && d->statisticsAvailable) min = const_cast(this)->statistics().minimum; else { int start, end; if (count == 0) { start = 0; end = rowCount(); } else if (count > 0) { start = 0; end = qMin(rowCount(), count); } else { start = qMax(rowCount() + count, 0); end = rowCount(); } return minimum(start, end); } return min; } /*! * \brief Column::minimum * Calculates the minimum value in the column between the \p startIndex and \p endIndex, endIndex is excluded. * If startIndex is greater than endIndex the indices are swapped * \p startIndex * \p endIndex */ double Column::minimum(int startIndex, int endIndex) const { double min = INFINITY; if (rowCount() == 0) return min; if (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) std::swap(startIndex, endIndex); startIndex = qMax(startIndex, 0); endIndex = qMax(endIndex, 0); startIndex = qMin(startIndex, rowCount() - 1); endIndex = qMin(endIndex, rowCount() - 1); int foundIndex = 0; ColumnMode mode = columnMode(); Properties property = properties(); if (property == Properties::No) { // skipping values is only in Properties::No needed, because // when there are invalid values the property must be Properties::No switch (mode) { case Numeric: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const double val = vec->at(row); if (std::isnan(val)) continue; if (val < min) min = val; } break; } case Integer: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const int val = vec->at(row); if (val < min) min = val; } break; } case BigInt: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const qint64 val = vec->at(row); if (val < min) min = val; } break; } case Text: break; case DateTime: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const qint64 val = vec->at(row).toMSecsSinceEpoch(); if (val < min) min = val; } break; } case Day: case Month: default: break; } return min; } // use the properties knowledge to determine maximum faster if (property == Properties::Constant || property == Properties::MonotonicIncreasing) foundIndex = startIndex; else if (property == Properties::MonotonicDecreasing) foundIndex = endIndex; switch (mode) { case Numeric: case Integer: case BigInt: return valueAt(foundIndex); case DateTime: case Month: case Day: return dateTimeAt(foundIndex).toMSecsSinceEpoch(); case Text: default: break; } return min; } /*! * calculates the maximal value in the column. * for \c count = 0, the maximum of all elements is returned. * for \c count > 0, the maximum of the first \p count elements is returned. * for \c count < 0, the maximum of the last \p count elements is returned. */ double Column::maximum(int count) const { double max = -INFINITY; if (count == 0 && d->statisticsAvailable) max = const_cast(this)->statistics().maximum; else { int start, end; if (count == 0) { start = 0; end = rowCount(); } else if (count > 0) { start = 0; end = qMin(rowCount(), count); } else { start = qMax(rowCount() + count, 0); end = rowCount(); } return maximum(start, end); } return max; } /*! * \brief Column::maximum * Calculates the maximum value in the column between the \p startIndex and \p endIndex. * If startIndex is greater than endIndex the indices are swapped * \p startIndex * \p endIndex */ double Column::maximum(int startIndex, int endIndex) const { double max = -INFINITY; if (rowCount() == 0) return max; if (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) std::swap(startIndex, endIndex); startIndex = qMax(startIndex, 0); endIndex = qMax(endIndex, 0); startIndex = qMin(startIndex, rowCount() - 1); endIndex = qMin(endIndex, rowCount() - 1); int foundIndex = 0; ColumnMode mode = columnMode(); Properties property = properties(); if (property == Properties::No) { switch (mode) { case Numeric: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const double val = vec->at(row); if (std::isnan(val)) continue; if (val > max) max = val; } break; } case Integer: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const int val = vec->at(row); if (val > max) max = val; } break; } case BigInt: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const qint64 val = vec->at(row); if (val > max) max = val; } break; } case Text: break; case DateTime: { auto* vec = static_cast*>(data()); for (int row = startIndex; row < endIndex; ++row) { if (!isValid(row) || isMasked(row)) continue; const qint64 val = vec->at(row).toMSecsSinceEpoch(); if (val > max) max = val; } break; } case Day: case Month: default: break; } return max; } // use the properties knowledge to determine maximum faster if (property == Properties::Constant || property == Properties::MonotonicDecreasing) foundIndex = startIndex; else if (property == Properties::MonotonicIncreasing) foundIndex = endIndex; switch (mode) { case Numeric: case Integer: case BigInt: return valueAt(foundIndex); case DateTime: case Month: case Day: return dateTimeAt(foundIndex).toMSecsSinceEpoch(); case Text: default: break; } return max; } /*! * calculates log2(x)+1 for an integer value. * Used in y(double x) to calculate the maximum steps * source: https://stackoverflow.com/questions/11376288/fast-computing-of-log2-for-64-bit-integers * source: https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup * @param value * @return returns calculated value */ // TODO: testing if it is faster than calculating log2. int Column::calculateMaxSteps (unsigned int value) { const std::array LogTable256 = { -1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 }; unsigned int r; // r will be lg(v) unsigned int t, tt; // temporaries if ((tt = value >> 16)) r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt]; else r = (t = value >> 8) ? 8 + LogTable256[t] : LogTable256[value]; return r+1; } /*! * Find index which corresponds to a @p x . In a vector of values * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value. * @param x * @return -1 if index not found, otherwise the index */ int Column::indexForValue(double x, QVector& column, Properties properties) { int rowCount = column.count(); if (rowCount == 0) return -1; double prevValue = 0; //qint64 prevValueDateTime = 0; if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) { // bisects the index every time, so it is possible to find the value in log_2(rowCount) steps bool increase = true; if(properties == AbstractColumn::Properties::MonotonicDecreasing) increase = false; int lowerIndex = 0; int higherIndex = rowCount-1; unsigned int maxSteps = calculateMaxSteps(static_cast(rowCount))+1; for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); double value = column[index]; if (higherIndex - lowerIndex < 2) { if (qAbs(column[lowerIndex] - x) < qAbs(column[higherIndex] - x)) index = lowerIndex; else index = higherIndex; return index; } if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; } } else if (properties == AbstractColumn::Properties::Constant) { return 0; } else { // AbstractColumn::Properties::No // naiv way int index = 0; prevValue = column[0]; for (int row = 0; row < rowCount; row++) { double value = column[row]; if (qAbs(value - x) <= qAbs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 prevValue = value; index = row; } } return index; } return -1; } /*! * Find index which corresponds to a @p x . In a vector of values * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value. * @param x * @return -1 if index not found, otherwise the index */ int Column::indexForValue(const double x, const QVector& points, Properties properties) { int rowCount = points.count(); if (rowCount == 0) return -1; double prevValue = 0; //qint64 prevValueDateTime = 0; if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) { // bisects the index every time, so it is possible to find the value in log_2(rowCount) steps bool increase = true; if(properties == AbstractColumn::Properties::MonotonicDecreasing) increase = false; int lowerIndex = 0; int higherIndex = rowCount - 1; unsigned int maxSteps = calculateMaxSteps(static_cast(rowCount))+1; for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); double value = points[index].x(); if (higherIndex - lowerIndex < 2) { if (qAbs(points[lowerIndex].x() - x) < qAbs(points[higherIndex].x() - x)) index = lowerIndex; else index = higherIndex; return index; } if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; } } else if (properties == AbstractColumn::Properties::Constant) { return 0; } else { // AbstractColumn::Properties::No // naiv way prevValue = points[0].x(); int index = 0; for (int row = 0; row < rowCount; row++) { double value = points[row].x(); if (qAbs(value - x) <= qAbs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 prevValue = value; index = row; } } return index; } return -1; } /*! * Find index which corresponds to a @p x . In a vector of values * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value. * @param x * @return -1 if index not found, otherwise the index */ int Column::indexForValue(double x, QVector& lines, Properties properties) { int rowCount = lines.count(); if (rowCount == 0) return -1; // use only p1 to find index double prevValue = 0; //qint64 prevValueDateTime = 0; if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) { // bisects the index every time, so it is possible to find the value in log_2(rowCount) steps bool increase = true; if(properties == AbstractColumn::Properties::MonotonicDecreasing) increase = false; int lowerIndex = 0; int higherIndex = rowCount-1; unsigned int maxSteps = calculateMaxSteps(static_cast(rowCount))+1; for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); double value = lines[index].p1().x(); if (higherIndex - lowerIndex < 2) { if (qAbs(lines[lowerIndex].p1().x() - x) < qAbs(lines[higherIndex].p1().x() - x)) index = lowerIndex; else index = higherIndex; return index; } if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; } } else if (properties == AbstractColumn::Properties::Constant) { return 0; } else { // AbstractColumn::Properties::No // naiv way int index = 0; prevValue = lines[0].p1().x(); for (int row = 0; row < rowCount; row++) { double value = lines[row].p1().x(); if (qAbs(value - x) <= qAbs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 prevValue = value; index = row; } } return index; } return -1; } int Column::indexForValue(double x) const { double prevValue = 0; qint64 prevValueDateTime = 0; AbstractColumn::ColumnMode mode = columnMode(); int property = properties(); if (property == AbstractColumn::Properties::MonotonicIncreasing || property == AbstractColumn::Properties::MonotonicDecreasing) { // bisects the index every time, so it is possible to find the value in log_2(rowCount) steps bool increase = (property != AbstractColumn::Properties::MonotonicDecreasing); int lowerIndex = 0; int higherIndex = rowCount() - 1; unsigned int maxSteps = calculateMaxSteps(static_cast(rowCount()))+1; if ((mode == AbstractColumn::ColumnMode::Numeric || mode == AbstractColumn::ColumnMode::Integer || mode == AbstractColumn::ColumnMode::BigInt)) { for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); double value = valueAt(index); if (higherIndex - lowerIndex < 2) { if (qAbs(valueAt(lowerIndex) - x) < qAbs(valueAt(higherIndex) - x)) index = lowerIndex; else index = higherIndex; return index; } if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; } } else if ((mode == AbstractColumn::ColumnMode::DateTime || mode == AbstractColumn::ColumnMode::Month || mode == AbstractColumn::ColumnMode::Day)) { qint64 xInt64 = static_cast(x); for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); qint64 value = dateTimeAt(index).toMSecsSinceEpoch(); if (higherIndex - lowerIndex < 2) { if (abs(dateTimeAt(lowerIndex).toMSecsSinceEpoch() - xInt64) < abs(dateTimeAt(higherIndex).toMSecsSinceEpoch() - xInt64)) index = lowerIndex; else index = higherIndex; return index; } if (value > xInt64 && increase) higherIndex = index; else if (value >= xInt64 && !increase) lowerIndex = index; else if (value <= xInt64 && increase) lowerIndex = index; else if (value < xInt64 && !increase) higherIndex = index; } } } else if (property == AbstractColumn::Properties::Constant) { if (rowCount() > 0) return 0; else return -1; } else { // naiv way int index = 0; if ((mode == AbstractColumn::ColumnMode::Numeric || mode == AbstractColumn::ColumnMode::Integer || mode == AbstractColumn::ColumnMode::BigInt)) { for (int row = 0; row < rowCount(); row++) { if (!isValid(row) || isMasked(row)) continue; if (row == 0) prevValue = valueAt(row); double value = valueAt(row); if (abs(value - x) <= abs(prevValue - x)) { // <= prevents also that row - 1 become < 0 if (row < rowCount() - 1) { prevValue = value; index = row; } } } return index; } else if ((mode == AbstractColumn::ColumnMode::DateTime || mode == AbstractColumn::ColumnMode::Month || mode == AbstractColumn::ColumnMode::Day)) { qint64 xInt64 = static_cast(x); for (int row = 0; row < rowCount(); row++) { if (!isValid(row) || isMasked(row)) continue; if (row == 0) prevValueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); qint64 value = dateTimeAt(row).toMSecsSinceEpoch(); if (abs(value - xInt64) <= abs(prevValueDateTime - xInt64)) { // "<=" prevents also that row - 1 become < 0 prevValueDateTime = value; index = row; } } return index; } } return -1; } /*! * Finds the minimal and maximal index which are between v1 and v2 * \brief Column::indicesForX * \param v1 * \param v2 * \param start * \param end * \return */ bool Column::indicesMinMax(double v1, double v2, int& start, int& end) const { start = -1; end = -1; if (rowCount() == 0) return false; // Assumption: v1 is always the smaller value if (v1 > v2) qSwap(v1, v2); Properties property = properties(); if (property == Properties::MonotonicIncreasing || property == Properties::MonotonicDecreasing) { start = indexForValue(v1); end = indexForValue(v2); switch (columnMode()) { case Integer: case BigInt: case Numeric: { if (start > 0 && valueAt(start - 1) <= v2 && valueAt(start - 1) >= v1) start--; if (end < rowCount() - 1 && valueAt(end + 1) <= v2 && valueAt(end + 1) >= v1) end++; break; } case DateTime: case Month: case Day: { qint64 v1int64 = v1; qint64 v2int64 = v2; qint64 value; if (start > 0) { value = dateTimeAt(start -1).toMSecsSinceEpoch(); if (value <= v2int64 && value >= v1int64) start--; } if (end > rowCount() - 1) { value = dateTimeAt(end + 1).toMSecsSinceEpoch(); if (value <= v2int64 && value >= v1int64) end++; } break; } case Text: return false; } return true; } else if (property == Properties::Constant) { start = 0; end = rowCount() - 1; return true; } // property == Properties::No switch (columnMode()) { case Integer: case BigInt: case Numeric: { double value; for (int i = 0; i < rowCount(); i++) { if (!isValid(i) || isMasked(i)) continue; value = valueAt(i); if (value <= v2 && value >= v1) { end = i; if (start < 0) start = i; } } break; } case DateTime: case Month: case Day: { qint64 value; qint64 v2int64 = v2; qint64 v1int64 = v2; for (int i = 0; i < rowCount(); i++) { if (!isValid(i) || isMasked(i)) continue; value = dateTimeAt(i).toMSecsSinceEpoch(); if (value <= v2int64 && value >= v1int64) { end = i; if (start < 0) start = i; } } break; } case Text: return false; } return true; } diff --git a/src/backend/core/column/ColumnPrivate.cpp b/src/backend/core/column/ColumnPrivate.cpp index 8166551f6..9454054cf 100644 --- a/src/backend/core/column/ColumnPrivate.cpp +++ b/src/backend/core/column/ColumnPrivate.cpp @@ -1,1761 +1,1761 @@ /*************************************************************************** File : ColumnPrivate.cpp Project : AbstractColumn Description : Private data class of Column -------------------------------------------------------------------- Copyright : (C) 2007-2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2020 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ColumnPrivate.h" #include "ColumnStringIO.h" #include "Column.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/datatypes/filter.h" #include "backend/gsl/ExpressionParser.h" ColumnPrivate::ColumnPrivate(Column* owner, AbstractColumn::ColumnMode mode) : m_column_mode(mode), m_owner(owner) { Q_ASSERT(owner != nullptr); switch (mode) { case AbstractColumn::Numeric: m_input_filter = new String2DoubleFilter(); m_output_filter = new Double2StringFilter('g'); m_data = new QVector(); break; case AbstractColumn::Integer: m_input_filter = new String2IntegerFilter(); m_output_filter = new Integer2StringFilter(); m_data = new QVector(); break; case AbstractColumn::BigInt: m_input_filter = new String2BigIntFilter(); m_output_filter = new BigInt2StringFilter(); m_data = new QVector(); break; case AbstractColumn::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); m_data = new QStringList(); break; case AbstractColumn::DateTime: m_input_filter = new String2DateTimeFilter(); m_output_filter = new DateTime2StringFilter(); m_data = new QVector(); break; case AbstractColumn::Month: m_input_filter = new String2MonthFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("MMMM"); m_data = new QVector(); break; case AbstractColumn::Day: m_input_filter = new String2DayOfWeekFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("dddd"); m_data = new QVector(); break; } connect(m_output_filter, &AbstractSimpleFilter::formatChanged, m_owner, &Column::handleFormatChange); //m_input_filter->setName("InputFilter"); //m_output_filter->setName("OutputFilter"); } /** * \brief Special ctor (to be called from Column only!) */ ColumnPrivate::ColumnPrivate(Column* owner, AbstractColumn::ColumnMode mode, void* data) : m_column_mode(mode), m_data(data), m_owner(owner) { switch (mode) { case AbstractColumn::Numeric: m_input_filter = new String2DoubleFilter(); m_output_filter = new Double2StringFilter(); connect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: m_input_filter = new String2IntegerFilter(); m_output_filter = new Integer2StringFilter(); connect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::BigInt: m_input_filter = new String2BigIntFilter(); m_output_filter = new BigInt2StringFilter(); connect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); break; case AbstractColumn::DateTime: m_input_filter = new String2DateTimeFilter(); m_output_filter = new DateTime2StringFilter(); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Month: m_input_filter = new String2MonthFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("MMMM"); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Day: m_input_filter = new String2DayOfWeekFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("dddd"); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } //m_input_filter->setName("InputFilter"); //m_output_filter->setName("OutputFilter"); } ColumnPrivate::~ColumnPrivate() { if (!m_data) return; switch (m_column_mode) { case AbstractColumn::Numeric: delete static_cast*>(m_data); break; case AbstractColumn::Integer: delete static_cast*>(m_data); break; case AbstractColumn::BigInt: delete static_cast*>(m_data); break; case AbstractColumn::Text: delete static_cast*>(m_data); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: delete static_cast*>(m_data); break; } } AbstractColumn::ColumnMode ColumnPrivate::columnMode() const { return m_column_mode; } /** * \brief Set the column mode * * This sets the column mode and, if * necessary, converts it to another datatype. * Remark: setting the mode back to undefined (the * initial value) is not supported. */ void ColumnPrivate::setColumnMode(AbstractColumn::ColumnMode mode) { DEBUG("ColumnPrivate::setColumnMode() " << ENUM_TO_STRING(AbstractColumn, ColumnMode, m_column_mode) << " -> " << ENUM_TO_STRING(AbstractColumn, ColumnMode, mode)) if (mode == m_column_mode) return; void* old_data = m_data; // remark: the deletion of the old data will be done in the dtor of a command AbstractSimpleFilter* filter = nullptr, *new_in_filter = nullptr, *new_out_filter = nullptr; bool filter_is_temporary = false; // it can also become outputFilter(), which we may not delete here Column* temp_col = nullptr; emit m_owner->modeAboutToChange(m_owner); // determine the conversion filter and allocate the new data vector switch (m_column_mode) { // old mode case AbstractColumn::Numeric: { disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::Numeric: break; case AbstractColumn::Integer: filter = new Double2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data))); m_data = new QVector(); break; case AbstractColumn::BigInt: filter = new Double2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data))); m_data = new QVector(); break; case AbstractColumn::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; case AbstractColumn::DateTime: filter = new Double2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; case AbstractColumn::Month: filter = new Double2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; case AbstractColumn::Day: filter = new Double2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::Integer: { disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::Integer: break; case AbstractColumn::BigInt: filter = new Integer2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Numeric: filter = new Integer2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::DateTime: filter = new Integer2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Month: filter = new Integer2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Day: filter = new Integer2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::BigInt: { disconnect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::BigInt: break; case AbstractColumn::Integer: filter = new BigInt2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Numeric: filter = new BigInt2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::DateTime: filter = new BigInt2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Month: filter = new BigInt2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Day: filter = new BigInt2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::Text: { switch (mode) { case AbstractColumn::Text: break; case AbstractColumn::Numeric: filter = new String2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Integer: filter = new String2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::BigInt: filter = new String2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::DateTime: filter = new String2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Month: filter = new String2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Day: filter = new String2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: { disconnect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: break; case AbstractColumn::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QStringList(); break; case AbstractColumn::Numeric: if (m_column_mode == AbstractColumn::Month) filter = new Month2DoubleFilter(); else if (m_column_mode == AbstractColumn::Day) filter = new DayOfWeek2DoubleFilter(); else filter = new DateTime2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Integer: if (m_column_mode == AbstractColumn::Month) filter = new Month2IntegerFilter(); else if (m_column_mode == AbstractColumn::Day) filter = new DayOfWeek2IntegerFilter(); else filter = new DateTime2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::BigInt: if (m_column_mode == AbstractColumn::Month) filter = new Month2BigIntFilter(); else if (m_column_mode == AbstractColumn::Day) filter = new DayOfWeek2BigIntFilter(); else filter = new DateTime2BigIntFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } } // determine the new input and output filters switch (mode) { // new mode case AbstractColumn::Numeric: new_in_filter = new String2DoubleFilter(); new_out_filter = new Double2StringFilter(); connect(static_cast(new_out_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: new_in_filter = new String2IntegerFilter(); new_out_filter = new Integer2StringFilter(); connect(static_cast(new_out_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::BigInt: new_in_filter = new String2BigIntFilter(); new_out_filter = new BigInt2StringFilter(); connect(static_cast(new_out_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: new_in_filter = new SimpleCopyThroughFilter(); new_out_filter = new SimpleCopyThroughFilter(); break; case AbstractColumn::DateTime: new_in_filter = new String2DateTimeFilter(); new_out_filter = new DateTime2StringFilter(); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Month: new_in_filter = new String2MonthFilter(); new_out_filter = new DateTime2StringFilter(); static_cast(new_out_filter)->setFormat("MMMM"); - DEBUG(" Month out_filter format: " << static_cast(new_out_filter)->format().toStdString()); + DEBUG(" Month out_filter format: " << STDSTRING(static_cast(new_out_filter)->format())); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Day: new_in_filter = new String2DayOfWeekFilter(); new_out_filter = new DateTime2StringFilter(); static_cast(new_out_filter)->setFormat("dddd"); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } // switch(mode) m_column_mode = mode; //new_in_filter->setName("InputFilter"); //new_out_filter->setName("OutputFilter"); m_input_filter = new_in_filter; m_output_filter = new_out_filter; m_input_filter->input(0, m_owner->m_string_io); m_output_filter->input(0, m_owner); m_input_filter->setHidden(true); m_output_filter->setHidden(true); if (temp_col) { // if temp_col == 0, only the input/output filters need to be changed // copy the filtered, i.e. converted, column (mode is orig mode) DEBUG(" temp_col column mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, temp_col->columnMode())); filter->input(0, temp_col); DEBUG(" filter->output size = " << filter->output(0)->rowCount()); copy(filter->output(0)); delete temp_col; } if (filter_is_temporary) delete filter; emit m_owner->modeChanged(m_owner); DEBUG("ColumnPrivate::setColumnMode() DONE"); } /** * \brief Replace all mode related members * * Replace column mode, data type, data pointer and filters directly */ void ColumnPrivate::replaceModeData(AbstractColumn::ColumnMode mode, void* data, AbstractSimpleFilter* in_filter, AbstractSimpleFilter* out_filter) { DEBUG("ColumnPrivate::replaceModeData()"); emit m_owner->modeAboutToChange(m_owner); // disconnect formatChanged() switch (m_column_mode) { case AbstractColumn::Numeric: disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::BigInt: disconnect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: disconnect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } m_column_mode = mode; m_data = data; //in_filter->setName("InputFilter"); //out_filter->setName("OutputFilter"); m_input_filter = in_filter; m_output_filter = out_filter; m_input_filter->input(0, m_owner->m_string_io); m_output_filter->input(0, m_owner); // connect formatChanged() switch (m_column_mode) { case AbstractColumn::Numeric: connect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: connect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::BigInt: connect(static_cast(m_output_filter), &BigInt2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } emit m_owner->modeChanged(m_owner); } /** * \brief Replace data pointer */ void ColumnPrivate::replaceData(void* data) { DEBUG("ColumnPrivate::replaceData()"); emit m_owner->dataAboutToChange(m_owner); m_data = data; if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool ColumnPrivate::copy(const AbstractColumn* other) { DEBUG("ColumnPrivate::copy(other)"); if (other->columnMode() != columnMode()) return false; DEBUG(" mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, columnMode())); int num_rows = other->rowCount(); DEBUG(" rows " << num_rows); emit m_owner->dataAboutToChange(m_owner); resizeTo(num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } case AbstractColumn::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->bigIntAt(i); break; } case AbstractColumn::Text: { auto* vec = static_cast*>(m_data); for (int i = 0; i < num_rows; ++i) vec->replace(i, other->textAt(i)); break; } case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: { auto* vec = static_cast*>(m_data); for (int i = 0; i < num_rows; ++i) vec->replace(i, other->dateTimeAt(i)); break; } } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool ColumnPrivate::copy(const AbstractColumn* source, int source_start, int dest_start, int num_rows) { DEBUG("ColumnPrivate::copy()"); if (source->columnMode() != m_column_mode) return false; if (num_rows == 0) return true; emit m_owner->dataAboutToChange(m_owner); if (dest_start + num_rows > rowCount()) resizeTo(dest_start + num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->valueAt(source_start + i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->integerAt(source_start + i); break; } case AbstractColumn::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->bigIntAt(source_start + i); break; } case AbstractColumn::Text: for (int i = 0; i < num_rows; i++) static_cast*>(m_data)->replace(dest_start+i, source->textAt(source_start + i)); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < num_rows; i++) static_cast*>(m_data)->replace(dest_start+i, source->dateTimeAt(source_start + i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool ColumnPrivate::copy(const ColumnPrivate* other) { if (other->columnMode() != m_column_mode) return false; int num_rows = other->rowCount(); emit m_owner->dataAboutToChange(m_owner); resizeTo(num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } case AbstractColumn::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->bigIntAt(i); break; } case AbstractColumn::Text: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->textAt(i)); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->dateTimeAt(i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param source pointer to the column to copy * \param source_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool ColumnPrivate::copy(const ColumnPrivate* source, int source_start, int dest_start, int num_rows) { if (source->columnMode() != m_column_mode) return false; if (num_rows == 0) return true; emit m_owner->dataAboutToChange(m_owner); if (dest_start + num_rows > rowCount()) resizeTo(dest_start + num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->valueAt(source_start + i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->integerAt(source_start + i); break; } case AbstractColumn::BigInt: { qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->bigIntAt(source_start + i); break; } case AbstractColumn::Text: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(dest_start+i, source->textAt(source_start + i)); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i *>(m_data)->replace(dest_start+i, source->dateTimeAt(source_start + i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Return the data vector size * * This returns the number of rows that actually contain data. * Rows beyond this can be masked etc. but should be ignored by filters, * plots etc. */ int ColumnPrivate::rowCount() const { switch (m_column_mode) { case AbstractColumn::Numeric: return static_cast*>(m_data)->size(); case AbstractColumn::Integer: return static_cast*>(m_data)->size(); case AbstractColumn::BigInt: return static_cast*>(m_data)->size(); case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: return static_cast*>(m_data)->size(); case AbstractColumn::Text: return static_cast*>(m_data)->size(); } return 0; } /** * \brief Resize the vector to the specified number of rows * * Since selecting and masking rows higher than the * real internal number of rows is supported, this * does not change the interval attributes. Also * no signal is emitted. If the new rows are filled * with values AbstractColumn::dataChanged() * must be emitted. */ void ColumnPrivate::resizeTo(int new_size) { int old_size = rowCount(); if (new_size == old_size) return; DEBUG("ColumnPrivate::resizeTo() " << old_size << " -> " << new_size); switch (m_column_mode) { case AbstractColumn::Numeric: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, NAN); break; } case AbstractColumn::Integer: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, 0); break; } case AbstractColumn::BigInt: { auto* numeric_data = static_cast*>(m_data); numeric_data->insert(numeric_data->end(), new_size - old_size, 0); break; } case AbstractColumn::Text: { int new_rows = new_size - old_size; if (new_rows > 0) { for (int i = 0; i < new_rows; ++i) static_cast*>(m_data)->append(QString()); } else { for (int i = 0; i < -new_rows; ++i) static_cast*>(m_data)->removeLast(); } break; } case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: { int new_rows = new_size - old_size; if (new_rows > 0) { for (int i = 0; i < new_rows; ++i) static_cast*>(m_data)->append(QDateTime()); } else { for (int i = 0; i < -new_rows; ++i) static_cast*>(m_data)->removeLast(); } break; } } } /** * \brief Insert some empty (or initialized with zero) rows */ void ColumnPrivate::insertRows(int before, int count) { if (count == 0) return; m_formulas.insertRows(before, count); if (before <= rowCount()) { switch (m_column_mode) { case AbstractColumn::Numeric: static_cast*>(m_data)->insert(before, count, NAN); break; case AbstractColumn::Integer: static_cast*>(m_data)->insert(before, count, 0); break; case AbstractColumn::BigInt: static_cast*>(m_data)->insert(before, count, 0); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < count; ++i) static_cast*>(m_data)->insert(before, QDateTime()); break; case AbstractColumn::Text: for (int i = 0; i < count; ++i) static_cast*>(m_data)->insert(before, QString()); break; } } } /** * \brief Remove 'count' rows starting from row 'first' */ void ColumnPrivate::removeRows(int first, int count) { if (count == 0) return; m_formulas.removeRows(first, count); if (first < rowCount()) { int corrected_count = count; if (first + count > rowCount()) corrected_count = rowCount() - first; switch (m_column_mode) { case AbstractColumn::Numeric: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::Integer: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::BigInt: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < corrected_count; ++i) static_cast*>(m_data)->removeAt(first); break; case AbstractColumn::Text: for (int i = 0; i < corrected_count; ++i) static_cast*>(m_data)->removeAt(first); break; } } } //! Return the column name QString ColumnPrivate::name() const { return m_owner->name(); } /** * \brief Return the column plot designation */ AbstractColumn::PlotDesignation ColumnPrivate::plotDesignation() const { return m_plot_designation; } /** * \brief Set the column plot designation */ void ColumnPrivate::setPlotDesignation(AbstractColumn::PlotDesignation pd) { emit m_owner->plotDesignationAboutToChange(m_owner); m_plot_designation = pd; emit m_owner->plotDesignationChanged(m_owner); } /** * \brief Get width */ int ColumnPrivate::width() const { return m_width; } /** * \brief Set width */ void ColumnPrivate::setWidth(int value) { m_width = value; } /** * \brief Return the data pointer */ void* ColumnPrivate::data() const { return m_data; } /** * \brief Return the input filter (for string -> data type conversion) */ AbstractSimpleFilter *ColumnPrivate::inputFilter() const { return m_input_filter; } /** * \brief Return the output filter (for data type -> string conversion) */ AbstractSimpleFilter *ColumnPrivate::outputFilter() const { return m_output_filter; } //////////////////////////////////////////////////////////////////////////////// //! \name Formula related functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the formula last used to generate data for the column */ QString ColumnPrivate::formula() const { return m_formula; } bool ColumnPrivate::formulaAutoUpdate() const { return m_formulaAutoUpdate; } /** * \brief Sets the formula used to generate column values */ void ColumnPrivate::setFormula(const QString& formula, const QStringList& variableNames, const QVector& variableColumns, bool autoUpdate) { m_formula = formula; m_formulaVariableNames = variableNames; m_formulaVariableColumns = variableColumns; m_formulaAutoUpdate = autoUpdate; for (auto connection: m_connectionsUpdateFormula) disconnect(connection); m_formulaVariableColumnPaths.clear(); for (auto column : variableColumns) { m_formulaVariableColumnPaths << column->path(); if (autoUpdate) connectFormulaColumn(column); } } /*! * called after the import of the project was done and all columns were loaded in \sa Project::load() * to establish the required slot-signal connections for the formula update */ void ColumnPrivate::finalizeLoad() { if (m_formulaAutoUpdate) { for (auto column : m_formulaVariableColumns) connectFormulaColumn(column); } } /*! * \brief ColumnPrivate::connectFormulaColumn * This function is used to connect the columns to the needed slots for updating formulas * \param column */ void ColumnPrivate::connectFormulaColumn(const AbstractColumn* column) { if (!column) return; m_connectionsUpdateFormula << connect(column, &Column::dataChanged, m_owner, &Column::updateFormula); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &ColumnPrivate::formulaVariableColumnRemoved); connect(column, &AbstractColumn::reset, this, &ColumnPrivate::formulaVariableColumnRemoved); connect(column->parentAspect(), &AbstractAspect::aspectAdded, this, &ColumnPrivate::formulaVariableColumnAdded); } /*! * helper function used in \c Column::load() to set parameters read from the xml file. * \param variableColumnPaths is used to restore the pointers to columns from pathes * after the project was loaded in Project::load(). */ void ColumnPrivate::setFormula(const QString& formula, const QStringList& variableNames, const QStringList& variableColumnPaths, bool autoUpdate) { m_formula = formula; m_formulaVariableNames = variableNames; m_formulaVariableColumnPaths = variableColumnPaths; m_formulaVariableColumns.resize(variableColumnPaths.length()); m_formulaAutoUpdate = autoUpdate; } const QStringList& ColumnPrivate::formulaVariableNames() const { return m_formulaVariableNames; } const QVector& ColumnPrivate::formulaVariableColumns() const { return m_formulaVariableColumns; } const QStringList& ColumnPrivate::formulaVariableColumnPaths() const { return m_formulaVariableColumnPaths; } void ColumnPrivate::setformulVariableColumnsPath(int index, const QString& path) { m_formulaVariableColumnPaths[index] = path; } void ColumnPrivate::setformulVariableColumn(int index, Column* column) { if (m_formulaVariableColumns[index]) // if there exists already a valid column, disconnect it first disconnect(m_formulaVariableColumns[index], nullptr, this, nullptr); m_formulaVariableColumns[index] = column; connectFormulaColumn(column); } /*! * \sa FunctionValuesDialog::generate() */ void ColumnPrivate::updateFormula() { //determine variable names and the data vectors of the specified columns QVector*> xVectors; QVector*> xNewVectors; int maxRowCount = 0; bool valid = true; for (auto column : m_formulaVariableColumns) { if (!column) { valid = false; break; } if (column->columnMode() == AbstractColumn::Integer || column->columnMode() == AbstractColumn::BigInt) { //convert integers to doubles first auto* xVector = new QVector(column->rowCount()); for (int i = 0; irowCount(); ++i) xVector->operator[](i) = column->valueAt(i); xNewVectors << xVector; xVectors << xVector; } else xVectors << static_cast* >(column->data()); if (column->rowCount() > maxRowCount) maxRowCount = column->rowCount(); } if (valid) { //resize the spreadsheet if one of the data vectors from //other spreadsheet(s) has more elements than the parent spreadsheet Spreadsheet* spreadsheet = static_cast(m_owner->parentAspect()); if (spreadsheet->rowCount() < maxRowCount) spreadsheet->setRowCount(maxRowCount); //create new vector for storing the calculated values //the vectors with the variable data can be smaller then the result vector. So, not all values in the result vector might get initialized. //->"clean" the result vector first QVector new_data(rowCount(), NAN); //evaluate the expression for f(x_1, x_2, ...) and write the calculated values into a new vector. ExpressionParser* parser = ExpressionParser::getInstance(); parser->evaluateCartesian(m_formula, m_formulaVariableNames, xVectors, &new_data); replaceValues(0, new_data); // initialize remaining rows with NAN int remainingRows = rowCount() - maxRowCount; if (remainingRows > 0) { QVector emptyRows(remainingRows, NAN); replaceValues(maxRowCount, emptyRows); } } else { QVector new_data(rowCount(), NAN); replaceValues(0, new_data); } //delete help vectors created for the conversion from int to double for (auto* vector : xNewVectors) delete vector; } void ColumnPrivate::formulaVariableColumnRemoved(const AbstractAspect* aspect) { const Column* column = dynamic_cast(aspect); disconnect(column, nullptr, this, nullptr); //TODO: why is const_cast required here?!? int index = m_formulaVariableColumns.indexOf(const_cast(column)); if (index != -1) { m_formulaVariableColumns[index] = nullptr; updateFormula(); } } void ColumnPrivate::formulaVariableColumnAdded(const AbstractAspect* aspect) { int index = m_formulaVariableColumnPaths.indexOf(aspect->path()); if (index != -1) { const Column* column = dynamic_cast(aspect); m_formulaVariableColumns[index] = const_cast(column); updateFormula(); } } /** * \brief Return the formula associated with row 'row' */ QString ColumnPrivate::formula(int row) const { return m_formulas.value(row); } /** * \brief Return the intervals that have associated formulas * * This can be used to make a list of formulas with their intervals. * Here is some example code: * * \code * QStringList list; * QVector< Interval > intervals = my_column.formulaIntervals(); * foreach(Interval interval, intervals) * list << QString(interval.toString() + ": " + my_column.formula(interval.start())); * \endcode */ QVector< Interval > ColumnPrivate::formulaIntervals() const { return m_formulas.intervals(); } /** * \brief Set a formula string for an interval of rows */ void ColumnPrivate::setFormula(const Interval& i, const QString& formula) { m_formulas.setValue(i, formula); } /** * \brief Overloaded function for convenience */ void ColumnPrivate::setFormula(int row, const QString& formula) { setFormula(Interval(row,row), formula); } /** * \brief Clear all formulas */ void ColumnPrivate::clearFormulas() { m_formulas.clear(); } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //! \name type specific functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the content of row 'row'. * * Use this only when columnMode() is Text */ QString ColumnPrivate::textAt(int row) const { if (m_column_mode != AbstractColumn::Text) return QString(); return static_cast*>(m_data)->value(row); } /** * \brief Return the date part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDate ColumnPrivate::dateAt(int row) const { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return QDate{}; return dateTimeAt(row).date(); } /** * \brief Return the time part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QTime ColumnPrivate::timeAt(int row) const { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return QTime{}; return dateTimeAt(row).time(); } /** * \brief Return the QDateTime in row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDateTime ColumnPrivate::dateTimeAt(int row) const { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return QDateTime(); return static_cast*>(m_data)->value(row); } /** * \brief Return the double value in row 'row' for columns with type Numeric, Integer or BigInt. * This function has to be used everywhere where the exact type (double, int or qint64) is not relevant for numerical calculations. * For cases where the integer value is needed without any implicit conversions, \sa integerAt() has to be used. */ double ColumnPrivate::valueAt(int row) const { if (m_column_mode == AbstractColumn::Numeric) return static_cast*>(m_data)->value(row, NAN); else if (m_column_mode == AbstractColumn::Integer) return static_cast*>(m_data)->value(row, 0); else if (m_column_mode == AbstractColumn::BigInt) return static_cast*>(m_data)->value(row, 0); else return NAN; } /** * \brief Return the int value in row 'row' */ int ColumnPrivate::integerAt(int row) const { if (m_column_mode != AbstractColumn::Integer) return 0; return static_cast*>(m_data)->value(row, 0); } /** * \brief Return the bigint value in row 'row' */ qint64 ColumnPrivate::bigIntAt(int row) const { if (m_column_mode != AbstractColumn::BigInt) return 0; return static_cast*>(m_data)->value(row, 0); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Text */ void ColumnPrivate::setTextAt(int row, const QString& new_value) { if (m_column_mode != AbstractColumn::Text) return; emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row + 1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Text */ void ColumnPrivate::replaceTexts(int first, const QVector& new_values) { if (m_column_mode != AbstractColumn::Text) return; emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(first+i, new_values.at(i)); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setDateAt(int row, QDate new_value) { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return; setDateTimeAt(row, QDateTime(new_value, timeAt(row))); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setTimeAt(int row, QTime new_value) { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return; setDateTimeAt(row, QDateTime(dateAt(row), new_value)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setDateTimeAt(int row, const QDateTime& new_value) { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return; emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast< QVector* >(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::replaceDateTimes(int first, const QVector& new_values) { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return; emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(first+i, new_values.at(i)); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Numeric */ void ColumnPrivate::setValueAt(int row, double new_value) { // DEBUG("ColumnPrivate::setValueAt()"); if (m_column_mode != AbstractColumn::Numeric) return; emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Numeric */ void ColumnPrivate::replaceValues(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceValues()"); if (m_column_mode != AbstractColumn::Numeric) return; emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Integer */ void ColumnPrivate::setIntegerAt(int row, int new_value) { DEBUG("ColumnPrivate::setIntegerAt()"); if (m_column_mode != AbstractColumn::Integer) return; emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Integer */ void ColumnPrivate::replaceInteger(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceInteger()"); if (m_column_mode != AbstractColumn::Integer) return; emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is BigInt */ void ColumnPrivate::setBigIntAt(int row, qint64 new_value) { DEBUG("ColumnPrivate::setBigIntAt()"); if (m_column_mode != AbstractColumn::BigInt) return; emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is BigInt */ void ColumnPrivate::replaceBigInt(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceBigInt()"); if (m_column_mode != AbstractColumn::BigInt) return; emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); qint64* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /*! * Updates the properties. Will be called, when data in the column changed. * The properties will be used to speed up some algorithms. * See where variable properties will be used. */ void ColumnPrivate::updateProperties() { // TODO: for double Properties::Constant will never be used. Use an epsilon (difference smaller than epsilon is zero) if (rowCount() == 0) { properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } double prevValue = NAN; int prevValueInt = 0; qint64 prevValueBigInt = 0; qint64 prevValueDatetime = 0; if (m_column_mode == AbstractColumn::Integer) prevValueInt = integerAt(0); else if (m_column_mode == AbstractColumn::BigInt) prevValueBigInt = bigIntAt(0); else if (m_column_mode == AbstractColumn::Numeric) prevValue = valueAt(0); else if (m_column_mode == AbstractColumn::DateTime || m_column_mode == AbstractColumn::Month || m_column_mode == AbstractColumn::Day) prevValueDatetime = dateTimeAt(0).toMSecsSinceEpoch(); else { properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } int monotonic_decreasing = -1; int monotonic_increasing = -1; double value; int valueInt; qint64 valueBigInt; qint64 valueDateTime; for (int row = 1; row < rowCount(); row++) { if (!m_owner->isValid(row) || m_owner->isMasked(row)) { // if there is one invalid or masked value, the property is No, because // otherwise it's difficult to find the correct index in indexForValue(). // You don't know if you should increase the index or decrease it when // you hit an invalid value properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } if (m_column_mode == AbstractColumn::Integer) { valueInt = integerAt(row); if (valueInt > prevValueInt) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (valueInt < prevValueInt) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValueInt = valueInt; } else if (m_column_mode == AbstractColumn::BigInt) { valueBigInt = bigIntAt(row); if (valueBigInt > prevValueBigInt) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (valueBigInt < prevValueBigInt) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValueBigInt = valueBigInt; } else if (m_column_mode == AbstractColumn::Numeric) { value = valueAt(row); if (std::isnan(value)) { monotonic_increasing = 0; monotonic_decreasing = 0; break; } if (value > prevValue) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (value < prevValue) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValue = value; } else if (m_column_mode == AbstractColumn::DateTime || m_column_mode == AbstractColumn::Month || m_column_mode == AbstractColumn::Day) { valueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); if (valueDateTime > prevValueDatetime) { monotonic_decreasing = 0; if (monotonic_increasing < 0) monotonic_increasing = 1; else if (monotonic_increasing == 0) break; // when nor increasing, nor decreasing, break } else if (valueDateTime < prevValueDatetime) { monotonic_increasing = 0; if (monotonic_decreasing < 0) monotonic_decreasing = 1; else if (monotonic_decreasing == 0) break; // when nor increasing, nor decreasing, break } else { if (monotonic_increasing < 0 && monotonic_decreasing < 0) { monotonic_decreasing = 1; monotonic_increasing = 1; } } prevValueDatetime = valueDateTime; } } properties = AbstractColumn::Properties::No; if (monotonic_increasing > 0 && monotonic_decreasing > 0) properties = AbstractColumn::Properties::Constant; else if (monotonic_decreasing > 0) properties = AbstractColumn::Properties::MonotonicDecreasing; else if (monotonic_increasing > 0) properties = AbstractColumn::Properties::MonotonicIncreasing; propertiesAvailable = true; } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the interval attribute representing the formula strings */ IntervalAttribute ColumnPrivate::formulaAttribute() const { return m_formulas; } /** * \brief Replace the interval attribute for the formula strings */ void ColumnPrivate::replaceFormulas(const IntervalAttribute& formulas) { m_formulas = formulas; } diff --git a/src/backend/core/datatypes/String2BigIntFilter.h b/src/backend/core/datatypes/String2BigIntFilter.h index b2e3bbc24..78a4d87c6 100644 --- a/src/backend/core/datatypes/String2BigIntFilter.h +++ b/src/backend/core/datatypes/String2BigIntFilter.h @@ -1,76 +1,76 @@ /*************************************************************************** File : String2BigIntFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2020 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Locale-aware conversion filter QString -> bigint. ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef STRING2BIGINT_FILTER_H #define STRING2BIGINT_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Locale-aware conversion filter QString -> int. class String2BigIntFilter : public AbstractSimpleFilter { Q_OBJECT public: String2BigIntFilter() : m_use_default_locale(true) {} void setNumericLocale(const QLocale& locale) { m_numeric_locale = locale; m_use_default_locale = false; } void setNumericLocaleToDefault() { m_use_default_locale = true; } qint64 bigIntAt(int row) const override { //DEBUG("String2BigInt::bigIntAt()"); if (!m_inputs.value(0)) return 0; qint64 result; bool valid; QString textValue = m_inputs.value(0)->textAt(row); - //DEBUG(" textValue = " << textValue.toStdString()); + //DEBUG(" textValue = " << STDSTRING(textValue)); if (m_use_default_locale) // we need a new QLocale instance here in case the default changed since the last call result = QLocale().toLongLong(textValue, &valid); else result = m_numeric_locale.toLongLong(textValue, &valid); //DEBUG(" result = " << result << " valid = " << valid); if (valid) return result; return 0; } //! Return the data type of the column AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::BigInt; } protected: //! Using typed ports: only string inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { return source->columnMode() == AbstractColumn::Text; } private: QLocale m_numeric_locale; bool m_use_default_locale; }; #endif // ifndef STRING2BIGINT_FILTER_H diff --git a/src/backend/core/datatypes/String2IntegerFilter.h b/src/backend/core/datatypes/String2IntegerFilter.h index 76583055a..07c4d7a90 100644 --- a/src/backend/core/datatypes/String2IntegerFilter.h +++ b/src/backend/core/datatypes/String2IntegerFilter.h @@ -1,76 +1,76 @@ /*************************************************************************** File : String2IntegerFilter.h Project : AbstractColumn -------------------------------------------------------------------- Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : Locale-aware conversion filter QString -> int. ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef STRING2INTEGER_FILTER_H #define STRING2INTEGER_FILTER_H #include "../AbstractSimpleFilter.h" #include //! Locale-aware conversion filter QString -> int. class String2IntegerFilter : public AbstractSimpleFilter { Q_OBJECT public: String2IntegerFilter() : m_use_default_locale(true) {} void setNumericLocale(const QLocale& locale) { m_numeric_locale = locale; m_use_default_locale = false; } void setNumericLocaleToDefault() { m_use_default_locale = true; } int integerAt(int row) const override { //DEBUG("String2Integer::integerAt()"); if (!m_inputs.value(0)) return 0; int result; bool valid; QString textValue = m_inputs.value(0)->textAt(row); - //DEBUG(" textValue = " << textValue.toStdString()); + //DEBUG(" textValue = " << STDSTRING(textValue)); if (m_use_default_locale) // we need a new QLocale instance here in case the default changed since the last call result = QLocale().toInt(textValue, &valid); else result = m_numeric_locale.toInt(textValue, &valid); //DEBUG(" result = " << result << " valid = " << valid); if (valid) return result; return 0; } //! Return the data type of the column AbstractColumn::ColumnMode columnMode() const override { return AbstractColumn::Integer; } protected: //! Using typed ports: only string inputs are accepted. bool inputAcceptable(int, const AbstractColumn *source) override { return source->columnMode() == AbstractColumn::Text; } private: QLocale m_numeric_locale; bool m_use_default_locale; }; #endif // ifndef STRING2INTEGER_FILTER_H diff --git a/src/backend/datasources/LiveDataSource.cpp b/src/backend/datasources/LiveDataSource.cpp index 04d7c311e..a05e8adea 100644 --- a/src/backend/datasources/LiveDataSource.cpp +++ b/src/backend/datasources/LiveDataSource.cpp @@ -1,943 +1,943 @@ /*************************************************************************** File : LiveDataSource.cpp Project : LabPlot Description : Represents live data source -------------------------------------------------------------------- Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/ROOTFilter.h" #include "backend/core/Project.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class LiveDataSource \brief Represents data stored in a file. Reading and writing is done with the help of appropriate I/O-filters. \ingroup datasources */ LiveDataSource::LiveDataSource(const QString& name, bool loading) : Spreadsheet(name, loading, AspectType::LiveDataSource), m_updateTimer(new QTimer(this)), m_watchTimer(new QTimer(this)) { initActions(); connect(m_updateTimer, &QTimer::timeout, this, &LiveDataSource::read); connect(m_watchTimer, &QTimer::timeout, this, &LiveDataSource::readOnUpdate); } LiveDataSource::~LiveDataSource() { //stop reading before deleting the objects pauseReading(); delete m_filter; delete m_fileSystemWatcher; delete m_localSocket; delete m_tcpSocket; delete m_serialPort; } void LiveDataSource::initActions() { m_plotDataAction = new QAction(QIcon::fromTheme("office-chart-line"), i18n("Plot data"), this); connect(m_plotDataAction, &QAction::triggered, this, &LiveDataSource::plotData); m_watchTimer->setSingleShot(true); m_watchTimer->setInterval(100); } QWidget* LiveDataSource::view() const { if (!m_partView) { m_view = new SpreadsheetView(const_cast(this), true); m_partView = m_view; } return m_partView; } /*! * \brief Returns a list with the names of the available ports */ QStringList LiveDataSource::availablePorts() { QStringList ports; // qDebug() << "available ports count:" << QSerialPortInfo::availablePorts().size(); for (const QSerialPortInfo& sp : QSerialPortInfo::availablePorts()) { ports.append(sp.portName()); - DEBUG(" port " << sp.portName().toStdString() << ": " << sp.systemLocation().toStdString() << sp.description().toStdString() - << ' ' << sp.manufacturer().toStdString() << ' ' << sp.serialNumber().toStdString()); + DEBUG(" port " << STDSTRING(sp.portName()) << ": " << STDSTRING(sp.systemLocation()) << STDSTRING(sp.description()) + << ' ' << STDSTRING(sp.manufacturer()) << ' ' << STDSTRING(sp.serialNumber())); } // For Testing: // ports.append("/dev/pts/26"); return ports; } /*! * \brief Returns a list with the supported baud rates */ QStringList LiveDataSource::supportedBaudRates() { QStringList baudRates; for (const auto& baud : QSerialPortInfo::standardBaudRates()) baudRates.append(QString::number(baud)); return baudRates; } /*! * \brief Updates this data source at this moment */ void LiveDataSource::updateNow() { DEBUG("LiveDataSource::updateNow() update interval = " << m_updateInterval); if (m_updateType == TimeInterval) m_updateTimer->stop(); else m_pending = false; read(); //restart the timer after update if (m_updateType == TimeInterval && !m_paused) m_updateTimer->start(m_updateInterval); } /*! * \brief Continue reading from the live data source after it was paused */ void LiveDataSource::continueReading() { m_paused = false; if (m_pending) { m_pending = false; updateNow(); } } /*! * \brief Pause the reading of the live data source */ void LiveDataSource::pauseReading() { m_paused = true; if (m_updateType == TimeInterval) { m_pending = true; m_updateTimer->stop(); } } void LiveDataSource::setFileName(const QString& name) { m_fileName = name; } QString LiveDataSource::fileName() const { return m_fileName; } /*! * \brief Sets the local socket's server name to name * \param name */ void LiveDataSource::setLocalSocketName(const QString& name) { m_localSocketName = name; } QString LiveDataSource::localSocketName() const { return m_localSocketName; } void LiveDataSource::setFileType(AbstractFileFilter::FileType type) { m_fileType = type; } AbstractFileFilter::FileType LiveDataSource::fileType() const { return m_fileType; } void LiveDataSource::setFilter(AbstractFileFilter* f) { delete m_filter; m_filter = f; } AbstractFileFilter* LiveDataSource::filter() const { return m_filter; } /*! * \brief Sets the serial port's baud rate * \param baudrate */ void LiveDataSource::setBaudRate(int baudrate) { m_baudRate = baudrate; } int LiveDataSource::baudRate() const { return m_baudRate; } /*! * \brief Sets the source's update interval to \c interval * \param interval */ void LiveDataSource::setUpdateInterval(int interval) { m_updateInterval = interval; if (!m_paused) m_updateTimer->start(m_updateInterval); } int LiveDataSource::updateInterval() const { return m_updateInterval; } /*! * \brief Sets how many values we should keep when keepLastValues is true * \param keepnvalues */ void LiveDataSource::setKeepNValues(int keepnvalues) { m_keepNValues = keepnvalues; } int LiveDataSource::keepNValues() const { return m_keepNValues; } /*! * \brief Sets the network socket's port to port * \param port */ void LiveDataSource::setPort(quint16 port) { m_port = port; } void LiveDataSource::setBytesRead(qint64 bytes) { m_bytesRead = bytes; } int LiveDataSource::bytesRead() const { return m_bytesRead; } int LiveDataSource::port() const { return m_port; } /*! * \brief Sets the serial port's name to name * \param name */ void LiveDataSource::setSerialPort(const QString& name) { m_serialPortName = name; } QString LiveDataSource::serialPortName() const { return m_serialPortName; } bool LiveDataSource::isPaused() const { return m_paused; } /*! * \brief Sets the sample size to size * \param size */ void LiveDataSource::setSampleSize(int size) { m_sampleSize = size; } int LiveDataSource::sampleSize() const { return m_sampleSize; } /*! * \brief Sets the source's type to sourcetype * \param sourcetype */ void LiveDataSource::setSourceType(SourceType sourcetype) { m_sourceType = sourcetype; } LiveDataSource::SourceType LiveDataSource::sourceType() const { return m_sourceType; } /*! * \brief Sets the source's reading type to readingType * \param readingType */ void LiveDataSource::setReadingType(ReadingType readingType) { m_readingType = readingType; } LiveDataSource::ReadingType LiveDataSource::readingType() const { return m_readingType; } /*! * \brief Sets the source's update type to updatetype and handles this change * \param updatetype */ void LiveDataSource::setUpdateType(UpdateType updatetype) { switch (updatetype) { case NewData: { m_updateTimer->stop(); if (!m_fileSystemWatcher) m_fileSystemWatcher = new QFileSystemWatcher(this); m_fileSystemWatcher->addPath(m_fileName); QFileInfo file(m_fileName); // If the watched file currently does not exist (because it is recreated for instance), watch its containing // directory instead. Once the file exists again, switch to watching the file in readOnUpdate(). // Reading will only start 100ms after the last update, to prevent continuous re-reading while the file is updated. // If the watched file intentionally is updated more often than that, the user should switch to periodic reading. if (m_fileSystemWatcher->files().contains(m_fileName)) m_fileSystemWatcher->removePath(file.absolutePath()); else m_fileSystemWatcher->addPath(file.absolutePath()); connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [&](){ m_watchTimer->start(); }); connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [&](){ m_watchTimer->start(); }); break; } case TimeInterval: delete m_fileSystemWatcher; m_fileSystemWatcher = nullptr; break; } m_updateType = updatetype; } LiveDataSource::UpdateType LiveDataSource::updateType() const { return m_updateType; } /*! * \brief Sets the network socket's host * \param host */ void LiveDataSource::setHost(const QString& host) { m_host = host.simplified(); } QString LiveDataSource::host() const { return m_host; } /*! sets whether only a link to the file is saved in the project file (\c b=true) or the whole content of the file (\c b=false). */ void LiveDataSource::setFileLinked(bool b) { m_fileLinked = b; } /*! returns \c true if only a link to the file is saved in the project file. \c false otherwise. */ bool LiveDataSource::isFileLinked() const { return m_fileLinked; } void LiveDataSource::setUseRelativePath(bool b) { m_relativePath = b; } bool LiveDataSource::useRelativePath() const { return m_relativePath; } QIcon LiveDataSource::icon() const { QIcon icon; switch (m_fileType) { case AbstractFileFilter::Ascii: icon = QIcon::fromTheme("text-plain"); break; case AbstractFileFilter::Binary: icon = QIcon::fromTheme("application-octet-stream"); break; case AbstractFileFilter::Image: icon = QIcon::fromTheme("image-x-generic"); break; // TODO: missing icons case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: break; case AbstractFileFilter::FITS: icon = QIcon::fromTheme("kstars_fitsviewer"); break; case AbstractFileFilter::JSON: icon = QIcon::fromTheme("application-json"); break; case AbstractFileFilter::ROOT: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } return icon; } QMenu* LiveDataSource::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); QAction* firstAction = nullptr; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size() > 1) firstAction = menu->actions().at(1); menu->insertAction(firstAction, m_plotDataAction); menu->insertSeparator(firstAction); return menu; } //############################################################################## //################################# SLOTS #################################### //############################################################################## /* * Called when the watch timer times out, i.e. when modifying the file or directory * presumably has finished. Also see LiveDataSource::setUpdateType(). */ void LiveDataSource::readOnUpdate() { if (!m_fileSystemWatcher->files().contains(m_fileName)) { m_fileSystemWatcher->addPath(m_fileName); QFileInfo file(m_fileName); if (m_fileSystemWatcher->files().contains(m_fileName)) m_fileSystemWatcher->removePath(file.absolutePath()); else { m_fileSystemWatcher->addPath(file.absolutePath()); return; } } if (m_paused) // flag file for reading, once the user decides to continue reading m_pending = true; else read(); } /* * called periodically or on new data changes (file changed, new data in the socket, etc.) */ void LiveDataSource::read() { DEBUG("\nLiveDataSource::read()"); if (!m_filter) return; if (m_reading) return; m_reading = true; //initialize the device (file, socket, serial port) when calling this function for the first time if (!m_prepared) { DEBUG(" Preparing device: update type = " << ENUM_TO_STRING(LiveDataSource, UpdateType, m_updateType)); switch (m_sourceType) { case FileOrPipe: delete m_device; m_device = new QFile(m_fileName); break; case NetworkTcpSocket: m_tcpSocket = new QTcpSocket(this); m_device = m_tcpSocket; m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); connect(m_tcpSocket, &QTcpSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_tcpSocket, static_cast(&QTcpSocket::error), this, &LiveDataSource::tcpSocketError); break; case NetworkUdpSocket: m_udpSocket = new QUdpSocket(this); m_device = m_udpSocket; m_udpSocket->bind(QHostAddress(m_host), m_port); m_udpSocket->connectToHost(m_host, 0, QUdpSocket::ReadOnly); // only connect to readyRead when update is on new data if (m_updateType == NewData) connect(m_udpSocket, &QUdpSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_udpSocket, static_cast(&QUdpSocket::error), this, &LiveDataSource::tcpSocketError); break; case LocalSocket: m_localSocket = new QLocalSocket(this); m_device = m_localSocket; m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); connect(m_localSocket, &QLocalSocket::readyRead, this, &LiveDataSource::readyRead); connect(m_localSocket, static_cast(&QLocalSocket::error), this, &LiveDataSource::localSocketError); break; case SerialPort: m_serialPort = new QSerialPort(this); m_device = m_serialPort; - DEBUG(" Serial: " << m_serialPortName.toStdString() << ", " << m_baudRate); + DEBUG(" Serial: " << STDSTRING(m_serialPortName) << ", " << m_baudRate); m_serialPort->setBaudRate(m_baudRate); m_serialPort->setPortName(m_serialPortName); m_serialPort->open(QIODevice::ReadOnly); // only connect to readyRead when update is on new data if (m_updateType == NewData) connect(m_serialPort, &QSerialPort::readyRead, this, &LiveDataSource::readyRead); connect(m_serialPort, static_cast(&QSerialPort::error), this, &LiveDataSource::serialPortError); break; case MQTT: break; } m_prepared = true; } qint64 bytes = 0; switch (m_sourceType) { case FileOrPipe: DEBUG("Reading FileOrPipe. type = " << ENUM_TO_STRING(AbstractFileFilter, FileType, m_fileType)); switch (m_fileType) { case AbstractFileFilter::Ascii: if (m_readingType == LiveDataSource::ReadingType::WholeFile) { static_cast(m_filter)->readFromLiveDevice(*m_device, this, 0); } else { bytes = static_cast(m_filter)->readFromLiveDevice(*m_device, this, m_bytesRead); m_bytesRead += bytes; DEBUG("Read " << bytes << " bytes, in total: " << m_bytesRead); } break; case AbstractFileFilter::Binary: //TODO: not implemented yet // bytes = qSharedPointerCast(m_filter)->readFromLiveDevice(*m_file, this, m_bytesRead); // m_bytesRead += bytes; case AbstractFileFilter::ROOT: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: //only re-reading of the whole file is supported m_filter->readDataFromFile(m_fileName, this); break; //TODO: other types not implemented yet case AbstractFileFilter::Image: case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: case AbstractFileFilter::JSON: break; } break; case NetworkTcpSocket: DEBUG("reading from TCP socket. state before abort = " << m_tcpSocket->state()); m_tcpSocket->abort(); m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly); DEBUG("reading from TCP socket. state after reconnect = " << m_tcpSocket->state()); break; case NetworkUdpSocket: DEBUG(" Reading from UDP socket. state = " << m_udpSocket->state()); // reading data here if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); break; case LocalSocket: DEBUG(" Reading from local socket. state before abort = " << m_localSocket->state()); if (m_localSocket->state() == QLocalSocket::ConnectingState) m_localSocket->abort(); m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly); if (m_localSocket->waitForConnected()) m_localSocket->waitForReadyRead(); DEBUG(" Reading from local socket. state after reconnect = " << m_localSocket->state()); break; case SerialPort: DEBUG(" Reading from serial port"); // reading data here if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); break; case MQTT: break; } m_reading = false; } /*! * Slot for the signal that is emitted once every time new data is available for reading from the device (not UDP or Serial). * It will only be emitted again once new data is available, such as when a new payload of network data has arrived on the network socket, * or when a new block of data has been appended to your device. */ void LiveDataSource::readyRead() { DEBUG("LiveDataSource::readyRead() update type = " << ENUM_TO_STRING(LiveDataSource,UpdateType,m_updateType)); DEBUG(" REMAINING TIME = " << m_updateTimer->remainingTime()); if (m_fileType == AbstractFileFilter::Ascii) static_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); //TODO: not implemented yet // else if (m_fileType == AbstractFileFilter::Binary) // dynamic_cast(m_filter)->readFromLiveDeviceNotFile(*m_device, this); //since we won't have the timer to call read() where we create new connections //for sequential devices in read() we just request data/connect to servers if (m_updateType == NewData) read(); } void LiveDataSource::localSocketError(QLocalSocket::LocalSocketError socketError) { Q_UNUSED(socketError); /*disconnect(m_localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(localSocketError(QLocalSocket::LocalSocketError))); disconnect(m_localSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));*/ /*switch (socketError) { case QLocalSocket::ServerNotFoundError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The socket was not found. Please check the socket name.")); break; case QLocalSocket::ConnectionRefusedError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The connection was refused by the peer")); break; case QLocalSocket::PeerClosedError: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The socket has closed the connection.")); break; default: QMessageBox::critical(0, i18n("Local Socket Error"), i18n("The following error occurred: %1.", m_localSocket->errorString())); }*/ } void LiveDataSource::tcpSocketError(QAbstractSocket::SocketError socketError) { Q_UNUSED(socketError); /*switch (socketError) { case QAbstractSocket::ConnectionRefusedError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The connection was refused by the peer. Make sure the server is running and check the host name and port settings.")); break; case QAbstractSocket::RemoteHostClosedError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The remote host closed the connection.")); break; case QAbstractSocket::HostNotFoundError: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The host was not found. Please check the host name and port settings.")); break; default: QMessageBox::critical(0, i18n("TCP Socket Error"), i18n("The following error occurred: %1.", m_tcpSocket->errorString())); }*/ } void LiveDataSource::serialPortError(QSerialPort::SerialPortError serialPortError) { switch (serialPortError) { case QSerialPort::DeviceNotFoundError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to open the device.")); break; case QSerialPort::PermissionError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to open the device. Please check your permissions on this device.")); break; case QSerialPort::OpenError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Device already opened.")); break; case QSerialPort::NotOpenError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device is not opened.")); break; case QSerialPort::ReadError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data.")); break; case QSerialPort::ResourceError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data. The device is removed.")); break; case QSerialPort::TimeoutError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device timed out.")); break; #ifndef _MSC_VER //MSVC complains about the usage of deprecated enums, g++ and clang complain about missing enums case QSerialPort::ParityError: case QSerialPort::FramingError: case QSerialPort::BreakConditionError: #endif case QSerialPort::WriteError: case QSerialPort::UnsupportedOperationError: case QSerialPort::UnknownError: QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The following error occurred: %1.", m_serialPort->errorString())); break; case QSerialPort::NoError: break; } } void LiveDataSource::plotData() { auto* dlg = new PlotDataDialog(this); dlg->exec(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void LiveDataSource::save(QXmlStreamWriter* writer) const { writer->writeStartElement("liveDataSource"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); switch (m_sourceType) { case FileOrPipe: writer->writeAttribute("fileType", QString::number(m_fileType)); writer->writeAttribute("fileLinked", QString::number(m_fileLinked)); writer->writeAttribute("relativePath", QString::number(m_relativePath)); if (m_relativePath) { //convert from the absolute to the relative path and save it const Project* p = const_cast(this)->project(); QFileInfo fi(p->fileName()); writer->writeAttribute("fileName", fi.dir().relativeFilePath(m_fileName)); }else writer->writeAttribute("fileName", m_fileName); break; case SerialPort: writer->writeAttribute("baudRate", QString::number(m_baudRate)); writer->writeAttribute("serialPortName", m_serialPortName); break; case NetworkTcpSocket: case NetworkUdpSocket: writer->writeAttribute("host", m_host); writer->writeAttribute("port", QString::number(m_port)); break; case LocalSocket: break; case MQTT: break; default: break; } writer->writeAttribute("updateType", QString::number(m_updateType)); writer->writeAttribute("readingType", QString::number(m_readingType)); writer->writeAttribute("sourceType", QString::number(m_sourceType)); writer->writeAttribute("keepNValues", QString::number(m_keepNValues)); if (m_updateType == TimeInterval) writer->writeAttribute("updateInterval", QString::number(m_updateInterval)); if (m_readingType != TillEnd) writer->writeAttribute("sampleSize", QString::number(m_sampleSize)); writer->writeEndElement(); //general //filter m_filter->save(writer); //columns if (!m_fileLinked) { for (auto* col : children(IncludeHidden)) col->save(writer); } writer->writeEndElement(); // "liveDataSource" } /*! Loads from XML. */ bool LiveDataSource::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && (reader->name() == "liveDataSource" || reader->name() == "LiveDataSource")) //TODO: remove "LiveDataSources" in couple of releases break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("fileName").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileName").toString()); else m_fileName = str; str = attribs.value("fileType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileType").toString()); else m_fileType = (AbstractFileFilter::FileType)str.toInt(); str = attribs.value("fileLinked").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fileLinked").toString()); else m_fileLinked = str.toInt(); str = attribs.value("relativePath").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("relativePath").toString()); else m_relativePath = str.toInt(); str = attribs.value("updateType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("updateType").toString()); else m_updateType = static_cast(str.toInt()); str = attribs.value("sourceType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("sourceType").toString()); else m_sourceType = static_cast(str.toInt()); str = attribs.value("readingType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("readingType").toString()); else m_readingType = static_cast(str.toInt()); if (m_updateType == TimeInterval) { str = attribs.value("updateInterval").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("updateInterval").toString()); else m_updateInterval = str.toInt(); } if (m_readingType != TillEnd) { str = attribs.value("sampleSize").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("sampleSize").toString()); else m_sampleSize = str.toInt(); } switch (m_sourceType) { case SerialPort: str = attribs.value("baudRate").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("baudRate").toString()); else m_baudRate = str.toInt(); str = attribs.value("serialPortName").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("serialPortName").toString()); else m_serialPortName = str; break; case NetworkTcpSocket: case NetworkUdpSocket: str = attribs.value("host").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("host").toString()); else m_host = str; str = attribs.value("port").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("port").toString()); else m_host = str; break; case MQTT: break; case FileOrPipe: break; case LocalSocket: break; default: break; } } else if (reader->name() == "asciiFilter") { setFilter(new AsciiFilter); if (!m_filter->load(reader)) return false; } else if (reader->name() == "rootFilter") { setFilter(new ROOTFilter); if (!m_filter->load(reader)) return false; } else if (reader->name() == "column") { Column* column = new Column(QString(), AbstractColumn::Text); if (!column->load(reader, preview)) { delete column; setColumnCount(0); return false; } addChild(column); } else {// unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return !reader->hasError(); } void LiveDataSource::finalizeLoad() { //convert from the relative path saved in the project file to the absolute file to work with if (m_relativePath) { QFileInfo fi(project()->fileName()); m_fileName = fi.dir().absoluteFilePath(m_fileName); } //read the content of the file if it was only linked if (m_fileLinked && QFile::exists(m_fileName)) this->read(); //call setUpdateType() to start watching the file for changes, is required setUpdateType(m_updateType); } diff --git a/src/backend/datasources/filters/AbstractFileFilter.cpp b/src/backend/datasources/filters/AbstractFileFilter.cpp index de28b9e38..a3d68a224 100644 --- a/src/backend/datasources/filters/AbstractFileFilter.cpp +++ b/src/backend/datasources/filters/AbstractFileFilter.cpp @@ -1,188 +1,188 @@ /*************************************************************************** File : AbstractFileFilter.h Project : LabPlot Description : file I/O-filter related interface -------------------------------------------------------------------- Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/datasources/filters/NgspiceRawAsciiFilter.h" #include "backend/datasources/filters/NgspiceRawBinaryFilter.h" #include "backend/lib/macros.h" #include #include #include #include #include bool AbstractFileFilter::isNan(const QString& s) { const static QStringList nanStrings{"NA", "NAN", "N/A", "-NA", "-NAN", "NULL"}; if (nanStrings.contains(s, Qt::CaseInsensitive)) return true; return false; } AbstractColumn::ColumnMode AbstractFileFilter::columnMode(const QString& valueString, const QString& dateTimeFormat) { QLocale locale; return columnMode(valueString, dateTimeFormat, locale); } AbstractColumn::ColumnMode AbstractFileFilter::columnMode(const QString& valueString, const QString& dateTimeFormat, QLocale::Language lang) { QLocale locale(lang); return columnMode(valueString, dateTimeFormat, locale); } AbstractColumn::ColumnMode AbstractFileFilter::columnMode(const QString& valueString, const QString& dateTimeFormat, const QLocale& locale) { //TODO: use BigInt as default integer? if (valueString.size() == 0) // empty string treated as integer (meaning the non-empty strings will determine the data type) return AbstractColumn::Integer; if (isNan(valueString)) return AbstractColumn::Numeric; const std::string stdValueString = valueString.toStdString(); // check if integer first bool ok; int intValue = locale.toInt(valueString, &ok); DEBUG("string " << stdValueString << ": toInt " << intValue << "?:" << ok); Q_UNUSED(intValue) if (ok || isNan(valueString)) return AbstractColumn::Integer; //check big integer qint64 bigIntValue = locale.toLongLong(valueString, &ok); DEBUG("string " << stdValueString << ": toBigInt " << bigIntValue << "?:" << ok); Q_UNUSED(bigIntValue) if (ok || isNan(valueString)) return AbstractColumn::BigInt; //try to convert to a double AbstractColumn::ColumnMode mode = AbstractColumn::Numeric; double value = locale.toDouble(valueString, &ok); DEBUG("string " << stdValueString << ": toDouble " << value << "?:" << ok); Q_UNUSED(value) //if not a number, check datetime. if that fails: string if (!ok) { QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); if (valueDateTime.isValid()) mode = AbstractColumn::DateTime; else mode = AbstractColumn::Text; } return mode; } /* returns the list of all supported locales for numeric data */ QStringList AbstractFileFilter::numberFormats() { QStringList formats; for (int l = 0; l < ENUM_COUNT(QLocale, Language); ++l) formats << QLocale::languageToString((QLocale::Language)l); return formats; } AbstractFileFilter::FileType AbstractFileFilter::fileType(const QString& fileName) { QString fileInfo; #ifndef HAVE_WINDOWS //check, if we can guess the file type by content QProcess proc; proc.start("file", QStringList() << "-b" << "-z" << fileName); if (!proc.waitForFinished(1000)) { proc.kill(); - DEBUG("ERROR: reading file type of file" << fileName.toStdString()); + DEBUG("ERROR: reading file type of file" << STDSTRING(fileName)); return Binary; } fileInfo = proc.readLine(); #endif FileType fileType; QByteArray imageFormat = QImageReader::imageFormat(fileName); if (fileInfo.contains(QLatin1String("JSON")) || fileName.endsWith(QLatin1String("json"), Qt::CaseInsensitive) //json file can be compressed. add all formats supported by KFilterDev, \sa KCompressionDevice::CompressionType || fileName.endsWith(QLatin1String("json.gz"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("json.bz2"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("json.lzma"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("json.xz"), Qt::CaseInsensitive) ) { //*.json files can be recognized as ASCII. so, do the check for the json-extension as first. fileType = JSON; } else if (fileInfo.contains(QLatin1String("ASCII")) || fileName.endsWith(QLatin1String("txt"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("csv"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("dat"), Qt::CaseInsensitive) || fileInfo.contains(QLatin1String("compressed data"))/* for gzipped ascii data */ ) { if (NgspiceRawAsciiFilter::isNgspiceAsciiFile(fileName)) fileType = NgspiceRawAscii; else //probably ascii data fileType = Ascii; } else if (fileInfo.contains(QLatin1String("Hierarchical Data Format")) || fileName.endsWith(QLatin1String("h5"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("hdf"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("hdf5"), Qt::CaseInsensitive) ) fileType = HDF5; else if (fileInfo.contains(QLatin1String("NetCDF Data Format")) || fileName.endsWith(QLatin1String("nc"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("netcdf"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("cdf"), Qt::CaseInsensitive)) fileType = NETCDF; else if (fileInfo.contains(QLatin1String("FITS image data")) || fileName.endsWith(QLatin1String("fits"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("fit"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String("fts"), Qt::CaseInsensitive)) fileType = FITS; else if (fileInfo.contains(QLatin1String("ROOT")) //can be "ROOT Data Format" or "ROOT file Version ??? (Compression: 1)" || fileName.endsWith(QLatin1String("root"), Qt::CaseInsensitive)) // TODO find out file description fileType = ROOT; else if (fileInfo.contains("image") || fileInfo.contains("bitmap") || !imageFormat.isEmpty()) fileType = Image; else if (NgspiceRawBinaryFilter::isNgspiceBinaryFile(fileName)) fileType = NgspiceRawBinary; else fileType = Binary; return fileType; } /*! returns the list of all supported data file formats */ QStringList AbstractFileFilter::fileTypes() { return (QStringList() << i18n("ASCII data") << i18n("Binary data") << i18n("Image") << i18n("Hierarchical Data Format 5 (HDF5)") << i18n("Network Common Data Format (NetCDF)") << i18n("Flexible Image Transport System Data Format (FITS)") << i18n("JSON data") << i18n("ROOT (CERN) Histograms") << "Ngspice RAW ASCII" << "Ngspice RAW Binary" ); } diff --git a/src/backend/datasources/filters/AsciiFilter.cpp b/src/backend/datasources/filters/AsciiFilter.cpp index dc601a6ab..894060d76 100644 --- a/src/backend/datasources/filters/AsciiFilter.cpp +++ b/src/backend/datasources/filters/AsciiFilter.cpp @@ -1,2825 +1,2825 @@ /*************************************************************************** File : AsciiFilter.cpp Project : LabPlot Description : ASCII I/O-filter -------------------------------------------------------------------- Copyright : (C) 2009-2020 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/LiveDataSource.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/AsciiFilterPrivate.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #include "backend/datasources/MQTTTopic.h" #endif #include #include #include #if defined(Q_OS_LINUX) || defined(Q_OS_BSD4) #include #include #endif #include /*! \class AsciiFilter \brief Manages the import/export of data organized as columns (vectors) from/to an ASCII-file. \ingroup datasources */ AsciiFilter::AsciiFilter() : AbstractFileFilter(Ascii), d(new AsciiFilterPrivate(this)) {} AsciiFilter::~AsciiFilter() = default; /*! reads the content of the device \c device. */ void AsciiFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromDevice(device, dataSource, importMode, lines); } void AsciiFilter::readFromLiveDeviceNotFile(QIODevice &device, AbstractDataSource* dataSource) { d->readFromLiveDevice(device, dataSource); } qint64 AsciiFilter::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) { return d->readFromLiveDevice(device, dataSource, from); } #ifdef HAVE_MQTT QVector AsciiFilter::preview(const QString& message) { return d->preview(message); } /*! reads the content of a message received by the topic. */ void AsciiFilter::readMQTTTopic(const QString& message, AbstractDataSource* dataSource) { d->readMQTTTopic(message, dataSource); } /*! Returns the statistical data, that the MQTTTopic needs for the will message. */ QString AsciiFilter::MQTTColumnStatistics(const MQTTTopic* topic) const { return d->MQTTColumnStatistics(topic); } /*! Returns the column mode of the last column (the value column of the MQTTTopic). */ AbstractColumn::ColumnMode AsciiFilter::MQTTColumnMode() const { return d->MQTTColumnMode(); } /*! After the MQTTTopic is loaded, prepares the filter for reading. */ void AsciiFilter::setPreparedForMQTT(bool prepared, MQTTTopic* topic, const QString& separator) { d->setPreparedForMQTT(prepared, topic, separator); } #endif /*! returns the separator used by the filter. */ QString AsciiFilter::separator() const { return d->separator(); } /*! returns the separator used by the filter. */ int AsciiFilter::isPrepared() { return d->isPrepared(); } /*! reads the content of the file \c fileName. */ void AsciiFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } QVector AsciiFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } QVector AsciiFilter::preview(QIODevice& device) { return d->preview(device); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ //void AsciiFilter::read(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { // d->read(fileName, dataSource, importMode); //} /*! writes the content of the data source \c dataSource to the file \c fileName. */ void AsciiFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /*! loads the predefined filter settings for \c filterName */ void AsciiFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void AsciiFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /*! returns the list with the names of all saved (system wide or user defined) filter settings. */ QStringList AsciiFilter::predefinedFilters() { return QStringList(); } /*! returns the list of all predefined separator characters. */ QStringList AsciiFilter::separatorCharacters() { return (QStringList() << "auto" << "TAB" << "SPACE" << "," << ";" << ":" << ",TAB" << ";TAB" << ":TAB" << ",SPACE" << ";SPACE" << ":SPACE" << "2xSPACE" << "3xSPACE" << "4xSPACE" << "2xTAB"); } /*! returns the list of all predefined comment characters. */ QStringList AsciiFilter::commentCharacters() { return (QStringList() << "#" << "!" << "//" << "+" << "c" << ":" << ";"); } /*! returns the list of all predefined data types. */ QStringList AsciiFilter::dataTypes() { const QMetaObject& mo = AbstractColumn::staticMetaObject; const QMetaEnum& me = mo.enumerator(mo.indexOfEnumerator("ColumnMode")); QStringList list; for (int i = 0; i <= 100; ++i) // me.keyCount() does not work because we have holes in enum if (me.valueToKey(i)) list << me.valueToKey(i); return list; } QString AsciiFilter::fileInfoString(const QString& fileName) { QString info(i18n("Number of columns: %1", AsciiFilter::columnNumber(fileName))); info += QLatin1String("
"); info += i18n("Number of lines: %1", AsciiFilter::lineNumber(fileName)); return info; } /*! returns the number of columns in the file \c fileName. */ int AsciiFilter::columnNumber(const QString& fileName, const QString& separator) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { - DEBUG("Could not open file " << fileName.toStdString() << " for determining number of columns"); + DEBUG("Could not open file " << STDSTRING(fileName) << " for determining number of columns"); return -1; } QString line = device.readLine(); line.remove(QRegularExpression(QStringLiteral("[\\n\\r]"))); QStringList lineStringList; if (separator.length() > 0) lineStringList = line.split(separator); else lineStringList = line.split(QRegularExpression(QStringLiteral("\\s+"))); DEBUG("number of columns : " << lineStringList.size()); return lineStringList.size(); } size_t AsciiFilter::lineNumber(const QString& fileName) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { - DEBUG("Could not open file " << fileName.toStdString() << " to determine number of lines"); + DEBUG("Could not open file " << STDSTRING(fileName) << " to determine number of lines"); return 0; } // if (!device.canReadLine()) // return -1; size_t lineCount = 0; #if defined(Q_OS_LINUX) || defined(Q_OS_BSD4) //on linux and BSD use wc, if available, which is much faster than counting lines in the file if (device.compressionType() == KCompressionDevice::None && !QStandardPaths::findExecutable(QLatin1String("wc")).isEmpty()) { QProcess wc; wc.start(QLatin1String("wc"), QStringList() << QLatin1String("-l") << fileName); size_t lineCount = 0; while (wc.waitForReadyRead()) { QString line(wc.readLine()); // wc on macOS has leading spaces: use SkipEmptyParts lineCount = line.split(' ', QString::SkipEmptyParts)[0].toInt(); } return lineCount; } #endif while (!device.atEnd()) { device.readLine(); lineCount++; } return lineCount; } /*! returns the number of lines in the device \c device and 0 if sequential. resets the position to 0! */ size_t AsciiFilter::lineNumber(QIODevice& device) const { if (device.isSequential()) return 0; // if (!device.canReadLine()) // DEBUG("WARNING in AsciiFilter::lineNumber(): device cannot 'readLine()' but using it anyway."); size_t lineCount = 0; device.seek(0); if (d->readingFile) lineCount = lineNumber(d->readingFileName); else { while (!device.atEnd()) { device.readLine(); lineCount++; } } device.seek(0); return lineCount; } void AsciiFilter::setCommentCharacter(const QString& s) { d->commentCharacter = s; } QString AsciiFilter::commentCharacter() const { return d->commentCharacter; } void AsciiFilter::setSeparatingCharacter(const QString& s) { d->separatingCharacter = s; } QString AsciiFilter::separatingCharacter() const { return d->separatingCharacter; } void AsciiFilter::setDateTimeFormat(const QString &f) { d->dateTimeFormat = f; } QString AsciiFilter::dateTimeFormat() const { return d->dateTimeFormat; } void AsciiFilter::setNumberFormat(QLocale::Language lang) { d->numberFormat = lang; } QLocale::Language AsciiFilter::numberFormat() const { return d->numberFormat; } void AsciiFilter::setAutoModeEnabled(const bool b) { d->autoModeEnabled = b; } bool AsciiFilter::isAutoModeEnabled() const { return d->autoModeEnabled; } void AsciiFilter::setHeaderEnabled(const bool b) { d->headerEnabled = b; } bool AsciiFilter::isHeaderEnabled() const { return d->headerEnabled; } void AsciiFilter::setSkipEmptyParts(const bool b) { d->skipEmptyParts = b; } bool AsciiFilter::skipEmptyParts() const { return d->skipEmptyParts; } void AsciiFilter::setCreateIndexEnabled(bool b) { d->createIndexEnabled = b; } bool AsciiFilter::createIndexEnabled() const { return d->createIndexEnabled; } void AsciiFilter::setCreateTimestampEnabled(bool b) { d->createTimestampEnabled = b; } bool AsciiFilter::createTimestampEnabled() const { return d->createTimestampEnabled; } void AsciiFilter::setSimplifyWhitespacesEnabled(bool b) { d->simplifyWhitespacesEnabled = b; } bool AsciiFilter::simplifyWhitespacesEnabled() const { return d->simplifyWhitespacesEnabled; } void AsciiFilter::setNaNValueToZero(bool b) { if (b) d->nanValue = 0; else d->nanValue = std::numeric_limits::quiet_NaN(); } bool AsciiFilter::NaNValueToZeroEnabled() const { return (d->nanValue == 0); } void AsciiFilter::setRemoveQuotesEnabled(bool b) { d->removeQuotesEnabled = b; } bool AsciiFilter::removeQuotesEnabled() const { return d->removeQuotesEnabled; } void AsciiFilter::setVectorNames(const QString& s) { d->vectorNames.clear(); if (!s.simplified().isEmpty()) d->vectorNames = s.simplified().split(' '); } void AsciiFilter::setVectorNames(const QStringList& list) { d->vectorNames = list; } QStringList AsciiFilter::vectorNames() const { return d->vectorNames; } QVector AsciiFilter::columnModes() { return d->columnModes; } void AsciiFilter::setStartRow(const int r) { d->startRow = r; } int AsciiFilter::startRow() const { return d->startRow; } void AsciiFilter::setEndRow(const int r) { d->endRow = r; } int AsciiFilter::endRow() const { return d->endRow; } void AsciiFilter::setStartColumn(const int c) { d->startColumn = c; } int AsciiFilter::startColumn() const { return d->startColumn; } void AsciiFilter::setEndColumn(const int c) { d->endColumn = c; } int AsciiFilter::endColumn() const { return d->endColumn; } //##################################################################### //################### Private implementation ########################## //##################################################################### AsciiFilterPrivate::AsciiFilterPrivate(AsciiFilter* owner) : q(owner) { } /*! * get a single line from device */ QStringList AsciiFilterPrivate::getLineString(QIODevice& device) { QString line; do { // skip comment lines in data lines if (!device.canReadLine()) DEBUG("WARNING in AsciiFilterPrivate::getLineString(): device cannot 'readLine()' but using it anyway."); // line = device.readAll(); line = device.readLine(); } while (!commentCharacter.isEmpty() && line.startsWith(commentCharacter)); line.remove(QRegularExpression(QStringLiteral("[\\n\\r]"))); // remove any newline - DEBUG("data line : \'" << line.toStdString() << '\''); + DEBUG("data line : \'" << STDSTRING(line) << '\''); QStringList lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); //TODO: remove quotes here? if (simplifyWhitespacesEnabled) { for (int i = 0; i < lineStringList.size(); ++i) lineStringList[i] = lineStringList[i].simplified(); } QDEBUG("data line, parsed: " << lineStringList); return lineStringList; } /*! * returns -1 if the device couldn't be opened, 1 if the current read position in the device is at the end and 0 otherwise. */ int AsciiFilterPrivate::prepareDeviceToRead(QIODevice& device) { DEBUG("AsciiFilterPrivate::prepareDeviceToRead(): is sequential = " << device.isSequential() << ", can readLine = " << device.canReadLine()); if (!device.open(QIODevice::ReadOnly)) return -1; if (device.atEnd() && !device.isSequential()) // empty file return 1; ///////////////////////////////////////////////////////////////// // Parse the first line: // Determine the number of columns, create the columns and use (if selected) the first row to name them QString firstLine; // skip the comment lines and read the first line if (!commentCharacter.isEmpty()) { do { if (!device.canReadLine()) DEBUG("WARNING in AsciiFilterPrivate::prepareDeviceToRead(): device cannot 'readLine()' but using it anyway."); if (device.atEnd()) { DEBUG("device at end! Giving up."); if (device.isSequential()) break; else return 1; } firstLine = device.readLine(); } while (firstLine.startsWith(commentCharacter) || firstLine.simplified().isEmpty()); } else firstLine = device.readLine(); // navigate to the line where we asked to start reading from DEBUG(" Skipping " << startRow - 1 << " lines"); for (int i = 0; i < startRow - 1; ++i) { if (!device.canReadLine()) DEBUG("WARNING in AsciiFilterPrivate::prepareDeviceToRead(): device cannot 'readLine()' but using it anyway."); if (device.atEnd()) { DEBUG("device at end! Giving up."); if (device.isSequential()) break; else return 1; } firstLine = device.readLine(); - DEBUG(" line = " << firstLine.toStdString()); + DEBUG(" line = " << STDSTRING(firstLine)); } DEBUG(" device position after first line and comments = " << device.pos()); firstLine.remove(QRegularExpression(QStringLiteral("[\\n\\r]"))); // remove any newline if (removeQuotesEnabled) firstLine = firstLine.remove(QLatin1Char('"')); //TODO: this doesn't work, the split below introduces whitespaces again // if (simplifyWhitespacesEnabled) // firstLine = firstLine.simplified(); - DEBUG("First line: \'" << firstLine.toStdString() << '\''); + DEBUG("First line: \'" << STDSTRING(firstLine) << '\''); // determine separator and split first line QStringList firstLineStringList; if (separatingCharacter == "auto") { DEBUG("automatic separator"); if (firstLine.indexOf(QLatin1Char('\t')) != -1) { //in case we have a mix of tabs and spaces in the header, give the tab character the preference m_separator = QLatin1Char('\t'); firstLineStringList = firstLine.split(m_separator, (QString::SplitBehavior)skipEmptyParts); } else { const QRegularExpression regExp(QStringLiteral("[,;:]?\\s+")); firstLineStringList = firstLine.split(regExp, (QString::SplitBehavior)skipEmptyParts); if (!firstLineStringList.isEmpty()) { int length1 = firstLineStringList.at(0).length(); if (firstLineStringList.size() > 1) m_separator = firstLine.mid(length1, 1); else m_separator = ' '; } } } else { // use given separator // replace symbolic "TAB" with '\t' m_separator = separatingCharacter.replace(QLatin1String("2xTAB"), "\t\t", Qt::CaseInsensitive); m_separator = separatingCharacter.replace(QLatin1String("TAB"), "\t", Qt::CaseInsensitive); // replace symbolic "SPACE" with ' ' m_separator = m_separator.replace(QLatin1String("2xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("3xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("4xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); firstLineStringList = firstLine.split(m_separator, (QString::SplitBehavior)skipEmptyParts); } - DEBUG("separator: \'" << m_separator.toStdString() << '\''); + DEBUG("separator: \'" << STDSTRING(m_separator) << '\''); DEBUG("number of columns: " << firstLineStringList.size()); QDEBUG("first line: " << firstLineStringList); DEBUG("headerEnabled: " << headerEnabled); //optionally, remove potential spaces in the first line //TODO: this part should be obsolete actually if we do firstLine = firstLine.simplified(); above... if (simplifyWhitespacesEnabled) { for (int i = 0; i < firstLineStringList.size(); ++i) firstLineStringList[i] = firstLineStringList[i].simplified(); } //in GUI in AsciiOptionsWidget we start counting from 1, subtract 1 here to start from zero m_actualStartRow = startRow - 1; if (headerEnabled) { // use first line to name vectors vectorNames = firstLineStringList; ++m_actualStartRow; } // set range to read if (endColumn == -1) { if (headerEnabled || vectorNames.size() == 0) endColumn = firstLineStringList.size(); // last column else //number of vector names provided in the import dialog (not more than the maximal number of columns in the file) endColumn = qMin(vectorNames.size(), firstLineStringList.size()); } if (endColumn < startColumn) m_actualCols = 0; else m_actualCols = endColumn - startColumn + 1; if (createIndexEnabled) { vectorNames.prepend(i18n("Index")); m_actualCols++; } QDEBUG("vector names =" << vectorNames); //TEST: readline-seek-readline fails /* qint64 testpos = device.pos(); - DEBUG("read data line @ pos " << testpos << " : " << device.readLine().toStdString()); + DEBUG("read data line @ pos " << testpos << " : " << STDSTRING(device.readLine())); device.seek(testpos); testpos = device.pos(); - DEBUG("read data line again @ pos " << testpos << " : " << device.readLine().toStdString()); + DEBUG("read data line again @ pos " << testpos << " : " << STDSTRING(device.readLine())); */ ///////////////////////////////////////////////////////////////// // parse first data line to determine data type for each column // if the first line was already parsed as the header, read the next line if (headerEnabled && !device.isSequential()) firstLineStringList = getLineString(device); columnModes.resize(m_actualCols); int col = 0; if (createIndexEnabled) { columnModes[0] = AbstractColumn::Integer; col = 1; } for (auto& valueString : firstLineStringList) { // parse columns available in first data line if (simplifyWhitespacesEnabled) valueString = valueString.simplified(); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); if (col == m_actualCols) break; columnModes[col++] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); } // parsing more lines to better determine data types for (unsigned int i = 0; i < m_dataTypeLines; ++i) { if (device.atEnd()) // EOF reached break; firstLineStringList = getLineString(device); createIndexEnabled ? col = 1 : col = 0; for (auto& valueString : firstLineStringList) { if (simplifyWhitespacesEnabled) valueString = valueString.simplified(); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); if (col == m_actualCols) break; AbstractColumn::ColumnMode mode = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); // numeric: integer -> numeric if (mode == AbstractColumn::Numeric && columnModes[col] == AbstractColumn::Integer) columnModes[col] = mode; // text: non text -> text if (mode == AbstractColumn::Text && columnModes[col] != AbstractColumn::Text) columnModes[col] = mode; // numeric: text -> numeric/integer if (mode != AbstractColumn::Text && columnModes[col] == AbstractColumn::Text) columnModes[col] = mode; col++; } } QDEBUG("column modes = " << columnModes); // ATTENTION: This resets the position in the device to 0 m_actualRows = (int)q->lineNumber(device); const int actualEndRow = (endRow == -1 || endRow > m_actualRows) ? m_actualRows : endRow; if (actualEndRow > m_actualStartRow) m_actualRows = actualEndRow - m_actualStartRow; else m_actualRows = 0; DEBUG("start/end column: " << startColumn << ' ' << endColumn); DEBUG("start/end row: " << m_actualStartRow << ' ' << actualEndRow); DEBUG("actual cols/rows (w/o header): " << m_actualCols << ' ' << m_actualRows); if (m_actualRows == 0 && !device.isSequential()) return 1; return 0; } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { - DEBUG("AsciiFilterPrivate::readDataFromFile(): fileName = \'" << fileName.toStdString() << "\', dataSource = " + DEBUG("AsciiFilterPrivate::readDataFromFile(): fileName = \'" << STDSTRING(fileName) << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode)); //dirty hack: set readingFile and readingFileName in order to know in lineNumber(QIODevice) //that we're reading from a file and to benefit from much faster wc on linux //TODO: redesign the APIs and remove this later readingFile = true; readingFileName = fileName; KFilterDev device(fileName); readDataFromDevice(device, dataSource, importMode); readingFile = false; } qint64 AsciiFilterPrivate::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) { DEBUG("AsciiFilterPrivate::readFromLiveDevice(): bytes available = " << device.bytesAvailable() << ", from = " << from); if (device.bytesAvailable() <= 0) { DEBUG(" No new data available"); return 0; } //TODO: may be also a matrix? auto* spreadsheet = dynamic_cast(dataSource); if (!spreadsheet) return 0; if (spreadsheet->sourceType() != LiveDataSource::SourceType::FileOrPipe) if (device.isSequential() && device.bytesAvailable() < (int)sizeof(quint16)) return 0; if (!m_prepared) { DEBUG(" Preparing .."); switch (spreadsheet->sourceType()) { case LiveDataSource::SourceType::FileOrPipe: { const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG(" Device error = " << deviceError); return 0; } break; } case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: case LiveDataSource::SourceType::LocalSocket: case LiveDataSource::SourceType::SerialPort: m_actualRows = 1; if (createIndexEnabled) { m_actualCols = 2; columnModes << AbstractColumn::Integer << AbstractColumn::Numeric; vectorNames << i18n("Index") << i18n("Value"); } else { m_actualCols = 1; columnModes << AbstractColumn::Numeric; vectorNames << i18n("Value"); } QDEBUG(" vector names = " << vectorNames); break; case LiveDataSource::SourceType::MQTT: break; } // prepare import for spreadsheet spreadsheet->setUndoAware(false); spreadsheet->resize(AbstractFileFilter::Replace, vectorNames, m_actualCols); //columns in a file data source don't have any manual changes. //make the available columns undo unaware and suppress the "data changed" signal. //data changes will be propagated via an explicit Column::setChanged() call once new data was read. for (int i = 0; i < spreadsheet->childCount(); i++) { spreadsheet->child(i)->setUndoAware(false); spreadsheet->child(i)->setSuppressDataChangedSignal(true); } int keepNValues = spreadsheet->keepNValues(); if (keepNValues == 0) spreadsheet->setRowCount(m_actualRows > 1 ? m_actualRows : 1); else { spreadsheet->setRowCount(keepNValues); m_actualRows = keepNValues; } m_dataContainer.resize(m_actualCols); DEBUG(" data source resized to col: " << m_actualCols); DEBUG(" data source rowCount: " << spreadsheet->rowCount()); DEBUG(" Setting data .."); for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) spreadsheet->child(n)->setColumnMode(columnModes[n]); switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } DEBUG(" Prepared!"); } qint64 bytesread = 0; #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportTotal: "); #endif LiveDataSource::ReadingType readingType; if (!m_prepared) { readingType = LiveDataSource::ReadingType::TillEnd; } else { //we have to read all the data when reading from end //so we set readingType to TillEnd if (spreadsheet->readingType() == LiveDataSource::ReadingType::FromEnd) readingType = LiveDataSource::ReadingType::TillEnd; //if we read the whole file we just start from the beginning of it //and read till end else if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) readingType = LiveDataSource::ReadingType::TillEnd; else readingType = spreadsheet->readingType(); } DEBUG(" Reading type = " << ENUM_TO_STRING(LiveDataSource, ReadingType, readingType)); //move to the last read position, from == total bytes read //since the other source types are sequential we cannot seek on them if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) device.seek(from); //count the new lines, increase actualrows on each //now we read all the new lines, if we want to use sample rate //then here we can do it, if we have actually sample rate number of lines :-? int newLinesForSampleSizeNotTillEnd = 0; int newLinesTillEnd = 0; QVector newData; if (readingType != LiveDataSource::ReadingType::TillEnd) newData.resize(spreadsheet->sampleSize()); int newDataIdx = 0; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportReadingFromFile: "); #endif DEBUG(" source type = " << ENUM_TO_STRING(LiveDataSource, SourceType, spreadsheet->sourceType())); while (!device.atEnd()) { if (readingType != LiveDataSource::ReadingType::TillEnd) { switch (spreadsheet->sourceType()) { // different sources need different read methods case LiveDataSource::SourceType::LocalSocket: newData[newDataIdx++] = device.readAll(); break; case LiveDataSource::SourceType::NetworkUdpSocket: newData[newDataIdx++] = device.read(device.bytesAvailable()); break; case LiveDataSource::SourceType::FileOrPipe: newData.push_back(device.readLine()); break; case LiveDataSource::SourceType::NetworkTcpSocket: //TODO: check serial port case LiveDataSource::SourceType::SerialPort: newData[newDataIdx++] = device.read(device.bytesAvailable()); break; case LiveDataSource::SourceType::MQTT: break; } } else { // ReadingType::TillEnd switch (spreadsheet->sourceType()) { // different sources need different read methods case LiveDataSource::SourceType::LocalSocket: newData.push_back(device.readAll()); break; case LiveDataSource::SourceType::NetworkUdpSocket: newData.push_back(device.read(device.bytesAvailable())); break; case LiveDataSource::SourceType::FileOrPipe: newData.push_back(device.readLine()); break; case LiveDataSource::SourceType::NetworkTcpSocket: //TODO: check serial port case LiveDataSource::SourceType::SerialPort: newData.push_back(device.read(device.bytesAvailable())); break; case LiveDataSource::SourceType::MQTT: break; } } newLinesTillEnd++; if (readingType != LiveDataSource::ReadingType::TillEnd) { newLinesForSampleSizeNotTillEnd++; //for Continuous reading and FromEnd we read sample rate number of lines if possible //here TillEnd and Whole file behave the same if (newLinesForSampleSizeNotTillEnd == spreadsheet->sampleSize()) break; } } QDEBUG(" data read: " << newData); } //now we reset the readingType if (spreadsheet->readingType() == LiveDataSource::ReadingType::FromEnd) readingType = spreadsheet->readingType(); //we had less new lines than the sample size specified if (readingType != LiveDataSource::ReadingType::TillEnd) QDEBUG(" Removed empty lines: " << newData.removeAll(QString())); //back to the last read position before counting when reading from files if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) device.seek(from); const int spreadsheetRowCountBeforeResize = spreadsheet->rowCount(); int currentRow = 0; // indexes the position in the vector(column) int linesToRead = 0; int keepNValues = spreadsheet->keepNValues(); DEBUG(" Increase row count. keepNValues = " << keepNValues); if (m_prepared) { //increase row count if we don't have a fixed size //but only after the preparation step if (keepNValues == 0) { DEBUG(" keep All values"); if (readingType != LiveDataSource::ReadingType::TillEnd) m_actualRows += qMin(newData.size(), spreadsheet->sampleSize()); else { //we don't increase it if we reread the whole file, we reset it if (!(spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile)) m_actualRows += newData.size(); else m_actualRows = newData.size(); } //appending if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) linesToRead = m_actualRows; else linesToRead = m_actualRows - spreadsheetRowCountBeforeResize; } else { // fixed size DEBUG(" keep " << keepNValues << " values"); if (readingType == LiveDataSource::ReadingType::TillEnd) { //we had more lines than the fixed size, so we read m_actualRows number of lines if (newLinesTillEnd > m_actualRows) { linesToRead = m_actualRows; //TODO after reading we should skip the next data lines //because it's TillEnd actually } else linesToRead = newLinesTillEnd; } else { //we read max sample size number of lines when the reading mode //is ContinuouslyFixed or FromEnd, WholeFile is disabled linesToRead = qMin(spreadsheet->sampleSize(), newLinesTillEnd); } } if (linesToRead == 0) return 0; } else // not prepared linesToRead = newLinesTillEnd; DEBUG(" lines to read = " << linesToRead); DEBUG(" actual rows (w/o header) = " << m_actualRows); //TODO // if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe || spreadsheet->sourceType() == LiveDataSource::SourceType::NetworkUdpSocket) { // if (m_actualRows < linesToRead) { // DEBUG(" SET lines to read to " << m_actualRows); // linesToRead = m_actualRows; // } // } //new rows/resize columns if we don't have a fixed size //TODO if the user changes this value..m_resizedToFixedSize..setResizedToFixedSize if (keepNValues == 0) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportResizing: "); #endif if (spreadsheet->rowCount() < m_actualRows) spreadsheet->setRowCount(m_actualRows); if (!m_prepared) currentRow = 0; else { // indexes the position in the vector(column) if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) currentRow = 0; else currentRow = spreadsheetRowCountBeforeResize; } // if we have fixed size, we do this only once in preparation, here we can use // m_prepared and we need something to decide whether it has a fixed size or increasing for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } else { // fixed size //when we have a fixed size we have to pop sampleSize number of lines if specified //here popping, setting currentRow if (!m_prepared) { if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) currentRow = 0; else currentRow = m_actualRows - qMin(newLinesTillEnd, m_actualRows); } else { if (readingType == LiveDataSource::ReadingType::TillEnd) { if (newLinesTillEnd > m_actualRows) { currentRow = 0; } else { if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile) currentRow = 0; else currentRow = m_actualRows - newLinesTillEnd; } } else { //we read max sample size number of lines when the reading mode //is ContinuouslyFixed or FromEnd currentRow = m_actualRows - qMin(spreadsheet->sampleSize(), newLinesTillEnd); } } if (m_prepared) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportPopping: "); #endif // enable data change signal for (int col = 0; col < m_actualCols; ++col) spreadsheet->child(col)->setSuppressDataChangedSignal(false); for (int row = 0; row < linesToRead; ++row) { for (int col = 0; col < m_actualCols; ++col) { switch (columnModes[col]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } } } // from the last row we read the new data in the spreadsheet DEBUG(" Reading from line " << currentRow << " till end line " << newLinesTillEnd); DEBUG(" Lines to read:" << linesToRead <<", actual rows:" << m_actualRows << ", actual cols:" << m_actualCols); newDataIdx = 0; if (readingType == LiveDataSource::ReadingType::FromEnd) { if (m_prepared) { if (newData.size() > spreadsheet->sampleSize()) newDataIdx = newData.size() - spreadsheet->sampleSize(); //since we skip a couple of lines, we need to count those bytes too for (int i = 0; i < newDataIdx; ++i) bytesread += newData.at(i).size(); } } DEBUG(" newDataIdx: " << newDataIdx); static int indexColumnIdx = 1; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportFillingContainers: "); #endif int row = 0; if (readingType == LiveDataSource::ReadingType::TillEnd || (readingType == LiveDataSource::ReadingType::ContinuousFixed)) { if (headerEnabled) { if (!m_prepared) { row = 1; bytesread += newData.at(0).size(); } } } if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) { if (readingType == LiveDataSource::ReadingType::WholeFile) { if (headerEnabled) { row = 1; bytesread += newData.at(0).size(); } } } QLocale locale(numberFormat); for (; row < linesToRead; ++row) { DEBUG("\n Reading row " << row + 1 << " of " << linesToRead); QString line; if (readingType == LiveDataSource::ReadingType::FromEnd) line = newData.at(newDataIdx++); else line = newData.at(row); //when we read the whole file we don't care about the previous position //so we don't have to count those bytes if (readingType != LiveDataSource::ReadingType::WholeFile) { if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) { bytesread += line.size(); } } if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; QStringList lineStringList; // only FileOrPipe support multiple columns if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); else lineStringList << line; QDEBUG(" line = " << lineStringList << ", separator = \'" << m_separator << "\'"); - DEBUG(" Line bytes: " << line.size() << " line: " << line.toStdString()); + DEBUG(" Line bytes: " << line.size() << " line: " << STDSTRING(line)); if (simplifyWhitespacesEnabled) { for (int i = 0; i < lineStringList.size(); ++i) lineStringList[i] = lineStringList[i].simplified(); } if (createIndexEnabled) { if (spreadsheet->keepNValues() == 0) lineStringList.prepend(QString::number(currentRow + 1)); else lineStringList.prepend(QString::number(indexColumnIdx++)); } QDEBUG(" column modes = " << columnModes); for (int n = 0; n < m_actualCols; ++n) { DEBUG(" actual col = " << n); if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); - DEBUG(" value string = " << valueString.toStdString()); + DEBUG(" value string = " << STDSTRING(valueString)); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { DEBUG(" Numeric"); bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : nanValue); // qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } case AbstractColumn::Integer: { DEBUG(" Integer"); bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); // qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } case AbstractColumn::BigInt: { DEBUG(" BigInt"); bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); // qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } case AbstractColumn::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueString; break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } else { DEBUG(" missing columns in this line"); switch (columnModes[n]) { case AbstractColumn::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = nanValue; break; case AbstractColumn::Integer: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; case AbstractColumn::BigInt: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; case AbstractColumn::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow).clear(); break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } } currentRow++; } } if (m_prepared) { //notify all affected columns and plots about the changes PERFTRACE("AsciiLiveDataImport, notify affected columns and plots"); //determine the dependent plots QVector plots; for (int n = 0; n < m_actualCols; ++n) spreadsheet->column(n)->addUsedInPlots(plots); //suppress retransform in the dependent plots for (auto* plot : plots) plot->setSuppressDataChangedSignal(true); for (int n = 0; n < m_actualCols; ++n) spreadsheet->column(n)->setChanged(); //retransform the dependent plots for (auto* plot : plots) { plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } } else m_prepared = true; DEBUG("AsciiFilterPrivate::readFromLiveDevice() DONE"); return bytesread; } /*! reads the content of device \c device to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("AsciiFilterPrivate::readDataFromDevice(): dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode) << ", lines = " << lines); if (!m_prepared) { const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG("Device error = " << deviceError); return; } // matrix data has only one column mode if (dynamic_cast(dataSource)) { auto mode = columnModes[0]; //TODO: remove this when Matrix supports text type if (mode == AbstractColumn::Text) mode = AbstractColumn::Numeric; for (auto& c : columnModes) if (c != mode) c = mode; } m_columnOffset = dataSource->prepareImport(m_dataContainer, importMode, m_actualRows, m_actualCols, vectorNames, columnModes); m_prepared = true; } - DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); + DEBUG("locale = " << STDSTRING(QLocale::languageToString(numberFormat))); QLocale locale(numberFormat); // Read the data int currentRow = 0; // indexes the position in the vector(column) if (lines == -1) lines = m_actualRows; //skip data lines, if required DEBUG(" Skipping " << m_actualStartRow << " lines"); for (int i = 0; i < m_actualStartRow; ++i) device.readLine(); DEBUG(" Reading " << qMin(lines, m_actualRows) << " lines, " << m_actualCols << " columns"); if (qMin(lines, m_actualRows) == 0 || m_actualCols == 0) return; QString line; QString valueString; //Don't put the definition QStringList lineStringList outside of the for-loop, //the compiler doesn't seem to optimize the destructor of QList well enough in this case. lines = qMin(lines, m_actualRows); int progressIndex = 0; const float progressInterval = 0.01*lines; //update on every 1% only for (int i = 0; i < lines; ++i) { line = device.readLine(); // remove any newline line.remove(QLatin1Char('\n')); line.remove(QLatin1Char('\r')); if (removeQuotesEnabled) line.remove(QLatin1Char('"')); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; QStringList lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); -// DEBUG(" Line bytes: " << line.size() << " line: " << line.toStdString()); +// DEBUG(" Line bytes: " << line.size() << " line: " << STDSTRING(line)); if (simplifyWhitespacesEnabled) { for (int i = 0; i < lineStringList.size(); ++i) lineStringList[i] = lineStringList[i].simplified(); } // remove left white spaces if (skipEmptyParts) { for (int n = 0; n < lineStringList.size(); ++n) { valueString = lineStringList.at(n); if (!QString::compare(valueString, " ")) { lineStringList.removeAt(n); n--; } } } for (int n = 0; n < m_actualCols; ++n) { // index column if required if (n == 0 && createIndexEnabled) { static_cast*>(m_dataContainer[0])->operator[](currentRow) = i + 1; continue; } //column counting starts with 1, subtract 1 as well as another 1 for the index column if required int col = createIndexEnabled ? n + startColumn - 2: n + startColumn - 1; if (col < lineStringList.size()) { valueString = lineStringList.at(col); // set value depending on data type switch (columnModes.at(n)) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : nanValue); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); break; } case AbstractColumn::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); break; } case AbstractColumn::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: { auto* colData = static_cast*>(m_dataContainer[n]); colData->operator[](currentRow) = valueString; break; } case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } else { // missing columns in this line switch (columnModes.at(n)) { case AbstractColumn::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = nanValue; break; case AbstractColumn::Integer: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; case AbstractColumn::BigInt: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; case AbstractColumn::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow).clear(); break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } } currentRow++; //ask to update the progress bar only if we have more than 1000 lines //only in 1% steps progressIndex++; if (lines > 1000 && progressIndex > progressInterval) { emit q->completed(100 * currentRow/lines); progressIndex = 0; QApplication::processEvents(QEventLoop::AllEvents, 0); } } DEBUG(" Read " << currentRow << " lines"); //we might have skipped empty lines above. shrink the spreadsheet if the number of read lines (=currentRow) //is smaller than the initial size of the spreadsheet (=m_actualRows). //TODO: should also be relevant for Matrix auto* s = dynamic_cast(dataSource); if (s && currentRow != m_actualRows && importMode == AbstractFileFilter::Replace) s->setRowCount(currentRow); dataSource->finalizeImport(m_columnOffset, startColumn, startColumn + m_actualCols - 1, dateTimeFormat, importMode); } /*! * preview for special devices (local/UDP/TCP socket or serial port) */ QVector AsciiFilterPrivate::preview(QIODevice &device) { DEBUG("AsciiFilterPrivate::preview(): bytesAvailable = " << device.bytesAvailable() << ", isSequential = " << device.isSequential()); QVector dataStrings; if (!(device.bytesAvailable() > 0)) { DEBUG("No new data available"); return dataStrings; } if (device.isSequential() && device.bytesAvailable() < (int)sizeof(quint16)) return dataStrings; #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportTotal: "); #endif int linesToRead = 0; QVector newData; //TODO: serial port "read(nBytes)"? while (!device.atEnd()) { if (device.canReadLine()) newData.push_back(device.readLine()); else // UDP fails otherwise newData.push_back(device.readAll()); linesToRead++; } QDEBUG(" data = " << newData); if (linesToRead == 0) return dataStrings; int col = 0; int colMax = newData.at(0).size(); if (createIndexEnabled) colMax++; columnModes.resize(colMax); if (createIndexEnabled) { columnModes[0] = AbstractColumn::ColumnMode::Integer; col = 1; vectorNames.prepend(i18n("Index")); } vectorNames.append(i18n("Value")); QDEBUG(" vector names = " << vectorNames); for (const auto& valueString : newData.at(0).split(' ', QString::SkipEmptyParts)) { if (col == colMax) break; columnModes[col++] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); } QString line; QLocale locale(numberFormat); QStringList lineString; for (int i = 0; i < linesToRead; ++i) { line = newData.at(i); // remove any newline line = line.remove('\n'); line = line.remove('\r'); if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; QStringList lineStringList = line.split(' ', QString::SkipEmptyParts); if (createIndexEnabled) lineStringList.prepend(QString::number(i + 1)); for (int n = 0; n < lineStringList.size(); ++n) { if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : nanValue, 'g', 16); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } case AbstractColumn::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } case AbstractColumn::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } case AbstractColumn::Text: lineString += valueString; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } else // missing columns in this line lineString += QString(); } dataStrings << lineString; } return dataStrings; } /*! * generates the preview for the file \c fileName reading the provided number of \c lines. */ QVector AsciiFilterPrivate::preview(const QString& fileName, int lines) { QVector dataStrings; //dirty hack: set readingFile and readingFileName in order to know in lineNumber(QIODevice) //that we're reading from a file and to benefit from much faster wc on linux //TODO: redesign the APIs and remove this later readingFile = true; readingFileName = fileName; KFilterDev device(fileName); const int deviceError = prepareDeviceToRead(device); readingFile = false; if (deviceError != 0) { DEBUG("Device error = " << deviceError); return dataStrings; } //number formatting - DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); + DEBUG("locale = " << STDSTRING(QLocale::languageToString(numberFormat))); QLocale locale(numberFormat); // Read the data if (lines == -1) lines = m_actualRows; // set column names for preview if (!headerEnabled) { int start = 0; if (createIndexEnabled) start = 1; for (int i = start; i < m_actualCols; i++) vectorNames << "Column " + QString::number(i + 1); } QDEBUG(" column names = " << vectorNames); //skip data lines, if required DEBUG(" Skipping " << m_actualStartRow << " lines"); for (int i = 0; i < m_actualStartRow; ++i) device.readLine(); DEBUG(" Generating preview for " << qMin(lines, m_actualRows) << " lines"); QString line; for (int i = 0; i < qMin(lines, m_actualRows); ++i) { line = device.readLine(); // remove any newline line = line.remove('\n'); line = line.remove('\r'); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; QStringList lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); QDEBUG(" line = " << lineStringList); - DEBUG(" Line bytes: " << line.size() << " line: " << line.toStdString()); + DEBUG(" Line bytes: " << line.size() << " line: " << STDSTRING(line)); if (simplifyWhitespacesEnabled) { for (int i = 0; i < lineStringList.size(); ++i) lineStringList[i] = lineStringList[i].simplified(); } QStringList lineString; for (int n = 0; n < m_actualCols; ++n) { // index column if required if (n == 0 && createIndexEnabled) { lineString += QString::number(i + 1); continue; } //column counting starts with 1, subtract 1 as well as another 1 for the index column if required int col = createIndexEnabled ? n + startColumn - 2: n + startColumn - 1; if (col < lineStringList.size()) { QString valueString = lineStringList.at(col); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); - //DEBUG(" valueString = " << valueString.toStdString()); + //DEBUG(" valueString = " << STDSTRING(valueString)); if (skipEmptyParts && !QString::compare(valueString, " ")) // handle left white spaces continue; // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : nanValue, 'g', 15); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } case AbstractColumn::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } case AbstractColumn::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } case AbstractColumn::Text: lineString += valueString; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } else // missing columns in this line lineString += QString(); } dataStrings << lineString; } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void AsciiFilterPrivate::write(const QString& fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: save data to ascii file } /*! * create datetime from \c string using \c format considering corner cases */ QDateTime AsciiFilterPrivate::parseDateTime(const QString& string, const QString& format) { - //DEBUG("string = " << string.toStdString() << ", format = " << format.toStdString()) + //DEBUG("string = " << STDSTRING(string) << ", format = " << STDSTRING(format)) QString fixedString(string); QString fixedFormat(format); if (!format.contains("yy")) { // no year given: set temporary to 2000 (must be a leap year to parse "Feb 29") fixedString.append(" 2000"); fixedFormat.append(" yyyy"); } QDateTime dateTime = QDateTime::fromString(fixedString, fixedFormat); //QDEBUG("fromString() =" << dateTime) // interpret 2-digit year smaller than 50 as 20XX if (dateTime.date().year() < 1950 && !format.contains("yyyy")) dateTime = dateTime.addYears(100); //QDEBUG("dateTime fixed =" << dateTime) - //DEBUG("dateTime.toString =" << dateTime.toString(format).toStdString()) + //DEBUG("dateTime.toString =" << STDSTRING(dateTime.toString(format))) return dateTime; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void AsciiFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "asciiFilter"); writer->writeAttribute( "commentCharacter", d->commentCharacter); writer->writeAttribute( "separatingCharacter", d->separatingCharacter); writer->writeAttribute( "autoMode", QString::number(d->autoModeEnabled)); writer->writeAttribute( "createIndex", QString::number(d->createIndexEnabled)); writer->writeAttribute( "createTimestamp", QString::number(d->createTimestampEnabled)); writer->writeAttribute( "header", QString::number(d->headerEnabled)); writer->writeAttribute( "vectorNames", d->vectorNames.join(' ')); writer->writeAttribute( "skipEmptyParts", QString::number(d->skipEmptyParts)); writer->writeAttribute( "simplifyWhitespaces", QString::number(d->simplifyWhitespacesEnabled)); writer->writeAttribute( "nanValue", QString::number(d->nanValue)); writer->writeAttribute( "removeQuotes", QString::number(d->removeQuotesEnabled)); writer->writeAttribute( "startRow", QString::number(d->startRow)); writer->writeAttribute( "endRow", QString::number(d->endRow)); writer->writeAttribute( "startColumn", QString::number(d->startColumn)); writer->writeAttribute( "endColumn", QString::number(d->endColumn)); writer->writeEndElement(); } /*! Loads from XML. */ bool AsciiFilter::load(XmlStreamReader* reader) { KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); QString str; READ_STRING_VALUE("commentCharacter", commentCharacter); READ_STRING_VALUE("separatingCharacter", separatingCharacter); READ_INT_VALUE("createIndex", createIndexEnabled, bool); READ_INT_VALUE("createTimestamp", createTimestampEnabled, bool); READ_INT_VALUE("autoMode", autoModeEnabled, bool); READ_INT_VALUE("header", headerEnabled, bool); str = attribs.value("vectorNames").toString(); d->vectorNames = str.split(' '); //may be empty READ_INT_VALUE("simplifyWhitespaces", simplifyWhitespacesEnabled, bool); READ_DOUBLE_VALUE("nanValue", nanValue); READ_INT_VALUE("removeQuotes", removeQuotesEnabled, bool); READ_INT_VALUE("skipEmptyParts", skipEmptyParts, bool); READ_INT_VALUE("startRow", startRow, int); READ_INT_VALUE("endRow", endRow, int); READ_INT_VALUE("startColumn", startColumn, int); READ_INT_VALUE("endColumn", endColumn, int); return true; } int AsciiFilterPrivate::isPrepared() { return m_prepared; } #ifdef HAVE_MQTT int AsciiFilterPrivate::prepareToRead(const QString& message) { QStringList lines = message.split('\n'); if (lines.isEmpty()) return 1; // Parse the first line: // Determine the number of columns, create the columns and use (if selected) the first row to name them QString firstLine = lines.at(0); if (simplifyWhitespacesEnabled) firstLine = firstLine.simplified(); - DEBUG("First line: \'" << firstLine.toStdString() << '\''); + DEBUG("First line: \'" << STDSTRING(firstLine) << '\''); // determine separator and split first line QStringList firstLineStringList; if (separatingCharacter == "auto") { DEBUG("automatic separator"); const QRegularExpression regExp(QStringLiteral("[,;:]?\\s+")); firstLineStringList = firstLine.split(regExp, (QString::SplitBehavior)skipEmptyParts); } else { // use given separator // replace symbolic "TAB" with '\t' m_separator = separatingCharacter.replace(QLatin1String("2xTAB"), "\t\t", Qt::CaseInsensitive); m_separator = separatingCharacter.replace(QLatin1String("TAB"), "\t", Qt::CaseInsensitive); // replace symbolic "SPACE" with ' ' m_separator = m_separator.replace(QLatin1String("2xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("3xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("4xSPACE"), QLatin1String(" "), Qt::CaseInsensitive); m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); firstLineStringList = firstLine.split(m_separator, (QString::SplitBehavior)skipEmptyParts); } - DEBUG("separator: \'" << m_separator.toStdString() << '\''); + DEBUG("separator: \'" << STDSTRING(m_separator) << '\''); DEBUG("number of columns: " << firstLineStringList.size()); QDEBUG("first line: " << firstLineStringList); //all columns are read plus the optional column for the index and for the timestamp m_actualCols = firstLineStringList.size() + int(createIndexEnabled) + int(createTimestampEnabled); //column names: //when reading the message strings for different topics, it's not possible to specify vector names //since the different topics can have different content and different number of columns/vectors //->we always set the vector names here to fixed values vectorNames.clear(); columnModes.clear(); //add index column if (createIndexEnabled) { vectorNames << i18n("index"); columnModes << AbstractColumn::Integer; } //add timestamp column if (createTimestampEnabled) { vectorNames << i18n("timestamp"); columnModes << AbstractColumn::DateTime; } //parse the first data line to determine data type for each column int i = 1; for (auto& valueString : firstLineStringList) { if (simplifyWhitespacesEnabled) valueString = valueString.simplified(); if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); vectorNames << i18n("value %1", i); columnModes << AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); ++i; } m_actualStartRow = startRow; m_actualRows = lines.size(); QDEBUG("column modes = " << columnModes); DEBUG("actual cols/rows (w/o header): " << m_actualCols << ' ' << m_actualRows); return 0; } /*! * generates the preview for the string \s message. */ QVector AsciiFilterPrivate::preview(const QString& message) { QVector dataStrings; prepareToRead(message); //number formatting - DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); + DEBUG("locale = " << STDSTRING(QLocale::languageToString(numberFormat))); QLocale locale(numberFormat); // Read the data QStringList lines = message.split('\n'); int i = 0; for (auto line : lines) { if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines continue; const QStringList& lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); QDEBUG(" line = " << lineStringList); QStringList lineString; // index column if required if (createIndexEnabled) lineString += QString::number(i + 1); // timestamp column if required if (createTimestampEnabled) lineString += QDateTime::currentDateTime().toString(); int offset = int(createIndexEnabled) + int(createTimestampEnabled); for (int n = 0; n < m_actualCols - offset; ++n) { if (n < lineStringList.size()) { QString valueString = lineStringList.at(n); - //DEBUG(" valueString = " << valueString.toStdString()); + //DEBUG(" valueString = " << STDSTRING(valueString)); if (skipEmptyParts && !QString::compare(valueString, " ")) // handle left white spaces continue; // set value depending on data type switch (columnModes[n+offset]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : nanValue, 'g', 15); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } case AbstractColumn::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); lineString += QString::number(isNumber ? value : 0); break; } case AbstractColumn::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } case AbstractColumn::Text: if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); lineString += valueString; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } else // missing columns in this line lineString += QString(); } ++i; dataStrings << lineString; } return dataStrings; } /*! * \brief Returns the statistical data that is needed by the topic for its MQTTClient's will message * \param topic */ QString AsciiFilterPrivate::MQTTColumnStatistics(const MQTTTopic* topic) const { Column* const tempColumn = topic->child(m_actualCols - 1); QString statistics; QVector willStatistics = topic->mqttClient()->willStatistics(); //Add every statistical data to the string, the flag of which is set true for (int i = 0; i <= willStatistics.size(); i++) { if (willStatistics[i]) { switch (static_cast(i) ) { case MQTTClient::WillStatisticsType::ArithmeticMean: statistics += QLatin1String("Arithmetic mean: ") + QString::number(tempColumn->statistics().arithmeticMean) + "\n"; break; case MQTTClient::WillStatisticsType::ContraharmonicMean: statistics += QLatin1String("Contraharmonic mean: ") + QString::number(tempColumn->statistics().contraharmonicMean) + "\n"; break; case MQTTClient::WillStatisticsType::Entropy: statistics += QLatin1String("Entropy: ") + QString::number(tempColumn->statistics().entropy) + "\n"; break; case MQTTClient::WillStatisticsType::GeometricMean: statistics += QLatin1String("Geometric mean: ") + QString::number(tempColumn->statistics().geometricMean) + "\n"; break; case MQTTClient::WillStatisticsType::HarmonicMean: statistics += QLatin1String("Harmonic mean: ") + QString::number(tempColumn->statistics().harmonicMean) + "\n"; break; case MQTTClient::WillStatisticsType::Kurtosis: statistics += QLatin1String("Kurtosis: ") + QString::number(tempColumn->statistics().kurtosis) + "\n"; break; case MQTTClient::WillStatisticsType::Maximum: statistics += QLatin1String("Maximum: ") + QString::number(tempColumn->statistics().maximum) + "\n"; break; case MQTTClient::WillStatisticsType::MeanDeviation: statistics += QLatin1String("Mean deviation: ") + QString::number(tempColumn->statistics().meanDeviation) + "\n"; break; case MQTTClient::WillStatisticsType::MeanDeviationAroundMedian: statistics += QLatin1String("Mean deviation around median: ") + QString::number(tempColumn->statistics().meanDeviationAroundMedian) + "\n"; break; case MQTTClient::WillStatisticsType::Median: statistics += QLatin1String("Median: ") + QString::number(tempColumn->statistics().median) + "\n"; break; case MQTTClient::WillStatisticsType::MedianDeviation: statistics += QLatin1String("Median deviation: ") + QString::number(tempColumn->statistics().medianDeviation) + "\n"; break; case MQTTClient::WillStatisticsType::Minimum: statistics += QLatin1String("Minimum: ") + QString::number(tempColumn->statistics().minimum) + "\n"; break; case MQTTClient::WillStatisticsType::Skewness: statistics += QLatin1String("Skewness: ") + QString::number(tempColumn->statistics().skewness) + "\n"; break; case MQTTClient::WillStatisticsType::StandardDeviation: statistics += QLatin1String("Standard deviation: ") + QString::number(tempColumn->statistics().standardDeviation) + "\n"; break; case MQTTClient::WillStatisticsType::Variance: statistics += QLatin1String("Variance: ") + QString::number(tempColumn->statistics().variance) + "\n"; break; case MQTTClient::WillStatisticsType::NoStatistics: default: break; } } } return statistics; } AbstractColumn::ColumnMode AsciiFilterPrivate::MQTTColumnMode() const { return columnModes[m_actualCols - 1]; } /*! * \brief reads the content of a message received by the topic. * Uses the settings defined in the MQTTTopic's MQTTClient * \param message * \param topic * \param dataSource */ void AsciiFilterPrivate::readMQTTTopic(const QString& message, AbstractDataSource* dataSource) { //If the message is empty, there is nothing to do if (message.isEmpty()) { DEBUG("No new data available"); return; } MQTTTopic* spreadsheet = dynamic_cast(dataSource); if (!spreadsheet) return; const int keepNValues = spreadsheet->mqttClient()->keepNValues(); if (!m_prepared) { - DEBUG("Start preparing filter for: " << spreadsheet->topicName().toStdString()); + DEBUG("Start preparing filter for: " << STDSTRING(spreadsheet->topicName())); //Prepare the filter const int mqttPrepareError = prepareToRead(message); if (mqttPrepareError != 0) { DEBUG("Mqtt Prepare Error = " << mqttPrepareError); return; } // prepare import for spreadsheet spreadsheet->setUndoAware(false); spreadsheet->resize(AbstractFileFilter::Replace, vectorNames, m_actualCols); //columns in a MQTTTopic don't have any manual changes. //make the available columns undo unaware and suppress the "data changed" signal. //data changes will be propagated via an explicit Column::setChanged() call once new data was read. for (int i = 0; i < spreadsheet->childCount(); i++) { spreadsheet->child(i)->setUndoAware(false); spreadsheet->child(i)->setSuppressDataChangedSignal(true); } if (keepNValues == 0) spreadsheet->setRowCount(m_actualRows > 1 ? m_actualRows : 1); else { spreadsheet->setRowCount(spreadsheet->mqttClient()->keepNValues()); m_actualRows = spreadsheet->mqttClient()->keepNValues(); } m_dataContainer.resize(m_actualCols); for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) spreadsheet->child(n)->setColumnMode(columnModes[n]); switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportTotal: "); #endif MQTTClient::ReadingType readingType; if (!m_prepared) { //if filter is not prepared we read till the end readingType = MQTTClient::ReadingType::TillEnd; } else { //we have to read all the data when reading from end //so we set readingType to TillEnd if (static_cast (spreadsheet->mqttClient()->readingType()) == MQTTClient::ReadingType::FromEnd) readingType = MQTTClient::ReadingType::TillEnd; else readingType = spreadsheet->mqttClient()->readingType(); } //count the new lines, increase actualrows on each //now we read all the new lines, if we want to use sample rate //then here we can do it, if we have actually sample rate number of lines :-? int newLinesForSampleSizeNotTillEnd = 0; int newLinesTillEnd = 0; QVector newData; if (readingType != MQTTClient::ReadingType::TillEnd) { newData.reserve(spreadsheet->mqttClient()->sampleSize()); newData.resize(spreadsheet->mqttClient()->sampleSize()); } int newDataIdx = 0; //TODO: bool sampleSizeReached = false; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportReadingFromFile: "); #endif const QStringList newDataList = message.split(QRegularExpression(QStringLiteral("\n|\r\n|\r")), QString::SkipEmptyParts); for (auto& line : newDataList) { newData.push_back(line); newLinesTillEnd++; if (readingType != MQTTClient::ReadingType::TillEnd) { newLinesForSampleSizeNotTillEnd++; //for Continuous reading and FromEnd we read sample rate number of lines if possible if (newLinesForSampleSizeNotTillEnd == spreadsheet->mqttClient()->sampleSize()) { //TODO: sampleSizeReached = true; break; } } } } qDebug()<<"Processing message done"; //now we reset the readingType if (spreadsheet->mqttClient()->readingType() == MQTTClient::ReadingType::FromEnd) readingType = static_cast(spreadsheet->mqttClient()->readingType()); //we had less new lines than the sample rate specified if (readingType != MQTTClient::ReadingType::TillEnd) qDebug() << "Removed empty lines: " << newData.removeAll(QString()); const int spreadsheetRowCountBeforeResize = spreadsheet->rowCount(); if (m_prepared ) { if (keepNValues == 0) m_actualRows = spreadsheetRowCountBeforeResize; else { //if the keepNValues changed since the last read we have to manage the columns accordingly if (m_actualRows != spreadsheet->mqttClient()->keepNValues()) { if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { spreadsheet->setRowCount(spreadsheet->mqttClient()->keepNValues()); qDebug()<<"rowcount set to: " << spreadsheet->mqttClient()->keepNValues(); } //Calculate the difference between the old and new keepNValues int rowDiff = 0; if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) rowDiff = m_actualRows - spreadsheet->mqttClient()->keepNValues(); if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) rowDiff = spreadsheet->mqttClient()->keepNValues() - m_actualRows; for (int n = 0; n < columnModes.size(); ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with NaN if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i) = nanValue; } break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with 0 if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i) = 0; } break; } case AbstractColumn::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with 0 if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i) = 0; } break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with empty lines if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i).clear(); } break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); m_dataContainer[n] = static_cast(vector); //if the keepNValues got smaller then we move the last keepNValues count of data //in the first keepNValues places if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) { for (int i = 0; i < spreadsheet->mqttClient()->keepNValues(); i++) { static_cast*>(m_dataContainer[n])->operator[] (i) = static_cast*>(m_dataContainer[n])->operator[](m_actualRows - spreadsheet->mqttClient()->keepNValues() + i); } } //if the keepNValues got bigger we move the existing values to the last m_actualRows positions //then fill the remaining lines with null datetime if (m_actualRows < spreadsheet->mqttClient()->keepNValues()) { vector->reserve( spreadsheet->mqttClient()->keepNValues()); vector->resize( spreadsheet->mqttClient()->keepNValues()); for (int i = 1; i <= m_actualRows; i++) { static_cast*>(m_dataContainer[n])->operator[] (spreadsheet->mqttClient()->keepNValues() - i) = static_cast*>(m_dataContainer[n])->operator[](spreadsheet->mqttClient()->keepNValues() - i - rowDiff); } for (int i = 0; i < rowDiff; i++) static_cast*>(m_dataContainer[n])->operator[](i) = QDateTime(); } break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } //if the keepNValues got smaller resize the spreadsheet if (m_actualRows > spreadsheet->mqttClient()->keepNValues()) spreadsheet->setRowCount(spreadsheet->mqttClient()->keepNValues()); //set the new row count m_actualRows = spreadsheet->mqttClient()->keepNValues(); qDebug()<<"actual rows: "<mqttClient()->sampleSize()); else { m_actualRows += newData.size(); } } //fixed size if (keepNValues != 0) { if (readingType == MQTTClient::ReadingType::TillEnd) { //we had more lines than the fixed size, so we read m_actualRows number of lines if (newLinesTillEnd > m_actualRows) { linesToRead = m_actualRows; } else linesToRead = newLinesTillEnd; } else { //we read max sample size number of lines when the reading mode //is ContinuouslyFixed or FromEnd if (spreadsheet->mqttClient()->sampleSize() <= spreadsheet->mqttClient()->keepNValues()) linesToRead = qMin(spreadsheet->mqttClient()->sampleSize(), newLinesTillEnd); else linesToRead = qMin(spreadsheet->mqttClient()->keepNValues(), newLinesTillEnd); } } else linesToRead = m_actualRows - spreadsheetRowCountBeforeResize; if (linesToRead == 0) return; } else { if (keepNValues != 0) linesToRead = newLinesTillEnd > m_actualRows ? m_actualRows : newLinesTillEnd; else linesToRead = newLinesTillEnd; } qDebug()<<"linestoread = " << linesToRead; //new rows/resize columns if we don't have a fixed size if (keepNValues == 0) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportResizing: "); #endif if (spreadsheet->rowCount() < m_actualRows) spreadsheet->setRowCount(m_actualRows); if (!m_prepared) currentRow = 0; else { // indexes the position in the vector(column) currentRow = spreadsheetRowCountBeforeResize; } // if we have fixed size, we do this only once in preparation, here we can use // m_prepared and we need something to decide whether it has a fixed size or increasing for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } else { //when we have a fixed size we have to pop sampleSize number of lines if specified //here popping, setting currentRow if (!m_prepared) currentRow = m_actualRows - qMin(newLinesTillEnd, m_actualRows); else { if (readingType == MQTTClient::ReadingType::TillEnd) { if (newLinesTillEnd > m_actualRows) currentRow = 0; else currentRow = m_actualRows - newLinesTillEnd; } else { //we read max sample rate number of lines when the reading mode //is ContinuouslyFixed or FromEnd currentRow = m_actualRows - linesToRead; } } if (m_prepared) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportPopping: "); #endif for (int row = 0; row < linesToRead; ++row) { for (int col = 0; col < m_actualCols; ++col) { switch (columnModes[col]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::BigInt: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } } } // from the last row we read the new data in the spreadsheet qDebug() << "reading from line: " << currentRow << " lines till end: " << newLinesTillEnd; qDebug() << "Lines to read: " << linesToRead <<" actual rows: " << m_actualRows; newDataIdx = 0; //From end means that we read the last sample size amount of data if (readingType == MQTTClient::ReadingType::FromEnd) { if (m_prepared) { if (newData.size() > spreadsheet->mqttClient()->sampleSize()) newDataIdx = newData.size() - spreadsheet->mqttClient()->sampleSize(); } } qDebug() << "newDataIdx: " << newDataIdx; //read the data static int indexColumnIdx = 0; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportFillingContainers: "); #endif int row = 0; QLocale locale(numberFormat); for (; row < linesToRead; ++row) { QString line; if (readingType == MQTTClient::ReadingType::FromEnd) line = newData.at(newDataIdx++); else line = newData.at(row); if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) continue; //add index if required int offset = 0; if (createIndexEnabled) { int index = (keepNValues != 0) ? indexColumnIdx++ : currentRow; static_cast*>(m_dataContainer[0])->operator[](currentRow) = index; ++offset; } //add current timestamp if required if (createTimestampEnabled) { static_cast*>(m_dataContainer[offset])->operator[](currentRow) = QDateTime::currentDateTime(); ++offset; } //parse the columns QStringList lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts); qDebug()<<"########################################################################"; qDebug()<*>(m_dataContainer[col])->operator[](currentRow) = (isNumber ? value : nanValue); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[col])->operator[](currentRow) = (isNumber ? value : 0); break; } case AbstractColumn::BigInt: { bool isNumber; const qint64 value = locale.toLongLong(valueString, &isNumber); static_cast*>(m_dataContainer[col])->operator[](currentRow) = (isNumber ? value : 0); break; } case AbstractColumn::DateTime: { QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat); static_cast*>(m_dataContainer[col])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: if (removeQuotesEnabled) valueString.remove(QLatin1Char('"')); static_cast*>(m_dataContainer[col])->operator[](currentRow) = valueString; break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } else { DEBUG(" missing columns in this line"); switch (columnModes[n]) { case AbstractColumn::Numeric: static_cast*>(m_dataContainer[col])->operator[](currentRow) = nanValue; break; case AbstractColumn::Integer: static_cast*>(m_dataContainer[col])->operator[](currentRow) = 0; break; case AbstractColumn::BigInt: static_cast*>(m_dataContainer[col])->operator[](currentRow) = 0; break; case AbstractColumn::DateTime: static_cast*>(m_dataContainer[col])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: static_cast*>(m_dataContainer[col])->operator[](currentRow).clear(); break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } } currentRow++; } } if (m_prepared) { //notify all affected columns and plots about the changes PERFTRACE("AsciiLiveDataImport, notify affected columns and plots"); const Project* project = spreadsheet->project(); QVector curves = project->children(AbstractAspect::Recursive); QVector plots; for (int n = 0; n < m_actualCols; ++n) { Column* column = spreadsheet->column(n); //determine the plots where the column is consumed for (const auto* curve : curves) { if (curve->xColumn() == column || curve->yColumn() == column) { CartesianPlot* plot = static_cast(curve->parentAspect()); if (plots.indexOf(plot) == -1) { plots << plot; plot->setSuppressDataChangedSignal(true); } } } column->setChanged(); } //loop over all affected plots and retransform them for (auto* const plot : plots) { //TODO setting this back to true triggers again a lot of retransforms in the plot (one for each curve). // plot->setSuppressDataChangedSignal(false); plot->dataChanged(); } } else m_prepared = true; DEBUG("AsciiFilterPrivate::readFromMQTTTopic() DONE"); } /*! * \brief After the MQTTTopic was loaded, the filter is prepared for reading * \param prepared * \param topic * \param separator */ void AsciiFilterPrivate::setPreparedForMQTT(bool prepared, MQTTTopic* topic, const QString& separator) { m_prepared = prepared; //If originally it was prepared we have to restore the settings if (prepared) { m_separator = separator; m_actualCols = endColumn - startColumn + 1; m_actualRows = topic->rowCount(); //set the column modes columnModes.resize(topic->columnCount()); for (int i = 0; i < topic->columnCount(); ++i) { columnModes[i] = topic->column(i)->columnMode(); } //set the data containers m_dataContainer.resize(m_actualCols); for (int n = 0; n < m_actualCols; ++n) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) topic->child(n)->setColumnMode(columnModes[n]); switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::BigInt: { QVector* vector = static_cast* >(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(topic->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } } #endif /*! * \brief Returns the separator used by the filter * \return */ QString AsciiFilterPrivate::separator() const { return m_separator; } diff --git a/src/backend/datasources/filters/BinaryFilter.cpp b/src/backend/datasources/filters/BinaryFilter.cpp index f81f94071..5becc74dd 100644 --- a/src/backend/datasources/filters/BinaryFilter.cpp +++ b/src/backend/datasources/filters/BinaryFilter.cpp @@ -1,613 +1,613 @@ /*************************************************************************** File : BinaryFilter.cpp Project : LabPlot Description : Binary I/O-filter -------------------------------------------------------------------- Copyright : (C) 2015-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/BinaryFilterPrivate.h" #include "backend/datasources/AbstractDataSource.h" #include "backend/core/column/Column.h" #include #include #include #include #include /*! \class BinaryFilter \brief Manages the import/export of data organized as columns (vectors) from/to a binary file. \ingroup datasources */ BinaryFilter::BinaryFilter():AbstractFileFilter(Binary), d(new BinaryFilterPrivate(this)) {} BinaryFilter::~BinaryFilter() = default; /*! reads the content of the file \c fileName. */ void BinaryFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } /*! reads the content of the device \c device. */ void BinaryFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromDevice(device, dataSource, importMode, lines); } QVector BinaryFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void BinaryFilter::write(const QString & fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /*! returns the list of all predefined data formats. */ QStringList BinaryFilter::dataTypes() { return (QStringList() <<"int8 (8 bit signed integer)" <<"int16 (16 bit signed integer)" <<"int32 (32 bit signed integer)" <<"int64 (64 bit signed integer)" <<"uint8 (8 bit unsigned integer)" <<"uint16 (16 bit unsigned integer)" <<"uint32 (32 bit unsigned integer)" <<"uint64 (64 bit unsigned integer)" <<"real32 (single precision floats)" <<"real64 (double precision floats)" ); } /*! returns the size of the predefined data types */ int BinaryFilter::dataSize(BinaryFilter::DataType type) { std::array sizes = {1,2,4,8,1,2,4,8,4,8}; return sizes[(int)type]; } /*! returns the number of rows (length of vectors) in the file \c fileName. */ size_t BinaryFilter::rowNumber(const QString& fileName, const size_t vectors, const BinaryFilter::DataType type) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) return 0; size_t rows = 0; while (!device.atEnd()) { // one row for (size_t i = 0; i < vectors; ++i) { for (int j = 0; j < BinaryFilter::dataSize(type); ++j) device.read(1); } rows++; } return rows; } /////////////////////////////////////////////////////////////////////// /*! loads the predefined filter settings for \c filterName */ void BinaryFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void BinaryFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /////////////////////////////////////////////////////////////////////// void BinaryFilter::setVectors(const size_t v) { d->vectors = v; } size_t BinaryFilter::vectors() const { return d->vectors; } void BinaryFilter::setDataType(const BinaryFilter::DataType t) { d->dataType = t; } BinaryFilter::DataType BinaryFilter::dataType() const { return d->dataType; } void BinaryFilter::setByteOrder(const QDataStream::ByteOrder b) { d->byteOrder = b; } QDataStream::ByteOrder BinaryFilter::byteOrder() const { return d->byteOrder; } void BinaryFilter::setSkipStartBytes(const size_t s) { d->skipStartBytes = s; } size_t BinaryFilter::skipStartBytes() const { return d->skipStartBytes; } void BinaryFilter::setStartRow(const int s) { d->startRow = s; } int BinaryFilter::startRow() const { return d->startRow; } void BinaryFilter::setEndRow(const int e) { d->endRow = e; } int BinaryFilter::endRow() const { return d->endRow; } void BinaryFilter::setSkipBytes(const size_t s) { d->skipBytes = s; } size_t BinaryFilter::skipBytes() const { return d->skipBytes; } void BinaryFilter::setCreateIndexEnabled(bool b) { d->createIndexEnabled = b; } void BinaryFilter::setAutoModeEnabled(bool b) { d->autoModeEnabled = b; } bool BinaryFilter::isAutoModeEnabled() const { return d->autoModeEnabled; } QString BinaryFilter::fileInfoString(const QString& fileName) { DEBUG("BinaryFilter::fileInfoString()"); QString info; //TODO Q_UNUSED(fileName); return info; } //##################################################################### //################### Private implementation ########################## //##################################################################### BinaryFilterPrivate::BinaryFilterPrivate(BinaryFilter* owner) : q(owner) {} /*! reads the content of the device \c device to the data source \c dataSource or return as string for preview. Uses the settings defined in the data source. */ void BinaryFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { DEBUG("readDataFromFile()"); KFilterDev device(fileName); numRows = BinaryFilter::rowNumber(fileName, vectors, dataType); if (! device.open(QIODevice::ReadOnly)) { - DEBUG(" could not open file " << fileName.toStdString()); + DEBUG(" could not open file " << STDSTRING(fileName)); return; } readDataFromDevice(device, dataSource, importMode); } /*! * returns 1 if the current read position in the device is at the end and 0 otherwise. */ int BinaryFilterPrivate::prepareStreamToRead(QDataStream& in) { DEBUG("prepareStreamToRead()"); in.setByteOrder(byteOrder); // catch case that skipStartBytes or startRow is bigger than file if (skipStartBytes >= BinaryFilter::dataSize(dataType) * vectors * numRows || startRow > (int)numRows) return 1; // skip bytes at start for (size_t i = 0; i < skipStartBytes; ++i) { qint8 tmp; in >> tmp; } // skip until start row for (size_t i = 0; i < (startRow-1) * vectors; ++i) { for (int j = 0; j < BinaryFilter::dataSize(dataType); ++j) { qint8 tmp; in >> tmp; } } // set range of rows if (endRow == -1) m_actualRows = (int)numRows - startRow + 1; else if (endRow > (int)numRows - startRow + 1) m_actualRows = (int)numRows; else m_actualRows = endRow - startRow + 1; m_actualCols = (int)vectors; DEBUG("numRows = " << numRows); DEBUG("endRow = " << endRow); DEBUG("actual rows = " << m_actualRows); DEBUG("actual cols = " << m_actualCols); return 0; } /*! reads \c lines lines of the device \c device and return as string for preview. */ QVector BinaryFilterPrivate::preview(const QString& fileName, int lines) { - DEBUG("BinaryFilterPrivate::preview( " << fileName.toStdString() << ", " << lines << ")"); + DEBUG("BinaryFilterPrivate::preview( " << STDSTRING(fileName) << ", " << lines << ")"); QVector dataStrings; KFilterDev device(fileName); if (! device.open(QIODevice::ReadOnly)) return dataStrings << (QStringList() << i18n("could not open device")); numRows = BinaryFilter::rowNumber(fileName, vectors, dataType); QDataStream in(&device); const int deviceError = prepareStreamToRead(in); if (deviceError) return dataStrings << (QStringList() << i18n("data selection empty")); //TODO: support other modes columnModes.resize(m_actualCols); //TODO: use given names QStringList vectorNames; if (createIndexEnabled) vectorNames.prepend(i18n("Index")); if (lines == -1) lines = m_actualRows; // read data DEBUG("generating preview for " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(m_actualRows, lines); ++i) { QStringList lineString; //prepend the index if required if (createIndexEnabled) lineString << QString::number(i+1); for (int n = 0; n < m_actualCols; ++n) { //TODO: use ColumnMode when it supports all types switch (dataType) { case BinaryFilter::INT8: { qint8 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::INT16: { qint16 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::INT32: { qint32 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::INT64: { qint64 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::UINT8: { quint8 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::UINT16: { quint16 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::UINT32: { quint32 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::UINT64: { quint64 value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::REAL32: { float value; in >> value; lineString << QString::number(value); break; } case BinaryFilter::REAL64: { double value; in >> value; lineString << QString::number(value); break; } } } dataStrings << lineString; emit q->completed(100*i/m_actualRows); } return dataStrings; } /*! reads the content of the file \c fileName to the data source \c dataSource or return as string for preview. Uses the settings defined in the data source. */ void BinaryFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("BinaryFilterPrivate::readDataFromDevice()"); QDataStream in(&device); const int deviceError = prepareStreamToRead(in); if (deviceError) { dataSource->clear(); DEBUG("device error"); return; } if (createIndexEnabled) m_actualCols++; std::vector dataContainer; int columnOffset = 0; //TODO: support other modes columnModes.resize(m_actualCols); //TODO: use given names QStringList vectorNames; if (createIndexEnabled) { vectorNames.prepend(i18n("Index")); columnModes[0] = AbstractColumn::Integer; } columnOffset = dataSource->prepareImport(dataContainer, importMode, m_actualRows, m_actualCols, vectorNames, columnModes); if (lines == -1) lines = m_actualRows; // start column int startColumn = 0; if (createIndexEnabled) startColumn++; // read data DEBUG("reading " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(m_actualRows, lines); ++i) { DEBUG("reading row " << i); //prepend the index if required if (createIndexEnabled) static_cast*>(dataContainer[0])->operator[](i) = i+1; for (int n = startColumn; n < m_actualCols; ++n) { DEBUG("reading column " << n); //TODO: use ColumnMode when it supports all types switch (dataType) { case BinaryFilter::INT8: { qint8 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::INT16: { qint16 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::INT32: { qint32 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::INT64: { qint64 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::UINT8: { quint8 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::UINT16: { quint16 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::UINT32: { quint32 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::UINT64: { quint64 value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::REAL32: { float value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } case BinaryFilter::REAL64: { double value; in >> value; static_cast*>(dataContainer[n])->operator[](i) = value; break; } } } if (m_actualRows > 0) emit q->completed(100*i/m_actualRows); } dataSource->finalizeImport(columnOffset, 1, m_actualCols, QString(), importMode); } /*! writes the content of \c dataSource to the file \c fileName. */ void BinaryFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: writing binary files not supported yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void BinaryFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("binaryFilter"); writer->writeAttribute("vectors", QString::number(d->vectors) ); writer->writeAttribute("dataType", QString::number(d->dataType) ); writer->writeAttribute("byteOrder", QString::number(d->byteOrder) ); writer->writeAttribute("autoMode", QString::number(d->autoModeEnabled) ); writer->writeAttribute("startRow", QString::number(d->startRow) ); writer->writeAttribute("endRow", QString::number(d->endRow) ); writer->writeAttribute("skipStartBytes", QString::number(d->skipStartBytes) ); writer->writeAttribute("skipBytes", QString::number(d->skipBytes) ); writer->writeAttribute( "createIndex", QString::number(d->createIndexEnabled) ); writer->writeEndElement(); } /*! Loads from XML. */ bool BinaryFilter::load(XmlStreamReader* reader) { KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); // read attributes QString str = attribs.value("vectors").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("vectors").toString()); else d->vectors = (size_t)str.toULong(); str = attribs.value("dataType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("dataType").toString()); else d->dataType = (BinaryFilter::DataType) str.toInt(); str = attribs.value("byteOrder").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("byteOrder").toString()); else d->byteOrder = (QDataStream::ByteOrder) str.toInt(); str = attribs.value("autoMode").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("autoMode").toString()); else d->autoModeEnabled = str.toInt(); str = attribs.value("startRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("startRow").toString()); else d->startRow = str.toInt(); str = attribs.value("endRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("endRow").toString()); else d->endRow = str.toInt(); str = attribs.value("skipStartBytes").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("skipStartBytes").toString()); else d->skipStartBytes = (size_t)str.toULong(); str = attribs.value("skipBytes").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("skipBytes").toString()); else d->skipBytes = (size_t)str.toULong(); str = attribs.value("createIndex").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("createIndex").toString()); else d->createIndexEnabled = str.toInt(); return true; } diff --git a/src/backend/datasources/filters/FITSFilter.cpp b/src/backend/datasources/filters/FITSFilter.cpp index 93373d53d..ae2d16544 100644 --- a/src/backend/datasources/filters/FITSFilter.cpp +++ b/src/backend/datasources/filters/FITSFilter.cpp @@ -1,1604 +1,1604 @@ /*************************************************************************** File : FITSFilter.cpp Project : LabPlot Description : FITS I/O-filter -------------------------------------------------------------------- Copyright : (C) 2016 by Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "FITSFilter.h" #include "FITSFilterPrivate.h" #include "backend/core/column/Column.h" #include "backend/core/column/ColumnStringIO.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/matrix/MatrixModel.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/datasources/AbstractDataSource.h" #include "backend/matrix/Matrix.h" #include "commonfrontend/matrix/MatrixView.h" #include #include #include /*! \class FITSFilter * \brief Manages the import/export of data from/to a FITS file. * \since 2.2.0 * \ingroup datasources */ FITSFilter::FITSFilter():AbstractFileFilter(FITS), d(new FITSFilterPrivate(this)) {} FITSFilter::~FITSFilter() = default; void FITSFilter::readDataFromFile(const QString &fileName, AbstractDataSource *dataSource, AbstractFileFilter::ImportMode importMode) { d->readCHDU(fileName, dataSource, importMode); } QVector FITSFilter::readChdu(const QString &fileName, bool* okToMatrix, int lines) { return d->readCHDU(fileName, nullptr, AbstractFileFilter::Replace, okToMatrix, lines); } void FITSFilter::write(const QString &fileName, AbstractDataSource *dataSource) { d->writeCHDU(fileName, dataSource); } void FITSFilter::addNewKeyword(const QString &filename, const QList &keywords) { d->addNewKeyword(filename, keywords); } void FITSFilter::updateKeywords(const QString &fileName, const QList& originals, const QVector& updates) { d->updateKeywords(fileName, originals, updates); } void FITSFilter::deleteKeyword(const QString &fileName, const QList& keywords) { d->deleteKeyword(fileName, keywords); } void FITSFilter::addKeywordUnit(const QString &fileName, const QList &keywords) { d->addKeywordUnit(fileName, keywords); } void FITSFilter::removeExtensions(const QStringList &extensions) { d->removeExtensions(extensions); } void FITSFilter::parseHeader(const QString &fileName, QTableWidget *headerEditTable, bool readKeys, const QList &keys) { d->parseHeader(fileName, headerEditTable, readKeys, keys); } void FITSFilter::parseExtensions(const QString &fileName, QTreeWidget *tw, bool checkPrimary) { d->parseExtensions(fileName, tw, checkPrimary); } QList FITSFilter::chduKeywords(const QString &fileName) { return d->chduKeywords(fileName); } void FITSFilter::loadFilterSettings(const QString& fileName) { Q_UNUSED(fileName) } void FITSFilter::saveFilterSettings(const QString& fileName) const { Q_UNUSED(fileName) } /*! * \brief contains the {StandardKeywords \ MandatoryKeywords} keywords * \return A list of keywords */ QStringList FITSFilter::standardKeywords() { return QStringList() << QLatin1String("(blank)") << QLatin1String("CROTA") << QLatin1String("EQUINOX") << QLatin1String("NAXIS") << QLatin1String("TBCOL") << QLatin1String("TUNIT") << QLatin1String("AUTHOR") << QLatin1String("CRPIX") << QLatin1String("EXTEND") << QLatin1String("OBJECT") << QLatin1String("TDIM") << QLatin1String("TZERO") << QLatin1String("BITPIX") << QLatin1String("CRVAL") << QLatin1String("EXTLEVEL") << QLatin1String("OBSERVER") << QLatin1String("TDISP") << QLatin1String("XTENSION") << QLatin1String("BLANK") << QLatin1String("CTYPE") << QLatin1String("EXTNAME") << QLatin1String("ORIGIN") << QLatin1String("TELESCOP") << QLatin1String("BLOCKED") << QLatin1String("DATAMAX") << QLatin1String("EXTVER") << QLatin1String("BSCALE") << QLatin1String("DATAMIN") << QLatin1String("PSCAL") << QLatin1String("TFORM") << QLatin1String("BUNIT") << QLatin1String("DATE") << QLatin1String("GROUPS") << QLatin1String("PTYPE") << QLatin1String("THEAP") << QLatin1String("BZERO") << QLatin1String("DATE-OBS") << QLatin1String("HISTORY") << QLatin1String("PZERO") << QLatin1String("TNULL") << QLatin1String("CDELT") << QLatin1String("INSTRUME") << QLatin1String("REFERENC") << QLatin1String("TSCAL") << QLatin1String("COMMENT") << QLatin1String("EPOCH") << QLatin1String("NAXIS") << QLatin1String("SIMPLE") << QLatin1String("TTYPE"); } /*! * \brief Returns a list of keywords, that are mandatory for an image extension of a FITS file * see: * https://archive.stsci.edu/fits/fits_standard/node64.html * \return A list of keywords */ QStringList FITSFilter::mandatoryImageExtensionKeywords() { return QStringList() << QLatin1String("XTENSION") << QLatin1String("BITPIX") << QLatin1String("NAXIS") << QLatin1String("PCOUNT") << QLatin1String("GCOUNT") << QLatin1String("END"); } /*! * \brief Returns a list of keywords, that are mandatory for a table extension (ascii or bintable) * of a FITS file * see: * https://archive.stsci.edu/fits/fits_standard/node58.html * https://archive.stsci.edu/fits/fits_standard/node68.html * \return A list of keywords */ QStringList FITSFilter::mandatoryTableExtensionKeywords() { return QStringList() << QLatin1String("XTENSION") << QLatin1String("BITPIX") << QLatin1String("NAXIS") << QLatin1String("NAXIS1") << QLatin1String("NAXIS2") << QLatin1String("PCOUNT") << QLatin1String("GCOUNT") << QLatin1String("TFIELDS") << QLatin1String("END"); } /*! * \brief Returns a list of strings that represent units which are used for autocompletion when adding * keyword units to keywords * \return A list of strings that represent units */ QStringList FITSFilter::units() { return QStringList() << QLatin1String("m (Metre)") << QLatin1String("kg (Kilogram)") << QLatin1String("s (Second)") << QString("M☉ (Solar mass)") << QLatin1String("AU (Astronomical unit") << QLatin1String("l.y (Light year)") << QLatin1String("km (Kilometres") << QLatin1String("pc (Parsec)") << QLatin1String("K (Kelvin)") << QLatin1String("mol (Mole)") << QLatin1String("cd (Candela)"); } /*! * \brief Sets the startColumn to \a column * \param column the column to be set */ void FITSFilter::setStartColumn(const int column) { d->startColumn = column; } /*! * \brief Returns startColumn * \return The startColumn */ int FITSFilter::startColumn() const { return d->startColumn; } /*! * \brief Sets the endColumn to \a column * \param column the column to be set */ void FITSFilter::setEndColumn(const int column) { d->endColumn = column; } /*! * \brief Returns endColumn * \return The endColumn */ int FITSFilter::endColumn() const { return d->endColumn; } /*! * \brief Sets the startRow to \a row * \param row the row to be set */ void FITSFilter::setStartRow(const int row) { d->startRow = row; } /*! * \brief Returns startRow * \return The startRow */ int FITSFilter::startRow() const { return d->startRow; } /*! * \brief Sets the endRow to \a row * \param row the row to be set */ void FITSFilter::setEndRow(const int row) { d->endRow = row; } /*! * \brief Returns endRow * \return The endRow */ int FITSFilter::endRow() const { return d->endRow; } /*! * \brief Sets commentsAsUnits to \a commentsAsUnits * * This is used when spreadsheets are exported to FITS table extensions and comments are used as the * units of the table's columns. * \param commentsAsUnits */ void FITSFilter::setCommentsAsUnits(const bool commentsAsUnits) { d->commentsAsUnits = commentsAsUnits; } /*! * \brief Sets exportTo to \a exportTo * * This is used to decide whether the container should be exported to a FITS image or a FITS table * For an image \a exportTo should be 0, for a table 1 * \param exportTo */ void FITSFilter::setExportTo(const int exportTo) { d->exportTo = exportTo; } QString FITSFilter::fileInfoString(const QString& fileName) { const int imagesCount = FITSFilterPrivate::extensionNames(fileName).values(QLatin1String("IMAGES")).size(); QString info(i18n("Images: %1", QString::number(imagesCount))); info += QLatin1String("
"); const int tablesCount = FITSFilterPrivate::extensionNames(fileName).values(QLatin1String("TABLES")).size(); info += i18n("Tables: %1", QString::number(tablesCount)); return info; } //##################################################################### //################### Private implementation ########################## //##################################################################### FITSFilterPrivate::FITSFilterPrivate(FITSFilter* owner) : q(owner) {} /*! * \brief Read the current header data unit from file \a filename in data source \a dataSource in \a importMode import mode * \param fileName the name of the file to be read * \param dataSource the data source to be filled * \param importMode */ QVector FITSFilterPrivate::readCHDU(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, bool* okToMatrix, int lines) { - DEBUG("FITSFilterPrivate::readCHDU() file name = " << fileName.toStdString()); + DEBUG("FITSFilterPrivate::readCHDU() file name = " << STDSTRING(fileName)); QVector dataStrings; #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READONLY, &status)) { - DEBUG(" ERROR opening file " << fileName.toStdString()); + DEBUG(" ERROR opening file " << STDSTRING(fileName)); printError(status); return dataStrings; } int chduType; if (fits_get_hdu_type(m_fitsFile, &chduType, &status)) { printError(status); return dataStrings; } long actualRows; int actualCols; int columnOffset = 0; bool noDataSource = (dataSource == nullptr); if (chduType == IMAGE_HDU) { DEBUG("IMAGE_HDU"); int maxdim = 2; int bitpix; int naxis; long naxes[2]; if (fits_get_img_param(m_fitsFile, maxdim, &bitpix, &naxis, naxes, &status)) { printError(status); return dataStrings; } if (naxis == 0) return dataStrings; actualRows = naxes[1]; actualCols = naxes[0]; if (lines == -1) lines = actualRows; else { if (lines > actualRows) lines = actualRows; } if (endRow != -1) { if (!noDataSource) lines = endRow; } if (endColumn != -1) actualCols = endColumn; if (noDataSource) dataStrings.reserve(lines); int i = 0; int j = 0; if (startRow != 1) i = startRow; if (startColumn != 1) j = startColumn; const int jstart = j; //TODO: support other modes QVector columnModes; columnModes.resize(actualCols - j); QStringList vectorNames; std::vector dataContainer; if (!noDataSource) { dataContainer.reserve(actualCols - j); columnOffset = dataSource->prepareImport(dataContainer, importMode, lines - i, actualCols - j, vectorNames, columnModes); } long pixelCount = lines * actualCols; double* data = new double[pixelCount]; if (!data) { qDebug() << "Not enough memory for data"; return dataStrings; } if (fits_read_img(m_fitsFile, TDOUBLE, 1, pixelCount, nullptr, data, nullptr, &status)) { printError(status); return dataStrings << (QStringList() << QString("Error")); } int ii = 0; DEBUG(" Import " << lines << " lines"); for (; i < lines; ++i) { int jj = 0; QStringList line; line.reserve(actualCols - j); for (; j < actualCols; ++j) { if (noDataSource) line << QString::number(data[i*naxes[0] +j]); else static_cast*>(dataContainer[jj++])->operator[](ii) = data[i* naxes[0] + j]; } dataStrings << line; j = jstart; ii++; } delete[] data; if (dataSource) dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); fits_close_file(m_fitsFile, &status); return dataStrings; } else if ((chduType == ASCII_TBL) || (chduType == BINARY_TBL)) { DEBUG("ASCII_TBL or BINARY_TBL"); if (endColumn != -1) actualCols = endColumn; else fits_get_num_cols(m_fitsFile, &actualCols, &status); if (endRow != -1) actualRows = endRow; else fits_get_num_rows(m_fitsFile, &actualRows, &status); QStringList columnNames; QList columnsWidth; QStringList columnUnits; columnUnits.reserve(actualCols); columnsWidth.reserve(actualCols); columnNames.reserve(actualCols); int colWidth; char keyword[FLEN_KEYWORD]; char value[FLEN_VALUE]; int col = 1; if (startColumn != 1) { if (startColumn != 0) col = startColumn; } for (; col <= actualCols; ++col) { status = 0; fits_make_keyn("TTYPE", col, keyword, &status); fits_read_key(m_fitsFile, TSTRING, keyword, value, nullptr, &status); columnNames.append(QLatin1String(value)); fits_make_keyn("TUNIT", col, keyword, &status); fits_read_key(m_fitsFile, TSTRING, keyword, value, nullptr, &status); columnUnits.append(QLatin1String(value)); fits_get_col_display_width(m_fitsFile, col, &colWidth, &status); columnsWidth.append(colWidth); } status = 0; if (lines == -1) lines = actualRows; else if (lines > actualRows) lines = actualRows; if (endRow != -1) lines = endRow; QVector stringDataPointers; std::vector numericDataPointers; QList columnNumericTypes; int startCol = 0; if (startColumn != 1) startCol = startColumn; int startRrow = 0; if (startRow != 1) startRrow = startRow; columnNumericTypes.reserve(actualCols); int datatype; int c = 1; if (startColumn != 1) { if (startColumn != 0) c = startColumn; } QList matrixNumericColumnIndices; for (; c <= actualCols; ++c) { fits_get_coltype(m_fitsFile, c, &datatype, nullptr, nullptr, &status); switch (datatype) { case TSTRING: columnNumericTypes.append(false); break; case TSHORT: columnNumericTypes.append(true); break; case TLONG: columnNumericTypes.append(true); break; case TFLOAT: columnNumericTypes.append(true); break; case TDOUBLE: columnNumericTypes.append(true); break; case TLOGICAL: columnNumericTypes.append(false); break; case TBIT: columnNumericTypes.append(true); break; case TBYTE: columnNumericTypes.append(true); break; case TCOMPLEX: columnNumericTypes.append(true); break; default: columnNumericTypes.append(false); break; } if ((datatype != TSTRING) && (datatype != TLOGICAL)) matrixNumericColumnIndices.append(c); } if (noDataSource) *okToMatrix = matrixNumericColumnIndices.isEmpty() ? false : true; if (!noDataSource) { DEBUG("HAS DataSource"); auto* spreadsheet = dynamic_cast(dataSource); if (spreadsheet) { numericDataPointers.reserve(actualCols - startCol); stringDataPointers.reserve(actualCols - startCol); spreadsheet->setUndoAware(false); columnOffset = spreadsheet->resize(importMode, columnNames, actualCols - startCol); if (importMode == AbstractFileFilter::Replace) { spreadsheet->clear(); spreadsheet->setRowCount(lines - startRrow); } else { if (spreadsheet->rowCount() < (lines - startRrow)) spreadsheet->setRowCount(lines - startRrow); } DEBUG("Reading columns ..."); for (int n = 0; n < actualCols - startCol; ++n) { if (columnNumericTypes.at(n)) { spreadsheet->column(columnOffset+ n)->setColumnMode(AbstractColumn::Numeric); auto* datap = static_cast* >(spreadsheet->column(columnOffset+n)->data()); numericDataPointers.push_back(datap); if (importMode == AbstractFileFilter::Replace) datap->clear(); } else { spreadsheet->column(columnOffset+ n)->setColumnMode(AbstractColumn::Text); auto* list = static_cast(spreadsheet->column(columnOffset+n)->data()); stringDataPointers.push_back(list); if (importMode == AbstractFileFilter::Replace) list->clear(); } } DEBUG(" ... DONE"); stringDataPointers.squeeze(); } else { numericDataPointers.reserve(matrixNumericColumnIndices.size()); columnOffset = dataSource->prepareImport(numericDataPointers, importMode, lines - startRrow, matrixNumericColumnIndices.size()); } } int row = 1; if (startRow != 1) { if (startRow != 0) row = startRow; } int coll = 1; if (startColumn != 1) { if (startColumn != 0) coll = startColumn; } bool isMatrix = false; if (dynamic_cast(dataSource)) { isMatrix = true; coll = matrixNumericColumnIndices.first(); actualCols = matrixNumericColumnIndices.last(); if (importMode == AbstractFileFilter::Replace) { for (auto* col : numericDataPointers) static_cast*>(col)->clear(); } } char array[FLEN_VALUE]; char* tmpArr[1] = {array}; for (; row <= lines; ++row) { int numericixd = 0; int stringidx = 0; QStringList line; line.reserve(actualCols-coll); for (int col = coll; col <= actualCols; ++col) { if (isMatrix) { if (!matrixNumericColumnIndices.contains(col)) continue; } if (fits_read_col_str(m_fitsFile, col, row, 1, 1, nullptr, tmpArr, nullptr, &status)) printError(status); if (!noDataSource) { QString str = QString::fromLatin1(array); if (str.isEmpty()) { if (columnNumericTypes.at(col - 1)) static_cast*>(numericDataPointers[numericixd++])->push_back(0); else stringDataPointers[stringidx++]->append(QLatin1String("NULL")); } else { if (columnNumericTypes.at(col - 1)) static_cast*>(numericDataPointers[numericixd++])->push_back(str.toDouble()); else { if (!stringDataPointers.isEmpty()) stringDataPointers[stringidx++]->operator<<(str.simplified()); } } } else { QString tmpColstr = QString::fromLatin1(array); tmpColstr = tmpColstr.simplified(); if (tmpColstr.isEmpty()) line << QLatin1String("NULL"); else line << tmpColstr; } } dataStrings << line; } if (!noDataSource) dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); fits_close_file(m_fitsFile, &status); return dataStrings; } else qDebug() << "Incorrect header type"; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(fileName) Q_UNUSED(dataSource) Q_UNUSED(importMode) Q_UNUSED(okToMatrix) Q_UNUSED(lines) #endif return dataStrings; } /*! * \brief Export from data source \a dataSource to file \a fileName * \param fileName the name of the file to be exported to * \param dataSource the datasource whose data is exported */ void FITSFilterPrivate::writeCHDU(const QString &fileName, AbstractDataSource *dataSource) { #ifdef HAVE_FITS if (!fileName.endsWith(QLatin1String(".fits"))) return; int status = 0; bool existed = false; if (!QFile::exists(fileName)) { if (fits_create_file(&m_fitsFile, fileName.toLatin1(), &status)) { printError(status); qDebug() << fileName; return; } } else { if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } else existed = true; } Matrix* const matrix = dynamic_cast(dataSource); if (matrix) { //FITS image if (exportTo == 0) { long naxes[2] = { matrix->columnCount(), matrix->rowCount() }; if (fits_create_img(m_fitsFile, FLOAT_IMG, 2, naxes, &status)) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); return; } const long nelem = naxes[0] * naxes[1]; double* const array = new double[nelem]; const QVector >* const data = static_cast>*>(matrix->data()); for (int col = 0; col < naxes[0]; ++col) for (int row = 0; row < naxes[1]; ++row) array[row * naxes[0] + col] = data->at(row).at(col); if (fits_write_img(m_fitsFile, TDOUBLE, 1, nelem, array, &status )) { printError(status); status = 0; } fits_close_file(m_fitsFile, &status); delete[] array; //FITS table } else { const int nrows = matrix->rowCount(); const int tfields = matrix->columnCount(); QVector columnNames; columnNames.resize(tfields); columnNames.squeeze(); QVector tform; tform.resize(tfields); tform.squeeze(); //TODO: mode const QVector>* const matrixData = static_cast>*>(matrix->data()); const MatrixModel* matrixModel = static_cast(matrix->view())->model(); const int precision = matrix->precision(); for (int i = 0; i < tfields; ++i) { const QString& columnName = matrixModel->headerData(i, Qt::Horizontal).toString(); columnNames[i] = new char[columnName.size() + 1]; strcpy(columnNames[i], columnName.toLatin1().constData()); int maxSize = -1; for (int row = 0; row < nrows; ++row) { if (matrix->text(row, i).size() > maxSize) maxSize = matrix->text(row, i).size(); } QString tformn; if (precision > 0) { tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".") + QString::number(precision); } else tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".0"); tform[i] = new char[tformn.size() + 1]; strcpy(tform[i], tformn.toLatin1().constData()); } //TODO extension name containing[] ? int r = fits_create_tbl(m_fitsFile, ASCII_TBL, nrows, tfields, columnNames.data(), tform.data(), nullptr, matrix->name().toLatin1().constData(), &status); for (int i = 0; i < tfields; ++i) { delete[] tform[i]; delete[] columnNames[i]; } if (r) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } double* columnNumeric = new double[nrows]; for (int col = 1; col <= tfields; ++col) { const QVector& column = matrixData->at(col-1); for (int r = 0; r < column.size(); ++r) columnNumeric[r] = column.at(r); fits_write_col(m_fitsFile, TDOUBLE, col, 1, 1, nrows, columnNumeric, &status); if (status) { printError(status); delete[] columnNumeric; status = 0; if (!existed) { QFile file(fileName); file.remove(); } fits_close_file(m_fitsFile, &status); return; } } delete[] columnNumeric; fits_close_file(m_fitsFile, &status); } return; } auto* const spreadsheet = dynamic_cast(dataSource); if (spreadsheet) { //FITS image if (exportTo == 0) { int maxRowIdx = -1; //don't export lots of empty lines if all of those contain nans // TODO: option? for (int c = 0; c < spreadsheet->columnCount(); ++c) { const Column* const col = spreadsheet->column(c); int currMaxRoxIdx = -1; for (int r = col->rowCount(); r >= 0; --r) { if (col->isValid(r)) { currMaxRoxIdx = r; break; } } if (currMaxRoxIdx > maxRowIdx) { maxRowIdx = currMaxRoxIdx; } } long naxes[2] = { spreadsheet->columnCount(), maxRowIdx + 1}; if (fits_create_img(m_fitsFile, FLOAT_IMG, 2, naxes, &status)) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } const long nelem = naxes[0] * naxes[1]; double* array = new double[nelem]; for (int row = 0; row < naxes[1]; ++row) { for (int col = 0; col < naxes[0]; ++col) array[row * naxes[0] + col] = spreadsheet->column(col)->valueAt(row); } if (fits_write_img(m_fitsFile, TDOUBLE, 1, nelem, array, &status )) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } fits_close_file(m_fitsFile, &status); delete[] array; } else { const int nrows = spreadsheet->rowCount(); const int tfields = spreadsheet->columnCount(); QVector columnNames; columnNames.resize(tfields); columnNames.squeeze(); QVector tform; tform.resize(tfields); tform.squeeze(); QVector tunit; tunit.resize(tfields); tunit.squeeze(); for (int i = 0; i < tfields; ++i) { const Column* const column = spreadsheet->column(i); columnNames[i] = new char[column->name().size() + 1]; strcpy(columnNames[i], column->name().toLatin1().constData()); if (commentsAsUnits) { tunit[i] = new char[column->comment().size() + 1]; strcpy(tunit[i], column->comment().toLatin1().constData()); } else { tunit[i] = new char[1]; tunit[i][0] = '\0'; } switch (column->columnMode()) { case AbstractColumn::Numeric: { int maxSize = -1; for (int row = 0; row < nrows; ++row) { if (QString::number(column->valueAt(row)).size() > maxSize) maxSize = QString::number(column->valueAt(row)).size(); } const Double2StringFilter* const filter = static_cast(column->outputFilter()); bool decimals = false; for (int ii = 0; ii < nrows; ++ii) { bool ok; QString cell = column->asStringColumn()->textAt(ii); double val = cell.toDouble(&ok); if (cell.size() > QString::number(val).size() + 1) { decimals = true; break; } } QString tformn; if (decimals) { int maxStringSize = -1; for (int row = 0; row < nrows; ++row) { if (column->asStringColumn()->textAt(row).size() > maxStringSize) maxStringSize = column->asStringColumn()->textAt(row).size(); } const int diff = abs(maxSize - maxStringSize); maxSize+= diff; tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".") + QString::number(filter->numDigits()); } else tformn = QLatin1String("F")+ QString::number(maxSize) + QLatin1String(".0"); tform[i] = new char[tformn.size()]; strcpy(tform[i], tformn.toLatin1().data()); break; } case AbstractColumn::Text: { int maxSize = -1; for (int row = 0; row < nrows; ++row) { if (column->textAt(row).size() > maxSize) maxSize = column->textAt(row).size(); } const QString& tformn = QLatin1String("A") + QString::number(maxSize); tform[i] = new char[tformn.size()]; strcpy(tform[i], tformn.toLatin1().data()); break; } case AbstractColumn::Integer: //TODO case AbstractColumn::BigInt: case AbstractColumn::DateTime: case AbstractColumn::Day: case AbstractColumn::Month: break; } } //TODO extension name containing[] ? int r = fits_create_tbl(m_fitsFile, ASCII_TBL, nrows, tfields, columnNames.data(), tform.data(), tunit.data(), spreadsheet->name().toLatin1().constData(), &status); for (int i = 0; i < tfields; ++i) { delete[] tform[i]; delete[] tunit[i]; delete[] columnNames[i]; } if (r) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } QVector column; column.resize(nrows); column.squeeze(); double* columnNumeric = new double[nrows]; for (int col = 1; col <= tfields; ++col) { const Column* c = spreadsheet->column(col-1); AbstractColumn::ColumnMode columnMode = c->columnMode(); if (columnMode == AbstractColumn::Numeric) { for (int row = 0; row < nrows; ++row) columnNumeric[row] = c->valueAt(row); fits_write_col(m_fitsFile, TDOUBLE, col, 1, 1, nrows, columnNumeric, &status); if (status) { printError(status); delete[] columnNumeric; status = 0; fits_close_file(m_fitsFile, &status); if (!existed) { QFile file(fileName); file.remove(); } return; } } else { for (int row = 0; row < nrows; ++row) { column[row] = new char[c->textAt(row).size() + 1]; strcpy(column[row], c->textAt(row).toLatin1().constData()); } fits_write_col(m_fitsFile, TSTRING, col, 1, 1, nrows, column.data(), &status); for (int row = 0; row < nrows; ++row) delete[] column[row]; if (status) { printError(status); status = 0; fits_close_file(m_fitsFile, &status); delete[] columnNumeric; return; } } } delete[] columnNumeric; status = 0; fits_close_file(m_fitsFile, &status); } } #else Q_UNUSED(fileName) Q_UNUSED(dataSource) #endif } /*! * \brief Return a map of the available extensions names in file \a filename * The keys of the map are the extension types, the values are the names * \param fileName the name of the FITS file to be analyzed */ QMultiMap FITSFilterPrivate::extensionNames(const QString& fileName) { - DEBUG("FITSFilterPrivate::extensionNames() file name = " << fileName.toStdString()); + DEBUG("FITSFilterPrivate::extensionNames() file name = " << STDSTRING(fileName)); #ifdef HAVE_FITS QMultiMap extensions; int status = 0; fitsfile* fitsFile = nullptr; if (fits_open_file(&fitsFile, fileName.toLatin1(), READONLY, &status )) return QMultiMap(); int hduCount; if (fits_get_num_hdus(fitsFile, &hduCount, &status)) return QMultiMap(); int imageCount = 0; int asciiTableCount = 0; int binaryTableCount = 0; for (int currentHDU = 1; (currentHDU <= hduCount) && !status; ++currentHDU) { int hduType; status = 0; fits_get_hdu_type(fitsFile, &hduType, &status); switch (hduType) { case IMAGE_HDU: imageCount++; break; case ASCII_TBL: asciiTableCount++; break; case BINARY_TBL: binaryTableCount++; break; } char* keyVal = new char[FLEN_VALUE]; QString extName; if (!fits_read_keyword(fitsFile,"EXTNAME", keyVal, nullptr, &status)) { extName = QLatin1String(keyVal); extName = extName.mid(1, extName.length() -2).simplified(); } else { status = 0; if (!fits_read_keyword(fitsFile,"HDUNAME", keyVal, nullptr, &status)) { extName = QLatin1String(keyVal); extName = extName.mid(1, extName.length() -2).simplified(); } else { status = 0; switch (hduType) { case IMAGE_HDU: if (imageCount == 1) extName = i18n("Primary header"); else extName = i18n("IMAGE #%1", imageCount); break; case ASCII_TBL: extName = i18n("ASCII_TBL #%1", asciiTableCount); break; case BINARY_TBL: extName = i18n("BINARY_TBL #%1", binaryTableCount); break; } } } delete[] keyVal; status = 0; extName = extName.trimmed(); switch (hduType) { case IMAGE_HDU: extensions.insert(QLatin1String("IMAGES"), extName); break; case ASCII_TBL: extensions.insert(QLatin1String("TABLES"), extName); break; case BINARY_TBL: extensions.insert(QLatin1String("TABLES"), extName); break; } fits_movrel_hdu(fitsFile, 1, nullptr, &status); } if (status == END_OF_FILE) status = 0; fits_close_file(fitsFile, &status); return extensions; #else Q_UNUSED(fileName) return QMultiMap(); #endif } /*! * \brief Prints the error text corresponding to the status code \a status * \param status the status code of the error */ void FITSFilterPrivate::printError(int status) const { #ifdef HAVE_FITS if (status) { char errorText[FLEN_ERRMSG]; fits_get_errstatus(status, errorText ); qDebug() << QLatin1String(errorText); } #else Q_UNUSED(status) #endif } /*! * \brief Add the keywords \a keywords to the current header unit * \param keywords the keywords to be added * \param fileName the name of the FITS file (extension) in which the keywords are added */ void FITSFilterPrivate::addNewKeyword(const QString& fileName, const QList& keywords) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } for (const FITSFilter::Keyword& keyword: keywords) { status = 0; if (!keyword.key.compare(QLatin1String("COMMENT"))) { if (fits_write_comment(m_fitsFile, keyword.value.toLatin1(), &status)) printError(status); } else if (!keyword.key.compare(QLatin1String("HISTORY"))) { if (fits_write_history(m_fitsFile, keyword.value.toLatin1(), &status)) printError(status); } else if (!keyword.key.compare(QLatin1String("DATE"))) { if (fits_write_date(m_fitsFile, &status)) printError(status); } else { int ok = 0; if (keyword.key.length() <= FLEN_KEYWORD) { ok++; if (keyword.value.length() <= FLEN_VALUE) { ok++; if (keyword.comment.length() <= FLEN_COMMENT) ok++; } } if (ok == 3) { bool ok; double val = keyword.value.toDouble(&ok); if (ok) { if (fits_write_key(m_fitsFile, TDOUBLE, keyword.key.toLatin1().data(), &val, keyword.comment.toLatin1().data(), &status)) printError(status); } else { if (fits_write_key(m_fitsFile, TSTRING, keyword.key.toLatin1().data(), keyword.value.toLatin1().data(), keyword.comment.toLatin1().data(), &status)) printError(status); } } else if ( ok == 2) { //comment too long } else if ( ok == 1) { //value too long } else { //keyword too long } } } status = 0; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(keywords) Q_UNUSED(fileName) #endif } /*! * \brief Update keywords in the current header unit * \param fileName The name of the FITS file (extension) in which the keywords will be updated * \param originals The original keywords of the FITS file (extension) * \param updates The keywords that contain the updated values */ void FITSFilterPrivate::updateKeywords(const QString& fileName, const QList& originals, const QVector& updates) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } FITSFilter::Keyword updatedKeyword; FITSFilter::Keyword originalKeyword; FITSFilter::KeywordUpdate keywordUpdate; for (int i = 0; i < updates.size(); ++i) { updatedKeyword = updates.at(i); originalKeyword = originals.at(i); keywordUpdate = originals.at(i).updates; if (keywordUpdate.keyUpdated && keywordUpdate.valueUpdated && keywordUpdate.commentUpdated) { if (updatedKeyword.isEmpty()) { if (fits_delete_key(m_fitsFile, originalKeyword.key.toLatin1(), &status)) { printError(status); status = 0; } continue; } } if (!updatedKeyword.key.isEmpty()) { if (fits_modify_name(m_fitsFile, originalKeyword.key.toLatin1(), updatedKeyword.key.toLatin1(), &status )) { printError(status); status = 0; } } if (!updatedKeyword.value.isEmpty()) { bool ok; int intValue; double doubleValue; bool updated = false; doubleValue = updatedKeyword.value.toDouble(&ok); if (ok) { if (fits_update_key(m_fitsFile,TDOUBLE, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), &doubleValue, nullptr, &status)) printError(status); else updated = true; } if (!updated) { intValue = updatedKeyword.value.toInt(&ok); if (ok) { if (fits_update_key(m_fitsFile,TINT, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), &intValue, nullptr, &status)) printError(status); else updated = true; } } if (!updated) { if (fits_update_key(m_fitsFile,TSTRING, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), updatedKeyword.value.toLatin1().data(), nullptr, &status)) printError(status); } } else { if (keywordUpdate.valueUpdated) { if (fits_update_key_null(m_fitsFile, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), nullptr, &status)) { printError(status); status = 0; } } } if (!updatedKeyword.comment.isEmpty()) { if (fits_modify_comment(m_fitsFile, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), updatedKeyword.comment.toLatin1().data(), &status)) { printError(status); status = 0; } } else { if (keywordUpdate.commentUpdated) { if (fits_modify_comment(m_fitsFile, keywordUpdate.keyUpdated ? updatedKeyword.key.toLatin1() : originalKeyword.key.toLatin1(), QByteArray().constData(), &status)) { printError(status); status = 0; } } } } status = 0; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(fileName) Q_UNUSED(originals) Q_UNUSED(updates) #endif } /*! * \brief Delete the keywords \a keywords from the current header unit * \param fileName the name of the FITS file (extension) in which the keywords will be deleted. * \param keywords the keywords to deleted */ void FITSFilterPrivate::deleteKeyword(const QString& fileName, const QList &keywords) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } for (const auto& keyword : keywords) { if (!keyword.key.isEmpty()) { status = 0; if (fits_delete_key(m_fitsFile, keyword.key.toLatin1(), &status)) printError(status); } } status = 0; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(keywords) Q_UNUSED(fileName) #endif } /*! * \brief FITSFilterPrivate::addKeywordUnit * \param fileName the FITS file (extension) in which the keyword units are updated/added * \param keywords the keywords whose units were modified/added */ void FITSFilterPrivate::addKeywordUnit(const QString &fileName, const QList &keywords) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READWRITE, &status )) { printError(status); return; } for (const FITSFilter::Keyword& keyword : keywords) { if (keyword.updates.unitUpdated) { if (fits_write_key_unit(m_fitsFile, keyword.key.toLatin1(), keyword.unit.toLatin1().data(), &status)) { printError(status); status = 0; } } } status = 0; fits_close_file(m_fitsFile, &status); #else Q_UNUSED(fileName) Q_UNUSED(keywords) #endif } /*! * \brief Remove extensions from a FITS file * \param extensions The extensions to be removed from the FITS file */ void FITSFilterPrivate::removeExtensions(const QStringList &extensions) { #ifdef HAVE_FITS int status = 0; for (const auto& ext : extensions) { status = 0; if (fits_open_file(&m_fitsFile, ext.toLatin1(), READWRITE, &status )) { printError(status); continue; } if (fits_delete_hdu(m_fitsFile, nullptr, &status)) printError(status); status = 0; fits_close_file(m_fitsFile, &status); } #else Q_UNUSED(extensions) #endif } /*! * \brief Returns a list of keywords in the current header of \a fileName * \param fileName the name of the FITS file (extension) to be opened * \return A list of keywords */ QList FITSFilterPrivate::chduKeywords(const QString& fileName) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READONLY, &status )) { printError(status); return QList (); } int numberOfKeys; if (fits_get_hdrspace(m_fitsFile, &numberOfKeys, nullptr, &status)) { printError(status); return QList (); } QList keywords; keywords.reserve(numberOfKeys); char* key = new char[FLEN_KEYWORD]; char* value = new char[FLEN_VALUE]; char* comment = new char[FLEN_COMMENT]; char* unit = new char[FLEN_VALUE]; for (int i = 1; i <= numberOfKeys; ++i) { QStringList recordValues; FITSFilter::Keyword keyword; if (fits_read_keyn(m_fitsFile, i, key, value, comment, &status)) { printError(status); status = 0; continue; } fits_read_key_unit(m_fitsFile, key, unit, &status); recordValues << QLatin1String(key) << QLatin1String(value) << QLatin1String(comment) << QLatin1String(unit); keyword.key = recordValues[0].simplified(); keyword.value = recordValues[1].simplified(); keyword.comment = recordValues[2].simplified(); keyword.unit = recordValues[3].simplified(); keywords.append(keyword); } delete[] key; delete[] value; delete[] comment; delete[] unit; fits_close_file(m_fitsFile, &status); return keywords; #else Q_UNUSED(fileName) return QList(); #endif } /*! * \brief Builds the table \a headerEditTable from the keywords \a keys * \param fileName The name of the FITS file from which the keys are read if \a readKeys is \c true * \param headerEditTable The table to be built * \param readKeys It's used to determine whether the keywords are provided or they should be read from * file \a fileName * \param keys The keywords that are provided if the keywords were read already. */ void FITSFilterPrivate::parseHeader(const QString &fileName, QTableWidget *headerEditTable, bool readKeys, const QList& keys) { #ifdef HAVE_FITS QList keywords; if (readKeys) keywords = chduKeywords(fileName); else keywords = keys; headerEditTable->setRowCount(keywords.size()); QTableWidgetItem* item; for (int i = 0; i < keywords.size(); ++i) { const FITSFilter::Keyword& keyword = keywords.at(i); const bool mandatory = FITSFilter::mandatoryImageExtensionKeywords().contains(keyword.key) || FITSFilter::mandatoryTableExtensionKeywords().contains(keyword.key); item = new QTableWidgetItem(keyword.key); const QString& itemText = item->text(); const bool notEditableKey = mandatory || itemText.contains(QLatin1String("TFORM")) || itemText.contains(QLatin1String("TTYPE")) || itemText.contains(QLatin1String("TUNIT")) || itemText.contains(QLatin1String("TDISP")) || itemText.contains(QLatin1String("TBCOL")) || itemText.contains(QLatin1String("TZERO")); const bool notEditableValue = mandatory || itemText.contains(QLatin1String("TFORM")) || itemText.contains(QLatin1String("TDISP")) || itemText.contains(QLatin1String("TBCOL")) || itemText.contains(QLatin1String("TZERO")); if (notEditableKey) item->setFlags(item->flags() & ~Qt::ItemIsEditable); else item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); headerEditTable->setItem(i, 0, item ); item = new QTableWidgetItem(keyword.value); if (notEditableValue) item->setFlags(item->flags() & ~Qt::ItemIsEditable); else item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); headerEditTable->setItem(i, 1, item ); QString commentFieldText; if (!keyword.unit.isEmpty()) { if (keyword.updates.unitUpdated) { const QString& comment = keyword.comment.right(keyword.comment.size() - keyword.comment.indexOf(QChar(']'))-1); commentFieldText = QLatin1String("[") + keyword.unit + QLatin1String("] ") + comment; } else { if (keyword.comment.at(0) == QLatin1Char('[')) commentFieldText = keyword.comment; else commentFieldText = QLatin1String("[") + keyword.unit + QLatin1String("] ") + keyword.comment; } } else commentFieldText = keyword.comment; item = new QTableWidgetItem(commentFieldText); item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); headerEditTable->setItem(i, 2, item ); } headerEditTable->resizeColumnsToContents(); #else Q_UNUSED(fileName) Q_UNUSED(headerEditTable) Q_UNUSED(readKeys) Q_UNUSED(keys) #endif } /*! * \brief Helper function to return the value of the key \a key * \param fileName The name of the FITS file (extension) in which the keyword with key \a key should exist * \param key The key of the keyword whose value it's returned * \return The value of the keyword as a string */ const QString FITSFilterPrivate::valueOf(const QString& fileName, const char *key) { #ifdef HAVE_FITS int status = 0; if (fits_open_file(&m_fitsFile, fileName.toLatin1(), READONLY, &status )) { printError(status); return QString (); } char* keyVal = new char[FLEN_VALUE]; QString keyValue; if (!fits_read_keyword(m_fitsFile, key, keyVal, nullptr, &status)) { keyValue = QLatin1String(keyVal); keyValue = keyValue.simplified(); } else { printError(status); delete[] keyVal; fits_close_file(m_fitsFile, &status); return QString(); } delete[] keyVal; status = 0; fits_close_file(m_fitsFile, &status); return keyValue; #else Q_UNUSED(fileName) Q_UNUSED(key) return QString(); #endif } /*! * \brief Build the extensions tree from FITS file (extension) \a fileName * \param fileName The name of the FITS file to be opened * \param tw The QTreeWidget to be built * \param checkPrimary Used to determine whether the tree will be used for import or the header edit, * if it's \c true and if the primary array it's empty, then the item won't be added to the tree */ void FITSFilterPrivate::parseExtensions(const QString &fileName, QTreeWidget *tw, bool checkPrimary) { DEBUG("FITSFilterPrivate::parseExtensions()"); #ifdef HAVE_FITS const QMultiMap& extensions = extensionNames(fileName); const QStringList& imageExtensions = extensions.values(QLatin1String("IMAGES")); const QStringList& tableExtensions = extensions.values(QLatin1String("TABLES")); QTreeWidgetItem* root = tw->invisibleRootItem(); //TODO: fileName may contain any data type: check if it's a FITS file QTreeWidgetItem* treeNameItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << fileName); root->addChild(treeNameItem); treeNameItem->setExpanded(true); QTreeWidgetItem* imageExtensionItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << i18n("Images")); imageExtensionItem->setFlags(imageExtensionItem->flags() & ~Qt::ItemIsSelectable ); QString primaryHeaderNaxis = valueOf(fileName, "NAXIS"); const int naxis = primaryHeaderNaxis.toInt(); bool noImage = false; for (const QString& ext : imageExtensions) { QTreeWidgetItem* treeItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << ext); if (ext == i18n("Primary header")) { if (checkPrimary && naxis == 0) { delete treeItem; continue; } } imageExtensionItem->addChild(treeItem); } if (imageExtensionItem->childCount() > 0) { treeNameItem->addChild(imageExtensionItem); imageExtensionItem->setIcon(0,QIcon::fromTheme("view-preview")); imageExtensionItem->setExpanded(true); imageExtensionItem->child(0)->setSelected(true); tw->setCurrentItem(imageExtensionItem->child(0)); } else noImage = true; if (tableExtensions.size() > 0) { QTreeWidgetItem* tableExtensionItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << i18n("Tables")); tableExtensionItem->setFlags(tableExtensionItem->flags() & ~Qt::ItemIsSelectable ); for (const QString& ext : tableExtensions) { QTreeWidgetItem* treeItem = new QTreeWidgetItem((QTreeWidgetItem*)nullptr, QStringList() << ext); tableExtensionItem->addChild(treeItem); } if (tableExtensionItem->childCount() > 0) { treeNameItem->addChild(tableExtensionItem); tableExtensionItem->setIcon(0,QIcon::fromTheme("x-office-spreadsheet")); tableExtensionItem->setExpanded(true); if (noImage) { tableExtensionItem->child(0)->setSelected(true); tw->setCurrentItem(tableExtensionItem->child(0)); } } } #else Q_UNUSED(fileName) Q_UNUSED(tw) Q_UNUSED(checkPrimary) #endif DEBUG("FITSFilterPrivate::parseExtensions() DONE"); } /*! * \brief FITSFilterPrivate::~FITSFilterPrivate */ FITSFilterPrivate::~FITSFilterPrivate() = default; //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void FITSFilter::save(QXmlStreamWriter * writer) const { Q_UNUSED(writer) } /*! Loads from XML. */ bool FITSFilter::load(XmlStreamReader * loader) { Q_UNUSED(loader) return false; } diff --git a/src/backend/datasources/filters/HDF5Filter.cpp b/src/backend/datasources/filters/HDF5Filter.cpp index 57986c6e5..be474fd6a 100644 --- a/src/backend/datasources/filters/HDF5Filter.cpp +++ b/src/backend/datasources/filters/HDF5Filter.cpp @@ -1,1935 +1,1935 @@ /*************************************************************************** File : HDF5Filter.cpp Project : LabPlot Description : HDF5 I/O-filter -------------------------------------------------------------------- Copyright : (C) 2015-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /* TODO: * Feature: implement missing data types and ranks * Performance: only fill dataPointer or dataStrings (not both) */ #include "backend/datasources/filters/HDF5Filter.h" #include "backend/datasources/filters/HDF5FilterPrivate.h" #include "backend/datasources/LiveDataSource.h" #include "backend/core/column/Column.h" #include #include #include #include /*! \class HDF5Filter \brief Manages the import/export of data from/to a HDF5 file. \ingroup datasources */ HDF5Filter::HDF5Filter():AbstractFileFilter(HDF5), d(new HDF5FilterPrivate(this)) {} HDF5Filter::~HDF5Filter() = default; /*! parses the content of the file \c fileName. */ void HDF5Filter::parse(const QString & fileName, QTreeWidgetItem* rootItem) { d->parse(fileName, rootItem); } /*! reads the content of the data set \c dataSet from file \c fileName. */ QVector HDF5Filter::readCurrentDataSet(const QString& fileName, AbstractDataSource* dataSource, bool &ok, AbstractFileFilter::ImportMode importMode, int lines) { return d->readCurrentDataSet(fileName, dataSource, ok, importMode, lines); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ void HDF5Filter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode) { d->readDataFromFile(fileName, dataSource, mode); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void HDF5Filter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); } /////////////////////////////////////////////////////////////////////// /*! loads the predefined filter settings for \c filterName */ void HDF5Filter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void HDF5Filter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /////////////////////////////////////////////////////////////////////// void HDF5Filter::setCurrentDataSetName(const QString& ds) { d->currentDataSetName = ds; } const QString HDF5Filter::currentDataSetName() const { return d->currentDataSetName; } void HDF5Filter::setStartRow(const int s) { d->startRow = s; } int HDF5Filter::startRow() const { return d->startRow; } void HDF5Filter::setEndRow(const int e) { d->endRow = e; } int HDF5Filter::endRow() const { return d->endRow; } void HDF5Filter::setStartColumn(const int c) { d->startColumn = c; } int HDF5Filter::startColumn() const { return d->startColumn; } void HDF5Filter::setEndColumn(const int c) { d->endColumn = c; } int HDF5Filter::endColumn() const { return d->endColumn; } QString HDF5Filter::fileInfoString(const QString& fileName) { DEBUG("HDF5Filter::fileInfoString()"); QString info; #ifdef HAVE_HDF5 QByteArray bafileName = fileName.toLatin1(); DEBUG("fileName = " << bafileName.data()); // check file type first htri_t isHdf5 = H5Fis_hdf5(bafileName.data()); if (isHdf5 == 0) { DEBUG(bafileName.data() << " is not a HDF5 file! isHdf5 = " << isHdf5 << " Giving up."); return i18n("Not a HDF5 file"); } if (isHdf5 < 0) { DEBUG("H5Fis_hdf5() failed on " << bafileName.data() << "! Giving up."); return i18n("Failed checking file"); } // open file hid_t file = H5Fopen(bafileName.data(), H5F_ACC_RDONLY, H5P_DEFAULT); HDF5FilterPrivate::handleError((int)file, "H5Fopen", fileName); if (file < 0) { DEBUG("Opening file " << bafileName.data() << " failed! Giving up."); return i18n("Failed opening HDF5 file"); } hsize_t size; herr_t status = H5Fget_filesize(file, &size); if (status >= 0) { info += i18n("File size: %1 bytes", QString::number(size)); info += QLatin1String("
"); } hssize_t freesize = H5Fget_freespace(file); info += i18n("Free space: %1 bytes", QString::number(freesize)); info += QLatin1String("
"); info += QLatin1String("
"); ssize_t objectCount; objectCount = H5Fget_obj_count(file, H5F_OBJ_FILE); info += i18n("Number of files: %1", QString::number(objectCount)); info += QLatin1String("
"); objectCount = H5Fget_obj_count(file, H5F_OBJ_DATASET); info += i18n("Number of data sets: %1", QString::number(objectCount)); info += QLatin1String("
"); objectCount = H5Fget_obj_count(file, H5F_OBJ_GROUP); info += i18n("Number of groups: %1", QString::number(objectCount)); info += QLatin1String("
"); objectCount = H5Fget_obj_count(file, H5F_OBJ_DATATYPE); info += i18n("Number of named datatypes: %1", QString::number(objectCount)); info += QLatin1String("
"); objectCount = H5Fget_obj_count(file, H5F_OBJ_ATTR); info += i18n("Number of attributes: %1", QString::number(objectCount)); info += QLatin1String("
"); objectCount = H5Fget_obj_count(file, H5F_OBJ_ALL); info += i18n("Number of all objects: %1", QString::number(objectCount)); info += QLatin1String("
"); #ifdef HAVE_AT_LEAST_HDF5_1_10_0 // using H5Fget_info2 struct (see H5Fpublic.h) H5F_info2_t file_info; status = H5Fget_info2(file, &file_info); if (status >= 0) { info += QLatin1String("
"); info += i18n("Version of superblock: %1", QString::number(file_info.super.version)); info += QLatin1String("
"); info += i18n("Size of superblock: %1 bytes", QString::number(file_info.super.super_size)); info += QLatin1String("
"); info += i18n("Size of superblock extension: %1 bytes", QString::number(file_info.super.super_ext_size)); info += QLatin1String("
"); info += i18n("Version of free-space manager: %1", QString::number(file_info.free.version)); info += QLatin1String("
"); info += i18n("Size of free-space manager metadata: %1 bytes", QString::number(file_info.free.meta_size)); info += QLatin1String("
"); info += i18n("Total size of free space: %1 bytes", QString::number(file_info.free.tot_space)); info += QLatin1String("
"); info += i18n("Version of shared object header: %1", QString::number(file_info.sohm.version)); info += QLatin1String("
"); info += i18n("Size of shared object header: %1 bytes", QString::number(file_info.sohm.hdr_size)); info += QLatin1String("
"); info += i18n("Size of all shared object header indexes: %1 bytes", QString::number(file_info.sohm.msgs_info.index_size)); info += QLatin1String("
"); info += i18n("Size of the heap: %1 bytes", QString::number(file_info.sohm.msgs_info.heap_size)); info += QLatin1String("
"); } #else // using H5Fget_info1 struct (named H5F_info_t in HDF5 1.8) H5F_info_t file_info; status = H5Fget_info(file, &file_info); if (status >= 0) { info += i18n("Size of superblock extension: %1 bytes", QString::number(file_info.super_ext_size)); info += QLatin1String("
"); info += i18n("Size of shared object header: %1 bytes", QString::number(file_info.sohm.hdr_size)); info += QLatin1String("
"); info += i18n("Size of all shared object header indexes: %1 bytes", QString::number(file_info.sohm.msgs_info.index_size)); info += QLatin1String("
"); info += i18n("Size of the heap: %1 bytes", QString::number(file_info.sohm.msgs_info.heap_size)); info += QLatin1String("
"); } #endif // cache information //see https://support.hdfgroup.org/HDF5/doc/RM/RM_H5F.html info += QLatin1String("
"); H5AC_cache_config_t config; config.version = H5AC__CURR_CACHE_CONFIG_VERSION; status = H5Fget_mdc_config(file, &config); if (status >= 0) { info += i18n("Cache config version: %1", QString::number(config.version)); info += QLatin1String("
"); info += i18n("Adaptive cache resize report function enabled: %1", config.rpt_fcn_enabled ? i18n("Yes") : i18n("No")); info += QLatin1String("
"); info += i18n("Cache initial maximum size: %1 bytes", QString::number(config.initial_size)); info += QLatin1String("
"); info += i18n("Adaptive cache maximum size: %1 bytes", QString::number(config.max_size)); info += QLatin1String("
"); info += i18n("Adaptive cache minimum size: %1 bytes", QString::number(config.min_size)); info += QLatin1String("
"); //TODO: more settings } double hit_rate; status = H5Fget_mdc_hit_rate(file, &hit_rate); Q_UNUSED(status); info += i18n("Metadata cache hit rate: %1", QString::number(hit_rate)); info += QLatin1String("
"); //TODO: herr_t H5Fget_mdc_image_info(hid_t file_id, haddr_t *image_addr, hsize_t *image_len) size_t max_size, min_clean_size, cur_size; int cur_num_entries; status = H5Fget_mdc_size(file, &max_size, &min_clean_size, &cur_size, &cur_num_entries); if (status >= 0) { info += i18n("Current cache maximum size: %1 bytes", QString::number(max_size)); info += QLatin1String("
"); info += i18n("Current cache minimum clean size: %1 bytes", QString::number(min_clean_size)); info += QLatin1String("
"); info += i18n("Current cache size: %1 bytes", QString::number(cur_size)); info += QLatin1String("
"); info += i18n("Current number of entries in the cache: %1", QString::number(cur_num_entries)); info += QLatin1String("
"); } //TODO: 1.10 herr_t H5Fget_metadata_read_retry_info( hid_t file_id, H5F_retry_info_t *info ) /* TODO: not available hbool_t atomicMode; status = H5Fget_mpi_atomicity(file, &atomicMode); if (status >= 0) { info += i18n("MPI file access atomic mode: %1", atomicMode ? i18n("Yes") : i18n("No")); info += QLatin1String("
"); }*/ #ifdef HAVE_AT_LEAST_HDF5_1_10_0 hbool_t is_enabled, is_currently_logging; status = H5Fget_mdc_logging_status(file, &is_enabled, &is_currently_logging); if (status >= 0) { info += i18n("Logging enabled: %1", is_enabled ? i18n("Yes") : i18n("No")); info += QLatin1String("
"); info += i18n("Events are currently logged: %1", is_currently_logging ? i18n("Yes") : i18n("No")); info += QLatin1String("
"); } #endif #ifdef HAVE_AT_LEAST_HDF5_1_10_1 unsigned int accesses[2], hits[2], misses[2], evictions[2], bypasses[2]; status = H5Fget_page_buffering_stats(file, accesses, hits, misses, evictions, bypasses); if (status >= 0) { info += i18n("Metadata/raw data page buffer accesses: %1 %2", QString::number(accesses[0]), QString::number(accesses[1])); info += QLatin1String("
"); info += i18n("Metadata/raw data page buffer hits: %1 %2", QString::number(hits[0]), QString::number(hits[1])); info += QLatin1String("
"); info += i18n("Metadata/raw data page buffer misses: %1 %2", QString::number(misses[0]), QString::number(misses[1])); info += QLatin1String("
"); info += i18n("Metadata/raw data page buffer evictions: %1 %2", QString::number(evictions[0]), QString::number(evictions[1])); info += QLatin1String("
"); info += i18n("Metadata/raw data accesses bypassing page buffer: %1 %2", QString::number(bypasses[0]), QString::number(bypasses[1])); info += QLatin1String("
"); } else { info += i18n("Page buffer disabled"); info += QLatin1String("
"); DEBUG("H5Fget_page_buffering_stats() status = " << status); } #endif #else Q_UNUSED(fileName); #endif return info; } /*! * Get file content in DDL (Data Description Language) format * uses "h5dump" */ QString HDF5Filter::fileDDLString(const QString& fileName) { DEBUG("HDF5Filter::fileDDLString()"); QString DDLString; #ifdef Q_OS_LINUX auto* proc = new QProcess(); QStringList args; args << "-H" << fileName; proc->start( "h5dump", args); if (proc->waitForReadyRead(1000) == false) DDLString += i18n("Reading from file %1 failed.", fileName); else { DDLString += proc->readAll(); DDLString.replace('\n', "
\n"); DDLString.replace("\t","    "); - //DEBUG(" DDL string: " << DDLString.toStdString()); + //DEBUG(" DDL string: " << STDSTRING(DDLString)); } #else //TODO: h5dump on Win, Mac Q_UNUSED(fileName) #endif return DDLString; } //##################################################################### //################### Private implementation ########################## //##################################################################### HDF5FilterPrivate::HDF5FilterPrivate(HDF5Filter* owner) : q(owner) { #ifdef HAVE_HDF5 m_status = 0; #endif } #ifdef HAVE_HDF5 void HDF5FilterPrivate::handleError(int err, const QString& function, const QString& arg) { #ifdef NDEBUG Q_UNUSED(err) Q_UNUSED(function) Q_UNUSED(arg) #else if (err < 0) { - DEBUG("ERROR " << err << ": " << function.toStdString() << "() - " << arg.toStdString()); + DEBUG("ERROR " << err << ": " << STDSTRING(function) << "() - " << STDSTRING(arg)); } #endif } QString HDF5FilterPrivate::translateHDF5Order(H5T_order_t o) { QString order; switch (o) { case H5T_ORDER_LE: order = "LE"; break; case H5T_ORDER_BE: order = "BE"; break; case H5T_ORDER_VAX: order = "VAX"; break; case H5T_ORDER_MIXED: order = "MIXED"; break; case H5T_ORDER_NONE: order = "NONE"; break; case H5T_ORDER_ERROR: order = "ERROR"; break; } return order; } QString HDF5FilterPrivate::translateHDF5Type(hid_t t) { QString type; if (H5Tequal(t, H5T_STD_I8LE) || H5Tequal(t, H5T_STD_I8BE)) type = "CHAR"; else if (H5Tequal(t, H5T_STD_U8LE) || H5Tequal(t, H5T_STD_U8BE)) type = "UCHAR"; else if (H5Tequal(t, H5T_STD_I16LE) || H5Tequal(t, H5T_STD_I16BE)) type = "SHORT"; else if (H5Tequal(t, H5T_STD_U16LE) || H5Tequal(t, H5T_STD_U16BE)) type = "USHORT"; else if (H5Tequal(t, H5T_STD_I32LE) || H5Tequal(t, H5T_STD_I32BE)) type = "INT"; else if (H5Tequal(t, H5T_STD_U32LE) || H5Tequal(t, H5T_STD_U32BE)) type = "UINT"; else if (H5Tequal(t, H5T_NATIVE_LONG)) type = "LONG"; else if (H5Tequal(t, H5T_NATIVE_ULONG)) type = "ULONG"; else if (H5Tequal(t, H5T_STD_I64LE) || H5Tequal(t, H5T_STD_I64BE)) type = "LLONG"; else if (H5Tequal(t, H5T_STD_U64LE) || H5Tequal(t, H5T_STD_U64BE)) type = "ULLONG"; else if (H5Tequal(t, H5T_IEEE_F32LE) || H5Tequal(t, H5T_IEEE_F32BE)) type = "FLOAT"; else if (H5Tequal(t, H5T_IEEE_F64LE) || H5Tequal(t, H5T_IEEE_F64BE)) type = "DOUBLE"; else if (H5Tequal(t, H5T_NATIVE_LDOUBLE)) type = "LDOUBLE"; else type = "UNKNOWN"; return type; } QString HDF5FilterPrivate::translateHDF5Class(H5T_class_t c) { QString dclass; switch (c) { case H5T_INTEGER: dclass = "INTEGER"; break; case H5T_FLOAT: dclass = "FLOAT"; break; case H5T_STRING: dclass = "STRING"; break; case H5T_BITFIELD: dclass = "BITFIELD"; break; case H5T_OPAQUE: dclass = "OPAQUE"; break; case H5T_COMPOUND: dclass = "COMPOUND"; break; case H5T_ARRAY: dclass = "ARRAY"; break; case H5T_ENUM: dclass = "ENUM"; break; case H5T_REFERENCE: dclass = "REFERENCE"; break; case H5T_VLEN: dclass = "VLEN"; break; case H5T_TIME: dclass = "TIME"; break; case H5T_NCLASSES: dclass = "NCLASSES"; break; case H5T_NO_CLASS: dclass = "NOCLASS"; break; } return dclass; } QStringList HDF5FilterPrivate::readHDF5Compound(hid_t tid) { size_t typeSize = H5Tget_size(tid); QString line; line += QLatin1String("COMPOUND(") + QString::number(typeSize) + QLatin1String(") : ("); int members = H5Tget_nmembers(tid); handleError(members, "H5Tget_nmembers"); for (int i = 0; i < members; ++i) { H5T_class_t mclass = H5Tget_member_class(tid, i); handleError((int)mclass, "H5Tget_member_class"); hid_t mtype = H5Tget_member_type(tid, i); handleError((int)mtype, "H5Tget_member_type"); size_t size = H5Tget_size(mtype); handleError((int)size, "H5Tget_size"); QString typeString = translateHDF5Class(mclass); if (mclass == H5T_INTEGER || mclass == H5T_FLOAT) typeString = translateHDF5Type(mtype); line += H5Tget_member_name(tid, i) + QLatin1String("[") + typeString + QLatin1String("(") + QString::number(size) + QLatin1String(")]"); if (i == members-1) line += QLatin1String(")"); else line += QLatin1String(","); m_status = H5Tclose(mtype); handleError(m_status, "H5Tclose"); } QStringList dataString; dataString << line; return dataString; } template QStringList HDF5FilterPrivate::readHDF5Data1D(hid_t dataset, hid_t type, int rows, int lines, void* dataContainer) { DEBUG("readHDF5Data1D() rows = " << rows << ", lines = " << lines); QStringList dataString; // we read all rows of data T* data = new T[rows]; m_status = H5Dread(dataset, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); handleError(m_status, "H5Dread"); DEBUG(" startRow = " << startRow << ", endRow = " << endRow); DEBUG(" dataContainer = " << dataContainer); for (int i = startRow - 1; i < qMin(endRow, lines + startRow - 1); ++i) { if (dataContainer) // read to data source static_cast*>(dataContainer)->operator[](i-startRow+1) = data[i]; else // for preview dataString << QString::number(static_cast(data[i])); } delete[] data; return dataString; } QStringList HDF5FilterPrivate::readHDF5CompoundData1D(hid_t dataset, hid_t tid, int rows, int lines, std::vector& dataContainer) { DEBUG("HDF5FilterPrivate::readHDF5CompoundData1D()"); DEBUG(" dataContainer size = " << dataContainer.size()); int members = H5Tget_nmembers(tid); handleError(members, "H5Tget_nmembers"); DEBUG(" # members = " << members); QStringList dataString; if (!dataContainer[0]) { for (int i = 0; i < qMin(rows, lines); ++i) dataString << QLatin1String("("); dataContainer.resize(members); // avoid "index out of range" for preview } for (int m = 0; m < members; ++m) { hid_t mtype = H5Tget_member_type(tid, m); handleError((int)mtype, "H5Tget_member_type"); size_t msize = H5Tget_size(mtype); handleError((int)msize, "H5Tget_size"); hid_t ctype = H5Tcreate(H5T_COMPOUND, msize); handleError((int)ctype, "H5Tcreate"); m_status = H5Tinsert(ctype, H5Tget_member_name(tid, m), 0, mtype); handleError(m_status, "H5Tinsert"); QStringList mdataString; if (H5Tequal(mtype, H5T_STD_I8LE) || H5Tequal(mtype, H5T_STD_I8BE)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 2: mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 4: mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 8: mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; } } else if (H5Tequal(mtype, H5T_STD_U8LE) || H5Tequal(mtype, H5T_STD_U8BE)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 2: mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 4: mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; case 8: mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); break; } } else if (H5Tequal(mtype, H5T_STD_I16LE) || H5Tequal(mtype, H5T_STD_I16BE) || H5Tequal(mtype, H5T_NATIVE_SHORT)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_U16LE) || H5Tequal(mtype, H5T_STD_U16BE) || H5Tequal(mtype, H5T_NATIVE_SHORT)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_I32LE) || H5Tequal(mtype, H5T_STD_I32BE) || H5Tequal(mtype, H5T_NATIVE_INT)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_U32LE) || H5Tequal(mtype, H5T_STD_U32BE) || H5Tequal(mtype, H5T_NATIVE_UINT)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_LONG)) mdataString = readHDF5Data1D(dataset, ctype, rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_ULONG)) mdataString = readHDF5Data1D(dataset, ctype, rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_I64LE) || H5Tequal(mtype, H5T_STD_I64BE) || H5Tequal(mtype, H5T_NATIVE_LLONG)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_STD_U64LE) || H5Tequal(mtype, H5T_STD_U64BE) || H5Tequal(mtype, H5T_NATIVE_ULLONG)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_IEEE_F32LE) || H5Tequal(mtype, H5T_IEEE_F32BE)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_IEEE_F64LE) || H5Tequal(mtype, H5T_IEEE_F64BE)) mdataString = readHDF5Data1D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, lines, dataContainer[m]); else if (H5Tequal(mtype, H5T_NATIVE_LDOUBLE)) mdataString = readHDF5Data1D(dataset, ctype, rows, lines, dataContainer[m]); else { if (dataContainer[m]) { for (int i = startRow-1; i < qMin(endRow, lines + startRow - 1); ++i) static_cast*>(dataContainer[m])->operator[](i - startRow + 1) = 0; } else { for (int i = 0; i < qMin(rows, lines); ++i) mdataString << QLatin1String("_"); } H5T_class_t mclass = H5Tget_member_class(tid, m); handleError((int)mclass, "H5Tget_member_class"); - DEBUG("unsupported type of class " << translateHDF5Class(mclass).toStdString()); + DEBUG("unsupported type of class " << STDSTRING(translateHDF5Class(mclass))); } if (!dataContainer[0]) { for (int i = 0; i < qMin(rows, lines); ++i) { dataString[i] += mdataString[i]; if (m < members - 1) dataString[i] += QLatin1String(","); } } H5Tclose(ctype); } if (!dataContainer[0]) { for (int i = 0; i < qMin(rows, lines); ++i) dataString[i] += QLatin1String(")"); } return dataString; } template QVector HDF5FilterPrivate::readHDF5Data2D(hid_t dataset, hid_t type, int rows, int cols, int lines, std::vector& dataPointer) { DEBUG("readHDF5Data2D() rows = " << rows << ", cols =" << cols << ", lines =" << lines); QVector dataStrings; if (rows == 0 || cols == 0) return dataStrings; T** data = (T**) malloc(rows*sizeof(T*)); data[0] = (T*) malloc(cols*rows*sizeof(T)); for (int i = 1; i < rows; ++i) data[i] = data[0] + i*cols; m_status = H5Dread(dataset, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, &data[0][0]); handleError(m_status,"H5Dread"); for (int i = 0; i < qMin(rows, lines); ++i) { QStringList line; line.reserve(cols); for (int j = 0; j < cols; ++j) { if (dataPointer[0]) static_cast*>(dataPointer[j-startColumn+1])->operator[](i-startRow+1) = data[i][j]; else line << QString::number(static_cast(data[i][j])); } dataStrings << line; } free(data[0]); free(data); QDEBUG(dataStrings); return dataStrings; } QVector HDF5FilterPrivate::readHDF5CompoundData2D(hid_t dataset, hid_t tid, int rows, int cols, int lines) { DEBUG("readHDF5CompoundData2D() rows =" << rows << "cols =" << cols << "lines =" << lines); int members = H5Tget_nmembers(tid); handleError(members, "H5Tget_nmembers"); DEBUG(" # members =" << members); QVector dataStrings; for (int i = 0; i < qMin(rows, lines); ++i) { QStringList lineStrings; for (int j = 0; j < cols; ++j) lineStrings << QLatin1String("("); dataStrings << lineStrings; } //QStringList* data = new QStringList[members]; for (int m = 0; m < members; ++m) { hid_t mtype = H5Tget_member_type(tid, m); handleError((int)mtype, "H5Tget_member_type"); size_t msize = H5Tget_size(mtype); handleError((int)msize, "H5Tget_size"); hid_t ctype = H5Tcreate(H5T_COMPOUND, msize); handleError((int)ctype, "H5Tcreate"); m_status = H5Tinsert(ctype, H5Tget_member_name(tid, m), 0, mtype); handleError(m_status, "H5Tinsert"); // dummy container for all data columns // initially contains one pointer set to NULL std::vector dummy(1, nullptr); QVector mdataStrings; if (H5Tequal(mtype, H5T_STD_I8LE) || H5Tequal(mtype, H5T_STD_I8BE)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 2: mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 4: mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 8: mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; } } else if (H5Tequal(mtype, H5T_STD_U8LE) || H5Tequal(mtype, H5T_STD_U8BE)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 2: mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 4: mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; case 8: mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); break; } } else if (H5Tequal(mtype, H5T_STD_I16LE) || H5Tequal(mtype, H5T_STD_I16BE)|| H5Tequal(mtype, H5T_NATIVE_SHORT)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_U16LE) || H5Tequal(mtype, H5T_STD_U16BE) || H5Tequal(mtype, H5T_NATIVE_USHORT)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_I32LE) || H5Tequal(mtype, H5T_STD_I32BE) || H5Tequal(mtype, H5T_NATIVE_INT)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_U32LE) || H5Tequal(mtype, H5T_STD_U32BE) || H5Tequal(mtype, H5T_NATIVE_UINT)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_LONG)) mdataStrings = readHDF5Data2D(dataset, ctype, rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_ULONG)) mdataStrings = readHDF5Data2D(dataset, ctype, rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_I64LE) || H5Tequal(mtype, H5T_STD_I64BE) || H5Tequal(mtype, H5T_NATIVE_LLONG)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_STD_U64LE) || H5Tequal(mtype, H5T_STD_U64BE) || H5Tequal(mtype, H5T_NATIVE_ULLONG)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_IEEE_F32LE) || H5Tequal(mtype, H5T_IEEE_F32BE)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_IEEE_F64LE) || H5Tequal(mtype, H5T_IEEE_F64BE)) mdataStrings = readHDF5Data2D(dataset, H5Tget_native_type(ctype, H5T_DIR_DEFAULT), rows, cols, lines, dummy); else if (H5Tequal(mtype, H5T_NATIVE_LDOUBLE)) mdataStrings = readHDF5Data2D(dataset, ctype, rows, cols, lines, dummy); else { for (int i = 0; i < qMin(rows, lines); ++i) { QStringList lineString; for (int j = 0; j < cols; ++j) lineString << QLatin1String("_"); mdataStrings << lineString; } #ifndef NDEBUG H5T_class_t mclass = H5Tget_member_class(tid, m); #endif - DEBUG("unsupported class " << translateHDF5Class(mclass).toStdString()); + DEBUG("unsupported class " << STDSTRING(translateHDF5Class(mclass))); } m_status = H5Tclose(ctype); handleError(m_status, "H5Tclose"); for (int i = 0; i < qMin(rows, lines); i++) { for (int j = 0; j < cols; j++) { dataStrings[i][j] += mdataStrings[i][j]; if (m < members-1) dataStrings[i][j] += QLatin1String(","); } } } for (int i = 0; i < qMin(rows, lines); ++i) { for (int j = 0; j < cols; ++j) dataStrings[i][j] += QLatin1String(")"); } QDEBUG("dataStrings =" << dataStrings); return dataStrings; } QStringList HDF5FilterPrivate::readHDF5Attr(hid_t aid) { QStringList attr; char name[MAXNAMELENGTH]; m_status = H5Aget_name(aid, MAXNAMELENGTH, name); handleError(m_status, "H5Aget_name"); attr << QString(name); // DEBUG(" name =" << QString(name)); hid_t aspace = H5Aget_space(aid); // the dimensions of the attribute data handleError((int)aspace, "H5Aget_space"); hid_t atype = H5Aget_type(aid); handleError((int)atype, "H5Aget_type"); hid_t aclass = H5Tget_class(atype); handleError((int)aclass, "H5Aget_class"); if (aclass == H5T_STRING) { char buf[MAXSTRINGLENGTH]; // buffer to read attr value hid_t amem = H5Tget_native_type(atype, H5T_DIR_ASCEND); handleError((int)amem, "H5Tget_native_type"); m_status = H5Aread(aid, amem, buf); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString(buf); m_status = H5Tclose(amem); handleError(m_status, "H5Tclose"); } else if (aclass == H5T_INTEGER) { if (H5Tequal(atype, H5T_STD_I8LE)) { qint8 value; m_status = H5Aread(aid, H5T_STD_I8LE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_I8BE)) { qint8 value; m_status = H5Aread(aid, H5T_STD_I8BE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: { qint8 value; m_status = H5Aread(aid, H5T_NATIVE_CHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 2: { qint16 value; m_status = H5Aread(aid, H5T_NATIVE_CHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 4: { qint32 value; m_status = H5Aread(aid, H5T_NATIVE_CHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 8: { qint64 value; m_status = H5Aread(aid, H5T_NATIVE_CHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } default: DEBUG("unknown size " << sizeof(H5T_NATIVE_CHAR) << " of H5T_NATIVE_CHAR"); return QStringList(QString()); } } else if (H5Tequal(atype, H5T_STD_U8LE)) { uint8_t value; m_status = H5Aread(aid, H5T_STD_U8LE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_U8BE)) { uint8_t value; m_status = H5Aread(aid, H5T_STD_U8BE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: { uint8_t value; m_status = H5Aread(aid, H5T_NATIVE_UCHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 2: { uint16_t value; m_status = H5Aread(aid, H5T_NATIVE_UCHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 4: { uint32_t value; m_status = H5Aread(aid, H5T_NATIVE_UCHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } case 8: { uint64_t value; m_status = H5Aread(aid, H5T_NATIVE_UCHAR, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); break; } default: DEBUG("unknown size " << sizeof(H5T_NATIVE_UCHAR) << " of H5T_NATIVE_UCHAR"); return QStringList(QString()); } } else if (H5Tequal(atype, H5T_STD_I16LE) || H5Tequal(atype, H5T_STD_I16BE) || H5Tequal(atype, H5T_NATIVE_SHORT)) { short value; m_status = H5Aread(aid, H5T_NATIVE_SHORT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_U16LE) || H5Tequal(atype, H5T_STD_U16BE) || H5Tequal(atype, H5T_NATIVE_USHORT)) { unsigned short value; m_status = H5Aread(aid, H5T_NATIVE_USHORT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_I32LE) || H5Tequal(atype, H5T_STD_I32BE) || H5Tequal(atype, H5T_NATIVE_INT)) { int value; m_status = H5Aread(aid, H5T_NATIVE_INT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_U32LE) || H5Tequal(atype, H5T_STD_U32BE) || H5Tequal(atype, H5T_NATIVE_UINT)) { unsigned int value; m_status = H5Aread(aid, H5T_NATIVE_UINT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_LONG)) { long value; m_status = H5Aread(aid, H5T_NATIVE_LONG, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_ULONG)) { unsigned long value; m_status = H5Aread(aid, H5T_NATIVE_ULONG, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_I64LE) || H5Tequal(atype, H5T_STD_I64BE) || H5Tequal(atype, H5T_NATIVE_LLONG)) { long long value; m_status = H5Aread(aid, H5T_NATIVE_LLONG, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_STD_U64LE) || H5Tequal(atype, H5T_STD_U64BE) || H5Tequal(atype, H5T_NATIVE_ULLONG)) { unsigned long long value; m_status = H5Aread(aid, H5T_NATIVE_ULLONG, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else attr<<" (unknown integer)"; } else if (aclass == H5T_FLOAT) { if (H5Tequal(atype, H5T_IEEE_F32LE) || H5Tequal(atype, H5T_IEEE_F32BE)) { float value; m_status = H5Aread(aid, H5T_NATIVE_FLOAT, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_IEEE_F64LE) || H5Tequal(atype, H5T_IEEE_F64BE)) { double value; m_status = H5Aread(aid, H5T_NATIVE_DOUBLE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number(value); } else if (H5Tequal(atype, H5T_NATIVE_LDOUBLE)) { long double value; m_status = H5Aread(aid, H5T_NATIVE_LDOUBLE, &value); handleError(m_status, "H5Aread"); attr << QLatin1String("=") << QString::number((double)value); } else attr<<" (unknown float)"; } m_status = H5Tclose(atype); handleError(m_status, "H5Tclose"); m_status = H5Sclose(aspace); handleError(m_status, "H5Sclose"); return attr; } QStringList HDF5FilterPrivate::scanHDF5Attrs(hid_t oid) { QStringList attrList; int numAttr = H5Aget_num_attrs(oid); handleError(numAttr, "H5Aget_num_attrs"); DEBUG("number of attr = " << numAttr); for (int i = 0; i < numAttr; ++i) { hid_t aid = H5Aopen_idx(oid, i); handleError((int)aid, "H5Aopen_idx"); attrList << readHDF5Attr(aid); if (i < numAttr-1) attrList << QLatin1String(", "); m_status = H5Aclose(aid); handleError(m_status, "H5Aclose"); } return attrList; } QStringList HDF5FilterPrivate::readHDF5DataType(hid_t tid) { H5T_class_t typeClass = H5Tget_class(tid); handleError((int)typeClass, "H5Tget_class"); QStringList typeProps; QString typeString = translateHDF5Class(typeClass); if (typeClass == H5T_INTEGER || typeClass == H5T_FLOAT) typeString = translateHDF5Type(tid); typeProps<setIcon(0, QIcon::fromTheme("accessories-calculator")); dataTypeItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(dataTypeItem); } void HDF5FilterPrivate::scanHDF5DataSet(hid_t did, char *dataSetName, QTreeWidgetItem* parentItem) { QString attr = scanHDF5Attrs(did).join(QString()); char link[MAXNAMELENGTH]; m_status = H5Iget_name(did, link, MAXNAMELENGTH); handleError(m_status, "H5Iget_name"); QStringList dataSetProps; hsize_t size = H5Dget_storage_size(did); handleError((int)size, "H5Dget_storage_size"); hid_t datatype = H5Dget_type(did); handleError((int)datatype, "H5Dget_type"); size_t typeSize = H5Tget_size(datatype); handleError((int)typeSize, "H5Dget_size"); dataSetProps << readHDF5DataType(datatype); hid_t dataspace = H5Dget_space(did); int rank = H5Sget_simple_extent_ndims(dataspace); handleError(rank, "H5Sget_simple_extent_ndims"); unsigned int rows = 1, cols = 1, regs = 1; if (rank == 1) { hsize_t dims_out[1]; m_status = H5Sget_simple_extent_dims(dataspace, dims_out, nullptr); handleError(m_status, "H5Sget_simple_extent_dims"); rows = dims_out[0]; dataSetProps << QLatin1String(", ") << QString::number(rows) << QLatin1String(" (") << QString::number(size/typeSize) << QLatin1String(")"); } else if (rank == 2) { hsize_t dims_out[2]; m_status = H5Sget_simple_extent_dims(dataspace, dims_out, nullptr); handleError(m_status, "H5Sget_simple_extent_dims"); rows = dims_out[0]; cols = dims_out[1]; dataSetProps << QLatin1String(", ") << QString::number(rows) << QLatin1String("x") << QString::number(cols) << QLatin1String(" (") << QString::number(size/typeSize) << QLatin1String(")"); } else if (rank == 3) { hsize_t dims_out[3]; m_status = H5Sget_simple_extent_dims(dataspace, dims_out, nullptr); handleError(m_status, "H5Sget_simple_extent_dims"); rows = dims_out[0]; cols = dims_out[1]; regs = dims_out[2]; dataSetProps << QLatin1String(", ") << QString::number(rows) << QLatin1String("x") << QString::number(cols) << QLatin1String("x") << QString::number(regs) << QLatin1String(" (") << QString::number(size/typeSize) << QLatin1String(")"); } else dataSetProps << QLatin1String(", ") << i18n("rank %1 not supported yet", rank); hid_t pid = H5Dget_create_plist(did); handleError((int)pid, "H5Dget_create_plist"); dataSetProps << ", " << readHDF5PropertyList(pid).join(QString()); QTreeWidgetItem* dataSetItem = new QTreeWidgetItem(QStringList()<setIcon(0, QIcon::fromTheme("x-office-spreadsheet")); for (int i = 0; i < dataSetItem->columnCount(); ++i) { if (rows > 0 && cols > 0 && regs > 0) { dataSetItem->setBackground(i, QColor(192,255,192)); dataSetItem->setForeground(i, Qt::black); dataSetItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); } else dataSetItem->setFlags(Qt::NoItemFlags); } parentItem->addChild(dataSetItem); } void HDF5FilterPrivate::scanHDF5Link(hid_t gid, char *linkName, QTreeWidgetItem* parentItem) { char target[MAXNAMELENGTH]; m_status = H5Gget_linkval(gid, linkName, MAXNAMELENGTH, target) ; handleError(m_status, "H5Gget_linkval"); QTreeWidgetItem* linkItem = new QTreeWidgetItem(QStringList() << QString(linkName) << i18n("symbolic link") << i18n("link to %1", QFile::decodeName(target))); linkItem->setIcon(0, QIcon::fromTheme("emblem-symbolic-link")); linkItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(linkItem); } void HDF5FilterPrivate::scanHDF5Group(hid_t gid, char *groupName, QTreeWidgetItem* parentItem) { DEBUG("HDF5FilterPrivate::scanHDF5Group()"); //check for hard link H5G_stat_t statbuf; m_status = H5Gget_objinfo(gid, ".", true, &statbuf); handleError(m_status, "H5Gget_objinfo"); if (statbuf.nlink > 1) { if (m_multiLinkList.contains(statbuf.objno[0])) { QTreeWidgetItem* objectItem = new QTreeWidgetItem(QStringList()<setIcon(0, QIcon::fromTheme("link")); objectItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(objectItem); return; } else { m_multiLinkList.append(statbuf.objno[0]); DEBUG(" group multiple links: "<setIcon(0, QIcon::fromTheme("folder")); groupItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(groupItem); hsize_t numObj; m_status = H5Gget_num_objs(gid, &numObj); handleError(m_status, "H5Gget_num_objs"); for (unsigned int i = 0; i < numObj; ++i) { char memberName[MAXNAMELENGTH]; m_status = H5Gget_objname_by_idx(gid, (hsize_t)i, memberName, (size_t)MAXNAMELENGTH ); handleError(m_status, "H5Gget_objname_by_idx"); int otype = H5Gget_objtype_by_idx(gid, (size_t)i ); handleError(otype, "H5Gget_objtype_by_idx"); switch (otype) { case H5G_LINK: { scanHDF5Link(gid, memberName, groupItem); break; } case H5G_GROUP: { hid_t grpid = H5Gopen(gid, memberName, H5P_DEFAULT); handleError((int)grpid, "H5Gopen"); scanHDF5Group(grpid, memberName, groupItem); m_status = H5Gclose(grpid); handleError(m_status, "H5Gclose"); break; } case H5G_DATASET: { hid_t dsid = H5Dopen(gid, memberName, H5P_DEFAULT); handleError((int)dsid, "H5Dopen"); scanHDF5DataSet(dsid, memberName, groupItem); m_status = H5Dclose(dsid); handleError(m_status, "H5Dclose"); break; } case H5G_TYPE: { hid_t tid = H5Topen(gid, memberName, H5P_DEFAULT); handleError((int)tid, "H5Topen"); scanHDF5DataType(tid, memberName, groupItem); m_status = H5Tclose(tid); handleError(m_status, "H5Tclose"); break; } default: QTreeWidgetItem* objectItem = new QTreeWidgetItem(QStringList() << QString(memberName) << i18n("unknown")); objectItem->setFlags(Qt::ItemIsEnabled); groupItem->addChild(objectItem); break; } } } #endif /*! parses the content of the file \c fileName and fill the tree using rootItem. */ void HDF5FilterPrivate::parse(const QString& fileName, QTreeWidgetItem* rootItem) { DEBUG("HDF5FilterPrivate::parse()"); #ifdef HAVE_HDF5 QByteArray bafileName = fileName.toLatin1(); DEBUG("fileName = " << bafileName.data()); // check file type first htri_t isHdf5 = H5Fis_hdf5(bafileName.data()); if (isHdf5 == 0) { DEBUG(bafileName.data() << " is not a HDF5 file! Giving up."); return; } if (isHdf5 < 0) { DEBUG("H5Fis_hdf5() failed on " << bafileName.data() << "! Giving up."); return; } // open file hid_t file = H5Fopen(bafileName.data(), H5F_ACC_RDONLY, H5P_DEFAULT); handleError((int)file, "H5Fopen", fileName); if (file < 0) { DEBUG("Opening file " << bafileName.data() << " failed! Giving up."); return; } char rootName[] = "/"; hid_t group = H5Gopen(file, rootName, H5P_DEFAULT); handleError((int)group, "H5Gopen", rootName); // multiLinkList.clear(); crashes scanHDF5Group(group, rootName, rootItem); m_status = H5Gclose(group); handleError(m_status, "H5Gclose", QString()); m_status = H5Fclose(file); handleError(m_status, "H5Fclose", QString()); #else DEBUG("HDF5 not available"); Q_UNUSED(fileName) Q_UNUSED(rootItem) #endif } /*! reads the content of the date set in the file \c fileName to a string (for preview) or to the data source. */ QVector HDF5FilterPrivate::readCurrentDataSet(const QString& fileName, AbstractDataSource* dataSource, bool &ok, AbstractFileFilter::ImportMode mode, int lines) { DEBUG("HDF5Filter::readCurrentDataSet()"); QVector dataStrings; if (currentDataSetName.isEmpty()) { //return QString("No data set selected").replace(' ',QChar::Nbsp); ok = false; return dataStrings << (QStringList() << i18n("No data set selected")); } - DEBUG(" current data set = " << currentDataSetName.toStdString()); + DEBUG(" current data set = " << STDSTRING(currentDataSetName)); #ifdef HAVE_HDF5 QByteArray bafileName = fileName.toLatin1(); hid_t file = H5Fopen(bafileName.data(), H5F_ACC_RDONLY, H5P_DEFAULT); handleError((int)file, "H5Fopen", fileName); QByteArray badataSet = currentDataSetName.toLatin1(); hid_t dataset = H5Dopen2(file, badataSet.data(), H5P_DEFAULT); handleError((int)file, "H5Dopen2", currentDataSetName); // Get datatype and dataspace hid_t dtype = H5Dget_type(dataset); handleError((int)dtype, "H5Dget_type"); H5T_class_t dclass = H5Tget_class(dtype); handleError((int)dclass, "H5Dget_class"); size_t typeSize = H5Tget_size(dtype); handleError((int)(typeSize-1), "H5Dget_size"); hid_t dataspace = H5Dget_space(dataset); handleError((int)dataspace, "H5Dget_space"); int rank = H5Sget_simple_extent_ndims(dataspace); handleError(rank, "H5Dget_simple_extent_ndims"); DEBUG(" rank = " << rank); int columnOffset = 0; // offset to import data int actualRows = 0, actualCols = 0; // rows and cols to read // dataContainer is used to store the data read from the dataSource // it contains the pointers of all columns // initially there is one pointer set to nullptr // check for dataContainer[0] != nullptr to decide if dataSource can be used std::vector dataContainer(1, nullptr); // rank= 0: single value, 1: vector, 2: matrix, 3: 3D data, ... switch (rank) { case 0: { // single value actualCols = 1; switch (dclass) { case H5T_STRING: { char* data = (char *) malloc(typeSize * sizeof(char)); hid_t memtype = H5Tcopy(H5T_C_S1); handleError((int)memtype, "H5Tcopy"); m_status = H5Tset_size(memtype, typeSize); handleError(m_status, "H5Tset_size"); m_status = H5Dread(dataset, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); handleError(m_status, "H5Tread"); dataStrings << (QStringList() << data); free(data); break; } case H5T_INTEGER: case H5T_FLOAT: case H5T_TIME: case H5T_BITFIELD: case H5T_OPAQUE: case H5T_COMPOUND: case H5T_REFERENCE: case H5T_ENUM: case H5T_VLEN: case H5T_ARRAY: case H5T_NO_CLASS: case H5T_NCLASSES: { ok = false; dataStrings << (QStringList() << i18n("rank 0 not implemented yet for type %1", translateHDF5Class(dclass))); QDEBUG(dataStrings); } default: break; } break; } case 1: { // vector hsize_t size, maxSize; m_status = H5Sget_simple_extent_dims(dataspace, &size, &maxSize); handleError(m_status, "H5Sget_simple_extent_dims"); int rows = size; if (endRow == -1) endRow = rows; if (lines == -1) lines = endRow; actualRows = endRow - startRow + 1; actualCols = 1; #ifndef NDEBUG H5T_order_t order = H5Tget_order(dtype); handleError((int)order, "H5Sget_order"); #endif QDEBUG(translateHDF5Class(dclass) << '(' << typeSize << ')' << translateHDF5Order(order) << ", rows:" << rows << " max:" << maxSize); //TODO: support other modes QVector columnModes; columnModes.resize(actualCols); // use current data set name (without path) for column name QStringList vectorNames = {currentDataSetName.mid(currentDataSetName.lastIndexOf("/") + 1)}; QDEBUG(" vector names = " << vectorNames) if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); QStringList dataString; // data saved in a list switch (dclass) { case H5T_STRING: { DEBUG("rank 1 H5T_STRING"); hid_t memtype = H5Tcopy(H5T_C_S1); handleError((int)memtype, "H5Tcopy"); char** data = (char **) malloc(rows * sizeof (char *)); if (H5Tis_variable_str(dtype)) { m_status = H5Tset_size(memtype, H5T_VARIABLE); handleError((int)memtype, "H5Tset_size"); m_status = H5Dread(dataset, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, data); handleError(m_status, "H5Dread"); } else { data[0] = (char *) malloc(rows * typeSize * sizeof (char)); for (int i = 1; i < rows; ++i) data[i] = data[0] + i * typeSize; m_status = H5Tset_size(memtype, typeSize); handleError((int)memtype, "H5Tset_size"); m_status = H5Dread(dataset, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, data[0]); handleError(m_status, "H5Dread"); } for (int i = startRow-1; i < qMin(endRow, lines + startRow - 1); ++i) dataString << data[i]; free(data); break; } case H5T_INTEGER: { if (H5Tequal(dtype, H5T_STD_I8LE)) dataString = readHDF5Data1D(dataset, H5T_STD_I8LE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_I8BE)) dataString = readHDF5Data1D(dataset, H5T_STD_I8BE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: dataString = readHDF5Data1D(dataset, H5T_NATIVE_CHAR, rows, lines, dataContainer[0]); break; case 2: dataString = readHDF5Data1D(dataset, H5T_NATIVE_CHAR, rows, lines, dataContainer[0]); break; case 4: dataString = readHDF5Data1D(dataset, H5T_NATIVE_CHAR, rows, lines, dataContainer[0]); break; case 8: dataString = readHDF5Data1D(dataset, H5T_NATIVE_CHAR, rows, lines, dataContainer[0]); break; } } else if (H5Tequal(dtype, H5T_STD_U8LE)) dataString = readHDF5Data1D(dataset, H5T_STD_U8LE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_U8BE)) dataString = readHDF5Data1D(dataset, H5T_STD_U8BE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: dataString = readHDF5Data1D(dataset, H5T_NATIVE_UCHAR, rows, lines, dataContainer[0]); break; case 2: dataString = readHDF5Data1D(dataset, H5T_NATIVE_UCHAR, rows, lines, dataContainer[0]); break; case 4: dataString = readHDF5Data1D(dataset, H5T_NATIVE_UCHAR, rows, lines, dataContainer[0]); break; case 8: dataString = readHDF5Data1D(dataset, H5T_NATIVE_UCHAR, rows, lines, dataContainer[0]); break; } } else if (H5Tequal(dtype, H5T_STD_I16LE) || H5Tequal(dtype, H5T_STD_I16BE) || H5Tequal(dtype, H5T_NATIVE_SHORT)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_SHORT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_U16LE) || H5Tequal(dtype, H5T_STD_U16BE) || H5Tequal(dtype, H5T_NATIVE_USHORT)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_USHORT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_I32LE) || H5Tequal(dtype, H5T_STD_I32BE) || H5Tequal(dtype, H5T_NATIVE_INT)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_INT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_U32LE) || H5Tequal(dtype, H5T_STD_U32BE) || H5Tequal(dtype, H5T_NATIVE_UINT)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_UINT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_LONG)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_LONG, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_ULONG)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_ULONG, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_I64LE) || H5Tequal(dtype, H5T_STD_I64BE) || H5Tequal(dtype, H5T_NATIVE_LLONG)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_LLONG, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_STD_U64LE) || H5Tequal(dtype, H5T_STD_U64BE) || H5Tequal(dtype, H5T_NATIVE_ULLONG)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_ULLONG, rows, lines, dataContainer[0]); else { ok = false; dataString = (QStringList() << i18n("unsupported integer type for rank 1")); QDEBUG(dataString); } break; } case H5T_FLOAT: { if (H5Tequal(dtype, H5T_IEEE_F32LE) || H5Tequal(dtype, H5T_IEEE_F32BE)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_FLOAT, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_IEEE_F64LE) || H5Tequal(dtype, H5T_IEEE_F64BE)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_DOUBLE, rows, lines, dataContainer[0]); else if (H5Tequal(dtype, H5T_NATIVE_LDOUBLE)) dataString = readHDF5Data1D(dataset, H5T_NATIVE_LDOUBLE, rows, lines, dataContainer[0]); else { ok = false; dataString = (QStringList() << i18n("unsupported float type for rank 1")); QDEBUG(dataString); } break; } case H5T_COMPOUND: { int members = H5Tget_nmembers(dtype); handleError(members, "H5Tget_nmembers"); if (dataSource) { // re-create data pointer dataContainer.clear(); dataSource->prepareImport(dataContainer, mode, actualRows, members, vectorNames, columnModes); } else dataStrings << readHDF5Compound(dtype); dataString = readHDF5CompoundData1D(dataset, dtype, rows, lines, dataContainer); break; } case H5T_TIME: case H5T_BITFIELD: case H5T_OPAQUE: case H5T_REFERENCE: case H5T_ENUM: case H5T_VLEN: case H5T_ARRAY: case H5T_NO_CLASS: case H5T_NCLASSES: { ok = false; dataString = (QStringList() << i18n("rank 1 not implemented yet for type %1", translateHDF5Class(dclass))); QDEBUG(dataString); } default: break; } if (!dataSource) { // preview QDEBUG("dataString =" << dataString); DEBUG(" data string size = " << dataString.size()); DEBUG(" rows = " << rows << ", lines = " << lines << ", actual rows = " << actualRows); for (int i = 0; i < qMin(actualRows, lines); ++i) dataStrings << (QStringList() << dataString[i]); } break; } case 2: { // matrix hsize_t dims_out[2]; m_status = H5Sget_simple_extent_dims(dataspace, dims_out, nullptr); handleError(m_status, "H5Sget_simple_extent_dims"); int rows = dims_out[0]; int cols = dims_out[1]; if (endRow == -1) endRow = rows; if (lines == -1) lines = endRow; if (endColumn == -1) endColumn = cols; actualRows = endRow-startRow+1; actualCols = endColumn-startColumn+1; #ifndef NDEBUG H5T_order_t order = H5Tget_order(dtype); handleError((int)order, "H5Tget_order"); #endif QDEBUG(translateHDF5Class(dclass) << '(' << typeSize << ')' << translateHDF5Order(order) << "," << rows << "x" << cols); DEBUG("startRow/endRow" << startRow << endRow); DEBUG("startColumn/endColumn" << startColumn << endColumn); DEBUG("actual rows/cols" << actualRows << actualCols); DEBUG("lines" << lines); //TODO: support other modes QVector columnModes; columnModes.resize(actualCols); // use current data set name (without path) append by "_" and column number for column names QStringList vectorNames; QString colName = currentDataSetName.mid(currentDataSetName.lastIndexOf("/") + 1); for (int i = 0; i < actualCols; i++) vectorNames << colName + QLatin1String("_") + QString::number(i + 1); QDEBUG(" vector names = " << vectorNames) if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); // read data switch (dclass) { case H5T_INTEGER: { if (H5Tequal(dtype, H5T_STD_I8LE)) dataStrings << readHDF5Data2D(dataset, H5T_STD_I8LE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_I8BE)) dataStrings << readHDF5Data2D(dataset, H5T_STD_I8BE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_CHAR)) { switch (sizeof(H5T_NATIVE_CHAR)) { case 1: dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_CHAR, rows, cols, lines, dataContainer); break; case 2: dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_CHAR, rows, cols, lines, dataContainer); break; case 4: dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_CHAR, rows, cols, lines, dataContainer); break; case 8: dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_CHAR, rows, cols, lines, dataContainer); break; } } else if (H5Tequal(dtype, H5T_STD_U8LE)) dataStrings << readHDF5Data2D(dataset, H5T_STD_U8LE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_U8BE)) dataStrings << readHDF5Data2D(dataset, H5T_STD_U8BE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_UCHAR)) { switch (sizeof(H5T_NATIVE_UCHAR)) { case 1: dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_UCHAR, rows, cols, lines, dataContainer); break; case 2: dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_UCHAR, rows, cols, lines, dataContainer); break; case 4: dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_UCHAR, rows, cols, lines, dataContainer); break; case 8: dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_UCHAR, rows, cols, lines, dataContainer); break; } } else if (H5Tequal(dtype, H5T_STD_I16LE) || H5Tequal(dtype, H5T_STD_I16BE) || H5Tequal(dtype, H5T_NATIVE_SHORT)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_SHORT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_U16LE) || H5Tequal(dtype, H5T_STD_U16BE) || H5Tequal(dtype, H5T_NATIVE_USHORT)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_USHORT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_I32LE) || H5Tequal(dtype, H5T_STD_I32BE) || H5Tequal(dtype, H5T_NATIVE_INT)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_INT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_U32LE) || H5Tequal(dtype, H5T_STD_U32BE) || H5Tequal(dtype, H5T_NATIVE_UINT)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_UINT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_LONG)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_LONG, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_ULONG)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_ULONG, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_I64LE) || H5Tequal(dtype, H5T_STD_I64BE) || H5Tequal(dtype, H5T_NATIVE_LLONG)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_LLONG, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_STD_U64LE) || H5Tequal(dtype, H5T_STD_U64BE) || H5Tequal(dtype, H5T_NATIVE_ULLONG)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_ULLONG, rows, cols, lines, dataContainer); else { ok = false; dataStrings << (QStringList() << i18n("unsupported integer type for rank 2")); QDEBUG(dataStrings); } break; } case H5T_FLOAT: { if (H5Tequal(dtype, H5T_IEEE_F32LE) || H5Tequal(dtype, H5T_IEEE_F32BE)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_FLOAT, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_IEEE_F64LE) || H5Tequal(dtype, H5T_IEEE_F64BE)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_DOUBLE, rows, cols, lines, dataContainer); else if (H5Tequal(dtype, H5T_NATIVE_LDOUBLE)) dataStrings << readHDF5Data2D(dataset, H5T_NATIVE_LDOUBLE, rows, cols, lines, dataContainer); else { ok = false; dataStrings << (QStringList() << i18n("unsupported float type for rank 2")); QDEBUG(dataStrings); } break; } case H5T_COMPOUND: { dataStrings << readHDF5Compound(dtype); QDEBUG(dataStrings); dataStrings << readHDF5CompoundData2D(dataset,dtype,rows,cols,lines); break; } case H5T_STRING: { // TODO: implement this ok = false; dataStrings << (QStringList() << i18n("rank 2 not implemented yet for type %1, size = %2", translateHDF5Class(dclass), typeSize)); QDEBUG(dataStrings); break; } case H5T_TIME: case H5T_BITFIELD: case H5T_OPAQUE: case H5T_REFERENCE: case H5T_ENUM: case H5T_VLEN: case H5T_ARRAY: case H5T_NO_CLASS: case H5T_NCLASSES: { ok = false; dataStrings << (QStringList() << i18n("rank 2 not implemented yet for type %1", translateHDF5Class(dclass))); QDEBUG(dataStrings); } default: break; } break; } default: { // 3D or more data ok = false; dataStrings << (QStringList() << i18n("rank %1 not implemented yet for type %2", rank, translateHDF5Class(dclass))); QDEBUG(dataStrings); } } m_status = H5Sclose(dataspace); handleError(m_status, "H5Sclose"); m_status = H5Tclose(dtype); handleError(m_status, "H5Tclose"); m_status = H5Dclose(dataset); handleError(m_status, "H5Dclose"); m_status = H5Fclose(file); handleError(m_status, "H5Fclose"); if (!dataSource) return dataStrings; dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), mode); #else Q_UNUSED(fileName) Q_UNUSED(dataSource) Q_UNUSED(mode) Q_UNUSED(lines) #endif return dataStrings; } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void HDF5FilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode) { DEBUG("HDF5Filter::readDataFromFile()"); if (currentDataSetName.isEmpty()) { DEBUG("WARNING: No data set selected"); return; } bool ok = true; readCurrentDataSet(fileName, dataSource, ok, mode); } /*! writes the content of \c dataSource to the file \c fileName. */ void HDF5FilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: writing HDF5 not implemented yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void HDF5Filter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("hdfFilter"); writer->writeEndElement(); } /*! Loads from XML. */ bool HDF5Filter::load(XmlStreamReader* reader) { Q_UNUSED(reader); // KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); // QXmlStreamAttributes attribs = reader->attributes(); return true; } diff --git a/src/backend/datasources/filters/NetCDFFilter.cpp b/src/backend/datasources/filters/NetCDFFilter.cpp index e338fd9b0..ba0eef5fe 100644 --- a/src/backend/datasources/filters/NetCDFFilter.cpp +++ b/src/backend/datasources/filters/NetCDFFilter.cpp @@ -1,1001 +1,1001 @@ /*************************************************************************** File : NetCDFFilter.cpp Project : LabPlot Description : NetCDF I/O-filter -------------------------------------------------------------------- Copyright : (C) 2015-2019 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/NetCDFFilterPrivate.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/column/Column.h" #include #include ///////////// macros /////////////////////////////////////////////// #define NC_GET_ATT(type, ftype) \ auto* value = (type*)malloc(len*sizeof(type)); \ m_status = nc_get_att_ ##ftype(ncid, varid, name, value); \ handleError(m_status, "nc_get_att_" #ftype); \ for (unsigned int l = 0; l < len; l++) \ valueString << QString::number(value[l]); \ free(value); #define NC_SCAN_VAR(type, ftype) \ type data; \ m_status = nc_get_var_ ##ftype(ncid, i, &data); \ handleError(m_status, "nc_get_var_" #ftype); \ rowStrings << QString::number(data); #define NC_READ_VAR(type, ftype, dtype) \ type data; \ m_status = nc_get_var_ ##ftype(ncid, varid, &data); \ handleError(m_status, "nc_get_var_" #ftype); \ \ if (dataSource) { \ dtype *sourceData = static_cast*>(dataContainer[0])->data(); \ sourceData[0] = (dtype)data; \ } else { /* preview */ \ dataStrings << (QStringList() << QString::number(data)); \ } #define NC_READ_AVAR(type, ftype, dtype) \ auto* data = new type[(unsigned int)actualRows]; \ \ size_t start = (size_t)(startRow - 1), count = (size_t)actualRows; \ m_status = nc_get_vara_ ##ftype(ncid, varid, &start, &count, data); \ handleError(m_status, "nc_get_vara_" #ftype); \ \ if (dataSource) { \ dtype *sourceData = static_cast*>(dataContainer[0])->data(); \ for (int i = 0; i < actualRows; i++) \ sourceData[i] = (dtype)data[i]; \ } else { /* preview */ \ for (int i = 0; i < qMin(actualRows, lines); i++) \ dataStrings << (QStringList() << QString::number(data[i])); \ } \ delete[] data; // for native types (atm: int, double) #define NC_READ_AVAR_NATIVE(type) \ type* data = nullptr; \ if (dataSource) \ data = static_cast*>(dataContainer[0])->data(); \ else \ data = new type[(unsigned int)actualRows]; \ \ size_t start = (size_t)(startRow - 1), count = (size_t)actualRows; \ m_status = nc_get_vara_ ##type(ncid, varid, &start, &count, data); \ handleError(m_status, "nc_get_vara_" #type); \ \ if (!dataSource) { /* preview */ \ for (int i = 0; i < qMin(actualRows, lines); i++) \ dataStrings << (QStringList() << QString::number(data[i])); \ delete[] data; \ } #define NC_READ_VAR2(type, ftype, dtype) \ auto** data = (type**) malloc(rows * sizeof(type*)); \ data[0] = (type*)malloc(cols * rows * sizeof(type)); \ for (unsigned int i = 1; i < rows; i++) \ data[i] = data[0] + i*cols; \ \ m_status = nc_get_var_ ##ftype(ncid, varid, &data[0][0]); \ handleError(m_status, "nc_get_var_" #ftype); \ \ if (m_status == NC_NOERR) { \ for (int i = 0; i < qMin((int)rows, lines); i++) { \ QStringList line; \ for (size_t j = 0; j < cols; j++) { \ if (dataSource && dataContainer[0]) \ static_cast*>(dataContainer[(int)(j-(size_t)startColumn+1)])->operator[](i-startRow+1) = data[i][(int)j]; \ else \ line << QString::number(data[i][j]); \ } \ dataStrings << line; \ emit q->completed(100*i/actualRows); \ } \ } \ free(data[0]); \ free(data); ////////////////////////////////////////////////////////////////////// /*! \class NetCDFFilter \brief Manages the import/export of data from/to a NetCDF file. \ingroup datasources */ NetCDFFilter::NetCDFFilter():AbstractFileFilter(NETCDF), d(new NetCDFFilterPrivate(this)) {} NetCDFFilter::~NetCDFFilter() = default; /*! parses the content of the file \c ileName. */ void NetCDFFilter::parse(const QString & fileName, QTreeWidgetItem* rootItem) { d->parse(fileName, rootItem); } /*! reads the content of the selected attribute from file \c fileName. */ QString NetCDFFilter::readAttribute(const QString & fileName, const QString & name, const QString & varName) { return d->readAttribute(fileName, name, varName); } /*! reads the content of the current variable from file \c fileName. */ QVector NetCDFFilter::readCurrentVar(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { return d->readCurrentVar(fileName, dataSource, importMode, lines); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ void NetCDFFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode) { d->readDataFromFile(fileName, dataSource, mode); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void NetCDFFilter::write(const QString & fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /////////////////////////////////////////////////////////////////////// /*! loads the predefined filter settings for \c filterName */ void NetCDFFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void NetCDFFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /////////////////////////////////////////////////////////////////////// void NetCDFFilter::setCurrentVarName(const QString& ds) { d->currentVarName = ds; } const QString NetCDFFilter::currentVarName() const { return d->currentVarName; } void NetCDFFilter::setStartRow(const int s) { d->startRow = s; } int NetCDFFilter::startRow() const { return d->startRow; } void NetCDFFilter::setEndRow(const int e) { d->endRow = e; } int NetCDFFilter::endRow() const { return d->endRow; } void NetCDFFilter::setStartColumn(const int c) { d->startColumn = c; } int NetCDFFilter::startColumn() const { return d->startColumn; } void NetCDFFilter::setEndColumn(const int c) { d->endColumn = c; } int NetCDFFilter::endColumn() const { return d->endColumn; } QString NetCDFFilter::fileInfoString(const QString& fileName) { DEBUG("NetCDFFilter::fileInfoString()"); QByteArray bafileName = fileName.toLatin1(); DEBUG("fileName = " << bafileName.data()); QString info; #ifdef HAVE_NETCDF int ncid, status; status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); NetCDFFilterPrivate::handleError(status, "nc_open"); if (status != NC_NOERR) { DEBUG(" File error. Giving up"); return i18n("Error opening file"); } int ndims, nvars, nattr, uldid; status = nc_inq(ncid, &ndims, &nvars, &nattr, &uldid); NetCDFFilterPrivate::handleError(status, "nc_inq"); DEBUG(" nattr/ndims/nvars = " << nattr << ' ' << ndims << ' ' << nvars); if (status == NC_NOERR) { info += i18n("Number of global attributes: %1", QString::number(nattr)); info += QLatin1String("
"); info += i18n("Number of dimensions: %1", QString::number(ndims)); info += QLatin1String("
"); info += i18n("Number of variables: %1", QString::number(nvars)); info += QLatin1String("
"); int format; status = nc_inq_format(ncid, &format); if (status == NC_NOERR) info += i18n("Format version: %1", NetCDFFilterPrivate::translateFormat(format)); info += QLatin1String("
"); info += i18n("Using library version %1", QString(nc_inq_libvers())); } else { info += i18n("Error getting file info"); } status = ncclose(ncid); NetCDFFilterPrivate::handleError(status, "nc_close"); #endif return info; } /*! * Get file content in CDL (Common Data form Language) format * uses "ncdump" */ QString NetCDFFilter::fileCDLString(const QString& fileName) { DEBUG("NetCDFFilter::fileCDLString()"); QString CDLString; #ifdef Q_OS_LINUX auto* proc = new QProcess(); QStringList args; args << "-cs" << fileName; proc->start( "ncdump", args); if (proc->waitForReadyRead(1000) == false) CDLString += i18n("Reading from file %1 failed.", fileName); else { CDLString += proc->readAll(); CDLString.replace('\n', "
\n"); CDLString.replace("\t","    "); - //DEBUG(" CDL string: " << CDLString.toStdString()); + //DEBUG(" CDL string: " << STDSTRING(CDLString)); } #else //TODO: ncdump on Win, Mac Q_UNUSED(fileName) #endif return CDLString; } //##################################################################### //################### Private implementation ########################## //##################################################################### NetCDFFilterPrivate::NetCDFFilterPrivate(NetCDFFilter* owner) : q(owner) { #ifdef HAVE_NETCDF m_status = 0; #endif } #ifdef HAVE_NETCDF void NetCDFFilterPrivate::handleError(int err, const QString& function) { if (err != NC_NOERR) { - DEBUG("NETCDF ERROR:" << function.toStdString() << "() - " << nc_strerror(err)); + DEBUG("NETCDF ERROR:" << STDSTRING(function) << "() - " << nc_strerror(err)); return; } Q_UNUSED(function); } QString NetCDFFilterPrivate::translateDataType(nc_type type) { QString typeString; switch (type) { case NC_BYTE: typeString = "BYTE"; break; case NC_UBYTE: typeString = "UBYTE"; break; case NC_CHAR: typeString = "CHAR"; break; case NC_SHORT: typeString = "SHORT"; break; case NC_USHORT: typeString = "USHORT"; break; case NC_INT: typeString = "INT"; break; case NC_UINT: typeString = "UINT"; break; case NC_INT64: typeString = "INT64"; break; case NC_UINT64: typeString = "UINT64"; break; case NC_FLOAT: typeString = "FLOAT"; break; case NC_DOUBLE: typeString = "DOUBLE"; break; case NC_STRING: typeString = "STRING"; break; default: typeString = "UNKNOWN"; } return typeString; } QString NetCDFFilterPrivate::translateFormat(int format) { QString formatString; switch (format) { case NC_FORMAT_CLASSIC: formatString = "NC_FORMAT_CLASSIC"; break; case NC_FORMAT_64BIT: formatString = "NC_FORMAT_64BIT"; break; case NC_FORMAT_NETCDF4: formatString = "NC_FORMAT_NETCDF4"; break; case NC_FORMAT_NETCDF4_CLASSIC: formatString = "NC_FORMAT_NETCDF4_CLASSIC"; break; } return formatString; } QString NetCDFFilterPrivate::scanAttrs(int ncid, int varid, int attid, QTreeWidgetItem* parentItem) { char name[NC_MAX_NAME + 1]; int nattr, nstart = 0; if (attid == -1) { m_status = nc_inq_varnatts(ncid, varid, &nattr); handleError(m_status, "nc_inq_varnatts"); } else { nstart = attid; nattr = attid + 1; } nc_type type; size_t len; QStringList valueString; for (int i = nstart; i < nattr; i++) { valueString.clear(); m_status = nc_inq_attname(ncid, varid, i, name); handleError(m_status, "nc_inq_attname"); m_status = nc_inq_att(ncid, varid, name, &type, &len); handleError(m_status, "nc_inq_att"); QDEBUG(" attr" << i+1 << "name/type/len =" << name << translateDataType(type) << len); //read attribute switch (type) { case NC_BYTE: { NC_GET_ATT(signed char, schar); break; } case NC_UBYTE: { NC_GET_ATT(unsigned char, uchar); break; } case NC_CHAR: { // not number char *value = (char *)malloc((len+1)*sizeof(char)); m_status = nc_get_att_text(ncid, varid, name, value); handleError(m_status, "nc_get_att_text"); value[len] = 0; valueString << value; free(value); break; } case NC_SHORT: { NC_GET_ATT(short, short); break; } case NC_USHORT: { NC_GET_ATT(unsigned short, ushort); break; } case NC_INT: { NC_GET_ATT(int, int); break; } case NC_UINT: { NC_GET_ATT(unsigned int, uint); break; } case NC_INT64: { NC_GET_ATT(long long, longlong); break; } case NC_UINT64: { NC_GET_ATT(unsigned long long, ulonglong); break; } case NC_FLOAT: { NC_GET_ATT(float, float); break; } case NC_DOUBLE: { NC_GET_ATT(double, double); break; } //TODO: NC_STRING default: valueString << "not supported"; } if (parentItem != nullptr) { QString typeName; if (varid == NC_GLOBAL) typeName = i18n("global attribute"); else { char varName[NC_MAX_NAME + 1]; m_status = nc_inq_varname(ncid, varid, varName); typeName = i18n("%1 attribute", QString(varName)); } QStringList props; props << translateDataType(type) << " (" << QString::number(len) << ")"; QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(name) << typeName << props.join(QString()) << valueString.join(", ")); attrItem->setIcon(0, QIcon::fromTheme("accessories-calculator")); attrItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(attrItem); } } return valueString.join("\n"); } void NetCDFFilterPrivate::scanDims(int ncid, int ndims, QTreeWidgetItem* parentItem) { int ulid; m_status = nc_inq_unlimdim(ncid, &ulid); handleError(m_status, "nc_inq_att"); char name[NC_MAX_NAME + 1]; size_t len; for (int i = 0; i < ndims; i++) { m_status = nc_inq_dim(ncid, i, name, &len); handleError(m_status, "nc_inq_att"); DEBUG(" dim" << i+1 << ": name/len =" << name << len); QStringList props; props<setIcon(0, QIcon::fromTheme("accessories-calculator")); attrItem->setFlags(Qt::ItemIsEnabled); parentItem->addChild(attrItem); } } void NetCDFFilterPrivate::scanVars(int ncid, int nvars, QTreeWidgetItem* parentItem) { char name[NC_MAX_NAME + 1]; nc_type type; int ndims, nattrs; int dimids[NC_MAX_VAR_DIMS]; for (int i = 0; i < nvars; i++) { m_status = nc_inq_var(ncid, i, name, &type, &ndims, dimids, &nattrs); handleError(m_status, "nc_inq_att"); QDEBUG(" var" << i+1 << ": name/type=" << name << translateDataType(type)); DEBUG(" ndims/nattr" << ndims << nattrs); QStringList props; // properties column props << translateDataType(type); char dname[NC_MAX_NAME + 1]; size_t dlen; props << "("; if (ndims == 0) props << QString::number(0); for (int j = 0; j < ndims; j++) { m_status = nc_inq_dim(ncid, dimids[j], dname, &dlen); if (j != 0) props << "x"; props << QString::number(dlen); } props << ")"; QStringList rowStrings; rowStrings << QString(name) << i18n("variable") << props.join(QString()); if (ndims == 0) {// get value of zero dim var switch (type) { case NC_BYTE: { NC_SCAN_VAR(signed char, schar); break; } case NC_UBYTE: { NC_SCAN_VAR(unsigned char, uchar); break; } case NC_CHAR: { // not number char data; m_status = nc_get_var_text(ncid, i, &data); handleError(m_status, "nc_get_var_text"); rowStrings << QString(data); break; } case NC_SHORT: { NC_SCAN_VAR(short, short); break; } case NC_USHORT: { NC_SCAN_VAR(unsigned short, ushort); break; } case NC_INT: { NC_SCAN_VAR(int, int); break; } case NC_UINT: { NC_SCAN_VAR(unsigned int, uint); break; } case NC_INT64: { NC_SCAN_VAR(long long, longlong); break; } case NC_UINT64: { NC_SCAN_VAR(unsigned long long, ulonglong); break; } case NC_DOUBLE: { NC_SCAN_VAR(double, double); break; } case NC_FLOAT: { NC_SCAN_VAR(float, float); break; } } } else { rowStrings << QString(); } auto* varItem = new QTreeWidgetItem(rowStrings); varItem->setIcon(0, QIcon::fromTheme("x-office-spreadsheet")); varItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); // highlight item for (int c = 0; c < varItem->columnCount(); c++) { varItem->setBackground(c, QColor(192, 255, 192)); varItem->setForeground(c, Qt::black); } parentItem->addChild(varItem); scanAttrs(ncid, i, -1, varItem); } } #endif /*! parses the content of the file \c fileName and fill the tree using rootItem. */ void NetCDFFilterPrivate::parse(const QString & fileName, QTreeWidgetItem* rootItem) { DEBUG("NetCDFFilterPrivate::parse()"); #ifdef HAVE_NETCDF QByteArray bafileName = fileName.toLatin1(); DEBUG("fileName = " << bafileName.data()); int ncid; m_status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); handleError(m_status, "nc_open"); if (m_status != NC_NOERR) { DEBUG(" Giving up"); return; } int ndims, nvars, nattr, uldid; m_status = nc_inq(ncid, &ndims, &nvars, &nattr, &uldid); handleError(m_status, "nc_inq"); DEBUG(" nattr/ndims/nvars = " << nattr << ' ' << ndims << ' ' << nvars); QTreeWidgetItem *attrItem = new QTreeWidgetItem(QStringList() << QString(i18n("Attributes"))); attrItem->setIcon(0,QIcon::fromTheme("folder")); attrItem->setFlags(Qt::ItemIsEnabled); rootItem->addChild(attrItem); scanAttrs(ncid, NC_GLOBAL, -1, attrItem); QTreeWidgetItem *dimItem = new QTreeWidgetItem(QStringList() << QString(i18n("Dimensions"))); dimItem->setIcon(0, QIcon::fromTheme("folder")); dimItem->setFlags(Qt::ItemIsEnabled); rootItem->addChild(dimItem); scanDims(ncid, ndims, dimItem); QTreeWidgetItem *varItem = new QTreeWidgetItem(QStringList() << QString(i18n("Variables"))); varItem->setIcon(0, QIcon::fromTheme("folder")); varItem->setFlags(Qt::ItemIsEnabled); rootItem->addChild(varItem); scanVars(ncid, nvars, varItem); m_status = ncclose(ncid); handleError(m_status, "nc_close"); #else Q_UNUSED(fileName) Q_UNUSED(rootItem) #endif } QString NetCDFFilterPrivate::readAttribute(const QString & fileName, const QString & name, const QString & varName) { #ifdef HAVE_NETCDF int ncid; QByteArray bafileName = fileName.toLatin1(); m_status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); handleError(m_status, "nc_open"); if (m_status != NC_NOERR) { DEBUG(" Giving up"); return QString(); } // get varid int varid; if (varName == "global") varid = NC_GLOBAL; else { QByteArray bavarName = varName.toLatin1(); m_status = nc_inq_varid(ncid, bavarName.data(), &varid); handleError(m_status, "nc_inq_varid"); } // attribute 'name' int attid; QByteArray baName = name.toLatin1(); m_status = nc_inq_attid(ncid, varid, baName.data(), &attid); handleError(m_status, "nc_inq_attid"); QString nameString = scanAttrs(ncid, varid, attid); m_status = ncclose(ncid); handleError(m_status, "nc_close"); return nameString; #else Q_UNUSED(fileName) Q_UNUSED(name) Q_UNUSED(varName) return QString(); #endif } /*! reads the content of the variable in the file \c fileName to a string (for preview) or to the data source. */ QVector NetCDFFilterPrivate::readCurrentVar(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode, int lines) { QVector dataStrings; if (currentVarName.isEmpty()) return dataStrings << (QStringList() << i18n("No variable selected")); - DEBUG(" current variable = " << currentVarName.toStdString()); + DEBUG(" current variable = " << STDSTRING(currentVarName)); #ifdef HAVE_NETCDF int ncid; QByteArray bafileName = fileName.toLatin1(); m_status = nc_open(bafileName.data(), NC_NOWRITE, &ncid); handleError(m_status, "nc_open"); if (m_status != NC_NOERR) { DEBUG(" Giving up"); return dataStrings; } int varid; QByteArray baVarName = currentVarName.toLatin1(); m_status = nc_inq_varid(ncid, baVarName.data(), &varid); handleError(m_status, "nc_inq_varid"); int ndims; nc_type type; m_status = nc_inq_varndims(ncid, varid, &ndims); handleError(m_status, "nc_inq_varndims"); m_status = nc_inq_vartype(ncid, varid, &type); handleError(m_status, "nc_inq_type"); int* dimids = (int *) malloc(ndims * sizeof(int)); m_status = nc_inq_vardimid(ncid, varid, dimids); handleError(m_status, "nc_inq_vardimid"); int actualRows = 0, actualCols = 0; int columnOffset = 0; std::vector dataContainer; switch (ndims) { case 0: { DEBUG(" zero dimensions"); actualRows = actualCols = 1; // only one value QVector columnModes; columnModes.resize(actualCols); switch (type) { case NC_BYTE: case NC_UBYTE: case NC_SHORT: case NC_USHORT: case NC_INT: columnModes[0] = AbstractColumn::Integer; break; case NC_UINT: // converted to double (int is too small) case NC_INT64: columnModes[0] = AbstractColumn::BigInt; break; case NC_UINT64: // converted to double (int is too small) case NC_DOUBLE: case NC_FLOAT: columnModes[0] = AbstractColumn::Numeric; break; case NC_CHAR: columnModes[0] = AbstractColumn::Text; break; //TODO: NC_STRING } //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); - DEBUG(" Reading data of type " << translateDataType(type).toStdString()); + DEBUG(" Reading data of type " << STDSTRING(translateDataType(type))); switch (type) { case NC_BYTE: { NC_READ_VAR(signed char, schar, int); break; } case NC_UBYTE: { NC_READ_VAR(unsigned char, uchar, int); break; } case NC_CHAR: { // no number char data; m_status = nc_get_var_text(ncid, varid, &data); handleError(m_status, "nc_get_var_text"); if (dataSource) { QString *sourceData = static_cast*>(dataContainer[0])->data(); sourceData[0] = QString(data); } else { // preview dataStrings << (QStringList() << QString(data)); } break; } case NC_SHORT: { NC_READ_VAR(short, short, int); break; } case NC_USHORT: { NC_READ_VAR(unsigned short, ushort, int); break; } case NC_INT: { NC_READ_VAR(int, int, int); break; } case NC_UINT: { NC_READ_VAR(unsigned int, uint, double); break; } // converted to double (int is too small) case NC_INT64: { NC_READ_VAR(long long, longlong, double); break; } // converted to double (int is too small) case NC_UINT64: { NC_READ_VAR(unsigned long long, ulonglong, double); break; } // converted to double (int is too small) case NC_DOUBLE: { NC_READ_VAR(double, double, double); break; } case NC_FLOAT: { NC_READ_VAR(float, float, double); break; } } break; } case 1: { size_t size; m_status = nc_inq_dimlen(ncid, dimids[0], &size); handleError(m_status, "nc_inq_dimlen"); if (endRow == -1) endRow = (int)size; if (lines == -1) lines = endRow; actualRows = endRow - startRow + 1; actualCols = 1; // only one column DEBUG("start/end row: " << startRow << ' ' << endRow); DEBUG("act rows/cols: " << actualRows << ' ' << actualCols); QVector columnModes; columnModes.resize(actualCols); switch (type) { case NC_BYTE: case NC_UBYTE: case NC_SHORT: case NC_USHORT: case NC_INT: columnModes[0] = AbstractColumn::Integer; break; case NC_UINT: // converted to double (int is too small) case NC_INT64: columnModes[0] = AbstractColumn::BigInt; break; case NC_UINT64: // converted to double (int is too small) case NC_DOUBLE: case NC_FLOAT: columnModes[0] = AbstractColumn::Numeric; break; case NC_CHAR: columnModes[0] = AbstractColumn::Text; break; //TODO: NC_STRING } //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); - DEBUG(" Reading data of type " << translateDataType(type).toStdString()); + DEBUG(" Reading data of type " << STDSTRING(translateDataType(type))); switch (type) { case NC_BYTE: { NC_READ_AVAR(signed char, schar, int); break; } case NC_UBYTE: { NC_READ_AVAR(unsigned char, uchar, int); break; } case NC_CHAR: { // not number char* data = new char[(unsigned int)actualRows]; size_t start = (size_t)(startRow - 1), count = (size_t)actualRows; m_status = nc_get_vara_text(ncid, varid, &start, &count, data); handleError(m_status, "nc_get_vara_text"); if (dataSource) { QString *sourceData = static_cast*>(dataContainer[0])->data(); for (int i = 0; i < actualRows; i++) sourceData[i] = QString(data[i]); } else { // preview for (int i = 0; i < qMin(actualRows, lines); i++) dataStrings << (QStringList() << QString(data[i])); } delete[] data; break; } case NC_SHORT: { NC_READ_AVAR(short, short, int); break; } case NC_USHORT: { NC_READ_AVAR(unsigned short, ushort, int); break; } case NC_INT: { NC_READ_AVAR_NATIVE(int); break; } case NC_UINT: { NC_READ_AVAR(unsigned int, uint, double); break; } // converted to double (int is too small) case NC_INT64: { NC_READ_AVAR(long long, longlong, double); break; } // converted to double (int is too small) case NC_UINT64: { NC_READ_AVAR(unsigned long long, ulonglong, double); break; } // converted to double (int is too small) case NC_DOUBLE: { NC_READ_AVAR_NATIVE(double); break; } case NC_FLOAT: { NC_READ_AVAR(float, float, double); break; } //TODO: NC_STRING default: DEBUG(" data type not supported yet"); } break; } case 2: { size_t rows, cols; m_status = nc_inq_dimlen(ncid, dimids[0], &rows); handleError(m_status, "nc_inq_dimlen"); m_status = nc_inq_dimlen(ncid, dimids[1], &cols); handleError(m_status, "nc_inq_dimlen"); if (endRow == -1) endRow = (int)rows; if (lines == -1) lines = endRow; if (endColumn == -1) endColumn = (int)cols; actualRows = endRow-startRow+1; actualCols = endColumn-startColumn+1; DEBUG("dim = " << rows << "x" << cols); DEBUG("startRow/endRow: " << startRow << ' ' << endRow); DEBUG("startColumn/endColumn: " << startColumn << ' ' << endColumn); DEBUG("actual rows/cols: " << actualRows << ' ' << actualCols); DEBUG("lines: " << lines); QVector columnModes; columnModes.resize(actualCols); switch (type) { case NC_BYTE: case NC_UBYTE: case NC_SHORT: case NC_USHORT: case NC_INT: for (int i = 0; i < actualCols; i++) columnModes[i] = AbstractColumn::Integer; break; case NC_UINT: // converted to double (int is too small) case NC_INT64: for (int i = 0; i < actualCols; i++) columnModes[i] = AbstractColumn::BigInt; break; case NC_UINT64: // converted to double (int is too small) case NC_DOUBLE: case NC_FLOAT: for (int i = 0; i < actualCols; i++) columnModes[i] = AbstractColumn::Numeric; break; case NC_CHAR: for (int i = 0; i < actualCols; i++) columnModes[i] = AbstractColumn::Text; break; //TODO: NC_STRING } //TODO: use given names? QStringList vectorNames; if (dataSource) columnOffset = dataSource->prepareImport(dataContainer, mode, actualRows, actualCols, vectorNames, columnModes); switch (type) { case NC_BYTE: { NC_READ_VAR2(signed char, schar, int); break; } case NC_UBYTE: { NC_READ_VAR2(unsigned char, uchar, int); break; } case NC_CHAR: { // no number char** data = (char**) malloc(rows * sizeof(char*)); data[0] = (char*)malloc(cols * rows * sizeof(char)); for (unsigned int i = 1; i < rows; i++) data[i] = data[0] + i*cols; m_status = nc_get_var_text(ncid, varid, &data[0][0]); handleError(m_status, "nc_get_var_text"); if (m_status == NC_NOERR) { for (int i = 0; i < qMin((int)rows, lines); i++) { QStringList line; for (size_t j = 0; j < cols; j++) { if (dataSource && dataContainer[0]) static_cast*>(dataContainer[(int)(j-(size_t)startColumn+1)])->operator[](i-startRow+1) = data[i][(int)j]; else line << QString(data[i][j]); } dataStrings << line; emit q->completed(100*i/actualRows); } } free(data[0]); free(data); break; } case NC_SHORT: { NC_READ_VAR2(short, short, int); break; } case NC_USHORT: { NC_READ_VAR2(unsigned short, ushort, int); break; } case NC_INT: { NC_READ_VAR2(int, int, int); break; } case NC_UINT: { NC_READ_VAR2(unsigned int, uint, double); break; } // converted to double (int is too small) case NC_INT64: { NC_READ_VAR2(long long, longlong, double); break; } // converted to double (int is too small) case NC_UINT64: { NC_READ_VAR2(unsigned long long, ulonglong, double); break; } // converted to double (int is too small) case NC_FLOAT: { NC_READ_VAR2(float, float, double); break; } case NC_DOUBLE: { NC_READ_VAR2(double, double, double); break; } //TODO: NC_STRING default: DEBUG(" data type not supported yet"); } break; } default: dataStrings << (QStringList() << i18n("%1 dimensional data of type %2 not supported yet", ndims, translateDataType(type))); QDEBUG(dataStrings); } free(dimids); m_status = ncclose(ncid); handleError(m_status, "nc_close"); if (dataSource) dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), mode); #else Q_UNUSED(fileName) Q_UNUSED(dataSource) Q_UNUSED(mode) Q_UNUSED(lines) #endif return dataStrings; } /*! reads the content of the current selected variable from file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ QVector NetCDFFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode mode) { QVector dataStrings; if (currentVarName.isEmpty()) { DEBUG(" No variable selected"); return dataStrings; } return readCurrentVar(fileName, dataSource, mode); } /*! writes the content of \c dataSource to the file \c fileName. */ void NetCDFFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: write NetCDF files not implemented yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void NetCDFFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement("netcdfFilter"); writer->writeEndElement(); } /*! Loads from XML. */ bool NetCDFFilter::load(XmlStreamReader* reader) { Q_UNUSED(reader); // KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); // QXmlStreamAttributes attribs = reader->attributes(); return true; } diff --git a/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp b/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp index 24e03c9ce..a21670622 100644 --- a/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp +++ b/src/backend/datasources/filters/NgspiceRawAsciiFilter.cpp @@ -1,386 +1,386 @@ /*************************************************************************** File : NgspiceRawAsciiFilter.cpp Project : LabPlot Description : Ngspice RAW ASCII filter -------------------------------------------------------------------- Copyright : (C) 2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/NgspiceRawAsciiFilter.h" #include "backend/datasources/filters/NgspiceRawAsciiFilterPrivate.h" #include "backend/lib/trace.h" #include /*! \class NgspiceRawAsciiFilter \brief Import of data stored in Ngspice's raw formant, ASCCI version of it. \ingroup datasources */ NgspiceRawAsciiFilter::NgspiceRawAsciiFilter() : AbstractFileFilter(NgspiceRawAscii), d(new NgspiceRawAsciiFilterPrivate(this)) {} NgspiceRawAsciiFilter::~NgspiceRawAsciiFilter() = default; bool NgspiceRawAsciiFilter::isNgspiceAsciiFile(const QString& fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - DEBUG("Failed to open the file " << fileName.toStdString()); + DEBUG("Failed to open the file " << STDSTRING(fileName)); return false; } QString line = file.readLine(); if (!line.startsWith(QLatin1String("Title:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Date:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Plotname:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Flags:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("No. Variables:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("No. Points:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Variables:"))) return false; return true; } QString NgspiceRawAsciiFilter::fileInfoString(const QString& fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return QString(); QString info; while (!file.atEnd()) { QString line = file.readLine(); if (line.simplified() == QLatin1String("Values:")) break; if (!info.isEmpty()) info += QLatin1String("
"); info += line; } return info; } /*! reads the content of the file \c fileName. */ void NgspiceRawAsciiFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } QVector NgspiceRawAsciiFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void NgspiceRawAsciiFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); } /*! loads the predefined filter settings for \c filterName */ void NgspiceRawAsciiFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void NgspiceRawAsciiFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } void NgspiceRawAsciiFilter::setStartRow(const int r) { d->startRow = r; } int NgspiceRawAsciiFilter::startRow() const { return d->startRow; } void NgspiceRawAsciiFilter::setEndRow(const int r) { d->endRow = r; } int NgspiceRawAsciiFilter::endRow() const { return d->endRow; } QStringList NgspiceRawAsciiFilter::vectorNames() const { return d->vectorNames; } QVector NgspiceRawAsciiFilter::columnModes() { return d->columnModes; } //##################################################################### //################### Private implementation ########################## //##################################################################### NgspiceRawAsciiFilterPrivate::NgspiceRawAsciiFilterPrivate(NgspiceRawAsciiFilter* owner) : q(owner) { } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void NgspiceRawAsciiFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { - DEBUG("NgspiceRawAsciiFilterPrivate::readDataFromFile(): fileName = \'" << fileName.toStdString() << "\', dataSource = " + DEBUG("NgspiceRawAsciiFilterPrivate::readDataFromFile(): fileName = \'" << STDSTRING(fileName) << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode)); QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - DEBUG("Failed to open the file " << fileName.toStdString()); + DEBUG("Failed to open the file " << STDSTRING(fileName)); return; } //skip the first three lines in the header file.readLine(); //"Title" file.readLine(); //"Date" file.readLine(); //"Plotname" //evaluate the "Flags" line to check whether we have complex numbers QString line = file.readLine(); bool hasComplexValues = line.endsWith(QLatin1String("complex\n")); //number of variables line = file.readLine(); const int vars = line.rightRef(line.length() - 15).toInt(); //remove the "No. Variables: " sub-string //number of points line = file.readLine(); const int points = line.rightRef(line.length() - 12).toInt(); //remove the "No. Points: " sub-string //add names of the variables vectorNames.clear(); columnModes.clear(); file.readLine(); for (int i = 0; i < vars; ++i) { line = file.readLine(); QStringList tokens = line.split('\t'); //skip lines that don't contain the proper number of tokens (wrong format, corrupted file) if (tokens.size() < 4) continue; QString name = tokens.at(2) + QLatin1String(", ") + tokens.at(3).simplified(); if (hasComplexValues) { vectorNames << name + QLatin1String(" REAL"); vectorNames << name + QLatin1String(" IMAGINARY"); columnModes << AbstractColumn::Numeric; columnModes << AbstractColumn::Numeric; } else { vectorNames << name; columnModes << AbstractColumn::Numeric; } } file.readLine(); //skip the line with "Values" //prepare the data container const int actualEndRow = (endRow == -1 || endRow > points) ? points : endRow; const int actualRows = actualEndRow - startRow + 1; const int actualCols = hasComplexValues ? vars*2 : vars; const int columnOffset = dataSource->prepareImport(m_dataContainer, importMode, actualRows, actualCols, vectorNames, columnModes); //skip data lines, if required DEBUG(" Skipping " << startRow - 1 << " lines"); for (int i = 0; i < startRow - 1; ++i) { for (int j = 0; j < vars; ++j) file.readLine(); file.readLine(); //skip the empty line after each value block } //read the data points QStringList lineString; int currentRow = 0; // indexes the position in the vector(column) QLocale locale(QLocale::C); bool isNumber(false); for (int i = 0; i < actualRows; ++i) { lineString.clear(); for (int j = 0; j < vars; ++j) { line = file.readLine(); QStringList tokens = line.split(QLatin1Char('\t')); //skip lines that don't contain the proper number of tokens (wrong format, corrupted file) if (tokens.size() < 2) continue; QString valueString = tokens.at(1).simplified(); //string containing the value(s) if (hasComplexValues) { QStringList realImgTokens = valueString.split(QLatin1Char(',')); if (realImgTokens.size() == 2) { //sanity check to make sure we really have both parts //real part double value = locale.toDouble(realImgTokens.at(0), &isNumber); static_cast*>(m_dataContainer[2*j])->operator[](currentRow) = (isNumber ? value : NAN); //imaginary part value = locale.toDouble(realImgTokens.at(1), &isNumber); static_cast*>(m_dataContainer[2*j+1])->operator[](currentRow) = (isNumber ? value : NAN); } } else { const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[j])->operator[](currentRow) = (isNumber ? value : NAN); } } file.readLine(); //skip the empty line after each value block currentRow++; emit q->completed(100 * currentRow/actualRows); } dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); } /*! * generates the preview for the file \c fileName reading the provided number of \c lines. */ QVector NgspiceRawAsciiFilterPrivate::preview(const QString& fileName, int lines) { QVector dataStrings; QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - DEBUG("Failed to open the file " << fileName.toStdString()); + DEBUG("Failed to open the file " << STDSTRING(fileName)); return dataStrings; } //skip the first three lines in the header file.readLine(); //"Title" file.readLine(); //"Date" file.readLine(); //"Plotname" //evaluate the "Flags" line to check whether we have complex numbers QString line = file.readLine(); bool hasComplexValues = line.endsWith(QLatin1String("complex\n")); //number of variables line = file.readLine(); const int vars = line.rightRef(line.length() - 15).toInt(); //remove the "No. Variables: " sub-string //number of points line = file.readLine(); const int points = line.rightRef(line.length() - 12).toInt(); //remove the "No. Points: " sub-string //add names of the variables vectorNames.clear(); columnModes.clear(); file.readLine(); for (int i = 0; i < vars; ++i) { line = file.readLine(); QStringList tokens = line.split('\t'); //skip lines that don't contain the proper number of tokens (wrong format, corrupted file) if (tokens.size() < 4) continue; QString name = tokens.at(2) + QLatin1String(", ") + tokens.at(3).simplified(); if (hasComplexValues) { vectorNames << name + QLatin1String(" REAL"); vectorNames << name + QLatin1String(" IMAGINARY"); columnModes << AbstractColumn::Numeric; columnModes << AbstractColumn::Numeric; } else { vectorNames << name; columnModes << AbstractColumn::Numeric; } } file.readLine(); //skip the line with "Values" //read the data points QStringList lineString; for (int i = 0; i < qMin(lines, points); ++i) { lineString.clear(); for (int j = 0; j < vars; ++j) { line = file.readLine(); QStringList tokens = line.split(QLatin1Char('\t')); //skip lines that don't contain the proper number of tokens (wrong format, corrupted file) if (tokens.size() < 2) continue; QString value = tokens.at(1).simplified(); //string containing the value(s) if (hasComplexValues) { QStringList realImgTokens = value.split(QLatin1Char(',')); if (realImgTokens.size() == 2) { //sanity check to make sure we really have both parts lineString << realImgTokens.at(0); //real part lineString << realImgTokens.at(1); //imaginary part } } else lineString << value; } dataStrings << lineString; file.readLine(); //skip the empty line after each value block } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void NgspiceRawAsciiFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: not implemented yet } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void NgspiceRawAsciiFilter::save(QXmlStreamWriter* writer) const { Q_UNUSED(writer); } /*! Loads from XML. */ bool NgspiceRawAsciiFilter::load(XmlStreamReader* reader) { Q_UNUSED(reader); return true; } diff --git a/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp b/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp index 9fe119a20..28fa1b1cb 100644 --- a/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp +++ b/src/backend/datasources/filters/NgspiceRawBinaryFilter.cpp @@ -1,358 +1,358 @@ /*************************************************************************** File : NgspiceRawBinaryFilter.cpp Project : LabPlot Description : Ngspice RAW Binary filter -------------------------------------------------------------------- Copyright : (C) 2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/NgspiceRawBinaryFilter.h" #include "backend/datasources/filters/NgspiceRawBinaryFilterPrivate.h" #include "backend/lib/trace.h" #include /*! \class NgspiceRawBinaryFilter \brief Import of data stored in Ngspice's raw formant, ASCCI version of it. \ingroup datasources */ NgspiceRawBinaryFilter::NgspiceRawBinaryFilter() : AbstractFileFilter(NgspiceRawBinary), d(new NgspiceRawBinaryFilterPrivate(this)) {} NgspiceRawBinaryFilter::~NgspiceRawBinaryFilter() = default; bool NgspiceRawBinaryFilter::isNgspiceBinaryFile(const QString& fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - DEBUG("Failed to open the file " << fileName.toStdString()); + DEBUG("Failed to open the file " << STDSTRING(fileName)); return false; } QString line = file.readLine(); if (!line.startsWith(QLatin1String("Title:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Date:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Plotname:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Flags:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("No. Variables:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("No. Points:"))) return false; line = file.readLine(); if (!line.startsWith(QLatin1String("Variables:"))) return false; return true; } QString NgspiceRawBinaryFilter::fileInfoString(const QString& fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return QString(); QString info; while (!file.atEnd()) { QString line = file.readLine(); if (line.simplified() == QLatin1String("Binary:")) break; if (!info.isEmpty()) info += QLatin1String("
"); info += line; } return info; } /*! reads the content of the file \c fileName. */ void NgspiceRawBinaryFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { d->readDataFromFile(fileName, dataSource, importMode); } QVector NgspiceRawBinaryFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } /*! writes the content of the data source \c dataSource to the file \c fileName. */ void NgspiceRawBinaryFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); } /*! loads the predefined filter settings for \c filterName */ void NgspiceRawBinaryFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void NgspiceRawBinaryFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } void NgspiceRawBinaryFilter::setStartRow(const int r) { d->startRow = r; } int NgspiceRawBinaryFilter::startRow() const { return d->startRow; } void NgspiceRawBinaryFilter::setEndRow(const int r) { d->endRow = r; } int NgspiceRawBinaryFilter::endRow() const { return d->endRow; } QStringList NgspiceRawBinaryFilter::vectorNames() const { return d->vectorNames; } QVector NgspiceRawBinaryFilter::columnModes() { return d->columnModes; } //##################################################################### //################### Private implementation ########################## //##################################################################### NgspiceRawBinaryFilterPrivate::NgspiceRawBinaryFilterPrivate(NgspiceRawBinaryFilter* owner) : q(owner) { } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void NgspiceRawBinaryFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { - DEBUG("NgspiceRawBinaryFilterPrivate::readDataFromFile(): fileName = \'" << fileName.toStdString() << "\', dataSource = " + DEBUG("NgspiceRawBinaryFilterPrivate::readDataFromFile(): fileName = \'" << STDSTRING(fileName) << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode)); QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - DEBUG("Failed to open the file " << fileName.toStdString()); + DEBUG("Failed to open the file " << STDSTRING(fileName)); return; } //skip the first three lines in the header file.readLine(); //"Title" file.readLine(); //"Date" file.readLine(); //"Plotname" //evaluate the "Flags" line to check whether we have complex numbers QString line = file.readLine(); bool hasComplexValues = line.endsWith(QLatin1String("complex\n")); //number of variables line = file.readLine(); const int vars = line.rightRef(line.length() - 15).toInt(); //remove the "No. Variables: " sub-string //number of points line = file.readLine(); const int points = line.rightRef(line.length() - 12).toInt(); //remove the "No. Points: " sub-string //add names of the variables vectorNames.clear(); columnModes.clear(); file.readLine(); for (int i = 0; i points) ? points : endRow; const int actualRows = actualEndRow - startRow + 1; const int actualCols = hasComplexValues ? 2 * vars : vars; const int columnOffset = dataSource->prepareImport(m_dataContainer, importMode, actualRows, actualCols, vectorNames, columnModes); //skip data lines, if required const int skip = hasComplexValues ? 2 * vars * (startRow - 1) : vars * (startRow - 1); if (skip > 0) { DEBUG(" Skipping " << startRow - 1 << " lines"); file.read(BYTE_SIZE * skip); } //read the data points int currentRow = 0; // indexes the position in the vector(column) for (int i = 0; i < actualRows; ++i) { for (int j = 0; j < vars; ++j) { double value; QDataStream s(file.read(BYTE_SIZE)); s.setByteOrder(QDataStream::LittleEndian); s >> value; if (hasComplexValues) { //real part static_cast*>(m_dataContainer[2*j])->operator[](currentRow) = value; //imaginary part QDataStream sim(file.read(BYTE_SIZE)); sim.setByteOrder(QDataStream::LittleEndian); sim >> value; static_cast*>(m_dataContainer[2*j+1])->operator[](currentRow) = value; } else static_cast*>(m_dataContainer[j])->operator[](currentRow) = value; } currentRow++; emit q->completed(100 * currentRow/actualRows); } dataSource->finalizeImport(columnOffset, 1, actualCols, QString(), importMode); } /*! * generates the preview for the file \c fileName reading the provided number of \c lines. */ QVector NgspiceRawBinaryFilterPrivate::preview(const QString& fileName, int lines) { DEBUG("NgspiceRawBinaryFilterPrivate::preview()"); QVector dataStrings; QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - DEBUG("Failed to open the file " << fileName.toStdString()); + DEBUG("Failed to open the file " << STDSTRING(fileName)); return dataStrings; } //skip the first three lines in the header file.readLine(); //"Title" file.readLine(); //"Date" file.readLine(); //"Plotname" //evaluate the "Flags" line to check whether we have complex numbers QString line = file.readLine(); bool hasComplexValues = line.endsWith(QLatin1String("complex\n")); //number of variables line = file.readLine(); const int vars = line.rightRef(line.length() - 15).toInt(); //remove the "No. Variables: " sub-string DEBUG(" vars = " << vars); //number of points line = file.readLine(); const int points = line.rightRef(line.length() - 12).toInt(); //remove the "No. Points: " sub-string DEBUG(" points = " << points); //add names of the variables vectorNames.clear(); columnModes.clear(); file.readLine(); for (int i = 0; i < vars; ++i) { line = file.readLine(); QStringList tokens = line.split('\t'); QString name = tokens.at(2) + QLatin1String(", ") + tokens.at(3).simplified(); if (hasComplexValues) { vectorNames << name + QLatin1String(" REAL"); vectorNames << name + QLatin1String(" IMAGINARY"); columnModes << AbstractColumn::Numeric; columnModes << AbstractColumn::Numeric; } else { vectorNames << name; columnModes << AbstractColumn::Numeric; } } file.readLine(); //skip the line with "Binary" //read the binary data file.setTextModeEnabled(false); QStringList lineString; for (int i = 0; i < qMin(lines, points); ++i) { lineString.clear(); for (int j = 0; j < vars; ++j) { double v; QDataStream s(file.read(BYTE_SIZE)); s.setByteOrder(QDataStream::LittleEndian); s >> v; lineString << QString::number(v, 'e', 15); //real part if (hasComplexValues) { QDataStream sim(file.read(BYTE_SIZE)); sim.setByteOrder(QDataStream::LittleEndian); sim >> v; lineString << QString::number(v, 'e', 15); //imaginary part } } dataStrings << lineString; } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void NgspiceRawBinaryFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void NgspiceRawBinaryFilter::save(QXmlStreamWriter* writer) const { Q_UNUSED(writer); } /*! Loads from XML. */ bool NgspiceRawBinaryFilter::load(XmlStreamReader* reader) { Q_UNUSED(reader); return true; } diff --git a/src/backend/datasources/projects/OriginProjectParser.cpp b/src/backend/datasources/projects/OriginProjectParser.cpp index f0a5badaa..395f59c7f 100644 --- a/src/backend/datasources/projects/OriginProjectParser.cpp +++ b/src/backend/datasources/projects/OriginProjectParser.cpp @@ -1,2143 +1,2143 @@ /*************************************************************************** File : OriginProjectParser.h Project : LabPlot Description : parser for Origin projects -------------------------------------------------------------------- Copyright : (C) 2017-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2019 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/projects/OriginProjectParser.h" #include "backend/core/column/Column.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/core/Project.h" #include "backend/core/Workbook.h" #include "backend/matrix/Matrix.h" #include "backend/note/Note.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/XYEquationCurve.h" #include "backend/worksheet/TextLabel.h" #include #include #include #include #include #include /*! \class OriginProjectParser \brief parser for Origin projects. \ingroup datasources */ OriginProjectParser::OriginProjectParser() : ProjectParser() { m_topLevelClasses = {AspectType::Folder, AspectType::Workbook, AspectType::Spreadsheet, AspectType::Matrix, AspectType::Worksheet, AspectType::Note}; } bool OriginProjectParser::isOriginProject(const QString& fileName) { //TODO add opju later when liborigin supports it return fileName.endsWith(QLatin1String(".opj"), Qt::CaseInsensitive); } void OriginProjectParser::setImportUnusedObjects(bool importUnusedObjects) { m_importUnusedObjects = importUnusedObjects; } bool OriginProjectParser::hasUnusedObjects() { m_originFile = new OriginFile((const char*)m_projectFileName.toLocal8Bit()); if (!m_originFile->parse()) { delete m_originFile; m_originFile = nullptr; return false; } for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { const Origin::SpreadSheet& spread = m_originFile->spread(i); if (spread.objectID < 0) return true; } for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { const Origin::Excel& excel = m_originFile->excel(i); if (excel.objectID < 0) return true; } for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { const Origin::Matrix& originMatrix = m_originFile->matrix(i); if (originMatrix.objectID < 0) return true; } delete m_originFile; m_originFile = nullptr; return false; } QString OriginProjectParser::supportedExtensions() { //TODO add opju later when liborigin supports it static const QString extensions = "*.opj *.OPJ"; return extensions; } unsigned int OriginProjectParser::findSpreadByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { const Origin::SpreadSheet& spread = m_originFile->spread(i); if (spread.name == name.toStdString()) { m_spreadNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findMatrixByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { const Origin::Matrix& originMatrix = m_originFile->matrix(i); if (originMatrix.name == name.toStdString()) { m_matrixNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findExcelByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { const Origin::Excel& excel = m_originFile->excel(i); if (excel.name == name.toStdString()) { m_excelNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findGraphByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->graphCount(); i++) { const Origin::Graph& graph = m_originFile->graph(i); if (graph.name == name.toStdString()) { m_graphNameList << name; return i; } } return 0; } unsigned int OriginProjectParser::findNoteByName(const QString& name) { for (unsigned int i = 0; i < m_originFile->noteCount(); i++) { const Origin::Note& originNote = m_originFile->note(i); if (originNote.name == name.toStdString()) { m_noteNameList << name; return i; } } return 0; } //############################################################################## //############## Deserialization from Origin's project tree #################### //############################################################################## bool OriginProjectParser::load(Project* project, bool preview) { DEBUG("OriginProjectParser::load()"); //read and parse the m_originFile-file m_originFile = new OriginFile((const char*)m_projectFileName.toLocal8Bit()); if (!m_originFile->parse()) { delete m_originFile; m_originFile = nullptr; return false; } //Origin project tree and the iterator pointing to the root node const tree* projectTree = m_originFile->project(); tree::iterator projectIt = projectTree->begin(projectTree->begin()); m_spreadNameList.clear(); m_excelNameList.clear(); m_matrixNameList.clear(); m_graphNameList.clear(); m_noteNameList.clear(); //convert the project tree from liborigin's representation to LabPlot's project object project->setIsLoading(true); if (projectIt.node) { // only opj files from version >= 6.0 do have project tree DEBUG(" have a project tree"); QString name(QString::fromLatin1(projectIt->name.c_str())); project->setName(name); project->setCreationTime(creationTime(projectIt)); loadFolder(project, projectIt, preview); } else { // for lower versions put all windows on rootfolder DEBUG(" have no project tree"); int pos = m_projectFileName.lastIndexOf(QDir::separator()) + 1; project->setName((const char*)m_projectFileName.mid(pos).toLocal8Bit()); } // imports all loose windows (like prior version 6 which has no project tree) handleLooseWindows(project, preview); //restore column pointers: //1. extend the pathes to contain the parent structures first //2. restore the pointers from the pathes const QVector columns = project->children(AbstractAspect::Recursive); const QVector spreadsheets = project->children(AbstractAspect::Recursive); for (auto* curve : project->children(AbstractAspect::Recursive)) { curve->suppressRetransform(true); //x-column QString spreadsheetName = curve->xColumnPath().left(curve->xColumnPath().indexOf(QLatin1Char('/'))); for (const auto* spreadsheet : spreadsheets) { if (spreadsheet->name() == spreadsheetName) { const QString& newPath = spreadsheet->parentAspect()->path() + '/' + curve->xColumnPath(); curve->setXColumnPath(newPath); for (auto* column : columns) { if (!column) continue; if (column->path() == newPath) { curve->setXColumn(column); break; } } break; } } //x-column spreadsheetName = curve->yColumnPath().left(curve->yColumnPath().indexOf(QLatin1Char('/'))); for (const auto* spreadsheet : spreadsheets) { if (spreadsheet->name() == spreadsheetName) { const QString& newPath = spreadsheet->parentAspect()->path() + '/' + curve->yColumnPath(); curve->setYColumnPath(newPath); for (auto* column : columns) { if (!column) continue; if (column->path() == newPath) { curve->setYColumn(column); break; } } break; } } //TODO: error columns curve->suppressRetransform(false); } if (!preview) { for (auto* plot : project->children(AbstractAspect::Recursive)) { plot->setIsLoading(false); plot->retransform(); } } emit project->loaded(); project->setIsLoading(false); delete m_originFile; m_originFile = nullptr; return true; } bool OriginProjectParser::loadFolder(Folder* folder, tree::iterator baseIt, bool preview) { DEBUG("OriginProjectParser::loadFolder()") const tree* projectTree = m_originFile->project(); // do not skip anything if pathesToLoad() contains only root folder bool containsRootFolder = (folder->pathesToLoad().size() == 1 && folder->pathesToLoad().contains(folder->path())); if (containsRootFolder) { - DEBUG(" pathesToLoad contains only folder path \"" << folder->path().toStdString() << "\". Clearing pathes to load.") + DEBUG(" pathesToLoad contains only folder path \"" << STDSTRING(folder->path()) << "\". Clearing pathes to load.") folder->setPathesToLoad(QStringList()); } //load folder's children: logic for reading the selected objects only is similar to Folder::readChildAspectElement for (tree::sibling_iterator it = projectTree->begin(baseIt); it != projectTree->end(baseIt); ++it) { QString name(QString::fromLatin1(it->name.c_str())); //name of the current child - DEBUG(" * folder item name = " << name.toStdString()) + DEBUG(" * folder item name = " << STDSTRING(name)) //check whether we need to skip the loading of the current child if (!folder->pathesToLoad().isEmpty()) { //child's path is not available yet (child not added yet) -> construct the path manually const QString childPath = folder->path() + '/' + name; - DEBUG(" path = " << childPath.toStdString()) + DEBUG(" path = " << STDSTRING(childPath)) //skip the current child aspect it is not in the list of aspects to be loaded if (folder->pathesToLoad().indexOf(childPath) == -1) { DEBUG(" skip it!") continue; } } //load top-level children AbstractAspect* aspect = nullptr; switch (it->type) { case Origin::ProjectNode::Folder: { DEBUG(" top level folder"); Folder* f = new Folder(name); if (!folder->pathesToLoad().isEmpty()) { //a child folder to be read -> provide the list of aspects to be loaded to the child folder, too. //since the child folder and all its children are not added yet (path() returns empty string), //we need to remove the path of the current child folder from the full pathes provided in pathesToLoad. //E.g. we want to import the path "Project/Folder/Spreadsheet" in the following project // Project // \Spreadsheet // \Folder // \Spreadsheet // //Here, we remove the part "Project/Folder/" and proceed for this child folder with "Spreadsheet" only. //With this the logic above where it is determined whether to import the child aspect or not works out. //manually construct the path of the child folder to be read const QString& curFolderPath = folder->path() + '/' + name; //remove the path of the current child folder QStringList pathesToLoadNew; for (const auto& path : folder->pathesToLoad()) { if (path.startsWith(curFolderPath)) pathesToLoadNew << path.right(path.length() - curFolderPath.length()); } f->setPathesToLoad(pathesToLoadNew); } loadFolder(f, it, preview); aspect = f; break; } case Origin::ProjectNode::SpreadSheet: { DEBUG(" top level spreadsheet"); Spreadsheet* spreadsheet = new Spreadsheet(name); loadSpreadsheet(spreadsheet, preview, name); aspect = spreadsheet; break; } case Origin::ProjectNode::Graph: { DEBUG(" top level graph"); Worksheet* worksheet = new Worksheet(name); worksheet->setIsLoading(true); worksheet->setTheme(QString()); loadWorksheet(worksheet, preview); aspect = worksheet; break; } case Origin::ProjectNode::Matrix: { DEBUG(" top level matrix"); const Origin::Matrix& originMatrix = m_originFile->matrix(findMatrixByName(name)); DEBUG(" matrix name = " << originMatrix.name); DEBUG(" number of sheets = " << originMatrix.sheets.size()); if (originMatrix.sheets.size() == 1) { // single sheet -> load into a matrix Matrix* matrix = new Matrix(name); loadMatrix(matrix, preview); aspect = matrix; } else { // multiple sheets -> load into a workbook Workbook* workbook = new Workbook(name); loadMatrixWorkbook(workbook, preview); aspect = workbook; } break; } case Origin::ProjectNode::Excel: { DEBUG(" top level excel"); Workbook* workbook = new Workbook(name); loadWorkbook(workbook, preview); aspect = workbook; break; } case Origin::ProjectNode::Note: { DEBUG("top level note"); Note* note = new Note(name); loadNote(note, preview); aspect = note; break; } case Origin::ProjectNode::Graph3D: default: //TODO: add UnsupportedAspect break; } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(creationTime(it)); aspect->setIsLoading(false); } } // ResultsLog QString resultsLog = QString::fromLatin1(m_originFile->resultsLogString().c_str()); if (resultsLog.length() > 0) { DEBUG("Results log:\t\tyes"); Note* note = new Note("ResultsLog"); if (preview) folder->addChildFast(note); else { //only import the log if it is in the list of aspects to be loaded const QString childPath = folder->path() + '/' + note->name(); if (folder->pathesToLoad().indexOf(childPath) != -1) { note->setText(resultsLog); folder->addChildFast(note); } } } else DEBUG("Results log:\t\tno"); return folder; } void OriginProjectParser::handleLooseWindows(Folder* folder, bool preview) { DEBUG("OriginProjectParser::handleLooseWindows()"); QDEBUG("pathes to load:" << folder->pathesToLoad()); m_spreadNameList.removeDuplicates(); m_excelNameList.removeDuplicates(); m_matrixNameList.removeDuplicates(); m_graphNameList.removeDuplicates(); m_noteNameList.removeDuplicates(); QDEBUG(" spreads =" << m_spreadNameList); QDEBUG(" excels =" << m_excelNameList); QDEBUG(" matrices =" << m_matrixNameList); QDEBUG(" graphs =" << m_graphNameList); QDEBUG(" notes =" << m_noteNameList); DEBUG("Number of spreads loaded:\t" << m_spreadNameList.size() << ", in file: " << m_originFile->spreadCount()); DEBUG("Number of excels loaded:\t" << m_excelNameList.size() << ", in file: " << m_originFile->excelCount()); DEBUG("Number of matrices loaded:\t" << m_matrixNameList.size() << ", in file: " << m_originFile->matrixCount()); DEBUG("Number of graphs loaded:\t" << m_graphNameList.size() << ", in file: " << m_originFile->graphCount()); DEBUG("Number of notes loaded:\t\t" << m_noteNameList.size() << ", in file: " << m_originFile->noteCount()); // loop over all spreads to find loose ones for (unsigned int i = 0; i < m_originFile->spreadCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::SpreadSheet& spread = m_originFile->spread(i); QString name = QString::fromStdString(spread.name); DEBUG(" spread.objectId = " << spread.objectID); // skip unused spreads if selected if (spread.objectID < 0 && !m_importUnusedObjects) { - DEBUG(" Dropping unused loose spread: " << name.toStdString()); + DEBUG(" Dropping unused loose spread: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; // we could also use spread.loose if (!m_spreadNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { - DEBUG(" Adding loose spread: " << name.toStdString()); + DEBUG(" Adding loose spread: " << STDSTRING(name)); Spreadsheet* spreadsheet = new Spreadsheet(name); loadSpreadsheet(spreadsheet, preview, name); aspect = spreadsheet; } if (aspect) { folder->addChildFast(aspect); DEBUG(" creation time as reported by liborigin: " << spread.creationDate); aspect->setCreationTime(QDateTime::fromTime_t(spread.creationDate)); } } // loop over all excels to find loose ones for (unsigned int i = 0; i < m_originFile->excelCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Excel& excel = m_originFile->excel(i); QString name = QString::fromStdString(excel.name); DEBUG(" excel.objectId = " << excel.objectID); // skip unused data sets if selected if (excel.objectID < 0 && !m_importUnusedObjects) { - DEBUG(" Dropping unused loose excel: " << name.toStdString()); + DEBUG(" Dropping unused loose excel: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; // we could also use excel.loose if (!m_excelNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { - DEBUG(" Adding loose excel: " << name.toStdString()); + DEBUG(" Adding loose excel: " << STDSTRING(name)); DEBUG(" containing number of sheets = " << excel.sheets.size()); Workbook* workbook = new Workbook(name); loadWorkbook(workbook, preview); aspect = workbook; } if (aspect) { folder->addChildFast(aspect); DEBUG(" creation time as reported by liborigin: " << excel.creationDate); aspect->setCreationTime(QDateTime::fromTime_t(excel.creationDate)); } } // loop over all matrices to find loose ones for (unsigned int i = 0; i < m_originFile->matrixCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Matrix& originMatrix = m_originFile->matrix(i); QString name = QString::fromStdString(originMatrix.name); DEBUG(" originMatrix.objectId = " << originMatrix.objectID); // skip unused data sets if selected if (originMatrix.objectID < 0 && !m_importUnusedObjects) { - DEBUG(" Dropping unused loose matrix: " << name.toStdString()); + DEBUG(" Dropping unused loose matrix: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; if (!m_matrixNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { - DEBUG(" Adding loose matrix: " << name.toStdString()); + DEBUG(" Adding loose matrix: " << STDSTRING(name)); DEBUG(" containing number of sheets = " << originMatrix.sheets.size()); if (originMatrix.sheets.size() == 1) { // single sheet -> load into a matrix Matrix* matrix = new Matrix(name); loadMatrix(matrix, preview); aspect = matrix; } else { // multiple sheets -> load into a workbook Workbook* workbook = new Workbook(name); loadMatrixWorkbook(workbook, preview); aspect = workbook; } } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(QDateTime::fromTime_t(originMatrix.creationDate)); } } // handle loose graphs (is this even possible?) for (unsigned int i = 0; i < m_originFile->graphCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Graph& graph = m_originFile->graph(i); QString name = QString::fromStdString(graph.name); DEBUG(" graph.objectId = " << graph.objectID); // skip unused graph if selected if (graph.objectID < 0 && !m_importUnusedObjects) { - DEBUG(" Dropping unused loose graph: " << name.toStdString()); + DEBUG(" Dropping unused loose graph: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; if (!m_graphNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { - DEBUG(" Adding loose graph: " << name.toStdString()); + DEBUG(" Adding loose graph: " << STDSTRING(name)); Worksheet* worksheet = new Worksheet(name); loadWorksheet(worksheet, preview); aspect = worksheet; } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(QDateTime::fromTime_t(graph.creationDate)); } } // handle loose notes (is this even possible?) for (unsigned int i = 0; i < m_originFile->noteCount(); i++) { AbstractAspect* aspect = nullptr; const Origin::Note& originNote = m_originFile->note(i); QString name = QString::fromStdString(originNote.name); DEBUG(" originNote.objectId = " << originNote.objectID); // skip unused notes if selected if (originNote.objectID < 0 && !m_importUnusedObjects) { - DEBUG(" Dropping unused loose note: " << name.toStdString()); + DEBUG(" Dropping unused loose note: " << STDSTRING(name)); continue; } const QString childPath = folder->path() + '/' + name; if (!m_noteNameList.contains(name) && (preview || folder->pathesToLoad().indexOf(childPath) != -1)) { - DEBUG(" Adding loose note: " << name.toStdString()); + DEBUG(" Adding loose note: " << STDSTRING(name)); Note* note = new Note(name); loadNote(note, preview); aspect = note; } if (aspect) { folder->addChildFast(aspect); aspect->setCreationTime(QDateTime::fromTime_t(originNote.creationDate)); } } } bool OriginProjectParser::loadWorkbook(Workbook* workbook, bool preview) { DEBUG("loadWorkbook()"); //load workbook sheets const Origin::Excel& excel = m_originFile->excel(findExcelByName(workbook->name())); DEBUG(" excel name = " << excel.name); DEBUG(" number of sheets = " << excel.sheets.size()); for (unsigned int s = 0; s < excel.sheets.size(); ++s) { Spreadsheet* spreadsheet = new Spreadsheet(QString::fromLatin1(excel.sheets[s].name.c_str())); loadSpreadsheet(spreadsheet, preview, workbook->name(), s); workbook->addChildFast(spreadsheet); } return true; } // load spreadsheet from spread (sheetIndex == -1) or from excel (only sheet sheetIndex) bool OriginProjectParser::loadSpreadsheet(Spreadsheet* spreadsheet, bool preview, const QString& name, int sheetIndex) { DEBUG("loadSpreadsheet() sheetIndex = " << sheetIndex); //load spreadsheet data Origin::SpreadSheet spread; Origin::Excel excel; if (sheetIndex == -1) // spread spread = m_originFile->spread(findSpreadByName(name)); else { excel = m_originFile->excel(findExcelByName(name)); spread = excel.sheets[sheetIndex]; } const size_t cols = spread.columns.size(); int rows = 0; for (size_t j = 0; j < cols; ++j) rows = std::max((int)spread.columns[j].data.size(), rows); // alternative: int rows = excel.maxRows; DEBUG("loadSpreadsheet() cols/maxRows = " << cols << "/" << rows); //TODO QLocale locale = mw->locale(); spreadsheet->setRowCount(rows); spreadsheet->setColumnCount((int)cols); if (sheetIndex == -1) spreadsheet->setComment(QString::fromLatin1(spread.label.c_str())); else spreadsheet->setComment(QString::fromLatin1(excel.label.c_str())); //in Origin column width is measured in characters, we need to convert to pixels //TODO: determine the font used in Origin in order to get the same column width as in Origin QFont font; QFontMetrics fm(font); const int scaling_factor = fm.maxWidth(); for (size_t j = 0; j < cols; ++j) { Origin::SpreadColumn column = spread.columns[j]; Column* col = spreadsheet->column((int)j); QString name(column.name.c_str()); col->setName(name.replace(QRegExp(".*_"), QString())); if (preview) continue; //TODO: we don't support any formulas for cells yet. // if (column.command.size() > 0) // col->setFormula(Interval(0, rows), QString(column.command.c_str())); col->setComment(QString::fromLatin1(column.comment.c_str())); col->setWidth((int)column.width * scaling_factor); //plot designation switch (column.type) { case Origin::SpreadColumn::X: col->setPlotDesignation(AbstractColumn::X); break; case Origin::SpreadColumn::Y: col->setPlotDesignation(AbstractColumn::Y); break; case Origin::SpreadColumn::Z: col->setPlotDesignation(AbstractColumn::Z); break; case Origin::SpreadColumn::XErr: col->setPlotDesignation(AbstractColumn::XError); break; case Origin::SpreadColumn::YErr: col->setPlotDesignation(AbstractColumn::YError); break; case Origin::SpreadColumn::Label: case Origin::SpreadColumn::NONE: default: col->setPlotDesignation(AbstractColumn::NoDesignation); } QString format; switch (column.valueType) { case Origin::Numeric: { for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const double value = column.data.at(i).as_double(); if (value != _ONAN) col->setValueAt(i, value); } loadColumnNumericFormat(column, col); break; } case Origin::TextNumeric: { //A TextNumeric column can contain numeric and string values, there is no equivalent column mode in LabPlot. // -> Set the column mode as 'Numeric' or 'Text' depending on the type of first non-empty element in column. for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const Origin::variant value(column.data.at(i)); if (value.type() == Origin::Variant::V_DOUBLE) { if (value.as_double() != _ONAN) break; } else { if (value.as_string() != nullptr) { col->setColumnMode(AbstractColumn::Text); break; } } } if (col->columnMode() == AbstractColumn::Numeric) { for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const double value = column.data.at(i).as_double(); if (column.data.at(i).type() == Origin::Variant::V_DOUBLE && value != _ONAN) col->setValueAt(i, value); } loadColumnNumericFormat(column, col); } else { for (unsigned int i = column.beginRow; i < column.endRow; ++i) { const Origin::variant value(column.data.at(i)); if (value.type() == Origin::Variant::V_STRING) { if (value.as_string() != nullptr) col->setTextAt(i, value.as_string()); } else { if (value.as_double() != _ONAN) col->setTextAt(i, QString::number(value.as_double())); } } } break; } case Origin::Text: col->setColumnMode(AbstractColumn::Text); for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setTextAt(i, column.data[i].as_string()); break; case Origin::Time: { switch (column.valueTypeSpecification + 128) { case Origin::TIME_HH_MM: format = "hh:mm"; break; case Origin::TIME_HH: format = "hh"; break; case Origin::TIME_HH_MM_SS: format = "hh:mm:ss"; break; case Origin::TIME_HH_MM_SS_ZZ: format = "hh:mm:ss.zzz"; break; case Origin::TIME_HH_AP: format = "hh ap"; break; case Origin::TIME_HH_MM_AP: format = "hh:mm ap"; break; case Origin::TIME_MM_SS: format = "mm:ss"; break; case Origin::TIME_MM_SS_ZZ: format = "mm:ss.zzz"; break; case Origin::TIME_HHMM: format = "hhmm"; break; case Origin::TIME_HHMMSS: format = "hhmmss"; break; case Origin::TIME_HH_MM_SS_ZZZ: format = "hh:mm:ss.zzz"; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::DateTime); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Date: { switch (column.valueTypeSpecification) { case Origin::DATE_DD_MM_YYYY: format = "dd/MM/yyyy"; break; case Origin::DATE_DD_MM_YYYY_HH_MM: format = "dd/MM/yyyy HH:mm"; break; case Origin::DATE_DD_MM_YYYY_HH_MM_SS: format = "dd/MM/yyyy HH:mm:ss"; break; case Origin::DATE_DDMMYYYY: case Origin::DATE_DDMMYYYY_HH_MM: case Origin::DATE_DDMMYYYY_HH_MM_SS: format = "dd.MM.yyyy"; break; case Origin::DATE_MMM_D: format = "MMM d"; break; case Origin::DATE_M_D: format = "M/d"; break; case Origin::DATE_D: format = 'd'; break; case Origin::DATE_DDD: case Origin::DATE_DAY_LETTER: format = "ddd"; break; case Origin::DATE_YYYY: format = "yyyy"; break; case Origin::DATE_YY: format = "yy"; break; case Origin::DATE_YYMMDD: case Origin::DATE_YYMMDD_HH_MM: case Origin::DATE_YYMMDD_HH_MM_SS: case Origin::DATE_YYMMDD_HHMM: case Origin::DATE_YYMMDD_HHMMSS: format = "yyMMdd"; break; case Origin::DATE_MMM: case Origin::DATE_MONTH_LETTER: format = "MMM"; break; case Origin::DATE_M_D_YYYY: format = "M-d-yyyy"; break; default: format = "dd.MM.yyyy"; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::DateTime); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Month: { switch (column.valueTypeSpecification) { case Origin::MONTH_MMM: format = "MMM"; break; case Origin::MONTH_MMMM: format = "MMMM"; break; case Origin::MONTH_LETTER: format = 'M'; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::Month); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::Day: { switch (column.valueTypeSpecification) { case Origin::DAY_DDD: format = "ddd"; break; case Origin::DAY_DDDD: format = "dddd"; break; case Origin::DAY_LETTER: format = 'd'; break; } for (int i = 0; i < min((int)column.data.size(), rows); ++i) col->setValueAt(i, column.data[i].as_double()); col->setColumnMode(AbstractColumn::Day); DateTime2StringFilter *filter = static_cast(col->outputFilter()); filter->setFormat(format); break; } case Origin::ColumnHeading: case Origin::TickIndexedDataset: case Origin::Categorical: break; } } //TODO: "hidden" not supported yet // if (spread.hidden || spread.loose) // mw->hideWindow(spreadsheet); return true; } void OriginProjectParser::loadColumnNumericFormat(const Origin::SpreadColumn& originColumn, Column* column) const { if (originColumn.numericDisplayType != 0) { int fi = 0; switch (originColumn.valueTypeSpecification) { case Origin::Decimal: fi = 1; break; case Origin::Scientific: fi = 2; break; case Origin::Engineering: case Origin::DecimalWithMarks: break; } Double2StringFilter* filter = static_cast(column->outputFilter()); filter->setNumericFormat(fi); filter->setNumDigits(originColumn.decimalPlaces); } } bool OriginProjectParser::loadMatrixWorkbook(Workbook* workbook, bool preview) { DEBUG("loadMatrixWorkbook()"); //load matrix workbook sheets const Origin::Matrix& originMatrix = m_originFile->matrix(findMatrixByName(workbook->name())); for (size_t s = 0; s < originMatrix.sheets.size(); ++s) { Matrix* matrix = new Matrix(QString::fromLatin1(originMatrix.sheets[s].name.c_str())); loadMatrix(matrix, preview, s, workbook->name()); workbook->addChildFast(matrix); } return true; } bool OriginProjectParser::loadMatrix(Matrix* matrix, bool preview, size_t sheetIndex, const QString& mwbName) { DEBUG("loadMatrix()"); //import matrix data const Origin::Matrix& originMatrix = m_originFile->matrix(findMatrixByName(mwbName)); if (preview) return true; //in Origin column width is measured in characters, we need to convert to pixels //TODO: determine the font used in Origin in order to get the same column width as in Origin QFont font; QFontMetrics fm(font); const int scaling_factor = fm.maxWidth(); const Origin::MatrixSheet& layer = originMatrix.sheets[sheetIndex]; const int colCount = layer.columnCount; const int rowCount = layer.rowCount; matrix->setRowCount(rowCount); matrix->setColumnCount(colCount); matrix->setFormula(layer.command.c_str()); //TODO: how to handle different widths for different columns? for (int j = 0; j < colCount; j++) matrix->setColumnWidth(j, layer.width * scaling_factor); //TODO: check column major vs. row major to improve the performance here for (int i = 0; i < rowCount; i++) { for (int j = 0; j < colCount; j++) matrix->setCell(i, j, layer.data[j + i*colCount]); } char format = 'g'; //TODO: prec not support by Matrix //int prec = 6; switch (layer.valueTypeSpecification) { case 0: //Decimal 1000 format = 'f'; // prec = layer.decimalPlaces; break; case 1: //Scientific format = 'e'; // prec = layer.decimalPlaces; break; case 2: //Engineering case 3: //Decimal 1,000 format = 'g'; // prec = layer.significantDigits; break; } matrix->setNumericFormat(format); return true; } bool OriginProjectParser::loadWorksheet(Worksheet* worksheet, bool preview) { DEBUG("OriginProjectParser::loadWorksheet()"); //load worksheet data const Origin::Graph& graph = m_originFile->graph(findGraphByName(worksheet->name())); DEBUG(" graph name = " << graph.name); worksheet->setComment(graph.label.c_str()); //TODO: width, height, view mode (print view, page view, window view, draft view) //Origin allows to freely resize the window and ajusts the size of the plot (layer) automatically //by keeping a certain width-to-height ratio. It's not clear what the actual size of the plot/layer is and how to handle this. //For now we simply create a new wokrsheet here with it's default size and make it using the whole view size. //Later we can decide to use one of the following properties: // 1) Window.frameRect gives Rect-corner coordinates (in pixels) of the Window object // 2) GraphLayer.clientRect gives Rect-corner coordinates (pixels) of the Layer inside the (printer?) page. // 3) Graph.width, Graph.height give the (printer?) page size in pixels. // const QRectF size(0, 0, // Worksheet::convertToSceneUnits(graph.width/600., Worksheet::Inch), // Worksheet::convertToSceneUnits(graph.height/600., Worksheet::Inch)); // worksheet->setPageRect(size); worksheet->setUseViewSize(true); QHash textLabelPositions; // worksheet background color const Origin::ColorGradientDirection bckgColorGradient = graph.windowBackgroundColorGradient; const Origin::Color bckgBaseColor = graph.windowBackgroundColorBase; const Origin::Color bckgEndColor = graph.windowBackgroundColorEnd; worksheet->setBackgroundColorStyle(backgroundColorStyle(bckgColorGradient)); switch (bckgColorGradient) { case Origin::ColorGradientDirection::NoGradient: case Origin::ColorGradientDirection::TopLeft: case Origin::ColorGradientDirection::Left: case Origin::ColorGradientDirection::BottomLeft: case Origin::ColorGradientDirection::Top: worksheet->setBackgroundFirstColor(color(bckgEndColor)); worksheet->setBackgroundSecondColor(color(bckgBaseColor)); break; case Origin::ColorGradientDirection::Center: break; case Origin::ColorGradientDirection::Bottom: case Origin::ColorGradientDirection::TopRight: case Origin::ColorGradientDirection::Right: case Origin::ColorGradientDirection::BottomRight: worksheet->setBackgroundFirstColor(color(bckgBaseColor)); worksheet->setBackgroundSecondColor(color(bckgEndColor)); } //TODO: do we need changes on the worksheet layout? //add plots int index = 1; for (const auto& layer : graph.layers) { if (!layer.is3D()) { CartesianPlot* plot = new CartesianPlot(i18n("Plot%1", QString::number(index))); worksheet->addChildFast(plot); plot->setIsLoading(true); //TODO: width, height //background color const Origin::Color& regColor = layer.backgroundColor; if (regColor.type == Origin::Color::None) plot->plotArea()->setBackgroundOpacity(0); else plot->plotArea()->setBackgroundFirstColor(color(regColor)); //border if (layer.borderType == Origin::BorderType::None) plot->plotArea()->setBorderPen(QPen(Qt::NoPen)); else plot->plotArea()->setBorderPen(QPen(Qt::SolidLine)); //ranges plot->setAutoScaleX(false); plot->setAutoScaleY(false); const Origin::GraphAxis& originXAxis = layer.xAxis; const Origin::GraphAxis& originYAxis = layer.yAxis; plot->setXMin(originXAxis.min); plot->setXMax(originXAxis.max); plot->setYMin(originYAxis.min); plot->setYMax(originYAxis.max); //scales switch (originXAxis.scale) { case Origin::GraphAxis::Linear: plot->setXScale(CartesianPlot::ScaleLinear); break; case Origin::GraphAxis::Log10: plot->setXScale(CartesianPlot::ScaleLog10); break; case Origin::GraphAxis::Ln: plot->setXScale(CartesianPlot::ScaleLn); break; case Origin::GraphAxis::Log2: plot->setXScale(CartesianPlot::ScaleLog2); break; case Origin::GraphAxis::Probability: case Origin::GraphAxis::Probit: case Origin::GraphAxis::Reciprocal: case Origin::GraphAxis::OffsetReciprocal: case Origin::GraphAxis::Logit: //TODO: plot->setXScale(CartesianPlot::ScaleLinear); break; } switch (originYAxis.scale) { case Origin::GraphAxis::Linear: plot->setYScale(CartesianPlot::ScaleLinear); break; case Origin::GraphAxis::Log10: plot->setYScale(CartesianPlot::ScaleLog10); break; case Origin::GraphAxis::Ln: plot->setYScale(CartesianPlot::ScaleLn); break; case Origin::GraphAxis::Log2: plot->setYScale(CartesianPlot::ScaleLog2); break; case Origin::GraphAxis::Probability: case Origin::GraphAxis::Probit: case Origin::GraphAxis::Reciprocal: case Origin::GraphAxis::OffsetReciprocal: case Origin::GraphAxis::Logit: //TODO: plot->setYScale(CartesianPlot::ScaleLinear); break; } //axes //x bottom if (layer.curves.size()) { Origin::GraphCurve originCurve = layer.curves[0]; QString xColumnName = QString::fromLatin1(originCurve.xColumnName.c_str()); //TODO: "Partikelgrö" - DEBUG(" xColumnName = " << xColumnName.toStdString()); + DEBUG(" xColumnName = " << STDSTRING(xColumnName)); QDEBUG(" UTF8 xColumnName = " << xColumnName.toUtf8()); QString yColumnName = QString::fromLatin1(originCurve.yColumnName.c_str()); if (!originXAxis.formatAxis[0].hidden) { Axis* axis = new Axis("x", Axis::AxisHorizontal); axis->setSuppressRetransform(true); axis->setPosition(Axis::AxisBottom); plot->addChildFast(axis); loadAxis(originXAxis, axis, 0, xColumnName); axis->setSuppressRetransform(false); } //x top if (!originXAxis.formatAxis[1].hidden) { Axis* axis = new Axis("x top", Axis::AxisHorizontal); axis->setPosition(Axis::AxisTop); axis->setSuppressRetransform(true); plot->addChildFast(axis); loadAxis(originXAxis, axis, 1, xColumnName); axis->setSuppressRetransform(false); } //y left if (!originYAxis.formatAxis[0].hidden) { Axis* axis = new Axis("y", Axis::AxisVertical); axis->setSuppressRetransform(true); axis->setPosition(Axis::AxisLeft); plot->addChildFast(axis); loadAxis(originYAxis, axis, 0, yColumnName); axis->setSuppressRetransform(false); } //y right if (!originYAxis.formatAxis[1].hidden) { Axis* axis = new Axis("y right", Axis::AxisVertical); axis->setSuppressRetransform(true); axis->setPosition(Axis::AxisRight); plot->addChildFast(axis); loadAxis(originYAxis, axis, 1, yColumnName); axis->setSuppressRetransform(false); } } else { //TODO: ? } //range breaks //TODO //add legend if available const Origin::TextBox& originLegend = layer.legend; const QString& legendText = QString::fromLatin1(originLegend.text.c_str()); - DEBUG(" legend text = " << legendText.toStdString()); + DEBUG(" legend text = " << STDSTRING(legendText)); if (!originLegend.text.empty()) { CartesianPlotLegend* legend = new CartesianPlotLegend(plot, i18n("legend")); //Origin's legend uses "\l(...)" or "\L(...)" string to format the legend symbol // and "%(...) to format the legend text for each curve //s. a. https://www.originlab.com/doc/Origin-Help/Legend-ManualControl //the text before these formatting tags, if available, is interpreted as the legend title QString legendTitle; //search for the first occurrence of the legend symbol substring int index = legendText.indexOf(QLatin1String("\\l("), 0, Qt::CaseInsensitive); if (index != -1) legendTitle = legendText.left(index); else { //check legend text index = legendText.indexOf(QLatin1String("%(")); if (index != -1) legendTitle = legendText.left(index); } legendTitle = legendTitle.trimmed(); if (!legendTitle.isEmpty()) legendTitle = parseOriginText(legendTitle); - DEBUG(" legend title = " << legendTitle.toStdString()); + DEBUG(" legend title = " << STDSTRING(legendTitle)); legend->title()->setText(legendTitle); //TODO: text color //const Origin::Color& originColor = originLegend.color; //position //TODO: for the first release with OPJ support we put the legend to the bottom left corner, //in the next release we'll evaluate originLegend.clientRect giving the position inside of the whole page in Origin. //In Origin the legend can be placed outside of the plot which is not possible in LabPlot. //To achieve this we'll need to increase padding area in the plot and to place the legend outside of the plot area. CartesianPlotLegend::PositionWrapper position; position.horizontalPosition = CartesianPlotLegend::hPositionRight; position.verticalPosition = CartesianPlotLegend::vPositionBottom; legend->setPosition(position); //rotation legend->setRotationAngle(originLegend.rotation); //border line if (originLegend.borderType == Origin::BorderType::None) legend->setBorderPen(QPen(Qt::NoPen)); else legend->setBorderPen(QPen(Qt::SolidLine)); //background color, determine it with the help of the border type if (originLegend.borderType == Origin::BorderType::DarkMarble) legend->setBackgroundFirstColor(Qt::darkGray); else if (originLegend.borderType == Origin::BorderType::BlackOut) legend->setBackgroundFirstColor(Qt::black); else legend->setBackgroundFirstColor(Qt::white); plot->addLegend(legend); } //texts for (const auto& s : layer.texts) { DEBUG("EXTRA TEXT = " << s.text.c_str()); TextLabel* label = new TextLabel("text label"); label->setText(parseOriginText(QString::fromLatin1(s.text.c_str()))); plot->addChild(label); label->setParentGraphicsItem(plot->graphicsItem()); //position //determine the relative position inside of the layer rect const float horRatio = (float)(s.clientRect.left-layer.clientRect.left)/(layer.clientRect.right-layer.clientRect.left); const float vertRatio = (float)(s.clientRect.top-layer.clientRect.top)/(layer.clientRect.bottom-layer.clientRect.top); textLabelPositions[label] = QSizeF(horRatio, vertRatio); DEBUG("horizontal/vertical ratio = " << horRatio << ", " << vertRatio); //rotation label->setRotationAngle(s.rotation); //TODO: // Color color; // unsigned short fontSize; // int tab; // BorderType borderType; // Attach attach; } //curves int curveIndex = 1; for (const auto& originCurve : layer.curves) { QString data(originCurve.dataName.c_str()); switch (data[0].toLatin1()) { case 'T': case 'E': { if (originCurve.type == Origin::GraphCurve::Line || originCurve.type == Origin::GraphCurve::Scatter || originCurve.type == Origin::GraphCurve::LineSymbol || originCurve.type == Origin::GraphCurve::ErrorBar || originCurve.type == Origin::GraphCurve::XErrorBar) { // parse and use legend text // find substring between %c{curveIndex} and %c{curveIndex+1} int pos1 = legendText.indexOf(QString("\\c{%1}").arg(curveIndex)) + 5; int pos2 = legendText.indexOf(QString("\\c{%1}").arg(curveIndex+1)); QString curveText = legendText.mid(pos1, pos2 - pos1); // replace %(1), %(2), etc. with curve name curveText.replace(QString("%(%1)").arg(curveIndex), QString::fromLatin1(originCurve.yColumnName.c_str())); curveText = curveText.trimmed(); - DEBUG(" curve " << curveIndex << " text = " << curveText.toStdString()); + DEBUG(" curve " << curveIndex << " text = " << STDSTRING(curveText)); //XYCurve* xyCurve = new XYCurve(i18n("Curve%1", QString::number(curveIndex))); //TODO: curve (legend) does not support HTML text yet. //XYCurve* xyCurve = new XYCurve(curveText); XYCurve* curve = new XYCurve(QString::fromLatin1(originCurve.yColumnName.c_str())); const QString& tableName = data.right(data.length() - 2); curve->setXColumnPath(tableName + '/' + originCurve.xColumnName.c_str()); curve->setYColumnPath(tableName + '/' + originCurve.yColumnName.c_str()); curve->suppressRetransform(true); if (!preview) loadCurve(originCurve, curve); plot->addChildFast(curve); curve->suppressRetransform(false); } else if (originCurve.type == Origin::GraphCurve::Column) { //vertical bars } else if (originCurve.type == Origin::GraphCurve::Bar) { //horizontal bars } else if (originCurve.type == Origin::GraphCurve::Histogram) { } } break; case 'F': { Origin::Function function; const vector::difference_type funcIndex = m_originFile->functionIndex(data.right(data.length()-2).toStdString().c_str()); if (funcIndex < 0) { ++curveIndex; continue; } function = m_originFile->function(funcIndex); XYEquationCurve* xyEqCurve = new XYEquationCurve(function.name.c_str()); XYEquationCurve::EquationData eqData; eqData.count = function.totalPoints; eqData.expression1 = QString(function.formula.c_str()); if (function.type == Origin::Function::Polar) { eqData.type = XYEquationCurve::Polar; //replace 'x' by 'phi' eqData.expression1 = eqData.expression1.replace('x', "phi"); //convert from degrees to radians eqData.min = QString::number(function.begin/180) + QLatin1String("*pi"); eqData.max = QString::number(function.end/180) + QLatin1String("*pi"); } else { eqData.expression1 = QString(function.formula.c_str()); eqData.min = QString::number(function.begin); eqData.max = QString::number(function.end); } xyEqCurve->suppressRetransform(true); xyEqCurve->setEquationData(eqData); if (!preview) loadCurve(originCurve, xyEqCurve); plot->addChildFast(xyEqCurve); xyEqCurve->suppressRetransform(false); } } ++curveIndex; } } else { //no support for 3D plots yet //TODO: add an "UnsupportedAspect" here } ++index; } if (!preview) { worksheet->updateLayout(); //worksheet and plots got their sizes, //-> position all text labels inside the plots correctly by converting //the relative positions determined above to the absolute values QHash::const_iterator it = textLabelPositions.constBegin(); while (it != textLabelPositions.constEnd()) { TextLabel* label = it.key(); const QSizeF& ratios = it.value(); const CartesianPlot* plot = static_cast(label->parentAspect()); TextLabel::PositionWrapper position = label->position(); position.point.setX(plot->dataRect().width()*(ratios.width()-0.5)); position.point.setY(plot->dataRect().height()*(ratios.height()-0.5)); label->setPosition(position); ++it; } } return true; } /* * sets the axis properties (format and ticks) as defined in \c originAxis in \c axis, * \c index being 0 or 1 for "top" and "bottom" or "left" and "right" for horizontal or vertical axes, respectively. */ void OriginProjectParser::loadAxis(const Origin::GraphAxis& originAxis, Axis* axis, int index, const QString& axisTitle) const { // int axisPosition; // possible values: // 0: Axis is at default position // 1: Axis is at (axisPositionValue)% from standard position // 2: Axis is at (axisPositionValue) position of orthogonal axis // double axisPositionValue; // bool zeroLine; // bool oppositeLine; //ranges axis->setStart(originAxis.min); axis->setEnd(originAxis.max); //ticks axis->setMajorTicksType(Axis::TicksIncrement); axis->setMajorTicksIncrement(originAxis.step); axis->setMinorTicksType(Axis::TicksTotalNumber); axis->setMinorTicksNumber(originAxis.minorTicks); //scale switch (originAxis.scale) { case Origin::GraphAxis::Linear: axis->setScale(Axis::ScaleLinear); break; case Origin::GraphAxis::Log10: axis->setScale(Axis::ScaleLog10); break; case Origin::GraphAxis::Ln: axis->setScale(Axis::ScaleLn); break; case Origin::GraphAxis::Log2: axis->setScale(Axis::ScaleLog2); break; case Origin::GraphAxis::Probability: case Origin::GraphAxis::Probit: case Origin::GraphAxis::Reciprocal: case Origin::GraphAxis::OffsetReciprocal: case Origin::GraphAxis::Logit: //TODO: axis->setScale(Axis::ScaleLinear); break; } //major grid const Origin::GraphGrid& majorGrid = originAxis.majorGrid; QPen gridPen = axis->majorGridPen(); Qt::PenStyle penStyle(Qt::NoPen); if (!majorGrid.hidden) { switch (majorGrid.style) { case Origin::GraphCurve::Solid: penStyle = Qt::SolidLine; break; case Origin::GraphCurve::Dash: case Origin::GraphCurve::ShortDash: penStyle = Qt::DashLine; break; case Origin::GraphCurve::Dot: case Origin::GraphCurve::ShortDot: penStyle = Qt::DotLine; break; case Origin::GraphCurve::DashDot: case Origin::GraphCurve::ShortDashDot: penStyle = Qt::DashDotLine; break; case Origin::GraphCurve::DashDotDot: penStyle = Qt::DashDotDotLine; break; } } gridPen.setStyle(penStyle); Origin::Color gridColor; gridColor.type = Origin::Color::ColorType::Regular; gridColor.regular = majorGrid.color; gridPen.setColor(OriginProjectParser::color(gridColor)); gridPen.setWidthF(Worksheet::convertToSceneUnits(majorGrid.width, Worksheet::Point)); axis->setMajorGridPen(gridPen); //minor grid const Origin::GraphGrid& minorGrid = originAxis.minorGrid; gridPen = axis->minorGridPen(); penStyle = Qt::NoPen; if (!minorGrid.hidden) { switch (minorGrid.style) { case Origin::GraphCurve::Solid: penStyle = Qt::SolidLine; break; case Origin::GraphCurve::Dash: case Origin::GraphCurve::ShortDash: penStyle = Qt::DashLine; break; case Origin::GraphCurve::Dot: case Origin::GraphCurve::ShortDot: penStyle = Qt::DotLine; break; case Origin::GraphCurve::DashDot: case Origin::GraphCurve::ShortDashDot: penStyle = Qt::DashDotLine; break; case Origin::GraphCurve::DashDotDot: penStyle = Qt::DashDotDotLine; break; } } gridPen.setStyle(penStyle); gridColor.regular = minorGrid.color; gridPen.setColor(OriginProjectParser::color(gridColor)); gridPen.setWidthF(Worksheet::convertToSceneUnits(minorGrid.width, Worksheet::Point)); axis->setMinorGridPen(gridPen); //process Origin::GraphAxisFormat const Origin::GraphAxisFormat& axisFormat = originAxis.formatAxis[index]; QPen pen; Origin::Color color; color.type = Origin::Color::ColorType::Regular; color.regular = axisFormat.color; pen.setColor(OriginProjectParser::color(color)); pen.setWidthF(Worksheet::convertToSceneUnits(axisFormat.thickness, Worksheet::Point)); axis->setLinePen(pen); axis->setMajorTicksLength( Worksheet::convertToSceneUnits(axisFormat.majorTickLength, Worksheet::Point) ); axis->setMajorTicksDirection( (Axis::TicksFlags) axisFormat.majorTicksType); axis->setMajorTicksPen(pen); axis->setMinorTicksLength( axis->majorTicksLength()/2); // minorTicksLength is half of majorTicksLength axis->setMinorTicksDirection( (Axis::TicksFlags) axisFormat.minorTicksType); axis->setMinorTicksPen(pen); QString titleText = parseOriginText(QString::fromLatin1(axisFormat.label.text.c_str())); - DEBUG(" axis title text = " << titleText.toStdString()); + DEBUG(" axis title text = " << STDSTRING(titleText)); //TODO: parseOriginText() returns html formatted string. What is axisFormat.color used for? //TODO: use axisFormat.fontSize to override the global font size for the hmtl string? //TODO: convert special character here too - DEBUG(" curve name = " << axisTitle.toStdString()); + DEBUG(" curve name = " << STDSTRING(axisTitle)); titleText.replace("%(?X)", axisTitle); titleText.replace("%(?Y)", axisTitle); - DEBUG(" axis title = " << titleText.toStdString()); + DEBUG(" axis title = " << STDSTRING(titleText)); axis->title()->setText(titleText); axis->title()->setRotationAngle(axisFormat.label.rotation); axis->setLabelsPrefix(axisFormat.prefix.c_str()); axis->setLabelsSuffix(axisFormat.suffix.c_str()); //TODO: handle string factor member in GraphAxisFormat //process Origin::GraphAxisTick const Origin::GraphAxisTick& tickAxis = originAxis.tickAxis[index]; if (tickAxis.showMajorLabels) { color.type = Origin::Color::ColorType::Regular; color.regular = tickAxis.color; axis->setLabelsColor(OriginProjectParser::color(color)); //TODO: how to set labels position (top vs. bottom)? } else { axis->setLabelsPosition(Axis::LabelsPosition::NoLabels); } //TODO: handle ValueType valueType member in GraphAxisTick //TODO: handle int valueTypeSpecification in GraphAxisTick //precision if (tickAxis.decimalPlaces == -1) axis->setLabelsAutoPrecision(true); else { axis->setLabelsPrecision(tickAxis.decimalPlaces); axis->setLabelsAutoPrecision(false); } QFont font; //TODO: font family? font.setPixelSize( Worksheet::convertToSceneUnits(tickAxis.fontSize, Worksheet::Point) ); font.setBold(tickAxis.fontBold); axis->setLabelsFont(font); //TODO: handle string dataName member in GraphAxisTick //TODO: handle string columnName member in GraphAxisTick axis->setLabelsRotationAngle(tickAxis.rotation); } void OriginProjectParser::loadCurve(const Origin::GraphCurve& originCurve, XYCurve* curve) const { //line properties QPen pen = curve->linePen(); Qt::PenStyle penStyle(Qt::NoPen); if (originCurve.type == Origin::GraphCurve::Line || originCurve.type == Origin::GraphCurve::LineSymbol) { switch (originCurve.lineConnect) { case Origin::GraphCurve::NoLine: curve->setLineType(XYCurve::NoLine); break; case Origin::GraphCurve::Straight: curve->setLineType(XYCurve::Line); break; case Origin::GraphCurve::TwoPointSegment: curve->setLineType(XYCurve::Segments2); break; case Origin::GraphCurve::ThreePointSegment: curve->setLineType(XYCurve::Segments3); break; case Origin::GraphCurve::BSpline: case Origin::GraphCurve::Bezier: case Origin::GraphCurve::Spline: curve->setLineType(XYCurve::SplineCubicNatural); break; case Origin::GraphCurve::StepHorizontal: curve->setLineType(XYCurve::StartHorizontal); break; case Origin::GraphCurve::StepVertical: curve->setLineType(XYCurve::StartVertical); break; case Origin::GraphCurve::StepHCenter: curve->setLineType(XYCurve::MidpointHorizontal); break; case Origin::GraphCurve::StepVCenter: curve->setLineType(XYCurve::MidpointVertical); break; } switch (originCurve.lineStyle) { case Origin::GraphCurve::Solid: penStyle = Qt::SolidLine; break; case Origin::GraphCurve::Dash: case Origin::GraphCurve::ShortDash: penStyle = Qt::DashLine; break; case Origin::GraphCurve::Dot: case Origin::GraphCurve::ShortDot: penStyle = Qt::DotLine; break; case Origin::GraphCurve::DashDot: case Origin::GraphCurve::ShortDashDot: penStyle = Qt::DashDotLine; break; case Origin::GraphCurve::DashDotDot: penStyle = Qt::DashDotDotLine; break; } pen.setStyle(penStyle); pen.setWidthF( Worksheet::convertToSceneUnits(originCurve.lineWidth, Worksheet::Point) ); pen.setColor(color(originCurve.lineColor)); curve->setLineOpacity(1 - originCurve.lineTransparency/255); //TODO: handle unsigned char boxWidth of Origin::GraphCurve } pen.setStyle(penStyle); curve->setLinePen(pen); //symbol properties if (originCurve.type == Origin::GraphCurve::Scatter || originCurve.type == Origin::GraphCurve::LineSymbol) { //try to map the different symbols, mapping is not exact curve->setSymbolsRotationAngle(0); switch (originCurve.symbolShape) { case 0: //NoSymbol curve->setSymbolsStyle(Symbol::NoSymbols); break; case 1: //Rect curve->setSymbolsStyle(Symbol::Square); break; case 2: //Ellipse case 20://Sphere curve->setSymbolsStyle(Symbol::Circle); break; case 3: //UTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 4: //DTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 5: //Diamond curve->setSymbolsStyle(Symbol::Diamond); break; case 6: //Cross + curve->setSymbolsStyle(Symbol::Cross); break; case 7: //Cross x curve->setSymbolsStyle(Symbol::Cross); break; case 8: //Snow curve->setSymbolsStyle(Symbol::Star4); break; case 9: //Horizontal - curve->setSymbolsStyle(Symbol::Line); curve->setSymbolsRotationAngle(90); break; case 10: //Vertical | curve->setSymbolsStyle(Symbol::Line); break; case 15: //LTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 16: //RTriangle curve->setSymbolsStyle(Symbol::EquilateralTriangle); break; case 17: //Hexagon case 19: //Pentagon curve->setSymbolsStyle(Symbol::Square); break; case 18: //Star curve->setSymbolsStyle(Symbol::Star5); break; default: curve->setSymbolsStyle(Symbol::NoSymbols); } //symbol size curve->setSymbolsSize(Worksheet::convertToSceneUnits(originCurve.symbolSize, Worksheet::Point)); //symbol fill color QBrush brush = curve->symbolsBrush(); if (originCurve.symbolFillColor.type == Origin::Color::ColorType::Automatic) { //"automatic" color -> the color of the line, if available, has to be used, black otherwise if (curve->lineType() != XYCurve::NoLine) brush.setColor(curve->linePen().color()); else brush.setColor(Qt::black); } else brush.setColor(color(originCurve.symbolFillColor)); curve->setSymbolsBrush(brush); //symbol border/edge color and width QPen pen = curve->symbolsPen(); if (originCurve.symbolColor.type == Origin::Color::ColorType::Automatic) { //"automatic" color -> the color of the line, if available, has to be used, black otherwise if (curve->lineType() != XYCurve::NoLine) pen.setColor(curve->linePen().color()); else pen.setColor(Qt::black); } else pen.setColor(color(originCurve.symbolColor)); //border width (edge thickness in Origin) is given by percentage of the symbol radius pen.setWidthF(originCurve.symbolThickness/100.*curve->symbolsSize()/2.); curve->setSymbolsPen(pen); //handle unsigned char pointOffset member //handle bool connectSymbols member } else { curve->setSymbolsStyle(Symbol::NoSymbols); } //filling properties if (originCurve.fillArea) { //TODO: handle unsigned char fillAreaType; //with 'fillAreaType'=0x10 the area between the curve and the x-axis is filled //with 'fillAreaType'=0x14 the area included inside the curve is filled. First and last curve points are joined by a line to close the otherwise open area. //with 'fillAreaType'=0x12 the area excluded outside the curve is filled. The inverse of fillAreaType=0x14 is filled. //At the moment we only support the first type, so set it to XYCurve::FillingBelow curve->setFillingPosition(XYCurve::FillingBelow); if (originCurve.fillAreaPattern == 0) { curve->setFillingType(PlotArea::Color); } else { curve->setFillingType(PlotArea::Pattern); //map different patterns in originCurve.fillAreaPattern (has the values of Origin::FillPattern) to Qt::BrushStyle; switch (originCurve.fillAreaPattern) { case 0: curve->setFillingBrushStyle(Qt::NoBrush); break; case 1: case 2: case 3: curve->setFillingBrushStyle(Qt::BDiagPattern); break; case 4: case 5: case 6: curve->setFillingBrushStyle(Qt::FDiagPattern); break; case 7: case 8: case 9: curve->setFillingBrushStyle(Qt::DiagCrossPattern); break; case 10: case 11: case 12: curve->setFillingBrushStyle(Qt::HorPattern); break; case 13: case 14: case 15: curve->setFillingBrushStyle(Qt::VerPattern); break; case 16: case 17: case 18: curve->setFillingBrushStyle(Qt::CrossPattern); break; } } curve->setFillingFirstColor(color(originCurve.fillAreaColor)); curve->setFillingOpacity(1 - originCurve.fillAreaTransparency/255); //Color fillAreaPatternColor - color for the pattern lines, not supported //double fillAreaPatternWidth - width of the pattern lines, not supported //bool fillAreaWithLineTransparency - transparency of the pattern lines independent of the area transparency, not supported //TODO: //unsigned char fillAreaPatternBorderStyle; //Color fillAreaPatternBorderColor; //double fillAreaPatternBorderWidth; //The Border properties are used only in "Column/Bar" (histogram) plots. Those properties are: //fillAreaPatternBorderStyle for the line style (use enum Origin::LineStyle here) //fillAreaPatternBorderColor for the line color //fillAreaPatternBorderWidth for the line width } else curve->setFillingPosition(XYCurve::NoFilling); } bool OriginProjectParser::loadNote(Note* note, bool preview) { DEBUG("OriginProjectParser::loadNote()"); //load note data const Origin::Note& originNote = m_originFile->note(findNoteByName(note->name())); if (preview) return true; note->setComment(originNote.label.c_str()); note->setNote(QString::fromLatin1(originNote.text.c_str())); return true; } //############################################################################## //########################### Helper functions ################################ //############################################################################## QDateTime OriginProjectParser::creationTime(tree::iterator it) const { //this logic seems to be correct only for the first node (project node). For other nodes the current time is returned. char time_str[21]; strftime(time_str, sizeof(time_str), "%F %T", gmtime(&(*it).creationDate)); return QDateTime::fromString(QString(time_str), Qt::ISODate); } QString OriginProjectParser::parseOriginText(const QString &str) const { DEBUG("parseOriginText()"); QStringList lines = str.split('\n'); QString text; for (int i = 0; i < lines.size(); ++i) { if (i > 0) text.append("
"); text.append(parseOriginTags(lines[i])); } - DEBUG(" PARSED TEXT = " << text.toStdString()); + DEBUG(" PARSED TEXT = " << STDSTRING(text)); return text; } QColor OriginProjectParser::color(Origin::Color color) const { switch (color.type) { case Origin::Color::ColorType::Regular: switch (color.regular) { case Origin::Color::Black: return QColor{Qt::black}; case Origin::Color::Red: return QColor{Qt::red}; case Origin::Color::Green: return QColor{Qt::green}; case Origin::Color::Blue: return QColor{Qt::blue}; case Origin::Color::Cyan: return QColor{Qt::cyan}; case Origin::Color::Magenta: return QColor{Qt::magenta}; case Origin::Color::Yellow: return QColor{Qt::yellow}; case Origin::Color::DarkYellow: return QColor{Qt::darkYellow}; case Origin::Color::Navy: return QColor{0, 0, 128}; case Origin::Color::Purple: return QColor{128, 0, 128}; case Origin::Color::Wine: return QColor{128, 0, 0}; case Origin::Color::Olive: return QColor{0, 128, 0}; case Origin::Color::DarkCyan: return QColor{Qt::darkCyan}; case Origin::Color::Royal: return QColor{0, 0, 160}; case Origin::Color::Orange: return QColor{255, 128, 0}; case Origin::Color::Violet: return QColor{128, 0, 255}; case Origin::Color::Pink: return QColor{255, 0, 128}; case Origin::Color::White: return QColor{Qt::white}; case Origin::Color::LightGray: return QColor{Qt::lightGray}; case Origin::Color::Gray: return QColor{Qt::gray}; case Origin::Color::LTYellow: return QColor{255, 0, 128}; case Origin::Color::LTCyan: return QColor{128, 255, 255}; case Origin::Color::LTMagenta: return QColor{255, 128, 255}; case Origin::Color::DarkGray: return QColor{Qt::darkGray}; case Origin::Color::SpecialV7Axis: return QColor{Qt::black}; } break; case Origin::Color::ColorType::Custom: return QColor{color.custom[0], color.custom[1], color.custom[2]}; case Origin::Color::ColorType::None: case Origin::Color::ColorType::Automatic: case Origin::Color::ColorType::Increment: case Origin::Color::ColorType::Indexing: case Origin::Color::ColorType::RGB: case Origin::Color::ColorType::Mapping: break; } return QColor(Qt::white); } PlotArea::BackgroundColorStyle OriginProjectParser::backgroundColorStyle(Origin::ColorGradientDirection colorGradient) const { switch (colorGradient) { case Origin::ColorGradientDirection::NoGradient: return PlotArea::BackgroundColorStyle::SingleColor; case Origin::ColorGradientDirection::TopLeft: return PlotArea::BackgroundColorStyle::TopLeftDiagonalLinearGradient; case Origin::ColorGradientDirection::Left: return PlotArea::BackgroundColorStyle::HorizontalLinearGradient; case Origin::ColorGradientDirection::BottomLeft: return PlotArea::BackgroundColorStyle::BottomLeftDiagonalLinearGradient; case Origin::ColorGradientDirection::Top: return PlotArea::BackgroundColorStyle::VerticalLinearGradient; case Origin::ColorGradientDirection::Center: return PlotArea::BackgroundColorStyle::RadialGradient; case Origin::ColorGradientDirection::Bottom: return PlotArea::BackgroundColorStyle::VerticalLinearGradient; case Origin::ColorGradientDirection::TopRight: return PlotArea::BackgroundColorStyle::BottomLeftDiagonalLinearGradient; case Origin::ColorGradientDirection::Right: return PlotArea::BackgroundColorStyle::HorizontalLinearGradient; case Origin::ColorGradientDirection::BottomRight: return PlotArea::BackgroundColorStyle::TopLeftDiagonalLinearGradient; } return PlotArea::BackgroundColorStyle::SingleColor; } QString strreverse(const QString &str) { //QString reversing QByteArray ba = str.toLocal8Bit(); std::reverse(ba.begin(), ba.end()); return QString(ba); } QList> OriginProjectParser::charReplacementList() const { QList> replacements; // TODO: probably missed some. Is there any generic method? replacements << qMakePair(QString("ä"), QString("ä")); replacements << qMakePair(QString("ö"), QString("ö")); replacements << qMakePair(QString("ü"), QString("ü")); replacements << qMakePair(QString("Ä"), QString("Ä")); replacements << qMakePair(QString("Ö"), QString("Ö")); replacements << qMakePair(QString("Ü"), QString("Ü")); replacements << qMakePair(QString("ß"), QString("ß")); replacements << qMakePair(QString("€"), QString("€")); replacements << qMakePair(QString("£"), QString("£")); replacements << qMakePair(QString("¥"), QString("¥")); replacements << qMakePair(QString("¤"), QString("¤")); // krazy:exclude=spelling replacements << qMakePair(QString("¦"), QString("¦")); replacements << qMakePair(QString("§"), QString("§")); replacements << qMakePair(QString("µ"), QString("µ")); replacements << qMakePair(QString("¹"), QString("¹")); replacements << qMakePair(QString("²"), QString("²")); replacements << qMakePair(QString("³"), QString("³")); replacements << qMakePair(QString("¶"), QString("¶")); replacements << qMakePair(QString("ø"), QString("ø")); replacements << qMakePair(QString("æ"), QString("æ")); replacements << qMakePair(QString("ð"), QString("ð")); replacements << qMakePair(QString("ħ"), QString("ℏ")); replacements << qMakePair(QString("ĸ"), QString("κ")); replacements << qMakePair(QString("¢"), QString("¢")); replacements << qMakePair(QString("¼"), QString("¼")); replacements << qMakePair(QString("½"), QString("½")); replacements << qMakePair(QString("¾"), QString("¾")); replacements << qMakePair(QString("¬"), QString("¬")); replacements << qMakePair(QString("©"), QString("©")); replacements << qMakePair(QString("®"), QString("®")); replacements << qMakePair(QString("ª"), QString("ª")); replacements << qMakePair(QString("º"), QString("º")); replacements << qMakePair(QString("±"), QString("±")); replacements << qMakePair(QString("¿"), QString("¿")); replacements << qMakePair(QString("×"), QString("×")); replacements << qMakePair(QString("°"), QString("°")); replacements << qMakePair(QString("«"), QString("«")); replacements << qMakePair(QString("»"), QString("»")); replacements << qMakePair(QString("¯"), QString("¯")); replacements << qMakePair(QString("¸"), QString("¸")); replacements << qMakePair(QString("À"), QString("À")); replacements << qMakePair(QString("Á"), QString("Á")); replacements << qMakePair(QString("Â"), QString("Â")); replacements << qMakePair(QString("Ã"), QString("Ã")); replacements << qMakePair(QString("Å"), QString("Å")); replacements << qMakePair(QString("Æ"), QString("Æ")); replacements << qMakePair(QString("Ç"), QString("Ç")); replacements << qMakePair(QString("È"), QString("È")); replacements << qMakePair(QString("É"), QString("É")); replacements << qMakePair(QString("Ê"), QString("Ê")); replacements << qMakePair(QString("Ë"), QString("Ë")); replacements << qMakePair(QString("Ì"), QString("Ì")); replacements << qMakePair(QString("Í"), QString("Í")); replacements << qMakePair(QString("Î"), QString("Î")); replacements << qMakePair(QString("Ï"), QString("Ï")); replacements << qMakePair(QString("Ð"), QString("Ð")); replacements << qMakePair(QString("Ñ"), QString("Ñ")); replacements << qMakePair(QString("Ò"), QString("Ò")); replacements << qMakePair(QString("Ó"), QString("Ó")); replacements << qMakePair(QString("Ô"), QString("Ô")); replacements << qMakePair(QString("Õ"), QString("Õ")); replacements << qMakePair(QString("Ù"), QString("Ù")); replacements << qMakePair(QString("Ú"), QString("Ú")); replacements << qMakePair(QString("Û"), QString("Û")); replacements << qMakePair(QString("Ý"), QString("Ý")); replacements << qMakePair(QString("Þ"), QString("Þ")); replacements << qMakePair(QString("à"), QString("à")); replacements << qMakePair(QString("á"), QString("á")); replacements << qMakePair(QString("â"), QString("â")); replacements << qMakePair(QString("ã"), QString("ã")); replacements << qMakePair(QString("å"), QString("å")); replacements << qMakePair(QString("ç"), QString("ç")); replacements << qMakePair(QString("è"), QString("è")); replacements << qMakePair(QString("é"), QString("é")); replacements << qMakePair(QString("ê"), QString("ê")); replacements << qMakePair(QString("ë"), QString("ë")); replacements << qMakePair(QString("ì"), QString("ì")); replacements << qMakePair(QString("í"), QString("í")); replacements << qMakePair(QString("î"), QString("î")); replacements << qMakePair(QString("ï"), QString("ï")); replacements << qMakePair(QString("ñ"), QString("ñ")); replacements << qMakePair(QString("ò"), QString("ò")); replacements << qMakePair(QString("ó"), QString("ó")); replacements << qMakePair(QString("ô"), QString("ô")); replacements << qMakePair(QString("õ"), QString("õ")); replacements << qMakePair(QString("÷"), QString("÷")); replacements << qMakePair(QString("ù"), QString("ù")); replacements << qMakePair(QString("ú"), QString("ú")); replacements << qMakePair(QString("û"), QString("û")); replacements << qMakePair(QString("ý"), QString("ý")); replacements << qMakePair(QString("þ"), QString("þ")); replacements << qMakePair(QString("ÿ"), QString("ÿ")); replacements << qMakePair(QString("Œ"), QString("Œ")); replacements << qMakePair(QString("œ"), QString("œ")); replacements << qMakePair(QString("Š"), QString("Š")); replacements << qMakePair(QString("š"), QString("š")); replacements << qMakePair(QString("Ÿ"), QString("Ÿ")); replacements << qMakePair(QString("†"), QString("†")); replacements << qMakePair(QString("‡"), QString("‡")); replacements << qMakePair(QString("…"), QString("…")); replacements << qMakePair(QString("‰"), QString("‰")); replacements << qMakePair(QString("™"), QString("™")); return replacements; } QString OriginProjectParser::replaceSpecialChars(const QString& text) const { QString t = text; for (const auto& r : charReplacementList()) t.replace(r.first, r.second); return t; } /*! * converts the string with Origin's syntax for text formatting/highlighting * to a string in the richtext/html format supported by Qt. * For the supported syntax, see: * https://www.originlab.com/doc/LabTalk/ref/Label-cmd * https://www.originlab.com/doc/Origin-Help/TextOb-Prop-Text-tab * https://doc.qt.io/qt-5/richtext-html-subset.html */ QString OriginProjectParser::parseOriginTags(const QString& str) const { DEBUG("parseOriginTags()"); - DEBUG(" string: " << str.toStdString()); + DEBUG(" string: " << STDSTRING(str)); QDEBUG(" UTF8 string: " << str.toUtf8()); QString line = str; //replace %(...) tags // QRegExp rxcol("\\%\\(\\d+\\)"); // replace \l(x) (plot legend tags) with \\c{x}, where x is a digit line.replace(QRegularExpression(QStringLiteral("\\\\\\s*l\\s*\\(\\s*(\\d+)\\s*\\)")), QStringLiteral("\\c{\\1}")); // replace umlauts etc. line = replaceSpecialChars(line); // replace tabs (not really supported) line.replace('\t', "        "); // In PCRE2 (which is what QRegularExpression uses) variable-length lookbehind is supposed to be // exprimental in Perl 5.30; which means it doesn't work at the moment, i.e. using a variable-length // negative lookbehind isn't valid syntax from QRegularExpression POV. // Ultimately we have to reverse the string and use a negative _lookahead_ instead. // The goal is to temporatily replace '(' and ')' that don't denote tags; this is so that we // can handle parenthesis that are inside the tag, e.g. '\b(bold (cf))', we want the '(cf)' part // to remain as is. const QRegularExpression nonTagsRe("\\)([^)(]*)\\((?!\\s*([buigs\\+\\-]|\\d{1,3}\\s*[pc]|[\\w ]+\\s*:\\s*f)\\s*\\\\)"); QString linerev = strreverse(line); const QString lBracket = strreverse("&lbracket;"); const QString rBracket = strreverse("&rbracket;"); linerev.replace(nonTagsRe, rBracket + QStringLiteral("\\1") + lBracket); // change the line back to normal line = strreverse(linerev); //replace \-(...), \+(...), \b(...), \i(...), \u(...), \s(....), \g(...), \f:font(...), // \c'number'(...), \p'size'(...) tags with equivalent supported HTML syntax const QRegularExpression tagsRe(QStringLiteral("\\\\\\s*([-+bgisu]|f:(\\w[\\w ]+)|[pc]\\s*(\\d+))\\s*\\(([^()]+?)\\)")); QRegularExpressionMatch rmatch; while (line.contains(tagsRe, &rmatch)) { QString rep; const QString tagText = rmatch.captured(4); const QString marker = rmatch.captured(1); if (marker.startsWith(QLatin1Char('-'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('+'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('b'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('g'))) { // greek symbols e.g. α φ rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('i'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('s'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('u'))) { rep = QStringLiteral("%1").arg(tagText); } else if (marker.startsWith(QLatin1Char('f'))) { rep = QStringLiteral("%2").arg(rmatch.captured(2).trimmed(), tagText); } else if (marker.startsWith(QLatin1Char('p'))) { // e.g. \p200(...), means use font-size 200% rep = QStringLiteral("%2").arg(rmatch.captured(3), tagText); } else if (marker.startsWith(QLatin1Char('c'))) { // e.g. \c12(...), set the text color to the corresponding color from // the color drop-down list in OriginLab const int colorIndex = rmatch.captured(3).toInt(); Origin::Color c; c.type = Origin::Color::ColorType::Regular; c.regular = colorIndex <= 23 ? static_cast(colorIndex) : Origin::Color::RegularColor::Black; QColor color = OriginProjectParser::color(c); rep = QStringLiteral("%2").arg(color.name(), tagText); } line.replace(rmatch.capturedStart(0), rmatch.capturedLength(0), rep); } // put non-tag '(' and ')' back in their places line.replace("&lbracket;", "("); line.replace("&rbracket;", ")"); // special characters line.replace(QRegularExpression(QStringLiteral("\\\\\\((\\d+)\\)")), "&#\\1;"); - DEBUG(" result: " << line.toStdString()); + DEBUG(" result: " << STDSTRING(line)); return line; } diff --git a/src/backend/lib/macros.h b/src/backend/lib/macros.h index d442dc300..98c7dc8e3 100644 --- a/src/backend/lib/macros.h +++ b/src/backend/lib/macros.h @@ -1,493 +1,494 @@ /*************************************************************************** File : macros.h Project : LabPlot Description : Various preprocessor macros -------------------------------------------------------------------- Copyright : (C) 2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2013-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef MACROS_H #define MACROS_H #include #include // C++ style warning (works on Windows) #include #define WARN(x) std::cout << x << std::endl; #ifndef NDEBUG #include #define QDEBUG(x) qDebug() << x; // C++ style debugging (works on Windows) #include #define DEBUG(x) std::cout << x << std::endl; #else #define QDEBUG(x) {} #define DEBUG(x) {} #endif #define UTF8_QSTRING(str) QString::fromUtf8(str) +#define STDSTRING(qstr) qstr.toUtf8().constData() #define ENUM_TO_STRING(class, enum, value) \ (class::staticMetaObject.enumerator(class::staticMetaObject.indexOfEnumerator(#enum)).valueToKey(value)) #define ENUM_COUNT(class, enum) \ (class::staticMetaObject.enumerator(class::staticMetaObject.indexOfEnumerator(#enum)).keyCount()) #define BASIC_ACCESSOR(type, var, method, Method) \ type method() const { return var; }; \ void set ## Method(const type value) { var = value; } #define CLASS_ACCESSOR(type, var, method, Method) \ type method() const { return var; }; \ void set ## Method(const type & value) { var = value; } #define BASIC_D_ACCESSOR_DECL(type, method, Method) \ type method() const; \ void set ## Method(const type value); #define BASIC_D_ACCESSOR_IMPL(classname, type, method, Method, var) \ void classname::set ## Method(const type value) \ { \ d->var = value; \ } \ type classname::method() const \ { \ return d->var; \ } #define BASIC_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ return d->var; \ } #define BASIC_SHARED_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ Q_D(const classname); \ return d->var; \ } #define CLASS_D_ACCESSOR_DECL(type, method, Method) \ type method() const; \ void set ## Method(const type & value); #define CLASS_D_ACCESSOR_IMPL(classname, type, method, Method, var) \ void classname::set ## Method(const type & value) \ { \ d->var = value; \ } \ type classname::method() const \ { \ return d->var; \ } #define CLASS_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ return d->var; \ } #define CLASS_SHARED_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ Q_D(const classname); \ return d->var; \ } #define POINTER_D_ACCESSOR_DECL(type, method, Method) \ type *method() const; \ void set ## Method(type *ptr); #define FLAG_D_ACCESSOR_DECL(Method) \ bool is ## Method() const; \ bool has ## Method() const; \ void set ## Method(const bool value = true); \ void enable ## Method(const bool value = true); #define FLAG_D_ACCESSOR_IMPL(classname, Method, var) \ void classname::set ## Method(const bool value) \ { \ d->var = value; \ } \ void classname::enable ## Method(const bool value) \ { \ d->var = value; \ } \ bool classname::is ## Method() const \ { \ return d->var; \ } \ bool classname::has ## Method() const \ { \ return d->var; \ } #define WAIT_CURSOR QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)) #define RESET_CURSOR QApplication::restoreOverrideCursor() #define STD_SETTER_CMD_IMPL(class_name, cmd_name, value_type, field_name) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ }; #define STD_SETTER_CMD_IMPL_F(class_name, cmd_name, value_type, field_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() override { m_target->finalize_method(); } \ }; // setter class with finalize() and signal emitting. #define STD_SETTER_CMD_IMPL_S(class_name, cmd_name, value_type, field_name) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() override { emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; #define STD_SETTER_CMD_IMPL_F_S(class_name, cmd_name, value_type, field_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() override { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; // setter class with finalize() and signal emitting for changing several properties in one single step (embedded in beginMacro/endMacro) #define STD_SETTER_CMD_IMPL_M_F_S(class_name, cmd_name, value_type, field_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardMacroSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardMacroSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() override { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ virtual void finalizeUndo() override { emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; #define STD_SETTER_CMD_IMPL_I(class_name, cmd_name, value_type, field_name, init_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ }; #define STD_SETTER_CMD_IMPL_IF(class_name, cmd_name, value_type, field_name, init_method, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ virtual void finalize() { m_target->finalize_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL(class_name, cmd_name, value_type, method_name) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_F(class_name, cmd_name, value_type, method_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ virtual void finalize() override { m_target->finalize_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_I(class_name, cmd_name, value_type, method_name, init_method) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_IF(class_name, cmd_name, value_type, method_name, init_method, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ virtual void finalize() { m_target->finalize_method(); } \ }; //xml-serialization/deserialization //QColor #define WRITE_QCOLOR(color) \ do { \ writer->writeAttribute( "color_r", QString::number(color.red()) ); \ writer->writeAttribute( "color_g", QString::number(color.green()) ); \ writer->writeAttribute( "color_b", QString::number(color.blue()) ); \ } while (0) #define READ_QCOLOR(color) \ do { \ str = attribs.value("color_r").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_r").toString()); \ else \ color.setRed( str.toInt() ); \ \ str = attribs.value("color_g").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_g").toString()); \ else \ color.setGreen( str.toInt() ); \ \ str = attribs.value("color_b").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_b").toString()); \ else \ color.setBlue( str.toInt() ); \ } while(0) //QPen #define WRITE_QPEN(pen) \ do { \ writer->writeAttribute( "style", QString::number(pen.style()) ); \ writer->writeAttribute( "color_r", QString::number(pen.color().red()) ); \ writer->writeAttribute( "color_g", QString::number(pen.color().green()) ); \ writer->writeAttribute( "color_b", QString::number(pen.color().blue()) ); \ writer->writeAttribute( "width", QString::number(pen.widthF()) ); \ } while (0) #define READ_QPEN(pen) \ do { \ str = attribs.value("style").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("style").toString()); \ else \ pen.setStyle( (Qt::PenStyle)str.toInt() ); \ \ QColor color; \ str = attribs.value("color_r").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_r").toString()); \ else \ color.setRed( str.toInt() ); \ \ str = attribs.value("color_g").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_g").toString()); \ else \ color.setGreen( str.toInt() ); \ \ str = attribs.value("color_b").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("color_b").toString()); \ else \ color.setBlue( str.toInt() ); \ \ pen.setColor(color); \ \ str = attribs.value("width").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("width").toString()); \ else \ pen.setWidthF( str.toDouble() ); \ } while(0) //QFont #define WRITE_QFONT(font) \ do { \ writer->writeAttribute( "fontFamily", font.family() ); \ writer->writeAttribute( "fontSize", QString::number(font.pixelSize()) ); \ writer->writeAttribute( "fontPointSize", QString::number(font.pointSize()));\ writer->writeAttribute( "fontWeight", QString::number(font.weight()) ); \ writer->writeAttribute( "fontItalic", QString::number(font.italic()) ); \ } while(0) #define READ_QFONT(font) \ do { \ str = attribs.value("fontFamily").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontFamily").toString()); \ else \ font.setFamily( str ); \ \ str = attribs.value("fontSize").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontSize").toString()); \ else { \ int size = str.toInt(); \ if (size != -1) \ font.setPixelSize(size); \ } \ \ str = attribs.value("fontPointSize").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontPointSize").toString()); \ else { \ int size = str.toInt(); \ if (size != -1) \ font.setPointSize(size); \ } \ \ str = attribs.value("fontWeight").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontWeight").toString()); \ else \ font.setWeight( str.toInt() ); \ \ str = attribs.value("fontItalic").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("fontItalic").toString()); \ else \ font.setItalic( str.toInt() ); \ } while(0) //QBrush #define WRITE_QBRUSH(brush) \ do { \ writer->writeAttribute("brush_style", QString::number(brush.style()) ); \ writer->writeAttribute("brush_color_r", QString::number(brush.color().red())); \ writer->writeAttribute("brush_color_g", QString::number(brush.color().green()));\ writer->writeAttribute("brush_color_b", QString::number(brush.color().blue())); \ } while(0) #define READ_QBRUSH(brush) \ do { \ str = attribs.value("brush_style").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("brush_style").toString()); \ else \ brush.setStyle( (Qt::BrushStyle)str.toInt() ); \ \ QColor color; \ str = attribs.value("brush_color_r").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("brush_color_r").toString()); \ else \ color.setRed( str.toInt() ); \ \ str = attribs.value("brush_color_g").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("brush_color_g").toString()); \ else \ color.setGreen( str.toInt() ); \ \ str = attribs.value("brush_color_b").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs("brush_color_b").toString()); \ else \ color.setBlue( str.toInt() ); \ \ brush.setColor(color); \ } while(0) //Column #define WRITE_COLUMN(column, columnName) \ do { \ if (column){ \ writer->writeAttribute( #columnName, column->path() ); \ } else { \ writer->writeAttribute( #columnName, QString() ); \ } \ } while(0) //column names can be empty in case no columns were used before save //the actual pointers to the x- and y-columns are restored in Project::load() #define READ_COLUMN(columnName) \ do { \ str = attribs.value(#columnName).toString(); \ d->columnName ##Path = str; \ } while(0) #define READ_INT_VALUE(name, var, type) \ str = attribs.value(name).toString(); \ if (str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs(name).toString()); \ else \ d->var = (type)str.toInt(); #define READ_DOUBLE_VALUE(name, var) \ str = attribs.value(name).toString(); \ if (str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs(name).toString()); \ else \ d->var = str.toDouble(); #define READ_STRING_VALUE(name, var) \ str = attribs.value(name).toString(); \ if (str.isEmpty()) \ reader->raiseWarning(attributeWarning.subs(name).toString()); \ else \ d->var = str; //used in Project::load() #define RESTORE_COLUMN_POINTER(obj, col, Col) \ do { \ if (!obj->col ##Path().isEmpty()) { \ for (Column* column : columns) { \ if (!column) continue; \ if (column->path() == obj->col ##Path()) { \ obj->set## Col(column); \ break; \ } \ } \ } \ } while(0) #define WRITE_PATH(obj, name) \ do { \ if (obj){ \ writer->writeAttribute( #name, obj->path() ); \ } else { \ writer->writeAttribute( #name, QString() ); \ } \ } while(0) #define READ_PATH(name) \ do { \ str = attribs.value(#name).toString(); \ d->name ##Path = str; \ } while(0) #define RESTORE_POINTER(obj, name, Name, Type, list) \ do { \ if (!obj->name ##Path().isEmpty()) { \ for (AbstractAspect* aspect : list) { \ if (aspect->path() == obj->name ##Path()) { \ auto a = dynamic_cast(aspect); \ if (!a) continue; \ obj->set## Name(a); \ break; \ } \ } \ } \ } while(0) #endif // MACROS_H diff --git a/src/backend/worksheet/TextLabel.cpp b/src/backend/worksheet/TextLabel.cpp index 63b91b89a..32ba18447 100644 --- a/src/backend/worksheet/TextLabel.cpp +++ b/src/backend/worksheet/TextLabel.cpp @@ -1,1116 +1,1116 @@ /*************************************************************************** File : TextLabel.cpp Project : LabPlot Description : Text label supporting reach text and latex formatting -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2019 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "TextLabel.h" #include "Worksheet.h" #include "TextLabelPrivate.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * \class TextLabel * \brief A label supporting rendering of html- and tex-formatted texts. * * The label is aligned relative to the specified position. * The position can be either specified by providing the x- and y- coordinates * in parent's coordinate system, or by specifying one of the predefined position * flags (\c HorizontalPosition, \c VerticalPosition). */ TextLabel::TextLabel(const QString& name, Type type) : WorksheetElement(name, AspectType::TextLabel), d_ptr(new TextLabelPrivate(this)), m_type(type) { init(); } TextLabel::TextLabel(const QString &name, TextLabelPrivate *dd, Type type) : WorksheetElement(name, AspectType::TextLabel), d_ptr(dd), m_type(type) { init(); } TextLabel::Type TextLabel::type() const { return m_type; } void TextLabel::init() { Q_D(TextLabel); QString groupName; switch (m_type) { case General: groupName = "TextLabel"; break; case PlotTitle: groupName = "PlotTitle"; break; case AxisTitle: groupName = "AxisTitle"; break; case PlotLegendTitle: groupName = "PlotLegendTitle"; break; } const KConfig config; - DEBUG(" config has group \"" << groupName.toStdString() << "\": " << config.hasGroup(groupName)); + DEBUG(" config has group \"" << STDSTRING(groupName) << "\": " << config.hasGroup(groupName)); // group is always valid if you call config.group(..; KConfigGroup group; if (config.hasGroup(groupName)) group = config.group(groupName); // non-default settings d->staticText.setTextFormat(Qt::RichText); // explicitly set no wrap mode for text label to avoid unnecessary line breaks QTextOption textOption; textOption.setWrapMode(QTextOption::NoWrap); d->staticText.setTextOption(textOption); if (m_type == PlotTitle || m_type == PlotLegendTitle) { d->position.verticalPosition = WorksheetElement::vPositionTop; d->verticalAlignment = WorksheetElement::vAlignBottom; } else if (m_type == AxisTitle) { d->position.horizontalPosition = WorksheetElement::hPositionCustom; d->position.verticalPosition = WorksheetElement::vPositionCustom; } // read settings from config if group exists if (group.isValid()) { //properties common to all types d->textWrapper.teXUsed = group.readEntry("TeXUsed", d->textWrapper.teXUsed); d->teXFont.setFamily(group.readEntry("TeXFontFamily", d->teXFont.family())); d->teXFont.setPointSize(group.readEntry("TeXFontSize", d->teXFont.pointSize())); d->fontColor = group.readEntry("TeXFontColor", d->fontColor); d->backgroundColor = group.readEntry("TeXBackgroundColor", d->backgroundColor); d->rotationAngle = group.readEntry("Rotation", d->rotationAngle); //border d->borderShape = (TextLabel::BorderShape)group.readEntry("BorderShape", (int)d->borderShape); d->borderPen = QPen(group.readEntry("BorderColor", d->borderPen.color()), group.readEntry("BorderWidth", d->borderPen.width()), (Qt::PenStyle) group.readEntry("BorderStyle", (int)(d->borderPen.style()))); d->borderOpacity = group.readEntry("BorderOpacity", d->borderOpacity); //position and alignment relevant properties d->position.point.setX( group.readEntry("PositionXValue", d->position.point.x()) ); d->position.point.setY( group.readEntry("PositionYValue", d->position.point.y()) ); d->position.horizontalPosition = (HorizontalPosition) group.readEntry("PositionX", (int)d->position.horizontalPosition); d->position.verticalPosition = (VerticalPosition) group.readEntry("PositionY", (int)d->position.verticalPosition); d->horizontalAlignment = (WorksheetElement::HorizontalAlignment) group.readEntry("HorizontalAlignment", (int)d->horizontalAlignment); d->verticalAlignment = (WorksheetElement::VerticalAlignment) group.readEntry("VerticalAlignment", (int)d->verticalAlignment); } DEBUG("CHECK: default/run time image resolution: " << d->teXImageResolution << '/' << QApplication::desktop()->physicalDpiX()); connect(&d->teXImageFutureWatcher, &QFutureWatcher::finished, this, &TextLabel::updateTeXImage); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene TextLabel::~TextLabel() = default; QGraphicsItem* TextLabel::graphicsItem() const { return d_ptr; } void TextLabel::setParentGraphicsItem(QGraphicsItem* item) { Q_D(TextLabel); d->setParentItem(item); d->updatePosition(); } void TextLabel::retransform() { Q_D(TextLabel); d->retransform(); } void TextLabel::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("TextLabel::handleResize()"); Q_UNUSED(pageResize); Q_D(TextLabel); double ratio = 0; if (horizontalRatio > 1.0 || verticalRatio > 1.0) ratio = qMax(horizontalRatio, verticalRatio); else ratio = qMin(horizontalRatio, verticalRatio); d->teXFont.setPointSizeF(d->teXFont.pointSizeF() * ratio); d->updateText(); //TODO: doesn't seem to work QTextDocument doc; doc.setHtml(d->textWrapper.text); QTextCursor cursor(&doc); cursor.select(QTextCursor::Document); QTextCharFormat fmt = cursor.charFormat(); QFont font = fmt.font(); font.setPointSizeF(font.pointSizeF() * ratio); fmt.setFont(font); cursor.setCharFormat(fmt); } /*! Returns an icon to be used in the project explorer. */ QIcon TextLabel::icon() const{ return QIcon::fromTheme("draw-text"); } QMenu* TextLabel::createContextMenu() { QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" if (!visibilityAction) { visibilityAction = new QAction(i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &TextLabel::visibilityChanged); } visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); menu->insertSeparator(firstAction); return menu; } /* ============================ getter methods ================= */ CLASS_SHARED_D_READER_IMPL(TextLabel, TextLabel::TextWrapper, text, textWrapper) CLASS_SHARED_D_READER_IMPL(TextLabel, QColor, fontColor, fontColor); CLASS_SHARED_D_READER_IMPL(TextLabel, QColor, backgroundColor, backgroundColor); CLASS_SHARED_D_READER_IMPL(TextLabel, QFont, teXFont, teXFont); CLASS_SHARED_D_READER_IMPL(TextLabel, TextLabel::PositionWrapper, position, position); BASIC_SHARED_D_READER_IMPL(TextLabel, WorksheetElement::HorizontalAlignment, horizontalAlignment, horizontalAlignment); BASIC_SHARED_D_READER_IMPL(TextLabel, WorksheetElement::VerticalAlignment, verticalAlignment, verticalAlignment); BASIC_SHARED_D_READER_IMPL(TextLabel, qreal, rotationAngle, rotationAngle); BASIC_SHARED_D_READER_IMPL(TextLabel, TextLabel::BorderShape, borderShape, borderShape) CLASS_SHARED_D_READER_IMPL(TextLabel, QPen, borderPen, borderPen) BASIC_SHARED_D_READER_IMPL(TextLabel, qreal, borderOpacity, borderOpacity) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(TextLabel, SetText, TextLabel::TextWrapper, textWrapper, updateText); void TextLabel::setText(const TextWrapper &textWrapper) { Q_D(TextLabel); if ( (textWrapper.text != d->textWrapper.text) || (textWrapper.teXUsed != d->textWrapper.teXUsed) ) exec(new TextLabelSetTextCmd(d, textWrapper, ki18n("%1: set label text"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFont, QFont, teXFont, updateText); void TextLabel::setTeXFont(const QFont& font) { Q_D(TextLabel); if (font != d->teXFont) exec(new TextLabelSetTeXFontCmd(d, font, ki18n("%1: set TeX main font"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFontColor, QColor, fontColor, updateText); void TextLabel::setFontColor(const QColor color) { Q_D(TextLabel); if (color != d->fontColor) exec(new TextLabelSetTeXFontColorCmd(d, color, ki18n("%1: set font color"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXBackgroundColor, QColor, backgroundColor, updateText); void TextLabel::setBackgroundColor(const QColor color) { Q_D(TextLabel); if (color != d->backgroundColor) exec(new TextLabelSetTeXBackgroundColorCmd(d, color, ki18n("%1: set background color"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetPosition, TextLabel::PositionWrapper, position, retransform); void TextLabel::setPosition(const PositionWrapper& pos) { Q_D(TextLabel); if (pos.point != d->position.point || pos.horizontalPosition != d->position.horizontalPosition || pos.verticalPosition != d->position.verticalPosition) exec(new TextLabelSetPositionCmd(d, pos, ki18n("%1: set position"))); } /*! sets the position without undo/redo-stuff */ void TextLabel::setPosition(QPointF point) { Q_D(TextLabel); if (point != d->position.point) { d->position.point = point; retransform(); } } /*! * position is set to invalid if the parent item is not drawn on the scene * (e.g. axis is not drawn because it's outside plot ranges -> don't draw axis' title label) */ void TextLabel::setPositionInvalid(bool invalid) { Q_D(TextLabel); if (invalid != d->positionInvalid) d->positionInvalid = invalid; } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetRotationAngle, qreal, rotationAngle, recalcShapeAndBoundingRect); void TextLabel::setRotationAngle(qreal angle) { Q_D(TextLabel); if (angle != d->rotationAngle) exec(new TextLabelSetRotationAngleCmd(d, angle, ki18n("%1: set rotation angle"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetHorizontalAlignment, TextLabel::HorizontalAlignment, horizontalAlignment, retransform); void TextLabel::setHorizontalAlignment(const WorksheetElement::HorizontalAlignment hAlign) { Q_D(TextLabel); if (hAlign != d->horizontalAlignment) exec(new TextLabelSetHorizontalAlignmentCmd(d, hAlign, ki18n("%1: set horizontal alignment"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetVerticalAlignment, WorksheetElement::VerticalAlignment, verticalAlignment, retransform); void TextLabel::setVerticalAlignment(const TextLabel::VerticalAlignment vAlign) { Q_D(TextLabel); if (vAlign != d->verticalAlignment) exec(new TextLabelSetVerticalAlignmentCmd(d, vAlign, ki18n("%1: set vertical alignment"))); } //Border STD_SETTER_CMD_IMPL_F_S(TextLabel, SetBorderShape, TextLabel::BorderShape, borderShape, updateBorder) void TextLabel::setBorderShape(TextLabel::BorderShape shape) { Q_D(TextLabel); if (shape != d->borderShape) exec(new TextLabelSetBorderShapeCmd(d, shape, ki18n("%1: set border shape"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetBorderPen, QPen, borderPen, update) void TextLabel::setBorderPen(const QPen &pen) { Q_D(TextLabel); if (pen != d->borderPen) exec(new TextLabelSetBorderPenCmd(d, pen, ki18n("%1: set border"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetBorderOpacity, qreal, borderOpacity, update) void TextLabel::setBorderOpacity(qreal opacity) { Q_D(TextLabel); if (opacity != d->borderOpacity) exec(new TextLabelSetBorderOpacityCmd(d, opacity, ki18n("%1: set border opacity"))); } //misc STD_SWAP_METHOD_SETTER_CMD_IMPL_F(TextLabel, SetVisible, bool, swapVisible, retransform); void TextLabel::setVisible(bool on) { Q_D(TextLabel); exec(new TextLabelSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool TextLabel::isVisible() const { Q_D(const TextLabel); return d->isVisible(); } void TextLabel::setPrinting(bool on) { Q_D(TextLabel); d->m_printing = on; } void TextLabel::updateTeXImage() { Q_D(TextLabel); d->updateTeXImage(); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void TextLabel::visibilityChanged() { Q_D(const TextLabel); this->setVisible(!d->isVisible()); } //############################################################################## //####################### Private implementation ############################### //############################################################################## TextLabelPrivate::TextLabelPrivate(TextLabel* owner) : q(owner) { setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); setFlag(QGraphicsItem::ItemIsFocusable); setAcceptHoverEvents(true); } QString TextLabelPrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the label. Called on geometry or text changes. */ void TextLabelPrivate::retransform() { if (suppressRetransform) return; if (position.horizontalPosition != WorksheetElement::hPositionCustom || position.verticalPosition != WorksheetElement::vPositionCustom) updatePosition(); double x = position.point.x(); double y = position.point.y(); //determine the size of the label in scene units. double w, h; if (textWrapper.teXUsed) { //image size is in pixel, convert to scene units w = teXImage.width()*teXImageScaleFactor; h = teXImage.height()*teXImageScaleFactor; } else { //size is in points, convert to scene units w = staticText.size().width()*scaleFactor; h = staticText.size().height()*scaleFactor; } //depending on the alignment, calculate the new GraphicsItem's position in parent's coordinate system QPointF itemPos; switch (horizontalAlignment) { case WorksheetElement::hAlignLeft: itemPos.setX(x - w/2); break; case WorksheetElement::hAlignCenter: itemPos.setX(x); break; case WorksheetElement::hAlignRight: itemPos.setX(x + w/2); break; } switch (verticalAlignment) { case WorksheetElement::vAlignTop: itemPos.setY(y - h/2); break; case WorksheetElement::vAlignCenter: itemPos.setY(y); break; case WorksheetElement::vAlignBottom: itemPos.setY(y + h/2); break; } suppressItemChangeEvent = true; setPos(itemPos); suppressItemChangeEvent = false; boundingRectangle.setX(-w/2); boundingRectangle.setY(-h/2); boundingRectangle.setWidth(w); boundingRectangle.setHeight(h); updateBorder(); emit q->changed(); } /*! calculates the position of the label, when the position relative to the parent was specified (left, right, etc.) */ void TextLabelPrivate::updatePosition() { //determine the parent item QRectF parentRect; QGraphicsItem* parent = parentItem(); if (parent) { parentRect = parent->boundingRect(); } else { if (!scene()) return; parentRect = scene()->sceneRect(); } if (position.horizontalPosition != WorksheetElement::hPositionCustom) { if (position.horizontalPosition == WorksheetElement::hPositionLeft) position.point.setX( parentRect.x() ); else if (position.horizontalPosition == WorksheetElement::hPositionCenter) position.point.setX( parentRect.x() + parentRect.width()/2 ); else if (position.horizontalPosition == WorksheetElement::hPositionRight) position.point.setX( parentRect.x() + parentRect.width() ); } if (position.verticalPosition != WorksheetElement::vPositionCustom) { if (position.verticalPosition == WorksheetElement::vPositionTop) position.point.setY( parentRect.y() ); else if (position.verticalPosition == WorksheetElement::vPositionCenter) position.point.setY( parentRect.y() + parentRect.height()/2 ); else if (position.verticalPosition == WorksheetElement::vPositionBottom) position.point.setY( parentRect.y() + parentRect.height() ); } emit q->positionChanged(position); } /*! updates the static text. */ void TextLabelPrivate::updateText() { if (suppressRetransform) return; if (textWrapper.teXUsed) { TeXRenderer::Formatting format; format.fontColor = fontColor; format.backgroundColor = backgroundColor; format.fontSize = teXFont.pointSize(); format.fontFamily = teXFont.family(); format.dpi = teXImageResolution; QFuture future = QtConcurrent::run(TeXRenderer::renderImageLaTeX, textWrapper.text, &teXRenderSuccessful, format); teXImageFutureWatcher.setFuture(future); //don't need to call retransorm() here since it is done in updateTeXImage //when the asynchronous rendering of the image is finished. } else { staticText.setText(textWrapper.text); //the size of the label was most probably changed. //call retransform() to recalculate the position and the bounding box of the label retransform(); } } void TextLabelPrivate::updateTeXImage() { teXImage = teXImageFutureWatcher.result(); retransform(); DEBUG("teXRenderSuccessful =" << teXRenderSuccessful); emit q->teXImageUpdated(teXRenderSuccessful); } void TextLabelPrivate::updateBorder() { borderShapePath = QPainterPath(); switch (borderShape) { case (TextLabel::NoBorder): break; case (TextLabel::BorderShape::Rect): { borderShapePath.addRect(boundingRectangle); break; } case (TextLabel::BorderShape::Ellipse): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.addEllipse(xs - 0.1 * w, ys - 0.1 * h, 1.2 * w, 1.2 * h); break; } case (TextLabel::BorderShape::RoundSideRect): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs, ys); borderShapePath.lineTo(xs + w, ys); borderShapePath.quadTo(xs + w + h/2, ys + h/2, xs + w, ys + h); borderShapePath.lineTo(xs, ys + h); borderShapePath.quadTo(xs - h/2, ys + h/2, xs, ys); break; } case (TextLabel::BorderShape::RoundCornerRect): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs + h * 0.2, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } case (TextLabel::BorderShape::InwardsRoundCornerRect): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs, ys - 0.3 * h); borderShapePath.lineTo(xs + w, ys - 0.3 * h); borderShapePath.quadTo(xs + w, ys, xs + w + 0.3 * h, ys); borderShapePath.lineTo(xs + w + 0.3 * h, ys + h); borderShapePath.quadTo(xs + w, ys + h, xs + w, ys + h + 0.3 * h); borderShapePath.lineTo(xs, ys + h + 0.3 * h); borderShapePath.quadTo(xs, ys + h, xs - 0.3 * h, ys + h); borderShapePath.lineTo(xs - 0.3 * h, ys); borderShapePath.quadTo(xs, ys, xs, ys - 0.3 * h); break; } case (TextLabel::BorderShape::DentedBorderRect): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs - 0.2 * h, ys - 0.2 * h); borderShapePath.quadTo(xs + w / 2, ys, xs + w + 0.2 * h, ys - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h / 2, xs + w + 0.2 * h, ys + h + 0.2 * h); borderShapePath.quadTo(xs + w / 2, ys + h, xs - 0.2 * h, ys + h + 0.2 * h); borderShapePath.quadTo(xs, ys + h / 2, xs - 0.2 * h, ys - 0.2 * h); break; } case (TextLabel::BorderShape::Cuboid): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs, ys); borderShapePath.lineTo(xs + w, ys); borderShapePath.lineTo(xs + w, ys + h); borderShapePath.lineTo(xs, ys + h); borderShapePath.lineTo(xs, ys); borderShapePath.lineTo(xs + 0.3 * h, ys - 0.2 * h); borderShapePath.lineTo(xs + w + 0.3 * h, ys - 0.2 * h); borderShapePath.lineTo(xs + w, ys); borderShapePath.moveTo(xs + w, ys + h); borderShapePath.lineTo(xs + w + 0.3 * h, ys + h - 0.2 * h); borderShapePath.lineTo(xs + w + 0.3 * h, ys - 0.2 * h); break; } case (TextLabel::BorderShape::UpPointingRectangle): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs + h * 0.2, ys); borderShapePath.lineTo(xs + w / 2 - 0.2 * h, ys); borderShapePath.lineTo(xs + w / 2, ys - 0.2 * h); borderShapePath.lineTo(xs + w / 2 + 0.2 * h, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } case (TextLabel::BorderShape::DownPointingRectangle): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs +h * 0.2, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + w / 2 + 0.2 * h, ys + h); borderShapePath.lineTo(xs + w / 2, ys + h + 0.2 * h); borderShapePath.lineTo(xs + w / 2 - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } case (TextLabel::BorderShape::LeftPointingRectangle): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs + h*0.2, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + h / 2 + 0.2 * h); borderShapePath.lineTo(xs - 0.2 * h, ys + h / 2); borderShapePath.lineTo(xs, ys + h / 2 - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } case (TextLabel::BorderShape::RightPointingRectangle): { const double xs = boundingRectangle.x(); const double ys = boundingRectangle.y(); const double w = boundingRectangle.width(); const double h = boundingRectangle.height(); borderShapePath.moveTo(xs + h * 0.2, ys); borderShapePath.lineTo(xs + w - h * 0.2, ys); borderShapePath.quadTo(xs + w, ys, xs + w, ys + h * 0.2); borderShapePath.lineTo(xs + w, ys + h / 2 - 0.2 * h); borderShapePath.lineTo(xs + w + 0.2 * h, ys + h / 2); borderShapePath.lineTo(xs + w, ys + h / 2 + 0.2 * h); borderShapePath.lineTo(xs + w, ys + h - 0.2 * h); borderShapePath.quadTo(xs + w, ys + h, xs + w - 0.2 * h, ys + h); borderShapePath.lineTo(xs + 0.2 * h, ys + h); borderShapePath.quadTo(xs, ys + h, xs, ys + h - 0.2 * h); borderShapePath.lineTo(xs, ys + 0.2 * h); borderShapePath.quadTo(xs, ys, xs + 0.2 * h, ys); break; } } recalcShapeAndBoundingRect(); } bool TextLabelPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->changed(); emit q->visibleChanged(on); return oldValue; } /*! Returns the outer bounds of the item as a rectangle. */ QRectF TextLabelPrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath TextLabelPrivate::shape() const { return labelShape; } /*! recalculates the outer bounds and the shape of the label. */ void TextLabelPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); QMatrix matrix; matrix.rotate(-rotationAngle); labelShape = QPainterPath(); if (borderShape != TextLabel::NoBorder) { labelShape.addPath(WorksheetElement::shapeFromPath(borderShapePath, borderPen)); transformedBoundingRectangle = matrix.mapRect(labelShape.boundingRect()); } else { labelShape.addRect(boundingRectangle); transformedBoundingRectangle = matrix.mapRect(boundingRectangle); } labelShape = matrix.map(labelShape); } void TextLabelPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (positionInvalid) return; if (textWrapper.text.isEmpty()) return; painter->save(); //draw the text painter->rotate(-rotationAngle); if (textWrapper.teXUsed) { if (boundingRect().width() != 0.0 && boundingRect().height() != 0.0) painter->drawImage(boundingRect(), teXImage); } else { // don't set fontColor to pen, because the color // is already in the html code //painter->setPen(fontColor); painter->scale(scaleFactor, scaleFactor); float w = staticText.size().width(); float h = staticText.size().height(); painter->drawStaticText(QPoint(-w/2,-h/2), staticText); } painter->restore(); //draw the border if (borderShape != TextLabel::NoBorder) { painter->save(); painter->rotate(-rotationAngle); painter->setPen(borderPen); painter->setOpacity(borderOpacity); painter->drawPath(borderShapePath); painter->restore(); } if (m_hovered && !isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(labelShape); } if (isSelected() && !m_printing) { painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(labelShape); } } QVariant TextLabelPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //convert item's center point in parent's coordinates TextLabel::PositionWrapper tempPosition; tempPosition.point = positionFromItemPosition(value.toPointF()); tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; tempPosition.verticalPosition = WorksheetElement::vPositionCustom; //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. emit q->positionChanged(tempPosition); } return QGraphicsItem::itemChange(change, value); } void TextLabelPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //convert position of the item in parent coordinates to label's position QPointF point = positionFromItemPosition(pos()); if (point != position.point) { //position was changed -> set the position related member variables suppressRetransform = true; TextLabel::PositionWrapper tempPosition; tempPosition.point = point; tempPosition.horizontalPosition = TextLabel::hPositionCustom; tempPosition.verticalPosition = TextLabel::vPositionCustom; q->setPosition(tempPosition); suppressRetransform = false; } QGraphicsItem::mouseReleaseEvent(event); } void TextLabelPrivate::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up ||event->key() == Qt::Key_Down) { const int delta = 5; QPointF point = positionFromItemPosition(pos()); WorksheetElement::PositionWrapper tempPosition; if (event->key() == Qt::Key_Left) { point.setX(point.x() - delta); tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; tempPosition.verticalPosition = position.verticalPosition; } else if (event->key() == Qt::Key_Right) { point.setX(point.x() + delta); tempPosition.horizontalPosition = WorksheetElement::hPositionCustom; tempPosition.verticalPosition = position.verticalPosition; } else if (event->key() == Qt::Key_Up) { point.setY(point.y() - delta); tempPosition.horizontalPosition = position.horizontalPosition; tempPosition.verticalPosition = WorksheetElement::vPositionCustom; } else if (event->key() == Qt::Key_Down) { point.setY(point.y() + delta); tempPosition.horizontalPosition = position.horizontalPosition; tempPosition.verticalPosition = WorksheetElement::vPositionCustom; } tempPosition.point = point; q->setPosition(tempPosition); } QGraphicsItem::keyPressEvent(event); } /*! * converts label's position to GraphicsItem's position. */ QPointF TextLabelPrivate::positionFromItemPosition(QPointF itemPos) { double x = itemPos.x(); double y = itemPos.y(); double w, h; QPointF tmpPosition; if (textWrapper.teXUsed) { w = teXImage.width()*scaleFactor; h = teXImage.height()*scaleFactor; } else { w = staticText.size().width()*scaleFactor; h = staticText.size().height()*scaleFactor; } //depending on the alignment, calculate the new position switch (horizontalAlignment) { case WorksheetElement::hAlignLeft: tmpPosition.setX(x + w/2); break; case WorksheetElement::hAlignCenter: tmpPosition.setX(x); break; case WorksheetElement::hAlignRight: tmpPosition.setX(x - w/2); break; } switch (verticalAlignment) { case WorksheetElement::vAlignTop: tmpPosition.setY(y + h/2); break; case WorksheetElement::vAlignCenter: tmpPosition.setY(y); break; case WorksheetElement::vAlignBottom: tmpPosition.setY(y - h/2); break; } return tmpPosition; } void TextLabelPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void TextLabelPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void TextLabelPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void TextLabel::save(QXmlStreamWriter* writer) const { Q_D(const TextLabel); writer->writeStartElement( "textLabel" ); writeBasicAttributes(writer); writeCommentElement(writer); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.point.x()) ); writer->writeAttribute( "y", QString::number(d->position.point.y()) ); writer->writeAttribute( "horizontalPosition", QString::number(d->position.horizontalPosition) ); writer->writeAttribute( "verticalPosition", QString::number(d->position.verticalPosition) ); writer->writeAttribute( "horizontalAlignment", QString::number(d->horizontalAlignment) ); writer->writeAttribute( "verticalAlignment", QString::number(d->verticalAlignment) ); writer->writeAttribute( "rotationAngle", QString::number(d->rotationAngle) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); writer->writeStartElement( "text" ); writer->writeCharacters( d->textWrapper.text ); writer->writeEndElement(); writer->writeStartElement( "format" ); writer->writeAttribute( "teXUsed", QString::number(d->textWrapper.teXUsed) ); WRITE_QFONT(d->teXFont); writer->writeAttribute( "fontColor_r", QString::number(d->fontColor.red()) ); writer->writeAttribute( "fontColor_g", QString::number(d->fontColor.green()) ); writer->writeAttribute( "fontColor_b", QString::number(d->fontColor.blue()) ); writer->writeEndElement(); //border writer->writeStartElement("border"); writer->writeAttribute("borderShape", QString::number(d->borderShape)); WRITE_QPEN(d->borderPen); writer->writeAttribute("borderOpacity", QString::number(d->borderOpacity)); writer->writeEndElement(); if (d->textWrapper.teXUsed) { writer->writeStartElement("teXImage"); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); d->teXImage.save(&buffer, "PNG"); writer->writeCharacters(ba.toBase64()); writer->writeEndElement(); } writer->writeEndElement(); // close "textLabel" section } //! Load from XML bool TextLabel::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; Q_D(TextLabel); KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool teXImageFound = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "textLabel") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else d->position.point.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->position.point.setY(str.toDouble()); READ_INT_VALUE("horizontalPosition", position.horizontalPosition, WorksheetElement::HorizontalPosition); READ_INT_VALUE("verticalPosition", position.verticalPosition, WorksheetElement::VerticalPosition); READ_INT_VALUE("horizontalAlignment", horizontalAlignment, WorksheetElement::HorizontalAlignment); READ_INT_VALUE("verticalAlignment", verticalAlignment, WorksheetElement::VerticalAlignment); READ_DOUBLE_VALUE("rotationAngle", rotationAngle); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "text") { d->textWrapper.text = reader->readElementText(); } else if (!preview && reader->name() == "format") { attribs = reader->attributes(); READ_INT_VALUE("teXUsed", textWrapper.teXUsed, bool); READ_QFONT(d->teXFont); str = attribs.value("fontColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fontColor_r").toString()); else d->fontColor.setRed( str.toInt() ); str = attribs.value("fontColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fontColor_g").toString()); else d->fontColor.setGreen( str.toInt() ); str = attribs.value("fontColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("fontColor_b").toString()); else d->fontColor.setBlue( str.toInt() ); } else if (!preview && reader->name() == "border") { attribs = reader->attributes(); READ_INT_VALUE("borderShape", borderShape, BorderShape); READ_QPEN(d->borderPen); READ_DOUBLE_VALUE("borderOpacity", borderOpacity); } else if (!preview && reader->name() == "teXImage") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray ba = QByteArray::fromBase64(content.toLatin1()); teXImageFound = d->teXImage.loadFromData(ba); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (preview) return true; //in case we use latex and the image was stored (older versions of LabPlot didn't save the image)and loaded, //we just need to retransform. //otherwise, we set the static text and retransform in updateText() if ( !(d->textWrapper.teXUsed && teXImageFound) ) d->updateText(); else retransform(); return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void TextLabel::loadThemeConfig(const KConfig& config) { Q_D(TextLabel); KConfigGroup group = config.group("Label"); d->fontColor = group.readEntry("FontColor", QColor(Qt::white)); // used when it's latex text d->backgroundColor = group.readEntry("BackgroundColor", QColor(Qt::black)); // used when it's latex text if (!d->textWrapper.teXUsed && !d->textWrapper.text.isEmpty()) { // TODO: Replace QTextEdit by QTextDocument, because this does not contain the graphical stuff // to set the color in a html text, a qTextEdit must be used QTextEdit te; te.setHtml(d->textWrapper.text); te.selectAll(); te.setTextColor(d->fontColor); //te.setTextBackgroundColor(backgroundColor); // for plain text no background color supported, due to bug https://bugreports.qt.io/browse/QTBUG-25420 // update the text. also in the Widget to which is connected TextWrapper wrapper(te.toHtml(), false, true); setText(wrapper); } // otherwise when changing theme while the textlabel dock is visible, the // color comboboxes do not change the color backgroundColorChanged(d->backgroundColor); fontColorChanged(d->fontColor); group = config.group("CartesianPlot"); QPen pen = this->borderPen(); pen.setColor(group.readEntry("BorderColor", pen.color())); pen.setStyle((Qt::PenStyle)(group.readEntry("BorderStyle", (int) pen.style()))); pen.setWidthF(group.readEntry("BorderWidth", pen.widthF())); this->setBorderPen(pen); this->setBorderOpacity(group.readEntry("BorderOpacity", this->borderOpacity())); } void TextLabel::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("Label"); //TODO // group.writeEntry("TeXFontColor", (QColor) this->fontColor()); } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp index faebe324d..668d2f3c0 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -1,4171 +1,4171 @@ /*************************************************************************** File : CartesianPlot.cpp Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2020 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2018 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017-2018 by Garvit Khatri (garvitdelhi@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CartesianPlot.h" #include "CartesianPlotPrivate.h" #include "Axis.h" #include "XYCurve.h" #include "Histogram.h" #include "XYEquationCurve.h" #include "XYDataReductionCurve.h" #include "XYDifferentiationCurve.h" #include "XYIntegrationCurve.h" #include "XYInterpolationCurve.h" #include "XYSmoothCurve.h" #include "XYFitCurve.h" #include "XYFourierFilterCurve.h" #include "XYFourierTransformCurve.h" #include "XYConvolutionCurve.h" #include "XYCorrelationCurve.h" #include "backend/core/Project.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "backend/worksheet/plots/cartesian/ReferenceLine.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/Image.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" //for PlotDataDialog::AnalysisAction. TODO: find a better place for this enum. #include "kdefrontend/ThemeHandler.h" #include "kdefrontend/widgets/ThemesWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include /** * \class CartesianPlot * \brief A xy-plot. * * */ CartesianPlot::CartesianPlot(const QString &name) : AbstractPlot(name, new CartesianPlotPrivate(this), AspectType::CartesianPlot) { init(); } CartesianPlot::CartesianPlot(const QString &name, CartesianPlotPrivate *dd) : AbstractPlot(name, dd, AspectType::CartesianPlot) { init(); } CartesianPlot::~CartesianPlot() { if (m_menusInitialized) { delete addNewMenu; delete zoomMenu; delete themeMenu; } delete m_coordinateSystem; //don't need to delete objects added with addChild() //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } /*! initializes all member variables of \c CartesianPlot */ void CartesianPlot::init() { Q_D(CartesianPlot); d->cSystem = new CartesianCoordinateSystem(this); m_coordinateSystem = d->cSystem; d->rangeType = CartesianPlot::RangeFree; d->xRangeFormat = CartesianPlot::Numeric; d->yRangeFormat = CartesianPlot::Numeric; d->xRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss"; d->yRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss"; d->rangeFirstValues = 1000; d->rangeLastValues = 1000; d->autoScaleX = true; d->autoScaleY = true; d->xScale = ScaleLinear; d->yScale = ScaleLinear; d->xRangeBreakingEnabled = false; d->yRangeBreakingEnabled = false; //the following factor determines the size of the offset between the min/max points of the curves //and the coordinate system ranges, when doing auto scaling //Factor 0 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges. //TODO: make this factor optional. //Provide in the UI the possibility to choose between "exact" or 0% offset, 2%, 5% and 10% for the auto fit option d->autoScaleOffsetFactor = 0.0f; m_plotArea = new PlotArea(name() + " plot area", this); addChildFast(m_plotArea); //Plot title m_title = new TextLabel(this->name() + QLatin1String("- ") + i18n("Title"), TextLabel::PlotTitle); addChild(m_title); m_title->setHidden(true); m_title->setParentGraphicsItem(m_plotArea->graphicsItem()); //offset between the plot area and the area defining the coordinate system, in scene units. d->horizontalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->rightPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->bottomPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->symmetricPadding = true; connect(this, &AbstractAspect::aspectAdded, this, &CartesianPlot::childAdded); connect(this, &AbstractAspect::aspectRemoved, this, &CartesianPlot::childRemoved); graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true); //theme is not set at this point, initialize the color palette with default colors this->setColorPalette(KConfig()); } /*! initializes all children of \c CartesianPlot and setups a default plot of type \c type with a plot title. */ void CartesianPlot::initDefault(Type type) { Q_D(CartesianPlot); switch (type) { case FourAxes: { d->xMin = 0.0; d->xMax = 1.0; d->yMin = 0.0; d->yMax = 1.0; //Axes Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); QPen pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("x axis 2", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisTop); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMinorGridPen(pen); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("y axis 2", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisRight); axis->setStart(0); axis->setEnd(1); axis->setOffset(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::NoPen); axis->setMinorGridPen(pen); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxes: { d->xMin = 0.0; d->xMax = 1.0; d->yMin = 0.0; d->yMax = 1.0; Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); break; } case TwoAxesCentered: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxesCenteredZero: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } } d->xMinPrev = d->xMin; d->xMaxPrev = d->xMax; d->yMinPrev = d->yMin; d->yMaxPrev = d->yMax; //Geometry, specify the plot rect in scene coordinates. //TODO: Use default settings for left, top, width, height and for min/max for the coordinate system float x = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float y = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float w = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); float h = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); //all plot children are initialized -> set the geometry of the plot in scene coordinates. d->rect = QRectF(x,y,w,h); d->retransform(); } void CartesianPlot::initActions() { //"add new" actions addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this); addHistogramAction = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), this); addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical Equation"), this); // no icons yet addDataReductionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), this); addDifferentiationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiation"), this); addIntegrationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integration"), this); addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolation"), this); addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this); addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Fit"), this); addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this); addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), this); addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"),i18n("(De-)Convolution"), this); addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"),i18n("Auto-/Cross-Correlation"), this); addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("Legend"), this); if (children().size()>0) addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal Axis"), this); addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical Axis"), this); addTextLabelAction = new QAction(QIcon::fromTheme("draw-text"), i18n("Text Label"), this); addImageAction = new QAction(QIcon::fromTheme("viewimage"), i18n("Image"), this); addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("Custom Point"), this); addReferenceLineAction = new QAction(QIcon::fromTheme("draw-line"), i18n("Reference Line"), this); connect(addCurveAction, &QAction::triggered, this, &CartesianPlot::addCurve); connect(addHistogramAction,&QAction::triggered, this, &CartesianPlot::addHistogram); connect(addEquationCurveAction, &QAction::triggered, this, &CartesianPlot::addEquationCurve); connect(addDataReductionCurveAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); connect(addDifferentiationCurveAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); connect(addIntegrationCurveAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); connect(addInterpolationCurveAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); connect(addSmoothCurveAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); connect(addFitCurveAction, &QAction::triggered, this, &CartesianPlot::addFitCurve); connect(addFourierFilterCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve); connect(addFourierTransformCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve); connect(addConvolutionCurveAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve); connect(addCorrelationCurveAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve); connect(addLegendAction, &QAction::triggered, this, static_cast(&CartesianPlot::addLegend)); connect(addHorizontalAxisAction, &QAction::triggered, this, &CartesianPlot::addHorizontalAxis); connect(addVerticalAxisAction, &QAction::triggered, this, &CartesianPlot::addVerticalAxis); connect(addTextLabelAction, &QAction::triggered, this, &CartesianPlot::addTextLabel); connect(addImageAction, &QAction::triggered, this, &CartesianPlot::addImage); connect(addCustomPointAction, &QAction::triggered, this, &CartesianPlot::addCustomPoint); connect(addReferenceLineAction, &QAction::triggered, this, &CartesianPlot::addReferenceLine); //Analysis menu actions // addDataOperationAction = new QAction(i18n("Data Operation"), this); addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), this); addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiate"), this); addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integrate"), this); addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolate"), this); addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this); addConvolutionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Convolute/Deconvolute"), this); addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Auto-/Cross-Correlation"), this); QAction* fitAction = new QAction(i18n("Linear"), this); fitAction->setData(PlotDataDialog::FitLinear); addFitAction.append(fitAction); fitAction = new QAction(i18n("Power"), this); fitAction->setData(PlotDataDialog::FitPower); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 1)"), this); fitAction->setData(PlotDataDialog::FitExp1); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 2)"), this); fitAction->setData(PlotDataDialog::FitExp2); addFitAction.append(fitAction); fitAction = new QAction(i18n("Inverse exponential"), this); fitAction->setData(PlotDataDialog::FitInvExp); addFitAction.append(fitAction); fitAction = new QAction(i18n("Gauss"), this); fitAction->setData(PlotDataDialog::FitGauss); addFitAction.append(fitAction); fitAction = new QAction(i18n("Cauchy-Lorentz"), this); fitAction->setData(PlotDataDialog::FitCauchyLorentz); addFitAction.append(fitAction); fitAction = new QAction(i18n("Arc Tangent"), this); fitAction->setData(PlotDataDialog::FitTan); addFitAction.append(fitAction); fitAction = new QAction(i18n("Hyperbolic Tangent"), this); fitAction->setData(PlotDataDialog::FitTanh); addFitAction.append(fitAction); fitAction = new QAction(i18n("Error Function"), this); fitAction->setData(PlotDataDialog::FitErrFunc); addFitAction.append(fitAction); fitAction = new QAction(i18n("Custom"), this); fitAction->setData(PlotDataDialog::FitCustom); addFitAction.append(fitAction); addFourierFilterAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this); addFourierTransformAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), this); connect(addDataReductionAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve); connect(addDifferentiationAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve); connect(addIntegrationAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve); connect(addInterpolationAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve); connect(addSmoothAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve); connect(addConvolutionAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve); connect(addCorrelationAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve); for (const auto& action : addFitAction) connect(action, &QAction::triggered, this, &CartesianPlot::addFitCurve); connect(addFourierFilterAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve); connect(addFourierTransformAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve); //zoom/navigate actions scaleAutoAction = new QAction(QIcon::fromTheme("labplot-auto-scale-all"), i18n("Auto Scale"), this); scaleAutoXAction = new QAction(QIcon::fromTheme("labplot-auto-scale-x"), i18n("Auto Scale X"), this); scaleAutoYAction = new QAction(QIcon::fromTheme("labplot-auto-scale-y"), i18n("Auto Scale Y"), this); zoomInAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), this); zoomOutAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), this); zoomInXAction = new QAction(QIcon::fromTheme("labplot-zoom-in-x"), i18n("Zoom In X"), this); zoomOutXAction = new QAction(QIcon::fromTheme("labplot-zoom-out-x"), i18n("Zoom Out X"), this); zoomInYAction = new QAction(QIcon::fromTheme("labplot-zoom-in-y"), i18n("Zoom In Y"), this); zoomOutYAction = new QAction(QIcon::fromTheme("labplot-zoom-out-y"), i18n("Zoom Out Y"), this); shiftLeftXAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("Shift Left X"), this); shiftRightXAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("Shift Right X"), this); shiftUpYAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("Shift Up Y"), this); shiftDownYAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Shift Down Y"), this); connect(scaleAutoAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered); connect(scaleAutoXAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered); connect(scaleAutoYAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered); connect(zoomInAction, &QAction::triggered, this, &CartesianPlot::zoomIn); connect(zoomOutAction, &QAction::triggered, this, &CartesianPlot::zoomOut); connect(zoomInXAction, &QAction::triggered, this, &CartesianPlot::zoomInX); connect(zoomOutXAction, &QAction::triggered, this, &CartesianPlot::zoomOutX); connect(zoomInYAction, &QAction::triggered, this, &CartesianPlot::zoomInY); connect(zoomOutYAction, &QAction::triggered, this, &CartesianPlot::zoomOutY); connect(shiftLeftXAction, &QAction::triggered, this, &CartesianPlot::shiftLeftX); connect(shiftRightXAction, &QAction::triggered, this, &CartesianPlot::shiftRightX); connect(shiftUpYAction, &QAction::triggered, this, &CartesianPlot::shiftUpY); connect(shiftDownYAction, &QAction::triggered, this, &CartesianPlot::shiftDownY); //visibility action visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CartesianPlot::visibilityChanged); } void CartesianPlot::initMenus() { initActions(); addNewMenu = new QMenu(i18n("Add New")); addNewMenu->setIcon(QIcon::fromTheme("list-add")); addNewMenu->addAction(addCurveAction); addNewMenu->addAction(addHistogramAction); addNewMenu->addAction(addEquationCurveAction); addNewMenu->addSeparator(); addNewAnalysisMenu = new QMenu(i18n("Analysis Curve")); addNewAnalysisMenu->addAction(addFitCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addDifferentiationCurveAction); addNewAnalysisMenu->addAction(addIntegrationCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addInterpolationCurveAction); addNewAnalysisMenu->addAction(addSmoothCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addFourierFilterCurveAction); addNewAnalysisMenu->addAction(addFourierTransformCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addConvolutionCurveAction); addNewAnalysisMenu->addAction(addCorrelationCurveAction); addNewAnalysisMenu->addSeparator(); addNewAnalysisMenu->addAction(addDataReductionCurveAction); addNewMenu->addMenu(addNewAnalysisMenu); addNewMenu->addSeparator(); addNewMenu->addAction(addLegendAction); addNewMenu->addSeparator(); addNewMenu->addAction(addHorizontalAxisAction); addNewMenu->addAction(addVerticalAxisAction); addNewMenu->addSeparator(); addNewMenu->addAction(addTextLabelAction); addNewMenu->addAction(addImageAction); addNewMenu->addSeparator(); addNewMenu->addAction(addCustomPointAction); addNewMenu->addAction(addReferenceLineAction); zoomMenu = new QMenu(i18n("Zoom/Navigate")); zoomMenu->setIcon(QIcon::fromTheme("zoom-draw")); zoomMenu->addAction(scaleAutoAction); zoomMenu->addAction(scaleAutoXAction); zoomMenu->addAction(scaleAutoYAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInAction); zoomMenu->addAction(zoomOutAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInXAction); zoomMenu->addAction(zoomOutXAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInYAction); zoomMenu->addAction(zoomOutYAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftLeftXAction); zoomMenu->addAction(shiftRightXAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftUpYAction); zoomMenu->addAction(shiftDownYAction); // Data manipulation menu // QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation")); // dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); // dataManipulationMenu->addAction(addDataOperationAction); // dataManipulationMenu->addAction(addDataReductionAction); // Data fit menu QMenu* dataFitMenu = new QMenu(i18n("Fit")); dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve")); dataFitMenu->addAction(addFitAction.at(0)); dataFitMenu->addAction(addFitAction.at(1)); dataFitMenu->addAction(addFitAction.at(2)); dataFitMenu->addAction(addFitAction.at(3)); dataFitMenu->addAction(addFitAction.at(4)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(5)); dataFitMenu->addAction(addFitAction.at(6)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(7)); dataFitMenu->addAction(addFitAction.at(8)); dataFitMenu->addAction(addFitAction.at(9)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(10)); //analysis menu dataAnalysisMenu = new QMenu(i18n("Analysis")); dataAnalysisMenu->addMenu(dataFitMenu); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addDifferentiationAction); dataAnalysisMenu->addAction(addIntegrationAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addInterpolationAction); dataAnalysisMenu->addAction(addSmoothAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addFourierFilterAction); dataAnalysisMenu->addAction(addFourierTransformAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addConvolutionAction); dataAnalysisMenu->addAction(addCorrelationAction); dataAnalysisMenu->addSeparator(); // dataAnalysisMenu->insertMenu(nullptr, dataManipulationMenu); dataAnalysisMenu->addAction(addDataReductionAction); //themes menu themeMenu = new QMenu(i18n("Apply Theme")); themeMenu->setIcon(QIcon::fromTheme("color-management")); auto* themeWidget = new ThemesWidget(nullptr); connect(themeWidget, &ThemesWidget::themeSelected, this, &CartesianPlot::loadTheme); connect(themeWidget, &ThemesWidget::themeSelected, themeMenu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(themeWidget); themeMenu->addAction(widgetAction); m_menusInitialized = true; } QMenu* CartesianPlot::createContextMenu() { if (!m_menusInitialized) initMenus(); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); menu->insertMenu(firstAction, addNewMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, zoomMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, themeMenu); menu->insertSeparator(firstAction); visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } QMenu* CartesianPlot::analysisMenu() { if (!m_menusInitialized) initMenus(); return dataAnalysisMenu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlot::icon() const { return QIcon::fromTheme("office-chart-line"); } QVector CartesianPlot::dependsOn() const { //aspects which the plotted data in the worksheet depends on (spreadsheets and later matrices) QVector aspects; for (const auto* curve : children()) { if (curve->xColumn() && dynamic_cast(curve->xColumn()->parentAspect()) ) aspects << curve->xColumn()->parentAspect(); if (curve->yColumn() && dynamic_cast(curve->yColumn()->parentAspect()) ) aspects << curve->yColumn()->parentAspect(); } return aspects; } void CartesianPlot::navigate(CartesianPlot::NavigationOperation op) { Q_D(CartesianPlot); if (op == ScaleAuto) { if (d->curvesXMinMaxIsDirty || d->curvesYMinMaxIsDirty || !autoScaleX() || !autoScaleY()) { d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; } scaleAuto(); } else if (op == ScaleAutoX) setAutoScaleX(true); else if (op == ScaleAutoY) setAutoScaleY(true); else if (op == ZoomIn) zoomIn(); else if (op == ZoomOut) zoomOut(); else if (op == ZoomInX) zoomInX(); else if (op == ZoomOutX) zoomOutX(); else if (op == ZoomInY) zoomInY(); else if (op == ZoomOutY) zoomOutY(); else if (op == ShiftLeftX) shiftLeftX(); else if (op == ShiftRightX) shiftRightX(); else if (op == ShiftUpY) shiftUpY(); else if (op == ShiftDownY) shiftDownY(); } void CartesianPlot::setSuppressDataChangedSignal(bool value) { Q_D(CartesianPlot); d->suppressRetransform = value; } void CartesianPlot::processDropEvent(QDropEvent* event) { PERFTRACE("CartesianPlot::processDropEvent"); const QMimeData* mimeData = event->mimeData(); if (!mimeData) return; //deserialize the mime data to the vector of aspect pointers QByteArray data = mimeData->data(QLatin1String("labplot-dnd")); QVector vec; QDataStream stream(&data, QIODevice::ReadOnly); stream >> vec; QVector columns; for (auto a : vec) { auto* aspect = (AbstractAspect*)a; auto* column = dynamic_cast(aspect); if (column) columns << column; } //return if there are no columns being dropped. //TODO: extend this later when we allow to drag&drop plots, etc. if (columns.isEmpty()) return; //determine the first column with "x plot designation" as the x-data column for all curves to be created const AbstractColumn* xColumn = nullptr; for (const auto* column : columns) { if (column->plotDesignation() == AbstractColumn::X) { xColumn = column; break; } } //if no column with "x plot designation" is available, use the x-data column of the first curve in the plot, if (xColumn == nullptr) { QVector curves = children(); if (!curves.isEmpty()) xColumn = curves.at(0)->xColumn(); } //use the first dropped column if no column with "x plot designation" nor curves are available if (xColumn == nullptr) xColumn = columns.at(0); //create curves bool curvesAdded = false; for (const auto* column : columns) { if (column == xColumn) continue; XYCurve* curve = new XYCurve(column->name()); curve->suppressRetransform(true); //suppress retransform, all curved will be recalculated at the end curve->setXColumn(xColumn); curve->setYColumn(column); addChild(curve); curve->suppressRetransform(false); curvesAdded = true; } if (curvesAdded) dataChanged(); } bool CartesianPlot::isPanningActive() const { Q_D(const CartesianPlot); return d->panningStarted; } bool CartesianPlot::isHovered() const { Q_D(const CartesianPlot); return d->m_hovered; } bool CartesianPlot::isPrinted() const { Q_D(const CartesianPlot); return d->m_printing; } bool CartesianPlot::isSelected() const { Q_D(const CartesianPlot); return d->isSelected(); } //############################################################################## //################################ getter methods ############################ //############################################################################## BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeType, rangeType, rangeType) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormat) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormat) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeLastValues, rangeLastValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeFirstValues, rangeFirstValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleX, autoScaleX) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMin, xMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMax, xMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, xScale, xScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, xRangeBreakingEnabled, xRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, xRangeBreaks, xRangeBreaks) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleY, autoScaleY) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMin, yMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMax, yMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, yScale, yScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, yRangeBreakingEnabled, yRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, yRangeBreaks, yRangeBreaks) CLASS_SHARED_D_READER_IMPL(CartesianPlot, QPen, cursorPen, cursorPen); CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor0Enable, cursor0Enable); CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor1Enable, cursor1Enable); CLASS_SHARED_D_READER_IMPL(CartesianPlot, QString, theme, theme) /*! returns the actual bounding rectangular of the plot area showing data (plot's rectangular minus padding) in plot's coordinates */ QRectF CartesianPlot::dataRect() const { Q_D(const CartesianPlot); return d->dataRect; } CartesianPlot::MouseMode CartesianPlot::mouseMode() const { Q_D(const CartesianPlot); return d->mouseMode; } const QString& CartesianPlot::xRangeDateTimeFormat() const { Q_D(const CartesianPlot); return d->xRangeDateTimeFormat; } const QString& CartesianPlot::yRangeDateTimeFormat() const { Q_D(const CartesianPlot); return d->yRangeDateTimeFormat; } //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## /*! set the rectangular, defined in scene coordinates */ class CartesianPlotSetRectCmd : public QUndoCommand { public: CartesianPlotSetRectCmd(CartesianPlotPrivate* private_obj, QRectF rect) : m_private(private_obj), m_rect(rect) { setText(i18n("%1: change geometry rect", m_private->name())); }; void redo() override { // const double horizontalRatio = m_rect.width() / m_private->rect.width(); // const double verticalRatio = m_rect.height() / m_private->rect.height(); qSwap(m_private->rect, m_rect); // m_private->q->handleResize(horizontalRatio, verticalRatio, false); m_private->retransform(); emit m_private->q->rectChanged(m_private->rect); }; void undo() override { redo(); } private: CartesianPlotPrivate* m_private; QRectF m_rect; }; void CartesianPlot::setRect(const QRectF& rect) { Q_D(CartesianPlot); if (rect != d->rect) exec(new CartesianPlotSetRectCmd(d, rect)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, rangeChanged); void CartesianPlot::setRangeType(RangeType type) { Q_D(CartesianPlot); if (type != d->rangeType) exec(new CartesianPlotSetRangeTypeCmd(d, type, ki18n("%1: set range type"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeFormat, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormatChanged); void CartesianPlot::setXRangeFormat(RangeFormat format) { Q_D(CartesianPlot); if (format != d->xRangeFormat) exec(new CartesianPlotSetXRangeFormatCmd(d, format, ki18n("%1: set x-range format"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeFormat, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormatChanged); void CartesianPlot::setYRangeFormat(RangeFormat format) { Q_D(CartesianPlot); if (format != d->yRangeFormat) exec(new CartesianPlotSetYRangeFormatCmd(d, format, ki18n("%1: set y-range format"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, rangeChanged); void CartesianPlot::setRangeLastValues(int values) { Q_D(CartesianPlot); if (values != d->rangeLastValues) exec(new CartesianPlotSetRangeLastValuesCmd(d, values, ki18n("%1: set range"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, rangeChanged); void CartesianPlot::setRangeFirstValues(int values) { Q_D(CartesianPlot); if (values != d->rangeFirstValues) exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, ki18n("%1: set range"))); } class CartesianPlotSetAutoScaleXCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change x-range auto scaling", m_private->name())); }; void redo() override { m_autoScaleOld = m_private->autoScaleX; if (m_autoScale) { m_minOld = m_private->xMin; m_maxOld = m_private->xMax; m_private->q->scaleAutoX(); } m_private->autoScaleX = m_autoScale; emit m_private->q->xAutoScaleChanged(m_autoScale); }; void undo() override { if (!m_autoScaleOld) { m_private->xMin = m_minOld; m_private->xMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleX = m_autoScaleOld; emit m_private->q->xAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; double m_minOld; double m_maxOld; }; void CartesianPlot::setAutoScaleX(bool autoScaleX) { Q_D(CartesianPlot); if (autoScaleX != d->autoScaleX) exec(new CartesianPlotSetAutoScaleXCmd(d, autoScaleX)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, double, xMin, retransformScales) void CartesianPlot::setXMin(double xMin) { Q_D(CartesianPlot); if (xMin != d->xMin && xMin != -INFINITY && xMin != INFINITY) { d->curvesYMinMaxIsDirty = true; exec(new CartesianPlotSetXMinCmd(d, xMin, ki18n("%1: set min x"))); if (d->autoScaleY) scaleAutoY(); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, double, xMax, retransformScales) void CartesianPlot::setXMax(double xMax) { Q_D(CartesianPlot); if (xMax != d->xMax && xMax != -INFINITY && xMax != INFINITY) { d->curvesYMinMaxIsDirty = true; exec(new CartesianPlotSetXMaxCmd(d, xMax, ki18n("%1: set max x"))); if (d->autoScaleY) scaleAutoY(); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales) void CartesianPlot::setXScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->xScale) exec(new CartesianPlotSetXScaleCmd(d, scale, ki18n("%1: set x scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreakingEnabled, bool, xRangeBreakingEnabled, retransformScales) void CartesianPlot::setXRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->xRangeBreakingEnabled) exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, ki18n("%1: x-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreaks, CartesianPlot::RangeBreaks, xRangeBreaks, retransformScales) void CartesianPlot::setXRangeBreaks(const RangeBreaks& breakings) { Q_D(CartesianPlot); exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, ki18n("%1: x-range breaks changed"))); } class CartesianPlotSetAutoScaleYCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change y-range auto scaling", m_private->name())); }; void redo() override { m_autoScaleOld = m_private->autoScaleY; if (m_autoScale) { m_minOld = m_private->yMin; m_maxOld = m_private->yMax; m_private->q->scaleAutoY(); } m_private->autoScaleY = m_autoScale; emit m_private->q->yAutoScaleChanged(m_autoScale); }; void undo() override { if (!m_autoScaleOld) { m_private->yMin = m_minOld; m_private->yMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleY = m_autoScaleOld; emit m_private->q->yAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; double m_minOld; double m_maxOld; }; void CartesianPlot::setAutoScaleY(bool autoScaleY) { Q_D(CartesianPlot); if (autoScaleY != d->autoScaleY) exec(new CartesianPlotSetAutoScaleYCmd(d, autoScaleY)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMin, double, yMin, retransformScales) void CartesianPlot::setYMin(double yMin) { Q_D(CartesianPlot); if (yMin != d->yMin) { d->curvesXMinMaxIsDirty = true; exec(new CartesianPlotSetYMinCmd(d, yMin, ki18n("%1: set min y"))); if (d->autoScaleX) scaleAutoX(); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, double, yMax, retransformScales) void CartesianPlot::setYMax(double yMax) { Q_D(CartesianPlot); if (yMax != d->yMax) { d->curvesXMinMaxIsDirty = true; exec(new CartesianPlotSetYMaxCmd(d, yMax, ki18n("%1: set max y"))); if (d->autoScaleX) scaleAutoX(); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales) void CartesianPlot::setYScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->yScale) exec(new CartesianPlotSetYScaleCmd(d, scale, ki18n("%1: set y scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreakingEnabled, bool, yRangeBreakingEnabled, retransformScales) void CartesianPlot::setYRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->yRangeBreakingEnabled) exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, ki18n("%1: y-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreaks, CartesianPlot::RangeBreaks, yRangeBreaks, retransformScales) void CartesianPlot::setYRangeBreaks(const RangeBreaks& breaks) { Q_D(CartesianPlot); exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, ki18n("%1: y-range breaks changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursorPen, QPen, cursorPen, update) void CartesianPlot::setCursorPen(const QPen &pen) { Q_D(CartesianPlot); if (pen != d->cursorPen) exec(new CartesianPlotSetCursorPenCmd(d, pen, ki18n("%1: y-range breaks changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor0Enable, bool, cursor0Enable, updateCursor) void CartesianPlot::setCursor0Enable(const bool &enable) { Q_D(CartesianPlot); if (enable != d->cursor0Enable) { if (std::isnan(d->cursor0Pos.x())) { // if never set, set initial position d->cursor0Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x()); mousePressCursorModeSignal(0, d->cursor0Pos); // simulate mousePress to update values in the cursor dock } exec(new CartesianPlotSetCursor0EnableCmd(d, enable, ki18n("%1: Cursor0 enable"))); } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor1Enable, bool, cursor1Enable, updateCursor) void CartesianPlot::setCursor1Enable(const bool &enable) { Q_D(CartesianPlot); if (enable != d->cursor1Enable) { if (std::isnan(d->cursor1Pos.x())) { // if never set, set initial position d->cursor1Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x()); mousePressCursorModeSignal(1, d->cursor1Pos); // simulate mousePress to update values in the cursor dock } exec(new CartesianPlotSetCursor1EnableCmd(d, enable, ki18n("%1: Cursor1 enable"))); } } STD_SETTER_CMD_IMPL_S(CartesianPlot, SetTheme, QString, theme) void CartesianPlot::setTheme(const QString& theme) { Q_D(CartesianPlot); if (theme != d->theme) { if (!theme.isEmpty()) { beginMacro( i18n("%1: load theme %2", name(), theme) ); exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme"))); loadTheme(theme); endMacro(); } else exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: disable theming"))); } } //################################################################ //########################## Slots ############################### //################################################################ void CartesianPlot::addHorizontalAxis() { Axis* axis = new Axis("x-axis", Axis::AxisHorizontal); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(xMin()); axis->setEnd(xMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addVerticalAxis() { Axis* axis = new Axis("y-axis", Axis::AxisVertical); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(yMin()); axis->setEnd(yMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addCurve() { addChild(new XYCurve("xy-curve")); } void CartesianPlot::addEquationCurve() { addChild(new XYEquationCurve("f(x)")); } void CartesianPlot::addHistogram() { addChild(new Histogram("Histogram")); } /*! * returns the first selected XYCurve in the plot */ const XYCurve* CartesianPlot::currentCurve() const { for (const auto* curve : this->children()) { if (curve->graphicsItem()->isSelected()) return curve; } return nullptr; } void CartesianPlot::addDataReductionCurve() { XYDataReductionCurve* curve = new XYDataReductionCurve("Data reduction"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: reduce '%2'", name(), curCurve->name()) ); curve->setName( i18n("Reduction of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->dataReductionDataChanged(curve->dataReductionData()); } else { beginMacro(i18n("%1: add data reduction curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addDifferentiationCurve() { XYDifferentiationCurve* curve = new XYDifferentiationCurve("Differentiation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: differentiate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Derivative of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->differentiationDataChanged(curve->differentiationData()); } else { beginMacro(i18n("%1: add differentiation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addIntegrationCurve() { XYIntegrationCurve* curve = new XYIntegrationCurve("Integration"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: integrate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Integral of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->integrationDataChanged(curve->integrationData()); } else { beginMacro(i18n("%1: add integration curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addInterpolationCurve() { XYInterpolationCurve* curve = new XYInterpolationCurve("Interpolation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: interpolate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Interpolation of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); curve->recalculate(); this->addChild(curve); emit curve->interpolationDataChanged(curve->interpolationData()); } else { beginMacro(i18n("%1: add interpolation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addSmoothCurve() { XYSmoothCurve* curve = new XYSmoothCurve("Smooth"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: smooth '%2'", name(), curCurve->name()) ); curve->setName( i18n("Smoothing of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->smoothDataChanged(curve->smoothData()); } else { beginMacro(i18n("%1: add smoothing curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFitCurve() { DEBUG("CartesianPlot::addFitCurve()"); XYFitCurve* curve = new XYFitCurve("fit"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: fit to '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fit to '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); //set the fit model category and type const auto* action = qobject_cast(QObject::sender()); PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt(); curve->initFitData(type); curve->initStartValues(curCurve); //fit with weights for y if the curve has error bars for y if (curCurve->yErrorType() == XYCurve::SymmetricError && curCurve->yErrorPlusColumn()) { XYFitCurve::FitData fitData = curve->fitData(); fitData.yWeightsType = nsl_fit_weight_instrumental; curve->setFitData(fitData); curve->setYErrorColumn(curCurve->yErrorPlusColumn()); } curve->recalculate(); //add the child after the fit was calculated so the dock widgets gets the fit results //and call retransform() after this to calculate and to paint the data points of the fit-curve this->addChild(curve); curve->retransform(); } else { beginMacro(i18n("%1: add fit curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierFilterCurve() { XYFourierFilterCurve* curve = new XYFourierFilterCurve("Fourier filter"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: Fourier filtering of '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fourier filtering of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); } else { beginMacro(i18n("%1: add Fourier filter curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierTransformCurve() { XYFourierTransformCurve* curve = new XYFourierTransformCurve("Fourier transform"); this->addChild(curve); } void CartesianPlot::addConvolutionCurve() { XYConvolutionCurve* curve = new XYConvolutionCurve("Convolution"); this->addChild(curve); } void CartesianPlot::addCorrelationCurve() { XYCorrelationCurve* curve = new XYCorrelationCurve("Auto-/Cross-Correlation"); this->addChild(curve); } /*! * public helper function to set a legend object created outside of CartesianPlot, e.g. in \c OriginProjectParser. */ void CartesianPlot::addLegend(CartesianPlotLegend* legend) { m_legend = legend; this->addChild(legend); } void CartesianPlot::addLegend() { //don't do anything if there's already a legend if (m_legend) return; m_legend = new CartesianPlotLegend(this, "legend"); this->addChild(m_legend); m_legend->retransform(); //only one legend is allowed -> disable the action if (m_menusInitialized) addLegendAction->setEnabled(false); } void CartesianPlot::addTextLabel() { TextLabel* label = new TextLabel("text label"); this->addChild(label); label->setParentGraphicsItem(graphicsItem()); } void CartesianPlot::addImage() { Image* image = new Image("image"); this->addChild(image); } void CartesianPlot::addCustomPoint() { CustomPoint* point = new CustomPoint(this, "custom point"); this->addChild(point); point->retransform(); } void CartesianPlot::addReferenceLine() { ReferenceLine* line = new ReferenceLine(this, "reference line"); this->addChild(line); line->retransform(); } int CartesianPlot::curveCount(){ return children().length(); } const XYCurve* CartesianPlot::getCurve(int index){ return children().at(index); } double CartesianPlot::cursorPos(int cursorNumber) { Q_D(CartesianPlot); if (cursorNumber == 0) return d->cursor0Pos.x(); else return d->cursor1Pos.x(); } void CartesianPlot::childAdded(const AbstractAspect* child) { Q_D(CartesianPlot); const auto* curve = qobject_cast(child); if (curve) { connect(curve, &XYCurve::dataChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::xDataChanged, this, &CartesianPlot::xDataChanged); connect(curve, &XYCurve::xErrorTypeChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::xErrorPlusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::xErrorMinusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::yDataChanged, this, &CartesianPlot::yDataChanged); connect(curve, &XYCurve::yErrorTypeChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::yErrorPlusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, &XYCurve::yErrorMinusColumnChanged, this, &CartesianPlot::dataChanged); connect(curve, static_cast(&XYCurve::visibilityChanged), this, &CartesianPlot::curveVisibilityChanged); //update the legend on changes of the name, line and symbol styles connect(curve, &XYCurve::aspectDescriptionChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::aspectDescriptionChanged, this, &CartesianPlot::curveNameChanged); connect(curve, &XYCurve::lineTypeChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::linePenChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::linePenChanged, this, static_cast(&CartesianPlot::curveLinePenChanged)); connect(curve, &XYCurve::lineOpacityChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsStyleChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsSizeChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsRotationAngleChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsOpacityChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsBrushChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsPenChanged, this, &CartesianPlot::updateLegend); updateLegend(); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; //in case the first curve is added, check whether we start plotting datetime data if (children().size() == 1) { const auto* col = dynamic_cast(curve->xColumn()); if (col) { if (col->columnMode() == AbstractColumn::DateTime) { setUndoAware(false); setXRangeFormat(CartesianPlot::DateTime); setUndoAware(true); //set column's datetime format for all horizontal axis for (auto* axis : children()) { if (axis->orientation() == Axis::AxisHorizontal) { auto* filter = static_cast(col->outputFilter()); d->xRangeDateTimeFormat = filter->format(); axis->setUndoAware(false); axis->setLabelsDateTimeFormat(d->xRangeDateTimeFormat); axis->setUndoAware(true); } } } } col = dynamic_cast(curve->yColumn()); if (col) { if (col->columnMode() == AbstractColumn::DateTime) { setUndoAware(false); setYRangeFormat(CartesianPlot::DateTime); setUndoAware(true); //set column's datetime format for all vertical axis for (auto* axis : children()) { if (axis->orientation() == Axis::AxisVertical) { auto* filter = static_cast(col->outputFilter()); d->yRangeDateTimeFormat = filter->format(); axis->setUndoAware(false); axis->setLabelsDateTimeFormat(d->yRangeDateTimeFormat); axis->setUndoAware(true); } } } } } emit curveAdded(curve); } else { const auto* hist = qobject_cast(child); if (hist) { connect(hist, &Histogram::dataChanged, this, &CartesianPlot::dataChanged); connect(hist, &Histogram::visibilityChanged, this, &CartesianPlot::curveVisibilityChanged); updateLegend(); } // if an element is hovered, the curves which are handled manually in this class // must be unhovered const auto* element = static_cast(child); connect(element, &WorksheetElement::hovered, this, &CartesianPlot::childHovered); } if (!isLoading()) { //if a theme was selected, apply the theme settings for newly added children, too if (!d->theme.isEmpty()) { const auto* elem = dynamic_cast(child); if (elem) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(elem)->loadThemeConfig(config); } } else { //no theme is available, apply the default colors for curves only, s.a. XYCurve::loadThemeConfig() const auto* curve = dynamic_cast(child); if (curve) { int index = indexOfChild(curve); QColor themeColor; if (index < m_themeColorPalette.size()) themeColor = m_themeColorPalette.at(index); else { if (m_themeColorPalette.size()) themeColor = m_themeColorPalette.last(); } auto* c = const_cast(curve); //Line QPen p = curve->linePen(); p.setColor(themeColor); c->setLinePen(p); //Drop line p = curve->dropLinePen(); p.setColor(themeColor); c->setDropLinePen(p); //Symbol QBrush brush = c->symbolsBrush(); brush.setColor(themeColor); c->setSymbolsBrush(brush); p = c->symbolsPen(); p.setColor(themeColor); c->setSymbolsPen(p); //Filling c->setFillingFirstColor(themeColor); //Error bars p.setColor(themeColor); c->setErrorBarsPen(p); } } } } void CartesianPlot::childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); if (m_legend == child) { if (m_menusInitialized) addLegendAction->setEnabled(true); m_legend = nullptr; } else { const auto* curve = qobject_cast(child); if (curve) { updateLegend(); emit curveRemoved(curve); } } } /*! * \brief CartesianPlot::childHovered * Unhover all curves, when another child is hovered. The hover handling for the curves is done in their parent (CartesianPlot), * because the hover should set when the curve is hovered and not just the bounding rect (for more see hoverMoveEvent) */ void CartesianPlot::childHovered() { Q_D(CartesianPlot); bool curveSender = dynamic_cast(QObject::sender()) != nullptr; if (!d->isSelected()) { if (d->m_hovered) d->m_hovered = false; d->update(); } if (!curveSender) { for (auto curve: children()) curve->setHover(false); } } void CartesianPlot::updateLegend() { if (m_legend) m_legend->retransform(); } /*! called when in one of the curves the data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::dataChanged() { if (project() && project()->isLoading()) return; Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; bool updated = false; if (d->autoScaleX && d->autoScaleY) updated = scaleAuto(); else if (d->autoScaleX) updated = scaleAutoX(); else if (d->autoScaleY) updated = scaleAutoY(); if (!updated || !QObject::sender()) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); else { //no sender available, the function was called directly in the file filter (live data source got new data) //or in Project::load() -> retransform all available curves since we don't know which curves are affected. //TODO: this logic can be very expensive for (auto* c : children()) { c->recalcLogicalPoints(); c->retransform(); } } } } } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::xDataChanged() { if (project() && project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesXMinMaxIsDirty = true; bool updated = false; if (d->autoScaleX) updated = this->scaleAutoX(); if (!updated) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); } } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data if (children().size() == 1) { auto* curve = dynamic_cast(QObject::sender()); if (curve) { const AbstractColumn* col = curve->xColumn(); if (col->columnMode() == AbstractColumn::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { setUndoAware(false); setXRangeFormat(CartesianPlot::DateTime); setUndoAware(true); } } } emit curveDataChanged(dynamic_cast(QObject::sender())); } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::yDataChanged() { if (project() && project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesYMinMaxIsDirty = true; bool updated = false; if (d->autoScaleY) updated = this->scaleAutoY(); if (!updated) { //even if the plot ranges were not changed, either no auto scale active or the new data //is within the current ranges and no change of the ranges is required, //retransform the curve in order to show the changes auto* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { auto* hist = dynamic_cast(QObject::sender()); if (hist) hist->retransform(); } } //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data if (children().size() == 1) { auto* curve = dynamic_cast(QObject::sender()); if (curve) { const AbstractColumn* col = curve->yColumn(); if (col->columnMode() == AbstractColumn::DateTime && d->xRangeFormat != CartesianPlot::DateTime) { setUndoAware(false); setYRangeFormat(CartesianPlot::DateTime); setUndoAware(true); } } } emit curveDataChanged(dynamic_cast(QObject::sender())); } void CartesianPlot::curveVisibilityChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; updateLegend(); if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else if (d->autoScaleY) this->scaleAutoY(); emit curveVisibilityChangedSignal(); } void CartesianPlot::curveLinePenChanged(QPen pen) { const auto* curve = qobject_cast(QObject::sender()); emit curveLinePenChanged(pen, curve->name()); } void CartesianPlot::setMouseMode(const MouseMode mouseMode) { Q_D(CartesianPlot); d->mouseMode = mouseMode; d->setHandlesChildEvents(mouseMode != CartesianPlot::SelectionMode); QList items = d->childItems(); if (d->mouseMode == CartesianPlot::SelectionMode) { d->setZoomSelectionBandShow(false); d->setCursor(Qt::ArrowCursor); for (auto* item : items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, false); } else { for (auto* item : items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, true); } //when doing zoom selection, prevent the graphics item from being movable //if it's currently movable (no worksheet layout available) const auto* worksheet = dynamic_cast(parentAspect()); if (worksheet) { if (mouseMode == CartesianPlot::SelectionMode) { if (worksheet->layout() != Worksheet::NoLayout) graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); else graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); } else //zoom m_selection graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } emit mouseModeChanged(mouseMode); } void CartesianPlot::setLocked(bool locked) { Q_D(CartesianPlot); d->locked = locked; } bool CartesianPlot::scaleAutoX() { Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { calculateCurvesXMinMax(false); //loop over all histograms and determine the maximum and minimum x-values for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; if (!curve->dataColumn()) continue; const double min = curve->getXMinimum(); if (min < d->curvesXMin) d->curvesXMin = min; const double max = curve->getXMaximum(); if (max > d->curvesXMax) d->curvesXMax = max; } // do it at the end, because it must be from the real min/max values double errorBarsCapSize = -1; for (auto* curve : this->children()) { if (curve->yErrorType() != XYCurve::ErrorType::NoError) { errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); } } if (errorBarsCapSize > 0) { // must be done, because retransformScales uses xMin/xMax if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) d->xMin = d->curvesXMin; if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) d->xMax = d->curvesXMax; // When the previous scale is completely different. The mapTo functions scale with wrong values. To prevent // this a rescale must be done. // The errorBarsCapSize is in Scene coordinates. So this value must be transformed into a logical value. Due // to nonlinear scalings it cannot only be multiplied with a scaling factor and depends on the position of the // column value // dirty hack: call setIsLoading(true) to suppress the call of retransform() in retransformScales() since a // retransform is already done at the end of this function setIsLoading(true); d->retransformScales(); setIsLoading(false); QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::SuppressPageClipping); point.setX(point.x() - errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); // Problem is, when the scaling is not linear (for example log(x)) and the minimum is 0. In this // case mapLogicalToScene returns (0,0) which is smaller than the curves minimum if (point.x() < d->curvesXMin) d->curvesXMin = point.x(); point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::SuppressPageClipping); point.setX(point.x() + errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.x() > d->curvesXMax) d->curvesXMax = point.x(); } d->curvesYMinMaxIsDirty = true; d->curvesXMinMaxIsDirty = false; } bool update = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; update = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; update = true; } if (update) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } d->retransformScales(); } return update; } bool CartesianPlot::scaleAutoY() { Q_D(CartesianPlot); if (d->curvesYMinMaxIsDirty) { calculateCurvesYMinMax(false); // loop over all curves //loop over all histograms and determine the maximum y-value for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; const double min = curve->getYMinimum(); if (d->curvesYMin > min) d->curvesYMin = min; const double max = curve->getYMaximum(); if (max > d->curvesYMax) d->curvesYMax = max; } // do it at the end, because it must be from the real min/max values double errorBarsCapSize = -1; for (auto* curve : this->children()) { if (curve->xErrorType() != XYCurve::ErrorType::NoError) { errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); } } if (errorBarsCapSize > 0) { if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) d->yMin = d->curvesYMin; if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) d->yMax = d->curvesYMax; setIsLoading(true); d->retransformScales(); setIsLoading(false); QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::SuppressPageClipping); point.setY(point.y() + errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.y() < d->curvesYMin) d->curvesYMin = point.y(); point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::SuppressPageClipping); point.setY(point.y() - errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.y() > d->curvesYMax) d->curvesYMax = point.y(); } d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = false; } bool update = false; if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; update = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; update = true; } if (update) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } d->retransformScales(); } return update; } void CartesianPlot::scaleAutoTriggered() { QAction* action = dynamic_cast(QObject::sender()); if (!action) return; if (action == scaleAutoAction) scaleAuto(); else if (action == scaleAutoXAction) setAutoScaleX(true); else if (action == scaleAutoYAction) setAutoScaleY(true); } bool CartesianPlot::scaleAuto() { DEBUG("CartesianPlot::scaleAuto()"); Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { calculateCurvesXMinMax(); double errorBarsCapSize = -1; for (auto* curve : this->children()) { if (curve->yErrorType() != XYCurve::ErrorType::NoError) { errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); } } if (errorBarsCapSize > 0) { if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) d->xMin = d->curvesXMin; if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) d->xMax = d->curvesXMax; setIsLoading(true); d->retransformScales(); setIsLoading(false); QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::SuppressPageClipping); point.setX(point.x() - errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.x() < d->curvesXMin) d->curvesXMin = point.x(); point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::SuppressPageClipping); point.setX(point.x() + errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.x() > d->curvesXMax) d->curvesXMax = point.x(); } d->curvesXMinMaxIsDirty = false; } if (d->curvesYMinMaxIsDirty) { calculateCurvesYMinMax(); double errorBarsCapSize = -1; for (auto* curve : this->children()) { if (curve->xErrorType() != XYCurve::ErrorType::NoError) { errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); } } if (errorBarsCapSize > 0) { if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) d->yMin = d->curvesYMin; if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) d->yMax = d->curvesYMax; setIsLoading(true); d->retransformScales(); setIsLoading(false); QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::SuppressPageClipping); point.setY(point.y() + errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.y() < d->curvesYMin) d->curvesYMin = point.y(); point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::SuppressPageClipping); point.setY(point.y() - errorBarsCapSize); point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); if (point.y() > d->curvesYMax) d->curvesYMax = point.y(); } d->curvesYMinMaxIsDirty = false; } bool updateX = false; bool updateY = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; updateX = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; updateX = true; } if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; updateY = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; updateY = true; } DEBUG(" xmin/xmax = " << d->xMin << '/' << d->xMax << ", ymin/ymax = " << d->yMin << '/' << d->yMax); if (updateX || updateY) { if (updateX) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } setAutoScaleX(true); } if (updateY) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } setAutoScaleY(true); } d->retransformScales(); } return (updateX || updateY); } /*! * Calculates and sets curves y min and max. This function does not respect the range * of the y axis */ void CartesianPlot::calculateCurvesXMinMax(bool completeRange) { Q_D(CartesianPlot); d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum x-values for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; auto* xColumn = curve->xColumn(); if (!xColumn) continue; double min = d->curvesXMin; double max = d->curvesXMax; int start =0; int end = 0; if (d->rangeType == CartesianPlot::RangeFree && curve->yColumn() && !completeRange) { curve->yColumn()->indicesMinMax(yMin(), yMax(), start, end); if (end < curve->yColumn()->rowCount()) end ++; } else { switch (d->rangeType) { case CartesianPlot::RangeFree: start = 0; end = xColumn->rowCount(); break; case CartesianPlot::RangeLast: start = xColumn->rowCount() - d->rangeLastValues; end = xColumn->rowCount(); break; case CartesianPlot::RangeFirst: start = 0; end = d->rangeFirstValues; break; } } curve->minMaxX(start, end, min, max, true); if (min < d->curvesXMin) d->curvesXMin = min; if (max > d->curvesXMax) d->curvesXMax = max; } //loop over all histograms and determine the maximum and minimum x-values for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; if (!curve->dataColumn()) continue; const double min = curve->getXMinimum(); if (d->curvesXMin > min) d->curvesXMin = min; const double max = curve->getXMaximum(); if (max > d->curvesXMax) d->curvesXMax = max; } } /*! * Calculates and sets curves y min and max. This function does not respect the range * of the x axis */ void CartesianPlot::calculateCurvesYMinMax(bool completeRange) { Q_D(CartesianPlot); d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; double min = d->curvesYMin; double max = d->curvesYMax; //loop over all xy-curves and determine the maximum and minimum y-values for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; auto* yColumn = curve->yColumn(); if (!yColumn) continue; int start =0; int end = 0; if (d->rangeType == CartesianPlot::RangeFree && curve->xColumn() && !completeRange) { curve->xColumn()->indicesMinMax(xMin(), xMax(), start, end); if (end < curve->xColumn()->rowCount()) end ++; // because minMaxY excludes indexMax } else { switch (d->rangeType) { case CartesianPlot::RangeFree: start = 0; end = yColumn->rowCount(); break; case CartesianPlot::RangeLast: start = yColumn->rowCount() - d->rangeLastValues; end = yColumn->rowCount(); break; case CartesianPlot::RangeFirst: start = 0; end = d->rangeFirstValues; break; } } curve->minMaxY(start, end, min, max, true); if (min < d->curvesYMin) d->curvesYMin = min; if (max > d->curvesYMax) d->curvesYMax = max; } //loop over all histograms and determine the maximum y-value for (const auto* curve : this->children()) { if (!curve->isVisible()) continue; const double min = curve->getYMinimum(); if (d->curvesYMin > min) d->curvesYMin = min; const double max = curve->getYMaximum(); if (max > d->curvesYMax) d->curvesYMax = max; } } void CartesianPlot::zoomIn() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; zoom(true, true); //zoom in x zoom(false, true); //zoom in y d->retransformScales(); } void CartesianPlot::zoomOut() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; zoom(true, false); //zoom out x zoom(false, false); //zoom out y d->retransformScales(); } void CartesianPlot::zoomInX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; zoom(true, true); //zoom in x if (d->autoScaleY && autoScaleY()) return; d->retransformScales(); } void CartesianPlot::zoomOutX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; zoom(true, false); //zoom out x if (d->autoScaleY && autoScaleY()) return; d->retransformScales(); } void CartesianPlot::zoomInY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; zoom(false, true); //zoom in y if (d->autoScaleX && autoScaleX()) return; d->retransformScales(); } void CartesianPlot::zoomOutY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; zoom(false, false); //zoom out y if (d->autoScaleX && autoScaleX()) return; d->retransformScales(); } /*! * helper function called in other zoom*() functions * and doing the actual change of the data ranges. * @param x if set to \true the x-range is modified, the y-range for \c false * @param in the "zoom in" is performed if set to \c \true, "zoom out" for \c false */ void CartesianPlot::zoom(bool x, bool in) { Q_D(CartesianPlot); double min; double max; CartesianPlot::Scale scale; if (x) { min = d->xMin; max = d->xMax; scale = d->xScale; } else { min = d->yMin; max = d->yMax; scale = d->yScale; } double factor = m_zoomFactor; if (in) factor = 1/factor; switch (scale) { case ScaleLinear: { double oldRange = max - min; double newRange = (max - min) * factor; max = max + (newRange - oldRange) / 2; min = min - (newRange - oldRange) / 2; break; } case ScaleLog10: case ScaleLog10Abs: { double oldRange = log10(max) - log10(min); double newRange = (log10(max) - log10(min)) * factor; max = max * pow(10, (newRange - oldRange) / 2.); min = min / pow(10, (newRange - oldRange) / 2.); break; } case ScaleLog2: case ScaleLog2Abs: { double oldRange = log2(max) - log2(min); double newRange = (log2(max) - log2(min)) * factor; max = max * pow(2, (newRange - oldRange) / 2.); min = min / pow(2, (newRange - oldRange) / 2.); break; } case ScaleLn: case ScaleLnAbs: { double oldRange = log(max) - log(min); double newRange = (log(max) - log(min)) * factor; max = max * exp((newRange - oldRange) / 2.); min = min / exp((newRange - oldRange) / 2.); break; } case ScaleSqrt: case ScaleX2: break; } if (!std::isnan(min) && !std::isnan(max) && std::isfinite(min) && std::isfinite(max)) { if (x) { d->xMin = min; d->xMax = max; } else { d->yMin = min; d->yMax = max; } } } /*! * helper function called in other shift*() functions * and doing the actual change of the data ranges. * @param x if set to \true the x-range is modified, the y-range for \c false * @param leftOrDown the "shift left" for x or "shift dows" for y is performed if set to \c \true, * "shift right" or "shift up" for \c false */ void CartesianPlot::shift(bool x, bool leftOrDown) { Q_D(CartesianPlot); double min; double max; CartesianPlot::Scale scale; double offset = 0.0; double factor = 0.1; if (x) { min = d->xMin; max = d->xMax; scale = d->xScale; } else { min = d->yMin; max = d->yMax; scale = d->yScale; } if (leftOrDown) factor *= -1.; switch (scale) { case ScaleLinear: { offset = (max - min) * factor; min += offset; max += offset; break; } case ScaleLog10: case ScaleLog10Abs: { offset = (log10(max) - log10(min)) * factor; min *= pow(10, offset); max *= pow(10, offset); break; } case ScaleLog2: case ScaleLog2Abs: { offset = (log2(max) - log2(min)) * factor; min *= pow(2, offset); max *= pow(2, offset); break; } case ScaleLn: case ScaleLnAbs: { offset = (log10(max) - log10(min)) * factor; min *= exp(offset); max *= exp(offset); break; } case ScaleSqrt: case ScaleX2: break; } if (!std::isnan(min) && !std::isnan(max) && std::isfinite(min) && std::isfinite(max)) { if (x) { d->xMin = min; d->xMax = max; } else { d->yMin = min; d->yMax = max; } } } void CartesianPlot::shiftLeftX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; shift(true, true); if (d->autoScaleY && scaleAutoY()) return; d->retransformScales(); } void CartesianPlot::shiftRightX() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleX(false); setUndoAware(true); d->curvesYMinMaxIsDirty = true; shift(true, false); if (d->autoScaleY && scaleAutoY()) return; d->retransformScales(); } void CartesianPlot::shiftUpY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); d->curvesXMinMaxIsDirty = true; shift(false, false); if (d->autoScaleX && scaleAutoX()) return; d->retransformScales(); } void CartesianPlot::shiftDownY() { Q_D(CartesianPlot); setUndoAware(false); setAutoScaleY(false); setUndoAware(true); d->curvesXMinMaxIsDirty = true; shift(false, true); if (d->autoScaleX && scaleAutoX()) return; d->retransformScales(); } void CartesianPlot::cursor() { Q_D(CartesianPlot); d->retransformScales(); } void CartesianPlot::mousePressZoomSelectionMode(QPointF logicPos) { Q_D(CartesianPlot); d->mousePressZoomSelectionMode(logicPos); } void CartesianPlot::mousePressCursorMode(int cursorNumber, QPointF logicPos) { Q_D(CartesianPlot); d->mousePressCursorMode(cursorNumber, logicPos); } void CartesianPlot::mouseMoveZoomSelectionMode(QPointF logicPos) { Q_D(CartesianPlot); d->mouseMoveZoomSelectionMode(logicPos); } void CartesianPlot::mouseMoveCursorMode(int cursorNumber, QPointF logicPos) { Q_D(CartesianPlot); d->mouseMoveCursorMode(cursorNumber, logicPos); } void CartesianPlot::mouseReleaseZoomSelectionMode() { Q_D(CartesianPlot); d->mouseReleaseZoomSelectionMode(); } void CartesianPlot::mouseHoverZoomSelectionMode(QPointF logicPos) { Q_D(CartesianPlot); d->mouseHoverZoomSelectionMode(logicPos); } void CartesianPlot::mouseHoverOutsideDataRect() { Q_D(CartesianPlot); d->mouseHoverOutsideDataRect(); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlot::visibilityChanged() { Q_D(CartesianPlot); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### CartesianPlotPrivate::CartesianPlotPrivate(CartesianPlot* plot) : AbstractPlotPrivate(plot), q(plot) { setData(0, WorksheetElement::NameCartesianPlot); m_cursor0Text.prepare(); m_cursor1Text.prepare(); } /*! updates the position of plot rectangular in scene coordinates to \c r and recalculates the scales. The size of the plot corresponds to the size of the plot area, the area which is filled with the background color etc. and which can pose the parent item for several sub-items (like TextLabel). Note, the size of the area used to define the coordinate system doesn't need to be equal to this plot area. Also, the size (=bounding box) of CartesianPlot can be greater than the size of the plot area. */ void CartesianPlotPrivate::retransform() { if (suppressRetransform) return; PERFTRACE("CartesianPlotPrivate::retransform()"); prepareGeometryChange(); setPos( rect.x()+rect.width()/2, rect.y()+rect.height()/2); updateDataRect(); retransformScales(); //plotArea position is always (0, 0) in parent's coordinates, don't need to update here q->plotArea()->setRect(rect); //call retransform() for the title and the legend (if available) //when a predefined position relative to the (Left, Centered etc.) is used, //the actual position needs to be updated on plot's geometry changes. if (q->title()) q->title()->retransform(); if (q->m_legend) q->m_legend->retransform(); WorksheetElementContainerPrivate::recalcShapeAndBoundingRect(); } void CartesianPlotPrivate::retransformScales() { DEBUG("CartesianPlotPrivate::retransformScales()"); DEBUG(" xmin/xmax = " << xMin << '/'<< xMax << ", ymin/ymax = " << yMin << '/' << yMax); PERFTRACE("CartesianPlotPrivate::retransformScales()"); QVector scales; //check ranges for log-scales if (xScale != CartesianPlot::ScaleLinear) checkXRange(); //check whether we have x-range breaks - the first break, if available, should be valid bool hasValidBreak = (xRangeBreakingEnabled && !xRangeBreaks.list.isEmpty() && xRangeBreaks.list.first().isValid()); static const int breakGap = 20; double sceneStart, sceneEnd, logicalStart, logicalEnd; //create x-scales int plotSceneStart = dataRect.x(); int plotSceneEnd = dataRect.x() + dataRect.width(); if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = xMin; logicalEnd = xMax; //TODO: how should we handle the case sceneStart == sceneEnd? //(to reproduce, create plots and adjust the spacing/pading to get zero size for the plots) if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = xMin; for (const auto& rb : xRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &xRangeBreaks.list.first()) sceneStart += breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the x-data range) sceneStart = sceneEndLast+breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = xMax; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setXScales(scales); //check ranges for log-scales if (yScale != CartesianPlot::ScaleLinear) checkYRange(); //check whether we have y-range breaks - the first break, if available, should be valid hasValidBreak = (yRangeBreakingEnabled && !yRangeBreaks.list.isEmpty() && yRangeBreaks.list.first().isValid()); //create y-scales scales.clear(); plotSceneStart = dataRect.y() + dataRect.height(); plotSceneEnd = dataRect.y(); if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = yMin; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = yMin; for (const auto& rb : yRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &yRangeBreaks.list.first()) sceneStart -= breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the y-data range) sceneStart = sceneEndLast-breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setYScales(scales); //calculate the changes in x and y and save the current values for xMin, xMax, yMin, yMax double deltaXMin = 0; double deltaXMax = 0; double deltaYMin = 0; double deltaYMax = 0; if (xMin != xMinPrev) { deltaXMin = xMin - xMinPrev; emit q->xMinChanged(xMin); } if (xMax != xMaxPrev) { deltaXMax = xMax - xMaxPrev; emit q->xMaxChanged(xMax); } if (yMin != yMinPrev) { deltaYMin = yMin - yMinPrev; emit q->yMinChanged(yMin); } if (yMax != yMaxPrev) { deltaYMax = yMax - yMaxPrev; emit q->yMaxChanged(yMax); } xMinPrev = xMin; xMaxPrev = xMax; yMinPrev = yMin; yMaxPrev = yMax; //adjust auto-scale axes for (auto* axis : q->children()) { if (!axis->autoScale()) continue; if (axis->orientation() == Axis::AxisHorizontal) { if (deltaXMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(xMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaXMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(xMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaYMin != 0) { // axis->setOffset(axis->offset() + deltaYMin, false); // } } else { if (deltaYMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(yMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaYMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(yMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaXMin != 0) { // axis->setOffset(axis->offset() + deltaXMin, false); // } } } // call retransform() on the parent to trigger the update of all axes and curves. //no need to do this on load since all plots are retransformed again after the project is loaded. if (!q->isLoading()) q->retransform(); } /* * calculates the rectangular of the are showing the actual data (plot's rect minus padding), * in plot's coordinates. */ void CartesianPlotPrivate::updateDataRect() { dataRect = mapRectFromScene(rect); double paddingLeft = horizontalPadding; double paddingRight = rightPadding; double paddingTop = verticalPadding; double paddingBottom = bottomPadding; if (symmetricPadding) { paddingRight = horizontalPadding; paddingBottom = verticalPadding; } dataRect.setX(dataRect.x() + paddingLeft); dataRect.setY(dataRect.y() + paddingTop); double newHeight = dataRect.height() - paddingBottom; if (newHeight < 0) newHeight = 0; dataRect.setHeight(newHeight); double newWidth = dataRect.width() - paddingRight; if (newWidth < 0) newWidth = 0; dataRect.setWidth(newWidth); } void CartesianPlotPrivate::rangeChanged() { curvesXMinMaxIsDirty = true; curvesYMinMaxIsDirty = true; if (autoScaleX && autoScaleY) q->scaleAuto(); else if (autoScaleX) q->scaleAutoX(); else if (autoScaleY) q->scaleAutoY(); } void CartesianPlotPrivate::xRangeFormatChanged() { for (auto* axis : q->children()) { if (axis->orientation() == Axis::AxisHorizontal) axis->retransformTickLabelStrings(); } } void CartesianPlotPrivate::yRangeFormatChanged() { for (auto* axis : q->children()) { if (axis->orientation() == Axis::AxisVertical) axis->retransformTickLabelStrings(); } } /*! * don't allow any negative values for the x range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkXRange() { double min = 0.01; if (xMin <= 0.0) { (min < xMax*min) ? xMin = min : xMin = xMax*min; emit q->xMinChanged(xMin); } else if (xMax <= 0.0) { (-min > xMin*min) ? xMax = -min : xMax = xMin*min; emit q->xMaxChanged(xMax); } } /*! * don't allow any negative values for the y range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkYRange() { double min = 0.01; if (yMin <= 0.0) { (min < yMax*min) ? yMin = min : yMin = yMax*min; emit q->yMinChanged(yMin); } else if (yMax <= 0.0) { (-min > yMin*min) ? yMax = -min : yMax = yMin*min; emit q->yMaxChanged(yMax); } } CartesianScale* CartesianPlotPrivate::createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd) { DEBUG("CartesianPlotPrivate::createScale() scene start/end = " << sceneStart << '/' << sceneEnd << ", logical start/end = " << logicalStart << '/' << logicalEnd); // Interval interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale Interval interval (std::numeric_limits::lowest(), std::numeric_limits::max()); // Interval interval (logicalStart, logicalEnd); if (type == CartesianPlot::ScaleLinear) return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd); else return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, type); } /*! * Reimplemented from QGraphicsItem. */ QVariant CartesianPlotPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemPositionChange) { const QPointF& itemPos = value.toPointF();//item's center point in parent's coordinates; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and forward the changes to the frontend QRectF newRect; const qreal w = rect.width(); const qreal h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); emit q->rectChanged(newRect); } return QGraphicsItem::itemChange(change, value); } //############################################################################## //################################## Events ################################## //############################################################################## /*! * \brief CartesianPlotPrivate::mousePressEvent * In this function only basic stuff is done. The mousePressEvent is forwarded to the Worksheet, which * has access to all cartesian plots and can apply the changes to all plots if the option "applyToAll" * is set. The worksheet calls then the corresponding mousepressZoomMode/CursorMode function in this class * This is done for mousePress, mouseMove and mouseRelease event * This function sends a signal with the logical position, because this is the only value which is the same * in all plots. Using the scene coordinates is not possible * \param event */ void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) emit q->mousePressZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit)); else if (mouseMode == CartesianPlot::Cursor) { setCursor(Qt::SizeHorCursor); QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); double cursorPenWidth2 = cursorPen.width()/2.; if (cursorPenWidth2 < 10.) cursorPenWidth2 = 10.; if (cursor0Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) { selectedCursor = 0; } else if (cursor1Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2) { selectedCursor = 1; } else if (QApplication::keyboardModifiers() & Qt::ControlModifier){ cursor1Enable = true; selectedCursor = 1; emit q->cursor1EnableChanged(cursor1Enable); } else { cursor0Enable = true; selectedCursor = 0; emit q->cursor0EnableChanged(cursor0Enable); } emit q->mousePressCursorModeSignal(selectedCursor, logicalPos); } else { if (!locked && dataRect.contains(event->pos())) { panningStarted = true; m_panningStart = event->pos(); setCursor(Qt::ClosedHandCursor); } } QGraphicsItem::mousePressEvent(event); } void CartesianPlotPrivate::mousePressZoomSelectionMode(QPointF logicalPos) { if (mouseMode == CartesianPlot::ZoomSelectionMode) { if (logicalPos.x() < xMin) logicalPos.setX(xMin); if (logicalPos.x() > xMax) logicalPos.setX(xMax); if (logicalPos.y() < yMin) logicalPos.setY(yMin); if (logicalPos.y() > yMax) logicalPos.setY(yMax); m_selectionStart = cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::SuppressPageClipping); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionStart.setX(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::SuppressPageClipping).x()); m_selectionStart.setY(dataRect.y()); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionStart.setX(dataRect.x()); m_selectionStart.setY(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::SuppressPageClipping).y()); } m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } void CartesianPlotPrivate::mousePressCursorMode(int cursorNumber, QPointF logicalPos) { cursorNumber == 0 ? cursor0Enable = true : cursor1Enable = true; QPointF p1(logicalPos.x(), yMin); QPointF p2(logicalPos.x(), yMax); if (cursorNumber == 0) { cursor0Pos.setX(logicalPos.x()); cursor0Pos.setY(0); } else { cursor1Pos.setX(logicalPos.x()); cursor1Pos.setY(0); } update(); } void CartesianPlotPrivate::updateCursor() { update(); } void CartesianPlotPrivate::setZoomSelectionBandShow(bool show) { m_selectionBandIsShown = show; } void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) { if (panningStarted && dataRect.contains(event->pos()) ) { //don't retransform on small mouse movement deltas const int deltaXScene = (m_panningStart.x() - event->pos().x()); const int deltaYScene = (m_panningStart.y() - event->pos().y()); if (abs(deltaXScene) < 5 && abs(deltaYScene) < 5) return; const QPointF logicalEnd = cSystem->mapSceneToLogical(event->pos()); const QPointF logicalStart = cSystem->mapSceneToLogical(m_panningStart); //handle the change in x switch (xScale) { case CartesianPlot::ScaleLinear: { const float deltaX = (logicalStart.x() - logicalEnd.x()); xMax += deltaX; xMin += deltaX; break; } case CartesianPlot::ScaleLog10: case CartesianPlot::ScaleLog10Abs: { const float deltaX = log10(logicalStart.x()) - log10(logicalEnd.x()); xMin *= pow(10, deltaX); xMax *= pow(10, deltaX); break; } case CartesianPlot::ScaleLog2: case CartesianPlot::ScaleLog2Abs: { const float deltaX = log2(logicalStart.x()) - log2(logicalEnd.x()); xMin *= pow(2, deltaX); xMax *= pow(2, deltaX); break; } case CartesianPlot::ScaleLn: case CartesianPlot::ScaleLnAbs: { const float deltaX = log(logicalStart.x()) - log(logicalEnd.x()); xMin *= exp(deltaX); xMax *= exp(deltaX); break; } case CartesianPlot::ScaleSqrt: case CartesianPlot::ScaleX2: break; } //handle the change in y switch (yScale) { case CartesianPlot::ScaleLinear: { const float deltaY = (logicalStart.y() - logicalEnd.y()); yMax += deltaY; yMin += deltaY; break; } case CartesianPlot::ScaleLog10: case CartesianPlot::ScaleLog10Abs: { const float deltaY = log10(logicalStart.y()) - log10(logicalEnd.y()); yMin *= pow(10, deltaY); yMax *= pow(10, deltaY); break; } case CartesianPlot::ScaleLog2: case CartesianPlot::ScaleLog2Abs: { const float deltaY = log2(logicalStart.y()) - log2(logicalEnd.y()); yMin *= pow(2, deltaY); yMax *= pow(2, deltaY); break; } case CartesianPlot::ScaleLn: case CartesianPlot::ScaleLnAbs: { const float deltaY = log(logicalStart.y()) - log(logicalEnd.y()); yMin *= exp(deltaY); yMax *= exp(deltaY); break; } case CartesianPlot::ScaleSqrt: case CartesianPlot::ScaleX2: break; } q->setUndoAware(false); q->setAutoScaleX(false); q->setAutoScaleY(false); q->setUndoAware(true); retransformScales(); m_panningStart = event->pos(); } else QGraphicsItem::mouseMoveEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { QGraphicsItem::mouseMoveEvent(event); if ( !boundingRect().contains(event->pos()) ) { q->info(QString()); return; } emit q->mouseMoveZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos(), CartesianCoordinateSystem::MappingFlag::Limit)); } else if (mouseMode == CartesianPlot::Cursor) { QGraphicsItem::mouseMoveEvent(event); if (!boundingRect().contains(event->pos())) { q->info(i18n("Not inside of the bounding rect")); return; } QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit); // updating treeview data and cursor position // updatign cursor position is done in Worksheet, because // multiple plots must be updated emit q->mouseMoveCursorModeSignal(selectedCursor, logicalPos); } } void CartesianPlotPrivate::mouseMoveZoomSelectionMode(QPointF logicalPos) { QString info; QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); if (mouseMode == CartesianPlot::ZoomSelectionMode) { m_selectionEnd = cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); QPointF logicalEnd = logicalPos; if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); else info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat)); info += QLatin1String(", "); if (yRangeFormat == CartesianPlot::Numeric) info += QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); else info += i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat)); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionEnd.setX(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).x());//event->pos().x()); m_selectionEnd.setY(dataRect.bottom()); QPointF logicalEnd = logicalPos; if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); else info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat)); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionEnd.setX(dataRect.right()); logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes m_selectionEnd.setY(cSystem->mapLogicalToScene(logicalPos, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping).y());//event->pos().y()); QPointF logicalEnd = logicalPos; if (yRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); else info = i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat), QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat)); } q->info(info); update(); } void CartesianPlotPrivate::mouseMoveCursorMode(int cursorNumber, QPointF logicalPos) { QPointF p1(logicalPos.x(), 0); cursorNumber == 0 ? cursor0Pos = p1 : cursor1Pos = p1; QString info; if (xRangeFormat == CartesianPlot::Numeric) info = QString::fromUtf8("x=") + QString::number(logicalPos.x()); else info = i18n("x=%1", QDateTime::fromMSecsSinceEpoch(logicalPos.x()).toString(xRangeDateTimeFormat)); q->info(info); update(); } void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { setCursor(Qt::ArrowCursor); if (mouseMode == CartesianPlot::SelectionMode) { panningStarted = false; //TODO: why do we do this all the time?!?! const QPointF& itemPos = pos();//item's center point in parent's coordinates; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and set it QRectF newRect; const qreal w = rect.width(); const qreal h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); suppressRetransform = true; q->setRect(newRect); suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { emit q->mouseReleaseZoomSelectionModeSignal(); } } void CartesianPlotPrivate::mouseReleaseZoomSelectionMode() { //don't zoom if very small region was selected, avoid occasional/unwanted zooming if ( qAbs(m_selectionEnd.x()-m_selectionStart.x()) < 20 || qAbs(m_selectionEnd.y()-m_selectionStart.y()) < 20 ) { m_selectionBandIsShown = false; return; } bool retransformPlot = true; //determine the new plot ranges QPointF logicalZoomStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::SuppressPageClipping); QPointF logicalZoomEnd = cSystem->mapSceneToLogical(m_selectionEnd, AbstractCoordinateSystem::SuppressPageClipping); if (m_selectionEnd.x() > m_selectionStart.x()) { xMin = logicalZoomStart.x(); xMax = logicalZoomEnd.x(); } else { xMin = logicalZoomEnd.x(); xMax = logicalZoomStart.x(); } if (m_selectionEnd.y() > m_selectionStart.y()) { yMin = logicalZoomEnd.y(); yMax = logicalZoomStart.y(); } else { yMin = logicalZoomStart.y(); yMax = logicalZoomEnd.y(); } if (mouseMode == CartesianPlot::ZoomSelectionMode) { curvesXMinMaxIsDirty = true; curvesYMinMaxIsDirty = true; q->setAutoScaleX(false); q->setAutoScaleY(false); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { curvesYMinMaxIsDirty = true; q->setAutoScaleX(false); if (q->autoScaleY() && q->scaleAutoY()) retransformPlot = false; } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { curvesXMinMaxIsDirty = true; q->setAutoScaleY(false); if (q->autoScaleX() && q->scaleAutoX()) retransformPlot = false; } if (retransformPlot) retransformScales(); m_selectionBandIsShown = false; } void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { if (locked) return; //determine first, which axes are selected and zoom only in the corresponding direction. //zoom the entire plot if no axes selected. bool zoomX = false; bool zoomY = false; for (auto* axis : q->children()) { if (!axis->graphicsItem()->isSelected()) continue; if (axis->orientation() == Axis::AxisHorizontal) zoomX = true; else zoomY = true; } if (event->delta() > 0) { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomIn(); } else { if (zoomX) q->zoomInX(); if (zoomY) q->zoomInY(); } } else { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomOut(); } else { if (zoomX) q->zoomOutX(); if (zoomY) q->zoomOutY(); } } } void CartesianPlotPrivate::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Escape) { setCursor(Qt::ArrowCursor); q->setMouseMode(CartesianPlot::MouseMode::SelectionMode); m_selectionBandIsShown = false; } else if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || event->key() == Qt::Key_Up ||event->key() == Qt::Key_Down) { const auto* worksheet = static_cast(q->parentAspect()); if (worksheet->layout() == Worksheet::NoLayout) { const int delta = 5; QRectF rect = q->rect(); if (event->key() == Qt::Key_Left) { rect.setX(rect.x() - delta); rect.setWidth(rect.width() - delta); } else if (event->key() == Qt::Key_Right) { rect.setX(rect.x() + delta); rect.setWidth(rect.width() + delta); } else if (event->key() == Qt::Key_Up) { rect.setY(rect.y() - delta); rect.setHeight(rect.height() - delta); } else if (event->key() == Qt::Key_Down) { rect.setY(rect.y() + delta); rect.setHeight(rect.height() + delta); } q->setRect(rect); } } QGraphicsItem::keyPressEvent(event); } void CartesianPlotPrivate::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { QPointF point = event->pos(); QString info; if (dataRect.contains(point)) { QPointF logicalPoint = cSystem->mapSceneToLogical(point); if ((mouseMode == CartesianPlot::ZoomSelectionMode) || mouseMode == CartesianPlot::SelectionMode) { info = "x="; if (xRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); info += ", y="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.y()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); } if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) { emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { info = "x="; if (xRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { info = "y="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.y()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); } else if (mouseMode == CartesianPlot::MouseMode::SelectionMode) { // hover the nearest curve to the mousepointer // hovering curves is implemented in the parent, because no ignoreEvent() exists // for it. Checking all curves and hover the first bool curve_hovered = false; QVector curves = q->children(); for (int i=curves.count() - 1; i >= 0; i--){ // because the last curve is above the other curves if (curve_hovered){ // if a curve is already hovered, disable hover for the rest curves[i]->setHover(false); continue; } if (curves[i]->activateCurve(event->pos())){ curves[i]->setHover(true); curve_hovered = true; continue; } curves[i]->setHover(false); } } else if (mouseMode == CartesianPlot::Cursor){ info = "x="; if (yRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); double cursorPenWidth2 = cursorPen.width()/2.; if (cursorPenWidth2 < 10.) cursorPenWidth2 = 10.; if ((cursor0Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) || (cursor1Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2)) setCursor(Qt::SizeHorCursor); else setCursor(Qt::ArrowCursor); update(); } } else emit q->mouseHoverOutsideDataRectSignal(); q->info(info); QGraphicsItem::hoverMoveEvent(event); } void CartesianPlotPrivate::mouseHoverOutsideDataRect() { m_insideDataRect = false; update(); } void CartesianPlotPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) { QVector curves = q->children(); for (auto* curve : curves) curve->setHover(false); m_hovered = false; QGraphicsItem::hoverLeaveEvent(event); } void CartesianPlotPrivate::mouseHoverZoomSelectionMode(QPointF logicPos) { m_insideDataRect = true; if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) { } else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { QPointF p1(logicPos.x(), yMin); QPointF p2(logicPos.x(), yMax); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::Limit)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, CartesianCoordinateSystem::MappingFlag::Limit)); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { QPointF p1(xMin, logicPos.y()); QPointF p2(xMax, logicPos.y()); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::Limit)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2, CartesianCoordinateSystem::MappingFlag::Limit)); } update(); // because if previous another selection mode was selected, the lines must be deleted } void CartesianPlotPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; if (!m_printing) { painter->save(); painter->setPen(cursorPen); QFont font = painter->font(); font.setPointSize(font.pointSize() * 4); painter->setFont(font); QPointF p1 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)); if (cursor0Enable && p1 != QPointF(0,0)){ QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMax)); painter->drawLine(p1,p2); QPointF textPos = p2; textPos.setX(p2.x() - m_cursor0Text.size().width()/2); textPos.setY(p2.y() - m_cursor0Text.size().height()); if (textPos.y() < boundingRect().y()) textPos.setY(boundingRect().y()); painter->drawStaticText(textPos, m_cursor0Text); } p1 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)); if (cursor1Enable && p1 != QPointF(0,0)){ QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMax)); painter->drawLine(p1,p2); QPointF textPos = p2; // TODO: Moving this stuff into other function to not calculate it every time textPos.setX(p2.x() - m_cursor1Text.size().width()/2); textPos.setY(p2.y() - m_cursor1Text.size().height()); if (textPos.y() < boundingRect().y()) textPos.setY(boundingRect().y()); painter->drawStaticText(textPos, m_cursor1Text); } painter->restore(); } painter->setPen(QPen(Qt::black, 3)); if ((mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) && (!m_selectionBandIsShown) && m_insideDataRect) painter->drawLine(m_selectionStartLine); if (m_selectionBandIsShown) { QPointF selectionStart = m_selectionStart; if (m_selectionStart.x() > dataRect.right()) selectionStart.setX(dataRect.right()); if (m_selectionStart.x() < dataRect.left()) selectionStart.setX(dataRect.left()); if (m_selectionStart.y() > dataRect.bottom()) selectionStart.setY(dataRect.bottom()); if (m_selectionStart.y() < dataRect.top()) selectionStart.setY(dataRect.top()); QPointF selectionEnd = m_selectionEnd; if (m_selectionEnd.x() > dataRect.right()) selectionEnd.setX(dataRect.right()); if (m_selectionEnd.x() < dataRect.left()) selectionEnd.setX(dataRect.left()); if (m_selectionEnd.y() > dataRect.bottom()) selectionEnd.setY(dataRect.bottom()); if (m_selectionEnd.y() < dataRect.top()) selectionEnd.setY(dataRect.top()); painter->save(); painter->setPen(QPen(Qt::black, 5)); painter->drawRect(QRectF(selectionStart, selectionEnd)); painter->setBrush(Qt::blue); painter->setOpacity(0.2); painter->drawRect(QRectF(selectionStart, selectionEnd)); painter->restore(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlot::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlot); writer->writeStartElement( "cartesianPlot" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()) { writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //cursor writer->writeStartElement( "cursor" ); WRITE_QPEN(d->cursorPen); writer->writeEndElement(); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->rect.x()) ); writer->writeAttribute( "y", QString::number(d->rect.y()) ); writer->writeAttribute( "width", QString::number(d->rect.width()) ); writer->writeAttribute( "height", QString::number(d->rect.height()) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //coordinate system and padding writer->writeStartElement( "coordinateSystem" ); writer->writeAttribute( "autoScaleX", QString::number(d->autoScaleX) ); writer->writeAttribute( "autoScaleY", QString::number(d->autoScaleY) ); writer->writeAttribute( "xMin", QString::number(d->xMin, 'g', 16)); writer->writeAttribute( "xMax", QString::number(d->xMax, 'g', 16) ); writer->writeAttribute( "yMin", QString::number(d->yMin, 'g', 16) ); writer->writeAttribute( "yMax", QString::number(d->yMax, 'g', 16) ); writer->writeAttribute( "xScale", QString::number(d->xScale) ); writer->writeAttribute( "yScale", QString::number(d->yScale) ); writer->writeAttribute( "xRangeFormat", QString::number(d->xRangeFormat) ); writer->writeAttribute( "yRangeFormat", QString::number(d->yRangeFormat) ); writer->writeAttribute( "horizontalPadding", QString::number(d->horizontalPadding) ); writer->writeAttribute( "verticalPadding", QString::number(d->verticalPadding) ); writer->writeAttribute( "rightPadding", QString::number(d->rightPadding) ); writer->writeAttribute( "bottomPadding", QString::number(d->bottomPadding) ); writer->writeAttribute( "symmetricPadding", QString::number(d->symmetricPadding)); writer->writeEndElement(); //x-scale breaks if (d->xRangeBreakingEnabled || !d->xRangeBreaks.list.isEmpty()) { writer->writeStartElement("xRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->xRangeBreakingEnabled) ); for (const auto& rb : d->xRangeBreaks.list) { writer->writeStartElement("xRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //y-scale breaks if (d->yRangeBreakingEnabled || !d->yRangeBreaks.list.isEmpty()) { writer->writeStartElement("yRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->yRangeBreakingEnabled) ); for (const auto& rb : d->yRangeBreaks.list) { writer->writeStartElement("yRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //serialize all children (plot area, title text label, axes and curves) for (auto* elem : children(IncludeHidden)) elem->save(writer); writer->writeEndElement(); // close "cartesianPlot" section } //! Load from XML bool CartesianPlot::load(XmlStreamReader* reader, bool preview) { Q_D(CartesianPlot); if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool titleLabelRead = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cartesianPlot") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "theme") { attribs = reader->attributes(); d->theme = attribs.value("name").toString(); } else if (!preview && reader->name() == "cursor") { attribs = reader->attributes(); QPen pen; pen.setWidth(attribs.value("width").toInt()); pen.setStyle(static_cast(attribs.value("style").toInt())); QColor color; color.setRed(attribs.value("color_r").toInt()); color.setGreen(attribs.value("color_g").toInt()); color.setBlue(attribs.value("color_b").toInt()); pen.setColor(color); d->cursorPen = pen; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("x").toString()); else d->rect.setX( str.toDouble() ); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("y").toString()); else d->rect.setY( str.toDouble() ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("width").toString()); else d->rect.setWidth( str.toDouble() ); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("height").toString()); else d->rect.setHeight( str.toDouble() ); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "coordinateSystem") { attribs = reader->attributes(); READ_INT_VALUE("autoScaleX", autoScaleX, bool); READ_INT_VALUE("autoScaleY", autoScaleY, bool); str = attribs.value("xMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("xMin").toString()); else { d->xMin = str.toDouble(); d->xMinPrev = d->xMin; } str = attribs.value("xMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("xMax").toString()); else { d->xMax = str.toDouble(); d->xMaxPrev = d->xMax; } str = attribs.value("yMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("yMin").toString()); else { d->yMin = str.toDouble(); d->yMinPrev = d->yMin; } str = attribs.value("yMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("yMax").toString()); else { d->yMax = str.toDouble(); d->yMaxPrev = d->yMax; } READ_INT_VALUE("xScale", xScale, CartesianPlot::Scale); READ_INT_VALUE("yScale", yScale, CartesianPlot::Scale); READ_INT_VALUE("xRangeFormat", xRangeFormat, CartesianPlot::RangeFormat); READ_INT_VALUE("yRangeFormat", yRangeFormat, CartesianPlot::RangeFormat); READ_DOUBLE_VALUE("horizontalPadding", horizontalPadding); READ_DOUBLE_VALUE("verticalPadding", verticalPadding); READ_DOUBLE_VALUE("rightPadding", rightPadding); READ_DOUBLE_VALUE("bottomPadding", bottomPadding); READ_INT_VALUE("symmetricPadding", symmetricPadding, bool); } else if (!preview && reader->name() == "xRangeBreaks") { //delete default rang break d->xRangeBreaks.list.clear(); attribs = reader->attributes(); READ_INT_VALUE("enabled", xRangeBreakingEnabled, bool); } else if (!preview && reader->name() == "xRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("start").toString()); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("end").toString()); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("position").toString()); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("style").toString()); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->xRangeBreaks.list << b; } else if (!preview && reader->name() == "yRangeBreaks") { //delete default rang break d->yRangeBreaks.list.clear(); attribs = reader->attributes(); READ_INT_VALUE("enabled", yRangeBreakingEnabled, bool); } else if (!preview && reader->name() == "yRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("start").toString()); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("end").toString()); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("position").toString()); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("style").toString()); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->yRangeBreaks.list << b; } else if (reader->name() == "textLabel") { if (!titleLabelRead) { //the first text label is always the title label m_title->load(reader, preview); titleLabelRead = true; //TODO: the name is read in m_title->load() but we overwrite it here //since the old projects don't have this " - Title" appendix yet that we add in init(). //can be removed in couple of releases m_title->setName(name() + QLatin1String(" - ") + i18n("Title")); } else { TextLabel* label = new TextLabel("text label"); if (label->load(reader, preview)) { addChildFast(label); label->setParentGraphicsItem(graphicsItem()); } else { delete label; return false; } } } else if (reader->name() == "image") { Image* image = new Image(QString()); if (!image->load(reader, preview)) { delete image; return false; } else addChildFast(image); } else if (reader->name() == "plotArea") m_plotArea->load(reader, preview); else if (reader->name() == "axis") { Axis* axis = new Axis(QString()); if (axis->load(reader, preview)) addChildFast(axis); else { delete axis; return false; } } else if (reader->name() == "xyCurve") { XYCurve* curve = new XYCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyEquationCurve") { XYEquationCurve* curve = new XYEquationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDataReductionCurve") { XYDataReductionCurve* curve = new XYDataReductionCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDifferentiationCurve") { XYDifferentiationCurve* curve = new XYDifferentiationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyIntegrationCurve") { XYIntegrationCurve* curve = new XYIntegrationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyInterpolationCurve") { XYInterpolationCurve* curve = new XYInterpolationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xySmoothCurve") { XYSmoothCurve* curve = new XYSmoothCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFitCurve") { XYFitCurve* curve = new XYFitCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierFilterCurve") { XYFourierFilterCurve* curve = new XYFourierFilterCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierTransformCurve") { XYFourierTransformCurve* curve = new XYFourierTransformCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyConvolutionCurve") { XYConvolutionCurve* curve = new XYConvolutionCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyCorrelationCurve") { XYCorrelationCurve* curve = new XYCorrelationCurve(QString()); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "cartesianPlotLegend") { m_legend = new CartesianPlotLegend(this, QString()); if (m_legend->load(reader, preview)) addChildFast(m_legend); else { delete m_legend; return false; } } else if (reader->name() == "customPoint") { CustomPoint* point = new CustomPoint(this, QString()); if (point->load(reader, preview)) addChildFast(point); else { delete point; return false; } } else if (reader->name() == "referenceLine") { ReferenceLine* line = new ReferenceLine(this, QString()); if (line->load(reader, preview)) addChildFast(line); else { delete line; return false; } } else if (reader->name() == "Histogram") { Histogram* curve = new Histogram("Histogram"); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else { // unknown element reader->raiseWarning(i18n("unknown cartesianPlot element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (preview) return true; d->retransform(); //if a theme was used, initialize the color palette if (!d->theme.isEmpty()) { //TODO: check whether the theme config really exists KConfig config( ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig ); this->setColorPalette(config); } else { //initialize the color palette with default colors this->setColorPalette(KConfig()); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void CartesianPlot::loadTheme(const QString& theme) { KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); loadThemeConfig(config); } void CartesianPlot::loadThemeConfig(const KConfig& config) { QString str = config.name(); // theme path is saved with UNIX dir separator str = str.right(str.length() - str.lastIndexOf(QLatin1Char('/')) - 1); - DEBUG(" set theme to " << str.toStdString()); + DEBUG(" set theme to " << STDSTRING(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()); } void CartesianPlot::saveTheme(KConfig &config) { const QVector& axisElements = children(AbstractAspect::IncludeHidden); const QVector& plotAreaElements = children(AbstractAspect::IncludeHidden); const QVector& 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) { if (config.hasGroup(QLatin1String("Theme"))) { KConfigGroup group = config.group(QLatin1String("Theme")); //read the five colors defining the palette m_themeColorPalette.clear(); m_themeColorPalette.append(group.readEntry("ThemePaletteColor1", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor2", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor3", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor4", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor5", QColor())); } else { //no theme is available, provide 5 "default colors" m_themeColorPalette.clear(); m_themeColorPalette.append(QColor(25, 25, 25)); m_themeColorPalette.append(QColor(0, 0, 127)); m_themeColorPalette.append(QColor(127 ,0, 0)); m_themeColorPalette.append(QColor(0, 127, 0)); m_themeColorPalette.append(QColor(85, 0, 127)); } //generate 30 additional shades if the color palette contains more than one color if (m_themeColorPalette.at(0) != m_themeColorPalette.at(1)) { QColor c; //3 factors to create shades from theme's palette std::array fac = {0.25f, 0.45f, 0.65f}; //Generate 15 lighter shades for (int i = 0; i < 5; i++) { for (int j = 1; j < 4; j++) { c.setRed( m_themeColorPalette.at(i).red()*(1-fac[j-1]) ); c.setGreen( m_themeColorPalette.at(i).green()*(1-fac[j-1]) ); c.setBlue( m_themeColorPalette.at(i).blue()*(1-fac[j-1]) ); m_themeColorPalette.append(c); } } //Generate 15 darker shades for (int i = 0; i < 5; i++) { for (int j = 4; j < 7; j++) { c.setRed( m_themeColorPalette.at(i).red()+((255-m_themeColorPalette.at(i).red())*fac[j-4]) ); c.setGreen( m_themeColorPalette.at(i).green()+((255-m_themeColorPalette.at(i).green())*fac[j-4]) ); c.setBlue( m_themeColorPalette.at(i).blue()+((255-m_themeColorPalette.at(i).blue())*fac[j-4]) ); m_themeColorPalette.append(c); } } } } const QList& CartesianPlot::themeColorPalette() const { return m_themeColorPalette; } diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCurve.cpp index 3afaca9e5..d605549ca 100644 --- a/src/backend/worksheet/plots/cartesian/XYCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCurve.cpp @@ -1,3268 +1,3268 @@ /*************************************************************************** File : XYCurve.cpp Project : LabPlot Description : A xy-curve -------------------------------------------------------------------- Copyright : (C) 2010-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013-2020 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYCurve \brief A 2D-curve, provides an interface for editing many properties of the curve. \ingroup worksheet */ #include "XYCurve.h" #include "XYCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/lib/commandtemplates.h" #include "backend/core/Project.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "backend/gsl/errors.h" #include "tools/ImageTools.h" #include #include #include #include #include #include #include #include extern "C" { #include #include } XYCurve::XYCurve(const QString &name, AspectType type) : WorksheetElement(name, type), d_ptr(new XYCurvePrivate(this)) { init(); } XYCurve::XYCurve(const QString& name, XYCurvePrivate* dd, AspectType type) : WorksheetElement(name, type), d_ptr(dd) { init(); } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYCurve::~XYCurve() = default; void XYCurve::finalizeAdd() { Q_D(XYCurve); d->plot = static_cast(parentAspect()); d->cSystem = static_cast(d->plot->coordinateSystem()); } void XYCurve::init() { Q_D(XYCurve); KConfig config; KConfigGroup group = config.group("XYCurve"); d->lineType = (XYCurve::LineType) group.readEntry("LineType", (int)XYCurve::Line); d->lineIncreasingXOnly = group.readEntry("LineIncreasingXOnly", false); d->lineSkipGaps = group.readEntry("SkipLineGaps", false); d->lineInterpolationPointsCount = group.readEntry("LineInterpolationPointsCount", 1); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int)Qt::SolidLine) ); d->linePen.setColor( group.readEntry("LineColor", QColor(Qt::black)) ); d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->dropLineType = (XYCurve::DropLineType) group.readEntry("DropLineType", (int)XYCurve::NoLine); d->dropLinePen.setStyle( (Qt::PenStyle) group.readEntry("DropLineStyle", (int)Qt::SolidLine) ); d->dropLinePen.setColor( group.readEntry("DropLineColor", QColor(Qt::black))); d->dropLinePen.setWidthF( group.readEntry("DropLineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->dropLineOpacity = group.readEntry("DropLineOpacity", 1.0); d->symbolsStyle = (Symbol::Style)group.readEntry("SymbolStyle", (int)Symbol::NoSymbols); d->symbolsSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->symbolsRotationAngle = group.readEntry("SymbolRotation", 0.0); d->symbolsOpacity = group.readEntry("SymbolOpacity", 1.0); d->symbolsBrush.setStyle( (Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern) ); d->symbolsBrush.setColor( group.readEntry("SymbolFillingColor", QColor(Qt::black)) ); d->symbolsPen.setStyle( (Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine) ); d->symbolsPen.setColor( group.readEntry("SymbolBorderColor", QColor(Qt::black)) ); d->symbolsPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Point)) ); d->valuesType = (XYCurve::ValuesType) group.readEntry("ValuesType", (int)XYCurve::NoValues); d->valuesPosition = (XYCurve::ValuesPosition) group.readEntry("ValuesPosition", (int)XYCurve::ValuesAbove); d->valuesDistance = group.readEntry("ValuesDistance", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->valuesRotationAngle = group.readEntry("ValuesRotation", 0.0); d->valuesOpacity = group.readEntry("ValuesOpacity", 1.0); d->valuesPrefix = group.readEntry("ValuesPrefix", ""); d->valuesSuffix = group.readEntry("ValuesSuffix", ""); d->valuesFont = group.readEntry("ValuesFont", QFont()); d->valuesFont.setPixelSize( Worksheet::convertToSceneUnits( 8, Worksheet::Point ) ); d->valuesColor = group.readEntry("ValuesColor", QColor(Qt::black)); d->fillingPosition = (XYCurve::FillingPosition) group.readEntry("FillingPosition", (int)XYCurve::NoFilling); d->fillingType = (PlotArea::BackgroundType) group.readEntry("FillingType", (int)PlotArea::Color); d->fillingColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("FillingColorStyle", (int) PlotArea::SingleColor); d->fillingImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("FillingImageStyle", (int) PlotArea::Scaled); d->fillingBrushStyle = (Qt::BrushStyle) group.readEntry("FillingBrushStyle", (int) Qt::SolidPattern); d->fillingFileName = group.readEntry("FillingFileName", QString()); d->fillingFirstColor = group.readEntry("FillingFirstColor", QColor(Qt::white)); d->fillingSecondColor = group.readEntry("FillingSecondColor", QColor(Qt::black)); d->fillingOpacity = group.readEntry("FillingOpacity", 1.0); d->xErrorType = (XYCurve::ErrorType) group.readEntry("XErrorType", (int)XYCurve::NoError); d->yErrorType = (XYCurve::ErrorType) group.readEntry("YErrorType", (int)XYCurve::NoError); d->errorBarsType = (XYCurve::ErrorBarsType) group.readEntry("ErrorBarsType", (int)XYCurve::ErrorBarsSimple); d->errorBarsCapSize = group.readEntry( "ErrorBarsCapSize", Worksheet::convertToSceneUnits(10, Worksheet::Point) ); d->errorBarsPen.setStyle( (Qt::PenStyle)group.readEntry("ErrorBarsStyle", (int)Qt::SolidLine) ); d->errorBarsPen.setColor( group.readEntry("ErrorBarsColor", QColor(Qt::black)) ); d->errorBarsPen.setWidthF( group.readEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->errorBarsOpacity = group.readEntry("ErrorBarsOpacity", 1.0); } void XYCurve::initActions() { visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, SIGNAL(triggered(bool)), this, SLOT(visibilityChanged())); navigateToAction = new QAction(QIcon::fromTheme("go-next-view"), QString(), this); connect(navigateToAction, SIGNAL(triggered(bool)), this, SLOT(navigateTo())); m_menusInitialized = true; } QMenu* XYCurve::createContextMenu() { if (!m_menusInitialized) initActions(); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); //"data analysis" menu auto* plot = static_cast(parentAspect()); menu->insertMenu(visibilityAction, plot->analysisMenu()); menu->insertSeparator(visibilityAction); //"Navigate to spreadsheet"-action, show only if x- or y-columns have data from a spreadsheet AbstractAspect* parentSpreadsheet = nullptr; if (xColumn() && dynamic_cast(xColumn()->parentAspect()) ) parentSpreadsheet = xColumn()->parentAspect(); else if (yColumn() && dynamic_cast(yColumn()->parentAspect()) ) parentSpreadsheet = yColumn()->parentAspect(); if (parentSpreadsheet) { navigateToAction->setText(i18n("Navigate to \"%1\"", parentSpreadsheet->name())); navigateToAction->setData(parentSpreadsheet->path()); menu->insertAction(visibilityAction, navigateToAction); menu->insertSeparator(visibilityAction); } //if the context menu is called on an item that is not selected yet, select it if (!graphicsItem()->isSelected()) graphicsItem()->setSelected(true); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon XYCurve::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } QGraphicsItem* XYCurve::graphicsItem() const { return d_ptr; } STD_SWAP_METHOD_SETTER_CMD_IMPL(XYCurve, SetVisible, bool, swapVisible) void XYCurve::setVisible(bool on) { Q_D(XYCurve); exec(new XYCurveSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool XYCurve::isVisible() const { Q_D(const XYCurve); return d->isVisible(); } void XYCurve::setPrinting(bool on) { Q_D(XYCurve); d->setPrinting(on); } //############################################################################## //########################## getter methods ################################## //############################################################################## //data source BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xColumn, xColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yColumn, yColumn) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, xColumnPath, xColumnPath) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, yColumnPath, yColumnPath) //line BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::LineType, lineType, lineType) BASIC_SHARED_D_READER_IMPL(XYCurve, bool, lineSkipGaps, lineSkipGaps) BASIC_SHARED_D_READER_IMPL(XYCurve, bool, lineIncreasingXOnly, lineIncreasingXOnly) BASIC_SHARED_D_READER_IMPL(XYCurve, int, lineInterpolationPointsCount, lineInterpolationPointsCount) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, lineOpacity, lineOpacity) //droplines BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::DropLineType, dropLineType, dropLineType) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, dropLinePen, dropLinePen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, dropLineOpacity, dropLineOpacity) //symbols BASIC_SHARED_D_READER_IMPL(XYCurve, Symbol::Style, symbolsStyle, symbolsStyle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsOpacity, symbolsOpacity) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsRotationAngle, symbolsRotationAngle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsSize, symbolsSize) CLASS_SHARED_D_READER_IMPL(XYCurve, QBrush, symbolsBrush, symbolsBrush) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, symbolsPen, symbolsPen) //values BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesType, valuesType, valuesType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn *, valuesColumn, valuesColumn) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesColumnPath, valuesColumnPath) BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesPosition, valuesPosition, valuesPosition) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesDistance, valuesDistance) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesRotationAngle, valuesRotationAngle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesOpacity, valuesOpacity) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesPrefix, valuesPrefix) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesSuffix, valuesSuffix) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, valuesColor, valuesColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QFont, valuesFont, valuesFont) //filling BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::FillingPosition, fillingPosition, fillingPosition) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundType, fillingType, fillingType) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundColorStyle, fillingColorStyle, fillingColorStyle) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundImageStyle, fillingImageStyle, fillingImageStyle) CLASS_SHARED_D_READER_IMPL(XYCurve, Qt::BrushStyle, fillingBrushStyle, fillingBrushStyle) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, fillingFirstColor, fillingFirstColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, fillingSecondColor, fillingSecondColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, fillingFileName, fillingFileName) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, fillingOpacity, fillingOpacity) //error bars BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorType, xErrorType, xErrorType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xErrorPlusColumn, xErrorPlusColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xErrorMinusColumn, xErrorMinusColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorType, yErrorType, yErrorType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yErrorPlusColumn, yErrorPlusColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yErrorMinusColumn, yErrorMinusColumn) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, xErrorPlusColumnPath, xErrorPlusColumnPath) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, xErrorMinusColumnPath, xErrorMinusColumnPath) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, yErrorPlusColumnPath, yErrorPlusColumnPath) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, yErrorMinusColumnPath, yErrorMinusColumnPath) BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorBarsType, errorBarsType, errorBarsType) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, errorBarsCapSize, errorBarsCapSize) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, errorBarsPen, errorBarsPen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, errorBarsOpacity, errorBarsOpacity) /*! * return \c true if the data in the source columns (x, y) used in the analysis curves, \c false otherwise */ bool XYCurve::isSourceDataChangedSinceLastRecalc() const { Q_D(const XYCurve); return d->sourceDataChangedSinceLastRecalc; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## // 1) add XYCurveSetXColumnCmd as friend class to XYCurve // 2) add XYCURVE_COLUMN_CONNECT(x) as private method to XYCurve // 3) define all missing slots XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(X, x, recalcLogicalPoints) void XYCurve::setXColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xColumn) exec(new XYCurveSetXColumnCmd(d, column, ki18n("%1: x-data source changed"))); } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(Y, y, recalcLogicalPoints) void XYCurve::setYColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yColumn) exec(new XYCurveSetYColumnCmd(d, column, ki18n("%1: y-data source changed"))); } void XYCurve::setXColumnPath(const QString& path) { Q_D(XYCurve); d->xColumnPath = path; } void XYCurve::setYColumnPath(const QString& path) { Q_D(XYCurve); d->yColumnPath = path; } //Line STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineType, XYCurve::LineType, lineType, updateLines) void XYCurve::setLineType(LineType type) { Q_D(XYCurve); if (type != d->lineType) exec(new XYCurveSetLineTypeCmd(d, type, ki18n("%1: line type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineSkipGaps, bool, lineSkipGaps, updateLines) void XYCurve::setLineSkipGaps(bool skip) { Q_D(XYCurve); if (skip != d->lineSkipGaps) exec(new XYCurveSetLineSkipGapsCmd(d, skip, ki18n("%1: set skip line gaps"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineIncreasingXOnly, bool, lineIncreasingXOnly, updateLines) void XYCurve::setLineIncreasingXOnly(bool incr) { Q_D(XYCurve); if (incr != d->lineIncreasingXOnly) exec(new XYCurveSetLineIncreasingXOnlyCmd(d, incr, ki18n("%1: set increasing X"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineInterpolationPointsCount, int, lineInterpolationPointsCount, updateLines) void XYCurve::setLineInterpolationPointsCount(int count) { Q_D(XYCurve); if (count != d->lineInterpolationPointsCount) exec(new XYCurveSetLineInterpolationPointsCountCmd(d, count, ki18n("%1: set the number of interpolation points"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect) void XYCurve::setLinePen(const QPen &pen) { Q_D(XYCurve); if (pen != d->linePen) exec(new XYCurveSetLinePenCmd(d, pen, ki18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineOpacity, qreal, lineOpacity, updatePixmap); void XYCurve::setLineOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->lineOpacity) exec(new XYCurveSetLineOpacityCmd(d, opacity, ki18n("%1: set line opacity"))); } //Drop lines STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLineType, XYCurve::DropLineType, dropLineType, updateDropLines) void XYCurve::setDropLineType(DropLineType type) { Q_D(XYCurve); if (type != d->dropLineType) exec(new XYCurveSetDropLineTypeCmd(d, type, ki18n("%1: drop line type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLinePen, QPen, dropLinePen, recalcShapeAndBoundingRect) void XYCurve::setDropLinePen(const QPen &pen) { Q_D(XYCurve); if (pen != d->dropLinePen) exec(new XYCurveSetDropLinePenCmd(d, pen, ki18n("%1: set drop line style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLineOpacity, qreal, dropLineOpacity, updatePixmap) void XYCurve::setDropLineOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->dropLineOpacity) exec(new XYCurveSetDropLineOpacityCmd(d, opacity, ki18n("%1: set drop line opacity"))); } // Symbols-Tab STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsStyle, Symbol::Style, symbolsStyle, retransform) void XYCurve::setSymbolsStyle(Symbol::Style style) { Q_D(XYCurve); if (style != d->symbolsStyle) exec(new XYCurveSetSymbolsStyleCmd(d, style, ki18n("%1: set symbol style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsSize, qreal, symbolsSize, updateSymbols) void XYCurve::setSymbolsSize(qreal size) { Q_D(XYCurve); if (!qFuzzyCompare(1 + size, 1 + d->symbolsSize)) exec(new XYCurveSetSymbolsSizeCmd(d, size, ki18n("%1: set symbol size"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsRotationAngle, qreal, symbolsRotationAngle, updateSymbols) void XYCurve::setSymbolsRotationAngle(qreal angle) { Q_D(XYCurve); if (!qFuzzyCompare(1 + angle, 1 + d->symbolsRotationAngle)) exec(new XYCurveSetSymbolsRotationAngleCmd(d, angle, ki18n("%1: rotate symbols"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsBrush, QBrush, symbolsBrush, updatePixmap) void XYCurve::setSymbolsBrush(const QBrush &brush) { Q_D(XYCurve); if (brush != d->symbolsBrush) exec(new XYCurveSetSymbolsBrushCmd(d, brush, ki18n("%1: set symbol filling"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsPen, QPen, symbolsPen, updateSymbols) void XYCurve::setSymbolsPen(const QPen &pen) { Q_D(XYCurve); if (pen != d->symbolsPen) exec(new XYCurveSetSymbolsPenCmd(d, pen, ki18n("%1: set symbol outline style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsOpacity, qreal, symbolsOpacity, updatePixmap) void XYCurve::setSymbolsOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->symbolsOpacity) exec(new XYCurveSetSymbolsOpacityCmd(d, opacity, ki18n("%1: set symbols opacity"))); } //Values-Tab STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesType, XYCurve::ValuesType, valuesType, updateValues) void XYCurve::setValuesType(XYCurve::ValuesType type) { Q_D(XYCurve); if (type != d->valuesType) exec(new XYCurveSetValuesTypeCmd(d, type, ki18n("%1: set values type"))); } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(Values, values, updateValues) void XYCurve::setValuesColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->valuesColumn) { exec(new XYCurveSetValuesColumnCmd(d, column, ki18n("%1: set values column"))); if (column) connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateValues())); } } void XYCurve::setValuesColumnPath(const QString& path) { Q_D(XYCurve); d->valuesColumnPath = path; } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPosition, XYCurve::ValuesPosition, valuesPosition, updateValues) void XYCurve::setValuesPosition(ValuesPosition position) { Q_D(XYCurve); if (position != d->valuesPosition) exec(new XYCurveSetValuesPositionCmd(d, position, ki18n("%1: set values position"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesDistance, qreal, valuesDistance, updateValues) void XYCurve::setValuesDistance(qreal distance) { Q_D(XYCurve); if (distance != d->valuesDistance) exec(new XYCurveSetValuesDistanceCmd(d, distance, ki18n("%1: set values distance"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesRotationAngle, qreal, valuesRotationAngle, updateValues) void XYCurve::setValuesRotationAngle(qreal angle) { Q_D(XYCurve); if (!qFuzzyCompare(1 + angle, 1 + d->valuesRotationAngle)) exec(new XYCurveSetValuesRotationAngleCmd(d, angle, ki18n("%1: rotate values"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesOpacity, qreal, valuesOpacity, updatePixmap) void XYCurve::setValuesOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->valuesOpacity) exec(new XYCurveSetValuesOpacityCmd(d, opacity, ki18n("%1: set values opacity"))); } //TODO: Format, Precision STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPrefix, QString, valuesPrefix, updateValues) void XYCurve::setValuesPrefix(const QString& prefix) { Q_D(XYCurve); if (prefix != d->valuesPrefix) exec(new XYCurveSetValuesPrefixCmd(d, prefix, ki18n("%1: set values prefix"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesSuffix, QString, valuesSuffix, updateValues) void XYCurve::setValuesSuffix(const QString& suffix) { Q_D(XYCurve); if (suffix != d->valuesSuffix) exec(new XYCurveSetValuesSuffixCmd(d, suffix, ki18n("%1: set values suffix"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesFont, QFont, valuesFont, updateValues) void XYCurve::setValuesFont(const QFont& font) { Q_D(XYCurve); if (font != d->valuesFont) exec(new XYCurveSetValuesFontCmd(d, font, ki18n("%1: set values font"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesColor, QColor, valuesColor, updatePixmap) void XYCurve::setValuesColor(const QColor& color) { Q_D(XYCurve); if (color != d->valuesColor) exec(new XYCurveSetValuesColorCmd(d, color, ki18n("%1: set values color"))); } //Filling STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingPosition, XYCurve::FillingPosition, fillingPosition, updateFilling) void XYCurve::setFillingPosition(FillingPosition position) { Q_D(XYCurve); if (position != d->fillingPosition) exec(new XYCurveSetFillingPositionCmd(d, position, ki18n("%1: filling position changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingType, PlotArea::BackgroundType, fillingType, updatePixmap) void XYCurve::setFillingType(PlotArea::BackgroundType type) { Q_D(XYCurve); if (type != d->fillingType) exec(new XYCurveSetFillingTypeCmd(d, type, ki18n("%1: filling type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingColorStyle, PlotArea::BackgroundColorStyle, fillingColorStyle, updatePixmap) void XYCurve::setFillingColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(XYCurve); if (style != d->fillingColorStyle) exec(new XYCurveSetFillingColorStyleCmd(d, style, ki18n("%1: filling color style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingImageStyle, PlotArea::BackgroundImageStyle, fillingImageStyle, updatePixmap) void XYCurve::setFillingImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(XYCurve); if (style != d->fillingImageStyle) exec(new XYCurveSetFillingImageStyleCmd(d, style, ki18n("%1: filling image style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingBrushStyle, Qt::BrushStyle, fillingBrushStyle, updatePixmap) void XYCurve::setFillingBrushStyle(Qt::BrushStyle style) { Q_D(XYCurve); if (style != d->fillingBrushStyle) exec(new XYCurveSetFillingBrushStyleCmd(d, style, ki18n("%1: filling brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingFirstColor, QColor, fillingFirstColor, updatePixmap) void XYCurve::setFillingFirstColor(const QColor& color) { Q_D(XYCurve); if (color != d->fillingFirstColor) exec(new XYCurveSetFillingFirstColorCmd(d, color, ki18n("%1: set filling first color"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingSecondColor, QColor, fillingSecondColor, updatePixmap) void XYCurve::setFillingSecondColor(const QColor& color) { Q_D(XYCurve); if (color != d->fillingSecondColor) exec(new XYCurveSetFillingSecondColorCmd(d, color, ki18n("%1: set filling second color"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingFileName, QString, fillingFileName, updatePixmap) void XYCurve::setFillingFileName(const QString& fileName) { Q_D(XYCurve); if (fileName != d->fillingFileName) exec(new XYCurveSetFillingFileNameCmd(d, fileName, ki18n("%1: set filling image"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingOpacity, qreal, fillingOpacity, updatePixmap) void XYCurve::setFillingOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->fillingOpacity) exec(new XYCurveSetFillingOpacityCmd(d, opacity, ki18n("%1: set filling opacity"))); } //Error bars STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXErrorType, XYCurve::ErrorType, xErrorType, updateErrorBars) void XYCurve::setXErrorType(ErrorType type) { Q_D(XYCurve); if (type != d->xErrorType) exec(new XYCurveSetXErrorTypeCmd(d, type, ki18n("%1: x-error type changed"))); } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(XErrorPlus, xErrorPlus, updateErrorBars) void XYCurve::setXErrorPlusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xErrorPlusColumn) { exec(new XYCurveSetXErrorPlusColumnCmd(d, column, ki18n("%1: set x-error column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &XYCurve::updateErrorBars); //in the macro we connect to recalcLogicalPoints which is not needed for error columns disconnect(column, &AbstractColumn::dataChanged, this, &XYCurve::recalcLogicalPoints); } } } void XYCurve::setXErrorPlusColumnPath(const QString& path) { Q_D(XYCurve); d->xErrorPlusColumnPath = path; } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(XErrorMinus, xErrorMinus, updateErrorBars) void XYCurve::setXErrorMinusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xErrorMinusColumn) { exec(new XYCurveSetXErrorMinusColumnCmd(d, column, ki18n("%1: set x-error column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &XYCurve::updateErrorBars); //in the macro we connect to recalcLogicalPoints which is not needed for error columns disconnect(column, &AbstractColumn::dataChanged, this, &XYCurve::recalcLogicalPoints); } } } void XYCurve::setXErrorMinusColumnPath(const QString& path) { Q_D(XYCurve); d->xErrorMinusColumnPath = path; } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYErrorType, XYCurve::ErrorType, yErrorType, updateErrorBars) void XYCurve::setYErrorType(ErrorType type) { Q_D(XYCurve); if (type != d->yErrorType) exec(new XYCurveSetYErrorTypeCmd(d, type, ki18n("%1: y-error type changed"))); } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(YErrorPlus, yErrorPlus, updateErrorBars) void XYCurve::setYErrorPlusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yErrorPlusColumn) { exec(new XYCurveSetYErrorPlusColumnCmd(d, column, ki18n("%1: set y-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); //in the macro we connect to recalcLogicalPoints which is not needed for error columns disconnect(column, &AbstractColumn::dataChanged, this, &XYCurve::recalcLogicalPoints); } } } void XYCurve::setYErrorPlusColumnPath(const QString& path) { Q_D(XYCurve); d->yErrorPlusColumnPath = path; } XYCURVE_COLUMN_SETTER_CMD_IMPL_F_S(YErrorMinus, yErrorMinus, updateErrorBars) void XYCurve::setYErrorMinusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yErrorMinusColumn) { exec(new XYCurveSetYErrorMinusColumnCmd(d, column, ki18n("%1: set y-error column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &XYCurve::updateErrorBars); //in the macro we connect to recalcLogicalPoints which is not needed for error columns disconnect(column, &AbstractColumn::dataChanged, this, &XYCurve::recalcLogicalPoints); } } } void XYCurve::setYErrorMinusColumnPath(const QString& path) { Q_D(XYCurve); d->yErrorMinusColumnPath = path; } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsCapSize, qreal, errorBarsCapSize, updateErrorBars) void XYCurve::setErrorBarsCapSize(qreal size) { Q_D(XYCurve); if (size != d->errorBarsCapSize) exec(new XYCurveSetErrorBarsCapSizeCmd(d, size, ki18n("%1: set error bar cap size"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsType, XYCurve::ErrorBarsType, errorBarsType, updateErrorBars) void XYCurve::setErrorBarsType(ErrorBarsType type) { Q_D(XYCurve); if (type != d->errorBarsType) exec(new XYCurveSetErrorBarsTypeCmd(d, type, ki18n("%1: error bar type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsPen, QPen, errorBarsPen, recalcShapeAndBoundingRect) void XYCurve::setErrorBarsPen(const QPen& pen) { Q_D(XYCurve); if (pen != d->errorBarsPen) exec(new XYCurveSetErrorBarsPenCmd(d, pen, ki18n("%1: set error bar style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsOpacity, qreal, errorBarsOpacity, updatePixmap) void XYCurve::setErrorBarsOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->errorBarsOpacity) exec(new XYCurveSetErrorBarsOpacityCmd(d, opacity, ki18n("%1: set error bar opacity"))); } void XYCurve::suppressRetransform(bool b) { Q_D(XYCurve); d->suppressRetransform(b); } //############################################################################## //################################# SLOTS #################################### //############################################################################## void XYCurve::retransform() { Q_D(XYCurve); d->retransform(); } void XYCurve::recalcLogicalPoints() { Q_D(XYCurve); d->recalcLogicalPoints(); } void XYCurve::updateValues() { Q_D(XYCurve); d->updateValues(); } void XYCurve::updateErrorBars() { Q_D(XYCurve); d->updateErrorBars(); } //TODO void XYCurve::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(pageResize); Q_D(const XYCurve); setSymbolsSize(d->symbolsSize * horizontalRatio); QPen pen = d->symbolsPen; pen.setWidthF(pen.widthF() * (horizontalRatio + verticalRatio) / 2.0); setSymbolsPen(pen); pen = d->linePen; pen.setWidthF(pen.widthF() * (horizontalRatio + verticalRatio) / 2.0); setLinePen(pen); //setValuesDistance(d->distance*); QFont font = d->valuesFont; font.setPointSizeF(font.pointSizeF()*horizontalRatio); setValuesFont(font); } /*! * returns \c true if the aspect being removed \c removedAspect is equal to \c column * or to one of its parents. returns \c false otherwise. */ bool XYCurve::columnRemoved(const AbstractColumn* column, const AbstractAspect* removedAspect) const { // TODO: BAD HACK. // In macrosXYCurve.h every parent of the column is connected to the function aspectAboutToBeRemoved(). // When a column is removed, the function aspectAboutToBeRemoved is called and the column pointer is set to nullptr. // However, when a child of the parent is removed, the parent calls the aspectAboutToBeRemoved() again, but // the column was already disconnected. // Better solution would be to emit aspectAboutToBeRemoved() for every column when their parents are removed. // At the moment this signal is only emitted when the column is deleted directly and not when its parent is deleted. // Once this is done, the connection of all parents to the aspectAboutToBeRemoved() signal can be removed. if (!column) return false; bool removed = (removedAspect == column); if (!removed) { auto* parent = column->parentAspect(); while (parent) { if (parent == removedAspect) { removed = true; break; } parent = parent->parentAspect(); } } return removed; } void XYCurve::xColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->xColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->xColumn = nullptr; d->retransform(); } } void XYCurve::yColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->yColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->yColumn = nullptr; d->retransform(); } } void XYCurve::valuesColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->valuesColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->valuesColumn = nullptr; d->updateValues(); } } void XYCurve::xErrorPlusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->xErrorPlusColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->xErrorPlusColumn = nullptr; d->updateErrorBars(); } } void XYCurve::xErrorMinusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->xErrorMinusColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->xErrorMinusColumn = nullptr; d->updateErrorBars(); } } void XYCurve::yErrorPlusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->yErrorPlusColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->yErrorPlusColumn = nullptr; d->updateErrorBars(); } } void XYCurve::yErrorMinusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (columnRemoved(d->yErrorMinusColumn, aspect)) { disconnect(aspect, nullptr, this, nullptr); d->yErrorMinusColumn = nullptr; d->updateErrorBars(); } } void XYCurve::xColumnNameChanged() { Q_D(XYCurve); setXColumnPath(d->xColumn->path()); } void XYCurve::yColumnNameChanged() { Q_D(XYCurve); setYColumnPath(d->yColumn->path()); } void XYCurve::xErrorPlusColumnNameChanged() { Q_D(XYCurve); setXErrorPlusColumnPath(d->xErrorPlusColumn->path()); } void XYCurve::xErrorMinusColumnNameChanged() { Q_D(XYCurve); setXErrorMinusColumnPath(d->xErrorMinusColumn->path()); } void XYCurve::yErrorPlusColumnNameChanged() { Q_D(XYCurve); setYErrorPlusColumnPath(d->yErrorPlusColumn->path()); } void XYCurve::yErrorMinusColumnNameChanged() { Q_D(XYCurve); setYErrorMinusColumnPath(d->yErrorMinusColumn->path()); } void XYCurve::valuesColumnNameChanged() { Q_D(XYCurve); setValuesColumnPath(d->valuesColumn->path()); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void XYCurve::visibilityChanged() { Q_D(const XYCurve); this->setVisible(!d->isVisible()); } void XYCurve::navigateTo() { project()->navigateTo(navigateToAction->data().toString()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYCurvePrivate::XYCurvePrivate(XYCurve *owner) : q(owner) { setFlag(QGraphicsItem::ItemIsSelectable, true); setAcceptHoverEvents(false); } QString XYCurvePrivate::name() const { return q->name(); } QRectF XYCurvePrivate::boundingRect() const { return boundingRectangle; } /*! Returns the shape of the XYCurve as a QPainterPath in local coordinates */ QPainterPath XYCurvePrivate::shape() const { return curveShape; } void XYCurvePrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { if (q->activateCurve(event->pos())) { q->createContextMenu()->exec(event->screenPos()); return; } QGraphicsItem::contextMenuEvent(event); } bool XYCurvePrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); retransform(); return oldValue; } /*! called when the size of the plot or its data ranges (manual changes, zooming, etc.) were changed. recalculates the position of the scene points to be drawn. triggers the update of lines, drop lines, symbols etc. */ void XYCurvePrivate::retransform() { if (!isVisible()) return; - DEBUG("\nXYCurvePrivate::retransform() name = " << name().toStdString() << ", m_suppressRetransform = " << m_suppressRetransform); + DEBUG("\nXYCurvePrivate::retransform() name = " << STDSTRING(name()) << ", m_suppressRetransform = " << m_suppressRetransform); DEBUG(" plot = " << plot); if (m_suppressRetransform || !plot) return; { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform()"); #endif symbolPointsScene.clear(); if ( (nullptr == xColumn) || (nullptr == yColumn) ) { DEBUG(" xColumn or yColumn == NULL"); linePath = QPainterPath(); dropLinePath = QPainterPath(); symbolsPath = QPainterPath(); valuesPath = QPainterPath(); errorBarsPath = QPainterPath(); curveShape = QPainterPath(); lines.clear(); valuesPoints.clear(); valuesStrings.clear(); fillPolygons.clear(); recalcShapeAndBoundingRect(); return; } if (!plot->isPanningActive()) WAIT_CURSOR; //calculate the scene coordinates // This condition cannot be used, because symbolPointsLogical is also used in updateErrorBars(), updateDropLines() and in updateFilling() // TODO: check updateErrorBars() and updateDropLines() and if they aren't available don't calculate this part //if (symbolsStyle != Symbol::NoSymbols || valuesType != XYCurve::NoValues ) { { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform(), map logical points to scene coordinates"); #endif if (!symbolPointsLogical.isEmpty()) { float widthDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().width(), Worksheet::Inch); float heightDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().height(), Worksheet::Inch); int countPixelX = ceil(widthDatarectInch*QApplication::desktop()->physicalDpiX()); int countPixelY = ceil(heightDatarectInch*QApplication::desktop()->physicalDpiY()); if (countPixelX <=0 || countPixelY <=0) { RESET_CURSOR; return; } double minLogicalDiffX = 1/(plot->dataRect().width()/countPixelX); double minLogicalDiffY = 1/(plot->dataRect().height()/countPixelY); QVector> scenePointsUsed; // size of the datarect in pixels scenePointsUsed.resize(countPixelX+1); for (int i=0; i< countPixelX+1; i++) scenePointsUsed[i].resize(countPixelY+1); int columnProperties = xColumn->properties(); int startIndex; int endIndex; if (columnProperties == AbstractColumn::Properties::MonotonicDecreasing || columnProperties == AbstractColumn::Properties::MonotonicIncreasing) { double xMin = cSystem->mapSceneToLogical(plot->dataRect().topLeft()).x(); double xMax = cSystem->mapSceneToLogical(plot->dataRect().bottomRight()).x(); startIndex = Column::indexForValue(xMin, symbolPointsLogical, static_cast(columnProperties)); endIndex = Column::indexForValue(xMax, symbolPointsLogical, static_cast(columnProperties)); if (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) std::swap(startIndex, endIndex); if (startIndex < 0) startIndex = 0; if (endIndex < 0) endIndex = symbolPointsLogical.size()-1; } else { startIndex = 0; endIndex = symbolPointsLogical.size()-1; } visiblePoints = std::vector(symbolPointsLogical.count(), false); cSystem->mapLogicalToScene(startIndex, endIndex, symbolPointsLogical, symbolPointsScene, visiblePoints, scenePointsUsed, minLogicalDiffX, minLogicalDiffY); } } //} // (symbolsStyle != Symbol::NoSymbols || valuesType != XYCurve::NoValues ) m_suppressRecalc = true; updateLines(); updateDropLines(); updateSymbols(); updateValues(); m_suppressRecalc = false; updateErrorBars(); RESET_CURSOR; } } /*! * called if the x- or y-data was changed. * copies the valid data points from the x- and y-columns into the internal container */ void XYCurvePrivate::recalcLogicalPoints() { DEBUG("XYCurvePrivate::recalcLogicalPoints()"); PERFTRACE(name().toLatin1() + ", XYCurvePrivate::recalcLogicalPoints()"); symbolPointsLogical.clear(); connectedPointsLogical.clear(); validPointsIndicesLogical.clear(); visiblePoints.clear(); if (!xColumn || !yColumn) return; AbstractColumn::ColumnMode xColMode = xColumn->columnMode(); AbstractColumn::ColumnMode yColMode = yColumn->columnMode(); QPointF tempPoint; //take over only valid and non masked points. for (int row = 0; row < xColumn->rowCount(); row++) { if ( xColumn->isValid(row) && yColumn->isValid(row) && (!xColumn->isMasked(row)) && (!yColumn->isMasked(row)) ) { switch (xColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: case AbstractColumn::BigInt: tempPoint.setX(xColumn->valueAt(row)); break; case AbstractColumn::Text: break; case AbstractColumn::DateTime: tempPoint.setX(xColumn->dateTimeAt(row).toMSecsSinceEpoch()); break; case AbstractColumn::Month: case AbstractColumn::Day: break; } switch (yColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: case AbstractColumn::BigInt: tempPoint.setY(yColumn->valueAt(row)); break; case AbstractColumn::Text: break; case AbstractColumn::DateTime: tempPoint.setY(yColumn->dateTimeAt(row).toMSecsSinceEpoch()); break; case AbstractColumn::Month: case AbstractColumn::Day: break; } symbolPointsLogical.append(tempPoint); connectedPointsLogical.push_back(true); validPointsIndicesLogical.push_back(row); } else { if (!connectedPointsLogical.empty()) connectedPointsLogical[connectedPointsLogical.size()-1] = false; } } visiblePoints = std::vector(symbolPointsLogical.count(), false); } /*! * Adds a line, which connects two points, but only if the don't lie on the same xAxis pixel. * If they lie on the same x pixel, draw a vertical line between the minimum and maximum y value. So all points are included * This function is only valid for linear x Axis scale! * @param p0 first point * @param p1 second point * @param minY * @param maxY * @param overlap if at the previous call was an overlap between the previous two points * @param minLogicalDiffX logical difference between two pixels * @param pixelDiff x pixel distance between two points */ void XYCurvePrivate::addLine(QPointF p0, QPointF p1, double& minY, double& maxY, bool& overlap, double minLogicalDiffX, int& pixelDiff) { pixelDiff = (int)(p1.x() * minLogicalDiffX) - (int)(p0.x() * minLogicalDiffX); addLine(p0, p1, minY, maxY, overlap, pixelDiff); } /*! * Adds a line, which connects two points, but only if the don't lie on the same xAxis pixel. * If they lie on the same x pixel, draw a vertical line between the minimum and maximum y value. So all points are included * This function can be used for all axis scalings (log, sqrt, linear, ...). For the linear case use the above function, because it's optimized for the linear case * @param p0 first point * @param p1 second point * @param minY * @param maxY * @param overlap if at the previous call was an overlap between the previous two points * @param pixelDiff x pixel distance between two points * @param pixelCount pixel count */ void XYCurvePrivate::addLine(QPointF p0, QPointF p1, double& minY, double& maxY, bool& overlap, int& pixelDiff, int pixelCount) { if (plot->xScale() == CartesianPlot::Scale::ScaleLinear) { // implemented for completeness only double minLogicalDiffX = 1/((plot->xMax()-plot->xMin())/pixelCount); addLine(p0, p1, minY, maxY, overlap, minLogicalDiffX, pixelDiff); } else { // for nonlinear scaling the pixel distance must be calculated for every point pair QPointF p0Scene = cSystem->mapLogicalToScene(p0, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); QPointF p1Scene = cSystem->mapLogicalToScene(p1, CartesianCoordinateSystem::MappingFlag::SuppressPageClipping); // if the point is not valid, don't create a line //if (std::isnan(p0Scene.x()) || std::isnan(p0Scene.y())) if ((p0Scene.x() == 0 && p0Scene.y() == 0) || (p1Scene.x() == 0 && p1Scene.y() == 0)) // no possibility to create line return; // using only the difference between the points is not sufficient, because p0 is updated always // independent if new line added or not int p0Pixel = (int)((p0Scene.x() - plot->dataRect().x()) / plot->dataRect().width() * pixelCount); int p1Pixel = (int)((p1Scene.x() - plot->dataRect().x()) / plot->dataRect().width() * pixelCount); pixelDiff = p1Pixel - p0Pixel; addLine(p0, p1, minY, maxY, overlap, pixelDiff); } } /*! * \brief XYCurvePrivate::addLine * This function is part of the other two addLine() functions to not have two times the same code * @param p0 first point * @param p1 second point * @param minY * @param maxY * @param overlap if at the previous call was an overlap between the previous two points * @param pixelDiff x pixel distance between two points */ void XYCurvePrivate::addLine(QPointF p0, QPointF p1, double& minY, double& maxY, bool& overlap, int& pixelDiff) { if (pixelDiff == 0) { if (overlap) { // second and so the x axis pixels are the same if (p1.y() > maxY) maxY = p1.y(); if (p1.y() < minY) minY = p1.y(); } else { // first time pixel are same if (p0.y() < p1.y()) { minY = p0.y(); maxY = p1.y(); } else { maxY = p0.y(); minY = p1.y(); } overlap = true; } } else { if (overlap) { // when previously overlap was true, draw the previous line overlap = false; // last point from previous pixel must be evaluated if (p0.y() > maxY) maxY = p0.y(); if (p0.y() < minY) minY = p0.y(); if (true) { //p1.x() >= plot->xMin() && p1.x() <= plot->xMax()) { // x inside scene if (minY == maxY) { lines.append(QLineF(p0, p1)); // line from previous point to actual point } else if (p0.y() == minY) { // draw vertical line lines.append(QLineF(p0.x(),maxY, p0.x(), minY)); if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1) return; lines.append(QLineF(p0,p1)); } else if (p0.y() == maxY) { // draw vertical line lines.append(QLineF(p0.x(),maxY, p0.x(), minY)); if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1) return; // draw line, only if there is a pixelDiff = 1 otherwise no line needed, because when drawing a new vertical line, this line is already included lines.append(QLineF(p0,p1)); } else { // last point nor min nor max lines.append(QLineF(p0.x(),maxY, p0.x(), minY)); if (p1.y() >= minY && p1.y() <= maxY && pixelDiff == 1) return; lines.append(QLineF(p0,p1)); } } else// x in scene DEBUG("addLine: not in scene"); } else// no overlap lines.append(QLineF(p0,p1)); } } /*! recalculates the painter path for the lines connecting the data points. Called each time when the type of this connection is changed. At the moment also the points which are outside of the scene are added. This algorithm can be improved by letting away all lines where both points are outside of the scene */ void XYCurvePrivate::updateLines() { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines()"); #endif linePath = QPainterPath(); lines.clear(); if (lineType == XYCurve::NoLine) { DEBUG(" nothing to do, since line type is XYCurve::NoLine"); updateFilling(); recalcShapeAndBoundingRect(); return; } unsigned int count = (unsigned int)symbolPointsLogical.count(); if (count <= 1) { DEBUG(" nothing to do, since no data points available"); recalcShapeAndBoundingRect(); return; } float widthDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().width(), Worksheet::Inch); //float heightDatarectInch = Worksheet::convertFromSceneUnits(plot->dataRect().height(), Worksheet::Inch); // unsed int countPixelX = ceil(widthDatarectInch*QApplication::desktop()->physicalDpiX()); //int countPixelY = ceil(heightDatarectInch*QApplication::desktop()->physicalDpiY()); // unused // only valid for linear scale //double minLogicalDiffX = 1/((plot->xMax()-plot->xMin())/countPixelX); // unused //double minLogicalDiffY = 1/((plot->yMax()-plot->yMin())/countPixelY); // unused //calculate the lines connecting the data points { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), calculate the lines connecting the data points"); #endif QPointF tempPoint1, tempPoint2; // used as temporaryPoints to interpolate datapoints if the corresponding setting is set int startIndex, endIndex; // find index for xMin and xMax to not loop throug all values AbstractColumn::Properties columnProperties = q->xColumn()->properties(); if (columnProperties == AbstractColumn::Properties::MonotonicDecreasing || columnProperties == AbstractColumn::Properties::MonotonicIncreasing) { double xMin = cSystem->mapSceneToLogical(plot->dataRect().topLeft()).x(); double xMax = cSystem->mapSceneToLogical(plot->dataRect().bottomRight()).x(); startIndex= Column::indexForValue(xMin, symbolPointsLogical, columnProperties); endIndex = Column::indexForValue(xMax, symbolPointsLogical, columnProperties); if (startIndex > endIndex) std::swap(startIndex, endIndex); startIndex--; // use one value before endIndex ++; if (startIndex < 0) startIndex = 0; if(endIndex < 0 || endIndex >= static_cast(count)) endIndex = static_cast(count)-1; count = static_cast(endIndex - startIndex +1); }else { startIndex = 0; endIndex = static_cast(count)-1; } if (columnProperties == AbstractColumn::Properties::Constant) { tempPoint1 = QPointF(plot->xMin(), plot->yMin()); tempPoint2 = QPointF(plot->xMin(), plot->yMax()); lines.append(QLineF(tempPoint1, tempPoint2)); } else { bool overlap = false; double maxY, minY; // are initialized in add line() int pixelDiff; QPointF p0; QPointF p1; switch (lineType) { case XYCurve::NoLine: break; case XYCurve::Line: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) // when option set skip points continue; addLine(p0, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::StartHorizontal: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) continue; tempPoint1 = QPointF(p1.x(), p0.y()); addLine(p0, tempPoint1, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint1, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::StartVertical: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) continue; tempPoint1 = QPointF(p0.x(), p1.y()); addLine(p0, tempPoint1, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint1, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::MidpointHorizontal: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) continue; tempPoint1 = QPointF(p0.x() + (p1.x()-p0.x())/2, p0.y()); tempPoint2 = QPointF(p0.x() + (p1.x()-p0.x())/2, p1.y()); addLine(p0, tempPoint1, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint1, tempPoint2, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint2, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::MidpointVertical: { for (int i = startIndex; i < endIndex; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (lineIncreasingXOnly && (p1.x() < p0.x())) continue; tempPoint1 = QPointF(p0.x(), p0.y() + (p1.y()-p0.y())/2); tempPoint2 = QPointF(p1.x(), p0.y() + (p1.y()-p0.y())/2); addLine(p0, tempPoint1, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint1, tempPoint2, minY, maxY, overlap, pixelDiff, countPixelX); addLine(tempPoint2, p1, minY, maxY, overlap, pixelDiff, countPixelX); } // add last line if (overlap) lines.append(QLineF(QPointF(p1.x(), minY), QPointF(p1.x(), maxY))); break; } case XYCurve::Segments2: { int skip = 0; for (int i = startIndex; i < endIndex; i++) { p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if (skip != 1) { if ( (!lineSkipGaps && !connectedPointsLogical[i]) || (lineIncreasingXOnly && (p1.x() < p0.x())) ) { skip = 0; continue; } addLine(p0, p1, minY, maxY, overlap, pixelDiff, countPixelX); skip++; } else { skip = 0; if (overlap) { overlap = false; lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY))); } } } // add last line if (overlap) lines.append(QLineF(symbolPointsLogical[endIndex-1], symbolPointsLogical[endIndex])); break; } case XYCurve::Segments3: { int skip = 0; for (int i = startIndex; i < endIndex; i++) { if (skip != 2) { p0 = symbolPointsLogical[i]; p1 = symbolPointsLogical[i+1]; if ( (!lineSkipGaps && !connectedPointsLogical[i]) || (lineIncreasingXOnly && (p1.x() < p0.x())) ) { skip = 0; continue; } addLine(p0, p1, minY, maxY, overlap, pixelDiff, countPixelX); skip++; } else { skip = 0; if (overlap) { overlap = false; lines.append(QLineF(QPointF(p0.x(), minY), QPointF(p0.x(), maxY))); } } } // add last line if (overlap) lines.append(QLineF(symbolPointsLogical[endIndex-1], symbolPointsLogical[endIndex])); break; } case XYCurve::SplineCubicNatural: case XYCurve::SplineCubicPeriodic: case XYCurve::SplineAkimaNatural: case XYCurve::SplineAkimaPeriodic: { gsl_interp_accel *acc = gsl_interp_accel_alloc(); gsl_spline *spline = nullptr; double* x = new double[count]; double* y = new double[count]; for (unsigned int i = 0; i < count; i++) { // TODO: interpolating only between the visible points? x[i] = symbolPointsLogical[i+startIndex].x(); y[i] = symbolPointsLogical[i+startIndex].y(); } gsl_set_error_handler_off(); if (lineType == XYCurve::SplineCubicNatural) spline = gsl_spline_alloc(gsl_interp_cspline, count); else if (lineType == XYCurve::SplineCubicPeriodic) spline = gsl_spline_alloc(gsl_interp_cspline_periodic, count); else if (lineType == XYCurve::SplineAkimaNatural) spline = gsl_spline_alloc(gsl_interp_akima, count); else if (lineType == XYCurve::SplineAkimaPeriodic) spline = gsl_spline_alloc(gsl_interp_akima_periodic, count); if (!spline) { QString msg; if ( (lineType == XYCurve::SplineAkimaNatural || lineType == XYCurve::SplineAkimaPeriodic) && count < 5) msg = i18n("Error: Akima spline interpolation requires a minimum of 5 points."); else msg = i18n("Error: Could not initialize the spline function."); emit q->info(msg); recalcShapeAndBoundingRect(); delete[] x; delete[] y; gsl_interp_accel_free (acc); return; } int status = gsl_spline_init (spline, x, y, count); if (status) { //TODO: check in gsl/interp.c when GSL_EINVAL is thrown QString gslError; if (status == GSL_EINVAL) gslError = i18n("x values must be monotonically increasing."); else gslError = gslErrorToString(status); emit q->info( i18n("Error: %1", gslError) ); recalcShapeAndBoundingRect(); delete[] x; delete[] y; gsl_spline_free (spline); gsl_interp_accel_free (acc); return; } //create interpolating points std::vector xinterp, yinterp; for (unsigned int i = 0; i < count - 1; i++) { const double x1 = x[i]; const double x2 = x[i+1]; const double step = fabs(x2 - x1)/(lineInterpolationPointsCount + 1); for (int i = 0; i < (lineInterpolationPointsCount + 1); i++) { double xi = x1+i*step; double yi = gsl_spline_eval(spline, xi, acc); xinterp.push_back(xi); yinterp.push_back(yi); } } if (!xinterp.empty()) { for (unsigned int i = 0; i < xinterp.size() - 1; i++) { p0 = QPointF(xinterp[i], yinterp[i]); p1 = QPointF(xinterp[i+1], yinterp[i+1]); addLine(p0, p1, minY, maxY, overlap, pixelDiff, countPixelX); } addLine(QPointF(xinterp[xinterp.size()-1], yinterp[yinterp.size()-1]), QPointF(x[count-1], y[count-1]), minY, maxY, overlap, pixelDiff, countPixelX); // add last line if (overlap) lines.append(QLineF(QPointF(xinterp[xinterp.size()-1], yinterp[yinterp.size()-1]), QPointF(x[count-1], y[count-1]))); } delete[] x; delete[] y; gsl_spline_free (spline); gsl_interp_accel_free (acc); break; } } } } //map the lines to scene coordinates { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), map lines to scene coordinates"); #endif lines = cSystem->mapLogicalToScene(lines); } { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), calculate new line path"); #endif //new line path for (const auto& line : lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } } updateFilling(); recalcShapeAndBoundingRect(); } /*! recalculates the painter path for the drop lines. Called each time when the type of the drop lines is changed. */ void XYCurvePrivate::updateDropLines() { dropLinePath = QPainterPath(); if (dropLineType == XYCurve::NoDropLine) { recalcShapeAndBoundingRect(); return; } //calculate drop lines QVector lines; float xMin = 0; float yMin = 0; xMin = plot->xMin(); yMin = plot->yMin(); switch (dropLineType) { case XYCurve::NoDropLine: break; case XYCurve::DropLineX: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append(QLineF(point, QPointF(point.x(), yMin))); } break; case XYCurve::DropLineY: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append(QLineF(point, QPointF(xMin, point.y()))); } break; case XYCurve::DropLineXY: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append(QLineF(point, QPointF(point.x(), yMin))); lines.append(QLineF(point, QPointF(xMin, point.y()))); } break; case XYCurve::DropLineXZeroBaseline: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append(QLineF(point, QPointF(point.x(), 0))); } break; case XYCurve::DropLineXMinBaseline: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append( QLineF(point, QPointF(point.x(), yColumn->minimum())) ); } break; case XYCurve::DropLineXMaxBaseline: for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); lines.append( QLineF(point, QPointF(point.x(), yColumn->maximum())) ); } break; } //map the drop lines to scene coordinates lines = cSystem->mapLogicalToScene(lines); //new painter path for the drop lines for (const auto& line : lines) { dropLinePath.moveTo(line.p1()); dropLinePath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void XYCurvePrivate::updateSymbols() { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateSymbols()"); #endif symbolsPath = QPainterPath(); if (symbolsStyle != Symbol::NoSymbols) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(symbolsRotationAngle); path = trafo.map(path); } for (const auto& point : symbolPointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); symbolsPath.addPath(trafo.map(path)); } } recalcShapeAndBoundingRect(); } /*! recreates the value strings to be shown and recalculates their draw position. */ void XYCurvePrivate::updateValues() { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateValues()"); #endif valuesPath = QPainterPath(); valuesPoints.clear(); valuesStrings.clear(); if (valuesType == XYCurve::NoValues) { recalcShapeAndBoundingRect(); return; } //determine the value string for all points that are currently visible in the plot switch (valuesType) { case XYCurve::NoValues: case XYCurve::ValuesX: { for (int i = 0; i < symbolPointsScene.size(); ++i) { if (!visiblePoints[i]) continue; valuesStrings << valuesPrefix + QString::number(cSystem->mapSceneToLogical(symbolPointsScene[i]).x()) + valuesSuffix; } break; } case XYCurve::ValuesY: { for (int i = 0; i < symbolPointsScene.size(); ++i) { if (!visiblePoints[i]) continue; valuesStrings << valuesPrefix + QString::number(cSystem->mapSceneToLogical(symbolPointsScene[i]).y()) + valuesSuffix; } break; } case XYCurve::ValuesXY: { for (int i = 0; i < symbolPointsScene.size(); ++i) { if (!visiblePoints[i]) continue; QPointF logicalValue = cSystem->mapSceneToLogical(symbolPointsScene[i]); valuesStrings << valuesPrefix + QString::number(logicalValue.x()) + ',' + QString::number(logicalValue.y()) + valuesSuffix; } break; } case XYCurve::ValuesXYBracketed: { for (int i = 0; i < symbolPointsScene.size(); ++i) { if (!visiblePoints[i]) continue; QPointF logicalValue = cSystem->mapSceneToLogical(symbolPointsScene[i]); valuesStrings << valuesPrefix + '(' + QString::number(logicalValue.x()) + ',' + QString::number(logicalValue.y()) +')' + valuesSuffix; } break; } case XYCurve::ValuesCustomColumn: { if (!valuesColumn) { recalcShapeAndBoundingRect(); return; } int endRow; if (symbolPointsLogical.size()>valuesColumn->rowCount()) endRow = valuesColumn->rowCount(); else endRow = symbolPointsLogical.size(); AbstractColumn::ColumnMode xColMode = valuesColumn->columnMode(); for (int i = 0; i < endRow; ++i) { if (!visiblePoints[i]) continue; if ( !valuesColumn->isValid(i) || valuesColumn->isMasked(i) ) continue; switch (xColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: case AbstractColumn::BigInt: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix; break; case AbstractColumn::Text: valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: //TODO break; } } } } //Calculate the coordinates where to paint the value strings. //The coordinates depend on the actual size of the string. QPointF tempPoint; QFontMetrics fm(valuesFont); qreal w; qreal h = fm.ascent(); for (int i = 0; i < valuesStrings.size(); i++) { w = fm.boundingRect(valuesStrings.at(i)).width(); switch (valuesPosition) { case XYCurve::ValuesAbove: tempPoint.setX( symbolPointsScene.at(i).x() - w/2); tempPoint.setY( symbolPointsScene.at(i).y() - valuesDistance ); break; case XYCurve::ValuesUnder: tempPoint.setX( symbolPointsScene.at(i).x() -w/2 ); tempPoint.setY( symbolPointsScene.at(i).y() + valuesDistance + h/2); break; case XYCurve::ValuesLeft: tempPoint.setX( symbolPointsScene.at(i).x() - valuesDistance - w - 1 ); tempPoint.setY( symbolPointsScene.at(i).y()); break; case XYCurve::ValuesRight: tempPoint.setX( symbolPointsScene.at(i).x() + valuesDistance - 1 ); tempPoint.setY( symbolPointsScene.at(i).y() ); break; } valuesPoints.append(tempPoint); } QTransform trafo; QPainterPath path; for (int i = 0; i < valuesPoints.size(); i++) { path = QPainterPath(); path.addText( QPoint(0,0), valuesFont, valuesStrings.at(i) ); trafo.reset(); trafo.translate( valuesPoints.at(i).x(), valuesPoints.at(i).y() ); if (valuesRotationAngle != 0) trafo.rotate( -valuesRotationAngle ); valuesPath.addPath(trafo.map(path)); } recalcShapeAndBoundingRect(); } void XYCurvePrivate::updateFilling() { if (m_suppressRetransform) return; fillPolygons.clear(); //don't try to calculate the filling polygons if // - no filling was enabled // - the nubmer of visible points on the scene is too high // - no scene points available, everything outside of the plot region or no scene points calculated yet if (fillingPosition == XYCurve::NoFilling || symbolPointsScene.size() > 1000 || symbolPointsScene.isEmpty()) { recalcShapeAndBoundingRect(); return; } QVector fillLines; //if there're no interpolation lines available (XYCurve::NoLine selected), create line-interpolation, //use already available lines otherwise. if (!lines.isEmpty()) fillLines = lines; else { for (int i = 0; i < symbolPointsLogical.count() - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; fillLines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); } //no lines available (no points), nothing to do if (fillLines.isEmpty()) return; fillLines = cSystem->mapLogicalToScene(fillLines); //no lines available (no points) after mapping, nothing to do if (fillLines.isEmpty()) return; } //create polygon(s): //1. Depending on the current zoom-level, only a subset of the curve may be visible in the plot //and more of the filling area should be shown than the area defined by the start and end points of the currently visible points. //We check first whether the curve crosses the boundaries of the plot and determine new start and end points and put them to the boundaries. //2. Furthermore, depending on the current filling type we determine the end point (x- or y-coordinate) where all polygons are closed at the end. QPolygonF pol; QPointF start = fillLines.at(0).p1(); //starting point of the current polygon, initialize with the first visible point QPointF end = fillLines.at(fillLines.size()-1).p2(); //end point of the current polygon, initialize with the last visible point const QPointF& first = symbolPointsLogical.at(0); //first point of the curve, may not be visible currently const QPointF& last = symbolPointsLogical.at(symbolPointsLogical.size()-1);//last point of the curve, may not be visible currently QPointF edge; float xEnd = 0, yEnd = 0; if (fillingPosition == XYCurve::FillingAbove) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())).y(); } else if (fillingPosition == XYCurve::FillingBelow) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).y(); } else if (fillingPosition == XYCurve::FillingZeroBaseline) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (plot->yMax() > 0) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } else { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (plot->yMax() > 0) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } else { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } } yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin()>0 ? plot->yMin() : 0)).y(); } else if (fillingPosition == XYCurve::FillingLeft) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).x(); } else { //FillingRight edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())).x(); } if (start != fillLines.at(0).p1()) pol << start; QPointF p1, p2; for (int i = 0; i < fillLines.size(); ++i) { const QLineF& line = fillLines.at(i); p1 = line.p1(); p2 = line.p2(); if (i != 0 && p1 != fillLines.at(i-1).p2()) { //the first point of the current line is not equal to the last point of the previous line //->check whether we have a break in between. const bool gap = false; //TODO if (!gap) { //-> we have no break in the curve -> connect the points by a horizontal/vertical line pol << fillLines.at(i-1).p2() << p1; } else { //-> we have a break in the curve -> close the polygon, add it to the polygon list and start a new polygon if (fillingPosition == XYCurve::FillingAbove || fillingPosition == XYCurve::FillingBelow || fillingPosition == XYCurve::FillingZeroBaseline) { pol << QPointF(fillLines.at(i-1).p2().x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, fillLines.at(i-1).p2().y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; pol.clear(); start = p1; } } pol << p1 << p2; } if (p2 != end) pol << end; //close the last polygon if (fillingPosition == XYCurve::FillingAbove || fillingPosition == XYCurve::FillingBelow || fillingPosition == XYCurve::FillingZeroBaseline) { pol << QPointF(end.x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, end.y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; recalcShapeAndBoundingRect(); } /*! * Find y value which corresponds to a @p x . @p valueFound indicates, if value was found. * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value. * @param x * @param valueFound * @return */ double XYCurve::y(double x, bool &valueFound) const { if (!yColumn() || !xColumn()) { valueFound = false; return NAN; } AbstractColumn::ColumnMode yColumnMode = yColumn()->columnMode(); int index = xColumn()->indexForValue(x); if (index < 0) { valueFound = false; return NAN; } valueFound = true; if (yColumnMode == AbstractColumn::ColumnMode::Numeric || yColumnMode == AbstractColumn::ColumnMode::Integer || yColumnMode == AbstractColumn::ColumnMode::BigInt) { return yColumn()->valueAt(index); } else { valueFound = false; return NAN; } } /*! * Find y DateTime which corresponds to a @p x . @p valueFound indicates, if value was found. * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value. * @param x * @param valueFound * @return Return found value */ QDateTime XYCurve::yDateTime(double x, bool &valueFound) const { if (!yColumn() || !xColumn()) { valueFound = false; return QDateTime(); } AbstractColumn::ColumnMode yColumnMode = yColumn()->columnMode(); int index = xColumn()->indexForValue(x); if (index < 0) { valueFound = false; return QDateTime(); } valueFound = true; if (yColumnMode == AbstractColumn::ColumnMode::Day || yColumnMode == AbstractColumn::ColumnMode::Month || yColumnMode == AbstractColumn::ColumnMode::DateTime) return yColumn()->dateTimeAt(index); valueFound = false; return QDateTime(); } bool XYCurve::minMaxY(int indexMin, int indexMax, double& yMin, double& yMax, bool includeErrorBars) const { return minMax(yColumn(), xColumn(), yErrorType(), yErrorPlusColumn(), yErrorMinusColumn(), indexMin, indexMax, yMin, yMax, includeErrorBars); } bool XYCurve::minMaxX(int indexMin, int indexMax, double& xMin, double& xMax, bool includeErrorBars) const { return minMax(xColumn(), yColumn(), xErrorType(), xErrorPlusColumn(), xErrorMinusColumn(), indexMin, indexMax, xMin, xMax, includeErrorBars); } /*! * Calculates the minimum \p min and maximum \p max of a curve with optionally respecting the error bars * This function does not check if the values are out of range * \p indexMax is not included * \p column * \p errorType * \p errorPlusColumn * \p errorMinusColumn * \p indexMin * \p indexMax * \p min * \p max * \ includeErrorBars If true respect the error bars in the min/max calculation */ bool XYCurve::minMax(const AbstractColumn* column1, const AbstractColumn* column2, const ErrorType errorType, const AbstractColumn* errorPlusColumn, const AbstractColumn* errorMinusColumn, int indexMin, int indexMax, double& min, double& max, bool includeErrorBars) const { // when property is greater than 1 there is a benefit in finding minimum and maximum // for property == 0 it must be iterated over all values so it does not matter if this function or the below one is used // if the property of the second column is greater 0 means, that all values are valid and not masked if ((!includeErrorBars || errorType == XYCurve::NoError) && column1->properties() > 0 && column2 && column2->properties() > 0) { min = column1->minimum(indexMin, indexMax); max = column1->maximum(indexMin, indexMax); return true; } if (column1->rowCount() == 0) return false; min = INFINITY; max = -INFINITY; for (int i = indexMin; i < indexMax; ++i) { if (!column1->isValid(i) || column1->isMasked(i) || (column2 && (!column2->isValid(i) || column2->isMasked(i)))) continue; if ( (errorPlusColumn && i >= errorPlusColumn->rowCount()) || (errorMinusColumn && i >= errorMinusColumn->rowCount()) ) continue; //determine the values for the errors double errorPlus, errorMinus; if (errorPlusColumn && errorPlusColumn->isValid(i) && !errorPlusColumn->isMasked(i)) if (errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Numeric || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Integer || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) errorPlus = errorPlusColumn->valueAt(i); else if (errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::DateTime || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Month || errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Day) errorPlus = errorPlusColumn->dateTimeAt(i).toMSecsSinceEpoch(); else return false; else errorPlus = 0; if (errorType == XYCurve::SymmetricError) errorMinus = errorPlus; else { if (errorMinusColumn && errorMinusColumn->isValid(i) && !errorMinusColumn->isMasked(i)) if (errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Numeric || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Integer || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) errorMinus = errorMinusColumn->valueAt(i); else if (errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::DateTime || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Month || errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Day) errorMinus = errorMinusColumn->dateTimeAt(i).toMSecsSinceEpoch(); else return false; else errorMinus = 0; } double value; if (column1->columnMode() == AbstractColumn::ColumnMode::Numeric || column1->columnMode() == AbstractColumn::ColumnMode::Integer || column1->columnMode() == AbstractColumn::ColumnMode::BigInt) value = column1->valueAt(i); else if (column1->columnMode() == AbstractColumn::ColumnMode::DateTime || column1->columnMode() == AbstractColumn::ColumnMode::Month || column1->columnMode() == AbstractColumn::ColumnMode::Day) { value = column1->dateTimeAt(i).toMSecsSinceEpoch(); } else return false; if (value - errorMinus < min) min = value - errorMinus; if (value + errorPlus > max) max = value + errorPlus; } return true; } /*! * \brief XYCurve::activateCurve * Checks if the mousepos distance to the curve is less than @p pow(maxDist,2) * \p mouseScenePos * \p maxDist Maximum distance the point lies away from the curve * \return Returns true if the distance is smaller than pow(maxDist,2). */ bool XYCurve::activateCurve(QPointF mouseScenePos, double maxDist) { Q_D(XYCurve); return d->activateCurve(mouseScenePos, maxDist); } bool XYCurvePrivate::activateCurve(QPointF mouseScenePos, double maxDist) { if (!isVisible()) return false; int rowCount = 0; if (lineType != XYCurve::LineType::NoLine) rowCount = lines.count(); else if (symbolsStyle != Symbol::Style::NoSymbols) rowCount = symbolPointsScene.count(); else return false; if (rowCount == 0) return false; if (maxDist < 0) maxDist = linePen.width() < 10 ? 10: linePen.width(); double maxDistSquare = pow(maxDist,2); int properties = q->xColumn()->properties(); if (properties == AbstractColumn::Properties::No) { // assumption: points exist if no line. otherwise previously returned false if (lineType == XYCurve::NoLine) { QPointF curvePosPrevScene = symbolPointsScene[0]; QPointF curvePosScene = curvePosPrevScene; for (int row =0; row < rowCount; row ++) { if (pow(mouseScenePos.x() - curvePosScene.x(),2) + pow(mouseScenePos.y() - curvePosScene.y(),2) <= maxDistSquare) return true; curvePosPrevScene = curvePosScene; curvePosScene = symbolPointsScene[row]; } } else { for (int row=0; row < rowCount; row++) { QLineF line = lines[row]; if (pointLiesNearLine(line.p1(), line.p2(), mouseScenePos, maxDist)) return true; } } } else if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) { bool increase = true; if (properties == AbstractColumn::Properties::MonotonicDecreasing) increase = false; double x = mouseScenePos.x()-maxDist; int index = 0; QPointF curvePosScene; QPointF curvePosPrevScene; if (lineType == XYCurve::NoLine) { curvePosScene = symbolPointsScene[index]; curvePosPrevScene = curvePosScene; index = Column::indexForValue(x, symbolPointsScene, static_cast(properties)); } else index = Column::indexForValue(x, lines, static_cast(properties)); if (index >= 1) index --; // use one before so it is secured that I'm before point.x() else if (index == -1) return false; double xMaxSquare = mouseScenePos.x() + maxDist; bool stop = false; while (true) { // assumption: points exist if no line. otherwise previously returned false if (lineType == XYCurve::NoLine) {// check points only if no line otherwise check only the lines if (curvePosScene.x() > xMaxSquare) stop = true; // one more time if bigger if (pow(mouseScenePos.x()- curvePosScene.x(),2)+pow(mouseScenePos.y()-curvePosScene.y(),2) <= maxDistSquare) return true; } else { if (lines[index].p1().x() > xMaxSquare) stop = true; // one more time if bigger QLineF line = lines[index]; if (pointLiesNearLine(line.p1(), line.p2(), mouseScenePos, maxDist)) return true; } if (stop || (index >= rowCount-1 && increase) || (index <=0 && !increase)) break; if (increase) index++; else index--; if (lineType == XYCurve::NoLine) { curvePosPrevScene = curvePosScene; curvePosScene = symbolPointsScene[index]; } } } return false; } /*! * \brief XYCurve::pointLiesNearLine * Calculates if a point \p pos lies near than maxDist to the line created by the points \p p1 and \p p2 * https://stackoverflow.com/questions/11604680/point-laying-near-line * \p p1 first point of the line * \p p2 second point of the line * \p pos Position to check * \p maxDist Maximal distance away from the curve, which is valid * \return Return true if point lies next to the line */ bool XYCurvePrivate::pointLiesNearLine(const QPointF p1, const QPointF p2, const QPointF pos, const double maxDist) const{ double dx12 = p2.x() - p1.x(); double dy12 = p2.y() - p1.y(); double vecLenght = sqrt(pow(dx12,2) + pow(dy12,2)); if (vecLenght == 0) { if (pow(p1.x() - pos.x(), 2) + pow(p1.y()-pos.y(), 2) <= pow(maxDist, 2)) return true; return false; } QPointF unitvec(dx12/vecLenght,dy12/vecLenght); double dx1m = pos.x() - p1.x(); double dy1m = pos.y() - p1.y(); double dist_segm = qAbs(dx1m*unitvec.y() - dy1m*unitvec.x()); double scalarProduct = dx1m*unitvec.x() + dy1m*unitvec.y(); if (scalarProduct > 0) { if (scalarProduct < vecLenght && dist_segm < maxDist) return true; } return false; } // TODO: curvePosScene.x() >= mouseScenePos.x() && // curvePosPrevScene.x() < mouseScenePos.x() // dürfte eigentlich nicht drin sein bool XYCurvePrivate::pointLiesNearCurve(const QPointF mouseScenePos, const QPointF curvePosPrevScene, const QPointF curvePosScene, const int index, const double maxDist) const { if (q->lineType() != XYCurve::LineType::NoLine && curvePosScene.x() >= mouseScenePos.x() && curvePosPrevScene.x() < mouseScenePos.x()) { if (q->lineType() == XYCurve::LineType::Line) { // point is not in the near of the point, but it can be in the near of the connection line of two points if (pointLiesNearLine(curvePosPrevScene,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::StartHorizontal) { QPointF tempPoint = curvePosPrevScene; tempPoint.setX(curvePosScene.x()); if (pointLiesNearLine(curvePosPrevScene,tempPoint, mouseScenePos, maxDist)) return true; if (pointLiesNearLine(tempPoint,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::StartVertical) { QPointF tempPoint = curvePosPrevScene; tempPoint.setY(curvePosScene.y()); if (pointLiesNearLine(curvePosPrevScene,tempPoint, mouseScenePos, maxDist)) return true; if (pointLiesNearLine(tempPoint,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::MidpointHorizontal) { QPointF tempPoint = curvePosPrevScene; tempPoint.setX(curvePosPrevScene.x()+(curvePosScene.x()-curvePosPrevScene.x())/2); if (pointLiesNearLine(curvePosPrevScene,tempPoint, mouseScenePos, maxDist)) return true; QPointF tempPoint2(tempPoint.x(), curvePosScene.y()); if (pointLiesNearLine(tempPoint,tempPoint2, mouseScenePos, maxDist)) return true; if (pointLiesNearLine(tempPoint2,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::MidpointVertical) { QPointF tempPoint = curvePosPrevScene; tempPoint.setY(curvePosPrevScene.y()+(curvePosScene.y()-curvePosPrevScene.y())/2); if (pointLiesNearLine(curvePosPrevScene,tempPoint, mouseScenePos, maxDist)) return true; QPointF tempPoint2(tempPoint.y(), curvePosScene.x()); if (pointLiesNearLine(tempPoint,tempPoint2, mouseScenePos, maxDist)) return true; if (pointLiesNearLine(tempPoint2,curvePosScene, mouseScenePos, maxDist)) return true; } else if (q->lineType() == XYCurve::LineType::SplineAkimaNatural || q->lineType() == XYCurve::LineType::SplineCubicNatural || q->lineType() == XYCurve::LineType::SplineAkimaPeriodic || q->lineType() == XYCurve::LineType::SplineCubicPeriodic) { for (int i=0; i < q->lineInterpolationPointsCount()+1; i++) { QLineF line = lines[index*(q->lineInterpolationPointsCount()+1)+i]; QPointF p1 = line.p1(); //cSystem->mapLogicalToScene(line.p1()); QPointF p2 = line.p2(); //cSystem->mapLogicalToScene(line.p2()); if (pointLiesNearLine(p1, p2, mouseScenePos, maxDist)) return true; } } else { // point is not in the near of the point, but it can be in the near of the connection line of two points if (pointLiesNearLine(curvePosPrevScene,curvePosScene, mouseScenePos, maxDist)) return true; } } return false; } /*! * \brief XYCurve::setHover * Will be called in CartesianPlot::hoverMoveEvent() * See d->setHover(on) for more documentation * \p on */ void XYCurve::setHover(bool on) { Q_D(XYCurve); d->setHover(on); } void XYCurvePrivate::updateErrorBars() { errorBarsPath = QPainterPath(); if (xErrorType == XYCurve::NoError && yErrorType == XYCurve::NoError) { recalcShapeAndBoundingRect(); return; } QVector lines; QVector pointsErrorBarAnchorX; QVector pointsErrorBarAnchorY; float errorPlus, errorMinus; for (int i = 0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); int index = validPointsIndicesLogical.at(i); //error bars for x if (xErrorType != XYCurve::NoError) { //determine the values for the errors if (xErrorPlusColumn && xErrorPlusColumn->isValid(index) && !xErrorPlusColumn->isMasked(index)) errorPlus = xErrorPlusColumn->valueAt(index); else errorPlus = 0; if (xErrorType == XYCurve::SymmetricError) errorMinus = errorPlus; else { if (xErrorMinusColumn && xErrorMinusColumn->isValid(index) && !xErrorMinusColumn->isMasked(index)) errorMinus = xErrorMinusColumn->valueAt(index); else errorMinus = 0; } //draw the error bars if (errorMinus != 0 || errorPlus !=0) lines.append(QLineF(QPointF(point.x()-errorMinus, point.y()), QPointF(point.x()+errorPlus, point.y()))); //determine the end points of the errors bars in logical coordinates to draw later the cap if (errorBarsType == XYCurve::ErrorBarsWithEnds) { pointsErrorBarAnchorX << QPointF(point.x() - errorMinus, point.y()); pointsErrorBarAnchorX << QPointF(point.x() + errorPlus, point.y()); } } //error bars for y if (yErrorType != XYCurve::NoError) { //determine the values for the errors if (yErrorPlusColumn && yErrorPlusColumn->isValid(index) && !yErrorPlusColumn->isMasked(index)) errorPlus = yErrorPlusColumn->valueAt(index); else errorPlus = 0; if (yErrorType == XYCurve::SymmetricError) errorMinus = errorPlus; else { if (yErrorMinusColumn && yErrorMinusColumn->isValid(index) && !yErrorMinusColumn->isMasked(index) ) errorMinus = yErrorMinusColumn->valueAt(index); else errorMinus = 0; } //draw the error bars if (errorMinus != 0 || errorPlus !=0) lines.append(QLineF(QPointF(point.x(), point.y() + errorMinus), QPointF(point.x(), point.y() - errorPlus))); //determine the end points of the errors bars in logical coordinates to draw later the cap if (errorBarsType == XYCurve::ErrorBarsWithEnds) { pointsErrorBarAnchorY << QPointF(point.x(), point.y() + errorMinus); pointsErrorBarAnchorY << QPointF(point.x(), point.y() - errorPlus); } } } //map the error bars to scene coordinates lines = cSystem->mapLogicalToScene(lines); //new painter path for the error bars for (const auto& line : lines) { errorBarsPath.moveTo(line.p1()); errorBarsPath.lineTo(line.p2()); } //add caps for x error bars if (!pointsErrorBarAnchorX.isEmpty()) { pointsErrorBarAnchorX = cSystem->mapLogicalToScene(pointsErrorBarAnchorX); for (const auto& point : pointsErrorBarAnchorX) { errorBarsPath.moveTo(QPointF(point.x(), point.y() - errorBarsCapSize/2)); errorBarsPath.lineTo(QPointF(point.x(), point.y() + errorBarsCapSize/2)); } } //add caps for y error bars if (!pointsErrorBarAnchorY.isEmpty()) { pointsErrorBarAnchorY = cSystem->mapLogicalToScene(pointsErrorBarAnchorY); for (const auto& point : pointsErrorBarAnchorY) { errorBarsPath.moveTo(QPointF(point.x() - errorBarsCapSize/2, point.y())); errorBarsPath.lineTo(QPointF(point.x() + errorBarsCapSize/2, point.y())); } } recalcShapeAndBoundingRect(); } /*! recalculates the outer bounds and the shape of the curve. */ void XYCurvePrivate::recalcShapeAndBoundingRect() { DEBUG("XYCurvePrivate::recalcShapeAndBoundingRect() m_suppressRecalc = " << m_suppressRecalc); if (m_suppressRecalc) return; #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::recalcShapeAndBoundingRect()"); #endif prepareGeometryChange(); curveShape = QPainterPath(); if (lineType != XYCurve::NoLine) curveShape.addPath(WorksheetElement::shapeFromPath(linePath, linePen)); if (dropLineType != XYCurve::NoDropLine) curveShape.addPath(WorksheetElement::shapeFromPath(dropLinePath, dropLinePen)); if (symbolsStyle != Symbol::NoSymbols) curveShape.addPath(symbolsPath); if (valuesType != XYCurve::NoValues) curveShape.addPath(valuesPath); if (xErrorType != XYCurve::NoError || yErrorType != XYCurve::NoError) curveShape.addPath(WorksheetElement::shapeFromPath(errorBarsPath, errorBarsPen)); boundingRectangle = curveShape.boundingRect(); for (const auto& pol : fillPolygons) boundingRectangle = boundingRectangle.united(pol.boundingRect()); //TODO: when the selection is painted, line intersections are visible. //simplified() removes those artifacts but is horrible slow for curves with large number of points. //search for an alternative. //curveShape = curveShape.simplified(); updatePixmap(); } void XYCurvePrivate::draw(QPainter* painter) { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::draw()"); #endif //draw filling if (fillingPosition != XYCurve::NoFilling) { painter->setOpacity(fillingOpacity); painter->setPen(Qt::SolidLine); drawFilling(painter); } //draw lines if (lineType != XYCurve::NoLine) { painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::NoBrush); painter->drawPath(linePath); } //draw drop lines if (dropLineType != XYCurve::NoDropLine) { painter->setOpacity(dropLineOpacity); painter->setPen(dropLinePen); painter->setBrush(Qt::NoBrush); painter->drawPath(dropLinePath); } //draw error bars if ( (xErrorType != XYCurve::NoError) || (yErrorType != XYCurve::NoError) ) { painter->setOpacity(errorBarsOpacity); painter->setPen(errorBarsPen); painter->setBrush(Qt::NoBrush); painter->drawPath(errorBarsPath); } //draw symbols if (symbolsStyle != Symbol::NoSymbols) { painter->setOpacity(symbolsOpacity); painter->setPen(symbolsPen); painter->setBrush(symbolsBrush); drawSymbols(painter); } //draw values if (valuesType != XYCurve::NoValues) { painter->setOpacity(valuesOpacity); //don't use any painter pen, since this will force QPainter to render the text outline which is expensive painter->setPen(Qt::NoPen); painter->setBrush(valuesColor); drawValues(painter); } } void XYCurvePrivate::updatePixmap() { DEBUG("XYCurvePrivate::updatePixmap() m_suppressRecalc = " << m_suppressRecalc); if (m_suppressRecalc) return; WAIT_CURSOR; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; if (boundingRectangle.width() == 0 || boundingRectangle.height() == 0) { DEBUG(" boundingRectangle.width() or boundingRectangle.height() == 0"); m_pixmap = QPixmap(); RESET_CURSOR; return; } QPixmap pixmap(ceil(boundingRectangle.width()), ceil(boundingRectangle.height())); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, true); painter.translate(-boundingRectangle.topLeft()); draw(&painter); painter.end(); m_pixmap = pixmap; update(); RESET_CURSOR; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the curve. \sa QGraphicsItem::paint(). */ void XYCurvePrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->setPen(Qt::NoPen); painter->setBrush(Qt::NoBrush); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if ( KSharedConfig::openConfig()->group("Settings_Worksheet").readEntry("DoubleBuffering", true) ) painter->drawPixmap(boundingRectangle.topLeft(), m_pixmap); //draw the cached pixmap (fast) else draw(painter); //draw directly again (slow) if (m_hovered && !isSelected() && !m_printing) { if (m_hoverEffectImageIsDirty) { QPixmap pix = m_pixmap; QPainter p(&pix); p.setCompositionMode(QPainter::CompositionMode_SourceIn); // source (shadow) pixels merged with the alpha channel of the destination (m_pixmap) p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Shadow)); p.end(); m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_hoverEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect()); return; } if (isSelected() && !m_printing) { if (m_selectionEffectImageIsDirty) { QPixmap pix = m_pixmap; QPainter p(&pix); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Highlight)); p.end(); m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_selectionEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect()); } } /*! Drawing of symbolsPath is very slow, so we draw every symbol in the loop which is much faster (factor 10) */ void XYCurvePrivate::drawSymbols(QPainter* painter) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(-symbolsRotationAngle); path = trafo.map(path); } for (const auto& point : symbolPointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); painter->drawPath(trafo.map(path)); } } void XYCurvePrivate::drawValues(QPainter* painter) { QTransform trafo; QPainterPath path; for (int i = 0; i < valuesPoints.size(); i++) { path = QPainterPath(); path.addText( QPoint(0,0), valuesFont, valuesStrings.at(i) ); trafo.reset(); trafo.translate( valuesPoints.at(i).x(), valuesPoints.at(i).y() ); if (valuesRotationAngle != 0) trafo.rotate( -valuesRotationAngle ); painter->drawPath(trafo.map(path)); } } void XYCurvePrivate::drawFilling(QPainter* painter) { for (const auto& pol : fillPolygons) { QRectF rect = pol.boundingRect(); if (fillingType == PlotArea::Color) { switch (fillingColorStyle) { case PlotArea::SingleColor: { painter->setBrush(QBrush(fillingFirstColor)); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, fillingFirstColor); radialGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (fillingType == PlotArea::Image) { if ( !fillingFileName.trimmed().isEmpty() ) { QPixmap pix(fillingFileName); switch (fillingImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::Centered: { QPixmap backpix(rect.size().toSize()); backpix.fill(); QPainter p(&backpix); p.drawPixmap(QPointF(0, 0), pix); p.end(); painter->setBrush(QBrush(backpix)); painter->setBrushOrigin(-pix.size().width()/2, -pix.size().height()/2); break; } case PlotArea::Tiled: painter->setBrush(QBrush(pix)); break; case PlotArea::CenterTiled: painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); } } } else if (fillingType == PlotArea::Pattern) painter->setBrush(QBrush(fillingFirstColor, fillingBrushStyle)); painter->drawPolygon(pol); } } void XYCurvePrivate::setPrinting(bool on) { m_printing = on; } void XYCurvePrivate::suppressRetransform(bool on) { m_suppressRetransform = on; m_suppressRecalc = on; } /*! * \brief XYCurvePrivate::mousePressEvent * checks with activateCurve, if the mousePress was in the near * of the curve. If it was, the curve will be selected * \p event */ void XYCurvePrivate::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (plot->mouseMode() != CartesianPlot::MouseMode::SelectionMode) { event->ignore(); return QGraphicsItem::mousePressEvent(event); } if(q->activateCurve(event->pos())){ setSelected(true); return; } event->ignore(); setSelected(false); QGraphicsItem::mousePressEvent(event); } /*! * \brief XYCurvePrivate::setHover * Will be called from CartesianPlot::hoverMoveEvent which * determines, which curve is hovered * \p on */ void XYCurvePrivate::setHover(bool on) { if(on == m_hovered) return; // don't update if state not changed m_hovered = on; on ? emit q->hovered() : emit q->unhovered(); update(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYCurve); writer->writeStartElement( "xyCurve" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); WRITE_COLUMN(d->xColumn, xColumn); WRITE_COLUMN(d->yColumn, yColumn); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Line writer->writeStartElement( "lines" ); writer->writeAttribute( "type", QString::number(d->lineType) ); writer->writeAttribute( "skipGaps", QString::number(d->lineSkipGaps) ); writer->writeAttribute( "increasingXOnly", QString::number(d->lineIncreasingXOnly) ); writer->writeAttribute( "interpolationPointsCount", QString::number(d->lineInterpolationPointsCount) ); WRITE_QPEN(d->linePen); writer->writeAttribute( "opacity", QString::number(d->lineOpacity) ); writer->writeEndElement(); //Drop lines writer->writeStartElement( "dropLines" ); writer->writeAttribute( "type", QString::number(d->dropLineType) ); WRITE_QPEN(d->dropLinePen); writer->writeAttribute( "opacity", QString::number(d->dropLineOpacity) ); writer->writeEndElement(); //Symbols writer->writeStartElement( "symbols" ); writer->writeAttribute( "symbolsStyle", QString::number(d->symbolsStyle) ); writer->writeAttribute( "opacity", QString::number(d->symbolsOpacity) ); writer->writeAttribute( "rotation", QString::number(d->symbolsRotationAngle) ); writer->writeAttribute( "size", QString::number(d->symbolsSize) ); WRITE_QBRUSH(d->symbolsBrush); WRITE_QPEN(d->symbolsPen); writer->writeEndElement(); //Values writer->writeStartElement( "values" ); writer->writeAttribute( "type", QString::number(d->valuesType) ); WRITE_COLUMN(d->valuesColumn, valuesColumn); writer->writeAttribute( "position", QString::number(d->valuesPosition) ); writer->writeAttribute( "distance", QString::number(d->valuesDistance) ); writer->writeAttribute( "rotation", QString::number(d->valuesRotationAngle) ); writer->writeAttribute( "opacity", QString::number(d->valuesOpacity) ); //TODO values format and precision writer->writeAttribute( "prefix", d->valuesPrefix ); writer->writeAttribute( "suffix", d->valuesSuffix ); WRITE_QCOLOR(d->valuesColor); WRITE_QFONT(d->valuesFont); writer->writeEndElement(); //Filling writer->writeStartElement( "filling" ); writer->writeAttribute( "position", QString::number(d->fillingPosition) ); writer->writeAttribute( "type", QString::number(d->fillingType) ); writer->writeAttribute( "colorStyle", QString::number(d->fillingColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->fillingImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->fillingBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->fillingFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->fillingFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->fillingFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->fillingSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->fillingSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->fillingSecondColor.blue()) ); writer->writeAttribute( "fileName", d->fillingFileName ); writer->writeAttribute( "opacity", QString::number(d->fillingOpacity) ); writer->writeEndElement(); //Error bars writer->writeStartElement( "errorBars" ); writer->writeAttribute( "xErrorType", QString::number(d->xErrorType) ); WRITE_COLUMN(d->xErrorPlusColumn, xErrorPlusColumn); WRITE_COLUMN(d->xErrorMinusColumn, xErrorMinusColumn); writer->writeAttribute( "yErrorType", QString::number(d->yErrorType) ); WRITE_COLUMN(d->yErrorPlusColumn, yErrorPlusColumn); WRITE_COLUMN(d->yErrorMinusColumn, yErrorMinusColumn); writer->writeAttribute( "type", QString::number(d->errorBarsType) ); writer->writeAttribute( "capSize", QString::number(d->errorBarsCapSize) ); WRITE_QPEN(d->errorBarsPen); writer->writeAttribute( "opacity", QString::number(d->errorBarsOpacity) ); writer->writeEndElement(); writer->writeEndElement(); //close "xyCurve" section } //! Load from XML bool XYCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYCurve); if (!readBasicAttributes(reader)) return false; KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); READ_COLUMN(xColumn); READ_COLUMN(yColumn); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("visible").toString()); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "lines") { attribs = reader->attributes(); READ_INT_VALUE("type", lineType, XYCurve::LineType); READ_INT_VALUE("skipGaps", lineSkipGaps, bool); READ_INT_VALUE("increasingXOnly", lineIncreasingXOnly, bool); READ_INT_VALUE("interpolationPointsCount", lineInterpolationPointsCount, int); READ_QPEN(d->linePen); READ_DOUBLE_VALUE("opacity", lineOpacity); } else if (!preview && reader->name() == "dropLines") { attribs = reader->attributes(); READ_INT_VALUE("type", dropLineType, XYCurve::DropLineType); READ_QPEN(d->dropLinePen); READ_DOUBLE_VALUE("opacity", dropLineOpacity); } else if (!preview && reader->name() == "symbols") { attribs = reader->attributes(); READ_INT_VALUE("symbolsStyle", symbolsStyle, Symbol::Style); READ_DOUBLE_VALUE("opacity", symbolsOpacity); READ_DOUBLE_VALUE("rotation", symbolsRotationAngle); READ_DOUBLE_VALUE("size", symbolsSize); READ_QBRUSH(d->symbolsBrush); READ_QPEN(d->symbolsPen); } else if (!preview && reader->name() == "values") { attribs = reader->attributes(); READ_INT_VALUE("type", valuesType, XYCurve::ValuesType); READ_COLUMN(valuesColumn); READ_INT_VALUE("position", valuesPosition, XYCurve::ValuesPosition); READ_DOUBLE_VALUE("distance", valuesDistance); READ_DOUBLE_VALUE("rotation", valuesRotationAngle); READ_DOUBLE_VALUE("opacity", valuesOpacity); //don't produce any warning if no prefix or suffix is set (empty string is allowed here in xml) d->valuesPrefix = attribs.value("prefix").toString(); d->valuesSuffix = attribs.value("suffix").toString(); READ_QCOLOR(d->valuesColor); READ_QFONT(d->valuesFont); } else if (!preview && reader->name() == "filling") { attribs = reader->attributes(); READ_INT_VALUE("position", fillingPosition, XYCurve::FillingPosition); READ_INT_VALUE("type", fillingType, PlotArea::BackgroundType); READ_INT_VALUE("colorStyle", fillingColorStyle, PlotArea::BackgroundColorStyle); READ_INT_VALUE("imageStyle", fillingImageStyle, PlotArea::BackgroundImageStyle ); READ_INT_VALUE("brushStyle", fillingBrushStyle, Qt::BrushStyle); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_r").toString()); else d->fillingFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_g").toString()); else d->fillingFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("firstColor_b").toString()); else d->fillingFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_r").toString()); else d->fillingSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_g").toString()); else d->fillingSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.subs("secondColor_b").toString()); else d->fillingSecondColor.setBlue(str.toInt()); READ_STRING_VALUE("fileName", fillingFileName); READ_DOUBLE_VALUE("opacity", fillingOpacity); } else if (!preview && reader->name() == "errorBars") { attribs = reader->attributes(); READ_INT_VALUE("xErrorType", xErrorType, XYCurve::ErrorType); READ_COLUMN(xErrorPlusColumn); READ_COLUMN(xErrorMinusColumn); READ_INT_VALUE("yErrorType", yErrorType, XYCurve::ErrorType); READ_COLUMN(yErrorPlusColumn); READ_COLUMN(yErrorMinusColumn); READ_INT_VALUE("type", errorBarsType, XYCurve::ErrorBarsType); READ_DOUBLE_VALUE("capSize", errorBarsCapSize); READ_QPEN(d->errorBarsPen); READ_DOUBLE_VALUE("opacity", errorBarsOpacity); } } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void XYCurve::loadThemeConfig(const KConfig& config) { KConfigGroup group = config.group("XYCurve"); int index = parentAspect()->indexOfChild(this); const auto* plot = dynamic_cast(parentAspect()); QColor themeColor; if (indexthemeColorPalette().size()) themeColor = plot->themeColorPalette().at(index); else { if (plot->themeColorPalette().size()) themeColor = plot->themeColorPalette().last(); } QPen p; Q_D(XYCurve); d->m_suppressRecalc = true; //Line p.setStyle((Qt::PenStyle)group.readEntry("LineStyle", (int)this->linePen().style())); p.setWidthF(group.readEntry("LineWidth", this->linePen().widthF())); p.setColor(themeColor); this->setLinePen(p); this->setLineOpacity(group.readEntry("LineOpacity", this->lineOpacity())); //Drop line p.setStyle((Qt::PenStyle)group.readEntry("DropLineStyle",(int) this->dropLinePen().style())); p.setWidthF(group.readEntry("DropLineWidth", this->dropLinePen().widthF())); p.setColor(themeColor); this->setDropLinePen(p); this->setDropLineOpacity(group.readEntry("DropLineOpacity", this->dropLineOpacity())); //Symbol this->setSymbolsOpacity(group.readEntry("SymbolOpacity", this->symbolsOpacity())); QBrush brush = symbolsBrush(); brush.setColor(themeColor); this->setSymbolsBrush(brush); p = symbolsPen(); p.setColor(themeColor); this->setSymbolsPen(p); //Values this->setValuesOpacity(group.readEntry("ValuesOpacity", this->valuesOpacity())); this->setValuesColor(group.readEntry("ValuesColor", this->valuesColor())); //Filling this->setFillingBrushStyle((Qt::BrushStyle)group.readEntry("FillingBrushStyle",(int) this->fillingBrushStyle())); this->setFillingColorStyle((PlotArea::BackgroundColorStyle)group.readEntry("FillingColorStyle",(int) this->fillingColorStyle())); this->setFillingOpacity(group.readEntry("FillingOpacity", this->fillingOpacity())); this->setFillingPosition((XYCurve::FillingPosition)group.readEntry("FillingPosition",(int) this->fillingPosition())); this->setFillingSecondColor(group.readEntry("FillingSecondColor",(QColor) this->fillingSecondColor())); this->setFillingFirstColor(themeColor); this->setFillingType((PlotArea::BackgroundType)group.readEntry("FillingType",(int) this->fillingType())); //Error Bars p.setStyle((Qt::PenStyle)group.readEntry("ErrorBarsStyle",(int) this->errorBarsPen().style())); p.setWidthF(group.readEntry("ErrorBarsWidth", this->errorBarsPen().widthF())); p.setColor(themeColor); this->setErrorBarsPen(p); this->setErrorBarsOpacity(group.readEntry("ErrorBarsOpacity",this->errorBarsOpacity())); d->m_suppressRecalc = false; d->recalcShapeAndBoundingRect(); } void XYCurve::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("XYCurve"); //Drop line group.writeEntry("DropLineColor",(QColor) this->dropLinePen().color()); group.writeEntry("DropLineStyle",(int) this->dropLinePen().style()); group.writeEntry("DropLineWidth", this->dropLinePen().widthF()); group.writeEntry("DropLineOpacity",this->dropLineOpacity()); //Error Bars group.writeEntry("ErrorBarsCapSize",this->errorBarsCapSize()); group.writeEntry("ErrorBarsOpacity",this->errorBarsOpacity()); group.writeEntry("ErrorBarsColor",(QColor) this->errorBarsPen().color()); group.writeEntry("ErrorBarsStyle",(int) this->errorBarsPen().style()); group.writeEntry("ErrorBarsWidth", this->errorBarsPen().widthF()); //Filling group.writeEntry("FillingBrushStyle",(int) this->fillingBrushStyle()); group.writeEntry("FillingColorStyle",(int) this->fillingColorStyle()); group.writeEntry("FillingOpacity", this->fillingOpacity()); group.writeEntry("FillingPosition",(int) this->fillingPosition()); group.writeEntry("FillingSecondColor",(QColor) this->fillingSecondColor()); group.writeEntry("FillingType",(int) this->fillingType()); //Line group.writeEntry("LineOpacity", this->lineOpacity()); group.writeEntry("LineStyle",(int) this->linePen().style()); group.writeEntry("LineWidth", this->linePen().widthF()); //Symbol group.writeEntry("SymbolOpacity", this->symbolsOpacity()); //Values group.writeEntry("ValuesOpacity", this->valuesOpacity()); group.writeEntry("ValuesColor", (QColor) this->valuesColor()); group.writeEntry("ValuesFont", this->valuesFont()); int index = parentAspect()->indexOfChild(this); if (index < 5) { KConfigGroup themeGroup = config.group("Theme"); for (int i = index; i<5; i++) { QString s = "ThemePaletteColor" + QString::number(i+1); themeGroup.writeEntry(s,(QColor) this->linePen().color()); } } } diff --git a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp index c19662572..d2f465059 100644 --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp @@ -1,2406 +1,2406 @@ /*************************************************************************** File : XYFitCurve.cpp Project : LabPlot Description : A xy-curve defined by a fit model -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFitCurve \brief A xy-curve defined by a fit model \ingroup worksheet */ #include "XYFitCurve.h" #include "XYFitCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" #include "backend/gsl/ExpressionParser.h" extern "C" { #include #include #include #include #include #include #include "backend/gsl/parser.h" #include "backend/nsl/nsl_sf_stats.h" #include "backend/nsl/nsl_stats.h" } #include #include #include XYFitCurve::XYFitCurve(const QString& name) : XYAnalysisCurve(name, new XYFitCurvePrivate(this), AspectType::XYFitCurve) { } XYFitCurve::XYFitCurve(const QString& name, XYFitCurvePrivate* dd) : XYAnalysisCurve(name, dd, AspectType::XYFitCurve) { } //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene XYFitCurve::~XYFitCurve() = default; void XYFitCurve::recalculate() { Q_D(XYFitCurve); d->recalculate(); } void XYFitCurve::evaluate(bool preview) { Q_D(XYFitCurve); d->evaluate(preview); } void XYFitCurve::initStartValues(const XYCurve* curve) { Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; initStartValues(fitData, curve); } void XYFitCurve::initStartValues(XYFitCurve::FitData& fitData, const XYCurve* curve) { DEBUG("XYFitCurve::initStartValues()"); if (!curve) { DEBUG(" no curve given"); return; } const Column* tmpXDataColumn = dynamic_cast(curve->xColumn()); const Column* tmpYDataColumn = dynamic_cast(curve->yColumn()); if (!tmpXDataColumn || !tmpYDataColumn) { DEBUG(" data columns not available"); return; } DEBUG(" x data rows = " << tmpXDataColumn->rowCount()); nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; int degree = fitData.degree; DEBUG(" fit model type = " << modelType << ", degree = " << degree); QVector& paramStartValues = fitData.paramStartValues; //QVector* xVector = static_cast* >(tmpXDataColumn->data()); //double xmean = gsl_stats_mean(xVector->constData(), 1, tmpXDataColumn->rowCount()); double xmin = tmpXDataColumn->minimum(); double xmax = tmpXDataColumn->maximum(); //double ymin = tmpYDataColumn->minimum(); //double ymax = tmpYDataColumn->maximum(); double xrange = xmax-xmin; //double yrange = ymax-ymin; DEBUG(" x min/max = " << xmin << ' ' << xmax); //DEBUG(" y min/max = " << ymin << ' ' << ymax); switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // not needed (works anyway) break; //TODO: handle basic models case nsl_fit_model_power: case nsl_fit_model_exponential: case nsl_fit_model_inverse_exponential: case nsl_fit_model_fourier: break; } break; case nsl_fit_model_peak: // use equidistant mu's and (xmax-xmin)/(10*degree) as sigma(, gamma) switch (modelType) { case nsl_fit_model_gaussian: case nsl_fit_model_lorentz: case nsl_fit_model_sech: case nsl_fit_model_logistic: for (int d = 0; d < degree; d++) { paramStartValues[3*d+2] = xmin + (d+1.)*xrange/(degree+1.); // mu paramStartValues[3*d+1] = xrange/(10.*degree); // sigma } break; case nsl_fit_model_voigt: for (int d = 0; d < degree; d++) { paramStartValues[4*d+1] = xmin + (d+1.)*xrange/(degree+1.); // mu paramStartValues[4*d+2] = xrange/(10.*degree); // sigma paramStartValues[4*d+3] = xrange/(10.*degree); // gamma } break; case nsl_fit_model_pseudovoigt1: for (int d = 0; d < degree; d++) { paramStartValues[4*d+1] = 0.5; // eta paramStartValues[4*d+2] = xrange/(10.*degree); // sigma paramStartValues[4*d+3] = xmin + (d+1.)*xrange/(degree+1.); // mu } break; } break; case nsl_fit_model_growth: switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: case nsl_fit_model_sigmoid: // use (xmax+xmin)/2 as mu and (xmax-xmin)/10 as sigma paramStartValues[1] = (xmax+xmin)/2.; paramStartValues[2] = xrange/10.; break; case nsl_fit_model_hill: paramStartValues[2] = xrange/10.; break; case nsl_fit_model_gompertz: //TODO break; } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: // use (xmax+xmin)/2 as mu and (xmax-xmin)/10 as sigma paramStartValues[2] = (xmin+xmax)/2.; paramStartValues[1] = xrange/10.; break; //TODO: other types default: break; } break; case nsl_fit_model_custom: // not possible break; } } /*! * sets the parameter names for given model category, model type and degree in \c fitData for given action */ void XYFitCurve::initFitData(PlotDataDialog::AnalysisAction action) { if (!action) return; Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; if (action == PlotDataDialog::FitLinear) { //Linear fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_polynomial; fitData.degree = 1; } else if (action == PlotDataDialog::FitPower) { //Power fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_power; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp1) { //Exponential (degree 1) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_exponential; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp2) { //Exponential (degree 2) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_exponential; fitData.degree = 2; } else if (action == PlotDataDialog::FitInvExp) { //Inverse exponential fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_inverse_exponential; } else if (action == PlotDataDialog::FitGauss) { //Gauss fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = (int)nsl_fit_model_gaussian; fitData.degree = 1; } else if (action == PlotDataDialog::FitCauchyLorentz) { //Cauchy-Lorentz fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = (int)nsl_fit_model_lorentz; fitData.degree = 1; } else if (action == PlotDataDialog::FitTan) { //Arc tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_atan; } else if (action == PlotDataDialog::FitTanh) { //Hyperbolic tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_tanh; } else if (action == PlotDataDialog::FitErrFunc) { //Error function fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_erf; } else { //Custom fitData.modelCategory = nsl_fit_model_custom; fitData.modelType = 0; } XYFitCurve::initFitData(fitData); } /*! * sets the model expression and the parameter names for given model category, model type and degree in \c fitData */ void XYFitCurve::initFitData(XYFitCurve::FitData& fitData) { nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; QString& model = fitData.model; QStringList& paramNames = fitData.paramNames; QStringList& paramNamesUtf8 = fitData.paramNamesUtf8; int degree = fitData.degree; QVector& paramStartValues = fitData.paramStartValues; QVector& paramLowerLimits = fitData.paramLowerLimits; QVector& paramUpperLimits = fitData.paramUpperLimits; QVector& paramFixed = fitData.paramFixed; if (modelCategory != nsl_fit_model_custom) { DEBUG("XYFitCurve::initFitData() for model category = " << nsl_fit_model_category_name[modelCategory] << ", model type = " << modelType << ", degree = " << degree); paramNames.clear(); } else { DEBUG("XYFitCurve::initFitData() for model category = nsl_fit_model_custom, model type = " << modelType << ", degree = " << degree); } paramNamesUtf8.clear(); // 10 indices used in multi degree models QStringList indices = { UTF8_QSTRING("₁"), UTF8_QSTRING("₂"), UTF8_QSTRING("₃"), UTF8_QSTRING("₄"), UTF8_QSTRING("₅"), UTF8_QSTRING("₆"), UTF8_QSTRING("₇"), UTF8_QSTRING("₈"), UTF8_QSTRING("₉"), UTF8_QSTRING("₁₀")}; switch (modelCategory) { case nsl_fit_model_basic: model = nsl_fit_model_basic_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_polynomial: paramNames << "c0" << "c1"; paramNamesUtf8 << UTF8_QSTRING("c₀") << UTF8_QSTRING("c₁"); if (degree == 2) { model += " + c2*x^2"; paramNames << "c2"; paramNamesUtf8 << UTF8_QSTRING("c₂"); } else if (degree > 2) { for (int i = 2; i <= degree; ++i) { QString numStr = QString::number(i); model += "+c" + numStr + "*x^" + numStr; paramNames << 'c' + numStr; paramNamesUtf8 << 'c' + indices[i-1]; } } break; case nsl_fit_model_power: if (degree == 1) { paramNames << "a" << "b"; } else { paramNames << "a" << "b" << "c"; model = "a + b*x^c"; } break; case nsl_fit_model_exponential: if (degree == 1) { paramNames << "a" << "b"; } else { for (int i = 1; i <= degree; i++) { QString numStr = QString::number(i); if (i == 1) model = "a1*exp(b1*x)"; else model += " + a" + numStr + "*exp(b" + numStr + "*x)"; paramNames << 'a' + numStr << 'b' + numStr; paramNamesUtf8 << 'a' + indices[i-1] << 'b' + indices[i-1]; } } break; case nsl_fit_model_inverse_exponential: paramNames << "a" << "b" << "c"; break; case nsl_fit_model_fourier: paramNames << "w" << "a0" << "a1" << "b1"; paramNamesUtf8 << UTF8_QSTRING("ω") << UTF8_QSTRING("a₀") << UTF8_QSTRING("a₁") << UTF8_QSTRING("b₁"); if (degree > 1) { for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); model += "+ (a" + numStr + "*cos(" + numStr + "*w*x) + b" + numStr + "*sin(" + numStr + "*w*x))"; paramNames << 'a' + numStr << 'b' + numStr; paramNamesUtf8 << 'a' + indices[i-1] << 'b' + indices[i-1]; } } break; } break; case nsl_fit_model_peak: model = nsl_fit_model_peak_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_gaussian: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1./sqrt(2*pi) * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* exp(-((x-mu" + numStr + ")/s" + numStr + ")^2/2)"; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_lorentz: switch (degree) { case 1: paramNames << "a" << "g" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("μ"); break; default: model = "1./pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + " * g" + numStr + "/(g" + numStr + "^2+(x-mu" + numStr + ")^2)"; paramNames << 'a' + numStr << 'g' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("γ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_sech: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1/pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/s" + numStr + ')'; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_logistic: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1/4 * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/2/s" + numStr + ")**2"; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_voigt: switch (degree) { case 1: paramNames << "a" << "mu" << "s" << "g"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << UTF8_QSTRING("σ") << UTF8_QSTRING("γ"); break; default: model.clear(); for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "*voigt(x-mu" + numStr + ",s" + numStr + ",g" + numStr + ')'; paramNames << 'a' + numStr << "mu" + numStr << 's' + numStr << 'g' + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("γ") + indices[i-1]; } } break; case nsl_fit_model_pseudovoigt1: switch (degree) { case 1: paramNames << "a" << "et" << "w" << "mu"; // eta function exists! paramNamesUtf8 << "A" << UTF8_QSTRING("η") << "w" << UTF8_QSTRING("μ"); break; default: model.clear(); for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "*pseudovoigt1(x-mu" + numStr + ",eta" + numStr + ",w" + numStr + ')'; paramNames << 'a' + numStr << "eta" + numStr << 'w' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("η") + indices[i-1] << 'w' + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } } break; } break; case nsl_fit_model_growth: model = nsl_fit_model_growth_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: paramNames << "a" << "mu" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << UTF8_QSTRING("σ"); break; case nsl_fit_model_sigmoid: paramNames << "a" << "mu" << "k"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << "k"; break; case nsl_fit_model_hill: paramNames << "a" << "n" << "a"; paramNamesUtf8 << "A" << "n" << UTF8_QSTRING("σ"); break; case nsl_fit_model_gompertz: paramNames << "a" << "b" << "c"; break; } break; case nsl_fit_model_distribution: model = nsl_sf_stats_distribution_equation[fitData.modelType]; switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_gaussian_tail: paramNames << "A" << "s" << "a" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << "a" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_exponential: paramNames << "a" << "l" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("λ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_exponential_power: paramNames << "a" << "s" << "b" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << "b" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: paramNames << "a" << "g" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_rayleigh: paramNames << "a" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ"); break; case nsl_sf_stats_landau: paramNames << "a"; paramNamesUtf8 << "A"; break; case nsl_sf_stats_levy_alpha_stable: // unused distributions case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; case nsl_sf_stats_gamma: paramNames << "a" << "k" << "t"; paramNamesUtf8 << "A"<< "k" << UTF8_QSTRING("θ"); break; case nsl_sf_stats_flat: paramNames << "A" << "b" << "a"; break; case nsl_sf_stats_chi_squared: paramNames << "a" << "n"; paramNamesUtf8 << "A" << "n"; break; case nsl_sf_stats_fdist: paramNames << "a" << "n1" << "n2"; paramNamesUtf8 << "A" << UTF8_QSTRING("ν₁") << UTF8_QSTRING("ν₂"); break; case nsl_sf_stats_tdist: paramNames << "a" << "n"; paramNamesUtf8 << "A" << UTF8_QSTRING("ν"); break; case nsl_sf_stats_beta: case nsl_sf_stats_pareto: paramNames << "A" << "a" << "b"; break; case nsl_sf_stats_weibull: paramNames << "a" << "k" << "l" << "mu"; paramNamesUtf8 << "A" << "k" << UTF8_QSTRING("λ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_gumbel1: paramNames << "a" << "s" << "mu" << "b"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << UTF8_QSTRING("β"); break; case nsl_sf_stats_gumbel2: paramNames << "A" << "a" << "b" << "mu"; paramNamesUtf8 << "A" << "a" << "b" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_poisson: paramNames << "a" << "l"; paramNamesUtf8 << "A" << UTF8_QSTRING("λ"); break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: paramNames << "a" << "p" << "n"; paramNamesUtf8 << "A" << "p" << "n"; break; case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: paramNames << "a" << "p"; paramNamesUtf8 << "A" << "p"; break; case nsl_sf_stats_hypergeometric: paramNames << "a" << "n1" << "n2" << "t"; paramNamesUtf8 << "A" << UTF8_QSTRING("n₁") << UTF8_QSTRING("n₂") << "t"; break; case nsl_sf_stats_maxwell_boltzmann: paramNames << "a" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ"); break; case nsl_sf_stats_frechet: paramNames << "a" << "g" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; } break; case nsl_fit_model_custom: break; } - DEBUG("model: " << model.toStdString()); + DEBUG("model: " << STDSTRING(model)); if (paramNamesUtf8.isEmpty()) paramNamesUtf8 << paramNames; //resize the vector for the start values and set the elements to 1.0 //in case a custom model is used, do nothing, we take over the previous values if (modelCategory != nsl_fit_model_custom) { const int np = paramNames.size(); paramStartValues.resize(np); paramFixed.resize(np); paramLowerLimits.resize(np); paramUpperLimits.resize(np); for (int i = 0; i < np; ++i) { paramStartValues[i] = 1.0; paramFixed[i] = false; paramLowerLimits[i] = -std::numeric_limits::max(); paramUpperLimits[i] = std::numeric_limits::max(); } // set some model-dependent start values // TODO: see initStartValues() if (modelCategory == nsl_fit_model_distribution) { if (modelType == (int)nsl_sf_stats_flat) paramStartValues[2] = -1.0; else if (modelType == (int)nsl_sf_stats_levy) paramStartValues[2] = 0.0; else if (modelType == (int)nsl_sf_stats_exponential_power || modelType == (int)nsl_sf_stats_weibull || modelType == (int)nsl_sf_stats_gumbel2 || modelType == (int)nsl_sf_stats_frechet) paramStartValues[3] = 0.0; else if (modelType == (int)nsl_sf_stats_binomial || modelType == (int)nsl_sf_stats_negative_binomial || modelType == (int)nsl_sf_stats_pascal || modelType == (int)nsl_sf_stats_geometric || modelType == (int)nsl_sf_stats_logarithmic) paramStartValues[1] = 0.5; } } } /*! Returns an icon to be used in the project explorer. */ QIcon XYFitCurve::icon() const { return QIcon::fromTheme("labplot-xy-fit-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xErrorColumn, xErrorColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yErrorColumn, yErrorColumn) const QString& XYFitCurve::xErrorColumnPath() const { Q_D(const XYFitCurve); return d->xErrorColumnPath; } const QString& XYFitCurve::yErrorColumnPath() const { Q_D(const XYFitCurve); return d->yErrorColumnPath; } BASIC_SHARED_D_READER_IMPL(XYFitCurve, XYFitCurve::FitData, fitData, fitData) const XYFitCurve::FitResult& XYFitCurve::fitResult() const { Q_D(const XYFitCurve); return d->fitResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYFitCurve, SetXErrorColumn, const AbstractColumn*, xErrorColumn) void XYFitCurve::setXErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xErrorColumn) { exec(new XYFitCurveSetXErrorColumnCmd(d, column, ki18n("%1: assign x-error"))); handleSourceDataChanged(); if (column) { connect(column, &AbstractColumn::dataChanged, this, [=](){ handleSourceDataChanged(); }); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetYErrorColumn, const AbstractColumn*, yErrorColumn) void XYFitCurve::setYErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yErrorColumn) { exec(new XYFitCurveSetYErrorColumnCmd(d, column, ki18n("%1: assign y-error"))); handleSourceDataChanged(); if (column) { connect(column, &AbstractColumn::dataChanged, this, [=](){ handleSourceDataChanged(); }); //TODO disconnect on undo } } } // do not recalculate (allow preview) //STD_SETTER_CMD_IMPL_F_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData, recalculate) STD_SETTER_CMD_IMPL_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData) void XYFitCurve::setFitData(const XYFitCurve::FitData& fitData) { Q_D(XYFitCurve); exec(new XYFitCurveSetFitDataCmd(d, fitData, ki18n("%1: set fit options and perform the fit"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFitCurvePrivate::XYFitCurvePrivate(XYFitCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) {} //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed XYFitCurvePrivate::~XYFitCurvePrivate() = default; // data structure to pass parameter to fit functions struct data { size_t n; //number of data points double* x; //pointer to the vector with x-data values double* y; //pointer to the vector with y-data values double* weight; //pointer to the vector with weight values nsl_fit_model_category modelCategory; int modelType; int degree; QString* func; // string containing the definition of the model/function QStringList* paramNames; double* paramMin; // lower parameter limits double* paramMax; // upper parameter limits bool* paramFixed; // parameter fixed? }; /*! * \param paramValues vector containing current values of the fit parameters * \param params * \param f vector with the weighted residuals weight[i]*(Yi - y[i]) */ int func_f(const gsl_vector* paramValues, void* params, gsl_vector* f) { //DEBUG("func_f"); size_t n = ((struct data*)params)->n; double* x = ((struct data*)params)->x; double* y = ((struct data*)params)->y; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; QByteArray funcba = ((struct data*)params)->func->toLatin1(); // a local byte array is needed! const char *func = funcba.constData(); // function to evaluate QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; // set current values of the parameters for (int i = 0; i < paramNames->size(); i++) { double v = gsl_vector_get(paramValues, (size_t)i); // bound values if limits are set QByteArray paramnameba = paramNames->at(i).toLatin1(); assign_variable(paramnameba.constData(), nsl_fit_map_bound(v, min[i], max[i])); QDEBUG("Parameter"<n; double* xVector = ((struct data*)params)->x; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; unsigned int degree = ((struct data*)params)->degree; QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; bool *fixed = ((struct data*)params)->paramFixed; // calculate the Jacobian matrix: // Jacobian matrix J(i,j) = df_i / dx_j // where f_i = w_i*(Y_i - y_i), // Y_i = model and the x_j are the parameters double x; switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // Y(x) = c0 + c1*x + ... + cn*x^n for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < (unsigned int)paramNames->size(); ++j) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_polynomial_param_deriv(x, j, weight[i])); } } break; case nsl_fit_model_power: // Y(x) = a*x^b or Y(x) = a + b*x^c. if (degree == 1) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power1_param_deriv(j, x, a, b, weight[i])); } } } else if (degree == 2) { const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double c = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power2_param_deriv(j, x, b, c, weight[i])); } } } break; case nsl_fit_model_exponential: { // Y(x) = a*exp(b*x) + c*exp(d*x) + ... double *p = new double[2*degree]; for (unsigned int i = 0; i < 2*degree; i++) p[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, i), min[i], max[i]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2*degree; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponentialn_param_deriv(j, x, p, weight[i])); } } delete[] p; break; } case nsl_fit_model_inverse_exponential: { // Y(x) = a*(1-exp(b*x))+c const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_inverse_exponential_param_deriv(j, x, a, b, weight[i])); } } break; } case nsl_fit_model_fourier: { // Y(x) = a0 + (a1*cos(w*x) + b1*sin(w*x)) + ... + (an*cos(n*w*x) + bn*sin(n*w*x) //parameters: w, a0, a1, b1, ... an, bn double* a = new double[degree]; double* b = new double[degree]; double w = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); a[0] = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); b[0] = 0; for (unsigned int i = 1; i < degree; ++i) { a[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i), min[2*i], max[2*i]); b[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i+1), min[2*i+1], max[2*i+1]); } for (size_t i = 0; i < n; i++) { x = xVector[i]; double wd = 0; //first derivative with respect to the w parameter for (unsigned int j = 1; j < degree; ++j) { wd += -a[j]*j*x*sin(j*w*x) + b[j]*j*x*cos(j*w*x); } gsl_matrix_set(J, i, 0, weight[i]*wd); gsl_matrix_set(J, i, 1, weight[i]); for (unsigned int j = 1; j <= degree; ++j) { gsl_matrix_set(J, (size_t)i, (size_t)(2*j), nsl_fit_model_fourier_param_deriv(0, j, x, w, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(2*j+1), nsl_fit_model_fourier_param_deriv(1, j, x, w, weight[i])); } for (unsigned int j = 0; j <= 2*degree+1; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } delete[] a; delete[] b; break; } } break; case nsl_fit_model_peak: switch (modelType) { case nsl_fit_model_gaussian: case nsl_fit_model_lorentz: case nsl_fit_model_sech: case nsl_fit_model_logistic: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j), min[3*j], max[3*j]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+1), min[3*j+1], max[3*j+1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+2), min[3*j+2], max[3*j+2]); switch (modelType) { case nsl_fit_model_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_gaussian_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_gaussian_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_gaussian_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_lorentz: // a,s,t gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_lorentz_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_lorentz_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_lorentz_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_sech: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_sech_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_sech_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_sech_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_logistic: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_logistic_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_logistic_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_logistic_param_deriv(2, x, a, s, mu, weight[i])); break; } } for (unsigned int j = 0; j < 3*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; case nsl_fit_model_voigt: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j), min[4*j], max[4*j]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+1), min[4*j+1], max[4*j+1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+2), min[4*j+2], max[4*j+2]); const double g = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+3), min[4*j+3], max[4*j+3]); gsl_matrix_set(J, (size_t)i, (size_t)(4*j), nsl_fit_model_voigt_param_deriv(0, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+1), nsl_fit_model_voigt_param_deriv(1, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+2), nsl_fit_model_voigt_param_deriv(2, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+3), nsl_fit_model_voigt_param_deriv(3, x, a, mu, s, g, weight[i])); } for (unsigned int j = 0; j < 4*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; case nsl_fit_model_pseudovoigt1: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j), min[4*j], max[4*j]); const double eta = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+1), min[4*j+1], max[4*j+1]); const double w = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+2), min[4*j+2], max[4*j+2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+3), min[4*j+3], max[4*j+3]); gsl_matrix_set(J, (size_t)i, (size_t)(4*j), nsl_fit_model_voigt_param_deriv(0, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+1), nsl_fit_model_voigt_param_deriv(1, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+2), nsl_fit_model_voigt_param_deriv(2, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+3), nsl_fit_model_voigt_param_deriv(3, x, a, eta, w, mu, weight[i])); } for (unsigned int j = 0; j < 4*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; } break; case nsl_fit_model_growth: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) { gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } else { switch (modelType) { case nsl_fit_model_atan: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_atan_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_tanh: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_tanh_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_algebraic_sigmoid: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_algebraic_sigmoid_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_sigmoid: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sigmoid_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_erf: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_erf_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_hill: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hill_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_gompertz: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gompertz_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_gudermann: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gudermann_param_deriv(j, x, a, mu, s, weight[i])); break; } } } } } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_exponential: case nsl_sf_stats_laplace: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_levy: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_exponential: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponential_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_laplace: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_laplace_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_cauchy_lorentz: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lorentz_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_rayleigh_tail: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_tail_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_lognormal: if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lognormal_param_deriv(j, x, a, s, mu, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); break; case nsl_sf_stats_logistic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logistic_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_sech: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sech_dist_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_levy: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_levy_param_deriv(j, x, a, s, mu, weight[i])); break; } } } } break; } case nsl_sf_stats_gaussian_tail: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_tail_param_deriv(j, x, A, s, a, mu, weight[i])); } } break; } case nsl_sf_stats_exponential_power: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exp_pow_param_deriv(j, x, a, s, b, mu, weight[i])); } } break; } case nsl_sf_stats_rayleigh: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_param_deriv(j, x, a, s, weight[i])); } } break; } case nsl_sf_stats_gamma: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gamma_param_deriv(j, x, a, k, t, weight[i])); } } break; } case nsl_sf_stats_flat: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_flat_param_deriv(j, x, A, b, a, weight[i])); } } break; } case nsl_sf_stats_chi_squared: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_chi_square_param_deriv(j, x, a, nu, weight[i])); } } break; } case nsl_sf_stats_tdist: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_students_t_param_deriv(j, x, a, nu, weight[i])); } } break; } case nsl_sf_stats_fdist: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_fdist_param_deriv(j, x, a, n1, n2, weight[i])); } } break; } case nsl_sf_stats_beta: case nsl_sf_stats_pareto: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_beta: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_beta_param_deriv(j, x, A, a, b, weight[i])); break; case nsl_sf_stats_pareto: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pareto_param_deriv(j, x, A, a, b, weight[i])); break; } } } } break; } case nsl_sf_stats_weibull: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_weibull_param_deriv(j, x, a, k, l, mu, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } } } break; } case nsl_sf_stats_gumbel1: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel1_param_deriv(j, x, a, s, mu, b, weight[i])); } } break; } case nsl_sf_stats_gumbel2: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel2_param_deriv(j, x, A, a, b, mu, weight[i])); } } break; } case nsl_sf_stats_poisson: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_poisson_param_deriv(j, x, a, l, weight[i])); } } break; } case nsl_sf_stats_maxwell_boltzmann: { // Y(x) = a*sqrt(2/pi) * x^2/s^3 * exp(-(x/s)^2/2) const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_maxwell_param_deriv(j, x, a, s, weight[i])); } } break; } case nsl_sf_stats_frechet: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double g = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_frechet_param_deriv(j, x, a, g, s, mu, weight[i])); } } break; } case nsl_sf_stats_landau: { // const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); for (size_t i = 0; i < n; i++) { x = xVector[i]; if (fixed[0]) gsl_matrix_set(J, (size_t)i, 0, 0.); else gsl_matrix_set(J, (size_t)i, 0, nsl_fit_model_landau_param_deriv(0, x, weight[i])); } break; } case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double N = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_binomial_param_deriv(j, x, a, p, N, weight[i])); break; case nsl_sf_stats_negative_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_negative_binomial_param_deriv(j, x, a, p, N, weight[i])); break; case nsl_sf_stats_pascal: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pascal_param_deriv(j, x, a, p, N, weight[i])); break; } } } } break; } case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_geometric: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_geometric_param_deriv(j, x, a, p, weight[i])); break; case nsl_sf_stats_logarithmic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logarithmic_param_deriv(j, x, a, p, weight[i])); break; } } } } break; } case nsl_sf_stats_hypergeometric: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hypergeometric_param_deriv(j, x, a, n1, n2, t, weight[i])); } } break; } // unused distributions case nsl_sf_stats_levy_alpha_stable: case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; } break; case nsl_fit_model_custom: QByteArray funcba = ((struct data*)params)->func->toLatin1(); const char* func = funcba.data(); QByteArray nameba; double value; const unsigned int np = paramNames->size(); for (size_t i = 0; i < n; i++) { x = xVector[i]; assign_variable("x", x); for (unsigned int j = 0; j < np; j++) { for (unsigned int k = 0; k < np; k++) { if (k != j) { nameba = paramNames->at(k).toLatin1(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, k), min[k], max[k]); assign_variable(nameba.data(), value); } } nameba = paramNames->at(j).toLatin1(); const char *name = nameba.data(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, j), min[j], max[j]); assign_variable(name, value); const double f_p = parse(func); double eps = 1.e-9; if (std::abs(f_p) > 0) eps *= std::abs(f_p); // scale step size with function value value += eps; assign_variable(name, value); const double f_pdp = parse(func); // DEBUG("evaluate deriv"<* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); residualsVector = static_cast* >(residualsColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->addChild(residualsColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); residualsVector->clear(); } } void XYFitCurvePrivate::recalculate() { DEBUG("XYFitCurvePrivate::recalculate()"); QElapsedTimer timer; timer.start(); // prepare source data columns const AbstractColumn* tmpXDataColumn = nullptr; const AbstractColumn* tmpYDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { DEBUG(" spreadsheet columns as data source"); tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { DEBUG(" curve columns as data source"); tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } // clear the previous result fitResult = XYFitCurve::FitResult(); if (!tmpXDataColumn || !tmpYDataColumn) { DEBUG("ERROR: Preparing source data columns failed!"); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } prepareResultColumns(); //fit settings const unsigned int maxIters = fitData.maxIterations; //maximal number of iterations const double delta = fitData.eps; //fit tolerance const unsigned int np = fitData.paramNames.size(); //number of fit parameters if (np == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Model has no parameters."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (yErrorColumn) { if (yErrorColumn->rowCount() < tmpXDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Not sufficient weight data points provided."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } } //copy all valid data point for the fit to temporary vectors QVector xdataVector; QVector ydataVector; QVector xerrorVector; QVector yerrorVector; double xmin, xmax; if (fitData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = fitData.fitRange.first(); xmax = fitData.fitRange.last(); } DEBUG("fit range = " << xmin << " .. " << xmax); int rowCount = qMin(tmpXDataColumn->rowCount(), tmpYDataColumn->rowCount()); for (int row = 0; row < rowCount; ++row) { //only copy those data where _all_ values (for x and y and errors, if given) are valid if (std::isnan(tmpXDataColumn->valueAt(row)) || std::isnan(tmpYDataColumn->valueAt(row)) || tmpXDataColumn->isMasked(row) || tmpYDataColumn->isMasked(row)) continue; // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { if ((!xErrorColumn && !yErrorColumn) || !fitData.useDataErrors) { // x-y xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } else if (!xErrorColumn && yErrorColumn) { // x-y-dy if (!std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } else if (xErrorColumn && yErrorColumn) { // x-y-dx-dy if (!std::isnan(xErrorColumn->valueAt(row)) && !std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); xerrorVector.append(xErrorColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } } } //number of data points to fit const size_t n = xdataVector.size(); DEBUG("number of data points: " << n); if (n == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("No data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (n < np) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("The number of data points (%1) must be greater than or equal to the number of parameters (%2).", n, np); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (fitData.model.simplified().isEmpty()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Fit model not specified."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* xerror = xerrorVector.data(); // size may be 0 double* yerror = yerrorVector.data(); // size may be 0 DEBUG("x error vector size: " << xerrorVector.size()); DEBUG("y error vector size: " << yerrorVector.size()); double* weight = new double[n]; for (size_t i = 0; i < n; i++) weight[i] = 1.; const double minError = 1.e-199; // minimum error for weighting switch (fitData.yWeightsType) { case nsl_fit_weight_no: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative_fit: break; case nsl_fit_weight_instrumental: // yerror are sigmas for (int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./gsl_pow_2(qMax(yerror[i], qMax(sqrt(minError), fabs(ydata[i]) * 1.e-15))); break; case nsl_fit_weight_direct: // yerror are weights for (int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = yerror[i]; break; case nsl_fit_weight_inverse: // yerror are inverse weights for (int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./qMax(yerror[i], qMax(minError, fabs(ydata[i]) * 1.e-15)); break; case nsl_fit_weight_statistical: for (int i = 0; i < (int)n; i++) weight[i] = 1./qMax(ydata[i], minError); break; case nsl_fit_weight_relative: for (int i = 0; i < (int)n; i++) weight[i] = 1./qMax(gsl_pow_2(ydata[i]), minError); break; } /////////////////////// GSL >= 2 has a complete new interface! But the old one is still supported. /////////////////////////// // GSL >= 2 : "the 'fdf' field of gsl_multifit_function_fdf is now deprecated and does not need to be specified for nonlinear least squares problems" unsigned int nf = 0; // number of fixed parameter for (unsigned int i = 0; i < np; i++) { const bool fixed = fitData.paramFixed.data()[i]; if (fixed) nf++; DEBUG("parameter " << i << " fixed: " << fixed); } //function to fit gsl_multifit_function_fdf f; - DEBUG("model = " << fitData.model.toStdString()); + DEBUG("model = " << STDSTRING(fitData.model)); struct data params = {n, xdata, ydata, weight, fitData.modelCategory, fitData.modelType, fitData.degree, &fitData.model, &fitData.paramNames, fitData.paramLowerLimits.data(), fitData.paramUpperLimits.data(), fitData.paramFixed.data()}; f.f = &func_f; f.df = &func_df; f.fdf = &func_fdf; f.n = n; f.p = np; f.params = ¶ms; DEBUG("initialize the derivative solver (using Levenberg-Marquardt robust solver)"); const gsl_multifit_fdfsolver_type* T = gsl_multifit_fdfsolver_lmsder; gsl_multifit_fdfsolver* s = gsl_multifit_fdfsolver_alloc(T, n, np); DEBUG("set start values"); double* x_init = fitData.paramStartValues.data(); double* x_min = fitData.paramLowerLimits.data(); double* x_max = fitData.paramUpperLimits.data(); DEBUG("scale start values if limits are set"); for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_unbound(x_init[i], x_min[i], x_max[i]); DEBUG(" DONE"); gsl_vector_view x = gsl_vector_view_array(x_init, np); DEBUG("Turning off GSL error handler to avoid overflow/underflow"); gsl_set_error_handler_off(); DEBUG("Initialize solver with function f and initial guess x"); gsl_multifit_fdfsolver_set(s, &f, &x.vector); DEBUG("Iterate ..."); int status; unsigned int iter = 0; fitResult.solverOutput.clear(); writeSolverState(s); do { iter++; DEBUG(" iter " << iter); // update weights for Y-depending weights (using function values from residuals) if (fitData.yWeightsType == nsl_fit_weight_statistical_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i)/sqrt(weight[i]) + ydata[i]); // 1/Y_i } else if (fitData.yWeightsType == nsl_fit_weight_relative_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(gsl_vector_get(s->f, i)/sqrt(weight[i]) + ydata[i]); // 1/Y_i^2 } DEBUG(" run fdfsolver_iterate"); status = gsl_multifit_fdfsolver_iterate(s); DEBUG(" fdfsolver_iterate DONE"); writeSolverState(s); if (status) { DEBUG("iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); DEBUG(" iter " << iter << ", test status = " << status); } while (status == GSL_CONTINUE && iter < maxIters); // second run for x-error fitting if (xerrorVector.size() > 0) { DEBUG("Rerun fit with x errors"); unsigned int iter2 = 0; double chisq = 0, chisqOld = 0; double *fun = new double[n]; do { iter2++; chisqOld = chisq; //printf("iter2 = %d\n", iter2); // calculate function from residuals for (size_t i = 0; i < n; i++) fun[i] = gsl_vector_get(s->f, i) * 1./sqrt(weight[i]) + ydata[i]; // calculate weight[i] for (size_t i = 0; i < n; i++) { // calculate df[i] size_t index = i-1; if (i == 0) index = i; if (i == n-1) index = i-2; double df = (fun[index+1] - fun[index])/(xdata[index+1] - xdata[index]); //printf("df = %g\n", df); double sigmasq = 1.; switch (fitData.xWeightsType) { // x-error type: f'(x)^2*s_x^2 = f'(x)/w_x case nsl_fit_weight_no: break; case nsl_fit_weight_direct: // xerror = w_x sigmasq = df*df/qMax(xerror[i], minError); break; case nsl_fit_weight_instrumental: // xerror = s_x sigmasq = df*df*xerror[i]*xerror[i]; break; case nsl_fit_weight_inverse: // xerror = 1/w_x = s_x^2 sigmasq = df*df*xerror[i]; break; case nsl_fit_weight_statistical: // s_x^2 = 1/w_x = x sigmasq = xdata[i]; break; case nsl_fit_weight_relative: // s_x^2 = 1/w_x = x^2 sigmasq = xdata[i]*xdata[i]; break; case nsl_fit_weight_statistical_fit: // unused case nsl_fit_weight_relative_fit: break; } if (yerrorVector.size() > 0) { switch (fitData.yWeightsType) { // y-error types: s_y^2 = 1/w_y case nsl_fit_weight_no: break; case nsl_fit_weight_direct: // yerror = w_y sigmasq += 1./qMax(yerror[i], minError); break; case nsl_fit_weight_instrumental: // yerror = s_y sigmasq += yerror[i]*yerror[i]; break; case nsl_fit_weight_inverse: // yerror = 1/w_y sigmasq += yerror[i]; break; case nsl_fit_weight_statistical: // unused case nsl_fit_weight_relative: break; case nsl_fit_weight_statistical_fit: // s_y^2 = 1/w_y = Y_i sigmasq += fun[i]; break; case nsl_fit_weight_relative_fit: // s_y^2 = 1/w_y = Y_i^2 sigmasq += fun[i]*fun[i]; break; } } //printf ("sigma[%d] = %g\n", i, sqrt(sigmasq)); weight[i] = 1./qMax(sigmasq, minError); } // update weights gsl_multifit_fdfsolver_set(s, &f, &x.vector); do { // fit iter++; writeSolverState(s); status = gsl_multifit_fdfsolver_iterate(s); //printf ("status = %s\n", gsl_strerror (status)); if (status) { DEBUG("iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); chisq = gsl_blas_dnrm2(s->f); } while (iter2 < maxIters && fabs(chisq-chisqOld) > fitData.eps); delete[] fun; } delete[] weight; // unscale start parameter for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_bound(x_init[i], x_min[i], x_max[i]); //get the covariance matrix //TODO: scale the Jacobian when limits are used before constructing the covar matrix? gsl_matrix* covar = gsl_matrix_alloc(np, np); #if GSL_MAJOR_VERSION >= 2 // the Jacobian is not part of the solver anymore gsl_matrix *J = gsl_matrix_alloc(s->fdf->n, s->fdf->p); gsl_multifit_fdfsolver_jac(s, J); gsl_multifit_covar(J, 0.0, covar); #else gsl_multifit_covar(s->J, 0.0, covar); #endif //write the result fitResult.available = true; fitResult.valid = true; fitResult.status = gslErrorToString(status); fitResult.iterations = iter; fitResult.dof = n - (np - nf); // samples - (parameter - fixed parameter) //gsl_blas_dnrm2() - computes the Euclidian norm (||r||_2 = \sqrt {\sum r_i^2}) of the vector with the elements weight[i]*(Yi - y[i]) //gsl_blas_dasum() - computes the absolute sum \sum |r_i| of the elements of the vector with the elements weight[i]*(Yi - y[i]) fitResult.sse = gsl_pow_2(gsl_blas_dnrm2(s->f)); if (fitResult.dof != 0) { fitResult.rms = fitResult.sse/fitResult.dof; fitResult.rsd = sqrt(fitResult.rms); } fitResult.mse = fitResult.sse/n; fitResult.rmse = sqrt(fitResult.mse); fitResult.mae = gsl_blas_dasum(s->f)/n; // SST needed for coefficient of determination, R-squared fitResult.sst = gsl_stats_tss(ydata, 1, n); // for a linear model without intercept R-squared is calculated differently // see https://cran.r-project.org/doc/FAQ/R-FAQ.html#Why-does-summary_0028_0029-report-strange-results-for-the-R_005e2-estimate-when-I-fit-a-linear-model-with-no-intercept_003f if (fitData.modelCategory == nsl_fit_model_basic && fitData.modelType == nsl_fit_model_polynomial && fitData.degree == 1 && x_init[0] == 0) { DEBUG("Using alternative R^2 for linear model without intercept"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } if (fitResult.sst < fitResult.sse) { DEBUG("Using alternative R^2 since R^2 would be negative (probably custom model without intercept)"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } fitResult.rsquare = nsl_stats_rsquare(fitResult.sse, fitResult.sst); fitResult.rsquareAdj = nsl_stats_rsquareAdj(fitResult.rsquare, np, fitResult.dof, 1); fitResult.chisq_p = nsl_stats_chisq_p(fitResult.sse, fitResult.dof); fitResult.fdist_F = nsl_stats_fdist_F(fitResult.sst, fitResult.rms, np, 1); fitResult.fdist_p = nsl_stats_fdist_p(fitResult.fdist_F, np, fitResult.dof); fitResult.logLik = nsl_stats_logLik(fitResult.sse, n); fitResult.aic = nsl_stats_aic(fitResult.sse, n, np, 1); fitResult.bic = nsl_stats_bic(fitResult.sse, n, np, 1); //parameter values // GSL: const double c = GSL_MAX_DBL(1., sqrt(fitResult.rms)); // increase error for poor fit // NIST: const double c = sqrt(fitResult.rms); // increase error for poor fit, decrease for good fit const double c = sqrt(fitResult.rms); fitResult.paramValues.resize(np); fitResult.errorValues.resize(np); fitResult.tdist_tValues.resize(np); fitResult.tdist_pValues.resize(np); fitResult.tdist_marginValues.resize(np); for (unsigned int i = 0; i < np; i++) { // scale resulting values if they are bounded fitResult.paramValues[i] = nsl_fit_map_bound(gsl_vector_get(s->x, i), x_min[i], x_max[i]); // use results as start values if desired if (fitData.useResults) { fitData.paramStartValues.data()[i] = fitResult.paramValues[i]; DEBUG("saving parameter " << i << ": " << fitResult.paramValues[i] << ' ' << fitData.paramStartValues.data()[i]); } fitResult.errorValues[i] = c*sqrt(gsl_matrix_get(covar, i, i)); fitResult.tdist_tValues[i] = nsl_stats_tdist_t(fitResult.paramValues.at(i), fitResult.errorValues.at(i)); fitResult.tdist_pValues[i] = nsl_stats_tdist_p(fitResult.tdist_tValues.at(i), fitResult.dof); fitResult.tdist_marginValues[i] = nsl_stats_tdist_margin(0.05, fitResult.dof, fitResult.errorValues.at(i)); } // fill residuals vector. To get residuals on the correct x values, fill the rest with zeros. residualsVector->resize(tmpXDataColumn->rowCount()); if (fitData.autoRange) { // evaluate full range of residuals xVector->resize(tmpXDataColumn->rowCount()); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*xVector)[i] = tmpXDataColumn->valueAt(i); ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = parser->evaluateCartesian(fitData.model, xVector, residualsVector, fitData.paramNames, fitResult.paramValues); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*residualsVector)[i] = tmpYDataColumn->valueAt(i) - (*residualsVector)[i]; if (!rc) residualsVector->clear(); } else { // only selected range size_t j = 0; for (int i = 0; i < tmpXDataColumn->rowCount(); i++) { if (tmpXDataColumn->valueAt(i) >= xmin && tmpXDataColumn->valueAt(i) <= xmax) residualsVector->data()[i] = - gsl_vector_get(s->f, j++); else // outside range residualsVector->data()[i] = 0; } } residualsColumn->setChanged(); //free resources gsl_multifit_fdfsolver_free(s); gsl_matrix_free(covar); //calculate the fit function (vectors) evaluate(); fitResult.elapsedTime = timer.elapsed(); sourceDataChangedSinceLastRecalc = false; DEBUG("XYFitCurvePrivate::recalculate() DONE"); } /* evaluate fit function */ void XYFitCurvePrivate::evaluate(bool preview) { DEBUG("XYFitCurvePrivate::evaluate() preview = " << preview); // prepare source data columns const AbstractColumn* tmpXDataColumn = nullptr; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { DEBUG(" spreadsheet columns as data source"); tmpXDataColumn = xDataColumn; } else { DEBUG(" curve columns as data source"); if (dataSourceCurve) tmpXDataColumn = dataSourceCurve->xColumn(); } if (!tmpXDataColumn) { DEBUG("ERROR: Preparing source data column failed!"); recalcLogicalPoints(); emit q->dataChanged(); return; } prepareResultColumns(); if (!xVector || !yVector) { DEBUG(" xVector or yVector not defined!"); recalcLogicalPoints(); emit q->dataChanged(); return; } if (fitData.model.simplified().isEmpty()) { DEBUG(" no fit-model specified."); recalcLogicalPoints(); emit q->dataChanged(); return; } ExpressionParser* parser = ExpressionParser::getInstance(); double xmin, xmax; if (fitData.autoEvalRange) { // evaluate fit on full data range xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { // use given range for evaluation xmin = fitData.evalRange.first(); xmax = fitData.evalRange.last(); } DEBUG(" eval range = " << xmin << " .. " << xmax); xVector->resize((int)fitData.evaluatedPoints); yVector->resize((int)fitData.evaluatedPoints); DEBUG(" vector size = " << xVector->size()); QVector paramValues = fitResult.paramValues; if (preview) // results not available yet paramValues = fitData.paramStartValues; bool rc = parser->evaluateCartesian(fitData.model, QString::number(xmin), QString::number(xmax), (int)fitData.evaluatedPoints, xVector, yVector, fitData.paramNames, paramValues); if (!rc) { xVector->clear(); yVector->clear(); residualsVector->clear(); } recalcLogicalPoints(); emit q->dataChanged(); } /*! * writes out the current state of the solver \c s */ void XYFitCurvePrivate::writeSolverState(gsl_multifit_fdfsolver* s) { QString state; //current parameter values, semicolon separated double* min = fitData.paramLowerLimits.data(); double* max = fitData.paramUpperLimits.data(); for (int i = 0; i < fitData.paramNames.size(); ++i) { const double x = gsl_vector_get(s->x, i); // map parameter if bounded state += QString::number(nsl_fit_map_bound(x, min[i], max[i])) + '\t'; } //current value of the chi2-function state += QString::number(gsl_pow_2(gsl_blas_dnrm2(s->f))); state += ';'; DEBUG(" chi = " << gsl_pow_2(gsl_blas_dnrm2(s->f))); fitResult.solverOutput += state; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFitCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFitCurve); writer->writeStartElement("xyFitCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fit-curve specific information //fit data - only save model expression and parameter names for custom model, otherwise they are set in XYFitCurve::initFitData() writer->writeStartElement("fitData"); WRITE_COLUMN(d->xErrorColumn, xErrorColumn); WRITE_COLUMN(d->yErrorColumn, yErrorColumn); writer->writeAttribute("autoRange", QString::number(d->fitData.autoRange)); writer->writeAttribute("fitRangeMin", QString::number(d->fitData.fitRange.first(), 'g', 15)); writer->writeAttribute("fitRangeMax", QString::number(d->fitData.fitRange.last(), 'g', 15)); writer->writeAttribute("modelCategory", QString::number(d->fitData.modelCategory)); writer->writeAttribute("modelType", QString::number(d->fitData.modelType)); writer->writeAttribute("xWeightsType", QString::number(d->fitData.xWeightsType)); writer->writeAttribute("weightsType", QString::number(d->fitData.yWeightsType)); writer->writeAttribute("degree", QString::number(d->fitData.degree)); if (d->fitData.modelCategory == nsl_fit_model_custom) writer->writeAttribute("model", d->fitData.model); writer->writeAttribute("maxIterations", QString::number(d->fitData.maxIterations)); writer->writeAttribute("eps", QString::number(d->fitData.eps, 'g', 15)); writer->writeAttribute("evaluatedPoints", QString::number(d->fitData.evaluatedPoints)); writer->writeAttribute("autoEvalRange", QString::number(d->fitData.autoEvalRange)); writer->writeAttribute("useDataErrors", QString::number(d->fitData.useDataErrors)); writer->writeAttribute("useResults", QString::number(d->fitData.useResults)); writer->writeAttribute("previewEnabled", QString::number(d->fitData.previewEnabled)); if (d->fitData.modelCategory == nsl_fit_model_custom) { writer->writeStartElement("paramNames"); foreach (const QString &name, d->fitData.paramNames) writer->writeTextElement("name", name); writer->writeEndElement(); } writer->writeStartElement("paramStartValues"); foreach (const double &value, d->fitData.paramStartValues) writer->writeTextElement("startValue", QString::number(value, 'g', 15)); writer->writeEndElement(); // use 16 digits to handle -DBL_MAX writer->writeStartElement("paramLowerLimits"); foreach (const double &limit, d->fitData.paramLowerLimits) writer->writeTextElement("lowerLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); // use 16 digits to handle DBL_MAX writer->writeStartElement("paramUpperLimits"); foreach (const double &limit, d->fitData.paramUpperLimits) writer->writeTextElement("upperLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); writer->writeStartElement("paramFixed"); foreach (const double &fixed, d->fitData.paramFixed) writer->writeTextElement("fixed", QString::number(fixed)); writer->writeEndElement(); writer->writeEndElement(); //"fitData" //fit results (generated columns and goodness of the fit) writer->writeStartElement("fitResult"); writer->writeAttribute("available", QString::number(d->fitResult.available)); writer->writeAttribute("valid", QString::number(d->fitResult.valid)); writer->writeAttribute("status", d->fitResult.status); writer->writeAttribute("iterations", QString::number(d->fitResult.iterations)); writer->writeAttribute("time", QString::number(d->fitResult.elapsedTime)); writer->writeAttribute("dof", QString::number(d->fitResult.dof)); writer->writeAttribute("sse", QString::number(d->fitResult.sse, 'g', 15)); writer->writeAttribute("sst", QString::number(d->fitResult.sst, 'g', 15)); writer->writeAttribute("rms", QString::number(d->fitResult.rms, 'g', 15)); writer->writeAttribute("rsd", QString::number(d->fitResult.rsd, 'g', 15)); writer->writeAttribute("mse", QString::number(d->fitResult.mse, 'g', 15)); writer->writeAttribute("rmse", QString::number(d->fitResult.rmse, 'g', 15)); writer->writeAttribute("mae", QString::number(d->fitResult.mae, 'g', 15)); writer->writeAttribute("rsquare", QString::number(d->fitResult.rsquare, 'g', 15)); writer->writeAttribute("rsquareAdj", QString::number(d->fitResult.rsquareAdj, 'g', 15)); writer->writeAttribute("chisq_p", QString::number(d->fitResult.chisq_p, 'g', 15)); writer->writeAttribute("fdist_F", QString::number(d->fitResult.fdist_F, 'g', 15)); writer->writeAttribute("fdist_p", QString::number(d->fitResult.fdist_p, 'g', 15)); writer->writeAttribute("aic", QString::number(d->fitResult.aic, 'g', 15)); writer->writeAttribute("bic", QString::number(d->fitResult.bic, 'g', 15)); writer->writeAttribute("solverOutput", d->fitResult.solverOutput); writer->writeStartElement("paramValues"); foreach (const double &value, d->fitResult.paramValues) writer->writeTextElement("value", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("errorValues"); foreach (const double &value, d->fitResult.errorValues) writer->writeTextElement("error", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_tValues"); foreach (const double &value, d->fitResult.tdist_tValues) writer->writeTextElement("tdist_t", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_pValues"); foreach (const double &value, d->fitResult.tdist_pValues) writer->writeTextElement("tdist_p", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_marginValues"); foreach (const double &value, d->fitResult.tdist_marginValues) writer->writeTextElement("tdist_margin", QString::number(value, 'g', 15)); writer->writeEndElement(); //save calculated columns if available if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->save(writer); d->yColumn->save(writer); d->residualsColumn->save(writer); } writer->writeEndElement(); //"fitResult" writer->writeEndElement(); //"xyFitCurve" } //! Load from XML bool XYFitCurve::load(XmlStreamReader* reader, bool preview) { DEBUG("XYFitCurve::load()"); Q_D(XYFitCurve); KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFitCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "fitData") { attribs = reader->attributes(); READ_COLUMN(xErrorColumn); READ_COLUMN(yErrorColumn); READ_INT_VALUE("autoRange", fitData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", fitData.fitRange.first()); // old name READ_DOUBLE_VALUE("xRangeMax", fitData.fitRange.last()); // old name READ_DOUBLE_VALUE("fitRangeMin", fitData.fitRange.first()); READ_DOUBLE_VALUE("fitRangeMax", fitData.fitRange.last()); READ_INT_VALUE("modelCategory", fitData.modelCategory, nsl_fit_model_category); READ_INT_VALUE("modelType", fitData.modelType, unsigned int); READ_INT_VALUE("xWeightsType", fitData.xWeightsType, nsl_fit_weight_type); READ_INT_VALUE("weightsType", fitData.yWeightsType, nsl_fit_weight_type); READ_INT_VALUE("degree", fitData.degree, int); if (d->fitData.modelCategory == nsl_fit_model_custom) { READ_STRING_VALUE("model", fitData.model); - DEBUG("read model = " << d->fitData.model.toStdString()); + DEBUG("read model = " << STDSTRING(d->fitData.model)); } READ_INT_VALUE("maxIterations", fitData.maxIterations, int); READ_DOUBLE_VALUE("eps", fitData.eps); READ_INT_VALUE("fittedPoints", fitData.evaluatedPoints, size_t); // old name READ_INT_VALUE("evaluatedPoints", fitData.evaluatedPoints, size_t); READ_INT_VALUE("evaluateFullRange", fitData.autoEvalRange, bool); // old name READ_INT_VALUE("autoEvalRange", fitData.autoEvalRange, bool); READ_INT_VALUE("useDataErrors", fitData.useDataErrors, bool); READ_INT_VALUE("useResults", fitData.useResults, bool); READ_INT_VALUE("previewEnabled", fitData.previewEnabled, bool); //set the model expression and the parameter names (can be derived from the saved values for category, type and degree) XYFitCurve::initFitData(d->fitData); // remove default names and start values d->fitData.paramStartValues.clear(); } else if (!preview && reader->name() == "name") { // needed for custom model d->fitData.paramNames << reader->readElementText(); } else if (!preview && reader->name() == "startValue") { d->fitData.paramStartValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fixed") { d->fitData.paramFixed << (bool)reader->readElementText().toInt(); } else if (!preview && reader->name() == "lowerLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // -DBL_MAX results in conversion error d->fitData.paramLowerLimits << x; else d->fitData.paramLowerLimits << -std::numeric_limits::max(); } else if (!preview && reader->name() == "upperLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // DBL_MAX results in conversion error d->fitData.paramUpperLimits << x; else d->fitData.paramUpperLimits << std::numeric_limits::max(); } else if (!preview && reader->name() == "value") { d->fitResult.paramValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "error") { d->fitResult.errorValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_t") { d->fitResult.tdist_tValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_p") { d->fitResult.tdist_pValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_margin") { d->fitResult.tdist_marginValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fitResult") { attribs = reader->attributes(); READ_INT_VALUE("available", fitResult.available, int); READ_INT_VALUE("valid", fitResult.valid, int); READ_STRING_VALUE("status", fitResult.status); READ_INT_VALUE("iterations", fitResult.iterations, int); READ_INT_VALUE("time", fitResult.elapsedTime, int); READ_DOUBLE_VALUE("dof", fitResult.dof); READ_DOUBLE_VALUE("sse", fitResult.sse); READ_DOUBLE_VALUE("sst", fitResult.sst); READ_DOUBLE_VALUE("rms", fitResult.rms); READ_DOUBLE_VALUE("rsd", fitResult.rsd); READ_DOUBLE_VALUE("mse", fitResult.mse); READ_DOUBLE_VALUE("rmse", fitResult.rmse); READ_DOUBLE_VALUE("mae", fitResult.mae); READ_DOUBLE_VALUE("rsquare", fitResult.rsquare); READ_DOUBLE_VALUE("rsquareAdj", fitResult.rsquareAdj); READ_DOUBLE_VALUE("chisq_p", fitResult.chisq_p); READ_DOUBLE_VALUE("fdist_F", fitResult.fdist_F); READ_DOUBLE_VALUE("fdist_p", fitResult.fdist_p); READ_DOUBLE_VALUE("aic", fitResult.aic); READ_DOUBLE_VALUE("bic", fitResult.bic); READ_STRING_VALUE("solverOutput", fitResult.solverOutput); } else if (reader->name() == "column") { Column* column = new Column(QString(), AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } - DEBUG("############################ reading column " << column->name().toStdString()) + DEBUG("############################ reading column " << STDSTRING(column->name())) if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; else if (column->name() == "residuals") d->residualsColumn = column; } } // older model save the param names also for non-custom models: remove them while (d->fitData.paramNames.size() > d->fitData.paramStartValues.size()) d->fitData.paramNames.removeLast(); if (d->fitData.paramNamesUtf8.isEmpty()) d->fitData.paramNamesUtf8 << d->fitData.paramNames; DEBUG("# params = " << d->fitData.paramNames.size()); if (preview) return true; // new fit model style (reset model type of old projects) if (d->fitData.modelCategory == nsl_fit_model_basic && d->fitData.modelType >= NSL_FIT_MODEL_BASIC_COUNT) { DEBUG("RESET old fit model"); d->fitData.modelType = 0; d->fitData.degree = 1; d->fitData.paramNames.clear(); d->fitData.paramNamesUtf8.clear(); // reset size of fields not touched by initFitData() d->fitData.paramStartValues.resize(2); d->fitData.paramFixed.resize(2); d->fitResult.paramValues.resize(2); d->fitResult.errorValues.resize(2); d->fitResult.tdist_tValues.resize(2); d->fitResult.tdist_pValues.resize(2); d->fitResult.tdist_marginValues.resize(2); } // not present in old projects int np = d->fitResult.paramValues.size(); if (d->fitResult.tdist_tValues.size() == 0) d->fitResult.tdist_tValues.resize(np); if (d->fitResult.tdist_pValues.size() == 0) d->fitResult.tdist_pValues.resize(np); if (d->fitResult.tdist_marginValues.size() == 0) d->fitResult.tdist_marginValues.resize(np); DEBUG("# start values = " << d->fitData.paramStartValues.size()); // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); addChild(d->residualsColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); d->residualsVector = static_cast* >(d->residualsColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; recalcLogicalPoints(); } return true; } diff --git a/src/kdefrontend/LabPlot.cpp b/src/kdefrontend/LabPlot.cpp index 86d8bfd1e..520cf105f 100644 --- a/src/kdefrontend/LabPlot.cpp +++ b/src/kdefrontend/LabPlot.cpp @@ -1,176 +1,176 @@ /*************************************************************************** File : LabPlot.cpp Project : LabPlot Description : main function -------------------------------------------------------------------- Copyright : (C) 2008 by Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2008-2016 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif #include #include #include #include #include #include #include #include #include "MainWin.h" #include "backend/core/AbstractColumn.h" #include "backend/lib/macros.h" int main (int argc, char *argv[]) { QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication app(argc, argv); KLocalizedString::setApplicationDomain("labplot2"); KCrash::initialize(); #ifdef NDEBUG const QString buildType(i18n("Release version")); #else const QString buildType(i18n("Debug version")); #endif KAboutData aboutData( QStringLiteral("labplot2"), QString("LabPlot"), LVERSION, i18n("LabPlot2 is a KDE-application for interactive graphing and analysis of scientific data."), - KAboutLicense::GPL,i18n("(c) 2007-2020"), buildType, QStringLiteral("https://labplot.kde.org")); + KAboutLicense::GPL, i18n("(c) 2007-2020"), buildType, QStringLiteral("https://labplot.kde.org")); aboutData.addAuthor(i18n("Stefan Gerlach"), i18nc("@info:credit", "Developer"), "stefan.gerlach@uni.kn", nullptr); aboutData.addAuthor(i18n("Alexander Semke"), i18nc("@info:credit", "Developer"), "alexander.semke@web.de", nullptr); aboutData.addAuthor(i18n("Fábián Kristóf-Szabolcs"), i18nc("@info:credit", "Developer"), "f-kristof@hotmail.com", nullptr); aboutData.addAuthor(i18n("Martin Marmsoler"), i18nc("@info:credit", "Developer"), "martin.marmsoler@gmail.com", nullptr); aboutData.addAuthor(i18n("Andreas Kainz"), i18nc("@info:credit", "Icon designer"), "kainz.a@gmail.com", nullptr); aboutData.addCredit(i18n("Yuri Chornoivan"), i18nc("@info:credit", "Help on many questions about the KDE-infrastructure and translation related topics"), "yurchor@ukr.net", nullptr); aboutData.addCredit(i18n("Garvit Khatri"), i18nc("@info:credit", "Porting LabPlot2 to KF5 and Integration with Cantor"), "garvitdelhi@gmail.com", nullptr); aboutData.addCredit(i18n("Christoph Roick"), i18nc("@info:credit", "Support import of ROOT (CERN) TH1 histograms"), "chrisito@gmx.de", nullptr); aboutData.setOrganizationDomain(QByteArray("kde.org")); aboutData.setDesktopFileName(QStringLiteral("org.kde.labplot2")); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption nosplashOption("no-splash", i18n("disable splash screen")); parser.addOption(nosplashOption); QCommandLineOption presenterOption("presenter", i18n("start in the presenter mode")); parser.addOption(presenterOption); parser.addPositionalArgument("+[file]", i18n( "open a project file")); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); const QStringList args = parser.positionalArguments(); QString filename; if (args.count() > 0) filename = args[0]; if (!filename.isEmpty() ) { //determine the absolute file path in order to properly save it in MainWin in "Recent Files" QDir dir; filename = dir.absoluteFilePath(filename); if ( !QFile::exists(filename)) { if ( KMessageBox::warningContinueCancel( nullptr, i18n( "Could not open file \'%1\'. Click \'Continue\' to proceed starting or \'Cancel\' to exit the application.", filename), i18n("Failed to Open")) == KMessageBox::Cancel) { exit(-1); //"Cancel" clicked -> exit the application } else { filename.clear(); //Wrong file -> clear the file name and continue } } } QSplashScreen* splash = nullptr; if (!parser.isSet(nosplashOption)) { const QString& file = QStandardPaths::locate(QStandardPaths::DataLocation, "splash.png"); splash = new QSplashScreen(QPixmap(file)); splash->show(); } // needed in order to have the signals triggered by SignallingUndoCommand //TODO: redesign/remove this qRegisterMetaType("const AbstractAspect*"); qRegisterMetaType("const AbstractColumn*"); #ifdef _WIN32 // enable debugging on console if (AttachConsole(ATTACH_PARENT_PROCESS)) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); } #endif DEBUG("DEBUG debugging enabled") QDEBUG("QDEBUG debugging enabled") #ifndef NDEBUG // debugging paths QStringList appdatapaths = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); DEBUG("AppDataLocation paths:") for (const QString &path: appdatapaths) - DEBUG(" " << path.toStdString()); + DEBUG(" " << STDSTRING(path)); DEBUG("Icon theme search paths:") for (const QString &path: QIcon::themeSearchPaths()) - DEBUG(" " << path.toStdString()); + DEBUG(" " << STDSTRING(path)); DEBUG("Library search paths:") for (const QString &path: QCoreApplication::libraryPaths()) - DEBUG(" " << path.toStdString()); + DEBUG(" " << STDSTRING(path)); #endif KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 67, 0) QString schemeName = group.readEntry("ColorScheme"); #else KConfigGroup generalGlobalsGroup = KSharedConfig::openConfig(QLatin1String("kdeglobals"))->group("General"); QString defaultSchemeName = generalGlobalsGroup.readEntry("ColorScheme", QStringLiteral("Breeze")); QString schemeName = group.readEntry("ColorScheme", defaultSchemeName); #endif KColorSchemeManager manager; manager.activateScheme(manager.indexForScheme(schemeName)); MainWin* window = new MainWin(nullptr, filename); window->show(); if (splash) { splash->finish(window); delete splash; } if (parser.isSet(presenterOption)) window->showPresenter(); return app.exec(); } diff --git a/src/kdefrontend/MainWin.cpp b/src/kdefrontend/MainWin.cpp index a83c7ec95..fde13cdea 100644 --- a/src/kdefrontend/MainWin.cpp +++ b/src/kdefrontend/MainWin.cpp @@ -1,2446 +1,2446 @@ /*************************************************************************** File : MainWin.cc Project : LabPlot Description : Main window of the application -------------------------------------------------------------------- Copyright : (C) 2008-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2020 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "MainWin.h" #include "backend/core/Project.h" #include "backend/core/Folder.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Workbook.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/worksheet/Worksheet.h" #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/DatasetHandler.h" #ifdef HAVE_LIBORIGIN #include "backend/datasources/projects/OriginProjectParser.h" #endif #ifdef HAVE_CANTOR_LIBS #include "backend/cantorWorksheet/CantorWorksheet.h" #endif #include "backend/datapicker/Datapicker.h" #include "backend/note/Note.h" #include "backend/lib/macros.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #endif #include "commonfrontend/core/PartMdiView.h" #include "commonfrontend/ProjectExplorer.h" #include "commonfrontend/matrix/MatrixView.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "commonfrontend/worksheet/WorksheetView.h" #ifdef HAVE_CANTOR_LIBS #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h" #endif #include "commonfrontend/datapicker/DatapickerView.h" #include "commonfrontend/datapicker/DatapickerImageView.h" #include "commonfrontend/note/NoteView.h" #include "commonfrontend/widgets/MemoryWidget.h" #include "kdefrontend/datasources/ImportFileDialog.h" #include "kdefrontend/datasources/ImportDatasetDialog.h" #include "kdefrontend/datasources/ImportDatasetWidget.h" #include "kdefrontend/datasources/ImportProjectDialog.h" #include "kdefrontend/datasources/ImportSQLDatabaseDialog.h" #include #include "kdefrontend/dockwidgets/ProjectDock.h" #include "kdefrontend/HistoryDialog.h" #include "kdefrontend/SettingsDialog.h" #include "kdefrontend/GuiObserver.h" #include "kdefrontend/widgets/LabelWidget.h" #include "kdefrontend/widgets/FITSHeaderEditDialog.h" #include "DatasetModel.h" // #include "welcomescreen/WelcomeScreenHelper.h" #ifdef HAVE_KUSERFEEDBACK #include #include #include #include #include #include #endif #ifdef Q_OS_MAC #include "3rdparty/kdmactouchbar/src/kdmactouchbar.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include // #include // #include // #include // #include // #include // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CANTOR_LIBS #include #include #include #include //required to parse Cantor and Jupyter files #include #include #include #include #endif /*! \class MainWin \brief Main application window. \ingroup kdefrontend */ MainWin::MainWin(QWidget *parent, const QString& filename) : KXmlGuiWindow(parent), m_schemeManager(new KColorSchemeManager(this)) { initGUI(filename); setAcceptDrops(true); #ifdef HAVE_KUSERFEEDBACK m_userFeedbackProvider.setProductIdentifier(QStringLiteral("org.kde.labplot")); m_userFeedbackProvider.setFeedbackServer(QUrl(QStringLiteral("https://telemetry.kde.org/"))); m_userFeedbackProvider.setSubmissionInterval(7); m_userFeedbackProvider.setApplicationStartsUntilEncouragement(5); m_userFeedbackProvider.setEncouragementDelay(30); // software version info m_userFeedbackProvider.addDataSource(new KUserFeedback::ApplicationVersionSource); m_userFeedbackProvider.addDataSource(new KUserFeedback::QtVersionSource); // info about the machine m_userFeedbackProvider.addDataSource(new KUserFeedback::PlatformInfoSource); m_userFeedbackProvider.addDataSource(new KUserFeedback::ScreenInfoSource); // usage info m_userFeedbackProvider.addDataSource(new KUserFeedback::StartCountSource); m_userFeedbackProvider.addDataSource(new KUserFeedback::UsageTimeSource); #endif //restore the geometry KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); restoreGeometry(group.readEntry("geometry", QByteArray())); } MainWin::~MainWin() { //save the recent opened files m_recentProjectsAction->saveEntries( KSharedConfig::openConfig()->group("Recent Files") ); KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); group.writeEntry("geometry", saveGeometry()); KSharedConfig::openConfig()->sync(); //if welcome screen is shown, save its settings prior to deleting it // if(dynamic_cast(centralWidget())) // QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions"); if (m_project) { // if(dynamic_cast(centralWidget()) == nullptr) // m_mdiArea->closeAllSubWindows(); disconnect(m_project, nullptr, this, nullptr); delete m_project; } if (m_aspectTreeModel) delete m_aspectTreeModel; if (m_guiObserver) delete m_guiObserver; // if(m_welcomeScreenHelper) // delete m_welcomeScreenHelper; } void MainWin::showPresenter() { const Worksheet* w = dynamic_cast(m_currentAspect); if (w) { auto* view = static_cast(w->view()); view->presenterMode(); } else { //currently active object is not a worksheet but we're asked to start in the presenter mode //determine the first available worksheet and show it in the presenter mode auto worksheets = m_project->children(); if (worksheets.size() > 0) { auto* view = static_cast(worksheets.constFirst()->view()); view->presenterMode(); } else { QMessageBox::information(this, i18n("Presenter Mode"), i18n("No worksheets are available in the project. The presenter mode will not be started.")); } } } AspectTreeModel* MainWin::model() const { return m_aspectTreeModel; } Project* MainWin::project() const { return m_project; } void MainWin::initGUI(const QString& fileName) { statusBar()->showMessage(i18nc("%1 is the LabPlot version", "Welcome to LabPlot %1", QLatin1String(LVERSION))); initActions(); #ifdef Q_OS_MAC setupGUI(Default, QLatin1String("/Applications/labplot2.app/Contents/Resources/labplot2ui.rc")); m_touchBar = new KDMacTouchBar(this); //m_touchBar->setTouchButtonStyle(KDMacTouchBar::IconOnly); #else setupGUI(Default, KXMLGUIClient::xmlFile()); // should be "labplot2ui.rc" #endif - DEBUG("Component name: " << KXMLGUIClient::componentName().toStdString()); - DEBUG("XML file: " << KXMLGUIClient::xmlFile().toStdString() << " (should be \"labplot2ui.rc\")"); + DEBUG("Component name: " << STDSTRING(KXMLGUIClient::componentName())); + DEBUG("XML file: " << STDSTRING(KXMLGUIClient::xmlFile()) << " (should be \"labplot2ui.rc\")"); //all toolbars created via the KXMLGUI framework are locked on default: // * on the very first program start, unlock all toolbars // * on later program starts, set stored lock status //Furthermore, we want to show icons only after the first program start. KConfigGroup groupMain = KSharedConfig::openConfig()->group("MainWindow"); if (groupMain.exists()) { //KXMLGUI framework automatically stores "Disabled" for the key "ToolBarsMovable" //in case the toolbars are locked -> load this value const QString& str = groupMain.readEntry(QLatin1String("ToolBarsMovable"), ""); bool locked = (str == QLatin1String("Disabled")); KToolBar::setToolBarsLocked(locked); } else { //first start KToolBar::setToolBarsLocked(false); //show icons only for (auto* container : factory()->containers(QLatin1String("ToolBar"))) { auto* toolbar = dynamic_cast(container); if (toolbar) toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); } } initMenus(); auto* mainToolBar = qobject_cast(factory()->container("main_toolbar", this)); if (!mainToolBar) { QMessageBox::critical(this, i18n("GUI configuration file not found"), i18n("%1 file was not found. Please check your installation.", KXMLGUIClient::xmlFile())); //TODO: the application is not really usable if the rc file was not found. We should quit the application. The following line crashes //the application because of the splash screen. We need to find another solution. // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); //call close as soon as we enter the eventloop return; } auto* tbImport = new QToolButton(mainToolBar); tbImport->setPopupMode(QToolButton::MenuButtonPopup); tbImport->setMenu(m_importMenu); tbImport->setDefaultAction(m_importFileAction); mainToolBar->addWidget(tbImport); qobject_cast(factory()->container("import", this))->setIcon(QIcon::fromTheme("document-import")); setWindowIcon(QIcon::fromTheme("LabPlot2", QGuiApplication::windowIcon())); setAttribute( Qt::WA_DeleteOnClose ); //make the status bar of a fixed size in order to avoid height changes when placing a ProgressBar there. QFont font; font.setFamily(font.defaultFamily()); QFontMetrics fm(font); statusBar()->setFixedHeight(fm.height() + 5); //load recently used projects m_recentProjectsAction->loadEntries( KSharedConfig::openConfig()->group("Recent Files") ); //auto-save KConfigGroup group = KSharedConfig::openConfig()->group("Settings_General"); m_autoSaveActive = group.readEntry("AutoSave", false); int interval = group.readEntry("AutoSaveInterval", 1); interval = interval*60*1000; m_autoSaveTimer.setInterval(interval); connect(&m_autoSaveTimer, &QTimer::timeout, this, &MainWin::autoSaveProject); if (!fileName.isEmpty()) { createMdiArea(); setCentralWidget(m_mdiArea); #ifdef HAVE_LIBORIGIN if (Project::isLabPlotProject(fileName) || OriginProjectParser::isOriginProject(fileName)) { #else if (Project::isLabPlotProject(fileName)) { #endif QTimer::singleShot(0, this, [=] () { openProject(fileName); }); } else { newProject(); QTimer::singleShot(0, this, [=] () { importFileDialog(fileName); }); } } else { //There is no file to open. Depending on the settings do nothing, //create a new project or open the last used project. LoadOnStart load = (LoadOnStart)group.readEntry("LoadOnStart", (int)Nothing); if (load != WelcomeScreen) { createMdiArea(); setCentralWidget(m_mdiArea); if (load == NewProject) //create new project newProject(); else if (load == NewProjectWorksheet) { //create new project with a worksheet newProject(); newWorksheet(); } else if (load == LastProject) { //open last used project if (!m_recentProjectsAction->urls().isEmpty()) { QDEBUG("TO OPEN m_recentProjectsAction->urls() =" << m_recentProjectsAction->urls().constFirst()); openRecentProject( m_recentProjectsAction->urls().constFirst() ); } } updateGUIOnProjectChanges(); } else{ //welcome screen // m_showWelcomeScreen = true; // m_welcomeWidget = createWelcomeScreen(); // setCentralWidget(m_welcomeWidget); } } //show memory info const bool showMemoryInfo = group.readEntry(QLatin1String("ShowMemoryInfo"), true); if (showMemoryInfo) { m_memoryInfoWidget = new MemoryWidget(statusBar()); statusBar()->addPermanentWidget(m_memoryInfoWidget); } } /** * @brief Creates a new welcome screen to be set as central widget. */ /* QQuickWidget* MainWin::createWelcomeScreen() { QSize maxSize = qApp->primaryScreen()->availableSize(); resize(maxSize); setMinimumSize(700, 400); showMaximized(); KToolBar* toolbar = toolBar(); if(toolbar) toolbar->setVisible(false); QList recentList; for (QUrl& url : m_recentProjectsAction->urls()) recentList.append(QVariant(url)); //Set context property QQuickWidget* quickWidget = new QQuickWidget(this); QQmlContext* ctxt = quickWidget->rootContext(); QVariant variant(recentList); ctxt->setContextProperty("recentProjects", variant); //Create helper object if(m_welcomeScreenHelper) delete m_welcomeScreenHelper; m_welcomeScreenHelper = new WelcomeScreenHelper(); connect(m_welcomeScreenHelper, &WelcomeScreenHelper::openExampleProject, this, QOverload::of(&MainWin::openProject)); ctxt->setContextProperty("datasetModel", m_welcomeScreenHelper->getDatasetModel()); ctxt->setContextProperty("helper", m_welcomeScreenHelper); quickWidget->setSource(QUrl(QLatin1String("qrc:///main.qml"))); quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); QObject *item = quickWidget->rootObject(); //connect qml's signals QObject::connect(item, SIGNAL(recentProjectClicked(QUrl)), this, SLOT(openRecentProject(QUrl))); QObject::connect(item, SIGNAL(datasetClicked(QString,QString,QString)), m_welcomeScreenHelper, SLOT(datasetClicked(QString,QString,QString))); QObject::connect(item, SIGNAL(openDataset()), this, SLOT(openDatasetExample())); QObject::connect(item, SIGNAL(openExampleProject(QString)), m_welcomeScreenHelper, SLOT(exampleProjectClicked(QString))); emit m_welcomeScreenHelper->showFirstDataset(); return quickWidget; } */ /** * @brief Initiates resetting the layout of the welcome screen */ /* void MainWin::resetWelcomeScreen() { if(dynamic_cast(centralWidget())) QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "restoreOriginalLayout"); } */ /** * @brief Creates a new MDI area, to replace the Welcome Screen as central widget */ void MainWin::createMdiArea() { KToolBar* toolbar = toolBar(); if(toolbar) toolbar->setVisible(true); //Save welcome screen's dimensions. // if(m_showWelcomeScreen) // QMetaObject::invokeMethod(m_welcomeWidget->rootObject(), "saveWidgetDimensions"); m_mdiArea = new QMdiArea; setCentralWidget(m_mdiArea); connect(m_mdiArea, &QMdiArea::subWindowActivated, this, &MainWin::handleCurrentSubWindowChanged); //set the view mode of the mdi area KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); int viewMode = group.readEntry("ViewMode", 0); if (viewMode == 1) { m_mdiArea->setViewMode(QMdiArea::TabbedView); int tabPosition = group.readEntry("TabPosition", 0); m_mdiArea->setTabPosition(QTabWidget::TabPosition(tabPosition)); m_mdiArea->setTabsClosable(true); m_mdiArea->setTabsMovable(true); m_tileWindowsAction->setVisible(false); m_cascadeWindowsAction->setVisible(false); } connect(m_closeWindowAction, &QAction::triggered, m_mdiArea, &QMdiArea::closeActiveSubWindow); connect(m_closeAllWindowsAction, &QAction::triggered, m_mdiArea, &QMdiArea::closeAllSubWindows); connect(m_tileWindowsAction, &QAction::triggered, m_mdiArea, &QMdiArea::tileSubWindows); connect(m_cascadeWindowsAction, &QAction::triggered, m_mdiArea, &QMdiArea::cascadeSubWindows); connect(m_nextWindowAction, &QAction::triggered, m_mdiArea, &QMdiArea::activateNextSubWindow); connect(m_prevWindowAction, &QAction::triggered, m_mdiArea, &QMdiArea::activatePreviousSubWindow); } void MainWin::initActions() { // ******************** File-menu ******************************* //add some standard actions m_newProjectAction = KStandardAction::openNew(this, SLOT(newProject()),actionCollection()); m_openProjectAction = KStandardAction::open(this, SLOT(openProject()),actionCollection()); m_recentProjectsAction = KStandardAction::openRecent(this, SLOT(openRecentProject(QUrl)),actionCollection()); m_closeAction = KStandardAction::close(this, SLOT(closeProject()),actionCollection()); actionCollection()->setDefaultShortcut(m_closeAction, QKeySequence()); //remove the shortcut, QKeySequence::Close will be used for closing sub-windows m_saveAction = KStandardAction::save(this, SLOT(saveProject()),actionCollection()); m_saveAsAction = KStandardAction::saveAs(this, SLOT(saveProjectAs()),actionCollection()); m_printAction = KStandardAction::print(this, SLOT(print()),actionCollection()); m_printPreviewAction = KStandardAction::printPreview(this, SLOT(printPreview()),actionCollection()); //TODO: on Mac OS when going full-screen we get a crash because of an stack-overflow #ifndef Q_OS_MAC KStandardAction::fullScreen(this, SLOT(toggleFullScreen()), this, actionCollection()); #endif //New Folder/Workbook/Spreadsheet/Matrix/Worksheet/Datasources m_newWorkbookAction = new QAction(QIcon::fromTheme("labplot-workbook-new"),i18n("Workbook"),this); actionCollection()->addAction("new_workbook", m_newWorkbookAction); m_newWorkbookAction->setWhatsThis(i18n("Creates a new workbook for collection spreadsheets, matrices and plots")); connect(m_newWorkbookAction, &QAction::triggered, this, &MainWin::newWorkbook); m_newDatapickerAction = new QAction(QIcon::fromTheme("color-picker-black"), i18n("Datapicker"), this); m_newDatapickerAction->setWhatsThis(i18n("Creates a data picker for getting data from a picture")); actionCollection()->addAction("new_datapicker", m_newDatapickerAction); connect(m_newDatapickerAction, &QAction::triggered, this, &MainWin::newDatapicker); m_newSpreadsheetAction = new QAction(QIcon::fromTheme("labplot-spreadsheet-new"),i18n("Spreadsheet"),this); // m_newSpreadsheetAction->setShortcut(Qt::CTRL+Qt::Key_Equal); m_newSpreadsheetAction->setWhatsThis(i18n("Creates a new spreadsheet for data editing")); actionCollection()->addAction("new_spreadsheet", m_newSpreadsheetAction); connect(m_newSpreadsheetAction, &QAction::triggered, this, &MainWin::newSpreadsheet); m_newMatrixAction = new QAction(QIcon::fromTheme("labplot-matrix-new"),i18n("Matrix"),this); // m_newMatrixAction->setShortcut(Qt::CTRL+Qt::Key_Equal); m_newMatrixAction->setWhatsThis(i18n("Creates a new matrix for data editing")); actionCollection()->addAction("new_matrix", m_newMatrixAction); connect(m_newMatrixAction, &QAction::triggered, this, &MainWin::newMatrix); m_newWorksheetAction = new QAction(QIcon::fromTheme("labplot-worksheet-new"),i18n("Worksheet"),this); // m_newWorksheetAction->setShortcut(Qt::ALT+Qt::Key_X); m_newWorksheetAction->setWhatsThis(i18n("Creates a new worksheet for data plotting")); actionCollection()->addAction("new_worksheet", m_newWorksheetAction); connect(m_newWorksheetAction, &QAction::triggered, this, &MainWin::newWorksheet); m_newNotesAction = new QAction(QIcon::fromTheme("document-new"),i18n("Note"),this); m_newNotesAction->setWhatsThis(i18n("Creates a new note for arbitrary text")); actionCollection()->addAction("new_notes", m_newNotesAction); connect(m_newNotesAction, &QAction::triggered, this, &MainWin::newNotes); // m_newScriptAction = new QAction(QIcon::fromTheme("insert-text"),i18n("Note/Script"),this); // actionCollection()->addAction("new_script", m_newScriptAction); // connect(m_newScriptAction, &QAction::triggered,SLOT(newScript())); m_newFolderAction = new QAction(QIcon::fromTheme("folder-new"),i18n("Folder"),this); m_newFolderAction->setWhatsThis(i18n("Creates a new folder to collect sheets and other elements")); actionCollection()->addAction("new_folder", m_newFolderAction); connect(m_newFolderAction, &QAction::triggered, this, &MainWin::newFolder); //"New file datasources" m_newLiveDataSourceAction = new QAction(QIcon::fromTheme("application-octet-stream"),i18n("Live Data Source"),this); m_newLiveDataSourceAction->setWhatsThis(i18n("Creates a live data source to read data from a real time device")); actionCollection()->addAction("new_live_datasource", m_newLiveDataSourceAction); connect(m_newLiveDataSourceAction, &QAction::triggered, this, &MainWin::newLiveDataSourceActionTriggered); //Import/Export m_importFileAction = new QAction(QIcon::fromTheme("document-import"), i18n("Import from File"), this); actionCollection()->setDefaultShortcut(m_importFileAction, Qt::CTRL+Qt::SHIFT+Qt::Key_I); m_importFileAction->setWhatsThis(i18n("Import data from a regular file")); actionCollection()->addAction("import_file", m_importFileAction); connect(m_importFileAction, &QAction::triggered, this, [=]() {importFileDialog();}); m_importSqlAction = new QAction(QIcon::fromTheme("network-server-database"), i18n("From SQL Database"), this); m_importSqlAction->setWhatsThis(i18n("Import data from a SQL database")); actionCollection()->addAction("import_sql", m_importSqlAction); connect(m_importSqlAction, &QAction::triggered, this, &MainWin::importSqlDialog); m_importDatasetAction = new QAction(QIcon::fromTheme(QLatin1String("database-index")), i18n("From Dataset Collection"), this); m_importDatasetAction->setWhatsThis(i18n("Imports data from an online dataset")); actionCollection()->addAction("import_dataset_datasource", m_importDatasetAction); connect(m_importDatasetAction, &QAction::triggered, this, &MainWin::importDatasetDialog); m_importLabPlotAction = new QAction(QIcon::fromTheme("project-open"), i18n("LabPlot Project"), this); m_importLabPlotAction->setWhatsThis(i18n("Import a project from a LabPlot project file (.lml)")); actionCollection()->addAction("import_labplot", m_importLabPlotAction); connect(m_importLabPlotAction, &QAction::triggered, this, &MainWin::importProjectDialog); #ifdef HAVE_LIBORIGIN m_importOpjAction = new QAction(QIcon::fromTheme("project-open"), i18n("Origin Project (OPJ)"), this); m_importOpjAction->setWhatsThis(i18n("Import a project from an OriginLab Origin project file (.opj)")); actionCollection()->addAction("import_opj", m_importOpjAction); connect(m_importOpjAction, &QAction::triggered, this, &MainWin::importProjectDialog); #endif m_exportAction = new QAction(QIcon::fromTheme("document-export"), i18n("Export"), this); m_exportAction->setWhatsThis(i18n("Export selected element")); actionCollection()->setDefaultShortcut(m_exportAction, Qt::CTRL+Qt::SHIFT+Qt::Key_E); actionCollection()->addAction("export", m_exportAction); connect(m_exportAction, &QAction::triggered, this, &MainWin::exportDialog); m_editFitsFileAction = new QAction(QIcon::fromTheme("editor"), i18n("FITS Metadata Editor"), this); m_editFitsFileAction->setWhatsThis(i18n("Open editor to edit FITS meta data")); actionCollection()->addAction("edit_fits", m_editFitsFileAction); connect(m_editFitsFileAction, &QAction::triggered, this, &MainWin::editFitsFileDialog); // Edit //Undo/Redo-stuff m_undoAction = KStandardAction::undo(this, SLOT(undo()), actionCollection()); m_redoAction = KStandardAction::redo(this, SLOT(redo()), actionCollection()); m_historyAction = new QAction(QIcon::fromTheme("view-history"), i18n("Undo/Redo History"),this); actionCollection()->addAction("history", m_historyAction); connect(m_historyAction, &QAction::triggered, this, &MainWin::historyDialog); #ifdef Q_OS_MAC m_undoIconOnlyAction = new QAction(m_undoAction->icon(), QString()); connect(m_undoIconOnlyAction, &QAction::triggered, this, &MainWin::undo); m_redoIconOnlyAction = new QAction(m_redoAction->icon(), QString()); connect(m_redoIconOnlyAction, &QAction::triggered, this, &MainWin::redo); #endif // TODO: more menus // Appearance // Analysis: see WorksheetView.cpp // Drawing // Script //Windows m_closeWindowAction = new QAction(i18n("&Close"), this); actionCollection()->setDefaultShortcut(m_closeWindowAction, QKeySequence::Close); m_closeWindowAction->setStatusTip(i18n("Close the active window")); actionCollection()->addAction("close window", m_closeWindowAction); m_closeAllWindowsAction = new QAction(i18n("Close &All"), this); m_closeAllWindowsAction->setStatusTip(i18n("Close all the windows")); actionCollection()->addAction("close all windows", m_closeAllWindowsAction); m_tileWindowsAction = new QAction(i18n("&Tile"), this); m_tileWindowsAction->setStatusTip(i18n("Tile the windows")); actionCollection()->addAction("tile windows", m_tileWindowsAction); m_cascadeWindowsAction = new QAction(i18n("&Cascade"), this); m_cascadeWindowsAction->setStatusTip(i18n("Cascade the windows")); actionCollection()->addAction("cascade windows", m_cascadeWindowsAction); m_nextWindowAction = new QAction(QIcon::fromTheme("go-next-view"), i18n("Ne&xt"), this); actionCollection()->setDefaultShortcut(m_nextWindowAction, QKeySequence::NextChild); m_nextWindowAction->setStatusTip(i18n("Move the focus to the next window")); actionCollection()->addAction("next window", m_nextWindowAction); m_prevWindowAction = new QAction(QIcon::fromTheme("go-previous-view"), i18n("Pre&vious"), this); actionCollection()->setDefaultShortcut(m_prevWindowAction, QKeySequence::PreviousChild); m_prevWindowAction->setStatusTip(i18n("Move the focus to the previous window")); actionCollection()->addAction("previous window", m_prevWindowAction); //"Standard actions" KStandardAction::preferences(this, SLOT(settingsDialog()), actionCollection()); KStandardAction::quit(this, SLOT(close()), actionCollection()); //Actions for window visibility auto* windowVisibilityActions = new QActionGroup(this); windowVisibilityActions->setExclusive(true); m_visibilityFolderAction = new QAction(QIcon::fromTheme("folder"), i18n("Current &Folder Only"), windowVisibilityActions); m_visibilityFolderAction->setCheckable(true); m_visibilityFolderAction->setData(Project::folderOnly); m_visibilitySubfolderAction = new QAction(QIcon::fromTheme("folder-documents"), i18n("Current Folder and &Subfolders"), windowVisibilityActions); m_visibilitySubfolderAction->setCheckable(true); m_visibilitySubfolderAction->setData(Project::folderAndSubfolders); m_visibilityAllAction = new QAction(i18n("&All"), windowVisibilityActions); m_visibilityAllAction->setCheckable(true); m_visibilityAllAction->setData(Project::allMdiWindows); connect(windowVisibilityActions, &QActionGroup::triggered, this, &MainWin::setMdiWindowVisibility); //Actions for hiding/showing the dock widgets auto* docksActions = new QActionGroup(this); docksActions->setExclusive(false); m_toggleProjectExplorerDockAction = new QAction(QIcon::fromTheme("view-list-tree"), i18n("Project Explorer"), docksActions); m_toggleProjectExplorerDockAction->setCheckable(true); m_toggleProjectExplorerDockAction->setChecked(true); actionCollection()->addAction("toggle_project_explorer_dock", m_toggleProjectExplorerDockAction); m_togglePropertiesDockAction = new QAction(QIcon::fromTheme("view-list-details"), i18n("Properties Explorer"), docksActions); m_togglePropertiesDockAction->setCheckable(true); m_togglePropertiesDockAction->setChecked(true); actionCollection()->addAction("toggle_properties_explorer_dock", m_togglePropertiesDockAction); connect(docksActions, &QActionGroup::triggered, this, &MainWin::toggleDockWidget); //global search QAction* searchAction = new QAction(actionCollection()); searchAction->setShortcut(QKeySequence::Find); connect(searchAction, &QAction::triggered, this, [=]() { if (m_project) { if (!m_projectExplorerDock->isVisible()) { m_toggleProjectExplorerDockAction->setChecked(true); toggleDockWidget(m_toggleProjectExplorerDockAction); } m_projectExplorer->search(); } }); this->addAction(searchAction); } void MainWin::initMenus() { //menu in the main toolbar for adding new aspects auto* menu = dynamic_cast(factory()->container("new", this)); menu->setIcon(QIcon::fromTheme("window-new")); //menu in the project explorer and in the toolbar for adding new aspects m_newMenu = new QMenu(i18n("Add New"), this); m_newMenu->setIcon(QIcon::fromTheme("window-new")); m_newMenu->addAction(m_newFolderAction); m_newMenu->addAction(m_newWorkbookAction); m_newMenu->addAction(m_newSpreadsheetAction); m_newMenu->addAction(m_newMatrixAction); m_newMenu->addAction(m_newWorksheetAction); m_newMenu->addAction(m_newNotesAction); m_newMenu->addAction(m_newDatapickerAction); m_newMenu->addSeparator(); m_newMenu->addAction(m_newLiveDataSourceAction); //import menu m_importMenu = new QMenu(this); m_importMenu->setIcon(QIcon::fromTheme("document-import")); m_importMenu ->addAction(m_importFileAction); m_importMenu ->addAction(m_importSqlAction); m_importMenu->addAction(m_importDatasetAction); m_importMenu->addSeparator(); m_importMenu->addAction(m_importLabPlotAction); #ifdef HAVE_LIBORIGIN m_importMenu ->addAction(m_importOpjAction); #endif #ifdef HAVE_CANTOR_LIBS m_newMenu->addSeparator(); m_newCantorWorksheetMenu = new QMenu(i18n("CAS Worksheet"), this); m_newCantorWorksheetMenu->setIcon(QIcon::fromTheme("archive-insert")); //"Adding Cantor backends to menu and context menu" QStringList m_availableBackend = Cantor::Backend::listAvailableBackends(); if (m_availableBackend.count() > 0) { unplugActionList(QLatin1String("backends_list")); QList newBackendActions; for (auto* backend : Cantor::Backend::availableBackends()) { if (!backend->isEnabled()) continue; QAction* action = new QAction(QIcon::fromTheme(backend->icon()), backend->name(), this); action->setData(backend->name()); newBackendActions << action; m_newCantorWorksheetMenu->addAction(action); } connect(m_newCantorWorksheetMenu, &QMenu::triggered, this, &MainWin::newCantorWorksheet); plugActionList(QLatin1String("backends_list"), newBackendActions); } m_newMenu->addMenu(m_newCantorWorksheetMenu); #else delete this->guiFactory()->container("cas_worksheet", this); delete this->guiFactory()->container("new_cas_worksheet", this); delete this->guiFactory()->container("cas_worksheet_toolbar", this); #endif //menu subwindow visibility policy m_visibilityMenu = new QMenu(i18n("Window Visibility Policy"), this); m_visibilityMenu->setIcon(QIcon::fromTheme("window-duplicate")); m_visibilityMenu ->addAction(m_visibilityFolderAction); m_visibilityMenu ->addAction(m_visibilitySubfolderAction); m_visibilityMenu ->addAction(m_visibilityAllAction); //menu for editing files m_editMenu = new QMenu(i18n("Edit"), this); m_editMenu->addAction(m_editFitsFileAction); //set the action for the current color scheme checked KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); #if KCONFIGWIDGETS_VERSION > QT_VERSION_CHECK(5,67,0) // Since version 5.67 KColorSchemeManager has a system default option QString schemeName = group.readEntry("ColorScheme"); #else KConfigGroup generalGlobalsGroup = KSharedConfig::openConfig(QLatin1String("kdeglobals"))->group("General"); QString defaultSchemeName = generalGlobalsGroup.readEntry("ColorScheme", QStringLiteral("Breeze")); QString schemeName = group.readEntry("ColorScheme", defaultSchemeName); #endif KActionMenu* schemesMenu = m_schemeManager->createSchemeSelectionMenu(i18n("Color Scheme"), schemeName, this); schemesMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color"))); QMenu* settingsMenu = dynamic_cast(factory()->container("settings", this)); if (settingsMenu) settingsMenu->insertMenu(settingsMenu->actions().constFirst(), schemesMenu->menu()); connect(schemesMenu->menu(), &QMenu::triggered, this, &MainWin::colorSchemeChanged); #ifdef HAVE_CANTOR_LIBS QAction* action = new QAction(QIcon::fromTheme(QLatin1String("cantor")), i18n("Configure CAS"), this); connect(action, &QAction::triggered, this, &MainWin::cantorSettingsDialog); if (settingsMenu) settingsMenu->addAction(action); #endif } void MainWin::colorSchemeChanged(QAction* action) { QString schemeName = KLocalizedString::removeAcceleratorMarker(action->text()); //background of the mdi area is not updated on theme changes, do it here. QModelIndex index = m_schemeManager->indexForScheme(schemeName); const QPalette& palette = KColorScheme::createApplicationPalette( KSharedConfig::openConfig(index.data(Qt::UserRole).toString()) ); const QBrush& brush = palette.brush(QPalette::Dark); m_mdiArea->setBackground(brush); //save the selected color scheme KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); group.writeEntry("ColorScheme", schemeName); group.sync(); } /*! Asks to save the project if it was modified. \return \c true if the project still needs to be saved ("cancel" clicked), \c false otherwise. */ bool MainWin::warnModified() { if (m_project->hasChanged()) { int want_save = KMessageBox::warningYesNoCancel( this, i18n("The current project %1 has been modified. Do you want to save it?", m_project->name()), i18n("Save Project")); switch (want_save) { case KMessageBox::Yes: return !saveProject(); case KMessageBox::No: break; case KMessageBox::Cancel: return true; } } return false; } /*! * updates the state of actions, menus and toolbars (enabled or disabled) * on project changes (project closes and opens) */ void MainWin::updateGUIOnProjectChanges() { if (m_closing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == nullptr) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } //disable all menus if there is no project bool b = (m_project == nullptr); m_saveAction->setEnabled(!b); m_saveAsAction->setEnabled(!b); m_printAction->setEnabled(!b); m_printPreviewAction->setEnabled(!b); m_importFileAction->setEnabled(!b); m_importSqlAction->setEnabled(!b); #ifdef HAVE_LIBORIGIN m_importOpjAction->setEnabled(!b); #endif m_exportAction->setEnabled(!b); m_newWorkbookAction->setEnabled(!b); m_newSpreadsheetAction->setEnabled(!b); m_newMatrixAction->setEnabled(!b); m_newWorksheetAction->setEnabled(!b); m_newDatapickerAction->setEnabled(!b); m_closeAction->setEnabled(!b); m_toggleProjectExplorerDockAction->setEnabled(!b); m_togglePropertiesDockAction->setEnabled(!b); if (!m_mdiArea || !m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif } factory->container("new", this)->setEnabled(!b); factory->container("edit", this)->setEnabled(!b); factory->container("import", this)->setEnabled(!b); if (b) setCaption("LabPlot2"); else setCaption(m_project->name()); #ifdef Q_OS_MAC m_touchBar->clear(); if (b){ m_touchBar->addAction(m_newProjectAction); m_touchBar->addAction(m_openProjectAction); } else { m_touchBar->addAction(m_importFileAction); m_touchBar->addAction(m_newWorksheetAction); m_touchBar->addAction(m_newSpreadsheetAction); m_touchBar->addAction(m_newMatrixAction); } #endif // undo/redo actions are disabled in both cases - when the project is closed or opened m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } /* * updates the state of actions, menus and toolbars (enabled or disabled) * depending on the currently active window (worksheet or spreadsheet). */ void MainWin::updateGUI() { if (m_project == nullptr || m_project->isLoading()) return; if (m_closing || m_projectClosing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == nullptr) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } //reset the touchbar #ifdef Q_OS_MAC m_touchBar->clear(); m_touchBar->addAction(m_undoIconOnlyAction); m_touchBar->addAction(m_redoIconOnlyAction); m_touchBar->addSeparator(); #endif if (!m_mdiArea || !m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif return; } #ifdef Q_OS_MAC if (dynamic_cast(m_currentAspect)) { m_touchBar->addAction(m_newWorksheetAction); m_touchBar->addAction(m_newSpreadsheetAction); m_touchBar->addAction(m_newMatrixAction); } #endif //Handle the Worksheet-object const Worksheet* w = dynamic_cast(m_currentAspect); if (!w) w = dynamic_cast(m_currentAspect->parent(AspectType::Worksheet)); if (w) { //populate worksheet menu auto* view = qobject_cast(w->view()); auto* menu = qobject_cast(factory->container("worksheet", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); //populate analysis menu menu = qobject_cast(factory->container("analysis", this)); menu->clear(); view->createAnalysisMenu(menu); menu->setEnabled(true); //populate worksheet-toolbar auto* toolbar = qobject_cast(factory->container("worksheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //populate the toolbar for cartesian plots toolbar = qobject_cast(factory->container("cartesian_plot_toolbar", this)); toolbar->clear(); view->fillCartesianPlotToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //populate the touchbar on Mac #ifdef Q_OS_MAC view->fillTouchBar(m_touchBar); #endif //hide the spreadsheet toolbar factory->container("spreadsheet_toolbar", this)->setVisible(false); } else { factory->container("worksheet", this)->setEnabled(false); factory->container("worksheet_toolbar", this)->setVisible(false); factory->container("analysis", this)->setEnabled(false); // factory->container("drawing", this)->setEnabled(false); factory->container("worksheet_toolbar", this)->setEnabled(false); factory->container("cartesian_plot_toolbar", this)->setEnabled(false); } //Handle the Spreadsheet-object const auto* spreadsheet = this->activeSpreadsheet(); if (!spreadsheet) spreadsheet = dynamic_cast(m_currentAspect->parent(AspectType::Spreadsheet)); if (spreadsheet) { //populate spreadsheet-menu auto* view = qobject_cast(spreadsheet->view()); auto* menu = qobject_cast(factory->container("spreadsheet", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); //populate spreadsheet-toolbar auto* toolbar = qobject_cast(factory->container("spreadsheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //populate the touchbar on Mac #ifdef Q_OS_MAC m_touchBar->addAction(m_importFileAction); view->fillTouchBar(m_touchBar); #endif } else { factory->container("spreadsheet", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->setVisible(false); } //Handle the Matrix-object const Matrix* matrix = dynamic_cast(m_currentAspect); if (!matrix) matrix = dynamic_cast(m_currentAspect->parent(AspectType::Matrix)); if (matrix) { //populate matrix-menu auto* view = qobject_cast(matrix->view()); auto* menu = qobject_cast(factory->container("matrix", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); //populate the touchbar on Mac #ifdef Q_OS_MAC m_touchBar->addAction(m_importFileAction); //view->fillTouchBar(m_touchBar); #endif } else factory->container("matrix", this)->setEnabled(false); #ifdef HAVE_CANTOR_LIBS const CantorWorksheet* cantorworksheet = dynamic_cast(m_currentAspect); if (!cantorworksheet) cantorworksheet = dynamic_cast(m_currentAspect->parent(AspectType::CantorWorksheet)); if (cantorworksheet) { auto* view = qobject_cast(cantorworksheet->view()); auto* menu = qobject_cast(factory->container("cas_worksheet", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); auto* toolbar = qobject_cast(factory->container("cas_worksheet_toolbar", this)); toolbar->setVisible(true); toolbar->clear(); view->fillToolBar(toolbar); } else { //no Cantor worksheet selected -> deactivate Cantor worksheet related menu and toolbar factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->setVisible(false); } #endif const Datapicker* datapicker = dynamic_cast(m_currentAspect); if (!datapicker) datapicker = dynamic_cast(m_currentAspect->parent(AspectType::Datapicker)); if (!datapicker) { if (m_currentAspect->type() == AspectType::DatapickerCurve) datapicker = dynamic_cast(m_currentAspect->parentAspect()); } if (datapicker) { //populate datapicker-menu auto* view = qobject_cast(datapicker->view()); auto* menu = qobject_cast(factory->container("datapicker", this)); menu->clear(); view->createContextMenu(menu); menu->setEnabled(true); //populate spreadsheet-toolbar auto* toolbar = qobject_cast(factory->container("datapicker_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); } else { factory->container("datapicker", this)->setEnabled(false); factory->container("datapicker_toolbar", this)->setVisible(false); } } /*! creates a new empty project. Returns \c true, if a new project was created. */ bool MainWin::newProject() { //close the current project, if available if (!closeProject()) return false; // if(dynamic_cast(centralWidget())) { // createMdiArea(); // setCentralWidget(m_mdiArea); // } QApplication::processEvents(QEventLoop::AllEvents, 100); if (m_project) delete m_project; if (m_aspectTreeModel) delete m_aspectTreeModel; m_project = new Project(); m_currentAspect = m_project; m_currentFolder = m_project; KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); Project::MdiWindowVisibility vis = Project::MdiWindowVisibility(group.readEntry("MdiWindowVisibility", 0)); m_project->setMdiWindowVisibility( vis ); if (vis == Project::folderOnly) m_visibilityFolderAction->setChecked(true); else if (vis == Project::folderAndSubfolders) m_visibilitySubfolderAction->setChecked(true); else m_visibilityAllAction->setChecked(true); m_aspectTreeModel = new AspectTreeModel(m_project, this); connect(m_aspectTreeModel, &AspectTreeModel::statusInfo, [=](const QString& text){ statusBar()->showMessage(text); }); //newProject is called for the first time, there is no project explorer yet //-> initialize the project explorer, the GUI-observer and the dock widgets. if (m_projectExplorer == nullptr) { m_projectExplorerDock = new QDockWidget(this); m_projectExplorerDock->setObjectName("projectexplorer"); m_projectExplorerDock->setWindowTitle(i18nc("@title:window", "Project Explorer")); addDockWidget(Qt::LeftDockWidgetArea, m_projectExplorerDock); m_projectExplorer = new ProjectExplorer(m_projectExplorerDock); m_projectExplorerDock->setWidget(m_projectExplorer); connect(m_projectExplorer, &ProjectExplorer::currentAspectChanged, this, &MainWin::handleCurrentAspectChanged); connect(m_projectExplorerDock, &QDockWidget::visibilityChanged, this, &MainWin::projectExplorerDockVisibilityChanged); //Properties dock m_propertiesDock = new QDockWidget(this); m_propertiesDock->setObjectName("aspect_properties_dock"); m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); addDockWidget(Qt::RightDockWidgetArea, m_propertiesDock); auto* sa = new QScrollArea(m_propertiesDock); stackedWidget = new QStackedWidget(sa); sa->setWidget(stackedWidget); sa->setWidgetResizable(true); m_propertiesDock->setWidget(sa); connect(m_propertiesDock, &QDockWidget::visibilityChanged, this, &MainWin::propertiesDockVisibilityChanged); //GUI-observer; m_guiObserver = new GuiObserver(this); } m_projectExplorer->setModel(m_aspectTreeModel); m_projectExplorer->setProject(m_project); m_projectExplorer->setCurrentAspect(m_project); m_projectExplorerDock->show(); m_propertiesDock->show(); updateGUIOnProjectChanges(); connect(m_project, &Project::aspectAdded, this, &MainWin::handleAspectAdded); connect(m_project, &Project::aspectRemoved, this, &MainWin::handleAspectRemoved); connect(m_project, &Project::aspectAboutToBeRemoved, this, &MainWin::handleAspectAboutToBeRemoved); connect(m_project, SIGNAL(statusInfo(QString)), statusBar(), SLOT(showMessage(QString))); connect(m_project, &Project::changed, this, &MainWin::projectChanged); connect(m_project, &Project::requestProjectContextMenu, this, &MainWin::createContextMenu); connect(m_project, &Project::requestFolderContextMenu, this, &MainWin::createFolderContextMenu); connect(m_project, &Project::mdiWindowVisibilityChanged, this, &MainWin::updateMdiWindowVisibility); connect(m_project, &Project::closeRequested, this, &MainWin::closeProject); m_undoViewEmptyLabel = i18n("%1: created", m_project->name()); setCaption(m_project->name()); return true; } void MainWin::openProject() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWin"); const QString& dir = conf.readEntry("LastOpenDir", ""); QString extensions = i18n("LabPlot Projects (%1)", Project::supportedExtensions()); #ifdef HAVE_LIBORIGIN extensions += i18n(";;Origin Projects (%1)", OriginProjectParser::supportedExtensions()); #endif #ifdef HAVE_CANTOR_LIBS extensions += i18n(";;Cantor Projects (.cws)"); extensions += i18n(";;Jupyter Notebooks (.ipynb)"); #endif const QString& path = QFileDialog::getOpenFileName(this,i18n("Open Project"), dir, extensions); if (path.isEmpty())// "Cancel" was clicked return; this->openProject(path); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } } void MainWin::openProject(const QString& filename) { if (filename == m_currentFileName) { KMessageBox::information(this, i18n("The project file %1 is already opened.", filename), i18n("Open Project")); return; } // if(dynamic_cast(centralWidget())) { // createMdiArea(); // setCentralWidget(m_mdiArea); // } if (!newProject()) return; WAIT_CURSOR; QElapsedTimer timer; timer.start(); bool rc = false; if (Project::isLabPlotProject(filename)) { m_project->setFileName(filename); rc = m_project->load(filename); } #ifdef HAVE_LIBORIGIN else if (OriginProjectParser::isOriginProject(filename)) { OriginProjectParser parser; parser.setProjectFileName(filename); parser.importTo(m_project, QStringList()); //TODO: add return code rc = true; } #endif #ifdef HAVE_CANTOR_LIBS else if (QFileInfo(filename).completeSuffix() == QLatin1String("cws")) { QFile file(filename); KZip archive(&file); rc = archive.open(QIODevice::ReadOnly); if (rc) { const auto* contentEntry = archive.directory()->entry(QLatin1String("content.xml")); if (contentEntry && contentEntry->isFile()) { const auto* contentFile = static_cast(contentEntry); QByteArray data = contentFile->data(); archive.close(); //determine the name of the backend QDomDocument doc; doc.setContent(data); QString backendName = doc.documentElement().attribute(QLatin1String("backend")); if (!backendName.isEmpty()) { //create new Cantor worksheet and load the data auto* worksheet = new CantorWorksheet(backendName); worksheet->setName(QFileInfo(filename).fileName()); worksheet->setComment(filename); rc = file.open(QIODevice::ReadOnly); if (rc) { QByteArray content = file.readAll(); rc = worksheet->init(&content); if (rc) m_project->addChild(worksheet); else { delete worksheet; RESET_CURSOR; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } }else { RESET_CURSOR; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to open the file '%1'.", filename)); } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } else { RESET_CURSOR; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to open the file '%1'.", filename)); } } else if (QFileInfo(filename).completeSuffix() == QLatin1String("ipynb")) { QFile file(filename); rc = file.open(QIODevice::ReadOnly); if (rc) { QByteArray content = file.readAll(); QJsonParseError error; // TODO: use QJsonDocument& doc = QJsonDocument::fromJson(content, &error); if minimum Qt version is at least 5.10 const QJsonDocument& jsonDoc = QJsonDocument::fromJson(content, &error); const QJsonObject& doc = jsonDoc.object(); if (error.error == QJsonParseError::NoError) { //determine the backend name QString backendName; // TODO: use doc["metadata"]["kernelspec"], etc. if minimum Qt version is at least 5.10 if ((doc["metadata"] != QJsonValue::Undefined && doc["metadata"].isObject()) && (doc["metadata"].toObject()["kernelspec"] != QJsonValue::Undefined && doc["metadata"].toObject()["kernelspec"].isObject()) ) { QString kernel; if (doc["metadata"].toObject()["kernelspec"].toObject()["name"] != QJsonValue::Undefined) kernel = doc["metadata"].toObject()["kernelspec"].toObject()["name"].toString(); if (!kernel.isEmpty()) { if (kernel.startsWith(QLatin1String("julia"))) backendName = QLatin1String("julia"); else if (kernel == QLatin1String("sagemath")) backendName = QLatin1String("sage"); else if (kernel == QLatin1String("ir")) backendName = QLatin1String("r"); else backendName = kernel; } else backendName = doc["metadata"].toObject()["kernelspec"].toObject()["language"].toString(); if (!backendName.isEmpty()) { //create new Cantor worksheet and load the data auto* worksheet = new CantorWorksheet(backendName); worksheet->setName(QFileInfo(filename).fileName()); worksheet->setComment(filename); rc = worksheet->init(&content); if (rc) m_project->addChild(worksheet); else { delete worksheet; RESET_CURSOR; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to process the content of the file '%1'.", filename)); } } } else { RESET_CURSOR; rc = false; QMessageBox::critical(this, i18n("Failed to open project"), i18n("Failed to open the file '%1'.", filename)); } } #endif m_project->setChanged(false); if (!rc) { closeProject(); RESET_CURSOR; return; } m_currentFileName = filename; m_project->undoStack()->clear(); m_undoViewEmptyLabel = i18n("%1: opened", m_project->name()); m_recentProjectsAction->addUrl( QUrl(filename) ); setCaption(m_project->name()); updateGUIOnProjectChanges(); updateGUI(); //there are most probably worksheets or spreadsheets in the open project -> update the GUI m_saveAction->setEnabled(false); statusBar()->showMessage( i18n("Project successfully opened (in %1 seconds).", (float)timer.elapsed()/1000) ); if (m_autoSaveActive) m_autoSaveTimer.start(); RESET_CURSOR; } void MainWin::openRecentProject(const QUrl& url) { // if(dynamic_cast(centralWidget())) { // createMdiArea(); // setCentralWidget(m_mdiArea); // } if (url.isLocalFile()) // fix for Windows this->openProject(url.toLocalFile()); else this->openProject(url.path()); } /*! Closes the current project, if available. Return \c true, if the project was closed. */ bool MainWin::closeProject() { if (m_project == nullptr) return true; //nothing to close if (warnModified()) return false; if(!m_closing) { // if(dynamic_cast(centralWidget()) && m_showWelcomeScreen) { // m_welcomeWidget = createWelcomeScreen(); // setCentralWidget(m_welcomeWidget); // } } m_projectClosing = true; statusBar()->clearMessage(); delete m_aspectTreeModel; m_aspectTreeModel = nullptr; delete m_project; m_project = nullptr; m_currentFileName.clear(); m_projectClosing = false; //update the UI if we're just closing a project //and not closing(quitting) the application if (!m_closing) { m_projectExplorerDock->hide(); m_propertiesDock->hide(); m_currentAspect = nullptr; m_currentFolder = nullptr; updateGUIOnProjectChanges(); if (m_autoSaveActive) m_autoSaveTimer.stop(); } removeDockWidget(cursorDock); delete cursorDock; cursorDock = nullptr; cursorWidget = nullptr; // is deleted, because it's the cild of cursorDock return true; } bool MainWin::saveProject() { const QString& fileName = m_project->fileName(); if (fileName.isEmpty()) return saveProjectAs(); else return save(fileName); } bool MainWin::saveProjectAs() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWin"); const QString& dir = conf.readEntry("LastOpenDir", ""); QString path = QFileDialog::getSaveFileName(this, i18n("Save Project As"), dir, i18n("LabPlot Projects (*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ)")); if (path.isEmpty())// "Cancel" was clicked return false; if (path.contains(QLatin1String(".lml"), Qt::CaseInsensitive) == false) path.append(QLatin1String(".lml")); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } return save(path); } /*! * auxiliary function that does the actual saving of the project */ bool MainWin::save(const QString& fileName) { QTemporaryFile tempFile(QDir::tempPath() + QLatin1Char('/') + QLatin1String("labplot_save_XXXXXX")); if (!tempFile.open()) { KMessageBox::error(this, i18n("Couldn't open the temporary file for writing.")); return false; } WAIT_CURSOR; const QString& tempFileName = tempFile.fileName(); - DEBUG("Using temporary file " << tempFileName.toStdString()) + DEBUG("Using temporary file " << STDSTRING(tempFileName)) tempFile.close(); // use file ending to find out how to compress file QIODevice* file; // if ending is .lml, do gzip compression anyway if (fileName.endsWith(QLatin1String(".lml"))) file = new KCompressionDevice(tempFileName, KCompressionDevice::GZip); else file = new KFilterDev(tempFileName); if (file == nullptr) file = new QFile(tempFileName); bool ok; if (file->open(QIODevice::WriteOnly)) { m_project->setFileName(fileName); QPixmap thumbnail = centralWidget()->grab(); QXmlStreamWriter writer(file); m_project->setFileName(fileName); m_project->save(thumbnail, &writer); m_project->undoStack()->clear(); m_project->setChanged(false); file->close(); // target file must not exist if (QFile::exists(fileName)) QFile::remove(fileName); // do not rename temp file. Qt still holds a handle (which fails renaming on Windows) and deletes it bool rc = QFile::copy(tempFileName, fileName); if (rc) { setCaption(m_project->name()); statusBar()->showMessage(i18n("Project saved")); m_saveAction->setEnabled(false); m_recentProjectsAction->addUrl( QUrl(fileName) ); ok = true; //if the project dock is visible, refresh the shown content //(version and modification time might have been changed) if (stackedWidget->currentWidget() == projectDock) projectDock->setProject(m_project); //we have a file name now // -> auto save can be activated now if not happened yet if (m_autoSaveActive && !m_autoSaveTimer.isActive()) m_autoSaveTimer.start(); } else { RESET_CURSOR; KMessageBox::error(this, i18n("Couldn't save the file '%1'.", fileName)); ok = false; } } else { RESET_CURSOR; KMessageBox::error(this, i18n("Couldn't open the file '%1' for writing.", fileName)); ok = false; } delete file; RESET_CURSOR; return ok; } /*! * automatically saves the project in the specified time interval. */ void MainWin::autoSaveProject() { //don't auto save when there are no changes or the file name //was not provided yet (the project was never explicitly saved yet). if ( !m_project->hasChanged() || m_project->fileName().isEmpty()) return; this->saveProject(); } /*! prints the current sheet (worksheet, spreadsheet or matrix) */ void MainWin::print() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = static_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printView()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->clearMessage(); } void MainWin::printPreview() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = static_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printPreview()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->clearMessage(); } /**************************************************************************************/ /*! adds a new Folder to the project. */ void MainWin::newFolder() { Folder* folder = new Folder(i18n("Folder")); this->addAspectToProject(folder); } /*! adds a new Workbook to the project. */ void MainWin::newWorkbook() { Workbook* workbook = new Workbook(i18n("Workbook")); this->addAspectToProject(workbook); } /*! adds a new Datapicker to the project. */ void MainWin::newDatapicker() { Datapicker* datapicker = new Datapicker(i18n("Datapicker")); this->addAspectToProject(datapicker); } /*! adds a new Spreadsheet to the project. */ void MainWin::newSpreadsheet() { Spreadsheet* spreadsheet = new Spreadsheet(i18n("Spreadsheet")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new spreadsheet to the workbook Workbook* workbook = dynamic_cast(m_currentAspect); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); const auto* aspect = static_cast(index.internalPointer()); if (!aspect->inherits(AspectType::Folder)) { workbook->addChild(spreadsheet); return; } } this->addAspectToProject(spreadsheet); } /*! adds a new Matrix to the project. */ void MainWin::newMatrix() { Matrix* matrix = new Matrix(i18n("Matrix")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new matrix to the workbook Workbook* workbook = dynamic_cast(m_currentAspect); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); const auto* aspect = static_cast(index.internalPointer()); if (!aspect->inherits(AspectType::Folder)) { workbook->addChild(matrix); return; } } this->addAspectToProject(matrix); } /*! adds a new Worksheet to the project. */ void MainWin::newWorksheet() { Worksheet* worksheet = new Worksheet(i18n("Worksheet")); this->addAspectToProject(worksheet); } /*! adds a new Note to the project. */ void MainWin::newNotes() { Note* notes = new Note(i18n("Note")); this->addAspectToProject(notes); } /*! returns a pointer to a \c Spreadsheet object, if the currently active Mdi-Subwindow or if the currently selected tab in a \c WorkbookView is a \c SpreadsheetView Otherwise returns \c 0. */ Spreadsheet* MainWin::activeSpreadsheet() const { // if(dynamic_cast(centralWidget())) // return nullptr; if (!m_currentAspect) return nullptr; Spreadsheet* spreadsheet = nullptr; if (m_currentAspect->type() == AspectType::Spreadsheet) spreadsheet = dynamic_cast(m_currentAspect); else { //check whether one of spreadsheet columns is selected and determine the spreadsheet auto* parent = m_currentAspect->parentAspect(); if (parent && parent->type() == AspectType::Spreadsheet) spreadsheet = dynamic_cast(parent); } return spreadsheet; } #ifdef HAVE_CANTOR_LIBS /* adds a new Cantor Spreadsheet to the project. */ void MainWin::newCantorWorksheet(QAction* action) { CantorWorksheet* cantorworksheet = new CantorWorksheet(action->data().toString()); this->addAspectToProject(cantorworksheet); } /********************************************************************************/ #endif /*! called if there were changes in the project. Adds "changed" to the window caption and activates the save-Action. */ void MainWin::projectChanged() { setCaption(i18n("%1 [Changed]", m_project->name())); m_saveAction->setEnabled(true); m_undoAction->setEnabled(true); return; } void MainWin::handleCurrentSubWindowChanged(QMdiSubWindow* win) { if (!win) { updateGUI(); return; } auto* view = static_cast(win); if (view == m_currentSubWindow) { //do nothing, if the current sub-window gets selected again. //This event happens, when labplot loses the focus (modal window is opened or the user switches to another application) //and gets it back (modal window is closed or the user switches back to labplot). return; } else m_currentSubWindow = view; updateGUI(); if (!m_suppressCurrentSubWindowChangedEvent) m_projectExplorer->setCurrentAspect(view->part()); } void MainWin::handleAspectAdded(const AbstractAspect* aspect) { const auto* part = dynamic_cast(aspect); if (part) { // connect(part, &AbstractPart::importFromFileRequested, this, &MainWin::importFileDialog); connect(part, &AbstractPart::importFromFileRequested, this, [=]() {importFileDialog();}); connect(part, &AbstractPart::importFromSQLDatabaseRequested, this, &MainWin::importSqlDialog); //TODO: export, print and print preview should be handled in the views and not in MainWin. connect(part, &AbstractPart::exportRequested, this, &MainWin::exportDialog); connect(part, &AbstractPart::printRequested, this, &MainWin::print); connect(part, &AbstractPart::printPreviewRequested, this, &MainWin::printPreview); connect(part, &AbstractPart::showRequested, this, &MainWin::handleShowSubWindowRequested); const auto* worksheet = dynamic_cast(aspect); if (worksheet) connect(worksheet, &Worksheet::cartesianPlotMouseModeChanged, this, &MainWin::cartesianPlotMouseModeChanged); } } void MainWin::handleAspectRemoved(const AbstractAspect* parent,const AbstractAspect* before,const AbstractAspect* aspect) { Q_UNUSED(before); Q_UNUSED(aspect); //no need to react on removal of // - AbstractSimpleFilter // - columns in the data spreadsheet of a datapicker curve, // this can only happen when changing the error type and is done on the level of DatapickerImage if (!dynamic_cast(aspect) && !(parent->parentAspect() && parent->parentAspect()->type() == AspectType::DatapickerCurve) ) m_projectExplorer->setCurrentAspect(parent); } void MainWin::handleAspectAboutToBeRemoved(const AbstractAspect *aspect) { const auto* part = dynamic_cast(aspect); if (!part) return; const auto* workbook = dynamic_cast(aspect->parentAspect()); auto* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (!workbook && !datapicker) { PartMdiView* win = part->mdiSubWindow(); if (win) m_mdiArea->removeSubWindow(win); } } /*! called when the current aspect in the tree of the project explorer was changed. Selects the new aspect. */ void MainWin::handleCurrentAspectChanged(AbstractAspect *aspect) { if (!aspect) aspect = m_project; // should never happen, just in case m_suppressCurrentSubWindowChangedEvent = true; if (aspect->folder() != m_currentFolder) { m_currentFolder = aspect->folder(); updateMdiWindowVisibility(); } m_currentAspect = aspect; //activate the corresponding MDI sub window for the current aspect activateSubWindowForAspect(aspect); m_suppressCurrentSubWindowChangedEvent = false; updateGUI(); } void MainWin::activateSubWindowForAspect(const AbstractAspect* aspect) const { const auto* part = dynamic_cast(aspect); if (part) { //for LiveDataSource we currently don't show any view /*if (dynamic_cast(part)) return;*/ PartMdiView* win; //for aspects being children of a Workbook, we show workbook's window, otherwise the window of the selected part const auto* workbook = dynamic_cast(aspect->parentAspect()); auto* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (workbook) win = workbook->mdiSubWindow(); else if (datapicker) win = datapicker->mdiSubWindow(); else win = part->mdiSubWindow(); if (m_mdiArea && m_mdiArea->subWindowList().indexOf(win) == -1) { if (dynamic_cast(part)) m_mdiArea->addSubWindow(win, Qt::Tool); else m_mdiArea->addSubWindow(win); win->show(); //Qt provides its own "system menu" for every sub-window. The shortcut for the close-action //in this menu collides with our global m_closeAction. //remove the shortcuts in the system menu to avoid this collision. QMenu* menu = win->systemMenu(); if (menu) { for (QAction* action : menu->actions()) action->setShortcut(QKeySequence()); } } if (m_mdiArea) m_mdiArea->setActiveSubWindow(win); } else { //activate the mdiView of the parent, if a child was selected const AbstractAspect* parent = aspect->parentAspect(); if (parent) { activateSubWindowForAspect(parent); //if the parent's parent is a Workbook (a column of a spreadsheet in workbook was selected), //we need to select the corresponding tab in WorkbookView too if (parent->parentAspect()) { auto* workbook = dynamic_cast(parent->parentAspect()); auto* datapicker = dynamic_cast(parent->parentAspect()); if (!datapicker) datapicker = dynamic_cast(parent->parentAspect()->parentAspect()); if (workbook) workbook->childSelected(parent); else if (datapicker) datapicker->childSelected(parent); } } } return; } void MainWin::setMdiWindowVisibility(QAction* action) { m_project->setMdiWindowVisibility((Project::MdiWindowVisibility)(action->data().toInt())); } /*! shows the sub window of a worksheet, matrix or a spreadsheet. Used if the window was closed before and the user asks to show the window again via the context menu in the project explorer. */ void MainWin::handleShowSubWindowRequested() { activateSubWindowForAspect(m_currentAspect); } /*! this is called on a right click on the root folder in the project explorer */ void MainWin::createContextMenu(QMenu* menu) const { QAction* firstAction = nullptr; // if we're populating the context menu for the project explorer, then //there're already actions available there. Skip the first title-action //and insert the action at the beginning of the menu. if (menu->actions().size()>1) firstAction = menu->actions().at(1); menu->insertMenu(firstAction, m_newMenu); //The tabbed view collides with the visibility policy for the subwindows. //Hide the menus for the visibility policy if the tabbed view is used. if (m_mdiArea->viewMode() != QMdiArea::TabbedView) { menu->insertSeparator(firstAction); menu->insertMenu(firstAction, m_visibilityMenu); menu->insertSeparator(firstAction); } } /*! this is called on a right click on a non-root folder in the project explorer */ void MainWin::createFolderContextMenu(const Folder* folder, QMenu* menu) const { Q_UNUSED(folder); //Folder provides it's own context menu. Add a separator before adding additional actions. menu->addSeparator(); this->createContextMenu(menu); } void MainWin::undo() { WAIT_CURSOR; m_project->undoStack()->undo(); if (m_project->undoStack()->index() == 0) { setCaption(m_project->name()); m_saveAction->setEnabled(false); m_undoAction->setEnabled(false); m_project->setChanged(false); } m_redoAction->setEnabled(true); RESET_CURSOR; } void MainWin::redo() { WAIT_CURSOR; m_project->undoStack()->redo(); projectChanged(); if (m_project->undoStack()->index() == m_project->undoStack()->count()) m_redoAction->setEnabled(false); RESET_CURSOR; } /*! Shows/hides mdi sub-windows depending on the current visibility policy. */ void MainWin::updateMdiWindowVisibility() const { QList windows = m_mdiArea->subWindowList(); PartMdiView* part_view; switch (m_project->mdiWindowVisibility()) { case Project::allMdiWindows: for (auto* window : windows) window->show(); break; case Project::folderOnly: for (auto* window : windows) { part_view = qobject_cast(window); Q_ASSERT(part_view); if (part_view->part()->folder() == m_currentFolder) part_view->show(); else part_view->hide(); } break; case Project::folderAndSubfolders: for (auto* window : windows) { part_view = qobject_cast(window); if (part_view->part()->isDescendantOf(m_currentFolder)) part_view->show(); else part_view->hide(); } break; } } void MainWin::toggleDockWidget(QAction* action) { if (action->objectName() == "toggle_project_explorer_dock") { if (m_projectExplorerDock->isVisible()) m_projectExplorerDock->hide(); // toggleHideWidget(m_projectExplorerDock, true); else m_projectExplorerDock->show(); // toggleShowWidget(m_projectExplorerDock, true); } else if (action->objectName() == "toggle_properties_explorer_dock") { if (m_propertiesDock->isVisible()) m_propertiesDock->hide(); // toggleHideWidget(m_propertiesDock, false); else m_propertiesDock->show(); // toggleShowWidget(m_propertiesDock, false); } } /* void MainWin::toggleHideWidget(QWidget* widget, bool hideToLeft) { auto* timeline = new QTimeLine(800, this); timeline->setEasingCurve(QEasingCurve::InOutQuad); connect(timeline, &QTimeLine::valueChanged, [=] { const qreal value = timeline->currentValue(); const int widgetWidth = widget->width(); const int widgetPosY = widget->pos().y(); int moveX = 0; if (hideToLeft) { moveX = static_cast(value * widgetWidth) - widgetWidth; } else { const int frameRight = this->frameGeometry().right(); moveX = frameRight - static_cast(value * widgetWidth); } widget->move(moveX, widgetPosY); }); timeline->setDirection(QTimeLine::Backward); timeline->start(); connect(timeline, &QTimeLine::finished, [widget] {widget->hide();}); connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater); } void MainWin::toggleShowWidget(QWidget* widget, bool showToRight) { auto* timeline = new QTimeLine(800, this); timeline->setEasingCurve(QEasingCurve::InOutQuad); connect(timeline, &QTimeLine::valueChanged, [=]() { if (widget->isHidden()) { widget->show(); } const qreal value = timeline->currentValue(); const int widgetWidth = widget->width(); const int widgetPosY = widget->pos().y(); int moveX = 0; if (showToRight) { moveX = static_cast(value * widgetWidth) - widgetWidth; } else { const int frameRight = this->frameGeometry().right(); moveX = frameRight - static_cast(value * widgetWidth); } widget->move(moveX, widgetPosY); }); timeline->setDirection(QTimeLine::Forward); timeline->start(); connect(timeline, &QTimeLine::finished, timeline, &QTimeLine::deleteLater); } */ void MainWin::projectExplorerDockVisibilityChanged(bool visible) { m_toggleProjectExplorerDockAction->setChecked(visible); } void MainWin::propertiesDockVisibilityChanged(bool visible) { m_togglePropertiesDockAction->setChecked(visible); } void MainWin::cursorDockVisibilityChanged(bool visible) { //if the cursor dock was closed, switch to the "Select and Edit" mouse mode if (!visible) { // auto* worksheet = activeWorksheet(); //TODO: } } void MainWin::cartesianPlotMouseModeChanged(CartesianPlot::MouseMode mode) { if (mode != CartesianPlot::Cursor) { if (cursorDock) cursorDock->hide(); } else { if (!cursorDock) { cursorDock = new QDockWidget(i18n("Cursor"), this); cursorWidget = new CursorDock(cursorDock); cursorDock->setWidget(cursorWidget); connect(cursorDock, &QDockWidget::visibilityChanged, this, &MainWin::cursorDockVisibilityChanged); // cursorDock->setFloating(true); // does not work. Don't understand why // if (m_propertiesDock) // tabifyDockWidget(cursorDock, m_propertiesDock); // else addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, cursorDock); } auto* worksheet = static_cast(QObject::sender()); cursorWidget->setWorksheet(worksheet); cursorDock->show(); } } void MainWin::toggleFullScreen() { if (this->windowState() == Qt::WindowFullScreen) this->setWindowState(m_lastWindowState); else { m_lastWindowState = this->windowState(); this->showFullScreen(); } } void MainWin::closeEvent(QCloseEvent* event) { m_closing = true; if (!this->closeProject()) { m_closing = false; event->ignore(); } } void MainWin::dragEnterEvent(QDragEnterEvent* event) { event->accept(); } void MainWin::dropEvent(QDropEvent* event) { if (event->mimeData() && !event->mimeData()->urls().isEmpty()) { QUrl url = event->mimeData()->urls().at(0); const QString& f = url.toLocalFile(); bool open = Project::isLabPlotProject(f); #ifdef HAVE_LIBORIGIN if (!open) open = OriginProjectParser::isOriginProject(f); #endif #ifdef HAVE_CANTOR_LIBS if (!open) { QFileInfo fi(f); open = (fi.completeSuffix() == QLatin1String("cws")) || (fi.completeSuffix() == QLatin1String("ipynb")); } #endif if (open) openProject(f); else { if (!m_project) newProject(); importFileDialog(f); } event->accept(); } else event->ignore(); } void MainWin::handleSettingsChanges() { const KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); // if(dynamic_cast(centralWidget()) == nullptr) { // QMdiArea::ViewMode viewMode = QMdiArea::ViewMode(group.readEntry("ViewMode", 0)); // if (m_mdiArea->viewMode() != viewMode) { // m_mdiArea->setViewMode(viewMode); // if (viewMode == QMdiArea::SubWindowView) // this->updateMdiWindowVisibility(); // } // // if (m_mdiArea->viewMode() == QMdiArea::TabbedView) { // m_tileWindowsAction->setVisible(false); // m_cascadeWindowsAction->setVisible(false); // QTabWidget::TabPosition tabPosition = QTabWidget::TabPosition(group.readEntry("TabPosition", 0)); // if (m_mdiArea->tabPosition() != tabPosition) // m_mdiArea->setTabPosition(tabPosition); // } else { // m_tileWindowsAction->setVisible(true); // m_cascadeWindowsAction->setVisible(true); // } // } //autosave bool autoSave = group.readEntry("AutoSave", 0); if (m_autoSaveActive != autoSave) { m_autoSaveActive = autoSave; if (autoSave) m_autoSaveTimer.start(); else m_autoSaveTimer.stop(); } int interval = group.readEntry("AutoSaveInterval", 1); interval *= 60*1000; if (interval != m_autoSaveTimer.interval()) m_autoSaveTimer.setInterval(interval); //show memory info bool showMemoryInfo = group.readEntry(QLatin1String("ShowMemoryInfo"), true); if (m_showMemoryInfo != showMemoryInfo) { m_showMemoryInfo = showMemoryInfo; if (showMemoryInfo) { m_memoryInfoWidget = new MemoryWidget(statusBar()); statusBar()->addPermanentWidget(m_memoryInfoWidget); } else { if (m_memoryInfoWidget) { statusBar()->removeWidget(m_memoryInfoWidget); delete m_memoryInfoWidget; m_memoryInfoWidget = nullptr; } } } //update the units if (stackedWidget) { for (int i = 0; i < stackedWidget->count(); ++i) { auto* widget = stackedWidget->widget(i); BaseDock* dock = dynamic_cast(widget); if (dock) dock->updateUnits(); else { auto* labelWidget = dynamic_cast(widget); if (labelWidget) labelWidget->updateUnits(); } } } bool showWelcomeScreen = group.readEntry(QLatin1String("ShowWelcomeScreen"), true); if(m_showWelcomeScreen != showWelcomeScreen) m_showWelcomeScreen = showWelcomeScreen; } void MainWin::openDatasetExample() { newProject(); // addAspectToProject(m_welcomeScreenHelper->releaseConfiguredSpreadsheet()); } /***************************************************************************************/ /************************************** dialogs ***************************************/ /***************************************************************************************/ /*! shows the dialog with the Undo-history. */ void MainWin::historyDialog() { if (!m_project->undoStack()) return; auto* dialog = new HistoryDialog(this, m_project->undoStack(), m_undoViewEmptyLabel); int index = m_project->undoStack()->index(); if (dialog->exec() != QDialog::Accepted) { if (m_project->undoStack()->count() != 0) m_project->undoStack()->setIndex(index); } //disable undo/redo-actions if the history was cleared //(in both cases, when accepted or rejected in the dialog) if (m_project->undoStack()->count() == 0) { m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } } /*! Opens the dialog to import data to the selected workbook, spreadsheet or matrix */ void MainWin::importFileDialog(const QString& fileName) { DEBUG("MainWin::importFileDialog()"); auto* dlg = new ImportFileDialog(this, false, fileName); // select existing container if (m_currentAspect->type() == AspectType::Spreadsheet || m_currentAspect->type() == AspectType::Matrix || m_currentAspect->type() == AspectType::Workbook) dlg->setCurrentIndex(m_projectExplorer->currentIndex()); else if (m_currentAspect->type() == AspectType::Column && m_currentAspect->parentAspect()->type() == AspectType::Spreadsheet) dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importFileDialog() DONE"); } void MainWin::importSqlDialog() { DEBUG("MainWin::importSqlDialog()"); auto* dlg = new ImportSQLDatabaseDialog(this); // select existing container if (m_currentAspect->type() == AspectType::Spreadsheet || m_currentAspect->type() == AspectType::Matrix || m_currentAspect->type() == AspectType::Workbook) dlg->setCurrentIndex(m_projectExplorer->currentIndex()); else if (m_currentAspect->type() == AspectType::Column && m_currentAspect->parentAspect()->type() == AspectType::Spreadsheet) dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importSqlDialog() DONE"); } void MainWin::importProjectDialog() { DEBUG("MainWin::importProjectDialog()"); ImportProjectDialog::ProjectType type; if (QObject::sender() == m_importOpjAction) type = ImportProjectDialog::ProjectOrigin; else type = ImportProjectDialog::ProjectLabPlot; auto* dlg = new ImportProjectDialog(this, type); // set current folder dlg->setCurrentFolder(m_currentFolder); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importProjectDialog() DONE"); } /*! * \brief opens a dialog to import datasets */ void MainWin::importDatasetDialog() { ImportDatasetDialog* dlg = new ImportDatasetDialog(this); if (dlg->exec() == QDialog::Accepted) { Spreadsheet* spreadsheet = new Spreadsheet(i18n("Dataset%1", 1)); DatasetHandler* dataset = new DatasetHandler(spreadsheet); dlg->importToDataset(dataset, statusBar()); QTimer timer; timer.setSingleShot(true); QEventLoop loop; connect(dataset, &DatasetHandler::downloadCompleted, &loop, &QEventLoop::quit); connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); timer.start(1500); loop.exec(); if(timer.isActive()){ timer.stop(); addAspectToProject(spreadsheet); delete dataset; } else delete dataset; } delete dlg; } /*! opens the dialog for the export of the currently active worksheet, spreadsheet or matrix. */ void MainWin::exportDialog() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = static_cast(win)->part(); if (part->exportView()) statusBar()->showMessage(i18n("%1 exported", part->name())); } void MainWin::editFitsFileDialog() { auto* editDialog = new FITSHeaderEditDialog(this); if (editDialog->exec() == QDialog::Accepted) { if (editDialog->saved()) statusBar()->showMessage(i18n("FITS files saved")); } } /*! adds a new file data source to the current project. */ void MainWin::newLiveDataSourceActionTriggered() { ImportFileDialog* dlg = new ImportFileDialog(this, true); if (dlg->exec() == QDialog::Accepted) { if (static_cast(dlg->sourceType()) == LiveDataSource::MQTT) { #ifdef HAVE_MQTT MQTTClient* mqttClient = new MQTTClient(i18n("MQTT Client%1", 1)); dlg->importToMQTT(mqttClient); mqttClient->setName(mqttClient->clientHostName()); QVector existingClients = m_project->children(AbstractAspect::Recursive); //doesn't make sense to have more MQTTClients connected to the same broker bool found = false; for (const auto* client : existingClients) { if (client->clientHostName() == mqttClient->clientHostName() && client->clientPort() == mqttClient->clientPort()) { found = true; break; } } if (!found) addAspectToProject(mqttClient); else { delete mqttClient; QMessageBox::warning(this, "Warning", "There already is a MQTTClient with this host!"); } #endif } else { LiveDataSource* dataSource = new LiveDataSource(i18n("Live data source%1", 1), false); dlg->importToLiveDataSource(dataSource, statusBar()); addAspectToProject(dataSource); } } delete dlg; } void MainWin::addAspectToProject(AbstractAspect* aspect) { const QModelIndex& index = m_projectExplorer->currentIndex(); if (index.isValid()) { auto* parent = static_cast(index.internalPointer()); #ifdef HAVE_MQTT //doesn't make sense to add a new MQTTClient to an existing MQTTClient or to any of its successors QString className = parent->metaObject()->className(); MQTTClient* clientAncestor = parent->ancestor(); if (className == "MQTTClient") parent = parent->parentAspect(); else if (clientAncestor != nullptr) parent = clientAncestor->parentAspect(); #endif parent->folder()->addChild(aspect); } else m_project->addChild(aspect); } void MainWin::settingsDialog() { auto* dlg = new SettingsDialog(this); connect (dlg, &SettingsDialog::settingsChanged, this, &MainWin::handleSettingsChanges); // connect (dlg, &SettingsDialog::resetWelcomeScreen, this, &MainWin::resetWelcomeScreen); dlg->exec(); } #ifdef HAVE_CANTOR_LIBS void MainWin::cantorSettingsDialog() { static KCoreConfigSkeleton* emptyConfig = new KCoreConfigSkeleton(); KConfigDialog* cantorDialog = new KConfigDialog(this, QLatin1String("Cantor Settings"), emptyConfig); for (auto* backend : Cantor::Backend::availableBackends()) if (backend->config()) //It has something to configure, so add it to the dialog cantorDialog->addPage(backend->settingsWidget(cantorDialog), backend->config(), backend->name(), backend->icon()); cantorDialog->show(); } #endif diff --git a/src/kdefrontend/ThemeHandler.cpp b/src/kdefrontend/ThemeHandler.cpp index 905ec22a1..9ffa8b01c 100644 --- a/src/kdefrontend/ThemeHandler.cpp +++ b/src/kdefrontend/ThemeHandler.cpp @@ -1,256 +1,256 @@ /*************************************************************************** File : ThemeHandler.cpp Project : LabPlot Description : Widget for handling saving and loading of themes -------------------------------------------------------------------- Copyright : (C) 2016 Prakriti Bhardwaj (p_bhardwaj14@informatik.uni-kl.de) Copyright : (C) 2016-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ThemeHandler.h" #include "widgets/ThemesWidget.h" #include "backend/lib/macros.h" #include #include #include #include #include #include #include #include #include #include #include // #include /*! \class ThemeHandler \brief Provides a widget with buttons for loading of themes. Emits \c loadConfig() signal that have to be connected to the appropriate slots in the backend (plot widgets) \ingroup kdefrontend */ ThemeHandler::ThemeHandler(QWidget* parent) : QWidget(parent) { auto* horizontalLayout = new QHBoxLayout(this); horizontalLayout->setSpacing(0); horizontalLayout->setMargin(0); m_pbLoadTheme = new QPushButton(this); horizontalLayout->addWidget(m_pbLoadTheme); m_pbLoadTheme->setText(i18n("Apply Theme")); // pbSaveTheme = new QPushButton(this); // horizontalLayout->addWidget(pbSaveTheme); // pbSaveTheme->setText(i18n("Save Theme")); /* pbPublishTheme = new QPushButton(this); horizontalLayout->addWidget(pbPublishTheme); pbPublishTheme->setText("Publish Theme"); pbPublishTheme->setEnabled(false); */ connect(m_pbLoadTheme, &QPushButton::clicked, this, &ThemeHandler::showPanel); // connect( pbSaveTheme, SIGNAL(clicked()), this, SLOT(saveMenu())); // connect( pbPublishTheme, SIGNAL(clicked()), this, SLOT(publishThemes())); m_themeList = themeList(); m_pbLoadTheme->setEnabled(!m_themeList.isEmpty()); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /*! * get list of all theme files (full path) */ QStringList ThemeHandler::themeList() { DEBUG("ThemeHandler::themeList()"); // find all available themes files (system wide and user specific local files) QStringList dirs = QStandardPaths::locateAll(QStandardPaths::DataLocation, "themes", QStandardPaths::LocateDirectory); QStringList themes; for (const auto& dir : dirs) { QDirIterator it(dir, QStringList() << QStringLiteral("*"), QDir::Files); while (it.hasNext()) themes.append(it.next()); } if (!themes.isEmpty()) - DEBUG(" first theme path: " << themes.first().toStdString()); + DEBUG(" first theme path: " << STDSTRING(themes.first())); return themes; } /*! * get list of all theme names */ QStringList ThemeHandler::themes() { DEBUG("ThemeHandler::themes()"); QStringList themePaths = themeList(); QStringList themes; for (int i = 0; i < themePaths.size(); ++i) { QFileInfo fileinfo(themePaths.at(i)); themes.append(fileinfo.fileName().split('.').at(0)); } if (!themes.isEmpty()) { - DEBUG(" first theme: " << themes.first().toStdString()); + DEBUG(" first theme: " << STDSTRING(themes.first())); QDEBUG(" themes = " << themes); } return themes; } /*! * get path for theme of name 'name' */ const QString ThemeHandler::themeFilePath(const QString& name) { - DEBUG("ThemeHandler::themeFilePath() name = " << name.toStdString()); + DEBUG("ThemeHandler::themeFilePath() name = " << STDSTRING(name)); QStringList themePaths = themeList(); for (int i = 0; i < themePaths.size(); ++i) { const QString& path = themePaths.at(i); const QString& fileName = QFileInfo(path).fileName(); if (fileName == name) { - DEBUG(" theme \"" << name.toStdString() << "\" path: " << path.toStdString()); + DEBUG(" theme \"" << STDSTRING(name) << "\" path: " << STDSTRING(path)); return path; } } return QString(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void ThemeHandler::setCurrentTheme(const QString& name) { if (!name.isEmpty()) { m_pbLoadTheme->setText(i18n("Apply theme [active '%1']", name)); m_pbLoadTheme->setToolTip(i18n("Theme '%1' is active. Click on the button to change the theme.", name)); } else { m_pbLoadTheme->setText(i18n("Apply Theme")); m_pbLoadTheme->setToolTip(i18n("No theme is active. Click on the button to select a theme.")); } m_currentTheme = name; } void ThemeHandler::loadSelected(const QString& name) { emit loadThemeRequested(name); this->setCurrentTheme(name); if (!name.isEmpty()) emit info( i18n("Theme \"%1\" was loaded.", name) ); else emit info( i18n("Theming deactivated.") ); //in case a local theme file was loaded (we have write access), allow to publish it //TODO: activate this later // if (KStandardDirs::checkAccess(themeFilePath, W_OK)) { // pbPublishTheme->setEnabled(true); // m_currentLocalTheme = themeFilePath.right(themeFilePath.length() - themeFilePath.lastIndexOf(QDir::separator()) - 1); // } else { // pbPublishTheme->setEnabled(false); // m_currentLocalTheme.clear(); // } } void ThemeHandler::showPanel() { QMenu menu; ThemesWidget themeWidget(&menu); connect(&themeWidget, &ThemesWidget::themeSelected, this, &ThemeHandler::loadSelected); connect(&themeWidget, &ThemesWidget::themeSelected, &menu, &QMenu::close); connect(&themeWidget, &ThemesWidget::canceled, &menu, &QMenu::close); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&themeWidget); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width()+m_pbLoadTheme->width(),-menu.sizeHint().height()); menu.exec(m_pbLoadTheme->mapToGlobal(pos)); } // void ThemeHandler::saveMenu() { // QMenu menu; // menu.addSection(i18n("Save As")); // // // add editable action // QWidgetAction* widgetAction = new QWidgetAction(this); // QFrame* frame = new QFrame(this); // QHBoxLayout* layout = new QHBoxLayout(frame); // // QLabel* label = new QLabel(i18n("Enter name:"), frame); // layout->addWidget(label); // // QLineEdit* leFilename = new QLineEdit(QString(), frame); // layout->addWidget(leFilename); // connect(leFilename, SIGNAL(returnPressed(QString)), this, SLOT(saveNewSelected(QString))); // connect(leFilename, SIGNAL(returnPressed(QString)), &menu, SLOT(close())); // // widgetAction->setDefaultWidget(frame); // menu.addAction(widgetAction); // // QPoint pos(-menu.sizeHint().width() + m_pbSaveTheme->width(), -menu.sizeHint().height()); // menu.exec(m_pbSaveTheme->mapToGlobal(pos)); // leFilename->setFocus(); // } // void ThemeHandler::saveNewSelected(const QString& filename) { // KConfig config(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/' + "themes" + '/' + filename, KConfig::SimpleConfig); // emit saveThemeRequested(config); // emit info( i18n("New theme \"%1\" was saved.", filename) ); // // m_currentLocalTheme = filename; // m_themeList.append(config.name()); // // //enable the publish button so the newly created theme can be published // //TODO: enable this later // // pbPublishTheme->setEnabled(true); // } /*! opens the dialog to upload the currently selected local theme. The publish button is only enabled if a local theme was loaded or one of the themes was modified and saved locally. */ // void ThemeHandler::publishThemes() { // int ret = KMessageBox::questionYesNo(this, // i18n("Do you want to upload your theme %1 to public web server?", m_currentLocalTheme), // i18n("Publish Theme")); // if (ret != KMessageBox::Yes) // return; // // // creating upload dialog // KNS3::UploadDialog dialog("labplot2_themes.knsrc", this); // dialog.setUploadFile(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + '/' + "themes" + '/' + m_currentLocalTheme); // dialog.setUploadName(m_currentLocalTheme); // //dialog.setDescription(); TODO: allow the user to provide a short description for the theme to be uploaded // dialog.exec(); // } diff --git a/src/kdefrontend/datasources/FITSOptionsWidget.cpp b/src/kdefrontend/datasources/FITSOptionsWidget.cpp index 96ce6e7b2..8bebfcf8a 100644 --- a/src/kdefrontend/datasources/FITSOptionsWidget.cpp +++ b/src/kdefrontend/datasources/FITSOptionsWidget.cpp @@ -1,182 +1,182 @@ /*************************************************************************** File : FITSOptionsWidget.cpp Project : LabPlot Description : Widget providing options for the import of FITS data -------------------------------------------------------------------- Copyright : (C) 2016 Fabian Kristof (fkristofszabolcs@gmail.com) 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 "FITSOptionsWidget.h" #include "ImportFileWidget.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/lib/macros.h" FITSOptionsWidget::FITSOptionsWidget(QWidget* parent, ImportFileWidget* fileWidget) : QWidget(parent), m_fileWidget(fileWidget) { ui.setupUi(parent); ui.twExtensions->headerItem()->setText(0, i18n("Content")); ui.twExtensions->setSelectionMode(QAbstractItemView::SingleSelection); ui.twExtensions->setAlternatingRowColors(true); ui.twExtensions->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.twPreview->setEditTriggers(QAbstractItemView::NoEditTriggers); ui.bRefreshPreview->setIcon( QIcon::fromTheme("view-refresh") ); connect(ui.twExtensions, &QTreeWidget::itemSelectionChanged, this, &FITSOptionsWidget::fitsTreeWidgetSelectionChanged); connect(ui.bRefreshPreview, &QPushButton::clicked, fileWidget, &ImportFileWidget::refreshPreview); } void FITSOptionsWidget::clear() { ui.twExtensions->clear(); ui.twPreview->clear(); } QString FITSOptionsWidget::currentExtensionName() { QString name; if (ui.twExtensions->currentItem() != nullptr && ui.twExtensions->currentItem()->text(0) != i18n("Primary header")) name = ui.twExtensions->currentItem()->text(ui.twExtensions->currentColumn()); return name; } void FITSOptionsWidget::updateContent(FITSFilter* filter, const QString& fileName) { - DEBUG("FITSOptionsWidget::updateContent() file name = " << fileName.toStdString()); + DEBUG("FITSOptionsWidget::updateContent() file name = " << STDSTRING(fileName)); ui.twExtensions->clear(); filter->parseExtensions(fileName, ui.twExtensions, true); DEBUG("FITSOptionsWidget::updateContent() DONE"); } /*! updates the selected var name of a NetCDF file when the tree widget item is selected */ //TODO void FITSOptionsWidget::fitsTreeWidgetSelectionChanged() { DEBUG("fitsTreeWidgetSelectionChanges()"); QDEBUG("SELECTED ITEMS =" << ui.twExtensions->selectedItems()); if (ui.twExtensions->selectedItems().isEmpty()) return; QTreeWidgetItem* item = ui.twExtensions->selectedItems().first(); int column = ui.twExtensions->currentColumn(); WAIT_CURSOR; const QString& itemText = item->text(column); QString selectedExtension; int extType = 0; if (itemText.contains(QLatin1String("IMAGE #")) || itemText.contains(QLatin1String("ASCII_TBL #")) || itemText.contains(QLatin1String("BINARY_TBL #"))) extType = 1; else if (!itemText.compare(i18n("Primary header"))) extType = 2; if (extType == 0) { if (item->parent() != nullptr) { if (item->parent()->parent() != nullptr) selectedExtension = item->parent()->parent()->text(0) + QLatin1String("[") + item->text(column) + QLatin1String("]"); } } else if (extType == 1) { if (item->parent() != nullptr) { if (item->parent()->parent() != nullptr) { bool ok; int hduNum = itemText.rightRef(1).toInt(&ok); selectedExtension = item->parent()->parent()->text(0) + QLatin1String("[") + QString::number(hduNum-1) + QLatin1String("]"); } } } else { if (item->parent()->parent() != nullptr) selectedExtension = item->parent()->parent()->text(column); } if (!selectedExtension.isEmpty()) { auto filter = static_cast(m_fileWidget->currentFileFilter()); bool readFitsTableToMatrix; const QVector importedStrings = filter->readChdu(selectedExtension, &readFitsTableToMatrix, ui.sbPreviewLines->value()); emit m_fileWidget->checkedFitsTableToMatrix(readFitsTableToMatrix); const int rows = importedStrings.size(); ui.twPreview->clear(); ui.twPreview->setRowCount(rows); int colCount = 0; const int maxColumns = 300; for (int i = 0; i < rows; ++i) { QStringList lineString = importedStrings[i]; if (i == 0) { colCount = lineString.size() > maxColumns ? maxColumns : lineString.size(); ui.twPreview->setColumnCount(colCount); } colCount = lineString.size() > maxColumns ? maxColumns : lineString.size(); for (int j = 0; j < colCount; ++j) { auto* item = new QTableWidgetItem(lineString[j]); ui.twPreview->setItem(i, j, item); } } ui.twPreview->resizeColumnsToContents(); } RESET_CURSOR; } /*! return list of selected FITS extension names */ const QStringList FITSOptionsWidget::selectedExtensions() const { QStringList names; for (const auto* item : ui.twExtensions->selectedItems()) names << item->text(0); return names; } const QString FITSOptionsWidget::extensionName(bool* ok) { if (ui.twExtensions->currentItem() != nullptr) { const QTreeWidgetItem* item = ui.twExtensions->currentItem(); const int currentColumn = ui.twExtensions->currentColumn(); QString itemText = item->text(currentColumn); int extType = 0; if (itemText.contains(QLatin1String("IMAGE #")) || itemText.contains(QLatin1String("ASCII_TBL #")) || itemText.contains(QLatin1String("BINARY_TBL #"))) extType = 1; else if (!itemText.compare(i18n("Primary header"))) extType = 2; if (extType == 0) { if (item->parent() != nullptr && item->parent()->parent() != nullptr) return item->parent()->parent()->text(0) + QLatin1String("[")+ item->text(currentColumn) + QLatin1String("]"); } else if (extType == 1) { if (item->parent() != nullptr && item->parent()->parent() != nullptr) { int hduNum = itemText.rightRef(1).toInt(ok); return item->parent()->parent()->text(0) + QLatin1String("[") + QString::number(hduNum-1) + QLatin1String("]"); } } else { if (item->parent()->parent() != nullptr) return item->parent()->parent()->text(currentColumn); } } return QString(); } diff --git a/src/kdefrontend/datasources/ImportDatasetWidget.cpp b/src/kdefrontend/datasources/ImportDatasetWidget.cpp index 7e480e659..2a493b1b4 100644 --- a/src/kdefrontend/datasources/ImportDatasetWidget.cpp +++ b/src/kdefrontend/datasources/ImportDatasetWidget.cpp @@ -1,627 +1,627 @@ /*************************************************************************** File : ImportDatasetWidget.cpp Project : LabPlot Description : import online dataset widget -------------------------------------------------------------------- Copyright : (C) 2019 Kovacs Ferencz (kferike98@gmail.com) Copyright : (C) 2019 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/DatasetHandler.h" #include "kdefrontend/datasources/ImportDatasetWidget.h" #include "kdefrontend/DatasetModel.h" #include #include #include #include #include #include #include #include #include #include #include #include /*! \class ImportDatasetWidget \brief Widget for importing data from a dataset. \ingroup kdefrontend */ ImportDatasetWidget::ImportDatasetWidget(QWidget* parent) : QWidget(parent), m_networkManager(new QNetworkAccessManager(this)) { ui.setupUi(this); m_jsonDir = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("datasets"), QStandardPaths::LocateDirectory); loadCategories(); ui.lwDatasets->setSelectionMode(QAbstractItemView::SingleSelection); ui.twCategories->setSelectionMode(QAbstractItemView::SingleSelection); const int size = ui.leSearch->height(); ui.lSearch->setPixmap( QIcon::fromTheme(QLatin1String("edit-find")).pixmap(size, size) ); QString info = i18n("Enter the keyword you want to search for"); ui.lSearch->setToolTip(info); ui.leSearch->setToolTip(info); ui.leSearch->setPlaceholderText(i18n("Search...")); ui.leSearch->setFocus(); connect(ui.cbCollections, static_cast(&QComboBox::currentIndexChanged), this, &ImportDatasetWidget::collectionChanged); connect(ui.twCategories, &QTreeWidget::itemDoubleClicked, this, &ImportDatasetWidget::updateDatasets); connect(ui.twCategories, &QTreeWidget::itemSelectionChanged, [this] { if(!m_initializing) updateDatasets(ui.twCategories->selectedItems().first()); }); connect(ui.leSearch, &QLineEdit::textChanged, this, &ImportDatasetWidget::updateCategories); connect(ui.lwDatasets, &QListWidget::itemSelectionChanged, [this]() { if (!m_initializing) datasetChanged(); }); connect(ui.lwDatasets, &QListWidget::doubleClicked, [this]() {emit datasetDoubleClicked(); }); connect(m_networkManager, &QNetworkAccessManager::finished, this, &ImportDatasetWidget::downloadFinished); //select the last used collection KConfigGroup conf(KSharedConfig::openConfig(), "ImportDatasetWidget"); const QString& collection = conf.readEntry("Collection", QString()); if (collection.isEmpty()) ui.cbCollections->setCurrentIndex(0); else { for (int i = 0; i < ui.cbCollections->count(); ++i) { if (ui.cbCollections->itemData(i).toString() == collection) { ui.cbCollections->setCurrentIndex(i); break; } } } } ImportDatasetWidget::~ImportDatasetWidget() { delete m_model; //save the selected collection if (ui.cbCollections->currentIndex() != -1) { KConfigGroup conf(KSharedConfig::openConfig(), "ImportDatasetWidget"); conf.writeEntry("Collection", ui.cbCollections->itemData(ui.cbCollections->currentIndex()).toString()); } } /** * @brief Processes the json metadata file that contains the list of categories and subcategories and their datasets. */ void ImportDatasetWidget::loadCategories() { m_datasetsMap.clear(); ui.cbCollections->clear(); const QString collectionsFileName = m_jsonDir + QLatin1String("/DatasetCollections.json"); QFile file(collectionsFileName); if (file.open(QIODevice::ReadOnly)) { QJsonDocument document = QJsonDocument::fromJson(file.readAll()); file.close(); if (!document.isArray()) { QDEBUG("Invalid definition of " + collectionsFileName); return; } m_collections = document.array(); for (int col = 0; col < m_collections.size(); col++) { const QJsonObject& collection = m_collections[col].toObject(); const QString& m_collection = collection[QLatin1String("name")].toString(); QString path = m_jsonDir + QLatin1Char('/') + m_collection + ".json"; QFile collectionFile(path); if (collectionFile.open(QIODevice::ReadOnly)) { QJsonDocument collectionDocument = QJsonDocument::fromJson(collectionFile.readAll()); if (!collectionDocument.isObject()) { QDEBUG("Invalid definition of " + path); continue; } const QJsonObject& collectionObject = collectionDocument.object(); const QJsonArray& categoryArray = collectionObject.value(QLatin1String("categories")).toArray(); //processing categories for(int i = 0 ; i < categoryArray.size(); ++i) { const QJsonObject& currentCategory = categoryArray[i].toObject(); const QString& categoryName = currentCategory.value(QLatin1String("name")).toString(); const QJsonArray& subcategories = currentCategory.value(QLatin1String("subcategories")).toArray(); //processing subcategories for(int j = 0; j < subcategories.size(); ++j) { QJsonObject currentSubCategory = subcategories[j].toObject(); QString subcategoryName = currentSubCategory.value(QLatin1String("name")).toString(); const QJsonArray& datasetArray = currentSubCategory.value(QLatin1String("datasets")).toArray(); //processing the datasets of the actual subcategory for (const auto& dataset : datasetArray) m_datasetsMap[m_collection][categoryName][subcategoryName].push_back(dataset.toObject().value(QLatin1String("filename")).toString()); } } } } if(m_model) delete m_model; m_model = new DatasetModel(m_datasetsMap); //Fill up collections combo box ui.cbCollections->addItem(i18n("All") + QLatin1String(" (") + QString::number(m_model->allDatasetsList().toStringList().size()) + QLatin1Char(')')); for (const QString& collection : m_model->collections()) ui.cbCollections->addItem(collection + QLatin1String(" (") + QString::number(m_model->datasetCount(collection)) + QLatin1Char(')'), collection); collectionChanged(ui.cbCollections->currentIndex()); } else QMessageBox::critical(this, i18n("File not found"), i18n("Couldn't open the dataset collections file %1. Please check your installation.", collectionsFileName)); } /** * Shows all categories and sub-categories for the currently selected collection */ void ImportDatasetWidget::collectionChanged(int index) { m_allCollections = (index == 0); if (!m_allCollections) m_collection = ui.cbCollections->itemData(index).toString(); else m_collection = ""; //update the info field QString info; if (!m_allCollections) { for (int i = 0; i < m_collections.size(); ++i) { const QJsonObject& collection = m_collections[i].toObject(); if ( m_collection == collection[QLatin1String("name")].toString() ) { info += collection[QLatin1String("description")].toString(); info += QLatin1String("



"); break; } } } else { for (int i = 0; i < m_collections.size(); ++i) { const QJsonObject& collection = m_collections[i].toObject(); info += collection[QLatin1String("description")].toString(); info += QLatin1String("

"); } } ui.lInfo->setText(info); updateCategories(); //update the completer if(m_completer) delete m_completer; //add all categories, sub-categories and the dataset names for the current collection QStringList keywords; for(auto category : m_model->categories(m_collection)) { keywords << category; for(auto subcategory : m_model->subcategories(m_collection, category)) { keywords << subcategory; for (QString dataset : m_model->datasets(m_collection, category, subcategory)) keywords << dataset; } } m_completer = new QCompleter(keywords, this); m_completer->setCompletionMode(QCompleter::PopupCompletion); m_completer->setCaseSensitivity(Qt::CaseSensitive); ui.leSearch->setCompleter(m_completer); } void ImportDatasetWidget::updateCategories() { m_initializing = true; ui.twCategories->clear(); QTreeWidgetItem* rootItem = new QTreeWidgetItem(QStringList(i18n("All"))); ui.twCategories->addTopLevelItem(rootItem); const QString& filter = ui.leSearch->text(); //add categories for(auto category : m_model->categories(m_collection)) { bool categoryMatch = (filter.isEmpty() || category.startsWith(filter, Qt::CaseInsensitive)); if (categoryMatch) { QTreeWidgetItem* const item = new QTreeWidgetItem(QStringList(category)); rootItem->addChild(item); //add all sub-categories for(auto subcategory : m_model->subcategories(m_collection, category)) item->addChild(new QTreeWidgetItem(QStringList(subcategory))); } else { QTreeWidgetItem* item = nullptr; for(auto subcategory : m_model->subcategories(m_collection, category)) { bool subcategoryMatch = subcategory.startsWith(filter, Qt::CaseInsensitive); if (subcategoryMatch) { if (!item) { item = new QTreeWidgetItem(QStringList(category)); rootItem->addChild(item); item->setExpanded(true); } item->addChild(new QTreeWidgetItem(QStringList(subcategory))); } else { for (QString dataset : m_model->datasets(m_collection, category, subcategory)) { bool datasetMatch = dataset.startsWith(filter, Qt::CaseInsensitive); if (datasetMatch) { if (!item) { item = new QTreeWidgetItem(QStringList(category)); rootItem->addChild(item); item->setExpanded(true); } item->addChild(new QTreeWidgetItem(QStringList(subcategory))); break; } } } } } } //remote the root item "All" if nothing has matched to the filter string if (rootItem->childCount() == 0) ui.twCategories->clear(); //expand the root item and select the first category item rootItem->setExpanded(true); if (filter.isEmpty()) { rootItem->setSelected(true); updateDatasets(rootItem); } else { if (rootItem->child(0) && rootItem->child(0)->child(0)) { rootItem->child(0)->child(0)->setSelected(true); updateDatasets(rootItem->child(0)->child(0)); } else updateDatasets(nullptr); } m_initializing = false; } /** * @brief Populates lwDatasets with the datasets of the selected subcategory or its parent * @param item the selected subcategory */ void ImportDatasetWidget::updateDatasets(QTreeWidgetItem* item) { m_initializing = true; ui.lwDatasets->clear(); if (!item) { //no category item is selected because nothing matches the search string m_initializing = false; datasetChanged(); return; } const QString& filter = ui.leSearch->text(); if(item->childCount() == 0) { //sub-category was selected -> show all its datasets m_category = item->parent()->text(0); m_subcategory = item->text(0); addDatasetItems(m_collection, m_category, m_subcategory, filter); } else { if (!item->parent()) { //top-level item "All" was selected -> show datasets for all categories and their sub-categories m_category = ""; m_subcategory = ""; for (auto category : m_model->categories(m_collection)) { for (auto subcategory : m_model->subcategories(m_collection, category)) addDatasetItems(m_collection, category, subcategory, filter); } } else { //a category was selected -> show all its datasets m_category = item->text(0); m_subcategory = ""; for (auto subcategory : m_model->subcategories(m_collection, m_category)) addDatasetItems(m_collection, m_category, subcategory, filter); } } m_initializing = false; //select the first available dataset if (ui.lwDatasets->count()) ui.lwDatasets->setCurrentRow(0); } void ImportDatasetWidget::addDatasetItems(const QString& collection, const QString& category, const QString& subcategory, const QString& filter) { if (!filter.isEmpty() && (category.startsWith(filter, Qt::CaseInsensitive) || subcategory.startsWith(filter, Qt::CaseInsensitive))) { for (QString dataset : m_model->datasets(collection, category, subcategory)) ui.lwDatasets->addItem(new QListWidgetItem(dataset)); } else { for (QString dataset : m_model->datasets(collection, category, subcategory)) { if (filter.isEmpty() || dataset.startsWith(filter, Qt::CaseInsensitive)) ui.lwDatasets->addItem(new QListWidgetItem(dataset)); } } } /** * @brief Returns the name of the selected dataset */ QString ImportDatasetWidget::getSelectedDataset() const { if (!ui.lwDatasets->selectedItems().isEmpty()) return ui.lwDatasets->selectedItems().at(0)->text(); else return QString(); } /** * @brief Initiates the processing of the dataset's metadata file and of the dataset itself. * @param datasetHandler the DatasetHanlder that downloads processes the dataset */ void ImportDatasetWidget::import(DatasetHandler* datasetHandler) { datasetHandler->processMetadata(m_datasetObject); } /** * @brief Returns the QJsonObject associated with the currently selected dataset. */ QJsonObject ImportDatasetWidget::loadDatasetObject() { for (int i = 0; i < m_collections.size(); ++i) { const QJsonObject& collectionJson = m_collections[i].toObject(); const QString& collection = collectionJson[QLatin1String("name")].toString(); //we have to find the selected collection in the metadata file. if(m_allCollections || m_collection == collection) { QFile file(m_jsonDir + QLatin1Char('/') + collection + QLatin1String(".json")); //open the metadata file of the current collection if (file.open(QIODevice::ReadOnly)) { QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); file.close(); if(!doc.isObject()) { - DEBUG("The " + collection.toStdString() + ".json file is invalid"); + DEBUG("The " << STDSTRING(collection) << ".json file is invalid"); return QJsonObject(); } QJsonArray categoryArray = doc.object().value(QLatin1String("categories")).toArray(); //processing categories for(int i = 0 ; i < categoryArray.size(); ++i) { const QJsonObject currentCategory = categoryArray[i].toObject(); const QString categoryName = currentCategory.value(QLatin1String("name")).toString(); if(m_category.isEmpty() || categoryName.compare(m_category) == 0) { const QJsonArray subcategories = currentCategory.value(QLatin1String("subcategories")).toArray(); //processing subcategories for(int j = 0; j < subcategories.size(); ++j) { QJsonObject currentSubCategory = subcategories[j].toObject(); QString subcategoryName = currentSubCategory.value(QLatin1String("name")).toString(); if(m_subcategory.isEmpty() || subcategoryName.compare(m_subcategory) == 0) { const QJsonArray datasetArray = currentSubCategory.value(QLatin1String("datasets")).toArray(); //processing the datasets of the actual subcategory for (const auto& dataset : datasetArray) { if(getSelectedDataset().compare(dataset.toObject().value(QLatin1String("filename")).toString()) == 0) return dataset.toObject(); } if (!m_subcategory.isEmpty()) break; } } if (!m_category.isEmpty()) break; } } } if (!m_allCollections) break; } } return QJsonObject(); } /** * @brief Returns the structure containing the categories, subcategories and datasets. * @return the structure containing the categories, subcategories and datasets */ const DatasetsMap& ImportDatasetWidget::getDatasetsMap() { return m_datasetsMap; } /** * @brief Sets the currently selected collection * @param category the name of the collection */ void ImportDatasetWidget::setCollection(const QString& collection) { ui.cbCollections->setCurrentText(collection + QLatin1String(" (") + QString(m_model->datasetCount(collection)) + QLatin1Char(')')); } /** * @brief Sets the currently selected category * @param category the name of the category */ void ImportDatasetWidget::setCategory(const QString &category) { for(int i = 0; i < ui.twCategories->topLevelItemCount(); i++) { if (ui.twCategories->topLevelItem(i)->text(0).compare(category) == 0) { updateDatasets(ui.twCategories->topLevelItem(i)); break; } } } /** * @brief Sets the currently selected subcategory * @param subcategory the name of the subcategory */ void ImportDatasetWidget::setSubcategory(const QString &subcategory) { for(int i = 0; i < ui.twCategories->topLevelItemCount(); i++) { if (ui.twCategories->topLevelItem(i)->text(0).compare(m_category) == 0) { QTreeWidgetItem* categoryItem = ui.twCategories->topLevelItem(i); for(int j = 0; j childCount(); j++) { if(categoryItem->child(j)->text(0).compare(subcategory) == 0) { updateDatasets(categoryItem->child(j)); break; } } break; } } } /** * @brief Sets the currently selected dataset * @param the currently selected dataset */ void ImportDatasetWidget::setDataset(const QString &datasetName) { for(int i = 0; i < ui.lwDatasets->count() ; i++) { if(ui.lwDatasets->item(i)->text().compare(datasetName) == 0) { ui.lwDatasets->item(i)->setSelected(true); break; } } } /** * @brief Updates the details of the currently selected dataset */ void ImportDatasetWidget::datasetChanged() { QString dataset = getSelectedDataset(); //no need to fetch the same dataset description again if it's already shown if (m_collection == m_prevCollection && m_category == m_prevCategory && m_subcategory == m_prevSubcategory && dataset == m_prevDataset) return; m_prevCollection = m_collection; m_prevCategory = m_category; m_prevSubcategory = m_subcategory; m_prevDataset = dataset; QString info; if (ui.cbCollections->currentIndex() != 0) { const QString& m_collection = ui.cbCollections->itemData(ui.cbCollections->currentIndex()).toString(); for (int i = 0; i < m_collections.size(); ++i) { const QJsonObject& collection = m_collections[i].toObject(); if ( m_collection.startsWith(collection[QLatin1String("name")].toString()) ) { info += collection[QLatin1String("description")].toString(); info += QLatin1String("

"); break; } } } if(!dataset.isEmpty()) { m_datasetObject = loadDatasetObject(); info += QLatin1String("") + i18n("Dataset") + QLatin1String(":
"); info += m_datasetObject[QLatin1String("name")].toString(); info += QLatin1String("

"); if (m_datasetObject.contains(QLatin1String("description_url"))) { WAIT_CURSOR; if (m_networkManager->networkAccessible() == QNetworkAccessManager::Accessible) m_networkManager->get(QNetworkRequest(QUrl(m_datasetObject[QLatin1String("description_url")].toString()))); else info += m_datasetObject[QLatin1String("description")].toString(); } else { info += QLatin1String("") + i18n("Description") + QLatin1String(":
"); info += m_datasetObject[QLatin1String("description")].toString(); } } else m_datasetObject = QJsonObject(); ui.lInfo->setText(info); emit datasetSelected(); } void ImportDatasetWidget::downloadFinished(QNetworkReply* reply) { if (reply->error() == QNetworkReply::NoError) { QByteArray ba = reply->readAll(); QString info(ba); if (m_collection == QLatin1String("Rdatasets")) { //detailed descriptions for R is in html format, //remove the header from the html file since we construct our own header int headerStart = info.indexOf(QLatin1String("")); int headerEnd = info.indexOf(QLatin1String("")); info = info.left(headerStart) + info.right(info.length() - headerEnd - 7); headerStart = info.indexOf(QLatin1String("")); info = info.left(headerStart) + info.right(info.length() - headerEnd - 8); headerStart = info.indexOf(QLatin1String("

")); headerEnd = info.indexOf(QLatin1String("

")); info = info.left(headerStart) + info.right(info.length() - headerEnd - 5); info = info.replace(QLatin1String("\n\n\n\n\n\n"), QLatin1String("")); info = info.remove(QLatin1String("\n\n\n")); } else info = info.replace(QLatin1Char('\n'), QLatin1String("
")); //do further collection specific replacements to get better formatting if (m_collection == QLatin1String("JSEDataArchive")) { info = info.replace(QLatin1String("NAME:"), QLatin1String("NAME:"), Qt::CaseInsensitive); info = info.replace(QLatin1String("TYPE:"), QLatin1String("TYPE:"), Qt::CaseSensitive); info = info.replace(QLatin1String("SIZE:"), QLatin1String("SIZE:"), Qt::CaseSensitive); info = info.replace(QLatin1String("DESCRIPTIVE ABSTRACT:"), QLatin1String("DESCRIPTIVE ABSTRACT:"), Qt::CaseInsensitive); info = info.replace(QLatin1String("NOTE:"), QLatin1String("NOTE:"), Qt::CaseSensitive); info = info.replace(QLatin1String("SPECIAL NOTES:"), QLatin1String("SPECIAL NOTES:"), Qt::CaseSensitive); info = info.replace(QLatin1String("SOURCE:"), QLatin1String("SOURCE:"), Qt::CaseSensitive); info = info.replace(QLatin1String("SOURCES:"), QLatin1String("SOURCES:"), Qt::CaseInsensitive); info = info.replace(QLatin1String("DATA SOURCE:"), QLatin1String("DATA SOURCE:"), Qt::CaseSensitive); info = info.replace(QLatin1String("DATASET LAYOUT:"), QLatin1String("DATASET LAYOUT:"), Qt::CaseSensitive); info = info.replace(QLatin1String("DATASETS LAYOUT:"), QLatin1String("DATASETS LAYOUT:"), Qt::CaseSensitive); info = info.replace(QLatin1String("VARIABLE DESCRIPTIONS:"), QLatin1String("VARIABLE DESCRIPTIONS:"), Qt::CaseSensitive); info = info.replace(QLatin1String("VARIABLES DESCRIPTIONS:"), QLatin1String("VARIABLES DESCRIPTIONS:"), Qt::CaseSensitive); info = info.replace(QLatin1String("RELATED DATASETS:"), QLatin1String("RELATED DATASETS:"), Qt::CaseSensitive); info = info.replace(QLatin1String("SPECIAL NOTES:"), QLatin1String("SPECIAL NOTES:"), Qt::CaseSensitive); info = info.replace(QLatin1String("STORY BEHIND THE DATA:"), QLatin1String("STORY BEHIND THE DATA:"), Qt::CaseSensitive); info = info.replace(QLatin1String("THE STORY BEHIND THE DATA:"), QLatin1String("THE STORY BEHIND THE DATA:"), Qt::CaseSensitive); info = info.replace(QLatin1String("PEDAGOGICAL NOTES:"), QLatin1String("PEDAGOGICAL NOTES:"), Qt::CaseSensitive); info = info.replace(QLatin1String("REFERENCE:"), QLatin1String("REFERENCE:"), Qt::CaseSensitive); info = info.replace(QLatin1String("REFERENCES:"), QLatin1String("REFERENCES:"), Qt::CaseSensitive); info = info.replace(QLatin1String("SUBMITTED BY:"), QLatin1String("SUBMITTED BY:"), Qt::CaseSensitive); } ui.lInfo->setText(ui.lInfo->text() + info); } else { DEBUG("Failed to fetch the description."); ui.lInfo->setText(ui.lInfo->text() + m_datasetObject[QLatin1String("description")].toString()); } reply->deleteLater(); RESET_CURSOR; } diff --git a/src/kdefrontend/datasources/ImportFileDialog.cpp b/src/kdefrontend/datasources/ImportFileDialog.cpp index 089d8ac79..48cc5eeca 100644 --- a/src/kdefrontend/datasources/ImportFileDialog.cpp +++ b/src/kdefrontend/datasources/ImportFileDialog.cpp @@ -1,522 +1,522 @@ /*************************************************************************** File : ImportDialog.cc Project : LabPlot Description : import file data dialog -------------------------------------------------------------------- Copyright : (C) 2008-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2008-2015 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 "ImportFileDialog.h" #include "ImportFileWidget.h" #include "backend/core/AspectTreeModel.h" #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/datasources/filters/filters.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/core/Workbook.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/MainWin.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class ImportFileDialog \brief Dialog for importing data from a file. Embeds \c ImportFileWidget and provides the standard buttons. \ingroup kdefrontend */ ImportFileDialog::ImportFileDialog(MainWin* parent, bool liveDataSource, const QString& fileName) : ImportDialog(parent), m_importFileWidget(new ImportFileWidget(this, liveDataSource, fileName)) { vLayout->addWidget(m_importFileWidget); //dialog buttons QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Reset |QDialogButtonBox::Cancel); okButton = buttonBox->button(QDialogButtonBox::Ok); m_optionsButton = buttonBox->button(QDialogButtonBox::Reset); //we highjack the default "Reset" button and use if for showing/hiding the options okButton->setEnabled(false); //ok is only available if a valid container was selected vLayout->addWidget(buttonBox); //hide the data-source related widgets if (!liveDataSource) setModel(); //Signals/Slots connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); if (!liveDataSource) setWindowTitle(i18nc("@title:window", "Import Data to Spreadsheet or Matrix")); else setWindowTitle(i18nc("@title:window", "Add New Live Data Source")); setWindowIcon(QIcon::fromTheme("document-import-database")); //restore saved settings if available create(); // ensure there's a window created QApplication::processEvents(QEventLoop::AllEvents, 0); m_importFileWidget->loadSettings(); KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); if (conf.exists()) { m_showOptions = conf.readEntry("ShowOptions", false); KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else resize(QSize(0, 0).expandedTo(minimumSize())); m_importFileWidget->showOptions(m_showOptions); //do the signal-slot connections after all settings were loaded in import file widget and check the OK button after this connect(m_importFileWidget, &ImportFileWidget::checkedFitsTableToMatrix, this, &ImportFileDialog::checkOnFitsTableToMatrix); connect(m_importFileWidget, static_cast(&ImportFileWidget::fileNameChanged), this, &ImportFileDialog::checkOkButton); connect(m_importFileWidget, static_cast(&ImportFileWidget::sourceTypeChanged), this, &ImportFileDialog::checkOkButton); connect(m_importFileWidget, &ImportFileWidget::hostChanged, this, &ImportFileDialog::checkOkButton); connect(m_importFileWidget, &ImportFileWidget::portChanged, this, &ImportFileDialog::checkOkButton); //TODO: do we really need to check the ok button when the preview was refreshed? //If not, remove this together with the previewRefreshed signal in ImportFileWidget //connect(m_importFileWidget, &ImportFileWidget::previewRefreshed, this, &ImportFileDialog::checkOkButton); #ifdef HAVE_MQTT connect(m_importFileWidget, &ImportFileWidget::subscriptionsChanged, this, &ImportFileDialog::checkOkButton); connect(m_importFileWidget, &ImportFileWidget::checkFileType, this, &ImportFileDialog::checkOkButton); #endif m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); connect(m_optionsButton, &QPushButton::clicked, this, &ImportFileDialog::toggleOptions); ImportFileDialog::checkOkButton(); } ImportFileDialog::~ImportFileDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); conf.writeEntry("ShowOptions", m_showOptions); if (cbPosition) conf.writeEntry("Position", cbPosition->currentIndex()); KWindowConfig::saveWindowSize(windowHandle(), conf); } int ImportFileDialog::sourceType() const { return static_cast(m_importFileWidget->currentSourceType()); } /*! triggers data import to the live data source \c source */ void ImportFileDialog::importToLiveDataSource(LiveDataSource* source, QStatusBar* statusBar) const { DEBUG("ImportFileDialog::importToLiveDataSource()"); m_importFileWidget->saveSettings(source); //show a progress bar in the status bar auto* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(source->filter(), &AbstractFileFilter::completed, progressBar, &QProgressBar::setValue); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QElapsedTimer timer; timer.start(); DEBUG(" Initial read()"); source->read(); statusBar->showMessage( i18n("Live data source created in %1 seconds.", (float)timer.elapsed()/1000) ); RESET_CURSOR; statusBar->removeWidget(progressBar); } #ifdef HAVE_MQTT /*! triggers data import to the MQTTClient \c client */ void ImportFileDialog::importToMQTT(MQTTClient* client) const{ m_importFileWidget->saveMQTTSettings(client); client->read(); client->ready(); } #endif /*! triggers data import to the currently selected data container */ void ImportFileDialog::importTo(QStatusBar* statusBar) const { DEBUG("ImportFileDialog::importTo()"); QDEBUG(" cbAddTo->currentModelIndex() =" << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { DEBUG("ERROR in importTo(): No aspect available"); DEBUG(" cbAddTo->currentModelIndex().isValid() = " << cbAddTo->currentModelIndex().isValid()); DEBUG(" cbAddTo->currentModelIndex() row/column = " << cbAddTo->currentModelIndex().row() << ' ' << cbAddTo->currentModelIndex().column()); return; } if (m_importFileWidget->isFileEmpty()) { KMessageBox::information(nullptr, i18n("No data to import."), i18n("No Data")); return; } QString fileName = m_importFileWidget->fileName(); auto filter = m_importFileWidget->currentFileFilter(); auto mode = AbstractFileFilter::ImportMode(cbPosition->currentIndex()); //show a progress bar in the status bar auto* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(filter, &AbstractFileFilter::completed, progressBar, &QProgressBar::setValue); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QApplication::processEvents(QEventLoop::AllEvents, 100); QElapsedTimer timer; timer.start(); if (aspect->inherits(AspectType::Matrix)) { DEBUG("ImportFileDialog::importTo(): to Matrix"); auto* matrix = qobject_cast(aspect); filter->readDataFromFile(fileName, matrix, mode); } else if (aspect->inherits(AspectType::Spreadsheet)) { DEBUG("ImportFileDialog::importTo(): to Spreadsheet"); auto* spreadsheet = qobject_cast(aspect); DEBUG(" Calling filter->readDataFromFile() with spreadsheet " << spreadsheet); filter->readDataFromFile(fileName, spreadsheet, mode); } else if (aspect->inherits(AspectType::Workbook)) { DEBUG("ImportFileDialog::importTo(): to Workbook"); auto* workbook = static_cast(aspect); workbook->setUndoAware(false); auto sheets = workbook->children(); AbstractFileFilter::FileType fileType = m_importFileWidget->currentFileType(); // multiple data sets/variables for HDF5, NetCDF and ROOT if (fileType == AbstractFileFilter::HDF5 || fileType == AbstractFileFilter::NETCDF || fileType == AbstractFileFilter::ROOT) { QStringList names; if (fileType == AbstractFileFilter::HDF5) names = m_importFileWidget->selectedHDF5Names(); else if (fileType == AbstractFileFilter::NETCDF) names = m_importFileWidget->selectedNetCDFNames(); else names = m_importFileWidget->selectedROOTNames(); int nrNames = names.size(), offset = sheets.size(); //TODO: think about importing multiple sets into one sheet int start = 0; // add nrNames sheets (0 to nrNames) //in replace mode add only missing sheets (from offset to nrNames) //and rename the already available sheets if (mode == AbstractFileFilter::Replace) { start = offset; // if there are more available spreadsheets, than needed, // delete the unneeded spreadsheets if (offset > nrNames) { for (int i = nrNames; i < offset; i++) sheets[i]->remove(); offset = nrNames; } //rename the available sheets for (int i = 0; i < offset; ++i) { //HDF5 variable names contain the whole path, remove it and keep the name only QString sheetName = names.at(i); if (fileType == AbstractFileFilter::HDF5) sheetName = names[i].mid(names[i].lastIndexOf("/") + 1); auto* sheet = sheets.at(i); sheet->setUndoAware(false); sheet->setName(sheetName); sheet->setUndoAware(true); } } // add additional spreadsheets for (int i = start; i < nrNames; ++i) { //HDF5 variable names contain the whole path, remove it and keep the name only QString sheetName = names.at(i); if (fileType == AbstractFileFilter::HDF5) sheetName = names[i].mid(names[i].lastIndexOf("/") + 1); auto* spreadsheet = new Spreadsheet(sheetName); if (mode == AbstractFileFilter::Prepend && !sheets.isEmpty()) workbook->insertChildBefore(spreadsheet, sheets[0]); else workbook->addChildFast(spreadsheet); } // start at offset for append, else at 0 if (mode != AbstractFileFilter::Append) offset = 0; // import all sets to a different sheet sheets = workbook->children(); for (int i = 0; i < nrNames; ++i) { if (fileType == AbstractFileFilter::HDF5) static_cast(filter)->setCurrentDataSetName(names[i]); else if (fileType == AbstractFileFilter::NETCDF) static_cast(filter)->setCurrentVarName(names[i]); else static_cast(filter)->setCurrentObject(names[i]); int index = i + offset; filter->readDataFromFile(fileName, qobject_cast(sheets[index])); } workbook->setUndoAware(true); } else { // single import file types // use active spreadsheet/matrix if present, else new spreadsheet auto* sheet = workbook->currentSpreadsheet(); if (sheet) filter->readDataFromFile(fileName, sheet, mode); else { workbook->setUndoAware(true); auto* spreadsheet = new Spreadsheet(fileName); workbook->addChild(spreadsheet); workbook->setUndoAware(false); filter->readDataFromFile(fileName, spreadsheet, mode); } } } statusBar->showMessage(i18n("File %1 imported in %2 seconds.", fileName, (float)timer.elapsed()/1000)); RESET_CURSOR; statusBar->removeWidget(progressBar); } void ImportFileDialog::toggleOptions() { m_importFileWidget->showOptions(!m_showOptions); m_showOptions = !m_showOptions; m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); //resize the dialog layout()->activate(); resize( QSize(this->width(), 0).expandedTo(minimumSize()) ); } void ImportFileDialog::checkOnFitsTableToMatrix(const bool enable) { if (cbAddTo) { QDEBUG("cbAddTo->currentModelIndex() = " << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { DEBUG("ERROR: no aspect available."); return; } if (aspect->inherits(AspectType::Matrix)) { okButton->setEnabled(enable); if (enable) okButton->setToolTip(i18n("Close the dialog and import the data.")); else okButton->setToolTip(i18n("Cannot import into a matrix since the data contains non-numerical data.")); } } } void ImportFileDialog::checkOkButton() { DEBUG("ImportFileDialog::checkOkButton()"); if (cbAddTo) { //only check for the target container when no file data source is being added QDEBUG(" cbAddTo->currentModelIndex() = " << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { okButton->setEnabled(false); okButton->setToolTip(i18n("Select a data container where the data has to be imported into.")); lPosition->setEnabled(false); cbPosition->setEnabled(false); cbAddTo->setFocus(); //set the focus to make the user aware about the fact that a data container needs to be provided return; } else { lPosition->setEnabled(true); cbPosition->setEnabled(true); //when doing ASCII import to a matrix, hide the options for using the file header (first line) //to name the columns since the column names are fixed in a matrix const auto* matrix = dynamic_cast(aspect); m_importFileWidget->showAsciiHeaderOptions(matrix == nullptr); } } QString fileName = m_importFileWidget->fileName(); #ifndef HAVE_WINDOWS if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, m_importFileWidget->currentSourceType())); switch (m_importFileWidget->currentSourceType()) { case LiveDataSource::SourceType::FileOrPipe: { DEBUG(" fileName = " << fileName.toUtf8().constData()); const bool enable = QFile::exists(fileName); okButton->setEnabled(enable); if (enable) okButton->setToolTip(i18n("Close the dialog and import the data.")); else okButton->setToolTip(i18n("Provide an existing file.")); break; } case LiveDataSource::SourceType::LocalSocket: { const bool enable = QFile::exists(fileName); if (enable) { QLocalSocket lsocket{this}; DEBUG("CONNECT"); lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); if (lsocket.waitForConnected()) { // this is required for server that send data as soon as connected lsocket.waitForReadyRead(); DEBUG("DISCONNECT"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); } else { - DEBUG("failed connect to local socket - " << lsocket.errorString().toStdString()); + DEBUG("failed connect to local socket - " << STDSTRING(lsocket.errorString())); okButton->setEnabled(false); okButton->setToolTip(i18n("Could not connect to the provided local socket.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Selected local socket does not exist.")); } break; } case LiveDataSource::SourceType::NetworkTcpSocket: { const bool enable = !m_importFileWidget->host().isEmpty() && !m_importFileWidget->port().isEmpty(); if (enable) { QTcpSocket socket(this); socket.connectToHost(m_importFileWidget->host(), m_importFileWidget->port().toUShort(), QTcpSocket::ReadOnly); if (socket.waitForConnected()) { okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); socket.disconnectFromHost(); } else { - DEBUG("failed to connect to TCP socket - " << socket.errorString().toStdString()); + DEBUG("failed to connect to TCP socket - " << STDSTRING(socket.errorString())); okButton->setEnabled(false); okButton->setToolTip(i18n("Could not connect to the provided TCP socket.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Either the host name or the port number is missing.")); } break; } case LiveDataSource::SourceType::NetworkUdpSocket: { const bool enable = !m_importFileWidget->host().isEmpty() && !m_importFileWidget->port().isEmpty(); if (enable) { QUdpSocket socket(this); socket.bind(QHostAddress(m_importFileWidget->host()), m_importFileWidget->port().toUShort()); socket.connectToHost(m_importFileWidget->host(), 0, QUdpSocket::ReadOnly); if (socket.waitForConnected()) { okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); socket.disconnectFromHost(); // read-only socket is disconnected immediately (no waitForDisconnected()) } else { - DEBUG("failed to connect to UDP socket - " << socket.errorString().toStdString()); + DEBUG("failed to connect to UDP socket - " << STDSTRING(socket.errorString())); okButton->setEnabled(false); okButton->setToolTip(i18n("Could not connect to the provided UDP socket.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Either the host name or the port number is missing.")); } break; } case LiveDataSource::SourceType::SerialPort: { const QString sPort = m_importFileWidget->serialPort(); const int baudRate = m_importFileWidget->baudRate(); if (!sPort.isEmpty()) { QSerialPort serialPort{this}; - DEBUG(" Port: " << sPort.toStdString() << ", Settings: " << baudRate << ',' << serialPort.dataBits() + DEBUG(" Port: " << STDSTRING(sPort) << ", Settings: " << baudRate << ',' << serialPort.dataBits() << ',' << serialPort.parity() << ',' << serialPort.stopBits()); serialPort.setPortName(sPort); serialPort.setBaudRate(baudRate); const bool serialPortOpened = serialPort.open(QIODevice::ReadOnly); okButton->setEnabled(serialPortOpened); if (serialPortOpened) { okButton->setToolTip(i18n("Close the dialog and import the data.")); serialPort.close(); } else { DEBUG("Could not connect to the provided serial port"); okButton->setToolTip(i18n("Could not connect to the provided serial port.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Serial port number is missing.")); } break; } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT const bool enable = m_importFileWidget->isMqttValid(); if (enable) { okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Either there is no connection, or no subscriptions were made, or the file filter is not ASCII.")); } #endif break; } } } QString ImportFileDialog::selectedObject() const { return m_importFileWidget->selectedObject(); } diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index ca8384c1c..ce5c75036 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,2195 +1,2195 @@ /*************************************************************************** File : ImportFileWidget.cpp Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2019 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018-2019 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportFileWidget.h" #include "FileInfoDialog.h" #include "backend/datasources/filters/filters.h" #include "AsciiOptionsWidget.h" #include "BinaryOptionsWidget.h" #include "HDF5OptionsWidget.h" #include "ImageOptionsWidget.h" #include "NetCDFOptionsWidget.h" #include "FITSOptionsWidget.h" #include "JsonOptionsWidget.h" #include "ROOTOptionsWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" #include "MQTTConnectionManagerDialog.h" #include "MQTTSubscriptionWidget.h" #include #include #include #include #include #include #include #endif QString absolutePath(const QString& fileName) { #ifndef HAVE_WINDOWS // make absolute path // FIXME if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) return QDir::homePath() + QDir::separator() + fileName; #endif return fileName; } /*! \class ImportFileWidget \brief Widget for importing data from a file. \ingroup kdefrontend */ ImportFileWidget::ImportFileWidget(QWidget* parent, bool liveDataSource, const QString& fileName) : QWidget(parent), m_fileName(fileName), m_liveDataSource(liveDataSource) #ifdef HAVE_MQTT , m_connectTimeoutTimer(new QTimer(this)), m_subscriptionWidget(new MQTTSubscriptionWidget(this)) #endif { ui.setupUi(this); //add supported file types if (!liveDataSource) { ui.cbFileType->addItem(i18n("ASCII data"), AbstractFileFilter::Ascii); ui.cbFileType->addItem(i18n("Binary data"), AbstractFileFilter::Binary); ui.cbFileType->addItem(i18n("Image"), AbstractFileFilter::Image); #ifdef HAVE_HDF5 ui.cbFileType->addItem(i18n("Hierarchical Data Format 5 (HDF5)"), AbstractFileFilter::HDF5); #endif #ifdef HAVE_NETCDF ui.cbFileType->addItem(i18n("Network Common Data Format (NetCDF)"), AbstractFileFilter::NETCDF); #endif #ifdef HAVE_FITS ui.cbFileType->addItem(i18n("Flexible Image Transport System Data Format (FITS)"), AbstractFileFilter::FITS); #endif ui.cbFileType->addItem(i18n("JSON data"), AbstractFileFilter::JSON); #ifdef HAVE_ZIP ui.cbFileType->addItem(i18n("ROOT (CERN)"), AbstractFileFilter::ROOT); #endif ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), AbstractFileFilter::NgspiceRawAscii); ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), AbstractFileFilter::NgspiceRawBinary); //hide widgets relevant for live data reading only ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.lSourceType->hide(); ui.cbSourceType->hide(); ui.gbUpdateOptions->hide(); } else { ui.cbFileType->addItem(i18n("ASCII data"), AbstractFileFilter::Ascii); ui.cbFileType->addItem(i18n("Binary data"), AbstractFileFilter::Binary); #ifdef HAVE_ZIP ui.cbFileType->addItem(i18n("ROOT (CERN)"), AbstractFileFilter::ROOT); #endif ui.cbFileType->addItem(i18n("Ngspice RAW ASCII"), AbstractFileFilter::NgspiceRawAscii); ui.cbFileType->addItem(i18n("Ngspice RAW Binary"), AbstractFileFilter::NgspiceRawBinary); ui.lePort->setValidator( new QIntValidator(ui.lePort) ); ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates()); ui.cbSerialPort->addItems(LiveDataSource::availablePorts()); ui.tabWidget->removeTab(2); ui.chbLinkFile->setToolTip(i18n("If this option is checked, only the link to the file is stored in the project file but not its content.")); ui.chbRelativePath->setToolTip(i18n("If this option is checked, the relative path of the file (relative to project's folder) will be saved.")); #ifdef HAVE_MQTT m_connectTimeoutTimer->setInterval(6000); #endif } QStringList filterItems {i18n("Automatic"), i18n("Custom")}; ui.cbFilter->addItems(filterItems); //hide options that will be activated on demand ui.gbOptions->hide(); ui.gbUpdateOptions->hide(); setMQTTVisible(false); ui.cbReadingType->addItem(i18n("Whole file"), LiveDataSource::WholeFile); ui.bOpen->setIcon( QIcon::fromTheme(QLatin1String("document-open")) ); ui.bFileInfo->setIcon( QIcon::fromTheme(QLatin1String("help-about")) ); ui.bManageFilters->setIcon( QIcon::fromTheme(QLatin1String("configure")) ); ui.bSaveFilter->setIcon( QIcon::fromTheme(QLatin1String("document-save")) ); ui.bRefreshPreview->setIcon( QIcon::fromTheme(QLatin1String("view-refresh")) ); ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.tvJson->setAlternatingRowColors(true); showJsonModel(false); // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers); auto* layout = new QHBoxLayout; layout->addWidget(m_twPreview); ui.tePreview->setLayout(layout); m_twPreview->hide(); // the combobox for the import path m_cbFileName = new KUrlComboBox(KUrlComboBox::Mode::Files, this); m_cbFileName->setMaxItems(7); auto* gridLayout = dynamic_cast(ui.gbDataSource->layout()); if (gridLayout) gridLayout->addWidget(m_cbFileName, 1, 2, 1, 3); //tooltips QString info = i18n("Specify how the data source has to be processed on every read:" "
    " "
  • Continuously fixed - fixed amount of samples is processed starting from the beginning of the newly received data.
  • " "
  • From End - fixed amount of samples is processed starting from the end of the newly received data.
  • " "
  • Till the End - all newly received data is processed.
  • " "
  • Whole file - on every read the whole file is re-read completely and processed. Only available for \"File Or Named Pipe\" data sources.
  • " "
"); ui.lReadingType->setToolTip(info); ui.cbReadingType->setToolTip(info); info = i18n("Number of samples (lines) to be processed on every read.\n" "Only needs to be specified for the reading mode \"Continuously Fixed\" and \"From End\"."); ui.lSampleSize->setToolTip(info); ui.sbSampleSize->setToolTip(info); info = i18n("Specify when and how frequently the data source needs to be read:" "
    " "
  • Periodically - the data source is read periodically with user specified time interval.
  • " "
  • On New Data - the data source is read when new data arrives.
  • " "
"); ui.lUpdateType->setToolTip(info); ui.cbUpdateType->setToolTip(info); info = i18n("Specify how frequently the data source has to be read."); ui.lUpdateInterval->setToolTip(info); ui.sbUpdateInterval->setToolTip(info); info = i18n("Specify how many samples need to be kept in memory after reading.\n" "Use \"All\" if all data has to be kept."); ui.lKeepLastValues->setToolTip(info); ui.sbKeepNValues->setToolTip(info); #ifdef HAVE_MQTT ui.cbSourceType->addItem(QLatin1String("MQTT")); m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + QLatin1String("MQTT_connections"); //add subscriptions widget layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(m_subscriptionWidget); ui.frameSubscriptions->setLayout(layout); ui.bManageConnections->setIcon(QIcon::fromTheme(QLatin1String("network-server"))); ui.bManageConnections->setToolTip(i18n("Manage MQTT connections")); info = i18n("Specify the 'Last Will and Testament' message (LWT). At least one topic has to be subscribed."); ui.lLWT->setToolTip(info); ui.bLWT->setToolTip(info); ui.bLWT->setEnabled(false); ui.bLWT->setIcon(ui.bLWT->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); #endif //TODO: implement save/load of user-defined settings later and activate these buttons again ui.bSaveFilter->hide(); ui.bManageFilters->hide(); } void ImportFileWidget::loadSettings() { m_suppressRefresh = true; //load last used settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); //read the source type first since settings in fileNameChanged() depend on this ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt()); //general settings AbstractFileFilter::FileType fileType = static_cast(conf.readEntry("Type", 0)); for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == fileType) { if (ui.cbFileType->currentIndex() == i) initOptionsWidget(); else ui.cbFileType->setCurrentIndex(i); break; } } if (m_fileName.isEmpty()) { ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0)); m_cbFileName->setUrl(conf.readEntry("LastImportedFile", "")); QStringList urls = m_cbFileName->urls(); urls.append(conf.readXdgListEntry("LastImportedFiles")); m_cbFileName->setUrls(urls); filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed } else m_cbFileName->setUrl(QUrl(m_fileName)); ui.sbPreviewLines->setValue(conf.readEntry("PreviewLines", 100)); //live data related settings ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate", 13)); // index for bautrate 19200b/s ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType", (int)LiveDataSource::WholeFile)); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType", (int)LiveDataSource::NewData)); updateTypeChanged(ui.cbUpdateType->currentIndex()); ui.leHost->setText(conf.readEntry("Host","")); ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues", 0)); // keep all values ui.lePort->setText(conf.readEntry("Port","")); ui.sbSampleSize->setValue(conf.readEntry("SampleSize", 1)); ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval", 1000)); ui.chbLinkFile->setCheckState((Qt::CheckState)conf.readEntry("LinkFile", (int)Qt::CheckState::Unchecked)); ui.chbRelativePath->setCheckState((Qt::CheckState)conf.readEntry("RelativePath", (int)Qt::CheckState::Unchecked)); #ifdef HAVE_MQTT //read available MQTT connections m_initialisingMQTT = true; readMQTTConnections(); ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conf.readEntry("Connection", ""))); m_initialisingMQTT = false; m_willSettings.enabled = conf.readEntry("mqttWillEnabled", m_willSettings.enabled); m_willSettings.willRetain = conf.readEntry("mqttWillRetain", m_willSettings.willRetain); m_willSettings.willUpdateType = static_cast(conf.readEntry("mqttWillUpdateType", (int)m_willSettings.willUpdateType)); m_willSettings.willMessageType = static_cast(conf.readEntry("mqttWillMessageType", (int)m_willSettings.willMessageType)); m_willSettings.willQoS = conf.readEntry("mqttWillQoS", (int)m_willSettings.willQoS); m_willSettings.willOwnMessage = conf.readEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage); m_willSettings.willTimeInterval = conf.readEntry("mqttWillUpdateInterval", m_willSettings.willTimeInterval); const QString& willStatistics = conf.readEntry("mqttWillStatistics",""); const QStringList& statisticsList = willStatistics.split('|', QString::SplitBehavior::SkipEmptyParts); for (auto value : statisticsList) m_willSettings.willStatistics[value.toInt()] = true; #endif //initialize the slots after all settings were set in order to avoid unneeded refreshes initSlots(); //update the status of the widgets fileTypeChanged(fileType); sourceTypeChanged(currentSourceType()); readingTypeChanged(ui.cbReadingType->currentIndex()); //all set now, refresh the content of the file and the preview for the selected dataset m_suppressRefresh = false; QTimer::singleShot(100, this, [=] () { WAIT_CURSOR; if (currentSourceType() == LiveDataSource::FileOrPipe) { QString tempFileName = fileName(); const QString& fileName = absolutePath(tempFileName); if (QFile::exists(fileName)) updateContent(fileName); } refreshPreview(); RESET_CURSOR; }); } ImportFileWidget::~ImportFileWidget() { // save current settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); // general settings conf.writeEntry("Type", (int)currentFileType()); conf.writeEntry("Filter", ui.cbFilter->currentIndex()); conf.writeEntry("LastImportedFile", m_cbFileName->currentText()); conf.writeXdgListEntry("LastImportedFiles", m_cbFileName->urls()); conf.writeEntry("PreviewLines", ui.sbPreviewLines->value()); //live data related settings conf.writeEntry("SourceType", (int)currentSourceType()); conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex()); conf.writeEntry("ReadingType", ui.cbReadingType->currentIndex()); conf.writeEntry("SampleSize", ui.sbSampleSize->value()); conf.writeEntry("KeepNValues", ui.sbKeepNValues->value()); conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex()); conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex()); conf.writeEntry("Host", ui.leHost->text()); conf.writeEntry("Port", ui.lePort->text()); conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value()); conf.writeEntry("LinkFile", (int)ui.chbLinkFile->checkState()); conf.writeEntry("RelativePath", (int)ui.chbRelativePath->checkState()); #ifdef HAVE_MQTT delete m_connectTimeoutTimer; delete m_subscriptionWidget; //MQTT related settings conf.writeEntry("Connection", ui.cbConnection->currentText()); conf.writeEntry("mqttWillMessageType", static_cast(m_willSettings.willMessageType)); conf.writeEntry("mqttWillUpdateType", static_cast(m_willSettings.willUpdateType)); conf.writeEntry("mqttWillQoS", QString::number(m_willSettings.willQoS)); conf.writeEntry("mqttWillOwnMessage", m_willSettings.willOwnMessage); conf.writeEntry("mqttWillUpdateInterval", QString::number(m_willSettings.willTimeInterval)); QString willStatistics; for (int i = 0; i < m_willSettings.willStatistics.size(); ++i) { if (m_willSettings.willStatistics[i]) willStatistics += QString::number(i)+ QLatin1Char('|'); } conf.writeEntry("mqttWillStatistics", willStatistics); conf.writeEntry("mqttWillRetain", static_cast(m_willSettings.willRetain)); conf.writeEntry("mqttWillUse", static_cast(m_willSettings.enabled)); #endif // data type specific settings if (m_asciiOptionsWidget) m_asciiOptionsWidget->saveSettings(); if (m_binaryOptionsWidget) m_binaryOptionsWidget->saveSettings(); if (m_imageOptionsWidget) m_imageOptionsWidget->saveSettings(); if (m_jsonOptionsWidget) m_jsonOptionsWidget->saveSettings(); } void ImportFileWidget::initSlots() { //SLOTs for the general part of the data source configuration connect(ui.cbSourceType, static_cast(&QComboBox::currentIndexChanged), this, static_cast(&ImportFileWidget::sourceTypeChanged)); connect(m_cbFileName, &KUrlComboBox::urlActivated, this, [=](const QUrl &url){fileNameChanged(url.path());}); connect(ui.leHost, &QLineEdit::textChanged, this, &ImportFileWidget::hostChanged); connect(ui.lePort, &QLineEdit::textChanged, this, &ImportFileWidget::portChanged); connect(ui.tvJson, &QTreeView::clicked, this, &ImportFileWidget::refreshPreview); connect(ui.bOpen, &QPushButton::clicked, this, &ImportFileWidget::selectFile); connect(ui.bFileInfo, &QPushButton::clicked, this, &ImportFileWidget::fileInfoDialog); connect(ui.bSaveFilter, &QPushButton::clicked, this, &ImportFileWidget::saveFilter); connect(ui.bManageFilters, &QPushButton::clicked, this, &ImportFileWidget::manageFilters); connect(ui.cbFileType, static_cast(&KComboBox::currentIndexChanged), this, &ImportFileWidget::fileTypeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::readingTypeChanged); connect(ui.cbFilter, static_cast(&KComboBox::activated), this, &ImportFileWidget::filterChanged); connect(ui.bRefreshPreview, &QPushButton::clicked, this, &ImportFileWidget::refreshPreview); #ifdef HAVE_MQTT connect(ui.cbConnection, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::mqttConnectionChanged); connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout); connect(ui.cbFileType, static_cast(&QComboBox::currentIndexChanged), [this]() { emit checkFileType(); }); connect(ui.bManageConnections, &QPushButton::clicked, this, &ImportFileWidget::showMQTTConnectionManager); connect(ui.bLWT, &QPushButton::clicked, this, &ImportFileWidget::showWillSettings); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::makeSubscription, this, &ImportFileWidget::mqttSubscribe); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::MQTTUnsubscribeFromTopic, this, &ImportFileWidget::unsubscribeFromTopic); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::enableWill, this, &ImportFileWidget::enableWill); connect(m_subscriptionWidget, &MQTTSubscriptionWidget::subscriptionChanged, this, &ImportFileWidget::refreshPreview); #endif } void ImportFileWidget::showAsciiHeaderOptions(bool b) { if (m_asciiOptionsWidget) m_asciiOptionsWidget->showAsciiHeaderOptions(b); } void ImportFileWidget::showJsonModel(bool b) { ui.tvJson->setVisible(b); ui.lField->setVisible(b); } void ImportFileWidget::showOptions(bool b) { ui.gbOptions->setVisible(b); if (m_liveDataSource) ui.gbUpdateOptions->setVisible(b); resize(layout()->minimumSize()); } QString ImportFileWidget::fileName() const { - DEBUG("ImportFileWidget::fileName() : " << m_cbFileName->currentText().toStdString()) + DEBUG("ImportFileWidget::fileName() : " << STDSTRING(m_cbFileName->currentText())) return m_cbFileName->currentText(); } QString ImportFileWidget::selectedObject() const { const QString& path = fileName(); //determine the file name only QString name = path.right(path.length() - path.lastIndexOf('/') - 1); //strip away the extension if available if (name.indexOf('.') != -1) name = name.left(name.lastIndexOf('.')); //for multi-dimensional formats like HDF, netCDF and FITS add the currently selected object const auto format = currentFileType(); if (format == AbstractFileFilter::HDF5) { const QStringList& hdf5Names = m_hdf5OptionsWidget->selectedNames(); if (hdf5Names.size()) name += hdf5Names.first(); //the names of the selected HDF5 objects already have '/' } else if (format == AbstractFileFilter::NETCDF) { const QStringList& names = m_netcdfOptionsWidget->selectedNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } else if (format == AbstractFileFilter::FITS) { const QString& extensionName = m_fitsOptionsWidget->currentExtensionName(); if (!extensionName.isEmpty()) name += QLatin1Char('/') + extensionName; } else if (format == AbstractFileFilter::ROOT) { const QStringList& names = m_rootOptionsWidget->selectedNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } return name; } /*! * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"), * returns \c false otherwise. */ bool ImportFileWidget::isFileEmpty() const { return m_fileEmpty; } QString ImportFileWidget::host() const { return ui.leHost->text(); } QString ImportFileWidget::port() const { return ui.lePort->text(); } QString ImportFileWidget::serialPort() const { return ui.cbSerialPort->currentText(); } int ImportFileWidget::baudRate() const { return ui.cbBaudRate->currentText().toInt(); } /*! saves the settings to the data source \c source. */ void ImportFileWidget::saveSettings(LiveDataSource* source) const { AbstractFileFilter::FileType fileType = currentFileType(); auto updateType = static_cast(ui.cbUpdateType->currentIndex()); LiveDataSource::SourceType sourceType = currentSourceType(); auto readingType = static_cast(ui.cbReadingType->currentIndex()); source->setComment( fileName() ); source->setFileType(fileType); currentFileFilter(); source->setFilter(m_currentFilter.release()); // pass ownership of the filter to the LiveDataSource source->setSourceType(sourceType); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: source->setFileName(fileName()); source->setFileLinked(ui.chbLinkFile->isChecked()); if (m_liveDataSource) source->setUseRelativePath(ui.chbRelativePath->isChecked()); break; case LiveDataSource::SourceType::LocalSocket: source->setFileName(fileName()); source->setLocalSocketName(fileName()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: source->setHost(ui.leHost->text()); source->setPort((quint16)ui.lePort->text().toInt()); break; case LiveDataSource::SourceType::SerialPort: source->setBaudRate(ui.cbBaudRate->currentText().toInt()); source->setSerialPort(ui.cbSerialPort->currentText()); break; case LiveDataSource::SourceType::MQTT: break; default: break; } //reading options source->setReadingType(readingType); source->setKeepNValues(ui.sbKeepNValues->value()); source->setUpdateType(updateType); if (updateType == LiveDataSource::UpdateType::TimeInterval) source->setUpdateInterval(ui.sbUpdateInterval->value()); if (readingType != LiveDataSource::ReadingType::TillEnd) source->setSampleSize(ui.sbSampleSize->value()); } #ifdef HAVE_MQTT /*! saves the settings to the MQTTClient \c client. */ void ImportFileWidget::saveMQTTSettings(MQTTClient* client) const { DEBUG("ImportFileWidget::saveMQTTSettings"); MQTTClient::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); MQTTClient::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); client->setComment(fileName()); currentFileFilter(); client->setFilter(static_cast(m_currentFilter.release())); // pass ownership of the filter to MQTTClient client->setReadingType(readingType); if (updateType == MQTTClient::UpdateType::TimeInterval) client->setUpdateInterval(ui.sbUpdateInterval->value()); client->setKeepNValues(ui.sbKeepNValues->value()); client->setUpdateType(updateType); if (readingType != MQTTClient::ReadingType::TillEnd) client->setSampleSize(ui.sbSampleSize->value()); client->setMQTTClientHostPort(m_client->hostname(), m_client->port()); KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); bool useID = group.readEntry("UseID").toUInt(); bool useAuthentication = group.readEntry("UseAuthentication").toUInt(); client->setMQTTUseAuthentication(useAuthentication); if (useAuthentication) client->setMQTTClientAuthentication(m_client->username(), m_client->password()); client->setMQTTUseID(useID); if (useID) client->setMQTTClientId(m_client->clientId()); for (int i = 0; i < m_mqttSubscriptions.count(); ++i) client->addInitialMQTTSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos()); const bool retain = group.readEntry("Retain").toUInt(); client->setMQTTRetain(retain); if (m_willSettings.enabled) client->setWillSettings(m_willSettings); } #endif /*! returns the currently used file type. */ AbstractFileFilter::FileType ImportFileWidget::currentFileType() const { return static_cast(ui.cbFileType->currentData().toInt()); } LiveDataSource::SourceType ImportFileWidget::currentSourceType() const { return static_cast(ui.cbSourceType->currentIndex()); } /*! returns the currently used filter. */ AbstractFileFilter* ImportFileWidget::currentFileFilter() const { DEBUG("ImportFileWidget::currentFileFilter()"); AbstractFileFilter::FileType fileType = currentFileType(); if (m_currentFilter && m_currentFilter->type() != fileType) m_currentFilter.reset(); switch (fileType) { case AbstractFileFilter::Ascii: { DEBUG(" ASCII"); if (!m_currentFilter) m_currentFilter.reset(new AsciiFilter); auto filter = static_cast(m_currentFilter.get()); if (ui.cbFilter->currentIndex() == 0) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); if (m_asciiOptionsWidget) m_asciiOptionsWidget->applyFilterSettings(filter); } else filter->loadFilterSettings(ui.cbFilter->currentText()); //save the data portion to import filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::Binary: { DEBUG(" Binary"); if (!m_currentFilter) m_currentFilter.reset(new BinaryFilter); auto filter = static_cast(m_currentFilter.get()); if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" filter->setAutoModeEnabled(true); else if (ui.cbFilter->currentIndex() == 1) { //"custom" filter->setAutoModeEnabled(false); if (m_binaryOptionsWidget) m_binaryOptionsWidget->applyFilterSettings(filter); } else { //TODO: load filter settings // filter->setFilterName( ui.cbFilter->currentText() ); } filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } case AbstractFileFilter::Image: { DEBUG(" Image"); if (!m_currentFilter) m_currentFilter.reset(new ImageFilter); auto filter = static_cast(m_currentFilter.get()); filter->setImportFormat(m_imageOptionsWidget->currentFormat()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::HDF5: { DEBUG("ImportFileWidget::currentFileFilter(): HDF5"); if (!m_currentFilter) m_currentFilter.reset(new HDF5Filter); auto filter = static_cast(m_currentFilter.get()); QStringList names = selectedHDF5Names(); QDEBUG("ImportFileWidget::currentFileFilter(): selected HDF5 names =" << names); if (!names.isEmpty()) filter->setCurrentDataSetName(names[0]); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); DEBUG("ImportFileWidget::currentFileFilter(): OK"); break; } case AbstractFileFilter::NETCDF: { DEBUG(" NETCDF"); if (!m_currentFilter) m_currentFilter.reset(new NetCDFFilter); auto filter = static_cast(m_currentFilter.get()); if (!selectedNetCDFNames().isEmpty()) filter->setCurrentVarName(selectedNetCDFNames()[0]); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::FITS: { DEBUG(" FITS"); if (!m_currentFilter) m_currentFilter.reset(new FITSFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::JSON: { DEBUG(" JSON"); if (!m_currentFilter) m_currentFilter.reset(new JsonFilter); auto filter = static_cast(m_currentFilter.get()); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); filter->setStartColumn(ui.sbStartColumn->value()); filter->setEndColumn(ui.sbEndColumn->value()); break; } case AbstractFileFilter::ROOT: { DEBUG(" ROOT"); if (!m_currentFilter) m_currentFilter.reset(new ROOTFilter); auto filter = static_cast(m_currentFilter.get()); QStringList names = selectedROOTNames(); if (!names.isEmpty()) filter->setCurrentObject(names.first()); filter->setStartRow(m_rootOptionsWidget->startRow()); filter->setEndRow(m_rootOptionsWidget->endRow()); filter->setColumns(m_rootOptionsWidget->columns()); break; } case AbstractFileFilter::NgspiceRawAscii: { DEBUG(" NgspiceRawAscii"); if (!m_currentFilter) m_currentFilter.reset(new NgspiceRawAsciiFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } case AbstractFileFilter::NgspiceRawBinary: { DEBUG(" NgspiceRawBinary"); if (!m_currentFilter) m_currentFilter.reset(new NgspiceRawBinaryFilter); auto filter = static_cast(m_currentFilter.get()); filter->setStartRow(ui.sbStartRow->value()); filter->setEndRow(ui.sbEndRow->value()); break; } } return m_currentFilter.get(); } /*! opens a file dialog and lets the user select the file data source. */ void ImportFileWidget::selectFile() { DEBUG("ImportFileWidget::selectFile()") KConfigGroup conf(KSharedConfig::openConfig(), QLatin1String("ImportFileWidget")); const QString& dir = conf.readEntry(QLatin1String("LastDir"), ""); const QString& path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir); - DEBUG(" dir = " << dir.toStdString()) - DEBUG(" path = " << path.toStdString()) + DEBUG(" dir = " << STDSTRING(dir)) + DEBUG(" path = " << STDSTRING(path)) if (path.isEmpty()) //cancel was clicked in the file-dialog return; int pos = path.lastIndexOf('/'); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry(QLatin1String("LastDir"), newDir); } //process all events after the FileDialog was closed to repaint the widget //before we start calculating the preview QApplication::processEvents(QEventLoop::AllEvents, 0); QStringList urls = m_cbFileName->urls(); urls.insert(0, QUrl::fromLocalFile(path).url()); // add type of path m_cbFileName->setUrls(urls); m_cbFileName->setCurrentText(urls.first()); - DEBUG(" combobox text = " << m_cbFileName->currentText().toStdString()) + DEBUG(" combobox text = " << STDSTRING(m_cbFileName->currentText())) fileNameChanged(path); // why do I have to call this function separately } /*! hides the MQTT related items of the widget */ void ImportFileWidget::setMQTTVisible(bool visible) { ui.lConnections->setVisible(visible); ui.cbConnection->setVisible(visible); ui.bManageConnections->setVisible(visible); //topics if (ui.cbConnection->currentIndex() != -1 && visible) { ui.lTopics->setVisible(true); ui.frameSubscriptions->setVisible(true); #ifdef HAVE_MQTT m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); #endif } else { ui.lTopics->setVisible(false); ui.frameSubscriptions->setVisible(false); #ifdef HAVE_MQTT m_subscriptionWidget->setVisible(false); m_subscriptionWidget->makeVisible(false); #endif } //will message ui.lLWT->setVisible(visible); ui.bLWT->setVisible(visible); } #ifdef HAVE_MQTT /*! * returns \c true if there is a valid connection to an MQTT broker and the user has subscribed to at least 1 topic, * returns \c false otherwise. */ bool ImportFileWidget::isMqttValid() { if (!m_client) return false; bool connected = (m_client->state() == QMqttClient::ClientState::Connected); bool subscribed = (m_subscriptionWidget->subscriptionCount() > 0); bool fileTypeOk = false; if (this->currentFileType() == AbstractFileFilter::FileType::Ascii) fileTypeOk = true; return connected && subscribed && fileTypeOk; } /*! *\brief Unsubscribes from the given topic, and removes any data connected to it * * \param topicName the name of a topic we want to unsubscribe from */ void ImportFileWidget::unsubscribeFromTopic(const QString& topicName, QVector children) { if (topicName.isEmpty()) return; QMqttTopicFilter filter{topicName}; m_client->unsubscribe(filter); for (int i = 0; i< m_mqttSubscriptions.count(); ++i) if (m_mqttSubscriptions[i]->topic().filter() == topicName) { m_mqttSubscriptions.remove(i); break; } QMapIterator j(m_lastMessage); while (j.hasNext()) { j.next(); if (MQTTSubscriptionWidget::checkTopicContains(topicName, j.key().name())) m_lastMessage.remove(j.key()); } for (int i = 0; i < m_subscribedTopicNames.size(); ++i) { if (MQTTSubscriptionWidget::checkTopicContains(topicName, m_subscribedTopicNames[i])) { m_subscribedTopicNames.remove(i); i--; } } if (m_willSettings.willTopic == topicName) { if (m_subscriptionWidget->subscriptionCount() > 0) m_willSettings.willTopic = children[0]->text(0); else m_willSettings.willTopic.clear(); } //signals that there was a change among the subscribed topics emit subscriptionsChanged(); refreshPreview(); } #endif /************** SLOTS **************************************************************/ /*! called on file name changes. Determines the file format (ASCII, binary etc.), if the file exists, and activates the corresponding options. */ void ImportFileWidget::fileNameChanged(const QString& name) { - DEBUG("ImportFileWidget::fileNameChanged() : " << name.toStdString()) + DEBUG("ImportFileWidget::fileNameChanged() : " << STDSTRING(name)) const QString fileName = absolutePath(name); bool fileExists = QFile::exists(fileName); if (fileExists) m_cbFileName->setStyleSheet(QString()); else m_cbFileName->setStyleSheet("QComboBox{background:red;}"); ui.gbOptions->setEnabled(fileExists); ui.bManageFilters->setEnabled(fileExists); ui.cbFilter->setEnabled(fileExists); ui.cbFileType->setEnabled(fileExists); ui.bFileInfo->setEnabled(fileExists); ui.gbUpdateOptions->setEnabled(fileExists); if (!fileExists) { //file doesn't exist -> delete the content preview that is still potentially //available from the previously selected file ui.tePreview->clear(); m_twPreview->clear(); initOptionsWidget(); emit fileNameChanged(); return; } if (currentSourceType() == LiveDataSource::FileOrPipe) { const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName); for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == fileType) { // automatically select a new file type if (ui.cbFileType->currentIndex() != i) { ui.cbFileType->setCurrentIndex(i); // will call the slot fileTypeChanged which updates content and preview //automatically set the comma separator if a csv file was selected if (fileType == AbstractFileFilter::Ascii && name.endsWith(QLatin1String("csv"), Qt::CaseInsensitive)) m_asciiOptionsWidget->setSeparatingCharacter(QLatin1Char(',')); emit fileNameChanged(); return; } else { initOptionsWidget(); //automatically set the comma separator if a csv file was selected if (fileType == AbstractFileFilter::Ascii && name.endsWith(QLatin1String("csv"), Qt::CaseInsensitive)) m_asciiOptionsWidget->setSeparatingCharacter(QLatin1Char(',')); updateContent(fileName); break; } } } } emit fileNameChanged(); refreshPreview(); } /*! saves the current filter settings */ void ImportFileWidget::saveFilter() { bool ok; QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); if (ok && !text.isEmpty()) { //TODO //AsciiFilter::saveFilter() } } /*! opens a dialog for managing all available predefined filters. */ void ImportFileWidget::manageFilters() { //TODO } /*! Depending on the selected file type, activates the corresponding options in the data portion tab and populates the combobox with the available pre-defined filter settings for the selected type. */ void ImportFileWidget::fileTypeChanged(int index) { Q_UNUSED(index); AbstractFileFilter::FileType fileType = currentFileType(); DEBUG("ImportFileWidget::fileTypeChanged " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); initOptionsWidget(); //default ui.lFilter->show(); ui.cbFilter->show(); //different file types show different number of tabs in ui.tabWidget. //when switching from the previous file type we re-set the tab widget to its original state //and remove/add the required tabs further below for (int i = 0; icount(); ++i) ui.tabWidget->removeTab(0); ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); if (!m_liveDataSource) ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); ui.lPreviewLines->show(); ui.sbPreviewLines->show(); ui.lStartColumn->show(); ui.sbStartColumn->show(); ui.lEndColumn->show(); ui.sbEndColumn->show(); showJsonModel(false); switch (fileType) { case AbstractFileFilter::Ascii: break; case AbstractFileFilter::Binary: ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); break; case AbstractFileFilter::ROOT: ui.tabWidget->removeTab(1); // falls through case AbstractFileFilter::HDF5: case AbstractFileFilter::NETCDF: case AbstractFileFilter::FITS: ui.lFilter->hide(); ui.cbFilter->hide(); // hide global preview tab. we have our own ui.tabWidget->setTabText(0, i18n("Data format && preview")); ui.tabWidget->removeTab(1); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::Image: ui.lFilter->hide(); ui.cbFilter->hide(); ui.lPreviewLines->hide(); ui.sbPreviewLines->hide(); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: ui.lFilter->hide(); ui.cbFilter->hide(); ui.lStartColumn->hide(); ui.sbStartColumn->hide(); ui.lEndColumn->hide(); ui.sbEndColumn->hide(); ui.tabWidget->removeTab(0); ui.tabWidget->setCurrentIndex(0); break; case AbstractFileFilter::JSON: ui.lFilter->hide(); ui.cbFilter->hide(); showJsonModel(true); break; default: DEBUG("unknown file type"); } int lastUsedFilterIndex = ui.cbFilter->currentIndex(); ui.cbFilter->clear(); ui.cbFilter->addItem( i18n("Automatic") ); ui.cbFilter->addItem( i18n("Custom") ); //TODO: populate the combobox with the available pre-defined filter settings for the selected type ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); filterChanged(lastUsedFilterIndex); if (currentSourceType() == LiveDataSource::FileOrPipe) { QString tempFileName = fileName(); const QString& fileName = absolutePath(tempFileName); if (QFile::exists(fileName)) updateContent(fileName); } //for file types other than ASCII and binary we support re-reading the whole file only //select "read whole file" and deactivate the combobox if (m_liveDataSource && (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary)) { ui.cbReadingType->setCurrentIndex(LiveDataSource::ReadingType::WholeFile); ui.cbReadingType->setEnabled(false); } else ui.cbReadingType->setEnabled(true); refreshPreview(); } // file type specific option widgets void ImportFileWidget::initOptionsWidget() { DEBUG("ImportFileWidget::initOptionsWidget for " << ENUM_TO_STRING(AbstractFileFilter, FileType, currentFileType())); switch (currentFileType()) { case AbstractFileFilter::Ascii: { if (!m_asciiOptionsWidget) { QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); m_asciiOptionsWidget->loadSettings(); ui.swOptions->addWidget(asciiw); } //for MQTT topics we don't allow to set the vector names since the different topics //can have different number of columns bool isMQTT = (currentSourceType() == LiveDataSource::MQTT); m_asciiOptionsWidget->showAsciiHeaderOptions(!isMQTT); m_asciiOptionsWidget->showTimestampOptions(isMQTT); ui.swOptions->setCurrentWidget(m_asciiOptionsWidget->parentWidget()); break; } case AbstractFileFilter::Binary: if (!m_binaryOptionsWidget) { QWidget* binaryw = new QWidget(); m_binaryOptionsWidget = std::unique_ptr(new BinaryOptionsWidget(binaryw)); ui.swOptions->addWidget(binaryw); m_binaryOptionsWidget->loadSettings(); } ui.swOptions->setCurrentWidget(m_binaryOptionsWidget->parentWidget()); break; case AbstractFileFilter::Image: if (!m_imageOptionsWidget) { QWidget* imagew = new QWidget(); m_imageOptionsWidget = std::unique_ptr(new ImageOptionsWidget(imagew)); ui.swOptions->addWidget(imagew); m_imageOptionsWidget->loadSettings(); } ui.swOptions->setCurrentWidget(m_imageOptionsWidget->parentWidget()); break; case AbstractFileFilter::HDF5: if (!m_hdf5OptionsWidget) { QWidget* hdf5w = new QWidget(); m_hdf5OptionsWidget = std::unique_ptr(new HDF5OptionsWidget(hdf5w, this)); ui.swOptions->addWidget(hdf5w); } else m_hdf5OptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_hdf5OptionsWidget->parentWidget()); break; case AbstractFileFilter::NETCDF: if (!m_netcdfOptionsWidget) { QWidget* netcdfw = new QWidget(); m_netcdfOptionsWidget = std::unique_ptr(new NetCDFOptionsWidget(netcdfw, this)); ui.swOptions->insertWidget(AbstractFileFilter::NETCDF, netcdfw); } else m_netcdfOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_netcdfOptionsWidget->parentWidget()); break; case AbstractFileFilter::FITS: if (!m_fitsOptionsWidget) { QWidget* fitsw = new QWidget(); m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->addWidget(fitsw); } else m_fitsOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_fitsOptionsWidget->parentWidget()); break; case AbstractFileFilter::JSON: if (!m_jsonOptionsWidget) { QWidget* jsonw = new QWidget(); m_jsonOptionsWidget = std::unique_ptr(new JsonOptionsWidget(jsonw, this)); ui.tvJson->setModel(m_jsonOptionsWidget->model()); ui.swOptions->addWidget(jsonw); m_jsonOptionsWidget->loadSettings(); } else m_jsonOptionsWidget->clearModel(); ui.swOptions->setCurrentWidget(m_jsonOptionsWidget->parentWidget()); showJsonModel(true); break; case AbstractFileFilter::ROOT: if (!m_rootOptionsWidget) { QWidget* rootw = new QWidget(); m_rootOptionsWidget = std::unique_ptr(new ROOTOptionsWidget(rootw, this)); ui.swOptions->addWidget(rootw); } else m_rootOptionsWidget->clear(); ui.swOptions->setCurrentWidget(m_rootOptionsWidget->parentWidget()); break; case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } } const QStringList ImportFileWidget::selectedHDF5Names() const { return m_hdf5OptionsWidget->selectedNames(); } const QStringList ImportFileWidget::selectedNetCDFNames() const { return m_netcdfOptionsWidget->selectedNames(); } const QStringList ImportFileWidget::selectedFITSExtensions() const { return m_fitsOptionsWidget->selectedExtensions(); } const QStringList ImportFileWidget::selectedROOTNames() const { return m_rootOptionsWidget->selectedNames(); } /*! shows the dialog with the information about the file(s) to be imported. */ void ImportFileWidget::fileInfoDialog() { QStringList files = fileName().split(';'); auto* dlg = new FileInfoDialog(this); dlg->setFiles(files); dlg->exec(); } /*! enables the options if the filter "custom" was chosen. Disables the options otherwise. */ void ImportFileWidget::filterChanged(int index) { // ignore filter for these formats AbstractFileFilter::FileType fileType = currentFileType(); if (fileType != AbstractFileFilter::Ascii && fileType != AbstractFileFilter::Binary) { ui.swOptions->setEnabled(true); return; } if (index == 0) { // "automatic" ui.swOptions->setEnabled(false); ui.bSaveFilter->setEnabled(false); } else if (index == 1) { //custom ui.swOptions->setEnabled(true); ui.bSaveFilter->setEnabled(true); } else { // predefined filter settings were selected. //load and show them in the GUI. //TODO } } void ImportFileWidget::refreshPreview() { if (m_suppressRefresh) return; DEBUG("ImportFileWidget::refreshPreview()"); WAIT_CURSOR; QString tempFileName = fileName(); QString fileName = absolutePath(tempFileName); AbstractFileFilter::FileType fileType = currentFileType(); LiveDataSource::SourceType sourceType = currentSourceType(); int lines = ui.sbPreviewLines->value(); if (sourceType == LiveDataSource::SourceType::FileOrPipe) - DEBUG(" file name = " << fileName.toStdString()); + DEBUG(" file name = " << STDSTRING(fileName)); // generic table widget if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary || fileType == AbstractFileFilter::JSON || fileType == AbstractFileFilter::NgspiceRawAscii || fileType == AbstractFileFilter::NgspiceRawBinary) m_twPreview->show(); else m_twPreview->hide(); bool ok = true; QTableWidget* tmpTableWidget = m_twPreview; QVector importedStrings; QStringList vectorNameList; QVector columnModes; DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); switch (fileType) { case AbstractFileFilter::Ascii: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, sourceType)); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: { importedStrings = filter->preview(fileName, lines); break; } case LiveDataSource::SourceType::LocalSocket: { QLocalSocket lsocket{this}; DEBUG("Local socket: CONNECT PREVIEW"); lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); if (lsocket.waitForConnected()) { - DEBUG("connected to local socket " << fileName.toStdString()); + DEBUG("connected to local socket " << STDSTRING(fileName)); if (lsocket.waitForReadyRead()) importedStrings = filter->preview(lsocket); DEBUG("Local socket: DISCONNECT PREVIEW"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) } else - DEBUG("failed connect to local socket " << fileName.toStdString() << " - " << lsocket.errorString().toStdString()); + DEBUG("failed connect to local socket " << STDSTRING(fileName) << " - " << STDSTRING(lsocket.errorString())); break; } case LiveDataSource::SourceType::NetworkTcpSocket: { QTcpSocket tcpSocket{this}; tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly); if (tcpSocket.waitForConnected()) { DEBUG("connected to TCP socket"); if ( tcpSocket.waitForReadyRead() ) importedStrings = filter->preview(tcpSocket); tcpSocket.disconnectFromHost(); } else - DEBUG("failed to connect to TCP socket " << " - " << tcpSocket.errorString().toStdString()); + DEBUG("failed to connect to TCP socket " << " - " << STDSTRING(tcpSocket.errorString())); break; } case LiveDataSource::SourceType::NetworkUdpSocket: { QUdpSocket udpSocket{this}; DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.bind(QHostAddress(host()), port().toInt()); udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly); if (udpSocket.waitForConnected()) { - DEBUG(" connected to UDP socket " << host().toStdString() << ':' << port().toInt()); + DEBUG(" connected to UDP socket " << STDSTRING(host()) << ':' << port().toInt()); if (!udpSocket.waitForReadyRead(2000) ) DEBUG(" ERROR: not ready for read after 2 sec"); if (udpSocket.hasPendingDatagrams()) { DEBUG(" has pending data"); } else { DEBUG(" has no pending data"); } importedStrings = filter->preview(udpSocket); DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state()); udpSocket.disconnectFromHost(); } else - DEBUG("failed to connect to UDP socket " << " - " << udpSocket.errorString().toStdString()); + DEBUG("failed to connect to UDP socket " << " - " << STDSTRING(udpSocket.errorString())); break; } case LiveDataSource::SourceType::SerialPort: { QSerialPort sPort{this}; - DEBUG(" Port: " << serialPort().toStdString() << ", Settings: " << baudRate() << ',' << sPort.dataBits() + DEBUG(" Port: " << STDSTRING(serialPort()) << ", Settings: " << baudRate() << ',' << sPort.dataBits() << ',' << sPort.parity() << ',' << sPort.stopBits()); sPort.setPortName(serialPort()); sPort.setBaudRate(baudRate()); if (sPort.open(QIODevice::ReadOnly)) { if (sPort.waitForReadyRead(2000)) importedStrings = filter->preview(sPort); else DEBUG(" ERROR: not ready for read after 2 sec"); sPort.close(); } else DEBUG(" ERROR: failed to open serial port. error: " << sPort.error()); break; } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT //show the preview for the currently selected topic auto* item = m_subscriptionWidget->currentItem(); if (item && item->childCount() == 0) { //only preview if the lowest level (i.e. a topic) is selected const QString& topicName = item->text(0); auto i = m_lastMessage.find(topicName); if (i != m_lastMessage.end()) importedStrings = filter->preview(i.value().payload().data()); else importedStrings << QStringList{i18n("No data arrived yet for the selected topic")}; } #endif break; } } vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::Binary: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); break; } case AbstractFileFilter::Image: { ui.tePreview->clear(); QImage image(fileName); QTextCursor cursor = ui.tePreview->textCursor(); cursor.insertImage(image); RESET_CURSOR; return; } case AbstractFileFilter::HDF5: { DEBUG("ImportFileWidget::refreshPreview: HDF5"); auto filter = static_cast(currentFileFilter()); lines = m_hdf5OptionsWidget->lines(); importedStrings = filter->readCurrentDataSet(fileName, nullptr, ok, AbstractFileFilter::Replace, lines); tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); break; } case AbstractFileFilter::NETCDF: { auto filter = static_cast(currentFileFilter()); lines = m_netcdfOptionsWidget->lines(); importedStrings = filter->readCurrentVar(fileName, nullptr, AbstractFileFilter::Replace, lines); tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); break; } case AbstractFileFilter::FITS: { auto filter = static_cast(currentFileFilter()); lines = m_fitsOptionsWidget->lines(); QString extensionName = m_fitsOptionsWidget->extensionName(&ok); if (!extensionName.isEmpty()) { - DEBUG(" extension name = " << extensionName.toStdString()); + DEBUG(" extension name = " << STDSTRING(extensionName)); fileName = extensionName; } - DEBUG(" file name = " << fileName.toStdString()); + DEBUG(" file name = " << STDSTRING(fileName)); bool readFitsTableToMatrix; importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines); emit checkedFitsTableToMatrix(readFitsTableToMatrix); tmpTableWidget = m_fitsOptionsWidget->previewWidget(); break; } case AbstractFileFilter::JSON: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); importedStrings = filter->preview(fileName); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::ROOT: { auto filter = static_cast(currentFileFilter()); lines = m_rootOptionsWidget->lines(); m_rootOptionsWidget->setNRows(filter->rowsInCurrentObject(fileName)); importedStrings = filter->previewCurrentObject( fileName, m_rootOptionsWidget->startRow(), qMin(m_rootOptionsWidget->startRow() + lines - 1, m_rootOptionsWidget->endRow()) ); tmpTableWidget = m_rootOptionsWidget->previewWidget(); // the last vector element contains the column names vectorNameList = importedStrings.last(); importedStrings.removeLast(); columnModes = QVector(vectorNameList.size(), AbstractColumn::Numeric); break; } case AbstractFileFilter::NgspiceRawAscii: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } case AbstractFileFilter::NgspiceRawBinary: { ui.tePreview->clear(); auto filter = static_cast(currentFileFilter()); importedStrings = filter->preview(fileName, lines); vectorNameList = filter->vectorNames(); columnModes = filter->columnModes(); break; } } // fill the table widget tmpTableWidget->setRowCount(0); tmpTableWidget->setColumnCount(0); if ( !importedStrings.isEmpty() ) { if (!ok) { // show imported strings as error message tmpTableWidget->setRowCount(1); tmpTableWidget->setColumnCount(1); auto* item = new QTableWidgetItem(); item->setText(importedStrings[0][0]); tmpTableWidget->setItem(0, 0, item); } else { //TODO: maxrows not used const int rows = qMax(importedStrings.size(), 1); const int maxColumns = 300; tmpTableWidget->setRowCount(rows); for (int i = 0; i < rows; ++i) { const int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); if (cols > tmpTableWidget->columnCount()) tmpTableWidget->setColumnCount(cols); for (int j = 0; j < cols; ++j) { auto* item = new QTableWidgetItem(importedStrings[i][j]); tmpTableWidget->setItem(i, j, item); } } // set header if columnMode available for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { QString columnName = QString::number(i+1); if (i < vectorNameList.size()) columnName = vectorNameList[i]; auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); item->setTextAlignment(Qt::AlignLeft); item->setIcon(AbstractColumn::iconForMode(columnModes[i])); tmpTableWidget->setHorizontalHeaderItem(i, item); } } tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_fileEmpty = false; } else m_fileEmpty = true; emit previewRefreshed(); RESET_CURSOR; } void ImportFileWidget::updateContent(const QString& fileName) { if (m_suppressRefresh) return; QDEBUG("ImportFileWidget::updateContent(): file name = " << fileName); if (auto filter = currentFileFilter()) { switch (filter->type()) { case AbstractFileFilter::HDF5: m_hdf5OptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::NETCDF: m_netcdfOptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::FITS: #ifdef HAVE_FITS m_fitsOptionsWidget->updateContent(static_cast(filter), fileName); #endif break; case AbstractFileFilter::ROOT: m_rootOptionsWidget->updateContent(static_cast(filter), fileName); break; case AbstractFileFilter::JSON: m_jsonOptionsWidget->loadDocument(fileName); ui.tvJson->setExpanded( m_jsonOptionsWidget->model()->index(0, 0), true); //expand the root node break; case AbstractFileFilter::Ascii: case AbstractFileFilter::Binary: case AbstractFileFilter::Image: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; } } } void ImportFileWidget::updateTypeChanged(int idx) { const auto UpdateType = static_cast(idx); switch (UpdateType) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } } void ImportFileWidget::readingTypeChanged(int idx) { const auto readingType = static_cast(idx); const LiveDataSource::SourceType sourceType = currentSourceType(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } if (readingType == LiveDataSource::ReadingType::WholeFile) { ui.lKeepLastValues->hide(); ui.sbKeepNValues->hide(); } else { ui.lKeepLastValues->show(); ui.sbKeepNValues->show(); } } void ImportFileWidget::sourceTypeChanged(int idx) { const auto sourceType = static_cast(idx); // enable/disable "on new data"-option const auto* model = qobject_cast(ui.cbUpdateType->model()); QStandardItem* item = model->item(LiveDataSource::UpdateType::NewData); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: ui.lFileName->show(); m_cbFileName->show(); ui.bFileInfo->show(); ui.bOpen->show(); if (m_liveDataSource) { ui.lRelativePath->show(); ui.chbRelativePath->show(); } ui.chbLinkFile->show(); //option for sample size are available for "continuously fixed" and "from end" reading options if (ui.cbReadingType->currentIndex() < 2) { ui.lSampleSize->show(); ui.sbSampleSize->show(); } else { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); fileNameChanged(fileName()); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: ui.lHost->show(); ui.leHost->show(); ui.lePort->show(); ui.lPort->show(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::LocalSocket: ui.lFileName->show(); m_cbFileName->show(); ui.bFileInfo->hide(); ui.bOpen->show(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.chbLinkFile->hide(); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::SerialPort: ui.lBaudRate->show(); ui.cbBaudRate->show(); ui.lSerialPort->show(); ui.cbSerialPort->show(); ui.lSampleSize->show(); ui.sbSampleSize->show(); ui.lHost->hide(); ui.leHost->hide(); ui.lePort->hide(); ui.lPort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); ui.cbFileType->setEnabled(true); ui.cbFileType->show(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.lFileType->show(); setMQTTVisible(false); break; case LiveDataSource::SourceType::MQTT: #ifdef HAVE_MQTT item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //for MQTT we read ascii data only, hide the file type options for (int i = 0; i < ui.cbFileType->count(); ++i) { if (static_cast(ui.cbFileType->itemData(i).toInt()) == AbstractFileFilter::Ascii) { if (ui.cbFileType->currentIndex() == i) initOptionsWidget(); else ui.cbFileType->setCurrentIndex(i); break; } } ui.cbFileType->hide(); ui.lFileType->hide(); ui.lBaudRate->hide(); ui.cbBaudRate->hide(); ui.lSerialPort->hide(); ui.cbSerialPort->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.lFileName->hide(); m_cbFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.lRelativePath->hide(); ui.chbRelativePath->hide(); ui.chbLinkFile->hide(); setMQTTVisible(true); ui.cbFileType->setEnabled(true); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); //in case there are already connections defined, //show the available topics for the currently selected connection mqttConnectionChanged(); #endif break; } //deactivate/activate options that are specific to file of pipe sources only auto* typeModel = qobject_cast(ui.cbFileType->model()); if (sourceType != LiveDataSource::FileOrPipe) { //deactivate file types other than ascii and binary for (int i = 2; i < ui.cbFileType->count(); ++i) typeModel->item(i)->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (ui.cbFileType->currentIndex() > 1) ui.cbFileType->setCurrentIndex(1); //"whole file" read option is available for file or pipe only, disable it typeModel = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = typeModel->item(LiveDataSource::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (static_cast(ui.cbReadingType->currentIndex()) == LiveDataSource::WholeFile) ui.cbReadingType->setCurrentIndex(LiveDataSource::TillEnd); //"update options" groupbox can be deactivated for "file and pipe" if the file is invalid. //Activate the groupbox when switching from "file and pipe" to a different source type. ui.gbUpdateOptions->setEnabled(true); } else { for (int i = 2; i < ui.cbFileType->count(); ++i) typeModel->item(i)->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); //enable "whole file" item for file or pipe typeModel = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = typeModel->item(LiveDataSource::ReadingType::WholeFile); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } emit sourceTypeChanged(); refreshPreview(); } #ifdef HAVE_MQTT /*! *\brief called when a different MQTT connection is selected in the connection ComboBox. * connects to the MQTT broker according to the connection settings. */ void ImportFileWidget::mqttConnectionChanged() { if (m_initialisingMQTT || ui.cbConnection->currentIndex() == -1) return; WAIT_CURSOR; //disconnected from the broker that was selected before, if this is the case if (m_client && m_client->state() == QMqttClient::ClientState::Connected) { emit MQTTClearTopics(); disconnect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); QDEBUG("Disconnecting from " << m_client->hostname()); m_client->disconnectFromHost(); delete m_client; } //determine the connection settings for the new broker and initialize the mqtt client KConfig config(m_configPath, KConfig::SimpleConfig); KConfigGroup group = config.group(ui.cbConnection->currentText()); m_client = new QMqttClient; connect(m_client, &QMqttClient::connected, this, &ImportFileWidget::onMqttConnect); connect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); connect(m_client, &QMqttClient::messageReceived, this, &ImportFileWidget::mqttMessageReceived); connect(m_client, &QMqttClient::errorChanged, this, &ImportFileWidget::mqttErrorChanged); m_client->setHostname(group.readEntry("Host")); m_client->setPort(group.readEntry("Port").toUInt()); const bool useID = group.readEntry("UseID").toUInt(); if (useID) m_client->setClientId(group.readEntry("ClientID")); const bool useAuthentication = group.readEntry("UseAuthentication").toUInt(); if (useAuthentication) { m_client->setUsername(group.readEntry("UserName")); m_client->setPassword(group.readEntry("Password")); } //connect to the selected broker QDEBUG("Connect to " << m_client->hostname() << ":" << m_client->port()); m_connectTimeoutTimer->start(); m_client->connectToHost(); } /*! *\brief called when the client connects to the broker successfully. * subscribes to every topic (# wildcard) in order to later list every available topic */ void ImportFileWidget::onMqttConnect() { if (m_client->error() == QMqttClient::NoError) { m_connectTimeoutTimer->stop(); ui.frameSubscriptions->setVisible(true); m_subscriptionWidget->setVisible(true); m_subscriptionWidget->makeVisible(true); if (!m_client->subscribe(QMqttTopicFilter(QLatin1String("#")), 1)) QMessageBox::critical(this, i18n("Couldn't subscribe"), i18n("Couldn't subscribe to all available topics. Something went wrong")); } emit subscriptionsChanged(); RESET_CURSOR; } /*! *\brief called when the client disconnects from the broker successfully * removes every information about the former connection */ void ImportFileWidget::onMqttDisconnect() { - DEBUG("Disconected from " << m_client->hostname().toStdString()); + DEBUG("Disconnected from " << STDSTRING(m_client->hostname())); m_connectTimeoutTimer->stop(); ui.lTopics->hide(); ui.frameSubscriptions->hide(); ui.lLWT->hide(); ui.bLWT->hide(); ui.cbConnection->setItemText(ui.cbConnection->currentIndex(), ui.cbConnection->currentText() + ' ' + i18n("(Disconnected)")); emit subscriptionsChanged(); RESET_CURSOR; QMessageBox::critical(this, i18n("Disconnected"), i18n("Disconnected from the broker '%1' before the connection was successful.", m_client->hostname())); } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics */ void ImportFileWidget::mqttSubscribe(const QString& name, uint QoS) { const QMqttTopicFilter filter {name}; QMqttSubscription* tempSubscription = m_client->subscribe(filter, static_cast(QoS) ); if (tempSubscription) { m_mqttSubscriptions.push_back(tempSubscription); connect(tempSubscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); emit subscriptionsChanged(); } } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void ImportFileWidget::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message); // qDebug()<<"received " << topic.name(); if (m_addedTopics.contains(topic.name())) return; m_addedTopics.push_back(topic.name()); m_subscriptionWidget->setTopicTreeText(i18n("Available (%1)", m_addedTopics.size())); QStringList name; QString rootName; const QChar sep = '/'; if (topic.name().contains(sep)) { const QStringList& list = topic.name().split(sep, QString::SkipEmptyParts); if (!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); int topItemIdx = -1; //check whether the first level of the topic can be found in twTopics for (int i = 0; i < m_subscriptionWidget->topicCount(); ++i) { if (m_subscriptionWidget->topLevelTopic(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree if (topItemIdx < 0) { QTreeWidgetItem* currentItem = new QTreeWidgetItem(name); m_subscriptionWidget->addTopic(currentItem); for (int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { QTreeWidgetItem* currentItem = m_subscriptionWidget->topLevelTopic(topItemIdx); int listIdx = 1; for (; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; for (int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); if (childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } if (!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree for (; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topic.name(); name.append(topic.name()); m_subscriptionWidget->addTopic(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) { const QStringList subscriptionName = m_subscriptionWidget->topLevelSubscription(i)->text(0).split('/', QString::SkipEmptyParts); if (!subscriptionName.isEmpty()) { if (rootName == subscriptionName.first()) { QVector subscriptions; for(int i = 0; i < m_mqttSubscriptions.size(); ++i) subscriptions.push_back(m_mqttSubscriptions[i]->topic().filter()); emit updateSubscriptionTree(subscriptions); break; } } } //signals that a newTopic was added, in order to fill the completer of leTopics emit newTopic(rootName); } /*! *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard) */ void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) { QDEBUG("message received from: " << msg.topic().name()); if (!m_subscribedTopicNames.contains(msg.topic().name())) m_subscribedTopicNames.push_back(msg.topic().name()); //update the last message for the topic m_lastMessage[msg.topic()] = msg; } /*! *\brief called when the clientError of the MQTT client changes * * \param clientError the current error of the client */ void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) { switch (clientError) { case QMqttClient::BadUsernameOrPassword: QMessageBox::critical(this, i18n("Couldn't connect"), i18n("Wrong username or password")); break; case QMqttClient::IdRejected: QMessageBox::critical(this, i18n("Couldn't connect"), i18n("The client ID wasn't accepted")); break; case QMqttClient::ServerUnavailable: QMessageBox::critical(this, i18n("Server unavailable"), i18n("The broker couldn't be reached.")); break; case QMqttClient::NotAuthorized: QMessageBox::critical(this, i18n("Not authorized"), i18n("The client is not authorized to connect.")); break; case QMqttClient::UnknownError: QMessageBox::critical(this, i18n("Unknown MQTT error"), i18n("An unknown error occurred.")); break; case QMqttClient::NoError: case QMqttClient::InvalidProtocolVersion: case QMqttClient::TransportInvalid: case QMqttClient::ProtocolViolation: case QMqttClient::Mqtt5SpecificError: break; default: break; } } /*! *\brief called when m_connectTimeoutTimer ticks, * meaning that the client couldn't connect to the broker in 5 seconds * disconnects the client, stops the timer, and warns the user */ void ImportFileWidget::mqttConnectTimeout() { m_connectionTimedOut = true; m_client->disconnectFromHost(); m_connectTimeoutTimer->stop(); RESET_CURSOR; QMessageBox::warning(this, i18n("Warning"), i18n("Connecting to the given broker timed out! Try changing the settings")); } /*! Shows the MQTT connection manager where the connections are created and edited. The selected connection is selected in the connection combo box in this widget. */ void ImportFileWidget::showMQTTConnectionManager() { bool previousConnectionChanged = false; MQTTConnectionManagerDialog* dlg = new MQTTConnectionManagerDialog(this, ui.cbConnection->currentText(), previousConnectionChanged); if (dlg->exec() == QDialog::Accepted) { //re-read the available connections to be in sync with the changes in MQTTConnectionManager m_initialisingMQTT = true; const QString& prevConn = ui.cbConnection->currentText(); ui.cbConnection->clear(); readMQTTConnections(); m_initialisingMQTT = false; //select the connection the user has selected in MQTTConnectionManager const QString& conn = dlg->connection(); int index = ui.cbConnection->findText(conn); if (conn != prevConn) {//Current connection isn't the previous one if (ui.cbConnection->currentIndex() != index) ui.cbConnection->setCurrentIndex(index); else mqttConnectionChanged(); } else if (dlg->initialConnectionChanged()) {//Current connection is the same with previous one but it changed if (ui.cbConnection->currentIndex() == index) mqttConnectionChanged(); else ui.cbConnection->setCurrentIndex(index); } else { //Previous connection wasn't changed m_initialisingMQTT = true; ui.cbConnection->setCurrentIndex(index); m_initialisingMQTT = false; } } delete dlg; } /*! loads all available saved MQTT nconnections */ void ImportFileWidget::readMQTTConnections() { DEBUG("ImportFileWidget: reading available MQTT connections"); KConfig config(m_configPath, KConfig::SimpleConfig); for (const auto& name : config.groupList()) ui.cbConnection->addItem(name); } /*! * \brief Shows the mqtt will settings widget, which allows the user to modify the will settings */ void ImportFileWidget::showWillSettings() { QMenu menu; QVector children; for (int i = 0; i < m_subscriptionWidget->subscriptionCount(); ++i) MQTTSubscriptionWidget::findSubscriptionLeafChildren(children, m_subscriptionWidget->topLevelSubscription(i)); QVector topics; for (int i = 0; i < children.size(); ++i) topics.append(children[i]->text(0)); MQTTWillSettingsWidget willSettingsWidget(&menu, m_willSettings, topics); connect(&willSettingsWidget, &MQTTWillSettingsWidget::applyClicked, [this, &menu, &willSettingsWidget]() { m_willSettings = willSettingsWidget.will(); menu.close(); }); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&willSettingsWidget); menu.addAction(widgetAction); const QPoint pos(ui.bLWT->sizeHint().width(),ui.bLWT->sizeHint().height()); menu.exec(ui.bLWT->mapToGlobal(pos)); } void ImportFileWidget::enableWill(bool enable) { if(enable) { if(!ui.bLWT->isEnabled()) ui.bLWT->setEnabled(enable); } else ui.bLWT->setEnabled(enable); } #endif diff --git a/src/kdefrontend/datasources/NetCDFOptionsWidget.cpp b/src/kdefrontend/datasources/NetCDFOptionsWidget.cpp index 78189e20e..f4a846979 100644 --- a/src/kdefrontend/datasources/NetCDFOptionsWidget.cpp +++ b/src/kdefrontend/datasources/NetCDFOptionsWidget.cpp @@ -1,136 +1,136 @@ /*************************************************************************** File : NetCDFOptionsWidget.cpp Project : LabPlot Description : widget providing options for the import of NetCDF data -------------------------------------------------------------------- Copyright : (C) 2015-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 "NetCDFOptionsWidget.h" #include "ImportFileWidget.h" #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/lib/macros.h" #include /*! \class NetCDFOptionsWidget \brief Widget providing options for the import of NetCDF data \ingroup kdefrontend */ NetCDFOptionsWidget::NetCDFOptionsWidget(QWidget* parent, ImportFileWidget* fileWidget) : QWidget(parent), m_fileWidget(fileWidget) { ui.setupUi(parent); QStringList headers; headers << i18n("Name") << i18n("Type") << i18n("Properties") << i18n("Values"); ui.twContent->setHeaderLabels(headers); // type column is hidden ui.twContent->hideColumn(1); ui.twContent->setSelectionMode(QAbstractItemView::ExtendedSelection); ui.twContent->setAlternatingRowColors(true); ui.twContent->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.twPreview->setEditTriggers(QAbstractItemView::NoEditTriggers); ui.bRefreshPreview->setIcon( QIcon::fromTheme("view-refresh") ); connect(ui.twContent, &QTreeWidget::itemSelectionChanged, this, &NetCDFOptionsWidget::netcdfTreeWidgetSelectionChanged); connect(ui.bRefreshPreview, &QPushButton::clicked, fileWidget, &ImportFileWidget::refreshPreview); } void NetCDFOptionsWidget::clear() { ui.twContent->clear(); ui.twPreview->clear(); } void NetCDFOptionsWidget::updateContent(NetCDFFilter *filter, const QString& fileName) { ui.twContent->clear(); QTreeWidgetItem *rootItem = ui.twContent->invisibleRootItem(); filter->parse(fileName, rootItem); ui.twContent->insertTopLevelItem(0, rootItem); ui.twContent->expandAll(); ui.twContent->resizeColumnToContents(0); ui.twContent->resizeColumnToContents(2); } /*! updates the selected var name of a NetCDF file when the tree widget item is selected */ void NetCDFOptionsWidget::netcdfTreeWidgetSelectionChanged() { DEBUG("netcdfTreeWidgetSelectionChanged()"); QDEBUG("SELECTED ITEMS =" << ui.twContent->selectedItems()); if (ui.twContent->selectedItems().isEmpty()) return; QTreeWidgetItem* item = ui.twContent->selectedItems().first(); if (item->data(1, Qt::DisplayRole).toString() == "variable") m_fileWidget->refreshPreview(); else if (item->data(1, Qt::DisplayRole).toString().contains("attribute")) { // reads attributes (only for preview) auto filter = static_cast(m_fileWidget->currentFileFilter()); QString fileName = m_fileWidget->m_cbFileName->currentText(); QString name = item->data(0, Qt::DisplayRole).toString(); QString varName = item->data(1, Qt::DisplayRole).toString().split(' ')[0]; QDEBUG("name =" << name << "varName =" << varName); QString importedText = filter->readAttribute(fileName, name, varName); - DEBUG("importedText =" << importedText.toStdString()); + DEBUG("importedText =" << STDSTRING(importedText)); QStringList lineStrings = importedText.split('\n'); int rows = lineStrings.size(); ui.twPreview->setRowCount(rows); ui.twPreview->setColumnCount(0); for (int i = 0; i < rows; ++i) { QStringList lineString = lineStrings[i].split(' '); int cols = lineString.size(); if (ui.twPreview->columnCount() < cols) ui.twPreview->setColumnCount(cols); for (int j = 0; j < cols; ++j) { auto* item = new QTableWidgetItem(); item->setText(lineString[j]); ui.twPreview->setItem(i, j, item); } } } else DEBUG("non showable object selected in NetCDF tree widget"); } /*! return list of selected NetCDF item names selects all items if nothing is selected */ const QStringList NetCDFOptionsWidget::selectedNames() const { DEBUG("NetCDFOptionsWidget::selectedNames()"); QStringList names; if (ui.twContent->selectedItems().size() == 0) ui.twContent->selectAll(); for (auto* item : ui.twContent->selectedItems()) names << item->text(0); QDEBUG(" NetCDFOptionsWidget: selected names =" << names); return names; } diff --git a/src/kdefrontend/dockwidgets/ColumnDock.cpp b/src/kdefrontend/dockwidgets/ColumnDock.cpp index fa7a489a0..8f6dc37e2 100644 --- a/src/kdefrontend/dockwidgets/ColumnDock.cpp +++ b/src/kdefrontend/dockwidgets/ColumnDock.cpp @@ -1,432 +1,432 @@ /*************************************************************************** File : ColumnDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2011-2019 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013-2017 by Stefan Gerlach (stefan.gerlach@uni.kn) Description : widget for column properties ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ColumnDock.h" #include "backend/core/AbstractFilter.h" #include "backend/core/datatypes/SimpleCopyThroughFilter.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/String2DoubleFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "backend/core/datatypes/String2DateTimeFilter.h" #include "backend/datapicker/DatapickerCurve.h" #include "backend/datasources/LiveDataSource.h" #include "backend/spreadsheet/Spreadsheet.h" #include /*! \class ColumnDock \brief Provides a widget for editing the properties of the spreadsheet columns currently selected in the project explorer. \ingroup kdefrontend */ ColumnDock::ColumnDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); m_leName = ui.leName; m_leComment = ui.leComment; connect(ui.leName, &QLineEdit::textChanged, this, &ColumnDock::nameChanged); connect(ui.leComment, &QLineEdit::textChanged, this, &ColumnDock::commentChanged); connect(ui.cbType, SIGNAL(currentIndexChanged(int)), this, SLOT(typeChanged(int))); connect(ui.cbFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(formatChanged(int))); connect(ui.sbPrecision, SIGNAL(valueChanged(int)), this, SLOT(precisionChanged(int)) ); connect(ui.cbPlotDesignation, SIGNAL(currentIndexChanged(int)), this, SLOT(plotDesignationChanged(int))); retranslateUi(); } void ColumnDock::setColumns(QList list) { m_initializing = true; m_columnsList = list; m_column = list.first(); m_aspect = list.first(); //check whether we have non-editable columns: //1. columns in a LiveDataSource //2. columns in the spreadsheet of a datapicker curve //3. columns for residuals calculated in XYFitCurve) bool nonEditable = false; for (auto* col : m_columnsList) { auto* s = dynamic_cast(col->parentAspect()); if (s) { if (dynamic_cast(s) || dynamic_cast(s->parentAspect())) { nonEditable = true; break; } } else { nonEditable = true; break; } } if (list.size() == 1) { //names and comments of non-editable columns in a file data source can be changed. if (!nonEditable && dynamic_cast(m_column->parentAspect()) != nullptr) { ui.leName->setEnabled(false); ui.leComment->setEnabled(false); } else { ui.leName->setEnabled(true); ui.leComment->setEnabled(true); } ui.leName->setText(m_column->name()); ui.leComment->setText(m_column->comment()); } else { ui.leName->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(QString()); ui.leComment->setText(QString()); } ui.leName->setStyleSheet(""); ui.leName->setToolTip(""); //show the properties of the first column AbstractColumn::ColumnMode columnMode = m_column->columnMode(); this->updateFormatWidgets(columnMode); this->updateTypeWidgets(columnMode); ui.cbPlotDesignation->setCurrentIndex( int(m_column->plotDesignation()) ); // slots connect(m_column, &AbstractColumn::aspectDescriptionChanged, this, &ColumnDock::columnDescriptionChanged); connect(m_column, &AbstractColumn::modeChanged, this, &ColumnDock::columnModeChanged); connect(m_column->outputFilter(), &AbstractSimpleFilter::formatChanged, this, &ColumnDock::columnFormatChanged); connect(m_column->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &ColumnDock::columnPrecisionChanged); connect(m_column, &AbstractColumn::plotDesignationChanged, this, &ColumnDock::columnPlotDesignationChanged); //don't allow to change the column type at least one non-editable column ui.cbType->setEnabled(!nonEditable); m_initializing = false; } void ColumnDock::updateTypeWidgets(AbstractColumn::ColumnMode mode) { ui.cbType->setCurrentIndex(ui.cbType->findData((int)mode)); switch (mode) { case AbstractColumn::Numeric: { auto* filter = static_cast(m_column->outputFilter()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->numericFormat())); ui.sbPrecision->setValue(filter->numDigits()); break; } case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: { auto* filter = static_cast(m_column->outputFilter()); - DEBUG(" set column format: " << filter->format().toStdString()); + DEBUG(" set column format: " << STDSTRING(filter->format())); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->format())); break; } case AbstractColumn::Integer: // nothing to set case AbstractColumn::BigInt: case AbstractColumn::Text: break; } } /*! depending on the currently selected column type (column mode) updates the widgets for the column format, shows/hides the allowed widgets, fills the corresponding combobox with the possible entries. Called when the type (column mode) is changed. */ void ColumnDock::updateFormatWidgets(AbstractColumn::ColumnMode mode) { ui.cbFormat->clear(); switch (mode) { case AbstractColumn::Numeric: ui.cbFormat->addItem(i18n("Decimal"), QVariant('f')); ui.cbFormat->addItem(i18n("Scientific (e)"), QVariant('e')); ui.cbFormat->addItem(i18n("Scientific (E)"), QVariant('E')); ui.cbFormat->addItem(i18n("Automatic (g)"), QVariant('g')); ui.cbFormat->addItem(i18n("Automatic (G)"), QVariant('G')); break; case AbstractColumn::Month: ui.cbFormat->addItem(i18n("Number without Leading Zero"), QVariant("M")); ui.cbFormat->addItem(i18n("Number with Leading Zero"), QVariant("MM")); ui.cbFormat->addItem(i18n("Abbreviated Month Name"), QVariant("MMM")); ui.cbFormat->addItem(i18n("Full Month Name"), QVariant("MMMM")); break; case AbstractColumn::Day: ui.cbFormat->addItem(i18n("Number without Leading Zero"), QVariant("d")); ui.cbFormat->addItem(i18n("Number with Leading Zero"), QVariant("dd")); ui.cbFormat->addItem(i18n("Abbreviated Day Name"), QVariant("ddd")); ui.cbFormat->addItem(i18n("Full Day Name"), QVariant("dddd")); break; case AbstractColumn::DateTime: for (const auto& s : AbstractColumn::dateTimeFormats()) ui.cbFormat->addItem(s, QVariant(s)); break; case AbstractColumn::Integer: case AbstractColumn::BigInt: case AbstractColumn::Text: break; } if (mode == AbstractColumn::Numeric) { ui.lPrecision->show(); ui.sbPrecision->show(); } else { ui.lPrecision->hide(); ui.sbPrecision->hide(); } if (mode == AbstractColumn::Text || mode == AbstractColumn::Integer || mode == AbstractColumn::BigInt) { ui.lFormat->hide(); ui.cbFormat->hide(); } else { ui.lFormat->show(); ui.cbFormat->show(); } if (mode == AbstractColumn::DateTime) { ui.cbFormat->setEditable(true); ui.cbFormat->setCurrentItem("yyyy-MM-dd hh:mm:ss.zzz"); } else { ui.cbFormat->setEditable(false); ui.cbFormat->setCurrentIndex(0); } } //************************************************************* //******** SLOTs for changes triggered in ColumnDock ********** //************************************************************* void ColumnDock::retranslateUi() { m_initializing = true; ui.cbType->clear(); ui.cbType->addItem(i18n("Numeric"), QVariant(int(AbstractColumn::Numeric))); ui.cbType->addItem(i18n("Integer"), QVariant(int(AbstractColumn::Integer))); ui.cbType->addItem(i18n("Big Integer"), QVariant(int(AbstractColumn::BigInt))); ui.cbType->addItem(i18n("Text"), QVariant(int(AbstractColumn::Text))); ui.cbType->addItem(i18n("Month Names"), QVariant(int(AbstractColumn::Month))); ui.cbType->addItem(i18n("Day Names"), QVariant(int(AbstractColumn::Day))); ui.cbType->addItem(i18n("Date and Time"), QVariant(int(AbstractColumn::DateTime))); ui.cbPlotDesignation->clear(); ui.cbPlotDesignation->addItem(i18n("None")); ui.cbPlotDesignation->addItem(i18n("X")); ui.cbPlotDesignation->addItem(i18n("Y")); ui.cbPlotDesignation->addItem(i18n("Z")); ui.cbPlotDesignation->addItem(i18n("X-error")); ui.cbPlotDesignation->addItem(i18n("X-error -")); ui.cbPlotDesignation->addItem(i18n("X-error +")); ui.cbPlotDesignation->addItem(i18n("Y-error")); ui.cbPlotDesignation->addItem(i18n("Y-error -")); ui.cbPlotDesignation->addItem(i18n("Y-error +")); m_initializing = false; } /*! called when the type (column mode - numeric, text etc.) of the column was changed. */ void ColumnDock::typeChanged(int index) { DEBUG("ColumnDock::typeChanged()"); if (m_initializing) return; AbstractColumn::ColumnMode columnMode = (AbstractColumn::ColumnMode)ui.cbType->itemData(index).toInt(); m_initializing = true; this->updateFormatWidgets(columnMode); m_initializing = false; switch (columnMode) { case AbstractColumn::Numeric: { int digits = ui.sbPrecision->value(); for (auto* col : m_columnsList) { col->beginMacro(i18n("%1: change column type", col->name())); col->setColumnMode(columnMode); auto* filter = static_cast(col->outputFilter()); //TODO: using //char format = ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toChar().toLatin1(); //outside of the for-loop and //filter->setNumericFormat(format); //inseide the loop leads to wrong results when converting from integer to numeric -> 'f' is set instead of 'e' filter->setNumericFormat(ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toChar().toLatin1()); filter->setNumDigits(digits); col->endMacro(); } break; } case AbstractColumn::Integer: case AbstractColumn::BigInt: case AbstractColumn::Text: for (auto* col : m_columnsList) col->setColumnMode(columnMode); break; case AbstractColumn::Month: case AbstractColumn::Day: for (auto* col : m_columnsList) { col->beginMacro(i18n("%1: change column type", col->name())); // the format is saved as item data QString format = ui.cbFormat->itemData(ui.cbFormat->currentIndex()).toString(); col->setColumnMode(columnMode); auto* filter = static_cast(col->outputFilter()); filter->setFormat(format); col->endMacro(); } break; case AbstractColumn::DateTime: for (auto* col : m_columnsList) { col->beginMacro(i18n("%1: change column type", col->name())); // the format is the current text QString format = ui.cbFormat->currentText(); col->setColumnMode(columnMode); auto* filter = static_cast(col->outputFilter()); filter->setFormat(format); col->endMacro(); } break; } DEBUG("ColumnDock::typeChanged() DONE"); } /*! called when the format for the current type (column mode) was changed. */ void ColumnDock::formatChanged(int index) { DEBUG("ColumnDock::formatChanged()"); if (m_initializing) return; AbstractColumn::ColumnMode mode = (AbstractColumn::ColumnMode)ui.cbType->itemData(ui.cbType->currentIndex()).toInt(); switch (mode) { case AbstractColumn::Numeric: { char format = ui.cbFormat->itemData(index).toChar().toLatin1(); for (auto* col : m_columnsList) { auto* filter = static_cast(col->outputFilter()); filter->setNumericFormat(format); } break; } case AbstractColumn::Integer: case AbstractColumn::BigInt: case AbstractColumn::Text: break; case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: { QString format = ui.cbFormat->itemData(index).toString(); for (auto* col : m_columnsList) { auto* filter = static_cast(col->outputFilter()); filter->setFormat(format); } break; } } DEBUG("ColumnDock::formatChanged() DONE"); } void ColumnDock::precisionChanged(int digits) { if (m_initializing) return; for (auto* col : m_columnsList) { auto* filter = static_cast(col->outputFilter()); filter->setNumDigits(digits); } } void ColumnDock::plotDesignationChanged(int index) { if (m_initializing) return; auto pd = AbstractColumn::PlotDesignation(index); for (auto* col : m_columnsList) col->setPlotDesignation(pd); } //************************************************************* //********* SLOTs for changes triggered in Column ************* //************************************************************* void ColumnDock::columnDescriptionChanged(const AbstractAspect* aspect) { if (m_column != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void ColumnDock::columnModeChanged(const AbstractAspect* aspect) { if (m_column != aspect) return; m_initializing = true; AbstractColumn::ColumnMode columnMode = m_column->columnMode(); this->updateFormatWidgets(columnMode); this->updateTypeWidgets(columnMode); m_initializing = false; } void ColumnDock::columnFormatChanged() { DEBUG("ColumnDock::columnFormatChanged()"); m_initializing = true; AbstractColumn::ColumnMode columnMode = m_column->columnMode(); switch (columnMode) { case AbstractColumn::Numeric: { auto* filter = static_cast(m_column->outputFilter()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->numericFormat())); break; } case AbstractColumn::Integer: case AbstractColumn::BigInt: case AbstractColumn::Text: break; case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: { auto* filter = static_cast(m_column->outputFilter()); ui.cbFormat->setCurrentIndex(ui.cbFormat->findData(filter->format())); break; } } m_initializing = false; } void ColumnDock::columnPrecisionChanged() { m_initializing = true; auto* filter = static_cast(m_column->outputFilter()); ui.sbPrecision->setValue(filter->numDigits()); m_initializing = false; } void ColumnDock::columnPlotDesignationChanged(const AbstractColumn* col) { m_initializing = true; ui.cbPlotDesignation->setCurrentIndex( int(col->plotDesignation()) ); m_initializing = false; } diff --git a/src/kdefrontend/dockwidgets/XYCurveDock.cpp b/src/kdefrontend/dockwidgets/XYCurveDock.cpp index c83e90341..76faba21c 100644 --- a/src/kdefrontend/dockwidgets/XYCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYCurveDock.cpp @@ -1,2301 +1,2301 @@ /*************************************************************************** File : XYCurveDock.cpp Project : LabPlot Description : widget for XYCurve properties -------------------------------------------------------------------- Copyright : (C) 2010-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2017 Stefan Gerlach (stefan.gerlach@uni-konstanz.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYCurveDock.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/XYAnalysisCurve.h" #include "backend/worksheet/plots/cartesian/XYFitCurve.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/column/Column.h" #include "backend/core/Project.h" #include "backend/core/datatypes/Double2StringFilter.h" #include "backend/core/datatypes/DateTime2StringFilter.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include #include #include #include #include #include /*! \class XYCurveDock \brief Provides a widget for editing the properties of the XYCurves (2D-curves) currently selected in the project explorer. If more than one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYCurveDock::XYCurveDock(QWidget* parent) : BaseDock(parent) { ui.setupUi(this); //Tab "Values" auto* gridLayout = qobject_cast(ui.tabValues->layout()); cbValuesColumn = new TreeViewComboBox(ui.tabValues); gridLayout->addWidget(cbValuesColumn, 2, 2, 1, 1); //Tab "Filling" ui.cbFillingColorStyle->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); ui.bFillingOpen->setIcon( QIcon::fromTheme("document-open") ); ui.leFillingFileName->setCompleter(new QCompleter(new QDirModel, this)); //Tab "Error bars" gridLayout = qobject_cast(ui.tabErrorBars->layout()); cbXErrorPlusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbXErrorPlusColumn, 2, 2, 1, 1); cbXErrorMinusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbXErrorMinusColumn, 3, 2, 1, 1); cbYErrorPlusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbYErrorPlusColumn, 7, 2, 1, 1); cbYErrorMinusColumn = new TreeViewComboBox(ui.tabErrorBars); gridLayout->addWidget(cbYErrorMinusColumn, 8, 2, 1, 1); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { auto* layout = dynamic_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //Slots //Lines connect( ui.cbLineType, SIGNAL(currentIndexChanged(int)), this, SLOT(lineTypeChanged(int)) ); connect( ui.sbLineInterpolationPointsCount, SIGNAL(valueChanged(int)), this, SLOT(lineInterpolationPointsCountChanged(int)) ); connect( ui.chkLineSkipGaps, SIGNAL(clicked(bool)), this, SLOT(lineSkipGapsChanged(bool)) ); connect( ui.chkLineIncreasingXOnly, &QCheckBox::clicked, this, &XYCurveDock::lineIncreasingXOnlyChanged ); connect( ui.cbLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(lineStyleChanged(int)) ); connect( ui.kcbLineColor, SIGNAL(changed(QColor)), this, SLOT(lineColorChanged(QColor)) ); connect( ui.sbLineWidth, SIGNAL(valueChanged(double)), this, SLOT(lineWidthChanged(double)) ); connect( ui.sbLineOpacity, SIGNAL(valueChanged(int)), this, SLOT(lineOpacityChanged(int)) ); connect( ui.cbDropLineType, SIGNAL(currentIndexChanged(int)), this, SLOT(dropLineTypeChanged(int)) ); connect( ui.cbDropLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(dropLineStyleChanged(int)) ); connect( ui.kcbDropLineColor, SIGNAL(changed(QColor)), this, SLOT(dropLineColorChanged(QColor)) ); connect( ui.sbDropLineWidth, SIGNAL(valueChanged(double)), this, SLOT(dropLineWidthChanged(double)) ); connect( ui.sbDropLineOpacity, SIGNAL(valueChanged(int)), this, SLOT(dropLineOpacityChanged(int)) ); //Symbol connect( ui.cbSymbolStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsStyleChanged(int)) ); connect( ui.sbSymbolSize, SIGNAL(valueChanged(double)), this, SLOT(symbolsSizeChanged(double)) ); connect( ui.sbSymbolRotation, SIGNAL(valueChanged(int)), this, SLOT(symbolsRotationChanged(int)) ); connect( ui.sbSymbolOpacity, SIGNAL(valueChanged(int)), this, SLOT(symbolsOpacityChanged(int)) ); connect( ui.cbSymbolFillingStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsFillingStyleChanged(int)) ); connect( ui.kcbSymbolFillingColor, SIGNAL(changed(QColor)), this, SLOT(symbolsFillingColorChanged(QColor)) ); connect( ui.cbSymbolBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolsBorderStyleChanged(int)) ); connect( ui.kcbSymbolBorderColor, SIGNAL(changed(QColor)), this, SLOT(symbolsBorderColorChanged(QColor)) ); connect( ui.sbSymbolBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(symbolsBorderWidthChanged(double)) ); //Values connect( ui.cbValuesType, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesTypeChanged(int)) ); connect( cbValuesColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(valuesColumnChanged(QModelIndex)) ); connect( ui.cbValuesPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesPositionChanged(int)) ); connect( ui.sbValuesDistance, SIGNAL(valueChanged(double)), this, SLOT(valuesDistanceChanged(double)) ); connect( ui.sbValuesRotation, SIGNAL(valueChanged(int)), this, SLOT(valuesRotationChanged(int)) ); connect( ui.sbValuesOpacity, SIGNAL(valueChanged(int)), this, SLOT(valuesOpacityChanged(int)) ); //TODO connect( ui.cbValuesFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(valuesColumnFormatChanged(int)) ); connect( ui.leValuesPrefix, SIGNAL(returnPressed()), this, SLOT(valuesPrefixChanged()) ); connect( ui.leValuesSuffix, SIGNAL(returnPressed()), this, SLOT(valuesSuffixChanged()) ); connect( ui.kfrValuesFont, SIGNAL(fontSelected(QFont)), this, SLOT(valuesFontChanged(QFont)) ); connect( ui.kcbValuesColor, SIGNAL(changed(QColor)), this, SLOT(valuesColorChanged(QColor)) ); //Filling connect( ui.cbFillingPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingPositionChanged(int)) ); connect( ui.cbFillingType, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingTypeChanged(int)) ); connect( ui.cbFillingColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingColorStyleChanged(int)) ); connect( ui.cbFillingImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingImageStyleChanged(int)) ); connect( ui.cbFillingBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(fillingBrushStyleChanged(int)) ); connect(ui.bFillingOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile())); connect( ui.leFillingFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); connect( ui.leFillingFileName, SIGNAL(textChanged(QString)), this, SLOT(fileNameChanged()) ); connect( ui.kcbFillingFirstColor, SIGNAL(changed(QColor)), this, SLOT(fillingFirstColorChanged(QColor)) ); connect( ui.kcbFillingSecondColor, SIGNAL(changed(QColor)), this, SLOT(fillingSecondColorChanged(QColor)) ); connect( ui.sbFillingOpacity, SIGNAL(valueChanged(int)), this, SLOT(fillingOpacityChanged(int)) ); //Error bars connect( ui.cbXErrorType, SIGNAL(currentIndexChanged(int)), this, SLOT(xErrorTypeChanged(int)) ); connect( cbXErrorPlusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xErrorPlusColumnChanged(QModelIndex)) ); connect( cbXErrorMinusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xErrorMinusColumnChanged(QModelIndex)) ); connect( ui.cbYErrorType, SIGNAL(currentIndexChanged(int)), this, SLOT(yErrorTypeChanged(int)) ); connect( cbYErrorPlusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yErrorPlusColumnChanged(QModelIndex)) ); connect( cbYErrorMinusColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yErrorMinusColumnChanged(QModelIndex)) ); connect( ui.cbErrorBarsType, SIGNAL(currentIndexChanged(int)), this, SLOT(errorBarsTypeChanged(int)) ); connect( ui.sbErrorBarsCapSize, SIGNAL(valueChanged(double)), this, SLOT(errorBarsCapSizeChanged(double)) ); connect( ui.cbErrorBarsStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(errorBarsStyleChanged(int)) ); connect( ui.kcbErrorBarsColor, SIGNAL(changed(QColor)), this, SLOT(errorBarsColorChanged(QColor)) ); connect( ui.sbErrorBarsWidth, SIGNAL(valueChanged(double)), this, SLOT(errorBarsWidthChanged(double)) ); connect( ui.sbErrorBarsOpacity, SIGNAL(valueChanged(int)), this, SLOT(errorBarsOpacityChanged(int)) ); //template handler auto* frame = new QFrame(this); auto* layout = new QHBoxLayout(frame); layout->setContentsMargins(0, 11, 0, 11); auto* templateHandler = new TemplateHandler(this, TemplateHandler::XYCurve); layout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); ui.verticalLayout->addWidget(frame); retranslateUi(); init(); } XYCurveDock::~XYCurveDock() { if (m_aspectTreeModel) delete m_aspectTreeModel; } void XYCurveDock::setupGeneral() { QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); m_leName = uiGeneralTab.leName; m_leComment = uiGeneralTab.leComment; auto* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); // Tab "General" auto* gridLayout = qobject_cast(generalTab->layout()); cbXColumn = new TreeViewComboBox(generalTab); cbXColumn->useCurrentIndexText(false); gridLayout->addWidget(cbXColumn, 2, 2, 1, 1); cbYColumn = new TreeViewComboBox(generalTab); cbYColumn->useCurrentIndexText(false); gridLayout->addWidget(cbYColumn, 3, 2, 1, 1); //General connect(uiGeneralTab.leName, &QLineEdit::textChanged, this, &XYCurveDock::nameChanged); connect(uiGeneralTab.leComment, &QLineEdit::textChanged, this, &XYCurveDock::commentChanged); connect(uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool))); connect(cbXColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xColumnChanged(QModelIndex))); connect(cbYColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yColumnChanged(QModelIndex))); } void XYCurveDock::init() { m_initializing = true; //Line ui.cbLineType->addItem(i18n("None")); ui.cbLineType->addItem(i18n("Line")); ui.cbLineType->addItem(i18n("Horiz. Start")); ui.cbLineType->addItem(i18n("Vert. Start")); ui.cbLineType->addItem(i18n("Horiz. Midpoint")); ui.cbLineType->addItem(i18n("Vert. Midpoint")); ui.cbLineType->addItem(i18n("2-segments")); ui.cbLineType->addItem(i18n("3-segments")); ui.cbLineType->addItem(i18n("Cubic Spline (Natural)")); ui.cbLineType->addItem(i18n("Cubic Spline (Periodic)")); ui.cbLineType->addItem(i18n("Akima-spline (Natural)")); ui.cbLineType->addItem(i18n("Akima-spline (Periodic)")); QPainter pa; //TODO size of the icon depending on the actual height of the combobox? int iconSize = 20; QPixmap pm(iconSize, iconSize); ui.cbLineType->setIconSize(QSize(iconSize, iconSize)); QPen pen(Qt::SolidPattern, 0); const QColor& color = (palette().color(QPalette::Base).lightness() < 128) ? Qt::white : Qt::black; pen.setColor(color); pa.setPen( pen ); //no line pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.end(); ui.cbLineType->setItemIcon(0, pm); //line pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,17,17); pa.end(); ui.cbLineType->setItemIcon(1, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,17,3); pa.drawLine(17,3,17,17); pa.end(); ui.cbLineType->setItemIcon(2, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,3,17); pa.drawLine(3,17,17,17); pa.end(); ui.cbLineType->setItemIcon(3, pm); //horizontal midpoint pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,10,3); pa.drawLine(10,3,10,17); pa.drawLine(10,17,17,17); pa.end(); ui.cbLineType->setItemIcon(4, pm); //vertical midpoint pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,3,10); pa.drawLine(3,10,17,10); pa.drawLine(17,10,17,17); pa.end(); ui.cbLineType->setItemIcon(5, pm); //2-segments pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 8,8,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,10,10); pa.end(); ui.cbLineType->setItemIcon(6, pm); //3-segments pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 8,8,4,4); pa.drawEllipse( 15,15,4,4); pa.drawLine(3,3,17,17); pa.end(); ui.cbLineType->setItemIcon(7, pm); //natural spline pm.fill(Qt::transparent); pa.begin( &pm ); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.drawEllipse( 1,1,4,4); pa.drawEllipse( 15,15,4,4); pa.rotate(45); pa.drawArc(2*sqrt(2),-4,17*sqrt(2),20,30*16,120*16); pa.end(); ui.cbLineType->setItemIcon(8, pm); ui.cbLineType->setItemIcon(9, pm); ui.cbLineType->setItemIcon(10, pm); ui.cbLineType->setItemIcon(11, pm); GuiTools::updatePenStyles(ui.cbLineStyle, Qt::black); //Drop lines ui.cbDropLineType->addItem(i18n("No Drop Lines")); ui.cbDropLineType->addItem(i18n("Drop Lines, X")); ui.cbDropLineType->addItem(i18n("Drop Lines, Y")); ui.cbDropLineType->addItem(i18n("Drop Lines, XY")); ui.cbDropLineType->addItem(i18n("Drop Lines, X, Zero Baseline")); ui.cbDropLineType->addItem(i18n("Drop Lines, X, Min Baseline")); ui.cbDropLineType->addItem(i18n("Drop Lines, X, Max Baseline")); GuiTools::updatePenStyles(ui.cbDropLineStyle, Qt::black); //Symbols GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, Qt::black); ui.cbSymbolStyle->setIconSize(QSize(iconSize, iconSize)); QTransform trafo; trafo.scale(15, 15); ui.cbSymbolStyle->addItem(i18n("None")); for (int i = 1; i < 19; ++i) { //TODO: use enum count const auto style = (Symbol::Style)i; pm.fill(Qt::transparent); pa.begin(&pm); pa.setPen(pen); pa.setRenderHint(QPainter::Antialiasing); pa.translate(iconSize/2,iconSize/2); pa.drawPath(trafo.map(Symbol::pathFromStyle(style))); pa.end(); ui.cbSymbolStyle->addItem(QIcon(pm), Symbol::nameFromStyle(style)); } GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, Qt::black); m_initializing = false; //Values ui.cbValuesType->addItem(i18n("No Values")); ui.cbValuesType->addItem("x"); ui.cbValuesType->addItem("y"); ui.cbValuesType->addItem("x, y"); ui.cbValuesType->addItem("(x, y)"); ui.cbValuesType->addItem(i18n("Custom Column")); ui.cbValuesPosition->addItem(i18n("Above")); ui.cbValuesPosition->addItem(i18n("Below")); ui.cbValuesPosition->addItem(i18n("Left")); ui.cbValuesPosition->addItem(i18n("Right")); //Filling ui.cbFillingPosition->clear(); ui.cbFillingPosition->addItem(i18n("None")); ui.cbFillingPosition->addItem(i18n("Above")); ui.cbFillingPosition->addItem(i18n("Below")); ui.cbFillingPosition->addItem(i18n("Zero Baseline")); ui.cbFillingPosition->addItem(i18n("Left")); ui.cbFillingPosition->addItem(i18n("Right")); ui.cbFillingType->clear(); ui.cbFillingType->addItem(i18n("Color")); ui.cbFillingType->addItem(i18n("Image")); ui.cbFillingType->addItem(i18n("Pattern")); ui.cbFillingColorStyle->clear(); ui.cbFillingColorStyle->addItem(i18n("Single Color")); ui.cbFillingColorStyle->addItem(i18n("Horizontal Gradient")); ui.cbFillingColorStyle->addItem(i18n("Vertical Gradient")); ui.cbFillingColorStyle->addItem(i18n("Diag. Gradient (From Top Left)")); ui.cbFillingColorStyle->addItem(i18n("Diag. Gradient (From Bottom Left)")); ui.cbFillingColorStyle->addItem(i18n("Radial Gradient")); ui.cbFillingImageStyle->clear(); ui.cbFillingImageStyle->addItem(i18n("Scaled and Cropped")); ui.cbFillingImageStyle->addItem(i18n("Scaled")); ui.cbFillingImageStyle->addItem(i18n("Scaled, Keep Proportions")); ui.cbFillingImageStyle->addItem(i18n("Centered")); ui.cbFillingImageStyle->addItem(i18n("Tiled")); ui.cbFillingImageStyle->addItem(i18n("Center Tiled")); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, Qt::SolidPattern); //Error-bars pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.drawLine(3,10,17,10);//vert. line pa.drawLine(10,3,10,17);//hor. line pa.end(); ui.cbErrorBarsType->addItem(i18n("Bars")); ui.cbErrorBarsType->setItemIcon(0, pm); pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,17,10); //vert. line pa.drawLine(10,3,10,17); //hor. line pa.drawLine(7,3,13,3); //upper cap pa.drawLine(7,17,13,17); //bottom cap pa.drawLine(3,7,3,13); //left cap pa.drawLine(17,7,17,13); //right cap pa.end(); ui.cbErrorBarsType->addItem(i18n("Bars with Ends")); ui.cbErrorBarsType->setItemIcon(1, pm); ui.cbXErrorType->addItem(i18n("No")); ui.cbXErrorType->addItem(i18n("Symmetric")); ui.cbXErrorType->addItem(i18n("Asymmetric")); ui.cbYErrorType->addItem(i18n("No")); ui.cbYErrorType->addItem(i18n("Symmetric")); ui.cbYErrorType->addItem(i18n("Asymmetric")); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, Qt::black); } void XYCurveDock::setModel() { m_aspectTreeModel->enablePlottableColumnsOnly(true); m_aspectTreeModel->enableShowPlotDesignation(true); QList list{AspectType::Folder, AspectType::Workbook, AspectType::Datapicker, AspectType::DatapickerCurve, AspectType::Spreadsheet, AspectType::LiveDataSource, AspectType::Column, AspectType::Worksheet, AspectType::CartesianPlot, AspectType::XYFitCurve, AspectType::CantorWorksheet}; if (cbXColumn) { cbXColumn->setTopLevelClasses(list); cbYColumn->setTopLevelClasses(list); } cbValuesColumn->setTopLevelClasses(list); cbXErrorMinusColumn->setTopLevelClasses(list); cbXErrorPlusColumn->setTopLevelClasses(list); cbYErrorMinusColumn->setTopLevelClasses(list); cbYErrorPlusColumn->setTopLevelClasses(list); list = {AspectType::Column, AspectType::XYCurve}; m_aspectTreeModel->setSelectableAspects(list); if (cbXColumn) { cbXColumn->setModel(m_aspectTreeModel); cbYColumn->setModel(m_aspectTreeModel); } cbValuesColumn->setModel(m_aspectTreeModel); cbXErrorMinusColumn->setModel(m_aspectTreeModel); cbXErrorPlusColumn->setModel(m_aspectTreeModel); cbYErrorMinusColumn->setModel(m_aspectTreeModel); cbYErrorPlusColumn->setModel(m_aspectTreeModel); if (cbXColumn) { QString path = m_curve->xColumnPath().split('/').last(); if (m_curve->xColumn()) { path += QString("\t ")+m_curve->xColumn()->plotDesignationString(); cbXColumn->setInvalid(false); } else cbXColumn->setInvalid(true, i18n("The column \"%1\" is not available. If a new column at this path is created, it is linked to this curve. If you wanna hold this column, don't change anything in this combobox.", m_curve->xColumnPath())); cbXColumn->setText(path); path = m_curve->yColumnPath().split('/').last(); if (m_curve->yColumn()) { path += QString("\t ")+m_curve->yColumn()->plotDesignationString(); cbYColumn->setInvalid(false); } else cbYColumn->setInvalid(true, i18n("The column \"%1\" is not available. If a new column at this path is created, it is linked to this curve. If you wanna hold this column, don't change anything in this combobox.", m_curve->xColumnPath())); cbYColumn->setText(path); } } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYCurveDock::setCurves(QList list) { m_initializing = true; m_curvesList = list; m_curve = list.first(); m_aspect = list.first(); Q_ASSERT(m_curve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); setModel(); initGeneralTab(); initTabs(); m_initializing = false; } void XYCurveDock::initGeneralTab() { DEBUG("XYCurveDock::initGeneralTab()"); //if there are more than one curve in the list, disable the content in the tab "general" if (m_curvesList.size() == 1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.lXColumn->setEnabled(true); cbXColumn->setEnabled(true); uiGeneralTab.lYColumn->setEnabled(true); cbYColumn->setEnabled(true); DEBUG("setModelIndexFromAspect()"); this->setModelIndexFromAspect(cbXColumn, m_curve->xColumn()); this->setModelIndexFromAspect(cbYColumn, m_curve->yColumn()); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); } else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.lXColumn->setEnabled(false); cbXColumn->setEnabled(false); uiGeneralTab.lYColumn->setEnabled(false); cbYColumn->setEnabled(false); cbXColumn->setCurrentModelIndex(QModelIndex()); cbYColumn->setCurrentModelIndex(QModelIndex()); uiGeneralTab.leName->setText(QString()); uiGeneralTab.leComment->setText(QString()); } checkColumnAvailability(cbXColumn, m_curve->xColumn(), m_curve->xColumnPath()); checkColumnAvailability(cbYColumn, m_curve->yColumn(), m_curve->yColumnPath()); checkColumnAvailability(cbValuesColumn, m_curve->valuesColumn(), m_curve->valuesColumnPath()); checkColumnAvailability(cbXErrorPlusColumn, m_curve->xErrorPlusColumn(), m_curve->xErrorPlusColumnPath()); checkColumnAvailability(cbXErrorMinusColumn, m_curve->xErrorMinusColumn(), m_curve->xErrorMinusColumnPath()); checkColumnAvailability(cbYErrorPlusColumn, m_curve->yErrorPlusColumn(), m_curve->yErrorPlusColumnPath()); checkColumnAvailability(cbYErrorMinusColumn, m_curve->yErrorMinusColumn(), m_curve->yErrorMinusColumnPath()); //show the properties of the first curve uiGeneralTab.chkVisible->setChecked( m_curve->isVisible() ); //Slots connect(m_curve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)),this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_curve, SIGNAL(xColumnChanged(const AbstractColumn*)), this, SLOT(curveXColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(yColumnChanged(const AbstractColumn*)), this, SLOT(curveYColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(visibilityChanged(bool)), this, SLOT(curveVisibilityChanged(bool))); DEBUG("XYCurveDock::initGeneralTab() DONE"); } void XYCurveDock::initTabs() { //if there are more than one curve in the list, disable the tab "general" if (m_curvesList.size() == 1) { this->setModelIndexFromAspect(cbValuesColumn, m_curve->valuesColumn()); this->setModelIndexFromAspect(cbXErrorPlusColumn, m_curve->xErrorPlusColumn()); this->setModelIndexFromAspect(cbXErrorMinusColumn, m_curve->xErrorMinusColumn()); this->setModelIndexFromAspect(cbYErrorPlusColumn, m_curve->yErrorPlusColumn()); this->setModelIndexFromAspect(cbYErrorMinusColumn, m_curve->yErrorMinusColumn()); } else { cbValuesColumn->setCurrentModelIndex(QModelIndex()); cbXErrorPlusColumn->setCurrentModelIndex(QModelIndex()); cbXErrorMinusColumn->setCurrentModelIndex(QModelIndex()); cbYErrorPlusColumn->setCurrentModelIndex(QModelIndex()); cbYErrorMinusColumn->setCurrentModelIndex(QModelIndex()); } //show the properties of the first curve load(); //Slots //Line-Tab connect(m_curve, SIGNAL(lineTypeChanged(XYCurve::LineType)), this, SLOT(curveLineTypeChanged(XYCurve::LineType))); connect(m_curve, SIGNAL(lineSkipGapsChanged(bool)), this, SLOT(curveLineSkipGapsChanged(bool))); connect(m_curve, &XYCurve::lineIncreasingXOnlyChanged, this, &XYCurveDock::curveLineIncreasingXOnlyChanged); connect(m_curve, SIGNAL(lineInterpolationPointsCountChanged(int)), this, SLOT(curveLineInterpolationPointsCountChanged(int))); connect(m_curve, SIGNAL(linePenChanged(QPen)), this, SLOT(curveLinePenChanged(QPen))); connect(m_curve, SIGNAL(lineOpacityChanged(qreal)), this, SLOT(curveLineOpacityChanged(qreal))); connect(m_curve, SIGNAL(dropLineTypeChanged(XYCurve::DropLineType)), this, SLOT(curveDropLineTypeChanged(XYCurve::DropLineType))); connect(m_curve, SIGNAL(dropLinePenChanged(QPen)), this, SLOT(curveDropLinePenChanged(QPen))); connect(m_curve, SIGNAL(dropLineOpacityChanged(qreal)), this, SLOT(curveDropLineOpacityChanged(qreal))); //Symbol-Tab connect(m_curve, SIGNAL(symbolsStyleChanged(Symbol::Style)), this, SLOT(curveSymbolsStyleChanged(Symbol::Style))); connect(m_curve, SIGNAL(symbolsSizeChanged(qreal)), this, SLOT(curveSymbolsSizeChanged(qreal))); connect(m_curve, SIGNAL(symbolsRotationAngleChanged(qreal)), this, SLOT(curveSymbolsRotationAngleChanged(qreal))); connect(m_curve, SIGNAL(symbolsOpacityChanged(qreal)), this, SLOT(curveSymbolsOpacityChanged(qreal))); connect(m_curve, SIGNAL(symbolsBrushChanged(QBrush)), this, SLOT(curveSymbolsBrushChanged(QBrush))); connect(m_curve, SIGNAL(symbolsPenChanged(QPen)), this, SLOT(curveSymbolsPenChanged(QPen))); //Values-Tab connect(m_curve, SIGNAL(valuesTypeChanged(XYCurve::ValuesType)), this, SLOT(curveValuesTypeChanged(XYCurve::ValuesType))); connect(m_curve, SIGNAL(valuesColumnChanged(const AbstractColumn*)), this, SLOT(curveValuesColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(valuesPositionChanged(XYCurve::ValuesPosition)), this, SLOT(curveValuesPositionChanged(XYCurve::ValuesPosition))); connect(m_curve, SIGNAL(valuesDistanceChanged(qreal)), this, SLOT(curveValuesDistanceChanged(qreal))); connect(m_curve, SIGNAL(valuesOpacityChanged(qreal)), this, SLOT(curveValuesOpacityChanged(qreal))); connect(m_curve, SIGNAL(valuesRotationAngleChanged(qreal)), this, SLOT(curveValuesRotationAngleChanged(qreal))); connect(m_curve, SIGNAL(valuesPrefixChanged(QString)), this, SLOT(curveValuesPrefixChanged(QString))); connect(m_curve, SIGNAL(valuesSuffixChanged(QString)), this, SLOT(curveValuesSuffixChanged(QString))); connect(m_curve, SIGNAL(valuesFontChanged(QFont)), this, SLOT(curveValuesFontChanged(QFont))); connect(m_curve, SIGNAL(valuesColorChanged(QColor)), this, SLOT(curveValuesColorChanged(QColor))); //Filling-Tab connect( m_curve, SIGNAL(fillingPositionChanged(XYCurve::FillingPosition)), this, SLOT(curveFillingPositionChanged(XYCurve::FillingPosition)) ); connect( m_curve, SIGNAL(fillingTypeChanged(PlotArea::BackgroundType)), this, SLOT(curveFillingTypeChanged(PlotArea::BackgroundType)) ); connect( m_curve, SIGNAL(fillingColorStyleChanged(PlotArea::BackgroundColorStyle)), this, SLOT(curveFillingColorStyleChanged(PlotArea::BackgroundColorStyle)) ); connect( m_curve, SIGNAL(fillingImageStyleChanged(PlotArea::BackgroundImageStyle)), this, SLOT(curveFillingImageStyleChanged(PlotArea::BackgroundImageStyle)) ); connect( m_curve, SIGNAL(fillingBrushStyleChanged(Qt::BrushStyle)), this, SLOT(curveFillingBrushStyleChanged(Qt::BrushStyle)) ); connect( m_curve, SIGNAL(fillingFirstColorChanged(QColor&)), this, SLOT(curveFillingFirstColorChanged(QColor&)) ); connect( m_curve, SIGNAL(fillingSecondColorChanged(QColor&)), this, SLOT(curveFillingSecondColorChanged(QColor&)) ); connect( m_curve, SIGNAL(fillingFileNameChanged(QString&)), this, SLOT(curveFillingFileNameChanged(QString&)) ); connect( m_curve, SIGNAL(fillingOpacityChanged(float)), this, SLOT(curveFillingOpacityChanged(float)) ); //"Error bars"-Tab connect(m_curve, SIGNAL(xErrorTypeChanged(XYCurve::ErrorType)), this, SLOT(curveXErrorTypeChanged(XYCurve::ErrorType))); connect(m_curve, SIGNAL(xErrorPlusColumnChanged(const AbstractColumn*)), this, SLOT(curveXErrorPlusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(xErrorMinusColumnChanged(const AbstractColumn*)), this, SLOT(curveXErrorMinusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(yErrorTypeChanged(XYCurve::ErrorType)), this, SLOT(curveYErrorTypeChanged(XYCurve::ErrorType))); connect(m_curve, SIGNAL(yErrorPlusColumnChanged(const AbstractColumn*)), this, SLOT(curveYErrorPlusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(yErrorMinusColumnChanged(const AbstractColumn*)), this, SLOT(curveYErrorMinusColumnChanged(const AbstractColumn*))); connect(m_curve, SIGNAL(errorBarsCapSizeChanged(qreal)), this, SLOT(curveErrorBarsCapSizeChanged(qreal))); connect(m_curve, SIGNAL(errorBarsTypeChanged(XYCurve::ErrorBarsType)), this, SLOT(curveErrorBarsTypeChanged(XYCurve::ErrorBarsType))); connect(m_curve, SIGNAL(errorBarsPenChanged(QPen)), this, SLOT(curveErrorBarsPenChanged(QPen))); connect(m_curve, SIGNAL(errorBarsOpacityChanged(qreal)), this, SLOT(curveErrorBarsOpacityChanged(qreal))); } /*! depending on the currently selected values column type (column mode) updates the widgets for the values column format, shows/hides the allowed widgets, fills the corresponding combobox with the possible entries. Called when the values column was changed. synchronize this function with ColumnDock::updateFormat. */ void XYCurveDock::updateValuesFormatWidgets(AbstractColumn::ColumnMode columnMode) { ui.cbValuesFormat->clear(); switch (columnMode) { case AbstractColumn::Numeric: ui.cbValuesFormat->addItem(i18n("Decimal"), QVariant('f')); ui.cbValuesFormat->addItem(i18n("Scientific (e)"), QVariant('e')); ui.cbValuesFormat->addItem(i18n("Scientific (E)"), QVariant('E')); ui.cbValuesFormat->addItem(i18n("Automatic (e)"), QVariant('g')); ui.cbValuesFormat->addItem(i18n("Automatic (E)"), QVariant('G')); break; case AbstractColumn::Integer: case AbstractColumn::BigInt: break; case AbstractColumn::Text: ui.cbValuesFormat->addItem(i18n("Text"), QVariant()); break; case AbstractColumn::Month: ui.cbValuesFormat->addItem(i18n("Number without Leading Zero"), QVariant("M")); ui.cbValuesFormat->addItem(i18n("Number with Leading Zero"), QVariant("MM")); ui.cbValuesFormat->addItem(i18n("Abbreviated Month Name"), QVariant("MMM")); ui.cbValuesFormat->addItem(i18n("Full Month Name"), QVariant("MMMM")); break; case AbstractColumn::Day: ui.cbValuesFormat->addItem(i18n("Number without Leading Zero"), QVariant("d")); ui.cbValuesFormat->addItem(i18n("Number with Leading Zero"), QVariant("dd")); ui.cbValuesFormat->addItem(i18n("Abbreviated Day Name"), QVariant("ddd")); ui.cbValuesFormat->addItem(i18n("Full Day Name"), QVariant("dddd")); break; case AbstractColumn::DateTime: { for (const auto& s : AbstractColumn::dateFormats()) ui.cbValuesFormat->addItem(s, QVariant(s)); for (const auto& s : AbstractColumn::timeFormats()) ui.cbValuesFormat->addItem(s, QVariant(s)); for (const auto& s1 : AbstractColumn::dateFormats()) { for (const auto& s2 : AbstractColumn::timeFormats()) ui.cbValuesFormat->addItem(s1 + ' ' + s2, QVariant(s1 + ' ' + s2)); } break; } } if (columnMode == AbstractColumn::Numeric) { ui.lValuesPrecision->show(); ui.sbValuesPrecision->show(); } else { ui.lValuesPrecision->hide(); ui.sbValuesPrecision->hide(); } if (columnMode == AbstractColumn::Text) { ui.lValuesFormatTop->hide(); ui.lValuesFormat->hide(); ui.cbValuesFormat->hide(); } else { ui.lValuesFormatTop->show(); ui.lValuesFormat->show(); ui.cbValuesFormat->show(); } if (columnMode == AbstractColumn::DateTime) { ui.cbValuesFormat->setCurrentItem("yyyy-MM-dd hh:mm:ss.zzz"); ui.cbValuesFormat->setEditable(true); } else { ui.cbValuesFormat->setCurrentIndex(0); ui.cbValuesFormat->setEditable(false); } } void XYCurveDock::checkColumnAvailability(TreeViewComboBox* cb, const AbstractColumn* column, const QString& columnPath) { if (!cb) return;// normally it shouldn't be called // don't make the comboboxes red for initially created curves if (!column && columnPath.isEmpty()) { cb->setText(""); cb->setInvalid(false); return; } if (column) { // current index text should be used cb->useCurrentIndexText(true); cb->setInvalid(false); } else { cb->useCurrentIndexText(false); cb->setInvalid(true, i18n("The column \"%1\"\nis not available anymore. It will be automatically used once it is created again.", columnPath)); } cb->setText(columnPath.split('/').last()); } /*! shows the formatting properties of the column \c column. Called, when a new column for the values was selected - either by changing the type of the values (none, x, y, etc.) or by selecting a new custom column for the values. */ void XYCurveDock::showValuesColumnFormat(const Column* column) { if (!column) { // no valid column is available // -> hide all the format properties widgets (equivalent to showing the properties of the column mode "Text") this->updateValuesFormatWidgets(AbstractColumn::Text); } else { AbstractColumn::ColumnMode columnMode = column->columnMode(); //update the format widgets for the new column mode this->updateValuesFormatWidgets(columnMode); //show the actual formatting properties switch (columnMode) { case AbstractColumn::Numeric: { const auto* filter = static_cast(column->outputFilter()); ui.cbValuesFormat->setCurrentIndex(ui.cbValuesFormat->findData(filter->numericFormat())); ui.sbValuesPrecision->setValue(filter->numDigits()); break; } case AbstractColumn::Integer: case AbstractColumn::BigInt: case AbstractColumn::Text: break; case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: { const auto* filter = static_cast(column->outputFilter()); - DEBUG(" column values format = " << filter->format().toStdString()); + DEBUG(" column values format = " << STDSTRING(filter->format())); ui.cbValuesFormat->setCurrentIndex(ui.cbValuesFormat->findData(filter->format())); break; } } } } void XYCurveDock::setModelIndexFromAspect(TreeViewComboBox* cb, const AbstractAspect* aspect) { if (aspect) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(aspect)); else cb->setCurrentModelIndex(QModelIndex()); } //************************************************************* //********** SLOTs for changes triggered in XYCurveDock ******** //************************************************************* void XYCurveDock::retranslateUi() { ui.lLineSkipGaps->setToolTip(i18n("If checked, connect neighbour points with lines even if there are gaps (invalid or masked values) between them")); ui.chkLineSkipGaps->setToolTip(i18n("If checked, connect neighbour points with lines even if there are gaps (invalid or masked values) between them")); ui.lLineIncreasingXOnly->setToolTip(i18n("If checked, connect data points only for strictly increasing values of X")); ui.chkLineIncreasingXOnly->setToolTip(i18n("If checked, connect data points only for strictly increasing values of X")); //TODO: // uiGeneralTab.lName->setText(i18n("Name")); // uiGeneralTab.lComment->setText(i18n("Comment")); // uiGeneralTab.chkVisible->setText(i18n("Visible")); // uiGeneralTab.lXColumn->setText(i18n("x-data")); // uiGeneralTab.lYColumn->setText(i18n("y-data")); //TODO updatePenStyles, updateBrushStyles for all comboboxes } void XYCurveDock::xColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); AbstractColumn* column = nullptr; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve : m_curvesList) curve->setXColumn(column); } void XYCurveDock::yColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); AbstractColumn* column = nullptr; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve : m_curvesList) curve->setYColumn(column); } void XYCurveDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setVisible(state); } // "Line"-tab void XYCurveDock::lineTypeChanged(int index) { const auto lineType = XYCurve::LineType(index); if ( lineType == XYCurve::NoLine) { ui.chkLineSkipGaps->setEnabled(false); ui.cbLineStyle->setEnabled(false); ui.kcbLineColor->setEnabled(false); ui.sbLineWidth->setEnabled(false); ui.sbLineOpacity->setEnabled(false); ui.lLineInterpolationPointsCount->hide(); ui.sbLineInterpolationPointsCount->hide(); } else { ui.chkLineSkipGaps->setEnabled(true); ui.cbLineStyle->setEnabled(true); ui.kcbLineColor->setEnabled(true); ui.sbLineWidth->setEnabled(true); ui.sbLineOpacity->setEnabled(true); if (lineType == XYCurve::SplineCubicNatural || lineType == XYCurve::SplineCubicPeriodic || lineType == XYCurve::SplineAkimaNatural || lineType == XYCurve::SplineAkimaPeriodic) { ui.lLineInterpolationPointsCount->show(); ui.sbLineInterpolationPointsCount->show(); ui.lLineSkipGaps->hide(); ui.chkLineSkipGaps->hide(); } else { ui.lLineInterpolationPointsCount->hide(); ui.sbLineInterpolationPointsCount->hide(); ui.lLineSkipGaps->show(); ui.chkLineSkipGaps->show(); } } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setLineType(lineType); } void XYCurveDock::lineSkipGapsChanged(bool skip) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setLineSkipGaps(skip); } void XYCurveDock::lineIncreasingXOnlyChanged(bool incr) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setLineIncreasingXOnly(incr); } void XYCurveDock::lineInterpolationPointsCountChanged(int count) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setLineInterpolationPointsCount(count); } void XYCurveDock::lineStyleChanged(int index) { if (m_initializing) return; const auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* curve : m_curvesList) { pen = curve->linePen(); pen.setStyle(penStyle); curve->setLinePen(pen); } } void XYCurveDock::lineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->linePen(); pen.setColor(color); curve->setLinePen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, color); m_initializing = false; } void XYCurveDock::lineWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->linePen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); curve->setLinePen(pen); } } void XYCurveDock::lineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setLineOpacity(opacity); } void XYCurveDock::dropLineTypeChanged(int index) { const auto dropLineType = XYCurve::DropLineType(index); if ( dropLineType == XYCurve::NoDropLine) { ui.cbDropLineStyle->setEnabled(false); ui.kcbDropLineColor->setEnabled(false); ui.sbDropLineWidth->setEnabled(false); ui.sbDropLineOpacity->setEnabled(false); } else { ui.cbDropLineStyle->setEnabled(true); ui.kcbDropLineColor->setEnabled(true); ui.sbDropLineWidth->setEnabled(true); ui.sbDropLineOpacity->setEnabled(true); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setDropLineType(dropLineType); } void XYCurveDock::dropLineStyleChanged(int index) { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* curve : m_curvesList) { pen = curve->dropLinePen(); pen.setStyle(penStyle); curve->setDropLinePen(pen); } } void XYCurveDock::dropLineColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->dropLinePen(); pen.setColor(color); curve->setDropLinePen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbDropLineStyle, color); m_initializing = false; } void XYCurveDock::dropLineWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->dropLinePen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); curve->setDropLinePen(pen); } } void XYCurveDock::dropLineOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setDropLineOpacity(opacity); } //"Symbol"-tab void XYCurveDock::symbolsStyleChanged(int index) { const auto style = Symbol::Style(index); if (style == Symbol::NoSymbols) { ui.sbSymbolSize->setEnabled(false); ui.sbSymbolRotation->setEnabled(false); ui.sbSymbolOpacity->setEnabled(false); ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); ui.cbSymbolBorderStyle->setEnabled(false); ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.sbSymbolSize->setEnabled(true); ui.sbSymbolRotation->setEnabled(true); ui.sbSymbolOpacity->setEnabled(true); //enable/disable the symbol filling options in the GUI depending on the currently selected symbol. if (style != Symbol::Line && style != Symbol::Cross) { ui.cbSymbolFillingStyle->setEnabled(true); bool noBrush = (Qt::BrushStyle(ui.cbSymbolFillingStyle->currentIndex()) == Qt::NoBrush); ui.kcbSymbolFillingColor->setEnabled(!noBrush); } else { ui.kcbSymbolFillingColor->setEnabled(false); ui.cbSymbolFillingStyle->setEnabled(false); } ui.cbSymbolBorderStyle->setEnabled(true); bool noLine = (Qt::PenStyle(ui.cbSymbolBorderStyle->currentIndex()) == Qt::NoPen); ui.kcbSymbolBorderColor->setEnabled(!noLine); ui.sbSymbolBorderWidth->setEnabled(!noLine); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setSymbolsStyle(style); } void XYCurveDock::symbolsSizeChanged(double value) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setSymbolsSize( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void XYCurveDock::symbolsRotationChanged(int value) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setSymbolsRotationAngle(value); } void XYCurveDock::symbolsOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setSymbolsOpacity(opacity); } void XYCurveDock::symbolsFillingStyleChanged(int index) { const auto brushStyle = Qt::BrushStyle(index); ui.kcbSymbolFillingColor->setEnabled(!(brushStyle == Qt::NoBrush)); if (m_initializing) return; QBrush brush; for (auto* curve : m_curvesList) { brush = curve->symbolsBrush(); brush.setStyle(brushStyle); curve->setSymbolsBrush(brush); } } void XYCurveDock::symbolsFillingColorChanged(const QColor& color) { if (m_initializing) return; QBrush brush; for (auto* curve : m_curvesList) { brush = curve->symbolsBrush(); brush.setColor(color); curve->setSymbolsBrush(brush); } m_initializing = true; GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, color ); m_initializing = false; } void XYCurveDock::symbolsBorderStyleChanged(int index) { const auto penStyle = Qt::PenStyle(index); if ( penStyle == Qt::NoPen ) { ui.kcbSymbolBorderColor->setEnabled(false); ui.sbSymbolBorderWidth->setEnabled(false); } else { ui.kcbSymbolBorderColor->setEnabled(true); ui.sbSymbolBorderWidth->setEnabled(true); } if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->symbolsPen(); pen.setStyle(penStyle); curve->setSymbolsPen(pen); } } void XYCurveDock::symbolsBorderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->symbolsPen(); pen.setColor(color); curve->setSymbolsPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, color); m_initializing = false; } void XYCurveDock::symbolsBorderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->symbolsPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); curve->setSymbolsPen(pen); } } //Values-tab /*! called when the type of the values (none, x, y, (x,y) etc.) was changed. */ void XYCurveDock::valuesTypeChanged(int index) { const auto valuesType = XYCurve::ValuesType(index); if (valuesType == XYCurve::NoValues) { //no values are to paint -> deactivate all the pertinent widgets ui.cbValuesPosition->setEnabled(false); ui.lValuesColumn->hide(); cbValuesColumn->hide(); ui.sbValuesDistance->setEnabled(false); ui.sbValuesRotation->setEnabled(false); ui.sbValuesOpacity->setEnabled(false); ui.cbValuesFormat->setEnabled(false); ui.cbValuesFormat->setEnabled(false); ui.sbValuesPrecision->setEnabled(false); ui.leValuesPrefix->setEnabled(false); ui.leValuesSuffix->setEnabled(false); ui.kfrValuesFont->setEnabled(false); ui.kcbValuesColor->setEnabled(false); } else { ui.cbValuesPosition->setEnabled(true); ui.sbValuesDistance->setEnabled(true); ui.sbValuesRotation->setEnabled(true); ui.sbValuesOpacity->setEnabled(true); ui.cbValuesFormat->setEnabled(true); ui.sbValuesPrecision->setEnabled(true); ui.leValuesPrefix->setEnabled(true); ui.leValuesSuffix->setEnabled(true); ui.kfrValuesFont->setEnabled(true); ui.kcbValuesColor->setEnabled(true); const Column* column; if (valuesType == XYCurve::ValuesCustomColumn) { ui.lValuesColumn->show(); cbValuesColumn->show(); column = static_cast(cbValuesColumn->currentModelIndex().internalPointer()); } else { ui.lValuesColumn->hide(); cbValuesColumn->hide(); if (valuesType == XYCurve::ValuesY) column = static_cast(m_curve->yColumn()); else column = static_cast(m_curve->xColumn()); } this->showValuesColumnFormat(column); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesType(valuesType); } /*! called when the custom column for the values was changed. */ void XYCurveDock::valuesColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* column = static_cast(index.internalPointer()); this->showValuesColumnFormat(column); for (auto* curve : m_curvesList) { //TODO save also the format of the currently selected column for the values (precision etc.) curve->setValuesColumn(column); } } void XYCurveDock::valuesPositionChanged(int index) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesPosition(XYCurve::ValuesPosition(index)); } void XYCurveDock::valuesDistanceChanged(double value) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesDistance( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); } void XYCurveDock::valuesRotationChanged(int value) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesRotationAngle(value); } void XYCurveDock::valuesOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setValuesOpacity(opacity); } void XYCurveDock::valuesPrefixChanged() { if (m_initializing) return; QString prefix = ui.leValuesPrefix->text(); for (auto* curve : m_curvesList) curve->setValuesPrefix(prefix); } void XYCurveDock::valuesSuffixChanged() { if (m_initializing) return; QString suffix = ui.leValuesSuffix->text(); for (auto* curve : m_curvesList) curve->setValuesSuffix(suffix); } void XYCurveDock::valuesFontChanged(const QFont& font) { if (m_initializing) return; QFont valuesFont = font; valuesFont.setPixelSize( Worksheet::convertToSceneUnits(font.pointSizeF(), Worksheet::Point) ); for (auto* curve : m_curvesList) curve->setValuesFont(valuesFont); } void XYCurveDock::valuesColorChanged(const QColor& color) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setValuesColor(color); } //Filling-tab void XYCurveDock::fillingPositionChanged(int index) { const auto fillingPosition = XYCurve::FillingPosition(index); bool b = (fillingPosition != XYCurve::NoFilling); ui.cbFillingType->setEnabled(b); ui.cbFillingColorStyle->setEnabled(b); ui.cbFillingBrushStyle->setEnabled(b); ui.cbFillingImageStyle->setEnabled(b); ui.kcbFillingFirstColor->setEnabled(b); ui.kcbFillingSecondColor->setEnabled(b); ui.leFillingFileName->setEnabled(b); ui.bFillingOpen->setEnabled(b); ui.sbFillingOpacity->setEnabled(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingPosition(fillingPosition); } void XYCurveDock::fillingTypeChanged(int index) { const auto type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lFillingColorStyle->show(); ui.cbFillingColorStyle->show(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); auto style = (PlotArea::BackgroundColorStyle) ui.cbFillingColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lFillingFirstColor->setText(i18n("Color:")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else { ui.lFillingFirstColor->setText(i18n("First color:")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->show(); ui.cbFillingImageStyle->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); ui.lFillingFileName->show(); ui.leFillingFileName->show(); ui.bFillingOpen->show(); ui.lFillingFirstColor->hide(); ui.kcbFillingFirstColor->hide(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else if (type == PlotArea::Pattern) { ui.lFillingFirstColor->setText(i18n("Color:")); ui.lFillingColorStyle->hide(); ui.cbFillingColorStyle->hide(); ui.lFillingImageStyle->hide(); ui.cbFillingImageStyle->hide(); ui.lFillingBrushStyle->show(); ui.cbFillingBrushStyle->show(); ui.lFillingFileName->hide(); ui.leFillingFileName->hide(); ui.bFillingOpen->hide(); ui.lFillingFirstColor->show(); ui.kcbFillingFirstColor->show(); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingType(type); } void XYCurveDock::fillingColorStyleChanged(int index) { const auto style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lFillingFirstColor->setText(i18n("Color:")); ui.lFillingSecondColor->hide(); ui.kcbFillingSecondColor->hide(); } else { ui.lFillingFirstColor->setText(i18n("First color:")); ui.lFillingSecondColor->show(); ui.kcbFillingSecondColor->show(); ui.lFillingBrushStyle->hide(); ui.cbFillingBrushStyle->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingColorStyle(style); } void XYCurveDock::fillingImageStyleChanged(int index) { if (m_initializing) return; auto style = (PlotArea::BackgroundImageStyle)index; for (auto* curve : m_curvesList) curve->setFillingImageStyle(style); } void XYCurveDock::fillingBrushStyleChanged(int index) { if (m_initializing) return; auto style = (Qt::BrushStyle)index; for (auto* curve : m_curvesList) curve->setFillingBrushStyle(style); } void XYCurveDock::fillingFirstColorChanged(const QColor& c) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingFirstColor(c); m_initializing = true; GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, c); m_initializing = false; } void XYCurveDock::fillingSecondColorChanged(const QColor& c) { if (m_initializing) return; for (auto* curve : m_curvesList) curve->setFillingSecondColor(c); } /*! opens a file dialog and lets the user select the image file. */ void XYCurveDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "XYCurveDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const QByteArray& format : QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); if (f == QLatin1String("*.svg")) continue; formats.isEmpty() ? formats += f : formats += ' ' + f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.leFillingFileName->setText( path ); for (auto* curve : m_curvesList) curve->setFillingFileName(path); } void XYCurveDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.leFillingFileName->text(); for (auto* curve : m_curvesList) curve->setFillingFileName(fileName); } void XYCurveDock::fillingOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setFillingOpacity(opacity); } //"Error bars"-Tab void XYCurveDock::xErrorTypeChanged(int index) const { if (index == 0) { //no error ui.lXErrorDataPlus->setVisible(false); cbXErrorPlusColumn->setVisible(false); ui.lXErrorDataMinus->setVisible(false); cbXErrorMinusColumn->setVisible(false); } else if (index == 1) { //symmetric error ui.lXErrorDataPlus->setVisible(true); cbXErrorPlusColumn->setVisible(true); ui.lXErrorDataMinus->setVisible(false); cbXErrorMinusColumn->setVisible(false); ui.lXErrorDataPlus->setText(i18n("Data, +-")); } else if (index == 2) { //asymmetric error ui.lXErrorDataPlus->setVisible(true); cbXErrorPlusColumn->setVisible(true); ui.lXErrorDataMinus->setVisible(true); cbXErrorMinusColumn->setVisible(true); ui.lXErrorDataPlus->setText(i18n("Data, +")); } bool b = (index!=0 || ui.cbYErrorType->currentIndex()!=0); ui.lErrorFormat->setVisible(b); ui.lErrorBarsType->setVisible(b); ui.cbErrorBarsType->setVisible(b); ui.lErrorBarsStyle->setVisible(b); ui.cbErrorBarsStyle->setVisible(b); ui.lErrorBarsColor->setVisible(b); ui.kcbErrorBarsColor->setVisible(b); ui.lErrorBarsWidth->setVisible(b); ui.sbErrorBarsWidth->setVisible(b); ui.lErrorBarsOpacity->setVisible(b); ui.sbErrorBarsOpacity->setVisible(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setXErrorType(XYCurve::ErrorType(index)); } void XYCurveDock::xErrorPlusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column); for (auto* curve : m_curvesList) curve->setXErrorPlusColumn(column); } void XYCurveDock::xErrorMinusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column); for (auto* curve : m_curvesList) curve->setXErrorMinusColumn(column); } void XYCurveDock::yErrorTypeChanged(int index) const { if (index == 0) { //no error ui.lYErrorDataPlus->setVisible(false); cbYErrorPlusColumn->setVisible(false); ui.lYErrorDataMinus->setVisible(false); cbYErrorMinusColumn->setVisible(false); } else if (index == 1) { //symmetric error ui.lYErrorDataPlus->setVisible(true); cbYErrorPlusColumn->setVisible(true); ui.lYErrorDataMinus->setVisible(false); cbYErrorMinusColumn->setVisible(false); ui.lYErrorDataPlus->setText(i18n("Data, +-")); } else if (index == 2) { //asymmetric error ui.lYErrorDataPlus->setVisible(true); cbYErrorPlusColumn->setVisible(true); ui.lYErrorDataMinus->setVisible(true); cbYErrorMinusColumn->setVisible(true); ui.lYErrorDataPlus->setText(i18n("Data, +")); } bool b = (index!=0 || ui.cbXErrorType->currentIndex()!=0); ui.lErrorFormat->setVisible(b); ui.lErrorBarsType->setVisible(b); ui.cbErrorBarsType->setVisible(b); ui.lErrorBarsStyle->setVisible(b); ui.cbErrorBarsStyle->setVisible(b); ui.lErrorBarsColor->setVisible(b); ui.kcbErrorBarsColor->setVisible(b); ui.lErrorBarsWidth->setVisible(b); ui.sbErrorBarsWidth->setVisible(b); ui.lErrorBarsOpacity->setVisible(b); ui.sbErrorBarsOpacity->setVisible(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setYErrorType(XYCurve::ErrorType(index)); } void XYCurveDock::yErrorPlusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column); for (auto* curve : m_curvesList) curve->setYErrorPlusColumn(column); } void XYCurveDock::yErrorMinusColumnChanged(const QModelIndex& index) const { Q_UNUSED(index); if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); Q_ASSERT(column); for (auto* curve : m_curvesList) curve->setYErrorMinusColumn(column); } void XYCurveDock::errorBarsTypeChanged(int index) const { auto type = XYCurve::ErrorBarsType(index); bool b = (type == XYCurve::ErrorBarsWithEnds); ui.lErrorBarsCapSize->setVisible(b); ui.sbErrorBarsCapSize->setVisible(b); if (m_initializing) return; for (auto* curve : m_curvesList) curve->setErrorBarsType(type); } void XYCurveDock::errorBarsCapSizeChanged(double value) const { if (m_initializing) return; float size = Worksheet::convertToSceneUnits(value, Worksheet::Point); for (auto* curve : m_curvesList) curve->setErrorBarsCapSize(size); } void XYCurveDock::errorBarsStyleChanged(int index) const { if (m_initializing) return; auto penStyle = Qt::PenStyle(index); QPen pen; for (auto* curve : m_curvesList) { pen = curve->errorBarsPen(); pen.setStyle(penStyle); curve->setErrorBarsPen(pen); } } void XYCurveDock::errorBarsColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->errorBarsPen(); pen.setColor(color); curve->setErrorBarsPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbErrorBarsStyle, color); m_initializing = false; } void XYCurveDock::errorBarsWidthChanged(double value) const { if (m_initializing) return; QPen pen; for (auto* curve : m_curvesList) { pen = curve->errorBarsPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); curve->setErrorBarsPen(pen); } } void XYCurveDock::errorBarsOpacityChanged(int value) const { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* curve : m_curvesList) curve->setErrorBarsOpacity(opacity); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) uiGeneralTab.leName->setText(aspect->name()); else if (aspect->comment() != uiGeneralTab.leComment->text()) uiGeneralTab.leComment->setText(aspect->comment()); m_initializing = false; } void XYCurveDock::curveXColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbXColumn, column); cbXColumn->useCurrentIndexText(true); cbXColumn->setInvalid(false); m_initializing = false; } void XYCurveDock::curveYColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbYColumn, column); cbYColumn->useCurrentIndexText(true); cbYColumn->setInvalid(false); m_initializing = false; } void XYCurveDock::curveVisibilityChanged(bool on) { m_initializing = true; uiGeneralTab.chkVisible->setChecked(on); m_initializing = false; } //Line-Tab void XYCurveDock::curveLineTypeChanged(XYCurve::LineType type) { m_initializing = true; ui.cbLineType->setCurrentIndex( (int) type); m_initializing = false; } void XYCurveDock::curveLineSkipGapsChanged(bool skip) { m_initializing = true; ui.chkLineSkipGaps->setChecked(skip); m_initializing = false; } void XYCurveDock::curveLineIncreasingXOnlyChanged(bool incr) { m_initializing = true; ui.chkLineIncreasingXOnly->setChecked(incr); m_initializing = false; } void XYCurveDock::curveLineInterpolationPointsCountChanged(int count) { m_initializing = true; ui.sbLineInterpolationPointsCount->setValue(count); m_initializing = false; } void XYCurveDock::curveLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbLineStyle->setCurrentIndex( (int)pen.style()); ui.kcbLineColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbLineStyle, pen.color()); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits( pen.widthF(), Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void XYCurveDock::curveDropLineTypeChanged(XYCurve::DropLineType type) { m_initializing = true; ui.cbDropLineType->setCurrentIndex( (int)type ); m_initializing = false; } void XYCurveDock::curveDropLinePenChanged(const QPen& pen) { m_initializing = true; ui.cbDropLineStyle->setCurrentIndex( (int) pen.style()); ui.kcbDropLineColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbDropLineStyle, pen.color()); ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveDropLineOpacityChanged(qreal opacity) { m_initializing = true; ui.sbDropLineOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //Symbol-Tab void XYCurveDock::curveSymbolsStyleChanged(Symbol::Style style) { m_initializing = true; ui.cbSymbolStyle->setCurrentIndex((int)style); m_initializing = false; } void XYCurveDock::curveSymbolsSizeChanged(qreal size) { m_initializing = true; ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveSymbolsRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbSymbolRotation->setValue(angle); m_initializing = false; } void XYCurveDock::curveSymbolsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbSymbolOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void XYCurveDock::curveSymbolsBrushChanged(const QBrush& brush) { m_initializing = true; ui.cbSymbolFillingStyle->setCurrentIndex((int) brush.style()); ui.kcbSymbolFillingColor->setColor(brush.color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, brush.color()); m_initializing = false; } void XYCurveDock::curveSymbolsPenChanged(const QPen& pen) { m_initializing = true; ui.cbSymbolBorderStyle->setCurrentIndex( (int) pen.style()); ui.kcbSymbolBorderColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, pen.color()); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(), Worksheet::Point)); m_initializing = false; } //Values-Tab void XYCurveDock::curveValuesTypeChanged(XYCurve::ValuesType type) { m_initializing = true; ui.cbValuesType->setCurrentIndex((int) type); m_initializing = false; } void XYCurveDock::curveValuesColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbValuesColumn, column); m_initializing = false; } void XYCurveDock::curveValuesPositionChanged(XYCurve::ValuesPosition position) { m_initializing = true; ui.cbValuesPosition->setCurrentIndex((int) position); m_initializing = false; } void XYCurveDock::curveValuesDistanceChanged(qreal distance) { m_initializing = true; ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(distance, Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveValuesRotationAngleChanged(qreal angle) { m_initializing = true; ui.sbValuesRotation->setValue(angle); m_initializing = false; } void XYCurveDock::curveValuesOpacityChanged(qreal opacity) { m_initializing = true; ui.sbValuesOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void XYCurveDock::curveValuesPrefixChanged(const QString& prefix) { m_initializing = true; ui.leValuesPrefix->setText(prefix); m_initializing = false; } void XYCurveDock::curveValuesSuffixChanged(const QString& suffix) { m_initializing = true; ui.leValuesSuffix->setText(suffix); m_initializing = false; } void XYCurveDock::curveValuesFontChanged(QFont font) { m_initializing = true; font.setPointSizeF( round(Worksheet::convertFromSceneUnits(font.pixelSize(), Worksheet::Point)) ); ui.kfrValuesFont->setFont(font); m_initializing = false; } void XYCurveDock::curveValuesColorChanged(QColor color) { m_initializing = true; ui.kcbValuesColor->setColor(color); m_initializing = false; } //Filling void XYCurveDock::curveFillingPositionChanged(XYCurve::FillingPosition position) { m_initializing = true; ui.cbFillingPosition->setCurrentIndex((int)position); m_initializing = false; } void XYCurveDock::curveFillingTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbFillingType->setCurrentIndex(type); m_initializing = false; } void XYCurveDock::curveFillingColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbFillingColorStyle->setCurrentIndex(style); m_initializing = false; } void XYCurveDock::curveFillingImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbFillingImageStyle->setCurrentIndex(style); m_initializing = false; } void XYCurveDock::curveFillingBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbFillingBrushStyle->setCurrentIndex(style); m_initializing = false; } void XYCurveDock::curveFillingFirstColorChanged(QColor& color) { m_initializing = true; ui.kcbFillingFirstColor->setColor(color); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, color); m_initializing = false; } void XYCurveDock::curveFillingSecondColorChanged(QColor& color) { m_initializing = true; ui.kcbFillingSecondColor->setColor(color); m_initializing = false; } void XYCurveDock::curveFillingFileNameChanged(QString& filename) { m_initializing = true; ui.leFillingFileName->setText(filename); m_initializing = false; } void XYCurveDock::curveFillingOpacityChanged(float opacity) { m_initializing = true; ui.sbFillingOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //"Error bars"-Tab void XYCurveDock::curveXErrorTypeChanged(XYCurve::ErrorType type) { m_initializing = true; ui.cbXErrorType->setCurrentIndex((int) type); m_initializing = false; } void XYCurveDock::curveXErrorPlusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbXErrorPlusColumn, column); m_initializing = false; } void XYCurveDock::curveXErrorMinusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbXErrorMinusColumn, column); m_initializing = false; } void XYCurveDock::curveYErrorTypeChanged(XYCurve::ErrorType type) { m_initializing = true; ui.cbYErrorType->setCurrentIndex((int) type); m_initializing = false; } void XYCurveDock::curveYErrorPlusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbYErrorPlusColumn, column); m_initializing = false; } void XYCurveDock::curveYErrorMinusColumnChanged(const AbstractColumn* column) { m_initializing = true; this->setModelIndexFromAspect(cbYErrorMinusColumn, column); m_initializing = false; } void XYCurveDock::curveErrorBarsCapSizeChanged(qreal size) { m_initializing = true; ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(size, Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveErrorBarsTypeChanged(XYCurve::ErrorBarsType type) { m_initializing = true; ui.cbErrorBarsType->setCurrentIndex( (int) type); m_initializing = false; } void XYCurveDock::curveErrorBarsPenChanged(const QPen& pen) { m_initializing = true; ui.cbErrorBarsStyle->setCurrentIndex( (int) pen.style()); ui.kcbErrorBarsColor->setColor( pen.color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, pen.color()); ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point) ); m_initializing = false; } void XYCurveDock::curveErrorBarsOpacityChanged(qreal opacity) { m_initializing = true; ui.sbErrorBarsOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } //************************************************************* //************************* Settings ************************** //************************************************************* void XYCurveDock::load() { //General //This data is read in XYCurveDock::setCurves(). //Line ui.cbLineType->setCurrentIndex( (int) m_curve->lineType() ); ui.chkLineSkipGaps->setChecked( m_curve->lineSkipGaps() ); ui.sbLineInterpolationPointsCount->setValue( m_curve->lineInterpolationPointsCount() ); ui.cbLineStyle->setCurrentIndex( (int) m_curve->linePen().style() ); ui.kcbLineColor->setColor( m_curve->linePen().color() ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->linePen().widthF(), Worksheet::Point) ); ui.sbLineOpacity->setValue( round(m_curve->lineOpacity()*100.0) ); //Drop lines ui.cbDropLineType->setCurrentIndex( (int) m_curve->dropLineType() ); ui.cbDropLineStyle->setCurrentIndex( (int) m_curve->dropLinePen().style() ); ui.kcbDropLineColor->setColor( m_curve->dropLinePen().color() ); ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->dropLinePen().widthF(),Worksheet::Point) ); ui.sbDropLineOpacity->setValue( round(m_curve->dropLineOpacity()*100.0) ); //Symbols ui.cbSymbolStyle->setCurrentIndex( (int)m_curve->symbolsStyle() ); ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(m_curve->symbolsSize(), Worksheet::Point) ); ui.sbSymbolRotation->setValue( m_curve->symbolsRotationAngle() ); ui.sbSymbolOpacity->setValue( round(m_curve->symbolsOpacity()*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( (int) m_curve->symbolsBrush().style() ); ui.kcbSymbolFillingColor->setColor( m_curve->symbolsBrush().color() ); ui.cbSymbolBorderStyle->setCurrentIndex( (int) m_curve->symbolsPen().style() ); ui.kcbSymbolBorderColor->setColor( m_curve->symbolsPen().color() ); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->symbolsPen().widthF(), Worksheet::Point) ); //Values ui.cbValuesType->setCurrentIndex( (int) m_curve->valuesType() ); ui.cbValuesPosition->setCurrentIndex( (int) m_curve->valuesPosition() ); ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(m_curve->valuesDistance(), Worksheet::Point) ); ui.sbValuesRotation->setValue( m_curve->valuesRotationAngle() ); ui.sbValuesOpacity->setValue( round(m_curve->valuesOpacity()*100.0) ); ui.leValuesPrefix->setText( m_curve->valuesPrefix() ); ui.leValuesSuffix->setText( m_curve->valuesSuffix() ); QFont valuesFont = m_curve->valuesFont(); valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Point)) ); ui.kfrValuesFont->setFont(valuesFont); ui.kcbValuesColor->setColor( m_curve->valuesColor() ); //Filling ui.cbFillingPosition->setCurrentIndex( (int) m_curve->fillingPosition() ); ui.cbFillingType->setCurrentIndex( (int)m_curve->fillingType() ); ui.cbFillingColorStyle->setCurrentIndex( (int) m_curve->fillingColorStyle() ); ui.cbFillingImageStyle->setCurrentIndex( (int) m_curve->fillingImageStyle() ); ui.cbFillingBrushStyle->setCurrentIndex( (int) m_curve->fillingBrushStyle() ); ui.leFillingFileName->setText( m_curve->fillingFileName() ); ui.kcbFillingFirstColor->setColor( m_curve->fillingFirstColor() ); ui.kcbFillingSecondColor->setColor( m_curve->fillingSecondColor() ); ui.sbFillingOpacity->setValue( round(m_curve->fillingOpacity()*100.0) ); //Error bars ui.cbXErrorType->setCurrentIndex( (int) m_curve->xErrorType() ); ui.cbYErrorType->setCurrentIndex( (int) m_curve->yErrorType() ); ui.cbErrorBarsType->setCurrentIndex( (int) m_curve->errorBarsType() ); ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(m_curve->errorBarsCapSize(), Worksheet::Point) ); ui.cbErrorBarsStyle->setCurrentIndex( (int) m_curve->errorBarsPen().style() ); ui.kcbErrorBarsColor->setColor( m_curve->errorBarsPen().color() ); ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(m_curve->errorBarsPen().widthF(),Worksheet::Point) ); ui.sbErrorBarsOpacity->setValue( round(m_curve->errorBarsOpacity()*100.0) ); m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); GuiTools::updatePenStyles(ui.cbDropLineStyle, ui.kcbDropLineColor->color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, ui.kcbSymbolFillingColor->color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, ui.kcbSymbolBorderColor->color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, ui.kcbErrorBarsColor->color()); m_initializing = false; } void XYCurveDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_curvesList.size(); if (size > 1) m_curve->beginMacro(i18n("%1 xy-curves: template \"%2\" loaded", size, name)); else m_curve->beginMacro(i18n("%1: template \"%2\" loaded", m_curve->name(), name)); this->loadConfig(config); m_curve->endMacro(); } void XYCurveDock::loadConfig(KConfig& config) { KConfigGroup group = config.group( "XYCurve" ); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //This data is read in XYCurveDock::setCurves(). //Line ui.cbLineType->setCurrentIndex( group.readEntry("LineType", (int) m_curve->lineType()) ); ui.chkLineSkipGaps->setChecked( group.readEntry("LineSkipGaps", m_curve->lineSkipGaps()) ); ui.sbLineInterpolationPointsCount->setValue( group.readEntry("LineInterpolationPointsCount", m_curve->lineInterpolationPointsCount()) ); ui.cbLineStyle->setCurrentIndex( group.readEntry("LineStyle", (int) m_curve->linePen().style()) ); ui.kcbLineColor->setColor( group.readEntry("LineColor", m_curve->linePen().color()) ); ui.sbLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("LineWidth", m_curve->linePen().widthF()), Worksheet::Point) ); ui.sbLineOpacity->setValue( round(group.readEntry("LineOpacity", m_curve->lineOpacity())*100.0) ); //Drop lines ui.cbDropLineType->setCurrentIndex( group.readEntry("DropLineType", (int) m_curve->dropLineType()) ); ui.cbDropLineStyle->setCurrentIndex( group.readEntry("DropLineStyle", (int) m_curve->dropLinePen().style()) ); ui.kcbDropLineColor->setColor( group.readEntry("DropLineColor", m_curve->dropLinePen().color()) ); ui.sbDropLineWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("DropLineWidth", m_curve->dropLinePen().widthF()),Worksheet::Point) ); ui.sbDropLineOpacity->setValue( round(group.readEntry("DropLineOpacity", m_curve->dropLineOpacity())*100.0) ); //Symbols ui.cbSymbolStyle->setCurrentIndex( group.readEntry("SymbolStyle", (int)m_curve->symbolsStyle()) ); ui.sbSymbolSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolSize", m_curve->symbolsSize()), Worksheet::Point) ); ui.sbSymbolRotation->setValue( group.readEntry("SymbolRotation", m_curve->symbolsRotationAngle()) ); ui.sbSymbolOpacity->setValue( round(group.readEntry("SymbolOpacity", m_curve->symbolsOpacity())*100.0) ); ui.cbSymbolFillingStyle->setCurrentIndex( group.readEntry("SymbolFillingStyle", (int) m_curve->symbolsBrush().style()) ); ui.kcbSymbolFillingColor->setColor( group.readEntry("SymbolFillingColor", m_curve->symbolsBrush().color()) ); ui.cbSymbolBorderStyle->setCurrentIndex( group.readEntry("SymbolBorderStyle", (int) m_curve->symbolsPen().style()) ); ui.kcbSymbolBorderColor->setColor( group.readEntry("SymbolBorderColor", m_curve->symbolsPen().color()) ); ui.sbSymbolBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("SymbolBorderWidth",m_curve->symbolsPen().widthF()), Worksheet::Point) ); //Values ui.cbValuesType->setCurrentIndex( group.readEntry("ValuesType", (int) m_curve->valuesType()) ); ui.cbValuesPosition->setCurrentIndex( group.readEntry("ValuesPosition", (int) m_curve->valuesPosition()) ); ui.sbValuesDistance->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ValuesDistance", m_curve->valuesDistance()), Worksheet::Point) ); ui.sbValuesRotation->setValue( group.readEntry("ValuesRotation", m_curve->valuesRotationAngle()) ); ui.sbValuesOpacity->setValue( round(group.readEntry("ValuesOpacity",m_curve->valuesOpacity())*100.0) ); ui.leValuesPrefix->setText( group.readEntry("ValuesPrefix", m_curve->valuesPrefix()) ); ui.leValuesSuffix->setText( group.readEntry("ValuesSuffix", m_curve->valuesSuffix()) ); QFont valuesFont = m_curve->valuesFont(); valuesFont.setPointSizeF( round(Worksheet::convertFromSceneUnits(valuesFont.pixelSize(), Worksheet::Point)) ); ui.kfrValuesFont->setFont( group.readEntry("ValuesFont", valuesFont) ); ui.kcbValuesColor->setColor( group.readEntry("ValuesColor", m_curve->valuesColor()) ); //Filling ui.cbFillingPosition->setCurrentIndex( group.readEntry("FillingPosition", (int) m_curve->fillingPosition()) ); ui.cbFillingType->setCurrentIndex( group.readEntry("FillingType", (int) m_curve->fillingType()) ); ui.cbFillingColorStyle->setCurrentIndex( group.readEntry("FillingColorStyle", (int) m_curve->fillingColorStyle()) ); ui.cbFillingImageStyle->setCurrentIndex( group.readEntry("FillingImageStyle", (int) m_curve->fillingImageStyle()) ); ui.cbFillingBrushStyle->setCurrentIndex( group.readEntry("FillingBrushStyle", (int) m_curve->fillingBrushStyle()) ); ui.leFillingFileName->setText( group.readEntry("FillingFileName", m_curve->fillingFileName()) ); ui.kcbFillingFirstColor->setColor( group.readEntry("FillingFirstColor", m_curve->fillingFirstColor()) ); ui.kcbFillingSecondColor->setColor( group.readEntry("FillingSecondColor", m_curve->fillingSecondColor()) ); ui.sbFillingOpacity->setValue( round(group.readEntry("FillingOpacity", m_curve->fillingOpacity())*100.0) ); //Error bars ui.cbXErrorType->setCurrentIndex( group.readEntry("XErrorType", (int) m_curve->xErrorType()) ); ui.cbYErrorType->setCurrentIndex( group.readEntry("YErrorType", (int) m_curve->yErrorType()) ); ui.cbErrorBarsType->setCurrentIndex( group.readEntry("ErrorBarsType", (int) m_curve->errorBarsType()) ); ui.sbErrorBarsCapSize->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsCapSize", m_curve->errorBarsCapSize()), Worksheet::Point) ); ui.cbErrorBarsStyle->setCurrentIndex( group.readEntry("ErrorBarsStyle", (int) m_curve->errorBarsPen().style()) ); ui.kcbErrorBarsColor->setColor( group.readEntry("ErrorBarsColor", m_curve->errorBarsPen().color()) ); ui.sbErrorBarsWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("ErrorBarsWidth", m_curve->errorBarsPen().widthF()),Worksheet::Point) ); ui.sbErrorBarsOpacity->setValue( round(group.readEntry("ErrorBarsOpacity", m_curve->errorBarsOpacity())*100.0) ); m_initializing = true; GuiTools::updatePenStyles(ui.cbLineStyle, ui.kcbLineColor->color()); GuiTools::updatePenStyles(ui.cbDropLineStyle, ui.kcbDropLineColor->color()); GuiTools::updateBrushStyles(ui.cbSymbolFillingStyle, ui.kcbSymbolFillingColor->color()); GuiTools::updatePenStyles(ui.cbSymbolBorderStyle, ui.kcbSymbolBorderColor->color()); GuiTools::updatePenStyles(ui.cbErrorBarsStyle, ui.kcbErrorBarsColor->color()); GuiTools::updateBrushStyles(ui.cbFillingBrushStyle, ui.kcbFillingFirstColor->color()); m_initializing = false; } void XYCurveDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group( "XYCurve" ); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. group.writeEntry("LineType", ui.cbLineType->currentIndex()); group.writeEntry("LineSkipGaps", ui.chkLineSkipGaps->isChecked()); group.writeEntry("LineInterpolationPointsCount", ui.sbLineInterpolationPointsCount->value() ); group.writeEntry("LineStyle", ui.cbLineStyle->currentIndex()); group.writeEntry("LineColor", ui.kcbLineColor->color()); group.writeEntry("LineWidth", Worksheet::convertToSceneUnits(ui.sbLineWidth->value(),Worksheet::Point) ); group.writeEntry("LineOpacity", ui.sbLineOpacity->value()/100.0); //Drop Line group.writeEntry("DropLineType", ui.cbDropLineType->currentIndex()); group.writeEntry("DropLineStyle", ui.cbDropLineStyle->currentIndex()); group.writeEntry("DropLineColor", ui.kcbDropLineColor->color()); group.writeEntry("DropLineWidth", Worksheet::convertToSceneUnits(ui.sbDropLineWidth->value(),Worksheet::Point) ); group.writeEntry("DropLineOpacity", ui.sbDropLineOpacity->value()/100.0); //Symbol (TODO: character) group.writeEntry("SymbolStyle", ui.cbSymbolStyle->currentIndex()); group.writeEntry("SymbolSize", Worksheet::convertToSceneUnits(ui.sbSymbolSize->value(),Worksheet::Point)); group.writeEntry("SymbolRotation", ui.sbSymbolRotation->value()); group.writeEntry("SymbolOpacity", ui.sbSymbolOpacity->value()/100.0); group.writeEntry("SymbolFillingStyle", ui.cbSymbolFillingStyle->currentIndex()); group.writeEntry("SymbolFillingColor", ui.kcbSymbolFillingColor->color()); group.writeEntry("SymbolBorderStyle", ui.cbSymbolBorderStyle->currentIndex()); group.writeEntry("SymbolBorderColor", ui.kcbSymbolBorderColor->color()); group.writeEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(ui.sbSymbolBorderWidth->value(),Worksheet::Point)); //Values group.writeEntry("ValuesType", ui.cbValuesType->currentIndex()); group.writeEntry("ValuesPosition", ui.cbValuesPosition->currentIndex()); group.writeEntry("ValuesDistance", Worksheet::convertToSceneUnits(ui.sbValuesDistance->value(),Worksheet::Point)); group.writeEntry("ValuesRotation", ui.sbValuesRotation->value()); group.writeEntry("ValuesOpacity", ui.sbValuesOpacity->value()/100.0); group.writeEntry("ValuesPrefix", ui.leValuesPrefix->text()); group.writeEntry("ValuesSuffix", ui.leValuesSuffix->text()); group.writeEntry("ValuesFont", ui.kfrValuesFont->font()); group.writeEntry("ValuesColor", ui.kcbValuesColor->color()); //Filling group.writeEntry("FillingPosition", ui.cbFillingPosition->currentIndex()); group.writeEntry("FillingType", ui.cbFillingType->currentIndex()); group.writeEntry("FillingColorStyle", ui.cbFillingColorStyle->currentIndex()); group.writeEntry("FillingImageStyle", ui.cbFillingImageStyle->currentIndex()); group.writeEntry("FillingBrushStyle", ui.cbFillingBrushStyle->currentIndex()); group.writeEntry("FillingFileName", ui.leFillingFileName->text()); group.writeEntry("FillingFirstColor", ui.kcbFillingFirstColor->color()); group.writeEntry("FillingSecondColor", ui.kcbFillingSecondColor->color()); group.writeEntry("FillingOpacity", ui.sbFillingOpacity->value()/100.0); //Error bars group.writeEntry("XErrorType", ui.cbXErrorType->currentIndex()); group.writeEntry("YErrorType", ui.cbYErrorType->currentIndex()); group.writeEntry("ErrorBarsType", ui.cbErrorBarsType->currentIndex()); group.writeEntry("ErrorBarsCapSize", Worksheet::convertToSceneUnits(ui.sbErrorBarsCapSize->value(),Worksheet::Point) ); group.writeEntry("ErrorBarsStyle", ui.cbErrorBarsStyle->currentIndex()); group.writeEntry("ErrorBarsColor", ui.kcbErrorBarsColor->color()); group.writeEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(ui.sbErrorBarsWidth->value(),Worksheet::Point) ); group.writeEntry("ErrorBarsOpacity", ui.sbErrorBarsOpacity->value()/100.0); config.sync(); } diff --git a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp index 41a701406..1942f195f 100644 --- a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp @@ -1,1385 +1,1389 @@ /*************************************************************************** File : XYFitCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Description : widget for editing properties of fit curves ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYFitCurveDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/lib/macros.h" #include "backend/gsl/ExpressionParser.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/widgets/ConstantsWidget.h" #include "kdefrontend/widgets/FunctionsWidget.h" #include "kdefrontend/widgets/FitOptionsWidget.h" #include "kdefrontend/widgets/FitParametersWidget.h" #include #include #include #include #include #include extern "C" { #include "backend/nsl/nsl_sf_stats.h" } /*! \class XYFitCurveDock \brief Provides a widget for editing the properties of the XYFitCurves (2D-curves defined by a fit model) currently selected in the project explorer. If more then one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYFitCurveDock::XYFitCurveDock(QWidget* parent) : XYCurveDock(parent) { //remove the tab "Error bars" ui.tabWidget->removeTab(5); } /*! * set up "General" tab */ void XYFitCurveDock::setupGeneral() { DEBUG("XYFitCurveDock::setupGeneral()"); QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); m_leName = uiGeneralTab.leName; m_leComment = uiGeneralTab.leComment; auto* gridLayout = static_cast(generalTab->layout()); gridLayout->setContentsMargins(2, 2, 2, 2); gridLayout->setHorizontalSpacing(2); gridLayout->setVerticalSpacing(2); uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); cbDataSourceCurve = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbDataSourceCurve, 5, 3, 1, 4); cbXDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXDataColumn, 6, 3, 1, 4); cbXErrorColumn = new TreeViewComboBox(generalTab); cbXErrorColumn->setEnabled(false); uiGeneralTab.hlXError->addWidget(cbXErrorColumn); cbYDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYDataColumn, 7, 3, 1, 4); cbYErrorColumn = new TreeViewComboBox(generalTab); cbYErrorColumn->setEnabled(false); uiGeneralTab.hlYWeight->addWidget(cbYErrorColumn); // X/Y-Weight for (int i = 0; i < NSL_FIT_WEIGHT_TYPE_COUNT; i++) { uiGeneralTab.cbXWeight->addItem(nsl_fit_weight_type_name[i]); uiGeneralTab.cbYWeight->addItem(nsl_fit_weight_type_name[i]); } uiGeneralTab.cbXWeight->setCurrentIndex(nsl_fit_weight_no); uiGeneralTab.cbYWeight->setCurrentIndex(nsl_fit_weight_no); for (int i = 0; i < NSL_FIT_MODEL_CATEGORY_COUNT; i++) uiGeneralTab.cbCategory->addItem(nsl_fit_model_category_name[i]); uiGeneralTab.teEquation->setMaximumHeight(uiGeneralTab.leName->sizeHint().height() * 2); fitParametersWidget = new FitParametersWidget(uiGeneralTab.frameParameters); auto* l = new QVBoxLayout(); l->setContentsMargins(0, 0, 0, 0); l->addWidget(fitParametersWidget); uiGeneralTab.frameParameters->setLayout(l); //use white background in the preview label QPalette p; p.setColor(QPalette::Window, Qt::white); uiGeneralTab.lFuncPic->setAutoFillBackground(true); uiGeneralTab.lFuncPic->setPalette(p); uiGeneralTab.tbConstants->setIcon(QIcon::fromTheme("labplot-format-text-symbol")); uiGeneralTab.tbFunctions->setIcon(QIcon::fromTheme("preferences-desktop-font")); uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build")); // TODO: setting checked background color to unchecked color // p = uiGeneralTab.lData->palette(); // QWidget::palette().color(QWidget::backgroundRole()) // not working with 'transparent' // p.setColor(QPalette::Base, Qt::transparent); // uiGeneralTab.lData->setPalette(p); // see https://forum.qt.io/topic/41325/solved-background-of-checked-qpushbutton-with-stylesheet/2 // Styles not usable (here: text color not theme dependent). see https://forum.qt.io/topic/60546/qpushbutton-default-windows-style-sheet/9 // uiGeneralTab.lData->setStyleSheet("QToolButton:checked{background-color: transparent;border: 3px transparent;padding: 3px;}"); // uiGeneralTab.lData->setAutoFillBackground(true); uiGeneralTab.twLog->setEditTriggers(QAbstractItemView::NoEditTriggers); uiGeneralTab.twParameters->setEditTriggers(QAbstractItemView::NoEditTriggers); uiGeneralTab.twGoodness->setEditTriggers(QAbstractItemView::NoEditTriggers); //don't allow word wrapping in the log-table for the multi-line iterations string uiGeneralTab.twLog->setWordWrap(false); // show all options per default showDataOptions(true); showFitOptions(true); showWeightsOptions(true); showParameters(true); showResults(true); // context menus uiGeneralTab.twParameters->setContextMenuPolicy(Qt::CustomContextMenu); uiGeneralTab.twGoodness->setContextMenuPolicy(Qt::CustomContextMenu); uiGeneralTab.twLog->setContextMenuPolicy(Qt::CustomContextMenu); connect(uiGeneralTab.twParameters, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(resultParametersContextMenuRequest(QPoint)) ); connect(uiGeneralTab.twGoodness, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(resultGoodnessContextMenuRequest(QPoint)) ); connect(uiGeneralTab.twLog, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(resultLogContextMenuRequest(QPoint)) ); uiGeneralTab.twLog->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); uiGeneralTab.twGoodness->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); uiGeneralTab.twGoodness->item(0, 1)->setText(UTF8_QSTRING("χ²")); uiGeneralTab.twGoodness->item(1, 1)->setText(i18n("reduced") + ' ' + UTF8_QSTRING("χ²") + " (" + UTF8_QSTRING("χ²") + "/dof)"); uiGeneralTab.twGoodness->item(3, 1)->setText(UTF8_QSTRING("R²")); uiGeneralTab.twGoodness->item(4, 1)->setText(UTF8_QSTRING("R̄²")); uiGeneralTab.twGoodness->item(5, 0)->setText(UTF8_QSTRING("χ²") + ' ' + i18n("test")); uiGeneralTab.twGoodness->item(5, 1)->setText("P > " + UTF8_QSTRING("χ²")); auto* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); //Slots connect(uiGeneralTab.leName, &QLineEdit::textChanged, this, &XYFitCurveDock::nameChanged); connect(uiGeneralTab.leComment, &QLineEdit::textChanged, this, &XYFitCurveDock::commentChanged); connect(uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool))); connect(uiGeneralTab.cbDataSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(dataSourceTypeChanged(int))); connect(uiGeneralTab.lWeights, &QPushButton::clicked, this, &XYFitCurveDock::showWeightsOptions); connect(uiGeneralTab.cbXWeight, SIGNAL(currentIndexChanged(int)), this, SLOT(xWeightChanged(int))); connect(uiGeneralTab.cbYWeight, SIGNAL(currentIndexChanged(int)), this, SLOT(yWeightChanged(int))); connect(uiGeneralTab.cbCategory, SIGNAL(currentIndexChanged(int)), this, SLOT(categoryChanged(int))); connect(uiGeneralTab.cbModel, SIGNAL(currentIndexChanged(int)), this, SLOT(modelTypeChanged(int))); connect(uiGeneralTab.sbDegree, SIGNAL(valueChanged(int)), this, SLOT(updateModelEquation())); connect(uiGeneralTab.teEquation, SIGNAL(expressionChanged()), this, SLOT(expressionChanged())); connect(uiGeneralTab.tbConstants, SIGNAL(clicked()), this, SLOT(showConstants())); connect(uiGeneralTab.tbFunctions, SIGNAL(clicked()), this, SLOT(showFunctions())); connect(uiGeneralTab.pbOptions, SIGNAL(clicked()), this, SLOT(showOptions())); connect(uiGeneralTab.pbRecalculate, SIGNAL(clicked()), this, SLOT(recalculateClicked())); connect(uiGeneralTab.lData, &QPushButton::clicked, this, &XYFitCurveDock::showDataOptions); connect(uiGeneralTab.lFit, &QPushButton::clicked, this, &XYFitCurveDock::showFitOptions); connect(uiGeneralTab.lParameters, &QPushButton::clicked, this, &XYFitCurveDock::showParameters); connect(uiGeneralTab.lResults, &QPushButton::clicked, this, &XYFitCurveDock::showResults); connect(cbDataSourceCurve, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(dataSourceCurveChanged(QModelIndex))); connect(cbXDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xDataColumnChanged(QModelIndex))); connect(cbYDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yDataColumnChanged(QModelIndex))); connect(cbXErrorColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xErrorColumnChanged(QModelIndex))); connect(cbYErrorColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yErrorColumnChanged(QModelIndex))); } /* * load curve settings */ void XYFitCurveDock::initGeneralTab() { DEBUG("XYFitCurveDock::initGeneralTab()"); //if there are more then one curve in the list, disable the tab "general" if (m_curvesList.size() == 1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); } else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.leName->setText(QString()); uiGeneralTab.leComment->setText(QString()); } auto* analysisCurve = dynamic_cast(m_curve); checkColumnAvailability(cbXDataColumn, analysisCurve->xDataColumn(), analysisCurve->xDataColumnPath()); checkColumnAvailability(cbYDataColumn, analysisCurve->yDataColumn(), analysisCurve->yDataColumnPath()); auto* fitCurve = dynamic_cast(m_curve); checkColumnAvailability(cbXErrorColumn, fitCurve->xErrorColumn(), fitCurve->xErrorColumnPath()); checkColumnAvailability(cbYErrorColumn, fitCurve->yErrorColumn(), fitCurve->yErrorColumnPath()); uiGeneralTab.cbDataSourceType->setCurrentIndex(m_fitCurve->dataSourceType()); this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_fitCurve->dataSourceCurve()); XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_fitCurve->xDataColumn()); XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_fitCurve->yDataColumn()); XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, m_fitCurve->xErrorColumn()); XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, m_fitCurve->yErrorColumn()); int tmpModelType = m_fitData.modelType; // save type because it's reset when category changes if (m_fitData.modelCategory == nsl_fit_model_custom) uiGeneralTab.cbCategory->setCurrentIndex(uiGeneralTab.cbCategory->count() - 1); else uiGeneralTab.cbCategory->setCurrentIndex(m_fitData.modelCategory); if (m_fitData.modelCategory != nsl_fit_model_custom) uiGeneralTab.cbModel->setCurrentIndex(tmpModelType); m_fitData.modelType = tmpModelType; categoryChanged(m_fitData.modelCategory); // fill model types uiGeneralTab.cbXWeight->setCurrentIndex(m_fitData.xWeightsType); uiGeneralTab.cbYWeight->setCurrentIndex(m_fitData.yWeightsType); uiGeneralTab.sbDegree->setValue(m_fitData.degree); if (m_fitData.paramStartValues.size() > 0) { DEBUG(" B start value 0 = " << m_fitData.paramStartValues.at(0)); } DEBUG(" B model degree = " << m_fitData.degree); this->showFitResult(); uiGeneralTab.chkVisible->setChecked(m_curve->isVisible()); //Slots connect(m_fitCurve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_fitCurve, SIGNAL(dataSourceTypeChanged(XYAnalysisCurve::DataSourceType)), this, SLOT(curveDataSourceTypeChanged(XYAnalysisCurve::DataSourceType))); connect(m_fitCurve, SIGNAL(dataSourceCurveChanged(const XYCurve*)), this, SLOT(curveDataSourceCurveChanged(const XYCurve*))); connect(m_fitCurve, SIGNAL(xDataColumnChanged(const AbstractColumn*)), this, SLOT(curveXDataColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(yDataColumnChanged(const AbstractColumn*)), this, SLOT(curveYDataColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(xErrorColumnChanged(const AbstractColumn*)), this, SLOT(curveXErrorColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(yErrorColumnChanged(const AbstractColumn*)), this, SLOT(curveYErrorColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(fitDataChanged(XYFitCurve::FitData)), this, SLOT(curveFitDataChanged(XYFitCurve::FitData))); connect(m_fitCurve, SIGNAL(sourceDataChanged()), this, SLOT(enableRecalculate())); connect(fitParametersWidget, &FitParametersWidget::parametersChanged, this, &XYFitCurveDock::parametersChanged); connect(fitParametersWidget, &FitParametersWidget::parametersValid, this, &XYFitCurveDock::parametersValid); } void XYFitCurveDock::setModel() { DEBUG("XYFitCurveDock::setModel()"); QList list{AspectType::Folder, AspectType::Datapicker, AspectType::Worksheet, AspectType::CartesianPlot, AspectType::XYCurve}; cbDataSourceCurve->setTopLevelClasses(list); QList hiddenAspects; for (auto* curve : m_curvesList) hiddenAspects << curve; cbDataSourceCurve->setHiddenAspects(hiddenAspects); list = {AspectType::Folder, AspectType::Workbook, AspectType::Spreadsheet, AspectType::LiveDataSource, AspectType::Column, AspectType::CantorWorksheet, AspectType::Datapicker }; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbXErrorColumn->setTopLevelClasses(list); cbYErrorColumn->setTopLevelClasses(list); cbDataSourceCurve->setModel(m_aspectTreeModel); cbXDataColumn->setModel(m_aspectTreeModel); cbYDataColumn->setModel(m_aspectTreeModel); cbXErrorColumn->setModel(m_aspectTreeModel); cbYErrorColumn->setModel(m_aspectTreeModel); XYCurveDock::setModel(); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYFitCurveDock::setCurves(QList list) { DEBUG("XYFitCurveDock::setCurves()"); m_initializing = true; m_curvesList = list; m_curve = list.first(); m_fitCurve = dynamic_cast(m_curve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); this->setModel(); m_fitData = m_fitCurve->fitData(); if (m_fitData.paramStartValues.size() > 0) { DEBUG(" start value 1 = " << m_fitData.paramStartValues.at(0)); } DEBUG(" model degree = " << m_fitData.degree); DEBUG(" # params = " << m_fitData.paramNames.size()); DEBUG(" # start values = " << m_fitData.paramStartValues.size()); fitParametersWidget->setFitData(&m_fitData); initGeneralTab(); initTabs(); m_initializing = false; //init parameter list when not available if (m_fitData.paramStartValues.size() == 0) updateModelEquation(); enableRecalculate(); } //************************************************************* //**** SLOTs for changes triggered in XYFitCurveDock ***** //************************************************************* void XYFitCurveDock::dataSourceTypeChanged(int index) { const auto type = (XYAnalysisCurve::DataSourceType)index; if (type == XYAnalysisCurve::DataSourceSpreadsheet) { uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); uiGeneralTab.lXColumn->show(); cbXDataColumn->show(); uiGeneralTab.lYColumn->show(); cbYDataColumn->show(); } else { uiGeneralTab.lDataSourceCurve->show(); cbDataSourceCurve->show(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); } if (m_initializing) return; for (auto* curve : m_curvesList) dynamic_cast(curve)->setDataSourceType(type); } void XYFitCurveDock::dataSourceCurveChanged(const QModelIndex& index) { auto* aspect = static_cast(index.internalPointer()); auto* dataSourceCurve = dynamic_cast(aspect); if (m_initializing) return; for (auto* curve : m_curvesList) dynamic_cast(curve)->setDataSourceCurve(dataSourceCurve); } void XYFitCurveDock::xDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setXDataColumn(column); // set model dependent start values from new data XYFitCurve::initStartValues(m_fitData, m_curve); cbXDataColumn->useCurrentIndexText(true); cbXDataColumn->setInvalid(false); } void XYFitCurveDock::yDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setYDataColumn(column); // set model dependent start values from new data XYFitCurve::initStartValues(m_fitData, m_curve); cbYDataColumn->useCurrentIndexText(true); cbYDataColumn->setInvalid(false); } void XYFitCurveDock::xErrorColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setXErrorColumn(column); cbXErrorColumn->useCurrentIndexText(true); cbXErrorColumn->setInvalid(false); } void XYFitCurveDock::yErrorColumnChanged(const QModelIndex& index) { if (m_initializing) return; auto* aspect = static_cast(index.internalPointer()); auto* column = dynamic_cast(aspect); for (auto* curve : m_curvesList) dynamic_cast(curve)->setYErrorColumn(column); cbYErrorColumn->useCurrentIndexText(true); cbYErrorColumn->setInvalid(false); } ///////////////////////// fold/unfold options ////////////////////////////////////////////////// void XYFitCurveDock::showDataOptions(bool checked) { if (checked) { uiGeneralTab.lData->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.lDataSourceType->show(); uiGeneralTab.cbDataSourceType->show(); // select options for current source type dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); } else { uiGeneralTab.lData->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.lDataSourceType->hide(); uiGeneralTab.cbDataSourceType->hide(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); } } void XYFitCurveDock::showWeightsOptions(bool checked) { if (checked) { uiGeneralTab.lWeights->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.lXWeight->show(); uiGeneralTab.cbXWeight->show(); uiGeneralTab.lXErrorCol->show(); cbXErrorColumn->show(); uiGeneralTab.lYWeight->show(); uiGeneralTab.cbYWeight->show(); uiGeneralTab.lYErrorCol->show(); cbYErrorColumn->show(); } else { uiGeneralTab.lWeights->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.lXWeight->hide(); uiGeneralTab.cbXWeight->hide(); uiGeneralTab.lXErrorCol->hide(); cbXErrorColumn->hide(); uiGeneralTab.lYWeight->hide(); uiGeneralTab.cbYWeight->hide(); uiGeneralTab.lYErrorCol->hide(); cbYErrorColumn->hide(); } } void XYFitCurveDock::showFitOptions(bool checked) { if (checked) { uiGeneralTab.lFit->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.lCategory->show(); uiGeneralTab.cbCategory->show(); uiGeneralTab.lModel->show(); uiGeneralTab.cbModel->show(); uiGeneralTab.lEquation->show(); m_initializing = true; // do not change start parameter modelTypeChanged(uiGeneralTab.cbModel->currentIndex()); m_initializing = false; } else { uiGeneralTab.lFit->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.lCategory->hide(); uiGeneralTab.cbCategory->hide(); uiGeneralTab.lModel->hide(); uiGeneralTab.cbModel->hide(); uiGeneralTab.lDegree->hide(); uiGeneralTab.sbDegree->hide(); uiGeneralTab.lEquation->hide(); uiGeneralTab.lFuncPic->hide(); uiGeneralTab.teEquation->hide(); uiGeneralTab.tbFunctions->hide(); uiGeneralTab.tbConstants->hide(); } } void XYFitCurveDock::showParameters(bool checked) { if (checked) { uiGeneralTab.lParameters->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.frameParameters->show(); } else { uiGeneralTab.lParameters->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.frameParameters->hide(); } } void XYFitCurveDock::showResults(bool checked) { if (checked) { uiGeneralTab.lResults->setIcon(QIcon::fromTheme("arrow-down")); uiGeneralTab.twResults->show(); } else { uiGeneralTab.lResults->setIcon(QIcon::fromTheme("arrow-right")); uiGeneralTab.twResults->hide(); } } /////////////////////////////////////////////////////////////////////////// void XYFitCurveDock::xWeightChanged(int index) { DEBUG("xWeightChanged() weight = " << nsl_fit_weight_type_name[index]); m_fitData.xWeightsType = (nsl_fit_weight_type)index; // enable/disable weight column switch ((nsl_fit_weight_type)index) { case nsl_fit_weight_no: case nsl_fit_weight_statistical: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative: case nsl_fit_weight_relative_fit: cbXErrorColumn->setEnabled(false); uiGeneralTab.lXErrorCol->setEnabled(false); break; case nsl_fit_weight_instrumental: case nsl_fit_weight_direct: case nsl_fit_weight_inverse: cbXErrorColumn->setEnabled(true); uiGeneralTab.lXErrorCol->setEnabled(true); break; } enableRecalculate(); } void XYFitCurveDock::yWeightChanged(int index) { DEBUG("yWeightChanged() weight = " << nsl_fit_weight_type_name[index]); m_fitData.yWeightsType = (nsl_fit_weight_type)index; // enable/disable weight column switch ((nsl_fit_weight_type)index) { case nsl_fit_weight_no: case nsl_fit_weight_statistical: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative: case nsl_fit_weight_relative_fit: cbYErrorColumn->setEnabled(false); uiGeneralTab.lYErrorCol->setEnabled(false); break; case nsl_fit_weight_instrumental: case nsl_fit_weight_direct: case nsl_fit_weight_inverse: cbYErrorColumn->setEnabled(true); uiGeneralTab.lYErrorCol->setEnabled(true); break; } enableRecalculate(); } /*! * called when the fit model category (basic functions, peak functions etc.) was changed. * In the combobox for the model type shows the model types for the current category \index and calls \c modelTypeChanged() * to update the model type dependent widgets in the general-tab. */ void XYFitCurveDock::categoryChanged(int index) { if (index == nsl_fit_model_custom) { DEBUG("categoryChanged() category = \"nsl_fit_model_custom\""); } else { DEBUG("categoryChanged() category = \"" << nsl_fit_model_category_name[index] << "\""); } bool hasChanged = true; // nothing has changed when ... if (m_fitData.modelCategory == (nsl_fit_model_category)index || (m_fitData.modelCategory == nsl_fit_model_custom && index == uiGeneralTab.cbCategory->count() - 1) ) hasChanged = false; if (uiGeneralTab.cbCategory->currentIndex() == uiGeneralTab.cbCategory->count() - 1) m_fitData.modelCategory = nsl_fit_model_custom; else m_fitData.modelCategory = (nsl_fit_model_category)index; uiGeneralTab.cbModel->clear(); uiGeneralTab.cbModel->show(); uiGeneralTab.lModel->show(); switch (m_fitData.modelCategory) { case nsl_fit_model_basic: for (int i = 0; i < NSL_FIT_MODEL_BASIC_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_basic_name[i]); break; case nsl_fit_model_peak: { for (int i = 0; i < NSL_FIT_MODEL_PEAK_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_peak_name[i]); #if defined(_MSC_VER) // disable voigt model const QStandardItemModel* model = qobject_cast(uiGeneralTab.cbModel->model()); QStandardItem* item = model->item(nsl_fit_model_voigt); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); #endif break; } case nsl_fit_model_growth: for (int i = 0; i < NSL_FIT_MODEL_GROWTH_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_growth_name[i]); break; case nsl_fit_model_distribution: { for (int i = 0; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_sf_stats_distribution_name[i]); // not-used items are disabled here const auto* model = qobject_cast(uiGeneralTab.cbModel->model()); for (int i = 1; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) { // unused distributions if (i == nsl_sf_stats_levy_alpha_stable || i == nsl_sf_stats_levy_skew_alpha_stable || i == nsl_sf_stats_bernoulli) { QStandardItem* item = model->item(i); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); } } break; } case nsl_fit_model_custom: uiGeneralTab.cbModel->addItem(i18n("Custom")); uiGeneralTab.cbModel->hide(); uiGeneralTab.lModel->hide(); } if (hasChanged) { //show the fit-model for the currently selected default (first) fit-model uiGeneralTab.cbModel->setCurrentIndex(0); uiGeneralTab.sbDegree->setValue(1); // when model type does not change, call it here updateModelEquation(); } enableRecalculate(); } /*! * called when the fit model type (depends on category) was changed. * Updates the model type dependent widgets in the general-tab and calls \c updateModelEquation() to update the preview pixmap. */ void XYFitCurveDock::modelTypeChanged(int index) { DEBUG("modelTypeChanged() type = " << (unsigned int)index << ", initializing = " << m_initializing << ", current type = " << m_fitData.modelType); // leave if there is no selection if (index == -1) return; bool custom = false; if (m_fitData.modelCategory == nsl_fit_model_custom) custom = true; uiGeneralTab.teEquation->setReadOnly(!custom); uiGeneralTab.lModel->setVisible(!custom); uiGeneralTab.cbModel->setVisible(!custom); uiGeneralTab.tbFunctions->setVisible(custom); uiGeneralTab.tbConstants->setVisible(custom); // default settings uiGeneralTab.lDegree->setText(i18n("Degree:")); if (m_fitData.modelType != index) uiGeneralTab.sbDegree->setValue(1); switch (m_fitData.modelCategory) { case nsl_fit_model_basic: switch (index) { case nsl_fit_model_polynomial: case nsl_fit_model_fourier: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(10); break; case nsl_fit_model_power: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(2); break; case nsl_fit_model_exponential: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(10); break; default: uiGeneralTab.lDegree->setVisible(false); uiGeneralTab.sbDegree->setVisible(false); } break; case nsl_fit_model_peak: // all models support multiple peaks uiGeneralTab.lDegree->setText(i18n("Number of peaks:")); uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(9); break; case nsl_fit_model_growth: case nsl_fit_model_distribution: case nsl_fit_model_custom: uiGeneralTab.lDegree->setVisible(false); uiGeneralTab.sbDegree->setVisible(false); } m_fitData.modelType = index; updateModelEquation(); } /*! * Show the preview pixmap of the fit model expression for the current model category and type. * Called when the model type or the degree of the model were changed. */ void XYFitCurveDock::updateModelEquation() { if (m_fitData.modelCategory == nsl_fit_model_custom) { DEBUG("XYFitCurveDock::updateModelEquation() category = nsl_fit_model_custom, type = " << m_fitData.modelType); } else { DEBUG("XYFitCurveDock::updateModelEquation() category = " << nsl_fit_model_category_name[m_fitData.modelCategory] << ", type = " << m_fitData.modelType); } //this function can also be called when the value for the degree was changed -> update the fit data structure int degree = uiGeneralTab.sbDegree->value(); if (!m_initializing) { m_fitData.degree = degree; XYFitCurve::initFitData(m_fitData); // set model dependent start values from curve data XYFitCurve::initStartValues(m_fitData, m_curve); // udpate parameter widget fitParametersWidget->setFitData(&m_fitData); } // variables/parameter that are known QStringList vars = {"x"}; vars << m_fitData.paramNames; uiGeneralTab.teEquation->setVariables(vars); // set formula picture uiGeneralTab.lEquation->setText(QLatin1String("f(x) =")); QString file; switch (m_fitData.modelCategory) { case nsl_fit_model_basic: { // formula pic depends on degree QString numSuffix = QString::number(degree); if (degree > 4) numSuffix = '4'; if ((nsl_fit_model_type_basic)m_fitData.modelType == nsl_fit_model_power && degree > 2) numSuffix = '2'; file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_basic_pic_name[m_fitData.modelType]) + numSuffix + ".png"); break; } case nsl_fit_model_peak: { // formula pic depends on number of peaks QString numSuffix = QString::number(degree); if (degree > 4) numSuffix = '4'; file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_peak_pic_name[m_fitData.modelType]) + numSuffix + ".png"); break; } case nsl_fit_model_growth: file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_growth_pic_name[m_fitData.modelType]) + ".png"); break; case nsl_fit_model_distribution: file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/gsl_distributions/" + QString(nsl_sf_stats_distribution_pic_name[m_fitData.modelType]) + ".png"); // change label if (m_fitData.modelType == nsl_sf_stats_poisson) uiGeneralTab.lEquation->setText(QLatin1String("f(k)/A =")); else uiGeneralTab.lEquation->setText(QLatin1String("f(x)/A =")); break; case nsl_fit_model_custom: uiGeneralTab.lFuncPic->hide(); uiGeneralTab.teEquation->show(); uiGeneralTab.teEquation->setPlainText(m_fitData.model); } if (m_fitData.modelCategory != nsl_fit_model_custom) { - DEBUG("Model pixmap path = " << file.toStdString()); + DEBUG("Model pixmap path = " << STDSTRING(file)); uiGeneralTab.lFuncPic->setPixmap(file); uiGeneralTab.lFuncPic->show(); uiGeneralTab.teEquation->hide(); } enableRecalculate(); } void XYFitCurveDock::showConstants() { QMenu menu; ConstantsWidget constants(&menu); connect(&constants, SIGNAL(constantSelected(QString)), this, SLOT(insertConstant(QString))); connect(&constants, SIGNAL(constantSelected(QString)), &menu, SLOT(close())); connect(&constants, SIGNAL(canceled()), &menu, SLOT(close())); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&constants); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbConstants->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.tbConstants->mapToGlobal(pos)); } void XYFitCurveDock::showFunctions() { QMenu menu; FunctionsWidget functions(&menu); connect(&functions, SIGNAL(functionSelected(QString)), this, SLOT(insertFunction(QString))); connect(&functions, SIGNAL(functionSelected(QString)), &menu, SLOT(close())); connect(&functions, SIGNAL(canceled()), &menu, SLOT(close())); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&functions); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbFunctions->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.tbFunctions->mapToGlobal(pos)); } /*! * Update parameter by parsing expression * Only called for custom fit model */ void XYFitCurveDock::updateParameterList() { DEBUG("XYFitCurveDock::updateParameterList()"); // use current model function m_fitData.model = uiGeneralTab.teEquation->toPlainText(); ExpressionParser* parser = ExpressionParser::getInstance(); QStringList vars; // variables that are known vars << "x"; //TODO: others? m_fitData.paramNames = m_fitData.paramNamesUtf8 = parser->getParameter(m_fitData.model, vars); // if number of parameter changed int oldNumberOfParameter = m_fitData.paramStartValues.size(); int numberOfParameter = m_fitData.paramNames.size(); DEBUG(" old number of parameter: " << oldNumberOfParameter << " new number of parameter: " << numberOfParameter); if (numberOfParameter != oldNumberOfParameter) { m_fitData.paramStartValues.resize(numberOfParameter); m_fitData.paramFixed.resize(numberOfParameter); m_fitData.paramLowerLimits.resize(numberOfParameter); m_fitData.paramUpperLimits.resize(numberOfParameter); } if (numberOfParameter > oldNumberOfParameter) { for (int i = oldNumberOfParameter; i < numberOfParameter; ++i) { m_fitData.paramStartValues[i] = 1.0; m_fitData.paramFixed[i] = false; m_fitData.paramLowerLimits[i] = -std::numeric_limits::max(); m_fitData.paramUpperLimits[i] = std::numeric_limits::max(); } } parametersChanged(); } /*! * called when parameter names and/or start values for the model were changed * also called from parameter widget */ void XYFitCurveDock::parametersChanged(bool updateParameterWidget) { DEBUG("XYFitCurveDock::parametersChanged() m_initializing = " << m_initializing); //parameter names were (probably) changed -> set the new names in EquationTextEdit uiGeneralTab.teEquation->setVariables(m_fitData.paramNames); if (m_initializing) return; if (updateParameterWidget) fitParametersWidget->setFitData(&m_fitData); enableRecalculate(); } void XYFitCurveDock::parametersValid(bool valid) { DEBUG("XYFitCurveDock::parametersValid() valid = " << valid); m_parametersValid = valid; } void XYFitCurveDock::showOptions() { QMenu menu; FitOptionsWidget w(&menu, &m_fitData, m_fitCurve); connect(&w, SIGNAL(finished()), &menu, SLOT(close())); connect(&w, SIGNAL(optionsChanged()), this, SLOT(enableRecalculate())); auto* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&w); menu.addAction(widgetAction); menu.setTearOffEnabled(true); //menu.setWindowFlags(menu.windowFlags() & Qt::MSWindowsFixedSizeDialogHint); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.pbOptions->width(), 0); menu.exec(uiGeneralTab.pbOptions->mapToGlobal(pos)); } void XYFitCurveDock::insertFunction(const QString& str) const { //TODO: not all function have only one argument! uiGeneralTab.teEquation->insertPlainText(str + "(x)"); } void XYFitCurveDock::insertConstant(const QString& str) const { uiGeneralTab.teEquation->insertPlainText(str); } /*! * When a custom evaluate range is specified, set the plot range too. */ void XYFitCurveDock::setPlotXRange() { if (m_fitData.autoEvalRange || m_curve == nullptr) return; auto* plot = dynamic_cast(m_curve->parentAspect()); if (plot != nullptr) { double rmin = m_fitData.evalRange.first(); double rmax = m_fitData.evalRange.last(); double extend = (rmax-rmin) * 0.05; // 5 percent of range plot->setXMin(rmin - extend); plot->setXMax(rmax + extend); } } void XYFitCurveDock::recalculateClicked() { DEBUG("XYFitCurveDock::recalculateClicked()"); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); m_fitData.degree = uiGeneralTab.sbDegree->value(); if (m_fitData.modelCategory == nsl_fit_model_custom) updateParameterList(); for (XYCurve* curve: m_curvesList) dynamic_cast(curve)->setFitData(m_fitData); m_fitCurve->recalculate(); setPlotXRange(); //update fitParametersWidget if (m_fitData.useResults) { + //TODO: fix crash fitting datetime data + DEBUG(" nr of param names = " << m_fitData.paramNames.size()) + DEBUG(" size of start values = " << m_fitData.paramStartValues.size()) + DEBUG(" size of param values = " << m_fitCurve->fitResult().paramValues.size()) for (int i = 0; i < m_fitData.paramNames.size(); i++) m_fitData.paramStartValues[i] = m_fitCurve->fitResult().paramValues[i]; fitParametersWidget->setFitData(&m_fitData); } this->showFitResult(); uiGeneralTab.pbRecalculate->setEnabled(false); emit info(i18n("Fit status: %1", m_fitCurve->fitResult().status)); QApplication::restoreOverrideCursor(); DEBUG("XYFitCurveDock::recalculateClicked() DONE"); } void XYFitCurveDock::expressionChanged() { DEBUG("XYFitCurveDock::expressionChanged()"); if (m_initializing) return; // update parameter list for custom model if (m_fitData.modelCategory == nsl_fit_model_custom) updateParameterList(); enableRecalculate(); } void XYFitCurveDock::enableRecalculate() { DEBUG("XYFitCurveDock::enableRecalculate()"); if (m_initializing || m_fitCurve == nullptr) return; //no fitting possible without the x- and y-data bool hasSourceData = false; if (m_fitCurve->dataSourceType() == XYAnalysisCurve::DataSourceSpreadsheet) { AbstractAspect* aspectX = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); AbstractAspect* aspectY = static_cast(cbYDataColumn->currentModelIndex().internalPointer()); hasSourceData = (aspectX != nullptr && aspectY != nullptr); if (aspectX) { cbXDataColumn->useCurrentIndexText(true); cbXDataColumn->setInvalid(false); } if (aspectY) { cbYDataColumn->useCurrentIndexText(true); cbYDataColumn->setInvalid(false); } } else { hasSourceData = (m_fitCurve->dataSourceCurve() != nullptr); } uiGeneralTab.pbRecalculate->setEnabled(hasSourceData && m_parametersValid); // PREVIEW as soon as recalculate is enabled (does not need source data) if (m_parametersValid && m_fitData.previewEnabled) { DEBUG(" ENABLE EVALUATE AND PREVIEW"); // use recent fit data m_fitCurve->setFitData(m_fitData); // calculate fit function m_fitCurve->evaluate(true); setPlotXRange(); } else { DEBUG(" EVALUATE PREVIEW DISABLED"); } } void XYFitCurveDock::resultCopySelection() { QTableWidget* tw{nullptr}; int currentTab = uiGeneralTab.twResults->currentIndex(); DEBUG("current tab = " << currentTab); if (currentTab == 0) tw = uiGeneralTab.twParameters; else if (currentTab == 1) tw = uiGeneralTab.twGoodness; else if (currentTab == 2) tw = uiGeneralTab.twLog; else return; const QTableWidgetSelectionRange& range = tw->selectedRanges().constFirst(); QString str; for (int i = 0; i < range.rowCount(); ++i) { if (i > 0) str += '\n'; for (int j = 0; j < range.columnCount(); ++j) { if (j > 0) str += '\t'; str += tw->item(range.topRow() + i, range.leftColumn() + j)->text(); } } str += '\n'; QApplication::clipboard()->setText(str); - DEBUG(QApplication::clipboard()->text().toStdString()); + DEBUG(STDSTRING(QApplication::clipboard()->text())); } void XYFitCurveDock::resultCopyAll() { const XYFitCurve::FitResult& fitResult = m_fitCurve->fitResult(); int currentTab = uiGeneralTab.twResults->currentIndex(); QString str; if (currentTab == 0) { str = i18n("Parameters:") + '\n'; const int np = fitResult.paramValues.size(); for (int i = 0; i < np; i++) { if (m_fitData.paramFixed.at(i)) str += m_fitData.paramNamesUtf8.at(i) + QString(" = ") + QString::number(fitResult.paramValues.at(i)) + '\n'; else { str += m_fitData.paramNamesUtf8.at(i) + QString(" = ") + QString::number(fitResult.paramValues.at(i)) + UTF8_QSTRING("±") + QString::number(fitResult.errorValues.at(i)) + " (" + QString::number(100.*fitResult.errorValues.at(i)/std::abs(fitResult.paramValues.at(i)), 'g', 3) + " %)\n"; const double margin = fitResult.tdist_marginValues.at(i); QString tdistValueString; if (fitResult.tdist_tValues.at(i) < std::numeric_limits::max()) tdistValueString = QString::number(fitResult.tdist_tValues.at(i), 'g', 3); else tdistValueString = UTF8_QSTRING("∞"); str += " (" + i18n("t statistic:") + ' ' + tdistValueString + ", " + i18n("p value:") + ' ' + QString::number(fitResult.tdist_pValues.at(i), 'g', 3) + ", " + i18n("conf. interval:") + ' '; if (std::abs(fitResult.tdist_tValues.at(i)) < 1.e6) { str += QString::number(fitResult.paramValues.at(i) - margin) + " .. " + QString::number(fitResult.paramValues.at(i) + margin) + ")\n"; } else { str += i18n("too small"); } } } } else if (currentTab == 1) { str = i18n("Goodness of fit:") + '\n'; str += i18n("sum of squared residuals") + " (" + UTF8_QSTRING("χ²") + "): " + QString::number(fitResult.sse) + '\n'; if (fitResult.dof != 0) { str += i18n("reduced") + ' ' + UTF8_QSTRING("χ²") + ": " + QString::number(fitResult.rms) + '\n'; str += i18n("root mean square error") + " (RMSE): " + QString::number(fitResult.rsd) + '\n'; str += i18n("coefficient of determination") + " (" + UTF8_QSTRING("R²") + "): " + QString::number(fitResult.rsquare, 'g', 15) + '\n'; str += i18n("adj. coefficient of determination")+ " (" + UTF8_QSTRING("R̄²") + "): " + QString::number(fitResult.rsquareAdj, 'g', 15) + "\n\n"; str += i18n("P > ") + UTF8_QSTRING("χ²") + ": " + QString::number(fitResult.chisq_p, 'g', 3) + '\n'; str += i18n("F statistic") + ": " + QString::number(fitResult.fdist_F, 'g', 3) + '\n'; str += i18n("P > F") + ": " + QString::number(fitResult.fdist_p, 'g', 3) + '\n'; } str += i18n("mean absolute error:") + ' ' + QString::number(fitResult.mae) + '\n'; str += i18n("Akaike information criterion:") + ' ' + QString::number(fitResult.aic) + '\n'; str += i18n("Bayesian information criterion:") + ' ' + QString::number(fitResult.bic) + '\n'; } else if (currentTab == 2) { str = i18n("status:") + ' ' + fitResult.status + '\n'; str += i18n("iterations:") + ' ' + QString::number(fitResult.iterations) + '\n'; str += i18n("tolerance:") + ' ' + QString::number(m_fitData.eps) + '\n'; if (fitResult.elapsedTime > 1000) str += i18n("calculation time: %1 s", fitResult.elapsedTime/1000) + '\n'; else str += i18n("calculation time: %1 ms", fitResult.elapsedTime) + '\n'; str += i18n("degrees of freedom:") + ' ' + QString::number(fitResult.dof) + '\n'; str += i18n("number of parameters:") + ' ' + QString::number(fitResult.paramValues.size()) + '\n'; str += i18n("fit range:") + ' ' + QString::number(m_fitData.fitRange.first()) + " .. " + QString::number(m_fitData.fitRange.last()) + '\n'; str += i18n("Iterations:") + '\n'; for (const auto &s : m_fitData.paramNamesUtf8) str += s + '\t'; str += UTF8_QSTRING("χ²"); const QStringList iterations = fitResult.solverOutput.split(';'); for (const auto &s : iterations) if (!s.isEmpty()) str += '\n' + s; } QApplication::clipboard()->setText(str); - DEBUG(QApplication::clipboard()->text().toStdString()); + DEBUG(STDSTRING(QApplication::clipboard()->text())); } void XYFitCurveDock::resultParametersContextMenuRequest(QPoint pos) { auto* contextMenu = new QMenu; contextMenu->addAction(i18n("Copy Selection"), this, SLOT(resultCopySelection())); contextMenu->addAction(i18n("Copy All"), this, SLOT(resultCopyAll())); contextMenu->exec(uiGeneralTab.twParameters->mapToGlobal(pos)); } void XYFitCurveDock::resultGoodnessContextMenuRequest(QPoint pos) { auto* contextMenu = new QMenu; contextMenu->addAction(i18n("Copy Selection"), this, SLOT(resultCopySelection())); contextMenu->addAction(i18n("Copy All"), this, SLOT(resultCopyAll())); contextMenu->exec(uiGeneralTab.twGoodness->mapToGlobal(pos)); } void XYFitCurveDock::resultLogContextMenuRequest(QPoint pos) { auto* contextMenu = new QMenu; contextMenu->addAction(i18n("Copy Selection"), this, SLOT(resultCopySelection())); contextMenu->addAction(i18n("Copy All"), this, SLOT(resultCopyAll())); contextMenu->exec(uiGeneralTab.twLog->mapToGlobal(pos)); } /*! * show the result and details of the fit */ void XYFitCurveDock::showFitResult() { DEBUG("XYFitCurveDock::showFitResult()"); //clear the previous result uiGeneralTab.twParameters->setRowCount(0); for (int row = 0; row < uiGeneralTab.twGoodness->rowCount(); ++row) uiGeneralTab.twGoodness->item(row, 2)->setText(QString()); for (int row = 0; row < uiGeneralTab.twLog->rowCount(); ++row) uiGeneralTab.twLog->item(row, 1)->setText(QString()); const XYFitCurve::FitResult& fitResult = m_fitCurve->fitResult(); if (!fitResult.available) { DEBUG("fit result not available"); return; } // Log uiGeneralTab.twLog->item(0, 1)->setText(fitResult.status); if (!fitResult.valid) { DEBUG("fit result not valid"); return; } uiGeneralTab.twLog->item(1, 1)->setText(QString::number(fitResult.iterations)); uiGeneralTab.twLog->item(2, 1)->setText(QString::number(m_fitData.eps)); if (fitResult.elapsedTime > 1000) uiGeneralTab.twLog->item(3, 1)->setText(QString::number(fitResult.elapsedTime/1000) + " s"); else uiGeneralTab.twLog->item(3, 1)->setText(QString::number(fitResult.elapsedTime) + " ms"); uiGeneralTab.twLog->item(4, 1)->setText(QString::number(fitResult.dof)); uiGeneralTab.twLog->item(5, 1)->setText(QString::number(fitResult.paramValues.size())); uiGeneralTab.twLog->item(6, 1)->setText(QString::number(m_fitData.fitRange.first()) + " .. " + QString::number(m_fitData.fitRange.last()) ); // show all iterations QString str; for (const auto &s : m_fitData.paramNamesUtf8) str += s + '\t'; str += UTF8_QSTRING("χ²"); const QStringList iterations = fitResult.solverOutput.split(';'); for (const auto &s : iterations) if (!s.isEmpty()) str += '\n' + s; uiGeneralTab.twLog->item(7, 1)->setText(str); uiGeneralTab.twLog->resizeRowsToContents(); // Parameters const int np = m_fitData.paramNames.size(); uiGeneralTab.twParameters->setRowCount(np); QStringList headerLabels; headerLabels << i18n("Name") << i18n("Value") << i18n("Error") << i18n("Error, %") << i18n("t statistic") << QLatin1String("P > |t|") << i18n("Conf. Interval"); uiGeneralTab.twParameters->setHorizontalHeaderLabels(headerLabels); for (int i = 0; i < np; i++) { const double paramValue = fitResult.paramValues.at(i); const double errorValue = fitResult.errorValues.at(i); auto* item = new QTableWidgetItem(m_fitData.paramNamesUtf8.at(i)); item->setBackground(QApplication::palette().color(QPalette::Window)); uiGeneralTab.twParameters->setItem(i, 0, item); item = new QTableWidgetItem(QString::number(paramValue)); uiGeneralTab.twParameters->setItem(i, 1, item); if (!m_fitData.paramFixed.at(i)) { if (!std::isnan(errorValue)) { item = new QTableWidgetItem(QString::number(errorValue, 'g', 6)); uiGeneralTab.twParameters->setItem(i, 2, item); item = new QTableWidgetItem(QString::number(100.*errorValue/std::abs(paramValue), 'g', 3)); uiGeneralTab.twParameters->setItem(i, 3, item); } else { item = new QTableWidgetItem(UTF8_QSTRING("∞")); uiGeneralTab.twParameters->setItem(i, 2, item); item = new QTableWidgetItem(UTF8_QSTRING("∞")); uiGeneralTab.twParameters->setItem(i, 3, item); } // t values QString tdistValueString; if (fitResult.tdist_tValues.at(i) < std::numeric_limits::max()) tdistValueString = QString::number(fitResult.tdist_tValues.at(i), 'g', 3); else tdistValueString = UTF8_QSTRING("∞"); item = new QTableWidgetItem(tdistValueString); uiGeneralTab.twParameters->setItem(i, 4, item); // p values const double p = fitResult.tdist_pValues.at(i); item = new QTableWidgetItem(QString::number(p, 'g', 3)); // color p values depending on value if (p > 0.05) item->setForeground(QBrush(QApplication::palette().color(QPalette::LinkVisited))); else if (p > 0.01) item->setForeground(QBrush(Qt::darkGreen)); else if (p > 0.001) item->setForeground(QBrush(Qt::darkCyan)); else if (p > 0.0001) item->setForeground(QBrush(QApplication::palette().color(QPalette::Link))); else item->setForeground(QBrush(QApplication::palette().color(QPalette::Highlight))); uiGeneralTab.twParameters->setItem(i, 5, item); // Conf. interval if (!std::isnan(errorValue)) { const double margin = fitResult.tdist_marginValues.at(i); if (fitResult.tdist_tValues.at(i) < 1.e6) item = new QTableWidgetItem(QString::number(paramValue - margin) + QLatin1String(" .. ") + QString::number(paramValue + margin)); else item = new QTableWidgetItem(i18n("too small")); uiGeneralTab.twParameters->setItem(i, 6, item); } } } // Goodness of fit uiGeneralTab.twGoodness->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); uiGeneralTab.twGoodness->item(0, 2)->setText(QString::number(fitResult.sse)); if (fitResult.dof != 0) { uiGeneralTab.twGoodness->item(1, 2)->setText(QString::number(fitResult.rms)); uiGeneralTab.twGoodness->item(2, 2)->setText(QString::number(fitResult.rsd)); uiGeneralTab.twGoodness->item(3, 2)->setText(QString::number(fitResult.rsquare, 'g', 15)); uiGeneralTab.twGoodness->item(4, 2)->setText(QString::number(fitResult.rsquareAdj, 'g', 15)); // chi^2 and F test p-values uiGeneralTab.twGoodness->item(5, 2)->setText(QString::number(fitResult.chisq_p, 'g', 3)); uiGeneralTab.twGoodness->item(6, 2)->setText(QString::number(fitResult.fdist_F, 'g', 3)); uiGeneralTab.twGoodness->item(7, 2)->setText(QString::number(fitResult.fdist_p, 'g', 3)); uiGeneralTab.twGoodness->item(9, 2)->setText(QString::number(fitResult.aic, 'g', 3)); uiGeneralTab.twGoodness->item(10, 2)->setText(QString::number(fitResult.bic, 'g', 3)); } uiGeneralTab.twGoodness->item(8, 2)->setText(QString::number(fitResult.mae)); //resize the table headers to fit the new content uiGeneralTab.twLog->resizeColumnsToContents(); uiGeneralTab.twParameters->resizeColumnsToContents(); //twGoodness doesn't have any header -> resize sections uiGeneralTab.twGoodness->resizeColumnToContents(0); uiGeneralTab.twGoodness->resizeColumnToContents(1); uiGeneralTab.twGoodness->resizeColumnToContents(2); //enable the "recalculate"-button if the source data was changed since the last fit uiGeneralTab.pbRecalculate->setEnabled(m_fitCurve->isSourceDataChangedSinceLastRecalc()); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYFitCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) uiGeneralTab.leName->setText(aspect->name()); else if (aspect->comment() != uiGeneralTab.leComment->text()) uiGeneralTab.leComment->setText(aspect->comment()); m_initializing = false; } void XYFitCurveDock::curveDataSourceTypeChanged(XYAnalysisCurve::DataSourceType type) { m_initializing = true; uiGeneralTab.cbDataSourceType->setCurrentIndex(type); m_initializing = false; } void XYFitCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve); m_initializing = false; } void XYFitCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column); m_initializing = false; } void XYFitCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column); m_initializing = false; } void XYFitCurveDock::curveXErrorColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, column); m_initializing = false; } void XYFitCurveDock::curveYErrorColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, column); m_initializing = false; } /*! * called when fit data of fit curve changes */ void XYFitCurveDock::curveFitDataChanged(const XYFitCurve::FitData& fitData) { DEBUG("XYFitCurveDock::curveFitDataChanged()"); m_initializing = true; m_fitData = fitData; if (m_fitData.modelCategory != nsl_fit_model_custom) uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); uiGeneralTab.sbDegree->setValue(m_fitData.degree); m_initializing = false; } void XYFitCurveDock::dataChanged() { this->enableRecalculate(); } diff --git a/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp b/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp index 9d28b24e1..2cc994871 100644 --- a/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp +++ b/src/kdefrontend/spreadsheet/RandomValuesDialog.cpp @@ -1,1045 +1,1045 @@ /*************************************************************************** File : RandomValuesDialog.cpp Project : LabPlot Description : Dialog for generating non-uniformly distributed random numbers -------------------------------------------------------------------- Copyright : (C) 2014-2019 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2020 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "RandomValuesDialog.h" #include "backend/core/column/Column.h" #include "backend/lib/macros.h" #include "backend/spreadsheet/Spreadsheet.h" #include #include #include #include #include #include #include #include #include extern "C" { #include "backend/nsl/nsl_sf_stats.h" #include #include } /*! \class RandomValuesDialog \brief Dialog for generating non-uniform random numbers. \ingroup kdefrontend */ RandomValuesDialog::RandomValuesDialog(Spreadsheet* s, QWidget* parent) : QDialog(parent), m_spreadsheet(s) { setWindowTitle(i18nc("@title:window", "Random Values")); QWidget* mainWidget = new QWidget(this); ui.setupUi(mainWidget); auto* layout = new QVBoxLayout(this); auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_okButton = buttonBox->button(QDialogButtonBox::Ok); m_okButton->setDefault(true); m_okButton->setToolTip(i18n("Generate random values according to the selected distribution")); m_okButton->setText(i18n("&Generate")); connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &RandomValuesDialog::close); connect(buttonBox, &QDialogButtonBox::accepted, this, &RandomValuesDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &RandomValuesDialog::reject); layout->addWidget(mainWidget); layout->addWidget(buttonBox); setLayout(layout); setAttribute(Qt::WA_DeleteOnClose); for (int i = 0; i < NSL_SF_STATS_DISTRIBUTION_RNG_COUNT; i++) ui.cbDistribution->addItem(i18n(nsl_sf_stats_distribution_name[i]), i); //use white background in the preview label QPalette p; p.setColor(QPalette::Window, Qt::white); ui.lFuncPic->setAutoFillBackground(true); ui.lFuncPic->setPalette(p); ui.leParameter1->setClearButtonEnabled(true); ui.leParameter2->setClearButtonEnabled(true); ui.leParameter3->setClearButtonEnabled(true); ui.leParameter1->setValidator( new QDoubleValidator(ui.leParameter1) ); ui.leParameter2->setValidator( new QDoubleValidator(ui.leParameter2) ); ui.leParameter3->setValidator( new QDoubleValidator(ui.leParameter3) ); connect(ui.cbDistribution, static_cast(&QComboBox::currentIndexChanged), this, &RandomValuesDialog::distributionChanged); connect(ui.leParameter1, &QLineEdit::textChanged, this, &RandomValuesDialog::checkValues); connect(ui.leParameter2, &QLineEdit::textChanged, this, &RandomValuesDialog::checkValues); connect(ui.leParameter3, &QLineEdit::textChanged, this, &RandomValuesDialog::checkValues); connect(buttonBox, &QDialogButtonBox::accepted, this, &RandomValuesDialog::generate); //restore saved settings if available create(); // ensure there's a window created const KConfigGroup conf(KSharedConfig::openConfig(), "RandomValuesDialog"); if (conf.exists()) { ui.cbDistribution->setCurrentIndex(conf.readEntry("Distribution", 0)); this->distributionChanged(ui.cbDistribution->currentIndex()); //if index=0 no signal is emitted above, call this slot directly here ui.leParameter1->setText(conf.readEntry("Parameter1")); ui.leParameter2->setText(conf.readEntry("Parameter2")); ui.leParameter3->setText(conf.readEntry("Parameter3")); KWindowConfig::restoreWindowSize(windowHandle(), conf); resize(windowHandle()->size()); // workaround for QTBUG-40584 } else { //Gaussian distribution as default this->distributionChanged(0); resize( QSize(400,0).expandedTo(minimumSize()) ); } } RandomValuesDialog::~RandomValuesDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "RandomValuesDialog"); conf.writeEntry("Distribution", ui.cbDistribution->currentIndex()); conf.writeEntry("Parameter1", ui.leParameter1->text()); conf.writeEntry("Parameter2", ui.leParameter2->text()); conf.writeEntry("Parameter3", ui.leParameter3->text()); KWindowConfig::saveWindowSize(windowHandle(), conf); } void RandomValuesDialog::setColumns(const QVector& columns) { m_columns = columns; } void RandomValuesDialog::distributionChanged(int index) { nsl_sf_stats_distribution dist = (nsl_sf_stats_distribution)ui.cbDistribution->itemData(index).toInt(); // default settings (used by most distributions) ui.lParameter1->show(); ui.leParameter1->show(); ui.lParameter2->show(); ui.leParameter2->show(); ui.lParameter3->hide(); ui.leParameter3->hide(); ui.lFunc->setText("p(x) ="); switch (dist) { case nsl_sf_stats_gaussian: ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_gaussian_tail: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.lParameter3->setText("a ="); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("0.0"); break; case nsl_sf_stats_exponential: ui.lParameter1->setText(UTF8_QSTRING("λ =")); ui.leParameter1->setText("1.0"); ui.lParameter2->setText(UTF8_QSTRING("μ =")); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_laplace: ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_exponential_power: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.lParameter3->setText("b ="); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("1.0"); break; case nsl_sf_stats_cauchy_lorentz: ui.lParameter1->setText(UTF8_QSTRING("γ =")); ui.lParameter2->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_rayleigh: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lParameter1->setText(UTF8_QSTRING("σ =")); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_rayleigh_tail: ui.lParameter1->setText(UTF8_QSTRING("μ =")); ui.lParameter2->setText(UTF8_QSTRING("σ =")); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_landau: ui.lParameter1->hide(); ui.leParameter1->hide(); ui.lParameter2->hide(); ui.leParameter2->hide(); break; case nsl_sf_stats_levy_alpha_stable: ui.lParameter1->setText("c ="); ui.lParameter2->setText(UTF8_QSTRING("α =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_levy_skew_alpha_stable: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(UTF8_QSTRING("c =")); ui.lParameter2->setText(UTF8_QSTRING("α =")); ui.lParameter3->setText(UTF8_QSTRING("β =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("1.0"); break; case nsl_sf_stats_flat: ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.leParameter1->setText("0.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_gamma: ui.lParameter1->setText(UTF8_QSTRING("θ =")); ui.lParameter2->setText("k ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_weibull: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText("k ="); ui.lParameter2->setText(UTF8_QSTRING("λ =")); ui.lParameter3->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("1.0"); break; case nsl_sf_stats_beta: ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_gumbel1: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText(UTF8_QSTRING("σ =")); ui.lParameter2->setText(UTF8_QSTRING("β =")); ui.lParameter3->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("0.0"); break; case nsl_sf_stats_gumbel2: ui.lParameter3->show(); ui.leParameter3->show(); ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.lParameter3->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); ui.leParameter3->setText("0.0"); break; case nsl_sf_stats_pareto: ui.lParameter1->setText("a ="); ui.lParameter2->setText("b ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_lognormal: ui.lParameter1->setText(UTF8_QSTRING("σ =")); ui.lParameter2->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_chi_squared: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lParameter1->setText("n ="); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_fdist: ui.lParameter1->setText(UTF8_QSTRING("ν₁ =")); ui.lParameter2->setText(UTF8_QSTRING("ν₂ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("1.0"); break; case nsl_sf_stats_tdist: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lParameter1->setText(UTF8_QSTRING("ν =")); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_logistic: ui.lParameter1->setText(UTF8_QSTRING("σ =")); ui.lParameter2->setText(UTF8_QSTRING("μ =")); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("0.0"); break; case nsl_sf_stats_poisson: ui.lParameter2->hide(); ui.leParameter2->hide(); ui.lFunc->setText("p(k) ="); ui.lParameter1->setText(UTF8_QSTRING("λ =")); ui.leParameter1->setText("1.0"); break; case nsl_sf_stats_bernoulli: case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: ui.lParameter2->hide(); ui.leParameter2->hide(); if (dist == nsl_sf_stats_bernoulli) ui.lFunc->setText(QString()); else ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("p ="); ui.leParameter1->setText("0.5"); break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("p ="); ui.lParameter2->setText("n ="); ui.leParameter1->setText("0.5"); ui.leParameter2->setText("100"); break; case nsl_sf_stats_hypergeometric: ui.lParameter3->show(); ui.leParameter3->show(); ui.lFunc->setText("p(k) ="); ui.lParameter1->setText("n1 ="); ui.lParameter2->setText("n2 ="); ui.lParameter3->setText("t ="); ui.leParameter1->setText("1.0"); ui.leParameter2->setText("2.0"); ui.leParameter3->setText("3.0"); break; case nsl_sf_stats_maxwell_boltzmann: // additional non-GSL distros case nsl_sf_stats_sech: case nsl_sf_stats_levy: case nsl_sf_stats_frechet: break; } QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/gsl_distributions/" + QString(nsl_sf_stats_distribution_pic_name[dist]) + ".png"); - DEBUG("Distribution pixmap path = " << file.toStdString()); + DEBUG("Distribution pixmap path = " << STDSTRING(file)); ui.lFuncPic->setPixmap(QPixmap(file)); } void RandomValuesDialog::checkValues() { if (ui.leParameter1->text().simplified().isEmpty()) { m_okButton->setEnabled(false); return; } if (ui.leParameter2->isVisible() && ui.leParameter2->text().simplified().isEmpty()) { m_okButton->setEnabled(false); return; } if (ui.leParameter3->isVisible() && ui.leParameter3->text().simplified().isEmpty()) { m_okButton->setEnabled(false); return; } m_okButton->setEnabled(true); return; } void RandomValuesDialog::generate() { Q_ASSERT(m_spreadsheet); //create a generator chosen by the environment variable GSL_RNG_TYPE gsl_rng_env_setup(); const gsl_rng_type* T = gsl_rng_default; gsl_rng* r = gsl_rng_alloc(T); gsl_rng_set(r, QDateTime::currentMSecsSinceEpoch()); WAIT_CURSOR; for (auto* col : m_columns) col->setSuppressDataChangedSignal(true); m_spreadsheet->beginMacro(i18np("%1: fill column with non-uniform random numbers", "%1: fill columns with non-uniform random numbers", m_spreadsheet->name(), m_columns.size())); const int index = ui.cbDistribution->currentIndex(); const nsl_sf_stats_distribution dist = (nsl_sf_stats_distribution)ui.cbDistribution->itemData(index).toInt(); DEBUG("random number distribution: " << nsl_sf_stats_distribution_name[dist]); const int rows = m_spreadsheet->rowCount(); QVector data(rows); QVector data_int(rows); QVector data_bigint(rows); switch (dist) { case nsl_sf_stats_gaussian: { double mu = ui.leParameter1->text().toDouble(); double sigma = ui.leParameter2->text().toDouble(); DEBUG(" mu = " << mu << ", sigma = " << sigma); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gaussian(r, sigma) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gaussian(r, sigma) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gaussian(r, sigma) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_gaussian_tail: { double mu = ui.leParameter1->text().toDouble(); double sigma = ui.leParameter2->text().toDouble(); double a = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gaussian_tail(r, a, sigma) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gaussian_tail(r, a, sigma) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gaussian_tail(r, a, sigma) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_exponential: { double l = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { //GSL uses the inverse for exp. distrib. for (int i = 0; i < rows; ++i) data[i] = gsl_ran_exponential(r, 1./l) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_exponential(r, 1./l) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_exponential(r, 1./l) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_laplace: { double mu = ui.leParameter1->text().toDouble(); double s = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_laplace(r, s) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_laplace(r, s) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_laplace(r, s) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_exponential_power: { double mu = ui.leParameter1->text().toDouble(); double a = ui.leParameter2->text().toDouble(); double b = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_exppow(r, a, b) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_exppow(r, a, b) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_exppow(r, a, b) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_cauchy_lorentz: { double gamma = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_cauchy(r, gamma) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_cauchy(r, gamma) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_cauchy(r, gamma) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_rayleigh: { double s = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_rayleigh(r, s); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_rayleigh(r, s)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_rayleigh(r, s)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_rayleigh_tail: { double mu = ui.leParameter1->text().toDouble(); double sigma = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_rayleigh_tail(r, mu, sigma); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_rayleigh_tail(r, mu, sigma)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_rayleigh_tail(r, mu, sigma)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_landau: for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_landau(r); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_landau(r)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_landau(r)); col->replaceBigInt(0, data_bigint); } } break; case nsl_sf_stats_levy_alpha_stable: { double c = ui.leParameter1->text().toDouble(); double alpha = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_levy(r, c, alpha); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_levy(r, c, alpha)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_levy(r, c, alpha)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_levy_skew_alpha_stable: { double c = ui.leParameter1->text().toDouble(); double alpha = ui.leParameter2->text().toDouble(); double beta = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_levy_skew(r, c, alpha, beta); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_levy_skew(r, c, alpha, beta)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_levy_skew(r, c, alpha, beta)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_gamma: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gamma(r, a, b); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gamma(r, a, b)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gamma(r, a, b)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_flat: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_flat(r, a, b); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_flat(r, a, b)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_flat(r, a, b)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_lognormal: { double s = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_lognormal(r, mu, s); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_lognormal(r, mu, s)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_lognormal(r, mu, s)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_chi_squared: { double n = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_chisq(r, n); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_chisq(r, n)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_chisq(r, n)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_fdist: { double nu1 = ui.leParameter1->text().toDouble(); double nu2 = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_fdist(r, nu1, nu2); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_fdist(r, nu1, nu2)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_fdist(r, nu1, nu2)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_tdist: { double nu = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_tdist(r, nu); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_tdist(r, nu)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_tdist(r, nu)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_beta: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_beta(r, a, b); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_beta(r, a, b)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_beta(r, a, b)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_logistic: { double s = ui.leParameter1->text().toDouble(); double mu = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_logistic(r, s) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_logistic(r, s) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_logistic(r, s) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_pareto: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_pareto(r, a, b); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_pareto(r, a, b)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_pareto(r, a, b)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_weibull: { double k = ui.leParameter1->text().toDouble(); double l = ui.leParameter2->text().toDouble(); double mu = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_weibull(r, l, k) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_weibull(r, l, k) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_weibull(r, l, k) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_gumbel1: { double s = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); double mu = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gumbel1(r, 1./s, b) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gumbel1(r, 1./s, b) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gumbel1(r, 1./s, b) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_gumbel2: { double a = ui.leParameter1->text().toDouble(); double b = ui.leParameter2->text().toDouble(); double mu = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_gumbel2(r, a, b) + mu; col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_gumbel2(r, a, b) + mu); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_gumbel2(r, a, b) + mu); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_poisson: { double l = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_poisson(r, l); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_poisson(r, l)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_poisson(r, l)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_bernoulli: { double p = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_bernoulli(r, p); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_bernoulli(r, p)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_bernoulli(r, p)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_binomial: { double p = ui.leParameter1->text().toDouble(); double n = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_binomial(r, p, n); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_binomial(r, p, n)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_binomial(r, p, n)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_negative_binomial: { double p = ui.leParameter1->text().toDouble(); double n = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_negative_binomial(r, p, n); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_negative_binomial(r, p, n)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_negative_binomial(r, p, n)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_pascal: { double p = ui.leParameter1->text().toDouble(); double n = ui.leParameter2->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_pascal(r, p, n); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_pascal(r, p, n)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_pascal(r, p, n)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_geometric: { double p = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_geometric(r, p); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_geometric(r, p)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_geometric(r, p)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_hypergeometric: { double n1 = ui.leParameter1->text().toDouble(); double n2 = ui.leParameter2->text().toDouble(); double t = ui.leParameter3->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_hypergeometric(r, n1, n2, t); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_hypergeometric(r, n1, n2, t)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_hypergeometric(r, n1, n2, t)); col->replaceBigInt(0, data_bigint); } } break; } case nsl_sf_stats_logarithmic: { double p = ui.leParameter1->text().toDouble(); for (auto* col : m_columns) { if (col->columnMode() == AbstractColumn::Numeric) { for (int i = 0; i < rows; ++i) data[i] = gsl_ran_logarithmic(r, p); col->replaceValues(0, data); } else if (col->columnMode() == AbstractColumn::Integer) { for (int i = 0; i < rows; ++i) data_int[i] = (int)round(gsl_ran_logarithmic(r, p)); col->replaceInteger(0, data_int); } else if (col->columnMode() == AbstractColumn::BigInt) { for (int i = 0; i < rows; ++i) data_bigint[i] = (qint64)round(gsl_ran_logarithmic(r, p)); col->replaceBigInt(0, data_bigint); } } break; } // additional non-GSL distributions not needed case nsl_sf_stats_maxwell_boltzmann: case nsl_sf_stats_sech: case nsl_sf_stats_levy: case nsl_sf_stats_frechet: break; } for (auto* col : m_columns) { col->setSuppressDataChangedSignal(false); col->setChanged(); } m_spreadsheet->endMacro(); RESET_CURSOR; gsl_rng_free(r); } diff --git a/src/tools/TeXRenderer.cpp b/src/tools/TeXRenderer.cpp index 07e213a27..2bd2254d6 100644 --- a/src/tools/TeXRenderer.cpp +++ b/src/tools/TeXRenderer.cpp @@ -1,327 +1,327 @@ /*************************************************************************** File : TeXRenderer.cc Project : LabPlot Description : TeX renderer class -------------------------------------------------------------------- Copyright : (C) 2008-2016 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "TeXRenderer.h" #include "backend/lib/macros.h" #include #include #include #include #include #include #include #include #include /*! \class TeXRenderer \brief Implements rendering of latex code to a PNG image. Uses latex engine specified by the user (default xelatex) to render LaTeX text \ingroup tools */ QImage TeXRenderer::renderImageLaTeX(const QString& teXString, bool* success, const TeXRenderer::Formatting& format) { const QColor& fontColor = format.fontColor; const QColor& backgroundColor = format.backgroundColor; const int fontSize = format.fontSize; const QString& fontFamily = format.fontFamily; const int dpi = format.dpi; //determine the temp directory where the produced files are going to be created QString tempPath; #ifdef Q_OS_LINUX //on linux try to use shared memory device first if available static bool useShm = QDir("/dev/shm/").exists(); if (useShm) tempPath = "/dev/shm/"; else tempPath = QDir::tempPath(); #else tempPath = QDir::tempPath(); #endif // make sure we have preview.sty available if (!tempPath.contains(QLatin1String("preview.sty"))) { QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("latex/preview.sty")); if (file.isEmpty()) { WARN("Couldn't find preview.sty."); *success = false; return QImage(); } else QFile::copy(file, tempPath + QDir::separator() + QLatin1String("preview.sty")); } //create a temporary file QTemporaryFile file(tempPath + QDir::separator() + "labplot_XXXXXX.tex"); // FOR DEBUG: file.setAutoRemove(false); // DEBUG("temp file path = " << file.fileName().toUtf8().constData()); if (file.open()) { QDir::setCurrent(tempPath); } else { - WARN(QString("Couldn't open the file " + file.fileName()).toStdString()); + WARN("Couldn't open the file " << STDSTRING(file.fileName())); *success = false; return QImage(); } //determine latex engine to be used KConfigGroup group = KSharedConfig::openConfig()->group("Settings_Worksheet"); QString engine = group.readEntry("LaTeXEngine", "pdflatex"); // create latex code QTextStream out(&file); int headerIndex = teXString.indexOf("\\begin{document}"); QString body; if (headerIndex != -1) { //user provided a complete latex document -> extract the document header and body QString header = teXString.left(headerIndex); int footerIndex = teXString.indexOf("\\end{document}"); body = teXString.mid(headerIndex + 16, footerIndex - headerIndex - 16); out << header; } else { //user simply provided a document body (assume it's a math. expression) -> add a minimal header out << "\\documentclass{minimal}"; if (teXString.indexOf('$') == -1) body = '$' + teXString + '$'; else body = teXString; //replace line breaks with tex command for a line break '\\' body = body.replace(QLatin1String("\n"), QLatin1String("\\\\")); } if (engine == "xelatex" || engine == "lualatex") { out << "\\usepackage{xltxtra}"; out << "\\defaultfontfeatures{Ligatures=TeX}"; if (!fontFamily.isEmpty()) out << "\\setmainfont[Mapping=tex-text]{" << fontFamily << "}"; } out << "\\usepackage{color}"; out << "\\usepackage[active,displaymath,textmath,tightpage]{preview}"; // TODO: this fails with pdflatex //out << "\\usepackage{mathtools}"; out << "\\begin{document}"; out << "\\begin{preview}"; out << "\\colorbox[rgb]{" << backgroundColor.redF() << ',' << backgroundColor.greenF() << ',' << backgroundColor.blueF() << "}{"; out << "\\fontsize{" << QString::number(fontSize) << "}{" << QString::number(fontSize) << "}\\selectfont"; out << "\\color[rgb]{" << fontColor.redF() << ',' << fontColor.greenF() << ',' << fontColor.blueF() << "}"; out << body; out << "}"; out << "\\end{preview}"; out << "\\end{document}"; out.flush(); if (engine == "latex") return imageFromDVI(file, dpi, success); else return imageFromPDF(file, dpi, engine, success); } // TEX -> PDF -> PNG QImage TeXRenderer::imageFromPDF(const QTemporaryFile& file, const int dpi, const QString& engine, bool* success) { QFileInfo fi(file.fileName()); const QString& baseName = fi.completeBaseName(); // pdflatex: produce the PDF file QProcess latexProcess; #if defined(HAVE_WINDOWS) latexProcess.setNativeArguments("-interaction=batchmode " + file.fileName()); latexProcess.start(engine, QStringList() << QString()); #else latexProcess.start(engine, QStringList() << "-interaction=batchmode" << file.fileName()); #endif if (!latexProcess.waitForFinished() || latexProcess.exitCode() != 0) { WARN("pdflatex process failed, exit code = " << latexProcess.exitCode()); *success = false; QFile::remove(baseName + ".aux"); QFile::remove(baseName + ".log"); return QImage(); } // convert: PDF -> PNG QProcess convertProcess; #if defined(HAVE_WINDOWS) // need to set path to magick coder modules (which are in the labplot2 directory) QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("MAGICK_CODER_MODULE_PATH", qPrintable(qgetenv("PROGRAMFILES") + QString("\\labplot2"))); convertProcess.setProcessEnvironment(env); #endif const QStringList params{"-density", QString::number(dpi), baseName + ".pdf", baseName + ".png"}; convertProcess.start("convert", params); if (!convertProcess.waitForFinished() || convertProcess.exitCode() != 0) { WARN("convert process failed, exit code = " << convertProcess.exitCode()); *success = false; QFile::remove(baseName + ".aux"); QFile::remove(baseName + ".log"); QFile::remove(baseName + ".pdf"); return QImage(); } // read png file QImage image(baseName + ".png", "png"); // final clean up QFile::remove(baseName + ".aux"); QFile::remove(baseName + ".log"); QFile::remove(baseName + ".pdf"); QFile::remove(baseName + ".png"); *success = true; return image; } // TEX -> DVI -> PS -> PNG QImage TeXRenderer::imageFromDVI(const QTemporaryFile& file, const int dpi, bool* success) { QFileInfo fi(file.fileName()); const QString& baseName = fi.completeBaseName(); //latex: produce the DVI file QProcess latexProcess; latexProcess.start("latex", QStringList() << "-interaction=batchmode" << file.fileName()); if (!latexProcess.waitForFinished() || latexProcess.exitCode() != 0) { WARN("latex process failed, exit code = " << latexProcess.exitCode()); *success = false; QFile::remove(baseName + ".aux"); QFile::remove(baseName + ".log"); return QImage(); } // dvips: DVI -> PS QProcess dvipsProcess; dvipsProcess.start("dvips", QStringList() << "-E" << baseName); if (!dvipsProcess.waitForFinished() || dvipsProcess.exitCode() != 0) { WARN("dvips process failed, exit code = " << dvipsProcess.exitCode()); *success = false; QFile::remove(baseName + ".aux"); QFile::remove(baseName + ".log"); QFile::remove(baseName + ".dvi"); return QImage(); } // convert: PS -> PNG QProcess convertProcess; #if defined(HAVE_WINDOWS) // need to set path to magick coder modules (which are in the labplot2 directory) QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("MAGICK_CODER_MODULE_PATH", qPrintable(qgetenv("PROGRAMFILES") + QString("\\labplot2"))); convertProcess.setProcessEnvironment(env); #endif const QStringList params{"-density", QString::number(dpi), baseName + ".ps", baseName + ".png"}; convertProcess.start("convert", params); if (!convertProcess.waitForFinished() || convertProcess.exitCode() != 0) { WARN("convert process failed, exit code = " << convertProcess.exitCode()); *success = false; QFile::remove(baseName + ".aux"); QFile::remove(baseName + ".log"); QFile::remove(baseName + ".dvi"); QFile::remove(baseName + ".ps"); return QImage(); } // read png file QImage image(baseName + ".png", "png"); // final clean up QFile::remove(baseName + ".aux"); QFile::remove(baseName + ".log"); QFile::remove(baseName + ".dvi"); QFile::remove(baseName + ".ps"); QFile::remove(baseName + ".png"); *success = true; return image; } bool TeXRenderer::enabled() { KConfigGroup group = KSharedConfig::openConfig()->group("Settings_Worksheet"); QString engine = group.readEntry("LaTeXEngine", ""); if (engine.isEmpty()) { //empty string was found in the settings (either the settings never saved or no tex engine was available during the last save) //->check whether the latex environment was installed in the meantime engine = QLatin1String("xelatex"); if (!executableExists(engine)) { engine = QLatin1String("lualatex"); if (!executableExists(engine)) { engine = QLatin1String("pdflatex"); if (!executableExists(engine)) engine = QLatin1String("latex"); } } if (!engine.isEmpty()) { //one of the tex engines was found -> automatically save it in the settings without any user action group.writeEntry(QLatin1String("LaTeXEngine"), engine); group.sync(); } } else if (!executableExists(engine)) { WARN("LaTeX engine does not exist"); return false; } //engine found, check the presence of other required tools (s.a. TeXRenderer.cpp): //to convert the generated PDF/PS files to PNG we need 'convert' from the ImageMagic package if (!executableExists(QLatin1String("convert"))) { WARN("program \"convert\" does not exist"); return false; } //to convert the generated PS files to DVI we need 'dvips' if (engine == "latex") { if (!executableExists(QLatin1String("dvips"))) { WARN("program \"dvips\" does not exist"); return false; } } #if defined(_WIN64) if (!executableExists(QLatin1String("gswin64c")) && !QDir(qgetenv("PROGRAMFILES") + QString("/gs")).exists() && !QDir(qgetenv("PROGRAMFILES(X86)") + QString("/gs")).exists()) { WARN("ghostscript (64bit) does not exist"); return false; } #elif defined(HAVE_WINDOWS) if (!executableExists(QLatin1String("gswin32c")) && !QDir(qgetenv("PROGRAMFILES") + QString("/gs")).exists()) { WARN("ghostscript (32bit) does not exist"); return false; } #endif return true; } bool TeXRenderer::executableExists(const QString& exe) { return !QStandardPaths::findExecutable(exe).isEmpty(); }