/*
Copyright © 2019 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
#include
#include "plotcontrolpanel.h"
#include "ui_plotcontrolpanel.h"
#include "setting_defines.h"
/// Confirm if #samples is being set to a value greater than this
const int NUMSAMPLES_CONFIRM_AT = 1000000;
/// Precision used for channel info table numbers
const int DOUBLESP_PRECISION = 6;
/// Used for scale range selection combobox
struct Range
{
double rmin;
double rmax;
};
Q_DECLARE_METATYPE(Range);
/// Used for customizing double precision in tables
class SpinBoxDelegate : public QStyledItemDelegate
{
public:
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE
{
auto w = QStyledItemDelegate::createEditor(
parent, option, index);
auto sp = qobject_cast(w);
if (sp)
{
sp->setDecimals(DOUBLESP_PRECISION);
}
return w;
}
};
PlotControlPanel::PlotControlPanel(QWidget *parent) :
QWidget(parent),
ui(new Ui::PlotControlPanel),
resetAct(tr("Reset"), this),
resetNamesAct(tr("Reset Names"), this),
resetColorsAct(tr("Reset Colors"), this),
showAllAct(tr("Show All"), this),
hideAllAct(tr("Hide All"), this),
resetGainsAct(tr("Reset All Gain"), this),
resetOffsetsAct(tr("Reset All Offset"), this),
resetMenu(tr("Reset Menu"), this)
{
ui->setupUi(this);
delegate = new SpinBoxDelegate();
ui->tvChannelInfo->setItemDelegate(delegate);
warnNumOfSamples = true; // TODO: load from settings
_numOfSamples = ui->spNumOfSamples->value();
// set limits for axis limit boxes
ui->spYmin->setRange((-1) * std::numeric_limits::max(),
std::numeric_limits::max());
ui->spYmax->setRange((-1) * std::numeric_limits::max(),
std::numeric_limits::max());
ui->spXmin->setRange((-1) * std::numeric_limits::max(),
std::numeric_limits::max());
ui->spXmax->setRange((-1) * std::numeric_limits::max(),
std::numeric_limits::max());
// connect signals
connect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
this, SLOT(onNumOfSamples(int)));
connect(ui->cbAutoScale, &QCheckBox::toggled,
this, &PlotControlPanel::onAutoScaleChecked);
connect(ui->spYmax, SIGNAL(valueChanged(double)),
this, SLOT(onYScaleChanged()));
connect(ui->spYmin, SIGNAL(valueChanged(double)),
this, SLOT(onYScaleChanged()));
connect(ui->cbIndex, &QCheckBox::toggled,
this, &PlotControlPanel::onIndexChecked);
connect(ui->spXmax, SIGNAL(valueChanged(double)),
this, SLOT(onXScaleChanged()));
connect(ui->spXmax, static_cast(&QDoubleSpinBox::valueChanged),
[this](double v)
{
// set limit just a little below
double step = pow(10, -1 * ui->spXmin->decimals());
ui->spXmin->setMaximum(v - step);
});
connect(ui->spXmin, SIGNAL(valueChanged(double)),
this, SLOT(onXScaleChanged()));
connect(ui->spXmin, static_cast(&QDoubleSpinBox::valueChanged),
[this](double v)
{
// set limit just a little above
double step = pow(10, -1 * ui->spXmax->decimals());
ui->spXmax->setMinimum(v + step);
});
connect(ui->spPlotWidth, SIGNAL(valueChanged(int)),
this, SLOT(onPlotWidthChanged()));
ui->cbRangePresets->addItem("-100 to +100", QVariant::fromValue(Range{-100, +100}));
ui->cbRangePresets->addItem("0 to +100", QVariant::fromValue(Range{0, +100}));
QObject::connect(ui->cbRangePresets, SIGNAL(activated(int)),
this, SLOT(onRangeSelected()));
// color selector starts disabled until a channel is selected
ui->pbColorSel->setDisabled(true);
setSelectorColor(QColor(0,0,0,0));
connect(ui->pbColorSel, &QPushButton::clicked, this, &PlotControlPanel::onColorSelect);
// reset buttons
resetAct.setToolTip(tr("Reset channel names and colors"));
resetMenu.addAction(&resetNamesAct);
resetMenu.addAction(&resetColorsAct);
resetMenu.addAction(&resetGainsAct);
resetMenu.addAction(&resetOffsetsAct);
resetAct.setMenu(&resetMenu);
ui->tbReset->setDefaultAction(&resetAct);
showAllAct.setToolTip(tr("Show all channels"));
hideAllAct.setToolTip(tr("Hide all channels"));
ui->tbShowAll->setDefaultAction(&showAllAct);
ui->tbHideAll->setDefaultAction(&hideAllAct);
}
PlotControlPanel::~PlotControlPanel()
{
delete ui;
}
unsigned PlotControlPanel::numOfSamples()
{
return ui->spNumOfSamples->value();
}
void PlotControlPanel::onNumOfSamples(int value)
{
if (warnNumOfSamples && value > NUMSAMPLES_CONFIRM_AT)
{
// ask confirmation
if (!askNSConfirmation(value))
{
// revert to old value
disconnect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
this, SLOT(onNumOfSamples(int)));
ui->spNumOfSamples->setValue(_numOfSamples);
connect(ui->spNumOfSamples, SIGNAL(valueChanged(int)),
this, SLOT(onNumOfSamples(int)));
return;
}
}
_numOfSamples = value;
emit numOfSamplesChanged(value);
}
bool PlotControlPanel::askNSConfirmation(int value)
{
auto text = tr("Setting number of samples to a too big value "
"(>%1) can seriously impact the performance of "
"the application and cause freezes. Are you sure you "
"want to change the number of samples to %2?")
.arg(QString::number(NUMSAMPLES_CONFIRM_AT), QString::number(value));
// TODO: parent the mainwindow
QMessageBox mb(QMessageBox::Warning,
tr("Confirm Number of Samples"),
text,
QMessageBox::Apply | QMessageBox::Cancel,
this);
auto cb = new QCheckBox("Don't show this again.");
connect(cb, &QCheckBox::stateChanged, [this](int state)
{
warnNumOfSamples = (state == Qt::Unchecked);
});
mb.setCheckBox(cb);
return mb.exec() == QMessageBox::Apply;
}
void PlotControlPanel::setSelectorColor(QColor color)
{
ui->pbColorSel->setStyleSheet(QString("background-color: %1;").arg(color.name()));
}
void PlotControlPanel::onColorSelect()
{
auto selection = ui->tvChannelInfo->selectionModel()->currentIndex();
// no selection
if (!selection.isValid()) return;
// current color
auto model = ui->tvChannelInfo->model();
QColor color = model->data(selection, Qt::ForegroundRole).value();
// show dialog
color = QColorDialog::getColor(color, this);
if (color.isValid()) // color is set to invalid if user cancels
{
ui->tvChannelInfo->model()->setData(selection, color, Qt::ForegroundRole);
}
}
void PlotControlPanel::onAutoScaleChecked(bool checked)
{
if (checked)
{
ui->lYmin->setEnabled(false);
ui->lYmax->setEnabled(false);
ui->spYmin->setEnabled(false);
ui->spYmax->setEnabled(false);
emit yScaleChanged(true); // autoscale
}
else
{
ui->lYmin->setEnabled(true);
ui->lYmax->setEnabled(true);
ui->spYmin->setEnabled(true);
ui->spYmax->setEnabled(true);
emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
}
}
void PlotControlPanel::onYScaleChanged()
{
if (!autoScale())
{
emit yScaleChanged(false, ui->spYmin->value(), ui->spYmax->value());
}
}
bool PlotControlPanel::autoScale() const
{
return ui->cbAutoScale->isChecked();
}
double PlotControlPanel::yMax() const
{
return ui->spYmax->value();
}
double PlotControlPanel::yMin() const
{
return ui->spYmin->value();
}
bool PlotControlPanel::xAxisAsIndex() const
{
return ui->cbIndex->isChecked();
}
double PlotControlPanel::xMax() const
{
return ui->spXmax->value();
}
double PlotControlPanel::xMin() const
{
return ui->spXmin->value();
}
void PlotControlPanel::onRangeSelected()
{
Range r = ui->cbRangePresets->currentData().value();
ui->spYmin->setValue(r.rmin);
ui->spYmax->setValue(r.rmax);
ui->cbAutoScale->setChecked(false);
}
void PlotControlPanel::onIndexChecked(bool checked)
{
if (checked)
{
ui->lXmin->setEnabled(false);
ui->lXmax->setEnabled(false);
ui->spXmin->setEnabled(false);
ui->spXmax->setEnabled(false);
emit xScaleChanged(true); // use index
}
else
{
ui->lXmin->setEnabled(true);
ui->lXmax->setEnabled(true);
ui->spXmin->setEnabled(true);
ui->spXmax->setEnabled(true);
emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
}
emit plotWidthChanged(plotWidth());
}
void PlotControlPanel::onXScaleChanged()
{
if (!xAxisAsIndex())
{
emit xScaleChanged(false, ui->spXmin->value(), ui->spXmax->value());
emit plotWidthChanged(plotWidth());
}
}
double PlotControlPanel::plotWidth() const
{
double value = ui->spPlotWidth->value();
if (!xAxisAsIndex())
{
// scale by xmin and xmax
auto xmax = ui->spXmax->value();
auto xmin = ui->spXmin->value();
double scale = (xmax - xmin) / _numOfSamples;
value *= scale;
}
return value;
}
void PlotControlPanel::onPlotWidthChanged()
{
emit plotWidthChanged(plotWidth());
}
void PlotControlPanel::setChannelInfoModel(ChannelInfoModel* model)
{
ui->tvChannelInfo->setModel(model);
// channel color selector
connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::currentRowChanged,
[this](const QModelIndex ¤t, const QModelIndex &previous)
{
// TODO: duplicate with below lambda
QColor color(0,0,0,0); // transparent
if (current.isValid())
{
ui->pbColorSel->setEnabled(true);
auto model = ui->tvChannelInfo->model();
color = model->data(current, Qt::ForegroundRole).value();
}
else
{
ui->pbColorSel->setDisabled(true);
}
setSelectorColor(color);
});
connect(ui->tvChannelInfo->selectionModel(), &QItemSelectionModel::selectionChanged,
[this](const QItemSelection & selected, const QItemSelection & deselected)
{
if (!selected.length())
{
ui->pbColorSel->setDisabled(true);
setSelectorColor(QColor(0,0,0,0));
}
});
connect(model, &QAbstractItemModel::dataChanged,
[this](const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector & roles = QVector ())
{
auto current = ui->tvChannelInfo->selectionModel()->currentIndex();
// no current selection
if (!current.isValid()) return;
auto mod = ui->tvChannelInfo->model();
QColor color = mod->data(current, Qt::ForegroundRole).value();
setSelectorColor(color);
});
// reset actions
connect(&resetAct, &QAction::triggered, model, &ChannelInfoModel::resetInfos);
connect(&resetNamesAct, &QAction::triggered, model, &ChannelInfoModel::resetNames);
connect(&resetColorsAct, &QAction::triggered, model, &ChannelInfoModel::resetColors);
connect(&resetGainsAct, &QAction::triggered, model, &ChannelInfoModel::resetGains);
connect(&resetOffsetsAct, &QAction::triggered, model, &ChannelInfoModel::resetOffsets);
connect(&showAllAct, &QAction::triggered, [model]{model->resetVisibility(true);});
connect(&hideAllAct, &QAction::triggered, [model]{model->resetVisibility(false);});
}
void PlotControlPanel::saveSettings(QSettings* settings)
{
settings->beginGroup(SettingGroup_Plot);
settings->setValue(SG_Plot_NumOfSamples, numOfSamples());
settings->setValue(SG_Plot_PlotWidth, ui->spPlotWidth->value());
settings->setValue(SG_Plot_IndexAsX, xAxisAsIndex());
settings->setValue(SG_Plot_XMax, xMax());
settings->setValue(SG_Plot_XMin, xMin());
settings->setValue(SG_Plot_AutoScale, autoScale());
settings->setValue(SG_Plot_YMax, yMax());
settings->setValue(SG_Plot_YMin, yMin());
settings->endGroup();
}
void PlotControlPanel::loadSettings(QSettings* settings)
{
settings->beginGroup(SettingGroup_Plot);
ui->spNumOfSamples->setValue(
settings->value(SG_Plot_NumOfSamples, numOfSamples()).toInt());
ui->spPlotWidth->setValue(
settings->value(SG_Plot_PlotWidth, ui->spPlotWidth->value()).toInt());
ui->cbIndex->setChecked(
settings->value(SG_Plot_IndexAsX, xAxisAsIndex()).toBool());
ui->spXmax->setValue(settings->value(SG_Plot_XMax, xMax()).toDouble());
ui->spXmin->setValue(settings->value(SG_Plot_XMin, xMin()).toDouble());
ui->cbAutoScale->setChecked(
settings->value(SG_Plot_AutoScale, autoScale()).toBool());
ui->spYmax->setValue(settings->value(SG_Plot_YMax, yMax()).toDouble());
ui->spYmin->setValue(settings->value(SG_Plot_YMin, yMin()).toDouble());
settings->endGroup();
}