diff --git a/src/backend/core/AspectTreeModel.cpp b/src/backend/core/AspectTreeModel.cpp index 37caa07a0..63eeb23bd 100644 --- a/src/backend/core/AspectTreeModel.cpp +++ b/src/backend/core/AspectTreeModel.cpp @@ -1,473 +1,473 @@ /*************************************************************************** File : AspectTreeModel.h Project : LabPlot Description : Represents a tree of AbstractAspect objects as a Qt item model. -------------------------------------------------------------------- Copyright : (C) 2007-2009 by Knut Franke (knut.franke@gmx.de) Copyright : (C) 2007-2009 by Tilman Benkert (thzs@gmx.net) Copyright : (C) 2011-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 "backend/core/AbstractAspect.h" #include "backend/core/column/Column.h" #include "backend/worksheet/WorksheetElement.h" #include "backend/core/AspectTreeModel.h" #include #include #include #include #include #include /** * \class AspectTreeModel * \brief Represents a tree of AbstractAspect objects as a Qt item model. * * This class is an adapter between an AbstractAspect hierarchy and Qt's view classes. * * It represents children of an Aspect as rows in the model, with the fixed columns * Name (AbstractAspect::name()), Type (the class name), Created (AbstractAspect::creationTime()) * and Comment (AbstractAspect::comment()). Name is decorated using AbstractAspect::icon(). * The tooltip for all columns is generated from AbstractAspect::caption(). * * Name and Comment are editable. * * For views which support this (currently ProjectExplorer), the menu created by * AbstractAspect::createContextMenu() is made available via the custom role ContextMenuRole. */ /** * \enum AspectTreeModel::CustomDataRole * \brief Custom data roles used in addition to Qt::ItemDataRole */ /** * \var AspectTreeModel::ContextMenuRole * \brief pointer to a new context menu for an Aspect */ /** * \fn QModelIndex AspectTreeModel::modelIndexOfAspect(const AbstractAspect *aspect, int column=0) const * \brief Convenience wrapper around QAbstractItemModel::createIndex(). */ AspectTreeModel::AspectTreeModel(AbstractAspect* root, QObject* parent) : QAbstractItemModel(parent), m_root(root) { connect(m_root, &AbstractAspect::aspectDescriptionChanged, this, &AspectTreeModel::aspectDescriptionChanged); connect(m_root, &AbstractAspect::aspectAboutToBeAdded, this, &AspectTreeModel::aspectAboutToBeAdded); connect(m_root, &AbstractAspect::aspectAboutToBeRemoved, this, &AspectTreeModel::aspectAboutToBeRemoved); connect(m_root, &AbstractAspect::aspectAdded, this, &AspectTreeModel::aspectAdded); connect(m_root, &AbstractAspect::aspectRemoved, this, &AspectTreeModel::aspectRemoved); connect(m_root, &AbstractAspect::aspectHiddenAboutToChange, this, &AspectTreeModel::aspectHiddenAboutToChange); connect(m_root, &AbstractAspect::aspectHiddenChanged, this, &AspectTreeModel::aspectHiddenChanged); } /*! \c list contains the class names of the aspects, that can be selected in the corresponding model view. */ void AspectTreeModel::setSelectableAspects(const QList& list) { m_selectableAspects = list; } void AspectTreeModel::setReadOnly(bool readOnly) { m_readOnly = readOnly; } void AspectTreeModel::enablePlottableColumnsOnly(bool value) { m_plottableColumnsOnly = value; } void AspectTreeModel::enableNumericColumnsOnly(bool value) { m_numericColumnsOnly = value; } void AspectTreeModel::enableNonEmptyNumericColumnsOnly(bool value) { m_nonEmptyNumericColumnsOnly = value; } void AspectTreeModel::enableShowPlotDesignation(bool value) { m_showPlotDesignation = value; } QModelIndex AspectTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex{}; if (!parent.isValid()) { if (row != 0) return QModelIndex{}; return createIndex(row, column, m_root); } auto* parent_aspect = static_cast(parent.internalPointer()); auto* child_aspect = parent_aspect->child(row); if (!child_aspect) return QModelIndex{}; return createIndex(row, column, child_aspect); } QModelIndex AspectTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex{}; auto* parent_aspect = static_cast(index.internalPointer())->parentAspect(); if (!parent_aspect) return QModelIndex{}; return modelIndexOfAspect(parent_aspect); } int AspectTreeModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) return 1; auto* parent_aspect = static_cast(parent.internalPointer()); return parent_aspect->childCount(); } int AspectTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 4; } QVariant AspectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) return QVariant(); switch (role) { case Qt::DisplayRole: switch (section) { case 0: return i18n("Name"); case 1: return i18n("Type"); case 2: return i18n("Created"); case 3: return i18n("Comment"); default: return QVariant(); } default: return QVariant(); } } QVariant AspectTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto* aspect = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: case Qt::EditRole: switch (index.column()) { case 0: { const auto* column = dynamic_cast(aspect); if (column) { QString name = aspect->name(); if (m_plottableColumnsOnly && !column->isPlottable()) name = i18n("%1 (non-plottable data)", name); else if (m_numericColumnsOnly && !column->isNumeric()) name = i18n("%1 (non-numeric data)", name); else if (m_nonEmptyNumericColumnsOnly && !column->hasValues()) name = i18n("%1 (no values)", name); if (m_showPlotDesignation) name += QLatin1Char('\t') + column->plotDesignationString(); return name; } else return aspect->name(); } case 1: if (aspect->metaObject()->className() != QLatin1String("CantorWorksheet")) return aspect->metaObject()->className(); else return QLatin1String("CAS Worksheet"); case 2: return aspect->creationTime().toString(); case 3: return aspect->comment().replace('\n', ' ').simplified(); default: return QVariant(); } case Qt::ToolTipRole: if (aspect->comment().isEmpty()) return QLatin1String("") + aspect->name() + QLatin1String(""); else return QLatin1String("") + aspect->name() + QLatin1String("

") + aspect->comment().replace(QLatin1Char('\n'), QLatin1String("
")); case Qt::DecorationRole: return index.column() == 0 ? aspect->icon() : QIcon(); case Qt::ForegroundRole: { - const WorksheetElement* we = qobject_cast(aspect); + const WorksheetElement* we = dynamic_cast(aspect); if (we) { if (!we->isVisible()) return QVariant( QApplication::palette().color(QPalette::Disabled,QPalette::Text ) ); } return QVariant( QApplication::palette().color(QPalette::Active,QPalette::Text ) ); } default: return QVariant(); } } Qt::ItemFlags AspectTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return nullptr; Qt::ItemFlags result; auto* aspect = static_cast(index.internalPointer()); if (!m_selectableAspects.isEmpty()) { for (AspectType type : m_selectableAspects) { if (aspect->inherits(type)) { result = Qt::ItemIsEnabled | Qt::ItemIsSelectable; if (index != this->index(0,0,QModelIndex()) && !m_filterString.isEmpty()) { if (this->containsFilterString(aspect)) result = Qt::ItemIsEnabled | Qt::ItemIsSelectable; else result &= ~Qt::ItemIsEnabled; } break; } else result &= ~Qt::ItemIsEnabled; } } else { //default case: the list for the selectable aspects is empty and all aspects are selectable. // Apply filter, if available. Indices, that don't match the filter are not selectable. //Don't apply any filter to the very first index in the model - this top index corresponds to the project item. if (index != this->index(0,0,QModelIndex()) && !m_filterString.isEmpty()) { if (this->containsFilterString(aspect)) result = Qt::ItemIsEnabled | Qt::ItemIsSelectable; else result = Qt::ItemIsSelectable; } else result = Qt::ItemIsEnabled | Qt::ItemIsSelectable; } //the columns "name" and "description" are editable if (!m_readOnly) { if (index.column() == 0 || index.column() == 3) result |= Qt::ItemIsEditable; } const auto* column = dynamic_cast(aspect); if (column) { //allow to drag and drop columns for the faster creation of curves in the plots. //TODO: allow drag&drop later for other objects too, once we implement copy and paste in the project explorer result = result |Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; if (m_plottableColumnsOnly && !column->isPlottable()) result &= ~Qt::ItemIsEnabled; if (m_numericColumnsOnly && !column->isNumeric()) result &= ~Qt::ItemIsEnabled; if (m_nonEmptyNumericColumnsOnly && !(column->isNumeric() && column->hasValues())) result &= ~Qt::ItemIsEnabled; } return result; } void AspectTreeModel::aspectDescriptionChanged(const AbstractAspect* aspect) { emit dataChanged(modelIndexOfAspect(aspect), modelIndexOfAspect(aspect, 3)); } void AspectTreeModel::aspectAboutToBeAdded(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(child); int index = parent->indexOfChild(before); if (index == -1) index = parent->childCount(); beginInsertRows(modelIndexOfAspect(parent), index, index); } void AspectTreeModel::aspectAdded(const AbstractAspect* aspect) { endInsertRows(); AbstractAspect* parent = aspect->parentAspect(); emit dataChanged(modelIndexOfAspect(parent), modelIndexOfAspect(parent, 3)); connect(aspect, &AbstractAspect::renameRequested, this, &AspectTreeModel::renameRequestedSlot); connect(aspect, &AbstractAspect::childAspectSelectedInView, this, &AspectTreeModel::aspectSelectedInView); connect(aspect, &AbstractAspect::childAspectDeselectedInView, this, &AspectTreeModel::aspectDeselectedInView); //add signal-slot connects for all children, too for (const auto* child : aspect->children(AbstractAspect::Recursive)) { connect(child, &AbstractAspect::renameRequested, this, &AspectTreeModel::renameRequestedSlot); connect(child, &AbstractAspect::childAspectSelectedInView, this, &AspectTreeModel::aspectSelectedInView); connect(child, &AbstractAspect::childAspectDeselectedInView, this, &AspectTreeModel::aspectDeselectedInView); } } void AspectTreeModel::aspectAboutToBeRemoved(const AbstractAspect* aspect) { AbstractAspect* parent = aspect->parentAspect(); int index = parent->indexOfChild(aspect); beginRemoveRows(modelIndexOfAspect(parent), index, index); } void AspectTreeModel::aspectRemoved() { endRemoveRows(); } void AspectTreeModel::aspectHiddenAboutToChange(const AbstractAspect* aspect) { for (AbstractAspect* i = aspect->parentAspect(); i; i = i->parentAspect()) if (i->hidden()) return; if (aspect->hidden()) aspectAboutToBeAdded(aspect->parentAspect(), aspect, aspect); else aspectAboutToBeRemoved(aspect); } void AspectTreeModel::aspectHiddenChanged(const AbstractAspect* aspect) { for (AbstractAspect* i = aspect->parentAspect(); i; i = i->parentAspect()) if (i->hidden()) return; if (aspect->hidden()) aspectRemoved(); else aspectAdded(aspect); } bool AspectTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || role != Qt::EditRole) return false; auto* aspect = static_cast(index.internalPointer()); switch (index.column()) { case 0: { if (!aspect->setName(value.toString(), false)) { emit statusInfo(i18n("The name \"%1\" is already in use. Choose another name.", value.toString())); return false; } break; } case 3: aspect->setComment(value.toString()); break; default: return false; } emit dataChanged(index, index); return true; } QModelIndex AspectTreeModel::modelIndexOfAspect(const AbstractAspect* aspect, int column) const { AbstractAspect* parent = aspect->parentAspect(); return createIndex(parent ? parent->indexOfChild(aspect) : 0, column, const_cast(aspect)); } /*! returns the model index of an aspect defined via its path. */ QModelIndex AspectTreeModel::modelIndexOfAspect(const QString& path, int column) const { //determine the aspect out of aspect path AbstractAspect* aspect = nullptr; auto children = m_root->children(AspectType::AbstractAspect, AbstractAspect::Recursive); for (auto* child: children) { if (child->path() == path) { aspect = child; break; } } //return the model index of the aspect if (aspect) return modelIndexOfAspect(aspect, column); return QModelIndex{}; } void AspectTreeModel::setFilterString(const QString& s) { m_filterString = s; QModelIndex topLeft = this->index(0, 0, QModelIndex()); QModelIndex bottomRight = this->index(this->rowCount() - 1, 3, QModelIndex()); emit dataChanged(topLeft, bottomRight); } void AspectTreeModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs) { m_filterCaseSensitivity = cs; } void AspectTreeModel::setFilterMatchCompleteWord(bool b) { m_matchCompleteWord = b; } bool AspectTreeModel::containsFilterString(const AbstractAspect* aspect) const { if (m_matchCompleteWord) { if (aspect->name().compare(m_filterString, m_filterCaseSensitivity) == 0) return true; } else { if (aspect->name().contains(m_filterString, m_filterCaseSensitivity)) return true; } //check for the occurrence of the filter string in the names of the parents if ( aspect->parentAspect() ) return this->containsFilterString(aspect->parentAspect()); else return false; //TODO make this optional // //check for the occurrence of the filter string in the names of the children // foreach(const AbstractAspect * child, aspect->children()) { // if ( this->containsFilterString(child) ) // return true; // } } //############################################################################## //################################# SLOTS #################################### //############################################################################## void AspectTreeModel::renameRequestedSlot() { - auto* aspect = qobject_cast(QObject::sender()); + auto* aspect = dynamic_cast(QObject::sender()); if (aspect) emit renameRequested(modelIndexOfAspect(aspect)); } void AspectTreeModel::aspectSelectedInView(const AbstractAspect* aspect) { if (aspect->hidden()) { //a hidden aspect was selected in the view (e.g. plot title in WorksheetView) //select the parent aspect first, if available AbstractAspect* parent = aspect->parentAspect(); if (parent) emit indexSelected(modelIndexOfAspect(parent)); //emit also this signal, so the GUI can handle this selection. emit hiddenAspectSelected(aspect); } else emit indexSelected(modelIndexOfAspect(aspect)); //deselect the root item when one of the children was selected in the view //in order to avoid multiple selection with the project item (if selected) in the project explorer emit indexDeselected(modelIndexOfAspect(m_root)); } void AspectTreeModel::aspectDeselectedInView(const AbstractAspect* aspect) { if (aspect->hidden()) { AbstractAspect* parent = aspect->parentAspect(); if (parent) emit indexDeselected(modelIndexOfAspect(parent)); } else emit indexDeselected(modelIndexOfAspect(aspect)); } diff --git a/src/backend/core/column/ColumnPrivate.cpp b/src/backend/core/column/ColumnPrivate.cpp index 5cf3e93d9..2bd363faf 100644 --- a/src/backend/core/column/ColumnPrivate.cpp +++ b/src/backend/core/column/ColumnPrivate.cpp @@ -1,1496 +1,1496 @@ /*************************************************************************** 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 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(); m_data = new QVector(); break; case AbstractColumn::Integer: m_input_filter = new String2IntegerFilter(); m_output_filter = new Integer2StringFilter(); m_data = new QVector(); break; case AbstractColumn::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); m_data = new QStringList(); break; case AbstractColumn::DateTime: m_input_filter = new String2DateTimeFilter(); m_output_filter = new DateTime2StringFilter(); m_data = new QVector(); break; case AbstractColumn::Month: m_input_filter = new String2MonthFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("MMMM"); m_data = new QVector(); break; case AbstractColumn::Day: m_input_filter = new String2DayOfWeekFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("dddd"); m_data = new QVector(); break; } connect(m_output_filter, &AbstractSimpleFilter::formatChanged, m_owner, &Column::handleFormatChange); - m_input_filter->setName("InputFilter"); - m_output_filter->setName("OutputFilter"); + //m_input_filter->setName("InputFilter"); + //m_output_filter->setName("OutputFilter"); } /** * \brief Special ctor (to be called from Column only!) */ ColumnPrivate::ColumnPrivate(Column* owner, AbstractColumn::ColumnMode mode, void* data) : m_column_mode(mode), m_data(data), m_owner(owner) { switch (mode) { case AbstractColumn::Numeric: m_input_filter = new String2DoubleFilter(); m_output_filter = new Double2StringFilter(); connect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: m_input_filter = new String2IntegerFilter(); m_output_filter = new Integer2StringFilter(); connect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: m_input_filter = new SimpleCopyThroughFilter(); m_output_filter = new SimpleCopyThroughFilter(); break; case AbstractColumn::DateTime: m_input_filter = new String2DateTimeFilter(); m_output_filter = new DateTime2StringFilter(); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Month: m_input_filter = new String2MonthFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("MMMM"); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Day: m_input_filter = new String2DayOfWeekFilter(); m_output_filter = new DateTime2StringFilter(); static_cast(m_output_filter)->setFormat("dddd"); connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } - m_input_filter->setName("InputFilter"); - m_output_filter->setName("OutputFilter"); + //m_input_filter->setName("InputFilter"); + //m_output_filter->setName("OutputFilter"); } ColumnPrivate::~ColumnPrivate() { if (!m_data) return; switch (m_column_mode) { case AbstractColumn::Numeric: delete static_cast*>(m_data); break; case AbstractColumn::Integer: delete static_cast*>(m_data); break; case AbstractColumn::Text: delete static_cast*>(m_data); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: delete static_cast*>(m_data); break; } } AbstractColumn::ColumnMode ColumnPrivate::columnMode() const { return m_column_mode; } /** * \brief Set the column mode * * This sets the column mode and, if * necessary, converts it to another datatype. * Remark: setting the mode back to undefined (the * initial value) is not supported. */ void ColumnPrivate::setColumnMode(AbstractColumn::ColumnMode mode) { DEBUG("ColumnPrivate::setColumnMode() " << ENUM_TO_STRING(AbstractColumn, ColumnMode, m_column_mode) << " -> " << ENUM_TO_STRING(AbstractColumn, ColumnMode, mode)) if (mode == m_column_mode) return; void* old_data = m_data; // remark: the deletion of the old data will be done in the dtor of a command AbstractSimpleFilter* filter = nullptr, *new_in_filter = nullptr, *new_out_filter = nullptr; bool filter_is_temporary = false; // it can also become outputFilter(), which we may not delete here Column* temp_col = nullptr; emit m_owner->modeAboutToChange(m_owner); // determine the conversion filter and allocate the new data vector switch (m_column_mode) { // old mode case AbstractColumn::Numeric: { disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::Numeric: break; case AbstractColumn::Integer: filter = new Double2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data))); m_data = new QVector(); break; case AbstractColumn::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; case AbstractColumn::DateTime: filter = new Double2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; case AbstractColumn::Month: filter = new Double2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; case AbstractColumn::Day: filter = new Double2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data))); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::Integer: { disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::Integer: break; case AbstractColumn::Numeric: filter = new Integer2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::DateTime: filter = new Integer2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Month: filter = new Integer2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Day: filter = new Integer2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::Text: { switch (mode) { case AbstractColumn::Text: break; case AbstractColumn::Numeric: filter = new String2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Integer: filter = new String2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::DateTime: filter = new String2DateTimeFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Month: filter = new String2MonthFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Day: filter = new String2DayOfWeekFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast*>(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: { disconnect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); switch (mode) { case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: break; case AbstractColumn::Text: filter = outputFilter(); filter_is_temporary = false; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QStringList(); break; case AbstractColumn::Numeric: if (m_column_mode == AbstractColumn::Month) filter = new Month2DoubleFilter(); else if (m_column_mode == AbstractColumn::Day) filter = new DayOfWeek2DoubleFilter(); else filter = new DateTime2DoubleFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; case AbstractColumn::Integer: if (m_column_mode == AbstractColumn::Month) filter = new Month2IntegerFilter(); else if (m_column_mode == AbstractColumn::Day) filter = new DayOfWeek2IntegerFilter(); else filter = new DateTime2IntegerFilter(); filter_is_temporary = true; temp_col = new Column("temp_col", *(static_cast< QVector* >(old_data)), m_column_mode); m_data = new QVector(); break; } // switch(mode) break; } } // determine the new input and output filters switch (mode) { // new mode case AbstractColumn::Numeric: new_in_filter = new String2DoubleFilter(); new_out_filter = new Double2StringFilter(); connect(static_cast(new_out_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: new_in_filter = new String2IntegerFilter(); new_out_filter = new Integer2StringFilter(); connect(static_cast(new_out_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: new_in_filter = new SimpleCopyThroughFilter(); new_out_filter = new SimpleCopyThroughFilter(); break; case AbstractColumn::DateTime: new_in_filter = new String2DateTimeFilter(); new_out_filter = new DateTime2StringFilter(); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Month: new_in_filter = new String2MonthFilter(); new_out_filter = new DateTime2StringFilter(); static_cast(new_out_filter)->setFormat("MMMM"); DEBUG(" Month out_filter format: " << static_cast(new_out_filter)->format().toStdString()); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Day: new_in_filter = new String2DayOfWeekFilter(); new_out_filter = new DateTime2StringFilter(); static_cast(new_out_filter)->setFormat("dddd"); connect(static_cast(new_out_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } // switch(mode) m_column_mode = mode; - new_in_filter->setName("InputFilter"); - new_out_filter->setName("OutputFilter"); + //new_in_filter->setName("InputFilter"); + //new_out_filter->setName("OutputFilter"); m_input_filter = new_in_filter; m_output_filter = new_out_filter; m_input_filter->input(0, m_owner->m_string_io); m_output_filter->input(0, m_owner); m_input_filter->setHidden(true); m_output_filter->setHidden(true); if (temp_col) { // if temp_col == 0, only the input/output filters need to be changed // copy the filtered, i.e. converted, column (mode is orig mode) DEBUG(" temp_col column mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, temp_col->columnMode())); filter->input(0, temp_col); DEBUG(" filter->output size = " << filter->output(0)->rowCount()); copy(filter->output(0)); delete temp_col; } if (filter_is_temporary) delete filter; emit m_owner->modeChanged(m_owner); DEBUG("ColumnPrivate::setColumnMode() DONE"); } /** * \brief Replace all mode related members * * Replace column mode, data type, data pointer and filters directly */ void ColumnPrivate::replaceModeData(AbstractColumn::ColumnMode mode, void* data, AbstractSimpleFilter* in_filter, AbstractSimpleFilter* out_filter) { DEBUG("ColumnPrivate::replaceModeData()"); emit m_owner->modeAboutToChange(m_owner); // disconnect formatChanged() switch (m_column_mode) { case AbstractColumn::Numeric: disconnect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: disconnect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: disconnect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } m_column_mode = mode; m_data = data; - in_filter->setName("InputFilter"); - out_filter->setName("OutputFilter"); + //in_filter->setName("InputFilter"); + //out_filter->setName("OutputFilter"); m_input_filter = in_filter; m_output_filter = out_filter; m_input_filter->input(0, m_owner->m_string_io); m_output_filter->input(0, m_owner); // connect formatChanged() switch (m_column_mode) { case AbstractColumn::Numeric: connect(static_cast(m_output_filter), &Double2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Integer: connect(static_cast(m_output_filter), &Integer2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; case AbstractColumn::Text: break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: connect(static_cast(m_output_filter), &DateTime2StringFilter::formatChanged, m_owner, &Column::handleFormatChange); break; } emit m_owner->modeChanged(m_owner); } /** * \brief Replace data pointer */ void ColumnPrivate::replaceData(void* data) { DEBUG("ColumnPrivate::replaceData()"); emit m_owner->dataAboutToChange(m_owner); m_data = data; if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool ColumnPrivate::copy(const AbstractColumn* other) { DEBUG("ColumnPrivate::copy(other)"); if (other->columnMode() != columnMode()) return false; DEBUG(" mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, columnMode())); int num_rows = other->rowCount(); DEBUG(" rows " << num_rows); emit m_owner->dataAboutToChange(m_owner); resizeTo(num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } case AbstractColumn::Text: { for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->textAt(i)); break; } case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: { for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->dateTimeAt(i)); break; } } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param other pointer to the column to copy * \param src_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool ColumnPrivate::copy(const AbstractColumn* source, int source_start, int dest_start, int num_rows) { DEBUG("ColumnPrivate::copy()"); if (source->columnMode() != m_column_mode) return false; if (num_rows == 0) return true; emit m_owner->dataAboutToChange(m_owner); if (dest_start + num_rows > rowCount()) resizeTo(dest_start + num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->valueAt(source_start + i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; i++) ptr[dest_start+i] = source->integerAt(source_start + i); break; } case AbstractColumn::Text: for (int i = 0; i < num_rows; i++) static_cast*>(m_data)->replace(dest_start+i, source->textAt(source_start + i)); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < num_rows; i++) static_cast*>(m_data)->replace(dest_start+i, source->dateTimeAt(source_start + i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copy another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * Use a filter to convert a column to another type. */ bool ColumnPrivate::copy(const ColumnPrivate* other) { if (other->columnMode() != m_column_mode) return false; int num_rows = other->rowCount(); emit m_owner->dataAboutToChange(m_owner); resizeTo(num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->valueAt(i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[i] = other->integerAt(i); break; } case AbstractColumn::Text: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->textAt(i)); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(i, other->dateTimeAt(i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Copies a part of another column of the same type * * This function will return false if the data type * of 'other' is not the same as the type of 'this'. * \param other pointer to the column to copy * \param src_start first row to copy in the column to copy * \param dest_start first row to copy in * \param num_rows the number of rows to copy */ bool ColumnPrivate::copy(const ColumnPrivate* source, int source_start, int dest_start, int num_rows) { if (source->columnMode() != m_column_mode) return false; if (num_rows == 0) return true; emit m_owner->dataAboutToChange(m_owner); if (dest_start + num_rows > rowCount()) resizeTo(dest_start + num_rows); // copy the data switch (m_column_mode) { case AbstractColumn::Numeric: { double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->valueAt(source_start + i); break; } case AbstractColumn::Integer: { int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[dest_start+i] = source->integerAt(source_start + i); break; } case AbstractColumn::Text: for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(dest_start+i, source->textAt(source_start + i)); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i *>(m_data)->replace(dest_start+i, source->dateTimeAt(source_start + i)); break; } if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); return true; } /** * \brief Return the data vector size * * This returns the number of rows that actually contain data. * Rows beyond this can be masked etc. but should be ignored by filters, * plots etc. */ int ColumnPrivate::rowCount() const { switch (m_column_mode) { case AbstractColumn::Numeric: return static_cast*>(m_data)->size(); case AbstractColumn::Integer: return static_cast*>(m_data)->size(); case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: return static_cast*>(m_data)->size(); case AbstractColumn::Text: return static_cast*>(m_data)->size(); } return 0; } /** * \brief Resize the vector to the specified number of rows * * Since selecting and masking rows higher than the * real internal number of rows is supported, this * does not change the interval attributes. Also * no signal is emitted. If the new rows are filled * with values AbstractColumn::dataChanged() * must be emitted. */ void ColumnPrivate::resizeTo(int new_size) { int old_size = rowCount(); 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::Text: { int new_rows = new_size - old_size; if (new_rows > 0) { for (int i = 0; i < new_rows; ++i) static_cast*>(m_data)->append(QString()); } else { for (int i = 0; i < -new_rows; ++i) static_cast*>(m_data)->removeLast(); } break; } case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: { int new_rows = new_size - old_size; if (new_rows > 0) { for (int i = 0; i < new_rows; ++i) static_cast*>(m_data)->append(QDateTime()); } else { for (int i = 0; i < -new_rows; ++i) static_cast*>(m_data)->removeLast(); } break; } } } /** * \brief Insert some empty (or initialized with zero) rows */ void ColumnPrivate::insertRows(int before, int count) { if (count == 0) return; m_formulas.insertRows(before, count); if (before <= rowCount()) { switch (m_column_mode) { case AbstractColumn::Numeric: static_cast*>(m_data)->insert(before, count, NAN); break; case AbstractColumn::Integer: static_cast*>(m_data)->insert(before, count, 0); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < count; ++i) static_cast*>(m_data)->insert(before, QDateTime()); break; case AbstractColumn::Text: for (int i = 0; i < count; ++i) static_cast*>(m_data)->insert(before, QString()); break; } } } /** * \brief Remove 'count' rows starting from row 'first' */ void ColumnPrivate::removeRows(int first, int count) { if (count == 0) return; m_formulas.removeRows(first, count); if (first < rowCount()) { int corrected_count = count; if (first + count > rowCount()) corrected_count = rowCount() - first; switch (m_column_mode) { case AbstractColumn::Numeric: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::Integer: static_cast*>(m_data)->remove(first, corrected_count); break; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: for (int i = 0; i < corrected_count; ++i) static_cast*>(m_data)->removeAt(first); break; case AbstractColumn::Text: for (int i = 0; i < corrected_count; ++i) static_cast*>(m_data)->removeAt(first); break; } } } //! Return the column name QString ColumnPrivate::name() const { return m_owner->name(); } /** * \brief Return the column plot designation */ AbstractColumn::PlotDesignation ColumnPrivate::plotDesignation() const { return m_plot_designation; } /** * \brief Set the column plot designation */ void ColumnPrivate::setPlotDesignation(AbstractColumn::PlotDesignation pd) { emit m_owner->plotDesignationAboutToChange(m_owner); m_plot_designation = pd; emit m_owner->plotDesignationChanged(m_owner); } /** * \brief Get width */ int ColumnPrivate::width() const { return m_width; } /** * \brief Set width */ void ColumnPrivate::setWidth(int value) { m_width = value; } /** * \brief Return the data pointer */ void* ColumnPrivate::data() const { return m_data; } /** * \brief Return the input filter (for string -> data type conversion) */ AbstractSimpleFilter *ColumnPrivate::inputFilter() const { return m_input_filter; } /** * \brief Return the output filter (for data type -> string conversion) */ AbstractSimpleFilter *ColumnPrivate::outputFilter() const { return m_output_filter; } //////////////////////////////////////////////////////////////////////////////// //! \name Formula related functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the formula last used to generate data for the column */ QString ColumnPrivate::formula() const { return m_formula; } 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) { m_connectionsUpdateFormula << connect(column, &Column::dataChanged, m_owner, &Column::updateFormula); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &ColumnPrivate::formulaVariableColumnRemoved); connect(column->parentAspect(), &AbstractAspect::aspectAdded, this, &ColumnPrivate::formulaVariableColumnAdded); } } } /*! * 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) { m_connectionsUpdateFormula << connect(column, &Column::dataChanged, m_owner, &Column::updateFormula); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, 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 variableColumnPathes 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_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, QString path) { m_formulaVariableColumnPaths[index] = path; } void ColumnPrivate::setformulVariableColumn(int index, Column* column) { m_formulaVariableColumns[index] = 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) { //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 = dynamic_cast(m_owner->parentAspect()); Q_ASSERT(spreadsheet); 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); //TODO: why is const_cast requried 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(Interval i, QString formula) { m_formulas.setValue(i, formula); } /** * \brief Overloaded function for convenience */ void ColumnPrivate::setFormula(int row, QString formula) { setFormula(Interval(row,row), formula); } /** * \brief Clear all formulas */ void ColumnPrivate::clearFormulas() { m_formulas.clear(); } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //! \name type specific functions //@{ //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the content of row 'row'. * * Use this only when columnMode() is Text */ QString ColumnPrivate::textAt(int row) const { if (m_column_mode != AbstractColumn::Text) return QString(); return static_cast*>(m_data)->value(row); } /** * \brief Return the date part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDate ColumnPrivate::dateAt(int row) const { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return QDate{}; return dateTimeAt(row).date(); } /** * \brief Return the time part of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QTime ColumnPrivate::timeAt(int row) const { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return QTime{}; return dateTimeAt(row).time(); } /** * \brief Return the QDateTime in row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ QDateTime ColumnPrivate::dateTimeAt(int row) const { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return QDateTime(); return static_cast*>(m_data)->value(row); } /** * \brief Return the double value in row 'row' for columns with type Numeric and Integer. * This function has to be used everywhere where the exact type (double or int) is not relevant for numerical calculations. * For cases where the integer value is needed without any implicit conversions, \sa intergAt() has to be used. */ double ColumnPrivate::valueAt(int row) const { if (m_column_mode == AbstractColumn::Numeric) return static_cast*>(m_data)->value(row, NAN); else if (m_column_mode == AbstractColumn::Integer) return static_cast*>(m_data)->value(row, 0); else return NAN; } /** * \brief Return the int value in row 'row' */ int ColumnPrivate::integerAt(int row) const { if (m_column_mode != AbstractColumn::Integer) return 0; return static_cast*>(m_data)->value(row, 0); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Text */ void ColumnPrivate::setTextAt(int row, const QString& new_value) { if (m_column_mode != AbstractColumn::Text) return; emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Text */ void ColumnPrivate::replaceTexts(int first, const QVector& new_values) { if (m_column_mode != AbstractColumn::Text) return; emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(first+i, new_values.at(i)); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setDateAt(int row, QDate new_value) { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return; setDateTimeAt(row, QDateTime(new_value, timeAt(row))); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setTimeAt(int row, QTime new_value) { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return; setDateTimeAt(row, QDateTime(dateAt(row), new_value)); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::setDateTimeAt(int row, const QDateTime& new_value) { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return; emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast< QVector* >(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is DateTime, Month or Day */ void ColumnPrivate::replaceDateTimes(int first, const QVector& new_values) { if (m_column_mode != AbstractColumn::DateTime && m_column_mode != AbstractColumn::Month && m_column_mode != AbstractColumn::Day) return; emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); for (int i = 0; i < num_rows; ++i) static_cast*>(m_data)->replace(first+i, new_values.at(i)); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Numeric */ void ColumnPrivate::setValueAt(int row, double new_value) { // DEBUG("ColumnPrivate::setValueAt()"); if (m_column_mode != AbstractColumn::Numeric) return; emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Numeric */ void ColumnPrivate::replaceValues(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceValues()"); if (m_column_mode != AbstractColumn::Numeric) return; emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); double* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Set the content of row 'row' * * Use this only when columnMode() is Integer */ void ColumnPrivate::setIntegerAt(int row, int new_value) { DEBUG("ColumnPrivate::setIntegerAt()"); if (m_column_mode != AbstractColumn::Integer) return; emit m_owner->dataAboutToChange(m_owner); if (row >= rowCount()) resizeTo(row+1); static_cast*>(m_data)->replace(row, new_value); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /** * \brief Replace a range of values * * Use this only when columnMode() is Integer */ void ColumnPrivate::replaceInteger(int first, const QVector& new_values) { DEBUG("ColumnPrivate::replaceInteger()"); if (m_column_mode != AbstractColumn::Integer) return; emit m_owner->dataAboutToChange(m_owner); int num_rows = new_values.size(); if (first + num_rows > rowCount()) resizeTo(first + num_rows); int* ptr = static_cast*>(m_data)->data(); for (int i = 0; i < num_rows; ++i) ptr[first+i] = new_values.at(i); if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); } /*! * Updates the properties. Will be called, when data in the column changed. * The 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 prevValueDatetime = 0; if (m_column_mode == AbstractColumn::Integer) prevValueInt = integerAt(0); else if (m_column_mode == AbstractColumn::Numeric) prevValue = valueAt(0); else if (m_column_mode == AbstractColumn::DateTime || m_column_mode == AbstractColumn::Month || m_column_mode == AbstractColumn::Day) prevValueDatetime = dateTimeAt(0).toMSecsSinceEpoch(); else { properties = AbstractColumn::Properties::No; propertiesAvailable = true; return; } int monotonic_decreasing = -1; int monotonic_increasing = -1; double value; int valueInt; qint64 valueDateTime; for (int row = 1; row < rowCount(); row++) { if (m_column_mode == AbstractColumn::Integer) { valueInt = integerAt(row); // check monotonic increasing if (valueInt >= prevValueInt && monotonic_increasing < 0) monotonic_increasing = 1; else if (valueInt < prevValueInt && monotonic_increasing >= 0) monotonic_increasing = 0; // else: nothing // check monotonic decreasing if (valueInt <= prevValueInt && monotonic_decreasing < 0) monotonic_decreasing = 1; else if (valueInt > prevValueInt && monotonic_decreasing >= 0) monotonic_decreasing = 0; prevValueInt = valueInt; } else if (m_column_mode == AbstractColumn::Numeric) { value = valueAt(row); // check monotonic increasing if (value >= prevValue && monotonic_increasing < 0) monotonic_increasing = 1; else if (value < prevValue || std::isnan(value)) { monotonic_increasing = 0; if (monotonic_decreasing == 0) break; } // else: nothing // check monotonic decreasing if (value <= prevValue && monotonic_decreasing < 0) monotonic_decreasing = 1; else if (value > prevValue || std::isnan(value)) { monotonic_decreasing = 0; if (monotonic_increasing == 0) break; } prevValue = value; } else if (m_column_mode == AbstractColumn::DateTime || m_column_mode == AbstractColumn::Month || m_column_mode == AbstractColumn::Day) { valueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); // check monotonic increasing if (valueDateTime >= prevValueDatetime && monotonic_increasing < 0) monotonic_increasing = 1; else if (valueDateTime < prevValueDatetime) monotonic_increasing = 0; // else: nothing // check monotonic decreasing if (valueDateTime <= prevValueDatetime && monotonic_decreasing < 0) monotonic_decreasing = 1; else if (valueDateTime > prevValueDatetime) monotonic_decreasing = 0; prevValueDatetime = valueDateTime; } } properties = AbstractColumn::Properties::No; if (monotonic_increasing > 0 && monotonic_decreasing > 0) properties = AbstractColumn::Properties::Constant; else if (monotonic_decreasing > 0) properties = AbstractColumn::Properties::MonotonicDecreasing; else if (monotonic_increasing > 0) properties = AbstractColumn::Properties::MonotonicIncreasing; propertiesAvailable = true; } //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// /** * \brief Return the interval attribute representing the formula strings */ IntervalAttribute ColumnPrivate::formulaAttribute() const { return m_formulas; } /** * \brief Replace the interval attribute for the formula strings */ void ColumnPrivate::replaceFormulas(const IntervalAttribute& formulas) { m_formulas = formulas; } diff --git a/src/backend/spreadsheet/SpreadsheetModel.cpp b/src/backend/spreadsheet/SpreadsheetModel.cpp index 2936bcf33..a289e14ba 100644 --- a/src/backend/spreadsheet/SpreadsheetModel.cpp +++ b/src/backend/spreadsheet/SpreadsheetModel.cpp @@ -1,507 +1,509 @@ /*************************************************************************** File : SpreadsheetModel.cpp Project : LabPlot Description : Model for the access to a Spreadsheet -------------------------------------------------------------------- Copyright : (C) 2007 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2009 Knut Franke (knut.franke@gmx.de) Copyright : (C) 2013-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/spreadsheet/Spreadsheet.h" #include "backend/spreadsheet/SpreadsheetModel.h" #include "backend/core/datatypes/Double2StringFilter.h" #include #include #include #include /*! \class SpreadsheetModel \brief Model for the access to a Spreadsheet This is a model in the sense of Qt4 model/view framework which is used to access a Spreadsheet object from any of Qt4s view classes, typically a QTableView. Its main purposes are translating Spreadsheet signals into QAbstractItemModel signals and translating calls to the QAbstractItemModel read/write API into calls in the public API of Spreadsheet. In many cases a pointer to the addressed column is obtained by calling Spreadsheet::column() and the manipulation is done using the public API of column. \ingroup backend */ SpreadsheetModel::SpreadsheetModel(Spreadsheet* spreadsheet) : QAbstractItemModel(nullptr), m_spreadsheet(spreadsheet) { updateVerticalHeader(); updateHorizontalHeader(); connect(m_spreadsheet, &Spreadsheet::aspectAboutToBeAdded, this, &SpreadsheetModel::handleAspectAboutToBeAdded); connect(m_spreadsheet, &Spreadsheet::aspectAdded, this, &SpreadsheetModel::handleAspectAdded); connect(m_spreadsheet, &Spreadsheet::aspectAboutToBeRemoved, this, &SpreadsheetModel::handleAspectAboutToBeRemoved); connect(m_spreadsheet, &Spreadsheet::aspectRemoved, this, &SpreadsheetModel::handleAspectRemoved); connect(m_spreadsheet, &Spreadsheet::aspectDescriptionChanged, this, &SpreadsheetModel::handleDescriptionChange); for (int i = 0; i < spreadsheet->columnCount(); ++i) { beginInsertColumns(QModelIndex(), i, i); handleAspectAdded(spreadsheet->column(i)); } m_spreadsheet->setModel(this); } void SpreadsheetModel::suppressSignals(bool value) { m_suppressSignals = value; //update the headers after all the data was added to the model //and we start listening to signals again if (!m_suppressSignals) { + m_spreadsheet->emitColumnCountChanged(); updateVerticalHeader(); updateHorizontalHeader(); } } Qt::ItemFlags SpreadsheetModel::flags(const QModelIndex& index) const { if (index.isValid()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; else return Qt::ItemIsEnabled; } QVariant SpreadsheetModel::data(const QModelIndex& index, int role) const { if ( !index.isValid() ) return QVariant(); const int row = index.row(); const int col = index.column(); const Column* col_ptr = m_spreadsheet->column(col); if (!col_ptr) return QVariant(); switch (role) { case Qt::ToolTipRole: if (col_ptr->isValid(row)) { if (col_ptr->isMasked(row)) return QVariant(i18n("%1, masked (ignored in all operations)", col_ptr->asStringColumn()->textAt(row))); else return QVariant(col_ptr->asStringColumn()->textAt(row)); } else { if (col_ptr->isMasked(row)) return QVariant(i18n("invalid cell, masked (ignored in all operations)")); else return QVariant(i18n("invalid cell (ignored in all operations)")); } case Qt::EditRole: if (col_ptr->isValid(row)) return QVariant(col_ptr->asStringColumn()->textAt(row)); //m_formula_mode is not used at the moment //if (m_formula_mode) // return QVariant(col_ptr->formula(row)); break; case Qt::DisplayRole: if (!col_ptr->isValid(row)) return QVariant("-"); //m_formula_mode is not used at the moment //if (m_formula_mode) // return QVariant(col_ptr->formula(row)); return QVariant(col_ptr->asStringColumn()->textAt(row)); case Qt::ForegroundRole: if (!col_ptr->isValid(index.row())) return QVariant(QBrush(Qt::red)); break; case MaskingRole: return QVariant(col_ptr->isMasked(row)); case FormulaRole: return QVariant(col_ptr->formula(row)); // case Qt::DecorationRole: // if (m_formula_mode) // return QIcon(QPixmap(":/equals.png")); //TODO } return QVariant(); } QVariant SpreadsheetModel::headerData(int section, Qt::Orientation orientation, int role) const { if ( (orientation == Qt::Horizontal && section > m_spreadsheet->columnCount()-1) || (orientation == Qt::Vertical && section > m_spreadsheet->rowCount()-1) ) return QVariant(); switch (orientation) { case Qt::Horizontal: switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: case Qt::EditRole: return m_horizontal_header_data.at(section); case Qt::DecorationRole: return m_spreadsheet->child(section)->icon(); case SpreadsheetModel::CommentRole: return m_spreadsheet->child(section)->comment(); } break; case Qt::Vertical: switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: return m_vertical_header_data.at(section); } } return QVariant(); } int SpreadsheetModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent) return m_spreadsheet->rowCount(); } int SpreadsheetModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return m_spreadsheet->columnCount(); } bool SpreadsheetModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; int row = index.row(); Column* column = m_spreadsheet->column(index.column()); //don't do anything if no new value was provided if (column->columnMode() == AbstractColumn::Numeric) { bool ok; QLocale locale; double new_value = locale.toDouble(value.toString(), &ok); if (ok) { if (column->valueAt(row) == new_value ) return false; } else { //an empty (non-numeric value) was provided if (std::isnan(column->valueAt(row))) return false; } } else { if (column->asStringColumn()->textAt(row) == value.toString()) return false; } switch (role) { case Qt::EditRole: { // remark: the validity of the cell is determined by the input filter if (m_formula_mode) column->setFormula(row, value.toString()); else column->asStringColumn()->setTextAt(row, value.toString()); return true; } case MaskingRole: { m_spreadsheet->column(index.column())->setMasked(row, value.toBool()); return true; } case FormulaRole: { m_spreadsheet->column(index.column())->setFormula(row, value.toString()); return true; } } return false; } QModelIndex SpreadsheetModel::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent) return createIndex(row, column); } QModelIndex SpreadsheetModel::parent(const QModelIndex& child) const { Q_UNUSED(child) return QModelIndex{}; } bool SpreadsheetModel::hasChildren(const QModelIndex& parent) const { Q_UNUSED(parent) return false; } void SpreadsheetModel::handleAspectAboutToBeAdded(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* new_child) { if (m_suppressSignals) return; const Column* col = qobject_cast(new_child); if (!col || parent != static_cast(m_spreadsheet)) return; //TODO: breaks undo/redo Q_UNUSED(before); // int index = before ? m_spreadsheet->indexOfChild(before) : 0; //beginInsertColumns(QModelIndex(), index, index); } void SpreadsheetModel::handleAspectAdded(const AbstractAspect* aspect) { const Column* col = qobject_cast(aspect); if (!col || aspect->parentAspect() != static_cast(m_spreadsheet)) return; updateVerticalHeader(); updateHorizontalHeader(); connect(col, &Column::plotDesignationChanged, this, &SpreadsheetModel::handlePlotDesignationChange); connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleDataChange); connect(col, &Column::dataChanged, this, &SpreadsheetModel::handleDataChange); connect(col, &Column::modeChanged, this, &SpreadsheetModel::handleModeChange); connect(col, &Column::rowsInserted, this, &SpreadsheetModel::handleRowsInserted); connect(col, &Column::rowsRemoved, this, &SpreadsheetModel::handleRowsRemoved); connect(col, &Column::maskingChanged, this, &SpreadsheetModel::handleDataChange); connect(col->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &SpreadsheetModel::handleDigitsChange); beginResetModel(); //TODO: breaks undo/redo //endInsertColumns(); endResetModel(); - m_spreadsheet->emitColumnCountChanged(); + if (!m_suppressSignals) + m_spreadsheet->emitColumnCountChanged(); } void SpreadsheetModel::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { if (m_suppressSignals) return; const Column* col = qobject_cast(aspect); if (!col || aspect->parentAspect() != static_cast(m_spreadsheet)) return; beginResetModel(); disconnect(col, nullptr, this, nullptr); } void SpreadsheetModel::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(before) const Column* col = qobject_cast(child); if (!col || parent != static_cast(m_spreadsheet)) return; updateVerticalHeader(); updateHorizontalHeader(); endResetModel(); m_spreadsheet->emitColumnCountChanged(); } void SpreadsheetModel::handleDescriptionChange(const AbstractAspect* aspect) { if (m_suppressSignals) return; const Column* col = qobject_cast(aspect); if (!col || aspect->parentAspect() != static_cast(m_spreadsheet)) return; updateHorizontalHeader(); int index = m_spreadsheet->indexOfChild(col); emit headerDataChanged(Qt::Horizontal, index, index); } void SpreadsheetModel::handleModeChange(const AbstractColumn* col) { if (m_suppressSignals) return; updateHorizontalHeader(); int index = m_spreadsheet->indexOfChild(col); emit headerDataChanged(Qt::Horizontal, index, index); handleDataChange(col); //output filter was changed after the mode change, update the signal-slot connection disconnect(nullptr, SIGNAL(digitsChanged()), this, SLOT(handledigitsChange())); connect(dynamic_cast(col)->outputFilter(), &AbstractSimpleFilter::digitsChanged, this, &SpreadsheetModel::handleDigitsChange); } void SpreadsheetModel::handleDigitsChange() { if (m_suppressSignals) return; const auto* filter = dynamic_cast(QObject::sender()); if (!filter) return; const AbstractColumn* col = filter->output(0); handleDataChange(col); } void SpreadsheetModel::handlePlotDesignationChange(const AbstractColumn* col) { if (m_suppressSignals) return; updateHorizontalHeader(); int index = m_spreadsheet->indexOfChild(col); emit headerDataChanged(Qt::Horizontal, index, m_spreadsheet->columnCount()-1); } void SpreadsheetModel::handleDataChange(const AbstractColumn* col) { if (m_suppressSignals) return; int i = m_spreadsheet->indexOfChild(col); emit dataChanged(index(0, i), index(col->rowCount()-1, i)); } void SpreadsheetModel::handleRowsInserted(const AbstractColumn* col, int before, int count) { if (m_suppressSignals) return; Q_UNUSED(before) Q_UNUSED(count) updateVerticalHeader(); int i = m_spreadsheet->indexOfChild(col); emit dataChanged(index(0, i), index(col->rowCount()-1, i)); m_spreadsheet->emitRowCountChanged(); } void SpreadsheetModel::handleRowsRemoved(const AbstractColumn* col, int first, int count) { if (m_suppressSignals) return; Q_UNUSED(first) Q_UNUSED(count) updateVerticalHeader(); int i = m_spreadsheet->indexOfChild(col); emit dataChanged(index(0, i), index(col->rowCount()-1, i)); m_spreadsheet->emitRowCountChanged(); } void SpreadsheetModel::updateVerticalHeader() { int old_rows = m_vertical_header_data.size(); int new_rows = m_spreadsheet->rowCount(); if (new_rows > old_rows) { beginInsertRows(QModelIndex(), old_rows, new_rows-1); for (int i = old_rows+1; i <= new_rows; i++) m_vertical_header_data << i; endInsertRows(); } else if (new_rows < old_rows) { beginRemoveRows(QModelIndex(), new_rows, old_rows-1); while (m_vertical_header_data.size() > new_rows) m_vertical_header_data.removeLast(); endRemoveRows(); } } void SpreadsheetModel::updateHorizontalHeader() { int column_count = m_spreadsheet->childCount(); while (m_horizontal_header_data.size() < column_count) m_horizontal_header_data << QString(); while (m_horizontal_header_data.size() > column_count) m_horizontal_header_data.removeLast(); for (int i = 0; i < column_count; i++) { Column* col = m_spreadsheet->child(i); QString type; switch (col->columnMode()) { case AbstractColumn::Numeric: type = QLatin1String(" {") + i18n("Numeric") + QLatin1Char('}'); break; case AbstractColumn::Integer: type = QLatin1String(" {") + i18n("Integer") + QLatin1Char('}'); break; case AbstractColumn::Text: type = QLatin1String(" {") + i18n("Text") + QLatin1Char('}'); break; case AbstractColumn::Month: type = QLatin1String(" {") + i18n("Month Names") + QLatin1Char('}'); break; case AbstractColumn::Day: type = QLatin1String(" {") + i18n("Day Names") + QLatin1Char('}'); break; case AbstractColumn::DateTime: type = QLatin1String(" {") + i18n("Date and Time") + QLatin1Char('}'); break; } QString designation; switch (col->plotDesignation()) { case AbstractColumn::NoDesignation: break; case AbstractColumn::X: designation = QLatin1String(" [X]"); break; case AbstractColumn::Y: designation = QLatin1String(" [Y]"); break; case AbstractColumn::Z: designation = QLatin1String(" [Z]"); break; case AbstractColumn::XError: designation = QLatin1String(" [") + i18n("X-error") + QLatin1Char(']'); break; case AbstractColumn::XErrorPlus: designation = QLatin1String(" [") + i18n("X-error +") + QLatin1Char(']'); break; case AbstractColumn::XErrorMinus: designation = QLatin1String(" [") + i18n("X-error -") + QLatin1Char(']'); break; case AbstractColumn::YError: designation = QLatin1String(" [") + i18n("Y-error") + QLatin1Char(']'); break; case AbstractColumn::YErrorPlus: designation = QLatin1String(" [") + i18n("Y-error +") + QLatin1Char(']'); break; case AbstractColumn::YErrorMinus: designation = QLatin1String(" [") + i18n("Y-error -") + QLatin1Char(']'); break; } m_horizontal_header_data.replace(i, col->name() + type + designation); } } Column* SpreadsheetModel::column(int index) { return m_spreadsheet->column(index); } void SpreadsheetModel::activateFormulaMode(bool on) { if (m_formula_mode == on) return; m_formula_mode = on; int rows = m_spreadsheet->rowCount(); int cols = m_spreadsheet->columnCount(); if (rows > 0 && cols > 0) emit dataChanged(index(0,0), index(rows-1,cols-1)); } bool SpreadsheetModel::formulaModeActive() const { return m_formula_mode; }