diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -45,6 +45,7 @@ ${KDEFRONTEND_DIR}/datasources/MQTTConnectionManagerDialog.cpp ${KDEFRONTEND_DIR}/dockwidgets/BaseDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/AxisDock.cpp + ${KDEFRONTEND_DIR}/dockwidgets/CursorDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/NoteDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/CartesianPlotDock.cpp ${KDEFRONTEND_DIR}/dockwidgets/CartesianPlotLegendDock.cpp @@ -127,6 +128,7 @@ ${KDEFRONTEND_DIR}/ui/datasources/jsonoptionswidget.ui ${KDEFRONTEND_DIR}/ui/datasources/mqttconnectionmanagerwidget.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/axisdock.ui + ${KDEFRONTEND_DIR}/ui/dockwidgets/cursordock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/cartesianplotdock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/cartesianplotlegenddock.ui ${KDEFRONTEND_DIR}/ui/dockwidgets/histogramdock.ui @@ -241,6 +243,7 @@ ${BACKEND_DIR}/worksheet/Worksheet.cpp ${BACKEND_DIR}/worksheet/WorksheetElementContainer.cpp ${BACKEND_DIR}/worksheet/WorksheetElementGroup.cpp + ${BACKEND_DIR}/worksheet/TreeModel.cpp ${BACKEND_DIR}/worksheet/plots/AbstractPlot.cpp ${BACKEND_DIR}/worksheet/plots/AbstractCoordinateSystem.cpp ${BACKEND_DIR}/worksheet/plots/PlotArea.cpp diff --git a/src/backend/core/AbstractAspect.h b/src/backend/core/AbstractAspect.h --- a/src/backend/core/AbstractAspect.h +++ b/src/backend/core/AbstractAspect.h @@ -146,6 +146,7 @@ //functions related to the handling of the tree-like project structure AbstractAspect* parentAspect() const; + AbstractAspect* parent(AspectType type) const; void setParentAspect(AbstractAspect*); Folder* folder(); bool isDescendantOf(AbstractAspect* other); diff --git a/src/backend/core/AbstractAspect.cpp b/src/backend/core/AbstractAspect.cpp --- a/src/backend/core/AbstractAspect.cpp +++ b/src/backend/core/AbstractAspect.cpp @@ -345,6 +345,19 @@ return (static_cast(m_type) & static_cast(type)) == static_cast(type); } +/** + * \brief In the parent-child hierarchy, return the first parent of type \param type or null pointer if there is none. + */ +AbstractAspect* AbstractAspect::parent(AspectType type) const { + AbstractAspect* parent = parentAspect(); + if (!parent) + return nullptr; + + if (parent->inherits(type)) + return parent; + + return parent->parent(type); +} /** * \brief Return my parent Aspect or 0 if I currently don't have one. diff --git a/src/backend/core/AbstractColumn.h b/src/backend/core/AbstractColumn.h --- a/src/backend/core/AbstractColumn.h +++ b/src/backend/core/AbstractColumn.h @@ -148,6 +148,7 @@ virtual ColumnMode columnMode() const = 0; virtual void setColumnMode(AbstractColumn::ColumnMode); virtual PlotDesignation plotDesignation() const = 0; + virtual QString plotDesignationString() const = 0; virtual void setPlotDesignation(AbstractColumn::PlotDesignation); bool isNumeric() const; bool isPlottable() const; diff --git a/src/backend/core/AbstractSimpleFilter.h b/src/backend/core/AbstractSimpleFilter.h --- a/src/backend/core/AbstractSimpleFilter.h +++ b/src/backend/core/AbstractSimpleFilter.h @@ -46,6 +46,7 @@ AbstractColumn* output(int port) override; const AbstractColumn * output(int port) const override; virtual AbstractColumn::PlotDesignation plotDesignation() const; + virtual QString plotDesignationString() const; virtual AbstractColumn::ColumnMode columnMode() const; virtual QString textAt(int row) const; virtual QDate dateAt(int row) const; @@ -90,6 +91,7 @@ AbstractColumn::ColumnMode columnMode() const override; int rowCount() const override { return m_owner->rowCount(); } AbstractColumn::PlotDesignation plotDesignation() const override { return m_owner->plotDesignation(); } + QString plotDesignationString() const override { return m_owner->plotDesignationString(); } QString textAt(int row) const override; QDate dateAt(int row) const override; QTime timeAt(int row) const override; diff --git a/src/backend/core/AbstractSimpleFilter.cpp b/src/backend/core/AbstractSimpleFilter.cpp --- a/src/backend/core/AbstractSimpleFilter.cpp +++ b/src/backend/core/AbstractSimpleFilter.cpp @@ -154,6 +154,13 @@ return m_inputs.value(0) ? m_inputs.at(0)->plotDesignation() : AbstractColumn::NoDesignation; } +/** + * \brief Copy plot designation string of input port 0. + */ +QString AbstractSimpleFilter::plotDesignationString() const { + return m_inputs.value(0) ? m_inputs.at(0)->plotDesignationString() : QString(""); +} + /** * \brief Return the column mode * diff --git a/src/backend/core/AspectTreeModel.cpp b/src/backend/core/AspectTreeModel.cpp --- a/src/backend/core/AspectTreeModel.cpp +++ b/src/backend/core/AspectTreeModel.cpp @@ -193,41 +193,8 @@ else if (m_nonEmptyNumericColumnsOnly && !column->hasValues()) name = i18n("%1 (no values)", name); - if (m_showPlotDesignation) { - QString designation; - switch (column->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; - } - name += QLatin1Char('\t') + designation; - } + if (m_showPlotDesignation) + name += QLatin1Char('\t') + " " + column->plotDesignationString(); return name; } else diff --git a/src/backend/core/column/Column.h b/src/backend/core/column/Column.h --- a/src/backend/core/column/Column.h +++ b/src/backend/core/column/Column.h @@ -66,6 +66,7 @@ bool copy(const AbstractColumn* source, int source_start, int dest_start, int num_rows) override; AbstractColumn::PlotDesignation plotDesignation() const override; + QString plotDesignationString() const override; void setPlotDesignation(AbstractColumn::PlotDesignation) override; bool isReadOnly() const override; diff --git a/src/backend/core/column/Column.cpp b/src/backend/core/column/Column.cpp --- a/src/backend/core/column/Column.cpp +++ b/src/backend/core/column/Column.cpp @@ -1128,6 +1128,33 @@ 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(); } diff --git a/src/backend/core/column/ColumnStringIO.h b/src/backend/core/column/ColumnStringIO.h --- a/src/backend/core/column/ColumnStringIO.h +++ b/src/backend/core/column/ColumnStringIO.h @@ -39,6 +39,7 @@ explicit ColumnStringIO(Column* owner); AbstractColumn::ColumnMode columnMode() const override; AbstractColumn::PlotDesignation plotDesignation() const override; + QString plotDesignationString() const override; int rowCount() const override; QString textAt(int) const override; void setTextAt(int, const QString&) override; diff --git a/src/backend/core/column/ColumnStringIO.cpp b/src/backend/core/column/ColumnStringIO.cpp --- a/src/backend/core/column/ColumnStringIO.cpp +++ b/src/backend/core/column/ColumnStringIO.cpp @@ -47,6 +47,10 @@ return m_owner->plotDesignation(); } +QString ColumnStringIO::plotDesignationString() const { + return m_owner->plotDesignationString(); +} + int ColumnStringIO::rowCount() const { return m_owner->rowCount(); } diff --git a/src/backend/worksheet/TreeModel.h b/src/backend/worksheet/TreeModel.h new file mode 100644 --- /dev/null +++ b/src/backend/worksheet/TreeModel.h @@ -0,0 +1,113 @@ +/*************************************************************************** +File : TreeModel.h +Project : LabPlot +Description : This is an abstract treemodel which can be used by a treeview +-------------------------------------------------------------------- +Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@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 * +* * +***************************************************************************/ + +#ifndef TREEMODEL_H +#define TREEMODEL_H + +#include +#include + +/*! + * \brief The TreeItem class + * Item in the treemodel + */ +class TreeItem +{ +public: + explicit TreeItem(const QVector &data, TreeItem *parent = 0); + ~TreeItem(); + + TreeItem *child(int number); + int childCount() const; + int columnCount() const; + QVariant data(int column) const; + QVariant backgroundColor() const; + bool insertChildren(int position, int count, int columns); + bool insertColumns(int position, int columns); + TreeItem *parent(); + bool removeChildren(int position, int count); + bool removeColumns(int position, int columns); + int childNumber() const; + bool setData(int column, const QVariant &value); + bool setBackgroundColor(int column, const QVariant &value); + +private: + QList childItems; + QVector itemData; + QColor m_backgroundColor{QColor(0,0,0,0)}; + TreeItem *parentItem{nullptr}; +}; +/*! + * \brief The TreeModel class + * This is an abstract treemodel which can be used by a treeview + */ +class TreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + TreeModel(const QStringList &headers, + QObject *parent = nullptr); + ~TreeModel(); + QVariant treeData(const int row, const int column, const QModelIndex &parent = QModelIndex(), const int role = Qt::EditRole); + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool setTreeData(const QVariant data, const int row, const int column, + const QModelIndex &parent = QModelIndex(), int role = Qt::EditRole); + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override; + int compareStrings(const QString value, const int row, const int column, const QModelIndex &parent = QModelIndex()); + bool setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role = Qt::EditRole) override; + + bool insertColumns(int position, int columns, + const QModelIndex &parent = QModelIndex()) override; + bool removeColumns(int position, int columns, + const QModelIndex &parent = QModelIndex()) override; + bool insertRows(int position, int rows, + const QModelIndex &parent = QModelIndex()) override; + bool removeRows(int position, int rows, + const QModelIndex &parent = QModelIndex()) override; + +private: + TreeItem *getItem(const QModelIndex &index) const; + + TreeItem *rootItem{nullptr}; +}; + +#endif // TREEMODEL_H diff --git a/src/backend/worksheet/TreeModel.cpp b/src/backend/worksheet/TreeModel.cpp new file mode 100644 --- /dev/null +++ b/src/backend/worksheet/TreeModel.cpp @@ -0,0 +1,325 @@ +/*************************************************************************** +File : TreeModel.cpp +Project : LabPlot +Description : This is an abstract treemodel which can be used by a treeview +-------------------------------------------------------------------- +Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@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 "TreeModel.h" + +//########################################################## +// TreeItem ############################################### +//########################################################## + +TreeItem::TreeItem(const QVector &data, TreeItem *parent) : + itemData(data), + parentItem(parent) { +} + +TreeItem::~TreeItem() { + qDeleteAll(childItems); +} + +TreeItem *TreeItem::child(int number) { + return childItems.value(number); +} + +int TreeItem::childCount() const { + return childItems.count(); +} + +int TreeItem::childNumber() const { + if (parentItem) + return parentItem->childItems.indexOf(const_cast(this)); + + return 0; +} + +int TreeItem::columnCount() const { + return itemData.count(); +} + +QVariant TreeItem::data(int column) const { + return itemData.value(column); +} + +QVariant TreeItem::backgroundColor() const { + return m_backgroundColor; +} + +bool TreeItem::insertChildren(int position, int count, int columns) { + if (position < 0 || position > childItems.size()) + return false; + + for (int row = 0; row < count; ++row) { + QVector data(columns); + TreeItem *item = new TreeItem(data, this); + childItems.insert(position, item); + } + + return true; +} + +bool TreeItem::insertColumns(int position, int columns) { + if (position < 0 || position > itemData.size()) + return false; + + for (int column = 0; column < columns; ++column) + itemData.insert(position, QVariant()); + + foreach (TreeItem *child, childItems) + child->insertColumns(position, columns); + + return true; +} + +TreeItem *TreeItem::parent() { + return parentItem; +} + +bool TreeItem::removeChildren(int position, int count) { + if (position < 0 || position + count > childItems.size()) + return false; + + for (int row = 0; row < count; ++row) + delete childItems.takeAt(position); + + return true; +} + +bool TreeItem::removeColumns(int position, int columns) { + if (position < 0 || position + columns > itemData.size()) + return false; + + for (int column = 0; column < columns; ++column) + itemData.remove(position); + + foreach (TreeItem *child, childItems) + child->removeColumns(position, columns); + + return true; +} + +bool TreeItem::setData(int column, const QVariant &value) { + if (column < 0 || column >= itemData.size()) + return false; + + itemData[column] = value; + return true; +} + +bool TreeItem::setBackgroundColor(int column, const QVariant &value) { + if (column < 0 || column >= itemData.size()) + return false; + + m_backgroundColor = value.value(); + return true; +} + +//########################################################## +// TreeModel ############################################### +//########################################################## + +TreeModel::TreeModel(const QStringList &headers, QObject *parent) + : QAbstractItemModel(parent) { + QVector rootData; + for (auto header : headers) + rootData << header; + + rootItem = new TreeItem(rootData); +} + +TreeModel::~TreeModel() { + delete rootItem; +} + +int TreeModel::columnCount(const QModelIndex & /* parent */) const { + return rootItem->columnCount(); +} + + +QVariant TreeModel::treeData(const int row, const int column, const QModelIndex& parent, const int role) { + QModelIndex currentIndex = index(row, column, parent); + return data(currentIndex, role); +} + +QVariant TreeModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) + return QVariant(); + + if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::BackgroundRole) + return QVariant(); + + TreeItem *item = getItem(index); + + if (role != Qt::BackgroundRole) + return item->data(index.column()); + + return item->backgroundColor(); +} + +Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const { + if (!index.isValid()) + return nullptr; + + return Qt::ItemIsEditable | QAbstractItemModel::flags(index); +} + +TreeItem *TreeModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + TreeItem *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return rootItem; +} + +QVariant TreeModel::headerData(int section, Qt::Orientation orientation, + int role) const { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return rootItem->data(section); + + return QVariant(); +} + +QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const { + if (parent.isValid() && parent.column() != 0) + return QModelIndex(); + + TreeItem *parentItem = getItem(parent); + + TreeItem *childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +bool TreeModel::insertColumns(int position, int columns, const QModelIndex &parent) { + bool success; + + beginInsertColumns(parent, position, position + columns - 1); + success = rootItem->insertColumns(position, columns); + endInsertColumns(); + + return success; +} + +bool TreeModel::insertRows(int position, int rows, const QModelIndex &parent) { + TreeItem *parentItem = getItem(parent); + bool success; + + beginInsertRows(parent, position, position + rows - 1); + success = parentItem->insertChildren(position, rows, rootItem->columnCount()); + endInsertRows(); + + return success; +} + +QModelIndex TreeModel::parent(const QModelIndex &index) const { + if (!index.isValid()) + return QModelIndex(); + + TreeItem *childItem = getItem(index); + TreeItem *parentItem = childItem->parent(); + + if (parentItem == rootItem) + return QModelIndex(); + + return createIndex(parentItem->childNumber(), 0, parentItem); +} + +bool TreeModel::removeColumns(int position, int columns, const QModelIndex &parent) { + bool success; + + beginRemoveColumns(parent, position, position + columns - 1); + success = rootItem->removeColumns(position, columns); + endRemoveColumns(); + + if (rootItem->columnCount() == 0) + removeRows(0, rowCount()); + + return success; +} + +bool TreeModel::removeRows(int position, int rows, const QModelIndex &parent) { + TreeItem *parentItem = getItem(parent); + bool success = true; + + beginRemoveRows(parent, position, position + rows - 1); + success = parentItem->removeChildren(position, rows); + endRemoveRows(); + + return success; +} + +int TreeModel::rowCount(const QModelIndex &parent) const { + TreeItem *parentItem = getItem(parent); + + return parentItem->childCount(); +} + +bool TreeModel::setTreeData(const QVariant data, const int row, const int column, const QModelIndex &parent, int role) { + QModelIndex curveIndex = index(row, column, parent); + return setData(curveIndex, data, role); +} + +bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { + + if (role == Qt::EditRole || role == Qt::DisplayRole) { + TreeItem *item = getItem(index); + bool result = item->setData(index.column(), value); + + if (result) + emit dataChanged(index, index); + + return result; + } else if (role == Qt::BackgroundRole) { + TreeItem *item = getItem(index); + bool result = item->setBackgroundColor(index.column(), value); + + if (result) + emit dataChanged(index, index); + } + + return false; +} + +bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, + const QVariant &value, int role) { + if (role != Qt::EditRole && role != Qt::DisplayRole && orientation != Qt::Horizontal) + return false; + + bool result = rootItem->setData(section, value); + + if (result) + emit headerDataChanged(orientation, section, section); + + return result; +} + +int TreeModel::compareStrings(const QString value, const int row, const int column, const QModelIndex &parent) { + QModelIndex plotIndex = index(row, column, parent); + return plotIndex.data().toString().compare(value); +} diff --git a/src/backend/worksheet/Worksheet.h b/src/backend/worksheet/Worksheet.h --- a/src/backend/worksheet/Worksheet.h +++ b/src/backend/worksheet/Worksheet.h @@ -38,6 +38,9 @@ class WorksheetPrivate; class WorksheetView; +class TreeModel; +class XYCurve; +class CartesianPlot; class Worksheet : public AbstractPart { Q_OBJECT @@ -79,8 +82,16 @@ CartesianPlotActionMode cartesianPlotActionMode(); void setCartesianPlotActionMode(CartesianPlotActionMode mode); + CartesianPlotActionMode cartesianPlotCursorMode(); + void setCartesianPlotCursorMode(CartesianPlotActionMode mode); void setPlotsLocked(bool); bool plotsLocked(); + int getPlotCount(); + WorksheetElement* getPlot(int index); + TreeModel* cursorModel(); + + void cursorModelPlotAdded(QString name); + void cursorModelPlotRemoved(QString name); BASIC_D_ACCESSOR_DECL(float, backgroundOpacity, BackgroundOpacity) BASIC_D_ACCESSOR_DECL(PlotArea::BackgroundType, backgroundType, BackgroundType) @@ -115,6 +126,21 @@ public slots: void setTheme(const QString&); + void cartesianPlotmousePressZoomSelectionMode(QPointF logicPos); + void cartesianPlotmousePressCursorMode(int cursorNumber, QPointF logicPos); + void cartesianPlotmouseMoveZoomSelectionMode(QPointF logicPos); + void cartesianPlotmouseMoveCursorMode(int cursorNumber, QPointF logicPos); + void cartesianPlotmouseReleaseZoomSelectionMode(); + void cartesianPlotmouseHoverZoomSelectionMode(QPointF logicPos); + void cartesianPlotmouseModeChanged(); + + // slots needed by the cursor + void updateCurveBackground(QPen pen, QString curveName); + void updateCompleteCursorTreeModel(); + void cursorPosChanged(int cursorNumber, double xPos); + void curveAdded(const XYCurve* curve); + void curveRemoved(const XYCurve* curve); + void curveDataChanged(const XYCurve* curve); private: void init(); @@ -160,6 +186,7 @@ void layoutRowCountChanged(int); void layoutColumnCountChanged(int); void themeChanged(const QString&); + void showCursorDock(TreeModel*, QVector); }; #endif diff --git a/src/backend/worksheet/Worksheet.cpp b/src/backend/worksheet/Worksheet.cpp --- a/src/backend/worksheet/Worksheet.cpp +++ b/src/backend/worksheet/Worksheet.cpp @@ -32,6 +32,7 @@ #include "commonfrontend/worksheet/WorksheetView.h" #include "backend/core/Project.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" +#include "backend/worksheet/TreeModel.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" @@ -242,6 +243,27 @@ QGraphicsItem* item = addedElement->graphicsItem(); d->m_scene->addItem(item); + const CartesianPlot* plot = dynamic_cast(aspect); + if(plot){ + connect(plot, &CartesianPlot::mouseMoveCursorModeSignal, this, &Worksheet::cartesianPlotmouseMoveCursorMode); + connect(plot, &CartesianPlot::mouseMoveZoomSelectionModeSignal, this, &Worksheet::cartesianPlotmouseMoveZoomSelectionMode); + connect(plot, &CartesianPlot::mousePressCursorModeSignal, this, &Worksheet::cartesianPlotmousePressCursorMode); + connect(plot, &CartesianPlot::mousePressZoomSelectionModeSignal, this, &Worksheet::cartesianPlotmousePressZoomSelectionMode); + connect(plot, &CartesianPlot::mouseReleaseZoomSelectionModeSignal, this, &Worksheet::cartesianPlotmouseReleaseZoomSelectionMode); + connect(plot, &CartesianPlot::mouseHoverZoomSelectionModeSignal, this, &Worksheet::cartesianPlotmouseHoverZoomSelectionMode); + connect(plot, &CartesianPlot::curveRemoved, this, &Worksheet::curveRemoved); + connect(plot, &CartesianPlot::curveAdded, this, &Worksheet::curveAdded); + connect(plot, &CartesianPlot::visibleChanged, this, &Worksheet::updateCompleteCursorTreeModel); + connect(plot, &CartesianPlot::curveVisibilityChangedSignal, this, &Worksheet::updateCompleteCursorTreeModel); + connect(plot, &CartesianPlot::curveDataChanged, this, &Worksheet::curveDataChanged); + connect(plot, &CartesianPlot::curveDataChanged, this, &Worksheet::curveDataChanged); + connect(plot, QOverload::of(&CartesianPlot::curveLinePenChanged), this, &Worksheet::updateCurveBackground); + connect(plot, &CartesianPlot::mouseModeChanged, this, &Worksheet::cartesianPlotmouseModeChanged); + auto* p = const_cast(plot); + p->setLocked(d->plotsLocked); + + cursorModelPlotAdded(p->name()); + } qreal zVal = 0; for (auto* child : children(IncludeHidden)) child->graphicsItem()->setZValue(zVal++); @@ -252,12 +274,6 @@ const_cast(addedElement)->loadThemeConfig(config); } - const CartesianPlot* plot = dynamic_cast(aspect); - if (plot) { - auto* p = const_cast(plot); - p->setLocked(d->plotsLocked); - } - //recalculated the layout if (!isLoading()) { if (d->layout != Worksheet::NoLayout) @@ -276,10 +292,13 @@ void Worksheet::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); - Q_UNUSED(child); if (d->layout != Worksheet::NoLayout) d->updateLayout(false); + auto* plot = dynamic_cast(child); + if (plot) + cursorModelPlotRemoved(plot->name()); + } QGraphicsScene* Worksheet::scene() const { @@ -391,6 +410,30 @@ m_view->setIsClosing(); } +/*! + * \brief Worksheet::getPlotCount + * \return number of CartesianPlot's in the Worksheet + */ +int Worksheet::getPlotCount(){ + return children().length(); +} + +/*! + * \brief Worksheet::getPlot + * \param index Number of plot which shoud be returned + * \return Pointer to the CartesianPlot which was searched with index + */ +WorksheetElement *Worksheet::getPlot(int index){ + QVector cartesianPlots = children(); + if(cartesianPlots.length()-1 >= index) + return cartesianPlots[index]; + return nullptr; +} + +TreeModel* Worksheet::cursorModel() { + return d->cursorData; +} + void Worksheet::update() { emit requestUpdate(); } @@ -407,6 +450,10 @@ return d->cartesianPlotActionMode; } +Worksheet::CartesianPlotActionMode Worksheet::cartesianPlotCursorMode() { + return d->cartesianPlotCursorMode; +} + bool Worksheet::plotsLocked() { return d->plotsLocked; } @@ -419,6 +466,28 @@ project()->setChanged(true); } +void Worksheet::setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode mode) { + if (d->cartesianPlotCursorMode == mode) + return; + + d->cartesianPlotCursorMode = mode; + + if (mode == Worksheet::CartesianPlotActionMode::ApplyActionToAll) { + d->suppressCursorPosChanged = true; + QVector plots = children(); + QPointF logicPos; + if (!plots.isEmpty()) { + for (int i = 0; i < 2; i++) { + logicPos = QPointF(plots[0]->cursorPos(i), 0); // y value does not matter + cartesianPlotmousePressCursorMode(i, logicPos); + } + } + d->suppressCursorPosChanged = false; + } + updateCompleteCursorTreeModel(); + project()->setChanged(true); +} + void Worksheet::setPlotsLocked(bool lock) { if (d->plotsLocked == lock) return; @@ -665,10 +734,510 @@ } } +void Worksheet::cartesianPlotmousePressZoomSelectionMode(QPointF logicPos){ + if(cartesianPlotActionMode() == Worksheet::ApplyActionToAll){ + QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); + for (auto* child : childElements){ + CartesianPlot* plot = dynamic_cast(child); + if(plot) + plot->mousePressZoomSelectionMode(logicPos); + } + return; + } + CartesianPlot* plot = dynamic_cast(QObject::sender()); + plot->mousePressZoomSelectionMode(logicPos); +} + +void Worksheet::cartesianPlotmouseReleaseZoomSelectionMode(){ + if(cartesianPlotActionMode() == Worksheet::ApplyActionToAll){ + QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); + for (auto* child : childElements){ + CartesianPlot* plot = dynamic_cast(child); + if(plot) + plot->mouseReleaseZoomSelectionMode(); + } + return; + } + CartesianPlot* plot = dynamic_cast(QObject::sender()); + plot->mouseReleaseZoomSelectionMode(); +} + +void Worksheet::cartesianPlotmousePressCursorMode(int cursorNumber, QPointF logicPos){ + if(cartesianPlotCursorMode() == Worksheet::ApplyActionToAll){ + QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); + for (auto* child : childElements){ + CartesianPlot* plot = dynamic_cast(child); + if(plot) + plot->mousePressCursorMode(cursorNumber, logicPos); + } + } else { + CartesianPlot* plot = dynamic_cast(QObject::sender()); + if (plot) + plot->mousePressCursorMode(cursorNumber, logicPos); + } + + cursorPosChanged(cursorNumber, logicPos.x()); +} + +void Worksheet::cartesianPlotmouseMoveZoomSelectionMode(QPointF logicPos){ + if(cartesianPlotActionMode() == Worksheet::ApplyActionToAll){ + QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); + for (auto* child : childElements){ + CartesianPlot* plot = dynamic_cast(child); + if(plot) + plot->mouseMoveZoomSelectionMode(logicPos); + } + return; + } + CartesianPlot* plot = dynamic_cast(QObject::sender()); + plot->mouseMoveZoomSelectionMode(logicPos); +} + +void Worksheet::cartesianPlotmouseHoverZoomSelectionMode(QPointF logicPos) { + if(cartesianPlotActionMode() == Worksheet::ApplyActionToAll){ + QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); + for (auto* child : childElements){ + CartesianPlot* plot = dynamic_cast(child); + if(plot) + plot->mouseHoverZoomSelectionMode(logicPos); + } + return; + } + CartesianPlot* plot = dynamic_cast(QObject::sender()); + plot->mouseHoverZoomSelectionMode(logicPos); +} + +void Worksheet::cartesianPlotmouseMoveCursorMode(int cursorNumber, QPointF logicPos){ + if (cartesianPlotCursorMode() == Worksheet::ApplyActionToAll) { + QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); + for (auto* child : childElements) { + CartesianPlot* plot = dynamic_cast(child); + if (plot) + plot->mouseMoveCursorMode(cursorNumber, logicPos); + } + } else { + CartesianPlot* plot = dynamic_cast(QObject::sender()); + plot->mouseMoveCursorMode(cursorNumber, logicPos); + } + + cursorPosChanged(cursorNumber, logicPos.x()); +} + +/*! + * \brief Worksheet::cursorPosChanged + * Updates the cursor treemodel with the new data + * \param xPos: new position of the cursor + * It is assumed, that the plots/curves are in the same order than receiving from + * the children() function. It's not checked if the names are the same + */ +void Worksheet::cursorPosChanged(int cursorNumber, double xPos){ + + if (d->suppressCursorPosChanged) + return; + TreeModel* treeModel = cursorModel(); + + auto* sender = dynamic_cast(QObject::sender()); + + // if ApplyActionToSelection, each plot has it's own x value + int rowPlot = 0; + if(cartesianPlotCursorMode() == Worksheet::ApplyActionToAll){ + // x values + rowPlot = 1; + QModelIndex xName = treeModel->index(0, WorksheetPrivate::TreeModelColumn::SIGNALNAME); + treeModel->setData(xName, QVariant("X")); + double valueCursor[2]; + for (int i = 0; i < 2; i++) { // need both cursors to calculate diff + valueCursor[i] = sender->cursorPos(i); + treeModel->setTreeData(QVariant(valueCursor[i]), 0, + WorksheetPrivate::TreeModelColumn::CURSOR0+i); + + } + treeModel->setTreeData(QVariant(valueCursor[1] - valueCursor[0]), 0, + WorksheetPrivate::TreeModelColumn::CURSORDIFF); + + // y values + for (int i = 0; i < getPlotCount(); i++) { // i=0 is the x Axis + + auto* plot = dynamic_cast(getPlot(i)); + if (!plot || !plot->isVisible()) + continue; + + QModelIndex plotIndex = treeModel->index(rowPlot, + WorksheetPrivate::TreeModelColumn::PLOTNAME); + + // curves + int rowCurve = 0; + for (int j = 0; j < plot->curveCount(); j++) { + // assumption: index of signals in model is the same than the index of the signal in the plot + bool valueFound; + + const XYCurve* curve = plot->getCurve(j); + if (!curve->isVisible()) + continue; + + double value = curve->y(xPos, valueFound); + if (cursorNumber == 0) { + treeModel->setTreeData(QVariant(value), rowCurve, + WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex); + double valueCursor1 = treeModel->treeData(rowCurve, + WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex).toDouble(); + treeModel->setTreeData(QVariant(valueCursor1 - value), rowCurve, + WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); + } else { + treeModel->setTreeData(QVariant(value), rowCurve, + WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex); + double valueCursor0 = treeModel->treeData(rowCurve, + WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex).toDouble(); + treeModel->setTreeData(QVariant(value - valueCursor0), rowCurve, + WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); + } + rowCurve++; + } + rowPlot++; + } + } else { // apply to selection + // assumption: plot is visible + int rowCount = treeModel->rowCount(); + for (int i = 0; i < rowCount; i++) { + QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); + if (plotIndex.data().toString().compare(sender->name()) != 0) + continue; + + // x values (first row always exist) + treeModel->setTreeData(QVariant("X"), 0, + WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotIndex); + double valueCursor[2]; + for (int i = 0; i < 2; i++) { // need both cursors to calculate diff + valueCursor[i] = sender->cursorPos(i); + treeModel->setTreeData(QVariant(valueCursor[i]), 0, + WorksheetPrivate::TreeModelColumn::CURSOR0+i, plotIndex); + } + treeModel->setTreeData(QVariant(valueCursor[1]-valueCursor[0]), 0, + WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); + + // y values + int rowCurve = 1; // first is x value + for (int j = 0; j< sender->curveCount(); j++) { // j=0 are the x values + + const XYCurve* curve = sender->getCurve(j); // -1 because we start with 1 for the x axis + if (!curve->isVisible()) + continue; + + // assumption: index of signals in model is the same than the index of the signal in the plot + bool valueFound; + + double value = curve->y(xPos, valueFound); + if (cursorNumber == 0) { + treeModel->setTreeData(QVariant(value), rowCurve, + WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex); + double valueCursor1 = treeModel->treeData(rowCurve, + WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex).toDouble(); + treeModel->setTreeData(QVariant(valueCursor1 - value), rowCurve, + WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); + } else { + treeModel->setTreeData(QVariant(value), rowCurve, + WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex); + double valueCursor0 = treeModel->treeData(rowCurve, + WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex).toDouble(); + treeModel->setTreeData(QVariant(value - valueCursor0), rowCurve, + WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); + } + rowCurve++; + } + + } + } +} + +void Worksheet::cursorModelPlotAdded(QString name) { + TreeModel* treeModel = cursorModel(); + int rowCount = treeModel->rowCount(); + // add plot at the end + treeModel->insertRows(rowCount, 1); // add empty rows. Then they become filled + treeModel->setTreeData(QVariant(name), rowCount, WorksheetPrivate::TreeModelColumn::PLOTNAME); // rowCount instead of rowCount -1 because first row is the x value +} + +void Worksheet::cursorModelPlotRemoved(QString name) { + TreeModel* treeModel = cursorModel(); + int rowCount = treeModel->rowCount(); + + // first is x Axis + for (int i = 1; i < rowCount; i++) { + QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); + if (plotIndex.data().toString().compare(name) != 0) + continue; + treeModel->removeRows(plotIndex.row(), 1); + return; + } +} + +void Worksheet::cartesianPlotmouseModeChanged() { + // assumption: only called from a CartesianPlot + auto* plot = static_cast(QObject::sender()); + if (d->updateCompleteCursorModel) { + updateCompleteCursorTreeModel(); + d->updateCompleteCursorModel = false; + } + + // If cursor dock is closed open it only, when the MouseMode is set to cursor. + // If it is already open, let it open, so the gui does not change. + if (plot->mouseMode() == CartesianPlot::MouseMode::Cursor) + emit showCursorDock(cursorModel(), children()); +} + +void Worksheet::curveDataChanged(const XYCurve* curve) { + auto* plot = dynamic_cast(QObject::sender()); + if (!plot) + return; + + TreeModel* treeModel = cursorModel(); + int rowCount = treeModel->rowCount(); + + for (int i = 0; i < rowCount; i++) { + QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); + if (plotIndex.data().toString().compare(plot->name()) != 0) + continue; + + for (int j = 0; j < plot->curveCount(); j++) { + + if (plot->getCurve(j)->name().compare(curve->name()) != 0) + continue; + + treeModel->setTreeData(QVariant(curve->name()), j, + WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotIndex); + + bool valueFound; + double valueCursor0 = curve->y(plot->cursorPos(0), valueFound); + treeModel->setTreeData(QVariant(valueCursor0), j, + WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex); + + double valueCursor1 = curve->y(plot->cursorPos(1), valueFound); + treeModel->setTreeData(QVariant(valueCursor1), j, + WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex); + + treeModel->setTreeData(QVariant(valueCursor1-valueCursor0), j, + WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); + break; + } + break; + } +} + +void Worksheet::curveAdded(const XYCurve* curve) { + auto* plot = dynamic_cast(QObject::sender()); + if (!plot) + return; + + TreeModel* treeModel = cursorModel(); + int rowCount = treeModel->rowCount(); + + // first row is the x axis, so starting at the second row + for (int i = 1; i < rowCount; i++) { + QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); + if (plotIndex.data().toString().compare(plot->name()) != 0) + continue; + + for (int j = 0; j < plot->curveCount(); j++) { + + if (plot->getCurve(j)->name().compare(curve->name()) != 0) + continue; + + treeModel->insertRow(j, plotIndex); + + QModelIndex curveIndex = treeModel->index(j, WorksheetPrivate::TreeModelColumn::SIGNALNAME, + plotIndex); + treeModel->setData(curveIndex, QVariant(curve->name())); + + bool valueFound; + double valueCursor0 = curve->y(plot->cursorPos(0), valueFound); + treeModel->setTreeData(QVariant(valueCursor0), j, + WorksheetPrivate::TreeModelColumn::CURSOR0, plotIndex); + + double valueCursor1 = curve->y(plot->cursorPos(1), valueFound); + treeModel->setTreeData(QVariant(valueCursor1), j, + WorksheetPrivate::TreeModelColumn::CURSOR1, plotIndex); + + treeModel->setTreeData(QVariant(valueCursor1-valueCursor0), j, + WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotIndex); + break; + } + break; + } +} + +void Worksheet::curveRemoved(const XYCurve* curve) { + + auto* plot = dynamic_cast(QObject::sender()); + if (!plot) + return; + + TreeModel* treeModel = cursorModel(); + int rowCount = treeModel->rowCount(); + + for (int i = 0; i < rowCount; i++) { + QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); + if (plotIndex.data().toString().compare(plot->name()) != 0) + continue; + + int curveCount = treeModel->rowCount(plotIndex); + for (int j = 0; j < curveCount; j++) { + QModelIndex curveIndex = treeModel->index(j, WorksheetPrivate::TreeModelColumn::SIGNALNAME, + plotIndex); + + if (curveIndex.data().toString().compare(curve->name()) != 0) + continue; + treeModel->removeRow(j, plotIndex); + break; + } + break; + } +} + +/*! + * Updates the background of the cuves entry in the treeview + * @param pen Pen of the curve + * @param curveName Curve name to find in treemodel + */ +void Worksheet::updateCurveBackground(QPen pen, QString curveName) { + const CartesianPlot* plot = static_cast(QObject::sender()); + TreeModel* treeModel = cursorModel(); + int rowCount = treeModel->rowCount(); + + for (int i = 0; i < rowCount; i++) { + QModelIndex plotIndex = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); + if (plotIndex.data().toString().compare(plot->name()) != 0) + continue; + + int curveCount = treeModel->rowCount(plotIndex); + for (int j = 0; j < curveCount; j++) { + QModelIndex curveIndex = treeModel->index(j, WorksheetPrivate::TreeModelColumn::SIGNALNAME, + plotIndex); + + if (curveIndex.data().toString().compare(curveName) != 0) + continue; + + QColor curveColor = pen.color(); + curveColor.setAlpha(50); + treeModel->setTreeData(QVariant(curveColor), j, + WorksheetPrivate::TreeModelColumn::SIGNALNAME, + plotIndex, Qt::BackgroundRole); + return; + } + return; + } +} + +/** + * @brief Worksheet::updateCompleteCursorTreeModel + * If the plot or the curve are not available, the plot/curve is not in the treemodel! + */ +void Worksheet::updateCompleteCursorTreeModel() { + TreeModel* treeModel = cursorModel(); + + treeModel->removeRows(0,treeModel->rowCount()); // remove all data + + int plotCount = getPlotCount(); + if (plotCount < 1) + return; + + int rowPlot = 0; + + if (cartesianPlotCursorMode() == Worksheet::CartesianPlotActionMode::ApplyActionToAll) { + // 1 because of the X data + treeModel->insertRows(0, 1); //, treeModel->index(0,0)); // add empty rows. Then they become filled + rowPlot = 1; + + // set X data + QModelIndex xName = treeModel->index(0, WorksheetPrivate::TreeModelColumn::SIGNALNAME); + treeModel->setData(xName, QVariant("X")); + CartesianPlot* plot0 = dynamic_cast(getPlot(0)); + double valueCursor[2]; + for (int i = 0; i < 2; i++) { + valueCursor[i] = plot0->cursorPos(i); + QModelIndex cursor = treeModel->index(0,WorksheetPrivate::TreeModelColumn::CURSOR0+i); + + treeModel->setData(cursor, QVariant(valueCursor[i])); + } + QModelIndex diff = treeModel->index(0,WorksheetPrivate::TreeModelColumn::CURSORDIFF); + treeModel->setData(diff, QVariant(valueCursor[1]-valueCursor[0])); + } else { + //treeModel->insertRows(0, plotCount, treeModel->index(0,0)); // add empty rows. Then they become filled + } + + // set plot name, y value, background + for (int i = 0; i < plotCount; i++) { + CartesianPlot* plot = dynamic_cast(getPlot(i)); + QModelIndex plotName; + int addOne = 0; + + if (!plot || !plot->isVisible()) + continue; + + // add new entry for the plot + treeModel->insertRows(treeModel->rowCount(), 1); //, treeModel->index(0, 0)); + + if (cartesianPlotCursorMode() == Worksheet::CartesianPlotActionMode::ApplyActionToAll) { + plotName = treeModel->index(i + 1, WorksheetPrivate::TreeModelColumn::PLOTNAME); // plus one because first row are the x values + treeModel->setData(plotName, QVariant(plot->name())); + } else { + addOne = 1; + plotName = treeModel->index(i, WorksheetPrivate::TreeModelColumn::PLOTNAME); + treeModel->setData(plotName, QVariant(plot->name())); + treeModel->insertRows(0, 1, plotName); // one, because the first row are the x values + + QModelIndex xName = treeModel->index(0, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotName); + treeModel->setData(xName, QVariant("X")); + double valueCursor[2]; + for (int i = 0; i < 2; i++) { + valueCursor[i] = plot->cursorPos(i); + QModelIndex cursor = treeModel->index(0, WorksheetPrivate::TreeModelColumn::CURSOR0+i, plotName); + + treeModel->setData(cursor, QVariant(valueCursor[i])); + } + QModelIndex diff = treeModel->index(0, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotName); + treeModel->setData(diff, QVariant(valueCursor[1]-valueCursor[0])); + } + + + int rowCurve = addOne; + for (int j = 0; j < plot->curveCount(); j++) { + double cursorValue[2] = {NAN, NAN}; + const XYCurve* curve = plot->getCurve(j); + + if (!curve->isVisible()) + continue; + + for (int k = 0; k < 2; k++) { + double xPos = plot->cursorPos(k); + bool valueFound; + cursorValue[k] = curve->y(xPos,valueFound); + } + treeModel->insertRows(rowCurve, 1, plotName); + QModelIndex backgroundColor = treeModel->index(rowCurve, 0, plotName); + QColor curveColor = curve->linePen().color(); + curveColor.setAlpha(50); + treeModel->setData(backgroundColor, QVariant(curveColor), Qt::BackgroundRole); + QModelIndex signalName = treeModel->index(rowCurve, WorksheetPrivate::TreeModelColumn::SIGNALNAME, plotName); + treeModel->setData(signalName, QVariant(curve->name())); + QModelIndex signalCursor0 = treeModel->index(rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR0, plotName); + treeModel->setData(signalCursor0, QVariant(cursorValue[0])); + QModelIndex signalCursor2 = treeModel->index(rowCurve, WorksheetPrivate::TreeModelColumn::CURSOR1, plotName); + treeModel->setData(signalCursor2, QVariant(cursorValue[1])); + QModelIndex differenceValues = treeModel->index(rowCurve, WorksheetPrivate::TreeModelColumn::CURSORDIFF, plotName); + treeModel->setData(differenceValues, QVariant(cursorValue[1]-cursorValue[0])); + + rowCurve++; + } + rowPlot++; + } +} + //############################################################################## //###################### Private implementation ############################### //############################################################################## WorksheetPrivate::WorksheetPrivate(Worksheet* owner) : q(owner), m_scene(new QGraphicsScene()) { + QStringList headers = {i18n("Plot/Curve"), "V1", "V2", "V2-V1"}; + cursorData = new TreeModel(headers, nullptr); } QString WorksheetPrivate::name() const { @@ -849,6 +1418,7 @@ writer->writeStartElement( "plotProperties" ); writer->writeAttribute( "plotsLocked", QString::number(d->plotsLocked) ); writer->writeAttribute( "cartesianPlotActionMode", QString::number(d->cartesianPlotActionMode)); + writer->writeAttribute( "cartesianPlotCursorMode", QString::number(d->cartesianPlotCursorMode)); writer->writeEndElement(); //serialize all children @@ -978,6 +1548,7 @@ READ_INT_VALUE("plotsLocked", plotsLocked, bool); READ_INT_VALUE("cartesianPlotActionMode", cartesianPlotActionMode, Worksheet::CartesianPlotActionMode); + READ_INT_VALUE("cartesianPlotCursorMode", cartesianPlotCursorMode, Worksheet::CartesianPlotActionMode); } else if (reader->name() == "cartesianPlot") { CartesianPlot* plot = new CartesianPlot(QString()); plot->setIsLoading(true); @@ -1004,6 +1575,10 @@ d->updateLayout(); } + // when creating a new CartesianPlot, this plot sends, that new XYCurves where added, + // but after creating the CartesianPlot, the CartesianPlot will be added to the Worksheet, + // where all connections to the signals will be made. So no update will be done automatically + updateCompleteCursorTreeModel(); return true; } diff --git a/src/backend/worksheet/WorksheetPrivate.h b/src/backend/worksheet/WorksheetPrivate.h --- a/src/backend/worksheet/WorksheetPrivate.h +++ b/src/backend/worksheet/WorksheetPrivate.h @@ -37,6 +37,7 @@ class Worksheet; class WorksheetElementContainer; class QGraphicsScene; +class TreeModel; class WorksheetPrivate { public: @@ -66,6 +67,7 @@ Worksheet::Layout layout{Worksheet::VerticalLayout}; bool suppressLayoutUpdate{false}; + bool suppressCursorPosChanged{false}; float layoutTopMargin{0.0}; float layoutBottomMargin{0.0}; float layoutLeftMargin{0.0}; @@ -76,7 +78,19 @@ int layoutRowCount{2}; QString theme; bool plotsLocked{false}; + bool updateCompleteCursorModel{true}; Worksheet::CartesianPlotActionMode cartesianPlotActionMode{Worksheet::CartesianPlotActionMode::ApplyActionToSelection}; + Worksheet::CartesianPlotActionMode cartesianPlotCursorMode{Worksheet::CartesianPlotActionMode::ApplyActionToAll}; + + enum TreeModelColumn { + PLOTNAME = 0, + SIGNALNAME = 0, + CURSOR0, + CURSOR1, + CURSORDIFF + }; + + TreeModel* cursorData{nullptr}; }; #endif diff --git a/src/backend/worksheet/plots/AbstractCoordinateSystem.h b/src/backend/worksheet/plots/AbstractCoordinateSystem.h --- a/src/backend/worksheet/plots/AbstractCoordinateSystem.h +++ b/src/backend/worksheet/plots/AbstractCoordinateSystem.h @@ -43,6 +43,8 @@ DefaultMapping = 0x00, SuppressPageClipping = 0x01, MarkGaps = 0x02, + Limit = 0x04, // set limits, when point crosses the limits + SuppressPageClippingY = 0x08, }; Q_DECLARE_FLAGS(MappingFlags, MappingFlag) diff --git a/src/backend/worksheet/plots/AbstractPlot.cpp b/src/backend/worksheet/plots/AbstractPlot.cpp --- a/src/backend/worksheet/plots/AbstractPlot.cpp +++ b/src/backend/worksheet/plots/AbstractPlot.cpp @@ -119,14 +119,14 @@ void AbstractPlot::setRightPadding(double padding) { Q_D(AbstractPlot); if (padding != d->rightPadding) - exec(new AbstractPlotSetRightPaddingCmd(d, padding, ki18n("%1: set horizontal padding"))); + exec(new AbstractPlotSetRightPaddingCmd(d, padding, ki18n("%1: set right padding"))); } STD_SETTER_CMD_IMPL_F_S(AbstractPlot, SetBottomPadding, double, bottomPadding, retransform) void AbstractPlot::setBottomPadding(double padding) { Q_D(AbstractPlot); if (padding != d->bottomPadding) - exec(new AbstractPlotSetBottomPaddingCmd(d, padding, ki18n("%1: set vertical padding"))); + exec(new AbstractPlotSetBottomPaddingCmd(d, padding, ki18n("%1: set bottom padding"))); } STD_SETTER_CMD_IMPL_F_S(AbstractPlot, SetSymmetricPadding, bool, symmetricPadding, retransform) diff --git a/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp b/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp --- a/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianCoordinateSystem.cpp @@ -219,6 +219,12 @@ const QRectF pageRect = d->plot->dataRect(); QVector result; bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); + const bool limit = flags & Limit; + const bool noPageClippingY = flags & SuppressPageClippingY; + const double xPage = pageRect.x(); + const double yPage = pageRect.y(); + const double w = pageRect.width(); + const double h = pageRect.height(); for (const auto* xScale : d->xScales) { if (!xScale) continue; @@ -242,8 +248,24 @@ if (!yScale->map(&y)) continue; + if (limit) { + // set to max/min if passed over + if (x < xPage) + x = xPage; + if (x > xPage + w) + x = xPage + w; + + if (y < yPage) + y = yPage; + if (y > yPage + h) + y = yPage + h; + } + + if (noPageClippingY) + y = pageRect.y() + pageRect.height()/2; + const QPointF mappedPoint(x, y); - if (noPageClipping || rectContainsPoint(pageRect, mappedPoint)) + if (noPageClipping || limit || rectContainsPoint(pageRect, mappedPoint)) result.append(mappedPoint); } } @@ -263,6 +285,12 @@ QVector& scenePoints, std::vector& visiblePoints, MappingFlags flags) const { const QRectF pageRect = d->plot->dataRect(); const bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); + const bool limit = flags & Limit; + const bool noPageClippingY = flags & SuppressPageClippingY; + const double xPage = pageRect.x(); + const double yPage = pageRect.y(); + const double w = pageRect.width(); + const double h = pageRect.height(); for (const auto* xScale : d->xScales) { if (!xScale) continue; @@ -287,8 +315,24 @@ if (!yScale->map(&y)) continue; + if (limit) { + // set to max/min if passed over + if (x < xPage) + x = xPage; + if (x > xPage + w) + x = xPage + w; + + if (y < yPage) + y = yPage; + if (y > yPage + h) + y = yPage + h; + } + + if (noPageClippingY) + y = yPage + h/2; + const QPointF mappedPoint(x, y); - if (noPageClipping || rectContainsPoint(pageRect, mappedPoint)) { + if (noPageClipping || limit || rectContainsPoint(pageRect, mappedPoint)) { scenePoints.append(mappedPoint); visiblePoints[i].flip(); } @@ -309,6 +353,12 @@ QVector& scenePoints, std::vector& visiblePoints, QVector>& scenePointsUsed, double minLogicalDiffX, double minLogicalDiffY, MappingFlags flags) const { const QRectF pageRect = d->plot->dataRect(); const bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); + const bool limit = flags & Limit; + const bool noPageClippingY = flags & SuppressPageClippingY; + const double xPage = pageRect.x(); + const double yPage = pageRect.y(); + const double w = pageRect.width(); + const double h = pageRect.height(); for (const auto* xScale : d->xScales) { if (!xScale) continue; @@ -333,8 +383,24 @@ if (!yScale->map(&y)) continue; + if (limit) { + // set to max/min if passed over + if (x < xPage) + x = xPage; + if (x > xPage + w) + x = xPage + w; + + if (y < yPage) + y = yPage; + if (y > yPage + h) + y = yPage + h; + } + + if (noPageClippingY) + y = yPage + h/2; + const QPointF mappedPoint(x, y); - if (noPageClipping || rectContainsPoint(pageRect, mappedPoint)) { + if (noPageClipping || limit || rectContainsPoint(pageRect, mappedPoint)) { int indexX = (int)((mappedPoint.x() - d->plot->dataRect().x())*minLogicalDiffX); int indexY = (int)((mappedPoint.y() - d->plot->dataRect().y())*minLogicalDiffY); @@ -353,9 +419,15 @@ QPointF CartesianCoordinateSystem::mapLogicalToScene(QPointF logicalPoint, MappingFlags flags) const { const QRectF pageRect = d->plot->dataRect(); bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); + bool noPageClippingY = flags & SuppressPageClippingY; + bool limit = flags & Limit; double x = logicalPoint.x(); double y = logicalPoint.y(); + const double xPage = pageRect.x(); + const double yPage = pageRect.y(); + const double w = pageRect.width(); + const double h = pageRect.height(); for (const auto* xScale : d->xScales) { if (!xScale) continue; @@ -375,8 +447,24 @@ if (!yScale->map(&y)) continue; + if (limit) { + // set to max/min if passed over + if (x < xPage) + x = xPage; + if (x > xPage + w) + x = xPage + w; + + if (y < yPage) + y = yPage; + if (y > yPage + h) + y = yPage + h; + } + + if (noPageClippingY) + y = pageRect.y() + h/2; + QPointF mappedPoint(x, y); - if (noPageClipping || rectContainsPoint(pageRect, mappedPoint)) + if (noPageClipping || limit || rectContainsPoint(pageRect, mappedPoint)) return mappedPoint; } } @@ -544,14 +632,36 @@ QVector CartesianCoordinateSystem::mapSceneToLogical(const QVector& points, MappingFlags flags) const { QRectF pageRect = d->plot->dataRect(); QVector result; - bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); + const bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); + const bool limit = flags & Limit; + const bool noPageClippingY = flags & SuppressPageClippingY; + const double xPage = pageRect.x(); + const double yPage = pageRect.y(); + const double w = pageRect.width(); + const double h = pageRect.height(); + for (const auto& point : points) { - if (noPageClipping || pageRect.contains(point)) { - bool found = false; + double x = point.x(); + double y = point.y(); + if (limit) { + // set to max/min if passed over + if (x < xPage) + x = xPage; + if (x > xPage + w) + x = xPage + w; + + if (y < yPage) + y = yPage; + if (y > yPage + h) + y = yPage + h; + } + + if (noPageClippingY) + y = yPage + h/2; - double x = point.x(); - double y = point.y(); + if (noPageClipping || limit || pageRect.contains(point)) { + bool found = false; for (const auto* xScale : d->xScales) { if (found) break; @@ -595,8 +705,26 @@ QRectF pageRect = d->plot->dataRect(); QPointF result; bool noPageClipping = pageRect.isNull() || (flags & SuppressPageClipping); + bool limit = flags & Limit; + const bool noPageClippingY = flags & SuppressPageClippingY; + + if (limit) { + // set to max/min if passed over + if (logicalPoint.x() < pageRect.x()) + logicalPoint.setX(pageRect.x()); + if (logicalPoint.x() > pageRect.x() + pageRect.width()) + logicalPoint.setX(pageRect.x() + pageRect.width()); + + if (logicalPoint.y() < pageRect.y()) + logicalPoint.setY(pageRect.y()); + if (logicalPoint.y() > pageRect.y() + pageRect.height()) + logicalPoint.setY(pageRect.y() + pageRect.height()); + } + + if (noPageClippingY) + logicalPoint.setY(pageRect.y() + pageRect.height()/2); - if (noPageClipping || pageRect.contains(logicalPoint)) { + if (noPageClipping || limit || pageRect.contains(logicalPoint)) { double x = logicalPoint.x(); double y = logicalPoint.y(); diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.h b/src/backend/worksheet/plots/cartesian/CartesianPlot.h --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.h @@ -67,7 +67,7 @@ enum RangeFormat {Numeric, DateTime}; enum RangeType {RangeFree, RangeLast, RangeFirst}; enum RangeBreakStyle {RangeBreakSimple, RangeBreakVertical, RangeBreakSloped}; - enum MouseMode {SelectionMode, ZoomSelectionMode, ZoomXSelectionMode, ZoomYSelectionMode}; + enum MouseMode {SelectionMode, ZoomSelectionMode, ZoomXSelectionMode, ZoomYSelectionMode, Cursor}; enum NavigationOperation {ScaleAuto, ScaleAutoX, ScaleAutoY, ZoomIn, ZoomOut, ZoomInX, ZoomOutX, ZoomInY, ZoomOutY, ShiftLeftX, ShiftRightX, ShiftUpY, ShiftDownY }; @@ -109,11 +109,20 @@ void processDropEvent(QDropEvent*) override; bool isPanningActive() const; void addLegend(CartesianPlotLegend*); + int curveCount(); + const XYCurve* getCurve(int index); + double cursorPos(int cursorNumber); void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; void loadThemeConfig(const KConfig&) override; void saveTheme(KConfig& config); + void mousePressZoomSelectionMode(QPointF logicPos); + void mousePressCursorMode(int cursorNumber, QPointF logicPos); + void mouseMoveZoomSelectionMode(QPointF logicPos); + void mouseMoveCursorMode(int cursorNumber, QPointF logicPos); + void mouseReleaseZoomSelectionMode(); + void mouseHoverZoomSelectionMode(QPointF logicPos); BASIC_D_ACCESSOR_DECL(CartesianPlot::RangeFormat, xRangeFormat, XRangeFormat) BASIC_D_ACCESSOR_DECL(CartesianPlot::RangeFormat, yRangeFormat, YRangeFormat) @@ -134,6 +143,9 @@ BASIC_D_ACCESSOR_DECL(bool, yRangeBreakingEnabled, YRangeBreakingEnabled) CLASS_D_ACCESSOR_DECL(RangeBreaks, xRangeBreaks, XRangeBreaks) CLASS_D_ACCESSOR_DECL(RangeBreaks, yRangeBreaks, YRangeBreaks) + CLASS_D_ACCESSOR_DECL(QPen, cursorPen, CursorPen); + CLASS_D_ACCESSOR_DECL(bool, cursor0Enable, Cursor0Enable); + CLASS_D_ACCESSOR_DECL(bool, cursor1Enable, Cursor1Enable); QString theme() const; @@ -191,6 +203,7 @@ QAction* shiftRightXAction; QAction* shiftUpYAction; QAction* shiftDownYAction; + QAction* cursorAction; //analysis menu actions QAction* addDataOperationAction; @@ -244,6 +257,7 @@ void shiftRightX(); void shiftUpY(); void shiftDownY(); + void cursor(); void dataChanged(); private slots: @@ -256,6 +270,8 @@ void yDataChanged(); void curveVisibilityChanged(); + void curveLinePenChanged(QPen); + //SLOTs for changes triggered via QActions in the context menu void visibilityChanged(); void loadTheme(const QString&); @@ -283,6 +299,23 @@ void yRangeBreakingEnabledChanged(bool); void yRangeBreaksChanged(const CartesianPlot::RangeBreaks&); void themeChanged(const QString&); + void mousePressZoomSelectionModeSignal(QPointF logicPos); + void mousePressCursorModeSignal(int cursorNumber, QPointF logicPos); + void mouseMoveZoomSelectionModeSignal(QPointF logicPos); + void mouseMoveCursorModeSignal(int cursorNumber, QPointF logicPos); + void mouseReleaseCursorModeSignal(); + void mouseReleaseZoomSelectionModeSignal(); + void mouseHoverZoomSelectionModeSignal(QPointF logicalPoint); + void cursorPosChanged(int cursorNumber, double xPos); + void curveAdded(const XYCurve*); + void curveRemoved(const XYCurve*); + void curveLinePenChanged(QPen, QString curveName); + void cursorPenChanged(QPen); + void curveDataChanged(const XYCurve*); + void curveVisibilityChangedSignal(); + void mouseModeChanged(); + void cursor0EnableChanged(bool enable); + void cursor1EnableChanged(bool enable); }; #endif diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -70,6 +70,8 @@ #include #include +#include + #include #include #include @@ -529,6 +531,7 @@ 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); + cursorAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"),i18n("Cursor"), this); // TODO: change icon connect(scaleAutoAction, &QAction::triggered, this, &CartesianPlot::scaleAuto); connect(scaleAutoXAction, &QAction::triggered, this, &CartesianPlot::scaleAutoX); @@ -543,6 +546,7 @@ connect(shiftRightXAction, &QAction::triggered, this, &CartesianPlot::shiftRightX); connect(shiftUpYAction, &QAction::triggered, this, &CartesianPlot::shiftUpY); connect(shiftDownYAction, &QAction::triggered, this, &CartesianPlot::shiftDownY); + connect(cursorAction, &QAction::triggered, this, &CartesianPlot::cursor); //visibility action visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this); @@ -812,6 +816,9 @@ 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) /*! @@ -1064,6 +1071,37 @@ 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->cursor0Pos); // 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); @@ -1326,6 +1364,22 @@ this->addChild(point); } +int CartesianPlot::curveCount(){ + return children().length(); +} + +const XYCurve* CartesianPlot::getCurve(int index){ + return children()[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); @@ -1347,6 +1401,7 @@ connect(curve, &XYCurve::symbolsOpacityChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsBrushChanged, this, &CartesianPlot::updateLegend); connect(curve, &XYCurve::symbolsPenChanged, this, &CartesianPlot::updateLegend); + connect(curve, SIGNAL(linePenChanged(QPen)), this, SIGNAL(curveLinePenChanged(QPen))); // feed forward linePenChanged, because Worksheet needs because CursorDock must be updated too updateLegend(); d->curvesXMinMaxIsDirty = true; @@ -1394,6 +1449,8 @@ } } } + emit curveAdded(curve); + } else { const auto* hist = qobject_cast(child); if (hist) { @@ -1469,8 +1526,10 @@ m_legend = nullptr; } else { const auto* curve = qobject_cast(child); - if (curve) + if (curve) { updateLegend(); + emit curveRemoved(curve); + } } } @@ -1580,6 +1639,7 @@ setUndoAware(true); } } + emit curveDataChanged(dynamic_cast(QObject::sender())); } /*! @@ -1623,6 +1683,7 @@ setUndoAware(true); } } + emit curveDataChanged(dynamic_cast(QObject::sender())); } void CartesianPlot::curveVisibilityChanged() { @@ -1636,6 +1697,13 @@ 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) { @@ -1665,6 +1733,8 @@ } else //zoom m_selection graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } + + emit mouseModeChanged(); } void CartesianPlot::setLocked(bool locked) { @@ -2139,6 +2209,38 @@ 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); +} + //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## @@ -2509,6 +2611,7 @@ //############################################################################## void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { + emit q->mousePressZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos())); if (mouseMode == CartesianPlot::ZoomSelectionMode) m_selectionStart = event->pos(); @@ -2522,6 +2625,26 @@ m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; + } else if (mouseMode == CartesianPlot::Cursor){ + 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; @@ -2532,7 +2655,59 @@ } } +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.height()/2); + } 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.width()/2); + 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::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { + if (mouseMode == CartesianPlot::SelectionMode) { if (panningStarted && dataRect.contains(event->pos()) ) { //don't retransform on small mouse movement deltas @@ -2559,46 +2734,78 @@ q->info(QString()); return; } + emit q->mouseMoveZoomSelectionModeSignal(cSystem->mapSceneToLogical(event->pos(), CartesianCoordinateSystem::MappingFlag::Limit)); - QString info; - QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart); - if (mouseMode == CartesianPlot::ZoomSelectionMode) { - m_selectionEnd = event->pos(); - QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); - 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) { - m_selectionEnd.setX(event->pos().x()); - m_selectionEnd.setY(dataRect.y() + dataRect.height()); - QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); - 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.x() + dataRect.width()); - m_selectionEnd.setY(event->pos().y()); - QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); - 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::Cursor) { + QGraphicsItem::mouseMoveEvent(event); + if (!boundingRect().contains(event->pos())) { + q->info(i18n("Not inside of the bounding rect")); + return; } - q->info(info); - update(); + 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.height()/2); + QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); + 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.width()/2); + 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 = cSystem->mapSceneToLogical(m_selectionEnd); + 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) { @@ -2626,34 +2833,38 @@ QGraphicsItem::mouseReleaseEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { - //don't zoom if very small region was selected, avoid occasional/unwanted zooming - if ( qAbs(m_selectionEnd.x()-m_selectionStart.x()) < 20 || qAbs(m_selectionEnd.y()-m_selectionStart.y()) < 20 ) { - m_selectionBandIsShown = false; - return; - } + emit q->mouseReleaseZoomSelectionModeSignal(); + } +} - //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(); - } +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; + } - if (m_selectionEnd.y() > m_selectionStart.y()) { - yMin = logicalZoomEnd.y(); - yMax = logicalZoomStart.y(); - } else { - yMin = logicalZoomStart.y(); - yMax = logicalZoomEnd.y(); - } + //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(); + } - m_selectionBandIsShown = false; - retransformScales(); + if (m_selectionEnd.y() > m_selectionStart.y()) { + yMin = logicalZoomEnd.y(); + yMax = logicalZoomStart.y(); + } else { + yMin = logicalZoomStart.y(); + yMax = logicalZoomEnd.y(); } + + m_selectionBandIsShown = false; + retransformScales(); } void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { @@ -2710,62 +2921,119 @@ info += QString::number(logicalPoint.y()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); + emit q->mouseHoverZoomSelectionModeSignal(logicalPoint); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { - QPointF p1(logicalPoint.x(), yMin); - QPointF p2(logicalPoint.x(), yMax); - m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1)); - m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2)); info = "x="; if (xRangeFormat == CartesianPlot::Numeric) info += QString::number(logicalPoint.x()); else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat); - update(); - } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { - QPointF p1(xMin, logicalPoint.y()); - QPointF p2(xMax, logicalPoint.y()); - m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1)); - m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2)); + 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); - update(); + 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 + 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())){ + 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(); } } q->info(info); QGraphicsItem::hoverMoveEvent(event); } +void CartesianPlotPrivate::mouseHoverZoomSelectionMode(QPointF logicPos) { + 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); + + if (cursor0Enable && cSystem->mapLogicalToScene(cursor0Pos, AbstractCoordinateSystem::MappingFlag::SuppressPageClippingY) != QPointF(0,0)){ + QPointF p1(cursor0Pos.x(),yMin); + p1 = cSystem->mapLogicalToScene(p1); + QPointF p2(cursor0Pos.x(),yMax); + p2 = cSystem->mapLogicalToScene(p2); + painter->drawLine(p1,p2); + painter->drawText(p2, "1"); + } + + if (cursor1Enable && cSystem->mapLogicalToScene(cursor0Pos, AbstractCoordinateSystem::MappingFlag::SuppressPageClippingY) != QPointF(0,0)){ + QPointF p1(cursor1Pos.x(),yMin); + p1 = cSystem->mapLogicalToScene(p1); + QPointF p2(cursor1Pos.x(),yMax); + p2 = cSystem->mapLogicalToScene(p2); + painter->drawText(p2, "2"); + painter->drawLine(p1,p2); + } + + painter->restore(); + } + painter->setPen(QPen(Qt::black, 3)); - if ((mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) + if ((mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) && (!m_selectionBandIsShown)) painter->drawLine(m_selectionStartLine); @@ -2814,6 +3082,10 @@ writer->writeEndElement(); } + //cursor + writer->writeStartElement( "cursor" ); + WRITE_QPEN(d->cursorPen); + writer->writeEndElement(); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->rect.x()) ); @@ -2906,7 +3178,18 @@ } else if (!preview && reader->name() == "theme") { attribs = reader->attributes(); d->theme = attribs.value("name").toString(); - } else if (!preview && reader->name() == "geometry") { + } 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(); diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h --- a/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotPrivate.h @@ -45,6 +45,13 @@ void rangeChanged(); void xRangeFormatChanged(); void yRangeFormatChanged(); + void mouseMoveZoomSelectionMode(QPointF logicalPos); + void mouseMoveCursorMode(int cursorNumber, QPointF logicalPos); + void mouseReleaseZoomSelectionMode(); + void mouseHoverZoomSelectionMode(QPointF logicPos); + void mousePressZoomSelectionMode(QPointF logicalPos); + void mousePressCursorMode(int cursorNumber, QPointF logicalPos); + void updateCursor(); QRectF dataRect; CartesianPlot::RangeType rangeType{CartesianPlot::RangeFree}; @@ -75,6 +82,17 @@ bool suppressRetransform{false}; bool panningStarted{false}; bool locked{false}; + // Cursor + bool cursor0Enable{false}; + int selectedCursor{0}; + QPointF cursor0Pos{QPointF(NAN, NAN)}; + bool cursor1Enable{false}; + QPointF cursor1Pos{QPointF(NAN, NAN)}; + QPen cursorPen{QPen(Qt::red, 5, Qt::SolidLine)}; + +signals: + void mousePressZoomSelectionModeSignal(QPointF logicalPos); + void mousePressCursorModeSignal(QPointF logicalPos); private: QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCurve.cpp @@ -2089,6 +2089,10 @@ * @return */ double XYCurve::y(double x, bool &valueFound) const { + if (!yColumn()) { + valueFound = false; + return NAN; + } AbstractColumn::ColumnMode yColumnMode = yColumn()->columnMode(); int index = indexForX(x); @@ -2163,7 +2167,7 @@ double value = xColumn()->valueAt(index); if (higherIndex - lowerIndex < 2) { - if (abs(xColumn()->valueAt(lowerIndex) - x) < abs(xColumn()->valueAt(higherIndex) - x)) + if (qAbs(xColumn()->valueAt(lowerIndex) - x) < qAbs(xColumn()->valueAt(higherIndex) - x)) index = lowerIndex; else index = higherIndex; @@ -2217,46 +2221,40 @@ return -1; } else { // naiv way + int index = 0; if ((xColumnMode == AbstractColumn::ColumnMode::Numeric || xColumnMode == AbstractColumn::ColumnMode::Integer)) { for (int row = 0; row < rowCount; row++) { if (xColumn()->isValid(row)) { if (row == 0) prevValue = xColumn()->valueAt(row); double value = xColumn()->valueAt(row); - if (abs(value - x) <= abs(prevValue - x)) { // <= prevents also that row - 1 become < 0 - if (row < rowCount - 1) + if (qAbs(value - x) <= qAbs(prevValue - x)) { // <= prevents also that row - 1 become < 0 prevValue = value; - else { - return row; - } - }else{ - return row-1; + index = row; } } } + return index; } else if ((xColumnMode == AbstractColumn::ColumnMode::DateTime || xColumnMode == AbstractColumn::ColumnMode::Month || xColumnMode == AbstractColumn::ColumnMode::Day)) { qint64 xInt64 = static_cast(x); + int index = 0; for (int row = 0; row < rowCount; row++) { if (xColumn()->isValid(row)) { if (row == 0) prevValueDateTime = xColumn()->dateTimeAt(row).toMSecsSinceEpoch(); qint64 value = xColumn()->dateTimeAt(row).toMSecsSinceEpoch(); if (abs(value - xInt64) <= abs(prevValueDateTime - xInt64)) { // "<=" prevents also that row - 1 become < 0 - if (row < rowCount - 1) prevValueDateTime = value; - else - return row; - } else { - return row - 1; + index = row; } } } - + return index; } } return -1; @@ -2292,7 +2290,7 @@ double value = column[index]; if (higherIndex - lowerIndex < 2) { - if (abs(column[lowerIndex] - x) < abs(column[higherIndex] - x)) + if (qAbs(column[lowerIndex] - x) < qAbs(column[higherIndex] - x)) index = lowerIndex; else index = higherIndex; @@ -2316,19 +2314,17 @@ } else { // AbstractColumn::Properties::No // naiv way + int index = 0; prevValue = column[0]; for (int row = 0; row < rowCount; row++) { double value = column[row]; - if (abs(value - x) <= abs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 - if (row < rowCount - 1) + if (qAbs(value - x) <= qAbs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 prevValue = value; - else - return row; - } else { - return row - 1; + index = row; } } + return index; } return -1; } @@ -2364,7 +2360,7 @@ double value = points[index].x(); if (higherIndex - lowerIndex < 2) { - if (abs(points[lowerIndex].x() - x) < abs(points[higherIndex].x() - x)) + if (qAbs(points[lowerIndex].x() - x) < qAbs(points[higherIndex].x() - x)) index = lowerIndex; else index = higherIndex; @@ -2389,18 +2385,16 @@ // 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 (abs(value - x) <= abs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 - if (row < rowCount - 1) + if (qAbs(value - x) <= qAbs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 prevValue = value; - else - return row; - } else { - return row - 1; + index = row; } } + return index; } return -1; } @@ -2435,7 +2429,7 @@ double value = lines[index].p1().x(); if (higherIndex - lowerIndex < 2) { - if (abs(lines[lowerIndex].p1().x() - x) < abs(lines[higherIndex].p1().x() - x)) + if (qAbs(lines[lowerIndex].p1().x() - x) < qAbs(lines[higherIndex].p1().x() - x)) index = lowerIndex; else index = higherIndex; @@ -2459,18 +2453,16 @@ } 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 (abs(value - x) <= abs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 - if (row < rowCount - 1) - prevValue = value; - else - return row; - } else { - return row - 1; + if (qAbs(value - x) <= qAbs(prevValue - x)) { // "<=" prevents also that row - 1 become < 0 + prevValue = value; + index = row; } } + return index; } return -1; } @@ -2612,7 +2604,7 @@ double dx1m = pos.x() - p1.x(); double dy1m = pos.y() - p1.y(); - double dist_segm = abs(dx1m*unitvec.y() - dy1m*unitvec.x()); + double dist_segm = qAbs(dx1m*unitvec.y() - dy1m*unitvec.x()); double scalarProduct = dx1m*unitvec.x() + dy1m*unitvec.y(); if (scalarProduct > 0) { diff --git a/src/commonfrontend/ProjectExplorer.h b/src/commonfrontend/ProjectExplorer.h --- a/src/commonfrontend/ProjectExplorer.h +++ b/src/commonfrontend/ProjectExplorer.h @@ -56,6 +56,7 @@ void setModel(AspectTreeModel*); void setProject(Project*); QModelIndex currentIndex() const; + void updateSelectedAspects(); private: void createActions(); diff --git a/src/commonfrontend/ProjectExplorer.cpp b/src/commonfrontend/ProjectExplorer.cpp --- a/src/commonfrontend/ProjectExplorer.cpp +++ b/src/commonfrontend/ProjectExplorer.cpp @@ -620,6 +620,19 @@ emit selectedAspectsChanged(selectedAspects); } +/*! + * Used to udpate the cursor Dock + */ +void ProjectExplorer::updateSelectedAspects() { + QModelIndexList items = m_treeView->selectionModel()->selectedRows(); + QList selectedAspects; + for (const QModelIndex& index : items) { + selectedAspects << static_cast(index.internalPointer()); + } + + emit selectedAspectsChanged(selectedAspects); +} + void ProjectExplorer::expandSelected() { const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes(); for (const auto& index : items) diff --git a/src/commonfrontend/worksheet/WorksheetView.h b/src/commonfrontend/worksheet/WorksheetView.h --- a/src/commonfrontend/worksheet/WorksheetView.h +++ b/src/commonfrontend/worksheet/WorksheetView.h @@ -62,19 +62,21 @@ double opacity; }; + enum MouseMode {SelectionMode, NavigationMode, ZoomSelectionMode}; + void setScene(QGraphicsScene*); void exportToFile(const QString&, const ExportFormat, const ExportArea, const bool, const int); void exportToClipboard(); void setIsClosing(); void setIsBeingPresented(bool presenting); void setCartesianPlotActionMode(Worksheet::CartesianPlotActionMode mode); + void setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode mode); void setPlotLock(bool lock); + Worksheet::CartesianPlotActionMode getCartesianPlotActionMode(); void registerShortcuts(); void unregisterShortcuts(); - private: - enum MouseMode {SelectionMode, NavigationMode, ZoomSelectionMode}; void initBasicActions(); void initActions(); @@ -103,7 +105,6 @@ Worksheet* m_worksheet; MouseMode m_mouseMode{SelectionMode}; - Worksheet::CartesianPlotActionMode m_cartesianPlotActionMode{Worksheet::CartesianPlotActionMode::ApplyActionToSelection}; CartesianPlot::MouseMode m_cartesianPlotMouseMode{CartesianPlot::SelectionMode}; bool m_selectionBandIsShown{false}; QPoint m_selectionStart; @@ -134,6 +135,7 @@ QMenu* m_cartesianPlotAddNewMenu{nullptr}; QMenu* m_cartesianPlotZoomMenu{nullptr}; QMenu* m_cartesianPlotActionModeMenu{nullptr}; + QMenu* m_cartesianPlotCursorModeMenu{nullptr}; QMenu* m_dataManipulationMenu{nullptr}; QToolButton* tbNewCartesianPlot{nullptr}; @@ -191,10 +193,13 @@ //Actions for cartesian plots QAction* cartesianPlotApplyToSelectionAction; QAction* cartesianPlotApplyToAllAction; + QAction* cartesianPlotApplyToAllCursor; + QAction* cartesianPlotApplyToSelectionCursor; QAction* cartesianPlotSelectionModeAction; QAction* cartesianPlotZoomSelectionModeAction; QAction* cartesianPlotZoomXSelectionModeAction; QAction* cartesianPlotZoomYSelectionModeAction; + QAction* cartesianPlotCursorModeAction; QAction* addCurveAction; QAction* addHistogramAction; @@ -281,6 +286,7 @@ //SLOTs for cartesian plots void cartesianPlotActionModeChanged(QAction*); + void cartesianPlotCursorModeChanged(QAction*); void cartesianPlotMouseModeChanged(QAction*); void cartesianPlotNavigationChanged(QAction*); void cartesianPlotAddNew(QAction*); diff --git a/src/commonfrontend/worksheet/WorksheetView.cpp b/src/commonfrontend/worksheet/WorksheetView.cpp --- a/src/commonfrontend/worksheet/WorksheetView.cpp +++ b/src/commonfrontend/worksheet/WorksheetView.cpp @@ -271,6 +271,17 @@ setCartesianPlotActionMode(m_worksheet->cartesianPlotActionMode()); connect(cartesianPlotActionModeActionGroup, &QActionGroup::triggered, this, &WorksheetView::cartesianPlotActionModeChanged); + // cursor apply to all/selected + auto* cartesianPlotActionCursorGroup = new QActionGroup(this); + cartesianPlotActionCursorGroup->setExclusive(true); + cartesianPlotApplyToSelectionCursor = new QAction(i18n("Selected Plots"), cartesianPlotActionCursorGroup); + cartesianPlotApplyToSelectionCursor->setCheckable(true); + cartesianPlotApplyToAllCursor = new QAction(i18n("All Plots"), cartesianPlotActionCursorGroup); + cartesianPlotApplyToAllCursor->setCheckable(true); + setCartesianPlotCursorMode(m_worksheet->cartesianPlotCursorMode()); + connect(cartesianPlotActionCursorGroup, SIGNAL(triggered(QAction*)), SLOT(cartesianPlotCursorModeChanged(QAction*))); + + auto* cartesianPlotMouseModeActionGroup = new QActionGroup(this); cartesianPlotMouseModeActionGroup->setExclusive(true); cartesianPlotSelectionModeAction = new QAction(QIcon::fromTheme("labplot-cursor-arrow"), i18n("Select and Edit"), cartesianPlotMouseModeActionGroup); @@ -286,7 +297,11 @@ cartesianPlotZoomYSelectionModeAction = new QAction(QIcon::fromTheme("labplot-zoom-select-y"), i18n("Select y-region and Zoom In"), cartesianPlotMouseModeActionGroup); cartesianPlotZoomYSelectionModeAction->setCheckable(true); - connect(cartesianPlotMouseModeActionGroup, &QActionGroup::triggered, this, &WorksheetView::cartesianPlotMouseModeChanged); + // TODO: change ICON + cartesianPlotCursorModeAction = new QAction(QIcon::fromTheme("labplot-cursor"),i18n("Cursor"), cartesianPlotMouseModeActionGroup); + cartesianPlotCursorModeAction->setCheckable(true); + + connect(cartesianPlotMouseModeActionGroup, SIGNAL(triggered(QAction*)), SLOT(cartesianPlotMouseModeChanged(QAction*))); auto* cartesianPlotAddNewActionGroup = new QActionGroup(this); addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), cartesianPlotAddNewActionGroup); @@ -447,6 +462,8 @@ m_cartesianPlotMouseModeMenu->addAction(cartesianPlotZoomXSelectionModeAction); m_cartesianPlotMouseModeMenu->addAction(cartesianPlotZoomYSelectionModeAction); m_cartesianPlotMouseModeMenu->addSeparator(); + m_cartesianPlotMouseModeMenu->addAction(cartesianPlotCursorModeAction); + m_cartesianPlotMouseModeMenu->addSeparator(); m_cartesianPlotAddNewMenu = new QMenu(i18n("Add New"), this); m_cartesianPlotAddNewMenu->setIcon(QIcon::fromTheme("list-add")); @@ -497,11 +514,16 @@ m_cartesianPlotActionModeMenu->addAction(cartesianPlotApplyToSelectionAction); m_cartesianPlotActionModeMenu->addAction(cartesianPlotApplyToAllAction); + m_cartesianPlotCursorModeMenu = new QMenu(i18n("Apply Cursor to"), this); + m_cartesianPlotCursorModeMenu->addAction(cartesianPlotApplyToSelectionCursor); + m_cartesianPlotCursorModeMenu->addAction(cartesianPlotApplyToAllCursor); + m_cartesianPlotMenu->addMenu(m_cartesianPlotMouseModeMenu); m_cartesianPlotMenu->addMenu(m_cartesianPlotAddNewMenu); m_cartesianPlotMenu->addMenu(m_cartesianPlotZoomMenu); m_cartesianPlotMenu->addSeparator(); m_cartesianPlotMenu->addMenu(m_cartesianPlotActionModeMenu); + m_cartesianPlotMenu->addMenu(m_cartesianPlotCursorModeMenu); m_cartesianPlotMenu->addAction(plotsLockedAction); // Data manipulation menu @@ -618,6 +640,7 @@ toolBar->addAction(cartesianPlotZoomSelectionModeAction); toolBar->addAction(cartesianPlotZoomXSelectionModeAction); toolBar->addAction(cartesianPlotZoomYSelectionModeAction); + toolBar->addAction(cartesianPlotCursorModeAction); toolBar->addSeparator(); toolBar->addAction(addCurveAction); toolBar->addAction(addHistogramAction); @@ -653,6 +676,7 @@ toolBar->addAction(shiftRightXAction); toolBar->addAction(shiftUpYAction); toolBar->addAction(shiftDownYAction); + toolBar->addSeparator(); handleCartesianPlotActions(); } @@ -672,6 +696,13 @@ cartesianPlotApplyToSelectionAction->setChecked(true); } +void WorksheetView::setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode mode) { + if (mode == Worksheet::CartesianPlotActionMode::ApplyActionToAll) + cartesianPlotApplyToAllCursor->setChecked(true); + else + cartesianPlotApplyToSelectionCursor->setChecked(true); +} + void WorksheetView::setPlotLock(bool lock) { plotsLockedAction->setChecked(lock); } @@ -1516,6 +1547,7 @@ cartesianPlotZoomSelectionModeAction->setEnabled(plot); cartesianPlotZoomXSelectionModeAction->setEnabled(plot); cartesianPlotZoomYSelectionModeAction->setEnabled(plot); + cartesianPlotCursorModeAction->setEnabled(plot); addCurveAction->setEnabled(plot); addHistogramAction->setEnabled(plot); @@ -1763,6 +1795,15 @@ handleCartesianPlotActions(); } +void WorksheetView::cartesianPlotCursorModeChanged(QAction* action) { + if (action == cartesianPlotApplyToSelectionCursor) + m_worksheet->setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode::ApplyActionToSelection); + else + m_worksheet->setCartesianPlotCursorMode(Worksheet::CartesianPlotActionMode::ApplyActionToAll); + + handleCartesianPlotActions(); +} + void WorksheetView::plotsLockedActionChanged(bool checked) { m_worksheet->setPlotsLocked(checked); } @@ -1776,6 +1817,8 @@ m_cartesianPlotMouseMode = CartesianPlot::ZoomXSelectionMode; else if (action == cartesianPlotZoomYSelectionModeAction) m_cartesianPlotMouseMode = CartesianPlot::ZoomYSelectionMode; + else if (action == cartesianPlotCursorModeAction) + m_cartesianPlotMouseMode = CartesianPlot::Cursor; for (auto* plot : m_worksheet->children() ) plot->setMouseMode(m_cartesianPlotMouseMode); @@ -1885,6 +1928,10 @@ } } +Worksheet::CartesianPlotActionMode WorksheetView::getCartesianPlotActionMode(){ + return m_worksheet->cartesianPlotActionMode(); +} + void WorksheetView::presenterMode() { KConfigGroup group = KSharedConfig::openConfig()->group("Settings_Worksheet"); diff --git a/src/kdefrontend/GuiObserver.cpp b/src/kdefrontend/GuiObserver.cpp --- a/src/kdefrontend/GuiObserver.cpp +++ b/src/kdefrontend/GuiObserver.cpp @@ -58,6 +58,7 @@ #include "kdefrontend/MainWin.h" #include "kdefrontend/dockwidgets/AxisDock.h" #include "kdefrontend/dockwidgets/NoteDock.h" +#include "kdefrontend/dockwidgets/CursorDock.h" #include "kdefrontend/dockwidgets/CartesianPlotDock.h" #include "kdefrontend/dockwidgets/CartesianPlotLegendDock.h" #include "kdefrontend/dockwidgets/ColumnDock.h" @@ -171,6 +172,27 @@ return; } + // update cursor dock + AbstractAspect* parent = selectedAspects[0]->parent(AspectType::Worksheet); + if (selectedAspects[0]->inherits(AspectType::Worksheet)) { + Worksheet* worksheet = static_cast(selectedAspects[0]); + + if (m_mainWindow->cursorWidget) { + m_mainWindow->cursorWidget->setCursorTreeViewModel(worksheet->cursorModel()); + QVector plots = worksheet->children(); + if (!plots.isEmpty()) + m_mainWindow->cursorWidget->setPlots(plots); + } + } else if (parent) { + if (m_mainWindow->cursorWidget) { + Worksheet* worksheet = static_cast(parent); + QVector plots = worksheet->children(); + m_mainWindow->cursorWidget->setCursorTreeViewModel(worksheet->cursorModel()); + m_mainWindow->cursorWidget->setPlots(plots); + } + + } + const AspectType type = selectedAspects.front()->type(); // Check, whether objects of different types were selected. @@ -198,17 +220,22 @@ raiseDockConnect(m_mainWindow->matrixDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->matrixDock->setMatrices(castList(selectedAspects)); break; - case AspectType::Worksheet: + case AspectType::Worksheet: { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Worksheet")); + if (!m_mainWindow->worksheetDock) { + m_mainWindow->worksheetDock = new WorksheetDock(m_mainWindow->stackedWidget); + connect(m_mainWindow->worksheetDock, SIGNAL(info(QString)), m_mainWindow->statusBar(), SLOT(showMessage(QString))); + m_mainWindow->stackedWidget->addWidget(m_mainWindow->worksheetDock); + } raiseDockConnect(m_mainWindow->worksheetDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->worksheetDock->setWorksheets(castList(selectedAspects)); break; - case AspectType::CartesianPlot: + } case AspectType::CartesianPlot: { m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Cartesian Plot")); raiseDockConnect(m_mainWindow->cartesianPlotDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->cartesianPlotDock->setPlots(castList(selectedAspects)); break; - case AspectType::CartesianPlotLegend: + } case AspectType::CartesianPlotLegend: m_mainWindow->m_propertiesDock->setWindowTitle(i18nc("@title:window", "Legend")); raiseDockConnect(m_mainWindow->cartesianPlotLegendDock, m_mainWindow->statusBar(), m_mainWindow->stackedWidget); m_mainWindow->cartesianPlotLegendDock->setLegends(castList(selectedAspects)); diff --git a/src/kdefrontend/MainWin.h b/src/kdefrontend/MainWin.h --- a/src/kdefrontend/MainWin.h +++ b/src/kdefrontend/MainWin.h @@ -45,6 +45,7 @@ class Matrix; class GuiObserver; class AxisDock; +class CursorDock; class NoteDock; class CartesianPlotDock; class HistogramDock; @@ -73,6 +74,7 @@ class DatapickerImageWidget; class DatapickerCurveWidget; class MemoryWidget; +class CartesianPlot; #ifdef HAVE_CANTOR_LIBS class CantorWorksheet; @@ -87,6 +89,7 @@ class QMdiSubWindow; class QToolButton; class KRecentFilesAction; +class TreeModel; class MainWin : public KXmlGuiWindow { Q_OBJECT @@ -186,6 +189,8 @@ //Docks QStackedWidget* stackedWidget; AxisDock* axisDock{nullptr}; + QDockWidget* cursorDock{nullptr}; + CursorDock* cursorWidget{nullptr}; NoteDock* notesDock{nullptr}; CartesianPlotDock* cartesianPlotDock{nullptr}; CartesianPlotLegendDock* cartesianPlotLegendDock{nullptr}; @@ -241,7 +246,8 @@ void closeEvent(QCloseEvent*) override; void dragEnterEvent(QDragEnterEvent*) override; void dropEvent(QDropEvent*) override; - +public slots: + void showCursorDock(TreeModel* model, QVector plots); private slots: void initGUI(const QString&); void updateGUI(); diff --git a/src/kdefrontend/MainWin.cpp b/src/kdefrontend/MainWin.cpp --- a/src/kdefrontend/MainWin.cpp +++ b/src/kdefrontend/MainWin.cpp @@ -46,6 +46,8 @@ #include "backend/datapicker/Datapicker.h" #include "backend/note/Note.h" #include "backend/lib/macros.h" +#include "backend/worksheet/TreeModel.h" +#include "backend/worksheet/plots/cartesian/CartesianPlot.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" @@ -67,6 +69,7 @@ #include "kdefrontend/datasources/ImportFileDialog.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" @@ -166,6 +169,28 @@ return m_aspectTreeModel; } +/*! + * Show cursor dock and set the treeview model + * @param model + */ +void MainWin::showCursorDock(TreeModel* model, QVector plots) { + if (!cursorDock) { + cursorDock = new QDockWidget(i18n("Cursor"), this); + cursorWidget = new CursorDock(cursorDock); + cursorDock->setWidget(cursorWidget); + cursorDock->setFloating(true); + + // does not work. Don't understand why +// if (m_propertiesDock) +// tabifyDockWidget(cursorDock, m_propertiesDock); +// else + addDockWidget(Qt::DockWidgetArea::AllDockWidgetAreas, cursorDock); + } + cursorWidget->setCursorTreeViewModel(model); + cursorWidget->setPlots(plots); + cursorDock->show(); +} + Project* MainWin::project() const { return m_project; } @@ -1048,6 +1073,10 @@ m_autoSaveTimer.stop(); } + removeDockWidget(cursorDock); + delete cursorDock; + cursorDock = nullptr; + cursorWidget = nullptr; // is deleted, because it's the cild of cursorDock return true; } @@ -1433,6 +1462,10 @@ 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::showCursorDock, this, &MainWin::showCursorDock); } void MainWin::handleAspectRemoved(const AbstractAspect* parent,const AbstractAspect* before,const AbstractAspect* aspect) { diff --git a/src/kdefrontend/dockwidgets/CartesianPlotDock.h b/src/kdefrontend/dockwidgets/CartesianPlotDock.h --- a/src/kdefrontend/dockwidgets/CartesianPlotDock.h +++ b/src/kdefrontend/dockwidgets/CartesianPlotDock.h @@ -129,6 +129,11 @@ void verticalPaddingChanged(double); void bottomPaddingChanged(double); + // "Cursor"-tab + void cursorLineWidthChanged(int width); + void cursorLineColorChanged(QColor color); + void cursorLineStyleChanged(int index); + //SLOTs for changes triggered in CartesianPlot //general void plotDescriptionChanged(const AbstractAspect*); @@ -176,6 +181,9 @@ void plotBottomPaddingChanged(double); void plotSymmetricPaddingChanged(bool); + // Cursor + void plotCursorPenChanged(QPen); + //save/load template void loadConfigFromTemplate(KConfig&); void saveConfigAsTemplate(KConfig&); diff --git a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp --- a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp +++ b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp @@ -98,6 +98,12 @@ layout->setVerticalSpacing(2); } + // "Cursor"-tab + QStringList list = {i18n("NoPen"), i18n("SolidLine"), i18n("DashLine"), i18n("DotLine"), i18n("DashDotLine"), i18n("DashDotDotLine")}; + ui.cbCursorLineStyle->clear(); + for (int i = 0; i < list.count(); i++) + ui.cbCursorLineStyle->addItem(list[i], i); + //Validators ui.leRangeFirst->setValidator( new QIntValidator(ui.leRangeFirst) ); ui.leRangeLast->setValidator( new QIntValidator(ui.leRangeLast) ); @@ -181,6 +187,12 @@ connect( ui.sbPaddingBottom, QOverload::of(&QDoubleSpinBox::valueChanged), this, &CartesianPlotDock::bottomPaddingChanged); connect( ui.cbPaddingSymmetric, &QCheckBox::toggled, this, &CartesianPlotDock::symmetricPaddingChanged); + // Cursor + connect(ui.sbCursorLineWidth, SIGNAL(valueChanged(int)), this, SLOT(cursorLineWidthChanged(int))); + //connect(ui.sbCursorLineWidth, qOverload(&QDoubleSpinBox::valueChanged), this, &CartesianPlotDock::cursorLineWidthChanged); + connect(ui.kcbCursorLineColor, &KColorButton::changed, this, &CartesianPlotDock::cursorLineColorChanged); + //connect(ui.cbCursorLineStyle, qOverload(&QComboBox::currentIndexChanged), this, &CartesianPlotDock::cursorLineStyleChanged); + connect(ui.cbCursorLineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(cursorLineStyleChanged(int))); //theme and template handlers auto* frame = new QFrame(this); auto* layout = new QHBoxLayout(frame); @@ -1230,6 +1242,42 @@ plot->setBottomPadding(padding); } +void CartesianPlotDock::cursorLineWidthChanged(int width) { + if (m_initializing) + return; + + for (auto* plot : m_plotList) { + QPen pen = plot->cursorPen(); + pen.setWidth(width); + plot->setCursorPen(pen); + } +} + +void CartesianPlotDock::cursorLineColorChanged(QColor color) { + if (m_initializing) + return; + + for (auto* plot : m_plotList) { + QPen pen = plot->cursorPen(); + pen.setColor(color); + plot->setCursorPen(pen); + } +} + +void CartesianPlotDock::cursorLineStyleChanged(int index) { + if (m_initializing) + return; + + if (index > 5) + return; + + for (auto* plot : m_plotList) { + QPen pen = plot->cursorPen(); + pen.setStyle(static_cast(index)); + plot->setCursorPen(pen); + } +} + //************************************************************* //****** SLOTs for changes triggered in CartesianPlot ********* //************************************************************* @@ -1477,6 +1525,14 @@ m_initializing = false; } +void CartesianPlotDock::plotCursorPenChanged(QPen pen) { + m_initializing = true; + ui.sbCursorLineWidth->setValue(pen.width()); + ui.kcbCursorLineColor->setColor(pen.color()); + ui.cbCursorLineStyle->setCurrentIndex(pen.style()); + m_initializing = false; +} + //************************************************************* //******************** SETTINGS ******************************* //************************************************************* @@ -1601,6 +1657,12 @@ ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(m_plot->plotArea()->borderCornerRadius(), Worksheet::Centimeter) ); ui.sbBorderOpacity->setValue( round(m_plot->plotArea()->borderOpacity()*100) ); GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); + + // Cursor + QPen pen = m_plot->cursorPen(); + ui.cbCursorLineStyle->setCurrentIndex(pen.style()); + ui.kcbCursorLineColor->setColor(pen.color()); + ui.sbCursorLineWidth->setValue(pen.width()); } void CartesianPlotDock::loadConfig(KConfig& config) { diff --git a/src/kdefrontend/dockwidgets/CursorDock.h b/src/kdefrontend/dockwidgets/CursorDock.h new file mode 100644 --- /dev/null +++ b/src/kdefrontend/dockwidgets/CursorDock.h @@ -0,0 +1,70 @@ +/*************************************************************************** +File : CursorDock.cpp +Project : LabPlot +Description : This dock represents the data from the cursors in the cartesian plots +-------------------------------------------------------------------- +Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@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 * +* * +***************************************************************************/ + +#ifndef CURSORDOCK_H +#define CURSORDOCK_H + +#include + +namespace Ui { +class CursorDock; +} +class CartesianPlot; +class TreeModel; + +/*! + * \brief The CursorDock class + * This class represents the data from the cursors from the cartesian plots in a treeview + */ +class CursorDock : public QWidget +{ + Q_OBJECT + +public: + explicit CursorDock(QWidget *parent = nullptr); + ~CursorDock(); + void setCursorTreeViewModel(TreeModel* model); + void setPlots(QVector); +public slots: + void plotCursor0EnableChanged(bool enable); + void plotCursor1EnableChanged(bool enable); +private: + void collapseAll(); + void expandAll(); + void cursor0EnableChanged(bool enable); + void cursor1EnableChanged(bool enable); + + Ui::CursorDock *ui; + +private: + bool m_initializing{false}; + QVector m_plotList; + CartesianPlot* m_plot{nullptr}; +}; + +#endif // CURSORDOCK_H diff --git a/src/kdefrontend/dockwidgets/CursorDock.cpp b/src/kdefrontend/dockwidgets/CursorDock.cpp new file mode 100644 --- /dev/null +++ b/src/kdefrontend/dockwidgets/CursorDock.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** +File : CursorDock.cpp +Project : LabPlot +Description : This dock represents the data from the cursors in the cartesian plots +-------------------------------------------------------------------- +Copyright : (C) 2019 Martin Marmsoler (martin.marmsoler@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 "CursorDock.h" +#include "ui_cursordock.h" +#include "backend/worksheet/Worksheet.h" +#include "backend/worksheet/WorksheetPrivate.h" +#include "backend/worksheet/plots/cartesian/CartesianPlot.h" +#include "backend/worksheet/plots/cartesian/XYCurve.h" +#include "backend/worksheet/TreeModel.h" + +CursorDock::CursorDock(QWidget *parent) : + QWidget(parent), + ui(new Ui::CursorDock) +{ + ui->setupUi(this); + ui->tvCursorData->setModel(nullptr); + + connect(ui->bCollapseAll, &QPushButton::clicked, this, &CursorDock::collapseAll); + connect(ui->bExpandAll, &QPushButton::clicked, this, &CursorDock::expandAll); + + connect(ui->cbCursor0en, &QCheckBox::clicked, this, &CursorDock::cursor0EnableChanged); + connect(ui->cbCursor1en, &QCheckBox::clicked, this, &CursorDock::cursor1EnableChanged); +} + +void CursorDock::setPlots(QVector list) { + m_initializing = true; + m_plotList = list; + m_plot = list.first(); + + bool cursor0Enabled = m_plot->cursor0Enable(); + bool cursor1Enabled = m_plot->cursor1Enable(); + ui->cbCursor0en->setChecked(cursor0Enabled); + ui->cbCursor1en->setChecked(cursor1Enabled); + + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSOR0, !cursor0Enabled); + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSOR1, !cursor1Enabled); + if (!cursor0Enabled) + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !cursor0Enabled); + else if(cursor1Enabled) + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !cursor0Enabled); + else + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, true); + + ui->tvCursorData->expandAll(); + + connect(m_plot, &CartesianPlot::cursor0EnableChanged, this, &CursorDock::plotCursor0EnableChanged); + connect(m_plot, &CartesianPlot::cursor1EnableChanged, this, &CursorDock::plotCursor1EnableChanged); + + m_initializing = false; +} + +CursorDock::~CursorDock() +{ + delete ui; +} + +void CursorDock::collapseAll() { + ui->tvCursorData->collapseAll(); +} + +void CursorDock::expandAll() { + ui->tvCursorData->expandAll(); +} + +void CursorDock::setCursorTreeViewModel(TreeModel* model) { + ui->tvCursorData->setModel(model); +} + +void CursorDock::cursor0EnableChanged(bool enable) { + if (m_initializing) + return; + + for (auto* plot : m_plotList) + plot->setCursor0Enable(enable); +} + +void CursorDock::cursor1EnableChanged(bool enable) { + if (m_initializing) + return; + + for (auto* plot : m_plotList) + plot->setCursor1Enable(enable); +} + +// ############################################################# +// back from plot +// ############################################################# +void CursorDock::plotCursor0EnableChanged(bool enable) { + m_initializing = true; + ui->cbCursor0en->setChecked(enable); + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSOR0, !enable); + if (!enable) + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !enable); + else if(ui->cbCursor1en->isChecked()) + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !enable); + m_initializing = false; +} + +void CursorDock::plotCursor1EnableChanged(bool enable) { + m_initializing = true; + ui->cbCursor1en->setChecked(enable); + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSOR1, !enable); + if (!enable) + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !enable); + else if(ui->cbCursor0en->isChecked()) + ui->tvCursorData->setColumnHidden(WorksheetPrivate::TreeModelColumn::CURSORDIFF, !enable); + m_initializing = false; +} + diff --git a/src/kdefrontend/dockwidgets/XYCurveDock.cpp b/src/kdefrontend/dockwidgets/XYCurveDock.cpp --- a/src/kdefrontend/dockwidgets/XYCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYCurveDock.cpp @@ -210,9 +210,11 @@ 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 @@ -505,6 +507,24 @@ 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")+ " \""+ m_curve->xColumnPath() + "\"\n"+ i18n("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.")); + 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")+ " \""+ m_curve->xColumnPath() + "\"\n"+ i18n("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.")); + cbYColumn->setText(path); + } } /*! @@ -744,7 +764,7 @@ return; } - if (column){ + if (column) { // current index text should be used cb->useCurrentIndexText(true); cb->setInvalid(false); diff --git a/src/kdefrontend/ui/dockwidgets/cartesianplotdock.ui b/src/kdefrontend/ui/dockwidgets/cartesianplotdock.ui --- a/src/kdefrontend/ui/dockwidgets/cartesianplotdock.ui +++ b/src/kdefrontend/ui/dockwidgets/cartesianplotdock.ui @@ -11,13 +11,10 @@ - - 0 - - 3 + 4 @@ -1293,6 +1290,81 @@ + + + Cursor + + + + + + Qt::Vertical + + + + 20 + 1238 + + + + + + + + + 0 + 0 + + + + Linecolor + + + + + + + + + + + 0 + 0 + + + + Linewidth + + + + + + + + 0 + 0 + + + + Linestyle + + + + + + + + + + px + + + + + + + + diff --git a/src/kdefrontend/ui/dockwidgets/cursordock.ui b/src/kdefrontend/ui/dockwidgets/cursordock.ui new file mode 100644 --- /dev/null +++ b/src/kdefrontend/ui/dockwidgets/cursordock.ui @@ -0,0 +1,94 @@ + + + CursorDock + + + + 0 + 0 + 831 + 571 + + + + Form + + + + + + + + + + Cursor 1 enabled + + + + + + + Cursor 2 enabled + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Collapse all + + + + + + + Expand all + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + + + + + +