/* Copyright © 2017 Hasan Yavuz Özderya This file is part of serialplot. serialplot 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 3 of the License, or (at your option) any later version. serialplot 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 serialplot. If not, see . */ #include #include #include #include #include "qwt_symbol.h" #include "plot.h" #include "plotmanager.h" #include "utils.h" #include "setting_defines.h" PlotManager::PlotManager(QWidget* plotArea, ChannelInfoModel* infoModel, QObject *parent) : QObject(parent), _plotArea(plotArea), showGridAction("&Grid", this), showMinorGridAction("&Minor Grid", this), unzoomAction("&Unzoom", this), darkBackgroundAction("&Dark Background", this), showLegendAction("&Legend", this), showMultiAction("Multi &Plot", this), setSymbolsAction("Symbols", this) { _autoScaled = true; _yMin = 0; _yMax = 1; _xAxisAsIndex = true; isDemoShown = false; _infoModel = infoModel; _numOfSamples = 1; _plotWidth = 1; showSymbols = Plot::ShowSymbolsAuto; emptyPlot = NULL; // initalize layout and single widget isMulti = false; scrollArea = NULL; setupLayout(isMulti); addPlotWidget(); // initialize menu actions showGridAction.setToolTip("Show Grid"); showMinorGridAction.setToolTip("Show Minor Grid"); unzoomAction.setToolTip("Unzoom the Plot"); darkBackgroundAction.setToolTip("Enable Dark Plot Background"); showLegendAction.setToolTip("Display the Legend on Plot"); showMultiAction.setToolTip("Display All Channels Separately"); setSymbolsAction.setToolTip("Show/Hide symbols"); showGridAction.setShortcut(QKeySequence("G")); showMinorGridAction.setShortcut(QKeySequence("M")); showGridAction.setCheckable(true); showMinorGridAction.setCheckable(true); darkBackgroundAction.setCheckable(true); showLegendAction.setCheckable(true); showMultiAction.setCheckable(true); showGridAction.setChecked(false); showMinorGridAction.setChecked(false); darkBackgroundAction.setChecked(false); showLegendAction.setChecked(true); showMultiAction.setChecked(false); showMinorGridAction.setEnabled(false); // setup symbols menu setSymbolsAutoAct = setSymbolsMenu.addAction("Show When Zoomed"); setSymbolsAutoAct->setCheckable(true); setSymbolsAutoAct->setChecked(true); connect(setSymbolsAutoAct, SELECT::OVERLOAD_OF(&QAction::triggered), [this](bool checked) { if (checked) setSymbols(Plot::ShowSymbolsAuto); }); setSymbolsShowAct = setSymbolsMenu.addAction("Always Show"); setSymbolsShowAct->setCheckable(true); connect(setSymbolsShowAct, SELECT::OVERLOAD_OF(&QAction::triggered), [this](bool checked) { if (checked) setSymbols(Plot::ShowSymbolsShow); }); setSymbolsHideAct = setSymbolsMenu.addAction("Always Hide"); setSymbolsHideAct->setCheckable(true); connect(setSymbolsHideAct, SELECT::OVERLOAD_OF(&QAction::triggered), [this](bool checked) { if (checked) setSymbols(Plot::ShowSymbolsHide); }); setSymbolsAction.setMenu(&setSymbolsMenu); // add symbol actions to same group so that they appear as radio buttons auto group = new QActionGroup(this); group->addAction(setSymbolsAutoAct); group->addAction(setSymbolsShowAct); group->addAction(setSymbolsHideAct); connect(&showGridAction, SELECT::OVERLOAD_OF(&QAction::triggered), this, &PlotManager::showGrid); connect(&showGridAction, SELECT::OVERLOAD_OF(&QAction::triggered), &showMinorGridAction, &QAction::setEnabled); connect(&showMinorGridAction, SELECT::OVERLOAD_OF(&QAction::triggered), this, &PlotManager::showMinorGrid); connect(&unzoomAction, &QAction::triggered, this, &PlotManager::unzoom); connect(&darkBackgroundAction, SELECT::OVERLOAD_OF(&QAction::triggered), this, &PlotManager::darkBackground); connect(&showLegendAction, SELECT::OVERLOAD_OF(&QAction::triggered), this, &PlotManager::showLegend); connect(&showLegendAction, SELECT::OVERLOAD_OF(&QAction::triggered), this, &PlotManager::showLegend); connect(&showMultiAction, SELECT::OVERLOAD_OF(&QAction::triggered), this, &PlotManager::setMulti); // connect to channel info model if (_infoModel != NULL) // TODO: remove when snapshots have infomodel { connect(_infoModel, &QAbstractItemModel::dataChanged, this, &PlotManager::onChannelInfoChanged); connect(_infoModel, &QAbstractItemModel::modelReset, [this]() { onChannelInfoChanged(_infoModel->index(0, 0), // start _infoModel->index(_infoModel->rowCount()-1, 0), // end {}); // roles ignored }); } } PlotManager::~PlotManager() { while (curves.size()) { delete curves.takeLast(); } // remove all widgets while (plotWidgets.size()) { delete plotWidgets.takeLast(); } if (scrollArea != NULL) delete scrollArea; if (emptyPlot != NULL) delete emptyPlot; } void PlotManager::onChannelInfoChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { int start = topLeft.row(); int end = bottomRight.row(); for (int ci = start; ci <= end; ci++) { QString name = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::EditRole).toString(); QColor color = topLeft.sibling(ci, ChannelInfoModel::COLUMN_NAME).data(Qt::ForegroundRole).value(); bool visible = topLeft.sibling(ci, ChannelInfoModel::COLUMN_VISIBILITY).data(Qt::CheckStateRole).toBool(); curves[ci]->setTitle(name); curves[ci]->setPen(color); curves[ci]->setVisible(visible); curves[ci]->setItemAttribute(QwtPlotItem::Legend, visible); // replot only updated widgets if (isMulti) { plotWidgets[ci]->updateSymbols(); // required for color change plotWidgets[ci]->updateLegend(curves[ci]); plotWidgets[ci]->setVisible(visible); if (visible) { plotWidgets[ci]->replot(); } } } checkNoVisChannels(); // replot single widget if (!isMulti) { plotWidgets[0]->updateSymbols(); plotWidgets[0]->updateLegend(); replot(); } } void PlotManager::checkNoVisChannels() { // if all channels are hidden show indicator bool allhidden = std::none_of(curves.cbegin(), curves.cend(), [](QwtPlotCurve* c) {return c->isVisible();}); plotWidgets[0]->showNoChannel(allhidden); if (isMulti) { plotWidgets[0]->showNoChannel(allhidden); plotWidgets[0]->setVisible(true); } } void PlotManager::setMulti(bool enabled) { if (enabled == isMulti) return; isMulti = enabled; // detach all curves for (auto curve : curves) { curve->detach(); } // remove all widgets while (plotWidgets.size()) { delete plotWidgets.takeLast(); } // setup new layout setupLayout(isMulti); if (isMulti) { // add new widgets and attach for (auto curve : curves) { auto plot = addPlotWidget(); plot->setVisible(curve->isVisible()); curve->attach(plot); } } else { // add a single widget auto plot = addPlotWidget(); // attach all curves for (auto curve : curves) { curve->attach(plot); } } checkNoVisChannels(); } void PlotManager::setupLayout(bool multiPlot) { // delete previous layout if it exists if (_plotArea->layout() != 0) { delete _plotArea->layout(); } if (multiPlot) { // setup a scroll area scrollArea = new QScrollArea(); auto scrolledPlotArea = new QWidget(scrollArea); scrollArea->setWidget(scrolledPlotArea); scrollArea->setWidgetResizable(true); _plotArea->setLayout(new QVBoxLayout()); _plotArea->layout()->addWidget(scrollArea); _plotArea->layout()->setContentsMargins(0,0,0,0); layout = new QVBoxLayout(scrolledPlotArea); } else { // delete scrollArea left from multi layout if (scrollArea != NULL) { delete scrollArea; scrollArea = NULL; } layout = new QVBoxLayout(_plotArea); } layout->setContentsMargins(2,2,2,2); layout->setSpacing(1); } Plot* PlotManager::addPlotWidget() { auto plot = new Plot(); plotWidgets.append(plot); layout->addWidget(plot); plot->darkBackground(darkBackgroundAction.isChecked()); plot->showGrid(showGridAction.isChecked()); plot->showMinorGrid(showMinorGridAction.isChecked()); plot->showLegend(showLegendAction.isChecked()); plot->showDemoIndicator(isDemoShown); plot->setYAxis(_autoScaled, _yMin, _yMax); plot->setNumOfSamples(_numOfSamples); plot->setSymbols(showSymbols); plot->setPlotWidth(_plotWidth); if (_xAxisAsIndex) { plot->setXAxis(0, _numOfSamples); } else { plot->setXAxis(_xMin, _xMax); } return plot; } void PlotManager::addCurve(QString title, FrameBuffer* buffer) { auto curve = new QwtPlotCurve(title); auto series = new FrameBufferSeries(buffer); series->setXAxis(_xAxisAsIndex, _xMin, _xMax); curve->setSamples(series); _addCurve(curve); } void PlotManager::addCurve(QString title, QVector data) { auto curve = new QwtPlotCurve(title); curve->setSamples(data); _addCurve(curve); } void PlotManager::_addCurve(QwtPlotCurve* curve) { // store and init the curve curves.append(curve); unsigned index = curves.size()-1; auto color = _infoModel->color(index); curve->setPen(color); // create the plot for the curve if we are on multi display Plot* plot; if (isMulti) { // create a new plot widget plot = addPlotWidget(); } else { plot = plotWidgets[0]; } // show the curve curve->attach(plot); plot->replot(); } void PlotManager::removeCurves(unsigned number) { for (unsigned i = 0; i < number; i++) { if (!curves.isEmpty()) { delete curves.takeLast(); if (isMulti) // delete corresponding widget as well { delete plotWidgets.takeLast(); } } } } unsigned PlotManager::numOfCurves() { return curves.size(); } void PlotManager::setTitle(unsigned index, QString title) { curves[index]->setTitle(title); plotWidget(index)->replot(); } Plot* PlotManager::plotWidget(unsigned curveIndex) { if (isMulti) { return plotWidgets[curveIndex]; } else { return plotWidgets[0]; } } void PlotManager::replot() { for (auto plot : plotWidgets) { plot->replot(); } } QList PlotManager::menuActions() { QList actions; actions << &showGridAction; actions << &showMinorGridAction; actions << &unzoomAction; actions << &darkBackgroundAction; actions << &showLegendAction; actions << &showMultiAction; actions << &setSymbolsAction; return actions; } void PlotManager::showGrid(bool show) { for (auto plot : plotWidgets) { plot->showGrid(show); } } void PlotManager::showMinorGrid(bool show) { for (auto plot : plotWidgets) { plot->showMinorGrid(show); } } void PlotManager::showLegend(bool show) { for (auto plot : plotWidgets) { plot->showLegend(show); } } void PlotManager::showDemoIndicator(bool show) { isDemoShown = show; for (auto plot : plotWidgets) { plot->showDemoIndicator(show); } } void PlotManager::unzoom() { for (auto plot : plotWidgets) { plot->unzoom(); } } void PlotManager::darkBackground(bool enabled) { for (auto plot : plotWidgets) { plot->darkBackground(enabled); } } void PlotManager::setSymbols(Plot::ShowSymbols shown) { showSymbols = shown; for (auto plot : plotWidgets) { plot->setSymbols(shown); } } void PlotManager::setYAxis(bool autoScaled, double yAxisMin, double yAxisMax) { _autoScaled = autoScaled; _yMin = yAxisMin; _yMax = yAxisMax; for (auto plot : plotWidgets) { plot->setYAxis(autoScaled, yAxisMin, yAxisMax); } } void PlotManager::setXAxis(bool asIndex, double xMin, double xMax) { _xAxisAsIndex = asIndex; _xMin = xMin; _xMax = xMax; for (auto curve : curves) { // TODO: what happens when addCurve(QVector) is used? FrameBufferSeries* series = static_cast(curve->data()); series->setXAxis(asIndex, xMin, xMax); } for (auto plot : plotWidgets) { if (asIndex) { plot->setXAxis(0, _numOfSamples); } else { plot->setXAxis(xMin, xMax); } } replot(); } void PlotManager::flashSnapshotOverlay() { for (auto plot : plotWidgets) { plot->flashSnapshotOverlay(darkBackgroundAction.isChecked()); } } void PlotManager::setNumOfSamples(unsigned value) { _numOfSamples = value; for (auto plot : plotWidgets) { plot->setNumOfSamples(value); if (_xAxisAsIndex) plot->setXAxis(0, value); } } void PlotManager::setPlotWidth(double width) { _plotWidth = width; for (auto plot : plotWidgets) { plot->setPlotWidth(width); } } PlotViewSettings PlotManager::viewSettings() const { return PlotViewSettings( { showGridAction.isChecked(), showMinorGridAction.isChecked(), darkBackgroundAction.isChecked(), showLegendAction.isChecked(), showMultiAction.isChecked(), showSymbols }); } void PlotManager::setViewSettings(const PlotViewSettings& settings) { showGridAction.setChecked(settings.showGrid); showGrid(settings.showGrid); showMinorGridAction.setChecked(settings.showMinorGrid); showMinorGrid(settings.showMinorGrid); darkBackgroundAction.setChecked(settings.darkBackground); darkBackground(settings.darkBackground); showLegendAction.setChecked(settings.showLegend); showLegend(settings.showLegend); showMultiAction.setChecked(settings.showMulti); setMulti(settings.showMulti); setSymbols(settings.showSymbols); if (showSymbols == Plot::ShowSymbolsAuto) { setSymbolsAutoAct->setChecked(true); } else if (showSymbols == Plot::ShowSymbolsShow) { setSymbolsShowAct->setChecked(true); } else { setSymbolsHideAct->setChecked(true); } } void PlotManager::saveSettings(QSettings* settings) { settings->beginGroup(SettingGroup_Plot); settings->setValue(SG_Plot_DarkBackground, darkBackgroundAction.isChecked()); settings->setValue(SG_Plot_Grid, showGridAction.isChecked()); settings->setValue(SG_Plot_MinorGrid, showMinorGridAction.isChecked()); settings->setValue(SG_Plot_Legend, showLegendAction.isChecked()); settings->setValue(SG_Plot_MultiPlot, showMultiAction.isChecked()); QString showSymbolsStr; if (showSymbols == Plot::ShowSymbolsAuto) { showSymbolsStr = "auto"; } else if (showSymbols == Plot::ShowSymbolsShow) { showSymbolsStr = "show"; } else { showSymbolsStr = "hide"; } settings->setValue(SG_Plot_Symbols, showSymbolsStr); settings->endGroup(); } void PlotManager::loadSettings(QSettings* settings) { settings->beginGroup(SettingGroup_Plot); darkBackgroundAction.setChecked( settings->value(SG_Plot_DarkBackground, darkBackgroundAction.isChecked()).toBool()); darkBackground(darkBackgroundAction.isChecked()); showGridAction.setChecked( settings->value(SG_Plot_Grid, showGridAction.isChecked()).toBool()); showGrid(showGridAction.isChecked()); showMinorGridAction.setChecked( settings->value(SG_Plot_MinorGrid, showMinorGridAction.isChecked()).toBool()); showMinorGridAction.setEnabled(showGridAction.isChecked()); showMinorGrid(showMinorGridAction.isChecked()); showLegendAction.setChecked( settings->value(SG_Plot_Legend, showLegendAction.isChecked()).toBool()); showLegend(showLegendAction.isChecked()); showMultiAction.setChecked( settings->value(SG_Plot_MultiPlot, showMultiAction.isChecked()).toBool()); setMulti(showMultiAction.isChecked()); QString showSymbolsStr = settings->value(SG_Plot_Symbols, QString()).toString(); if (showSymbolsStr == "auto") { setSymbols(Plot::ShowSymbolsAuto); setSymbolsAutoAct->setChecked(true); } else if (showSymbolsStr == "show") { setSymbols(Plot::ShowSymbolsShow); setSymbolsShowAct->setChecked(true); } else if (showSymbolsStr == "hide") { setSymbols(Plot::ShowSymbolsHide); setSymbolsHideAct->setChecked(true); } else { qCritical() << "Invalid symbol setting:" << showSymbolsStr; } settings->endGroup(); }