2022-09-28 11:59:35 -04:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Copyright (C) 2022 Jon Beniston, M7RCE //
|
|
|
|
// //
|
|
|
|
// 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 as version 3 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 V3 for more details. //
|
|
|
|
// //
|
|
|
|
// You should have received a copy of the GNU General Public License //
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
#include <QTableWidgetItem>
|
|
|
|
#include <QHeaderView>
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
#include <QMenu>
|
|
|
|
#include <QAction>
|
|
|
|
#include <QClipboard>
|
|
|
|
#include <QGuiApplication>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QPainter>
|
2022-10-02 15:06:52 -04:00
|
|
|
#include <QStyledItemDelegate>
|
2022-09-28 11:59:35 -04:00
|
|
|
|
|
|
|
#include "gui/spectrummeasurements.h"
|
|
|
|
|
2022-10-02 15:06:52 -04:00
|
|
|
QSize SpectrumMeasurementsTable::sizeHint() const
|
|
|
|
{
|
|
|
|
// QAbstractScrollArea::sizeHint() always returns 256x192 when sizeAdjustPolicy == AdjustIgnored
|
|
|
|
// If using AdjustToContents policy, the default sizeHint includes the stretched empty column
|
|
|
|
// which we don't want, as that prevents the Auto Stack feature from reducing it
|
|
|
|
// So we need some custom code to set size ignoring this column
|
|
|
|
int width = 0;
|
|
|
|
int height = 0;
|
|
|
|
for (int i = 0; i < columnCount() - 1; i++) { // -1 to ignore empty column at end of row
|
|
|
|
width += columnWidth(i);
|
|
|
|
}
|
|
|
|
for (int i = 0; i < rowCount(); i++) {
|
|
|
|
height += rowHeight(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
int doubleFrame = 2 * frameWidth();
|
|
|
|
|
|
|
|
width += verticalHeader()->width() + doubleFrame;
|
|
|
|
height += horizontalHeader()->height() + doubleFrame;
|
|
|
|
|
|
|
|
return QSize(width, height);
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize SpectrumMeasurementsTable::minimumSizeHint() const
|
|
|
|
{
|
|
|
|
QSize min1 = QTableWidget::minimumSizeHint(); // This seems to include vertical space for scroll bar, which we don't need
|
|
|
|
int height = horizontalHeader()->height() + 2 * frameWidth() + rowHeight(0);
|
|
|
|
return QSize(min1.width(), height);
|
|
|
|
}
|
2022-09-28 11:59:35 -04:00
|
|
|
|
|
|
|
class SDRGUI_API UnitsDelegate : public QStyledItemDelegate {
|
|
|
|
|
|
|
|
public:
|
|
|
|
UnitsDelegate(QObject *parent = nullptr);
|
|
|
|
|
|
|
|
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
|
|
|
|
|
|
|
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
|
|
|
|
{
|
|
|
|
QString s = text(index);
|
|
|
|
return QSize(width(s, option.fontMetrics) + 2, option.fontMetrics.height());
|
|
|
|
}
|
|
|
|
|
|
|
|
int width(const QString &s, const QFontMetrics &fm) const
|
|
|
|
{
|
|
|
|
int left = s.size() > 0 ? fm.leftBearing(s[0]) : 0;
|
|
|
|
int right = s.size() > 0 ? fm.rightBearing(s[s.size()-1]) : 0;
|
|
|
|
return fm.horizontalAdvance(s) + left + right;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString text(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
QString units = index.data(UNITS_ROLE).toString();
|
|
|
|
QString s;
|
|
|
|
if (units == "Hz")
|
|
|
|
{
|
|
|
|
s = formatEngineering(index.data().toLongLong());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int precision = index.data(PRECISION_ROLE).toInt();
|
|
|
|
double d = index.data().toDouble();
|
|
|
|
s = QString::number(d, 'f', precision);
|
|
|
|
}
|
|
|
|
return s + units;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Roles {
|
|
|
|
UNITS_ROLE = Qt::UserRole,
|
|
|
|
PRECISION_ROLE,
|
|
|
|
SPEC_ROLE
|
|
|
|
};
|
|
|
|
|
|
|
|
protected:
|
|
|
|
QString formatEngineering(int64_t value) const;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
UnitsDelegate::UnitsDelegate(QObject *parent) :
|
|
|
|
QStyledItemDelegate(parent)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
QString UnitsDelegate::formatEngineering(int64_t value) const
|
|
|
|
{
|
|
|
|
if (value == 0) {
|
|
|
|
return "0";
|
|
|
|
}
|
|
|
|
int64_t absValue = std::abs(value);
|
|
|
|
|
|
|
|
QString digits = QString::number(absValue);
|
|
|
|
int cnt = digits.size();
|
|
|
|
|
|
|
|
QString point = QLocale::system().decimalPoint();
|
|
|
|
QString group = QLocale::system().groupSeparator();
|
|
|
|
int i;
|
|
|
|
for (i = cnt - 3; i >= 4; i -= 3)
|
|
|
|
{
|
|
|
|
digits = digits.insert(i, group);
|
|
|
|
}
|
|
|
|
if (absValue >= 1000) {
|
|
|
|
digits = digits.insert(i, point);
|
|
|
|
}
|
|
|
|
if (cnt > 9) {
|
|
|
|
digits = digits.append("G");
|
|
|
|
} else if (cnt > 6) {
|
|
|
|
digits = digits.append("M");
|
|
|
|
} else if (cnt > 3) {
|
|
|
|
digits = digits.append("k");
|
|
|
|
}
|
|
|
|
if (value < 0) {
|
|
|
|
digits = digits.insert(0, "-");
|
|
|
|
}
|
|
|
|
|
|
|
|
return digits;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UnitsDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
QFontMetrics fm = painter->fontMetrics();
|
|
|
|
|
|
|
|
QString s = text(index);
|
|
|
|
int sWidth = width(s, fm);
|
|
|
|
while ((sWidth > option.rect.width()) && !s.isEmpty())
|
|
|
|
{
|
|
|
|
s = s.mid(1);
|
|
|
|
sWidth = width(s, fm);
|
|
|
|
}
|
|
|
|
|
|
|
|
int y = option.rect.y() + (option.rect.height()) - ((option.rect.height() - fm.ascent()) / 2); // Align center vertically
|
|
|
|
|
|
|
|
QStyleOptionViewItem opt = option;
|
|
|
|
initStyleOption(&opt, index);
|
|
|
|
QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
|
|
|
|
painter->setPen(opt.palette.color(cg, QPalette::Text));
|
|
|
|
|
|
|
|
painter->drawText(option.rect.x() + option.rect.width() - 1 - sWidth, y, s);
|
|
|
|
}
|
|
|
|
|
|
|
|
const QStringList SpectrumMeasurements::m_measurementColumns = {
|
|
|
|
"Current",
|
|
|
|
"Mean",
|
|
|
|
"Min",
|
|
|
|
"Max",
|
|
|
|
"Range",
|
|
|
|
"Std Dev",
|
|
|
|
"Count",
|
|
|
|
"Spec",
|
|
|
|
"Fails",
|
|
|
|
""
|
|
|
|
};
|
|
|
|
|
|
|
|
const QStringList SpectrumMeasurements::m_tooltips = {
|
|
|
|
"Current value",
|
|
|
|
"Mean average of values",
|
|
|
|
"Minimum value",
|
|
|
|
"Maximum value",
|
|
|
|
"Range of values (Max-Min)",
|
|
|
|
"Standard deviation",
|
|
|
|
"Count of values",
|
|
|
|
"Specification for value.\n\nE.g. <-100.5, >34.5 or =10.2",
|
|
|
|
"Count of values that failed to meet specification",
|
|
|
|
""
|
|
|
|
};
|
|
|
|
|
|
|
|
SpectrumMeasurements::SpectrumMeasurements(QWidget *parent) :
|
|
|
|
QWidget(parent),
|
2022-09-28 14:47:44 -04:00
|
|
|
m_measurement(SpectrumSettings::MeasurementPeaks),
|
2022-09-29 06:59:58 -04:00
|
|
|
m_precision(1),
|
2022-09-28 11:59:35 -04:00
|
|
|
m_table(nullptr),
|
|
|
|
m_peakTable(nullptr)
|
|
|
|
{
|
|
|
|
m_textBrush.setColor(Qt::white); // Should get this from the style sheet?
|
|
|
|
m_redBrush.setColor(Qt::red);
|
|
|
|
|
|
|
|
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
|
|
|
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout();
|
|
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
setLayout(layout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::createMeasurementsTable(const QStringList &rows, const QStringList &units)
|
|
|
|
{
|
2022-10-02 15:06:52 -04:00
|
|
|
m_table = new SpectrumMeasurementsTable();
|
2022-09-28 11:59:35 -04:00
|
|
|
|
|
|
|
m_table->horizontalHeader()->setSectionsMovable(true);
|
|
|
|
m_table->verticalHeader()->setSectionsMovable(true);
|
|
|
|
|
|
|
|
m_table->setColumnCount(m_measurementColumns.size());
|
|
|
|
for (int i = 0; i < m_measurementColumns.size(); i++)
|
|
|
|
{
|
|
|
|
QTableWidgetItem *item = new QTableWidgetItem(m_measurementColumns[i]);
|
|
|
|
item->setToolTip(m_tooltips[i]);
|
|
|
|
m_table->setHorizontalHeaderItem(i, item);
|
|
|
|
}
|
2022-10-02 15:06:52 -04:00
|
|
|
m_table->horizontalHeader()->setStretchLastSection(true);
|
2022-09-28 11:59:35 -04:00
|
|
|
|
|
|
|
m_table->setRowCount(rows.size());
|
|
|
|
for (int i = 0; i < rows.size(); i++)
|
|
|
|
{
|
|
|
|
m_table->setVerticalHeaderItem(i, new QTableWidgetItem(rows[i]));
|
|
|
|
for (int j = 0; j < m_measurementColumns.size(); j++)
|
|
|
|
{
|
|
|
|
QTableWidgetItem *item = new QTableWidgetItem();
|
|
|
|
item->setFlags(Qt::ItemIsEnabled);
|
|
|
|
item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
|
|
|
if (j < COL_COUNT)
|
|
|
|
{
|
|
|
|
item->setData(UnitsDelegate::UNITS_ROLE, units[i]);
|
2022-09-29 06:59:58 -04:00
|
|
|
item->setData(UnitsDelegate::PRECISION_ROLE, m_precision);
|
2022-09-28 11:59:35 -04:00
|
|
|
}
|
|
|
|
else if (j == COL_SPEC)
|
|
|
|
{
|
|
|
|
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable);
|
|
|
|
}
|
|
|
|
m_table->setItem(i, j, item);
|
|
|
|
}
|
|
|
|
Measurement m;
|
|
|
|
m.m_units = units[i];
|
|
|
|
m_measurements.append(m);
|
|
|
|
}
|
|
|
|
resizeMeasurementsTable();
|
2022-10-02 15:06:52 -04:00
|
|
|
|
|
|
|
m_table->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
|
|
|
m_table->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
|
|
|
|
|
2022-09-28 11:59:35 -04:00
|
|
|
for (int i = 0; i < COL_COUNT; i++) {
|
|
|
|
m_table->setItemDelegateForColumn(i, new UnitsDelegate());
|
|
|
|
}
|
|
|
|
createTableMenus();
|
|
|
|
|
2022-10-02 15:06:52 -04:00
|
|
|
// Cell context menu
|
|
|
|
m_table->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
connect(m_table, &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::tableContextMenu);
|
2022-09-28 11:59:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::createPeakTable(int peaks)
|
|
|
|
{
|
2022-10-02 15:06:52 -04:00
|
|
|
m_peakTable = new SpectrumMeasurementsTable();
|
2022-09-28 11:59:35 -04:00
|
|
|
m_peakTable->horizontalHeader()->setSectionsMovable(true);
|
|
|
|
|
|
|
|
QStringList columns = QStringList{"Frequency", "Power", ""};
|
|
|
|
|
|
|
|
m_peakTable->setColumnCount(columns.size());
|
|
|
|
m_peakTable->setRowCount(peaks);
|
|
|
|
|
|
|
|
for (int i = 0; i < columns.size(); i++) {
|
|
|
|
m_peakTable->setHorizontalHeaderItem(i, new QTableWidgetItem(columns[i]));
|
|
|
|
}
|
2022-10-02 15:06:52 -04:00
|
|
|
m_peakTable->horizontalHeader()->setStretchLastSection(true);
|
|
|
|
|
2022-09-28 11:59:35 -04:00
|
|
|
for (int i = 0; i < peaks; i++)
|
|
|
|
{
|
|
|
|
for (int j = 0; j < 3; j++)
|
|
|
|
{
|
|
|
|
QTableWidgetItem *item = new QTableWidgetItem();
|
|
|
|
item->setFlags(Qt::ItemIsEnabled);
|
|
|
|
if (j == COL_FREQUENCY) {
|
|
|
|
item->setData(UnitsDelegate::UNITS_ROLE, "Hz");
|
|
|
|
} else if (j == COL_POWER) {
|
|
|
|
item->setData(UnitsDelegate::UNITS_ROLE, " dB");
|
2022-09-29 06:59:58 -04:00
|
|
|
item->setData(UnitsDelegate::PRECISION_ROLE, m_precision);
|
2022-09-28 11:59:35 -04:00
|
|
|
}
|
|
|
|
m_peakTable->setItem(i, j, item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resizePeakTable();
|
|
|
|
|
|
|
|
m_peakTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
|
|
|
m_peakTable->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
|
|
|
|
|
2022-10-02 15:06:52 -04:00
|
|
|
m_peakTable->setItemDelegateForColumn(COL_FREQUENCY, new UnitsDelegate());
|
|
|
|
m_peakTable->setItemDelegateForColumn(COL_POWER, new UnitsDelegate());
|
|
|
|
|
2022-09-28 11:59:35 -04:00
|
|
|
// Cell context menu
|
|
|
|
m_peakTable->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
connect(m_peakTable, &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::peakTableContextMenu);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::createTableMenus()
|
|
|
|
{
|
|
|
|
// Add context menu to allow hiding/showing of columns
|
|
|
|
m_rowMenu = new QMenu(m_table);
|
2022-10-02 15:06:52 -04:00
|
|
|
for (int i = 0; i < m_table->verticalHeader()->count() - 1; i++) // -1 to skip empty column
|
2022-09-28 11:59:35 -04:00
|
|
|
{
|
|
|
|
QString text = m_table->verticalHeaderItem(i)->text();
|
|
|
|
m_rowMenu->addAction(createCheckableItem(text, i, true, true));
|
|
|
|
}
|
|
|
|
m_table->verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
connect(m_table->verticalHeader(), &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::rowSelectMenu);
|
|
|
|
|
|
|
|
// Add context menu to allow hiding/showing of rows
|
|
|
|
m_columnMenu = new QMenu(m_table);
|
|
|
|
for (int i = 0; i < m_table->horizontalHeader()->count(); i++)
|
|
|
|
{
|
|
|
|
QString text = m_table->horizontalHeaderItem(i)->text();
|
|
|
|
m_columnMenu->addAction(createCheckableItem(text, i, true, false));
|
|
|
|
}
|
|
|
|
m_table->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
connect(m_table->horizontalHeader(), &QTableWidget::customContextMenuRequested, this, &SpectrumMeasurements::columnSelectMenu);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::createChannelPowerTable()
|
|
|
|
{
|
|
|
|
QStringList rows = {"Channel power"};
|
|
|
|
QStringList units = {" dB"};
|
|
|
|
|
|
|
|
createMeasurementsTable(rows, units);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::createAdjacentChannelPowerTable()
|
|
|
|
{
|
|
|
|
QStringList rows = {"Left power", "Left ACPR", "Center power", "Right power", "Right ACPR"};
|
|
|
|
QStringList units = {" dB", " dBc", " dB", " dB", " dBc"};
|
|
|
|
|
|
|
|
createMeasurementsTable(rows, units);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::createSNRTable()
|
|
|
|
{
|
|
|
|
QStringList rows = {"SNR", "SNFR", "THD", "THD+N", "SINAD", "SFDR",};
|
|
|
|
QStringList units = {" dB", " dB", " dB", " dB", " dB", " dBc"};
|
|
|
|
|
|
|
|
createMeasurementsTable(rows, units);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create column select menu item
|
|
|
|
QAction *SpectrumMeasurements::createCheckableItem(QString &text, int idx, bool checked, bool row)
|
|
|
|
{
|
|
|
|
QAction *action = new QAction(text, this);
|
|
|
|
action->setCheckable(true);
|
|
|
|
action->setChecked(checked);
|
|
|
|
action->setData(QVariant(idx));
|
|
|
|
if (row) {
|
|
|
|
connect(action, &QAction::triggered, this, &SpectrumMeasurements::rowSelectMenuChecked);
|
|
|
|
} else {
|
|
|
|
connect(action, &QAction::triggered, this, &SpectrumMeasurements::columnSelectMenuChecked);
|
|
|
|
}
|
|
|
|
return action;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Right click in table header - show row select menu
|
|
|
|
void SpectrumMeasurements::rowSelectMenu(QPoint pos)
|
|
|
|
{
|
|
|
|
m_rowMenu->popup(m_table->verticalHeader()->viewport()->mapToGlobal(pos));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hide/show row when menu selected
|
|
|
|
void SpectrumMeasurements::rowSelectMenuChecked(bool checked)
|
|
|
|
{
|
|
|
|
(void) checked;
|
|
|
|
QAction* action = qobject_cast<QAction*>(sender());
|
|
|
|
if (action != nullptr)
|
|
|
|
{
|
|
|
|
int idx = action->data().toInt(nullptr);
|
|
|
|
m_table->setRowHidden(idx, !action->isChecked());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Right click in table header - show column select menu
|
|
|
|
void SpectrumMeasurements::columnSelectMenu(QPoint pos)
|
|
|
|
{
|
|
|
|
m_columnMenu->popup(m_table->horizontalHeader()->viewport()->mapToGlobal(pos));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hide/show column when menu selected
|
|
|
|
void SpectrumMeasurements::columnSelectMenuChecked(bool checked)
|
|
|
|
{
|
|
|
|
(void) checked;
|
|
|
|
QAction* action = qobject_cast<QAction*>(sender());
|
|
|
|
if (action != nullptr)
|
|
|
|
{
|
|
|
|
int idx = action->data().toInt(nullptr);
|
|
|
|
m_table->setColumnHidden(idx, !action->isChecked());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::tableContextMenu(QPoint pos)
|
|
|
|
{
|
|
|
|
QTableWidgetItem *item = m_table->itemAt(pos);
|
|
|
|
if (item)
|
|
|
|
{
|
|
|
|
QMenu* tableContextMenu = new QMenu(m_table);
|
|
|
|
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
|
|
|
|
|
|
|
|
// Copy current cell
|
|
|
|
QAction* copyAction = new QAction("Copy", tableContextMenu);
|
|
|
|
const QString text = item->text();
|
|
|
|
connect(copyAction, &QAction::triggered, this, [text]()->void {
|
|
|
|
QClipboard *clipboard = QGuiApplication::clipboard();
|
|
|
|
clipboard->setText(text);
|
|
|
|
});
|
|
|
|
tableContextMenu->addAction(copyAction);
|
|
|
|
tableContextMenu->addSeparator();
|
|
|
|
|
|
|
|
tableContextMenu->popup(m_table->viewport()->mapToGlobal(pos));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::peakTableContextMenu(QPoint pos)
|
|
|
|
{
|
|
|
|
QTableWidgetItem *item = m_peakTable->itemAt(pos);
|
|
|
|
if (item)
|
|
|
|
{
|
|
|
|
QMenu* tableContextMenu = new QMenu(m_peakTable);
|
|
|
|
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
|
|
|
|
|
|
|
|
// Copy current cell
|
|
|
|
QAction* copyAction = new QAction("Copy", tableContextMenu);
|
|
|
|
const QString text = item->text();
|
|
|
|
connect(copyAction, &QAction::triggered, this, [text]()->void {
|
|
|
|
QClipboard *clipboard = QGuiApplication::clipboard();
|
|
|
|
clipboard->setText(text);
|
|
|
|
});
|
|
|
|
tableContextMenu->addAction(copyAction);
|
|
|
|
tableContextMenu->addSeparator();
|
|
|
|
|
|
|
|
tableContextMenu->popup(m_peakTable->viewport()->mapToGlobal(pos));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::resizeMeasurementsTable()
|
|
|
|
{
|
|
|
|
// Fill table with a row of dummy data that will size the columns nicely
|
|
|
|
int row = m_table->rowCount();
|
|
|
|
m_table->setRowCount(row + 1);
|
|
|
|
m_table->setItem(row, COL_CURRENT, new QTableWidgetItem("-120.0 dBc"));
|
|
|
|
m_table->setItem(row, COL_MEAN, new QTableWidgetItem("-120.0 dBc"));
|
|
|
|
m_table->setItem(row, COL_MIN, new QTableWidgetItem("-120.0 dBc"));
|
|
|
|
m_table->setItem(row, COL_MAX, new QTableWidgetItem("-120.0 dBc"));
|
|
|
|
m_table->setItem(row, COL_RANGE, new QTableWidgetItem("-120.0 dBc"));
|
|
|
|
m_table->setItem(row, COL_STD_DEV, new QTableWidgetItem("-120.0 dBc"));
|
|
|
|
m_table->setItem(row, COL_COUNT, new QTableWidgetItem("100000"));
|
|
|
|
m_table->setItem(row, COL_SPEC, new QTableWidgetItem(">= -120.0"));
|
|
|
|
m_table->setItem(row, COL_FAILS, new QTableWidgetItem("100000"));
|
|
|
|
m_table->resizeColumnsToContents();
|
|
|
|
m_table->removeRow(row);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::resizePeakTable()
|
|
|
|
{
|
|
|
|
// Fill table with a row of dummy data that will size the columns nicely
|
|
|
|
int row = m_peakTable->rowCount();
|
|
|
|
m_peakTable->setRowCount(row + 1);
|
|
|
|
m_peakTable->setItem(row, COL_FREQUENCY, new QTableWidgetItem("6.000,000,000GHz"));
|
|
|
|
m_peakTable->setItem(row, COL_POWER, new QTableWidgetItem("-120.0 dB"));
|
|
|
|
m_peakTable->resizeColumnsToContents();
|
|
|
|
m_peakTable->removeRow(row);
|
|
|
|
}
|
|
|
|
|
2022-09-29 06:59:58 -04:00
|
|
|
void SpectrumMeasurements::setMeasurementParams(SpectrumSettings::Measurement measurement, int peaks, int precision)
|
2022-09-28 11:59:35 -04:00
|
|
|
{
|
2022-09-29 06:59:58 -04:00
|
|
|
if ( (measurement != m_measurement)
|
|
|
|
|| (m_precision != precision)
|
2022-09-28 15:03:59 -04:00
|
|
|
|| ((m_peakTable == nullptr) && (m_table == nullptr))
|
|
|
|
|| ((m_peakTable != nullptr) && (peaks != m_peakTable->rowCount()))
|
|
|
|
)
|
2022-09-28 11:59:35 -04:00
|
|
|
{
|
2022-09-28 14:47:44 -04:00
|
|
|
// Tried using setVisible(), but that would hang, so delete and recreate
|
|
|
|
delete m_peakTable;
|
|
|
|
m_peakTable = nullptr;
|
|
|
|
delete m_table;
|
|
|
|
m_table = nullptr;
|
|
|
|
|
|
|
|
m_measurement = measurement;
|
2022-09-29 06:59:58 -04:00
|
|
|
m_precision = precision;
|
2022-09-28 14:47:44 -04:00
|
|
|
|
|
|
|
switch (measurement)
|
|
|
|
{
|
|
|
|
case SpectrumSettings::MeasurementPeaks:
|
|
|
|
createPeakTable(peaks);
|
|
|
|
layout()->addWidget(m_peakTable);
|
|
|
|
break;
|
|
|
|
case SpectrumSettings::MeasurementChannelPower:
|
|
|
|
reset();
|
|
|
|
createChannelPowerTable();
|
|
|
|
layout()->addWidget(m_table);
|
|
|
|
break;
|
|
|
|
case SpectrumSettings::MeasurementAdjacentChannelPower:
|
|
|
|
reset();
|
|
|
|
createAdjacentChannelPowerTable();
|
|
|
|
layout()->addWidget(m_table);
|
|
|
|
break;
|
|
|
|
case SpectrumSettings::MeasurementSNR:
|
|
|
|
reset();
|
|
|
|
createSNRTable();
|
|
|
|
layout()->addWidget(m_table);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2022-10-02 15:06:52 -04:00
|
|
|
|
|
|
|
// Set size to show full table
|
|
|
|
if (m_peakTable)
|
|
|
|
{
|
|
|
|
m_peakTable->show(); // Need to call show() so that sizeHint() is not 0,0
|
|
|
|
resize(sizeHint());
|
|
|
|
}
|
|
|
|
else if (m_table)
|
|
|
|
{
|
|
|
|
m_table->show();
|
|
|
|
resize(sizeHint());
|
|
|
|
}
|
2022-09-28 11:59:35 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::reset()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < m_measurements.size(); i++) {
|
|
|
|
m_measurements[i].reset();
|
|
|
|
}
|
|
|
|
if (m_table)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < m_table->rowCount(); i++)
|
|
|
|
{
|
|
|
|
for (int j = 0; j < m_table->columnCount(); j++)
|
|
|
|
{
|
|
|
|
if (j != COL_SPEC) {
|
|
|
|
m_table->item(i, j)->setText("");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the value meets the user-defined specification
|
|
|
|
bool SpectrumMeasurements::checkSpec(const QString &spec, double value) const
|
|
|
|
{
|
|
|
|
if (spec.isEmpty()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (spec.startsWith("<="))
|
|
|
|
{
|
|
|
|
double limit = spec.mid(2).toDouble();
|
|
|
|
return value <= limit;
|
|
|
|
}
|
|
|
|
else if (spec[0] == '<')
|
|
|
|
{
|
|
|
|
double limit = spec.mid(1).toDouble();
|
|
|
|
return value < limit;
|
|
|
|
}
|
|
|
|
else if (spec.startsWith(">="))
|
|
|
|
{
|
|
|
|
double limit = spec.mid(2).toDouble();
|
|
|
|
return value >= limit;
|
|
|
|
}
|
|
|
|
else if (spec[0] == '>')
|
|
|
|
{
|
|
|
|
double limit = spec.mid(1).toDouble();
|
|
|
|
return value > limit;
|
|
|
|
}
|
|
|
|
else if (spec[0] == '=')
|
|
|
|
{
|
|
|
|
double limit = spec.mid(1).toDouble();
|
|
|
|
return value == limit;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::updateMeasurement(int row, float value)
|
|
|
|
{
|
|
|
|
m_measurements[row].add(value);
|
|
|
|
double mean = m_measurements[row].mean();
|
|
|
|
|
|
|
|
m_table->item(row, COL_CURRENT)->setData(Qt::DisplayRole, value);
|
|
|
|
m_table->item(row, COL_MEAN)->setData(Qt::DisplayRole, mean);
|
|
|
|
m_table->item(row, COL_MIN)->setData(Qt::DisplayRole, m_measurements[row].m_min);
|
|
|
|
m_table->item(row, COL_MAX)->setData(Qt::DisplayRole, m_measurements[row].m_max);
|
|
|
|
m_table->item(row, COL_RANGE)->setData(Qt::DisplayRole, m_measurements[row].m_max - m_measurements[row].m_min);
|
|
|
|
m_table->item(row, COL_STD_DEV)->setData(Qt::DisplayRole, m_measurements[row].stdDev());
|
|
|
|
m_table->item(row, COL_COUNT)->setData(Qt::DisplayRole, m_measurements[row].m_values.size());
|
|
|
|
|
|
|
|
QString spec = m_table->item(row, COL_SPEC)->text();
|
|
|
|
bool valueOK = checkSpec(spec, value);
|
|
|
|
bool meanOK = checkSpec(spec, mean);
|
|
|
|
bool minOK = checkSpec(spec, m_measurements[row].m_min);
|
|
|
|
bool mmaxOK = checkSpec(spec, m_measurements[row].m_max);
|
|
|
|
|
|
|
|
if (!valueOK)
|
|
|
|
{
|
|
|
|
m_measurements[row].m_fails++;
|
|
|
|
m_table->item(row, 8)->setData(Qt::DisplayRole, m_measurements[row].m_fails);
|
|
|
|
}
|
|
|
|
|
|
|
|
// item->setForeground doesn't work, perhaps as we have style sheet applied?
|
|
|
|
m_table->item(row, COL_CURRENT)->setData(Qt::ForegroundRole, valueOK ? m_textBrush : m_redBrush);
|
|
|
|
m_table->item(row, COL_MEAN)->setData(Qt::ForegroundRole, meanOK ? m_textBrush : m_redBrush);
|
|
|
|
m_table->item(row, COL_MIN)->setData(Qt::ForegroundRole, minOK ? m_textBrush : m_redBrush);
|
|
|
|
m_table->item(row, COL_MAX)->setData(Qt::ForegroundRole, mmaxOK ? m_textBrush : m_redBrush);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::setSNR(float snr, float snfr, float thd, float thdpn, float sinad)
|
|
|
|
{
|
|
|
|
updateMeasurement(0, snr);
|
|
|
|
updateMeasurement(1, snfr);
|
|
|
|
updateMeasurement(2, thd);
|
|
|
|
updateMeasurement(3, thdpn);
|
|
|
|
updateMeasurement(4, sinad);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::setSFDR(float sfdr)
|
|
|
|
{
|
|
|
|
updateMeasurement(5, sfdr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::setChannelPower(float power)
|
|
|
|
{
|
|
|
|
updateMeasurement(0, power);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::setAdjacentChannelPower(float left, float leftACPR, float center, float right, float rightACPR)
|
|
|
|
{
|
|
|
|
updateMeasurement(0, left);
|
|
|
|
updateMeasurement(1, leftACPR);
|
|
|
|
updateMeasurement(2, center);
|
|
|
|
updateMeasurement(3, right);
|
|
|
|
updateMeasurement(4, rightACPR);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SpectrumMeasurements::setPeak(int peak, int64_t frequency, float power)
|
|
|
|
{
|
2022-09-28 15:03:59 -04:00
|
|
|
if (peak < m_peakTable->rowCount())
|
|
|
|
{
|
|
|
|
m_peakTable->item(peak, COL_FREQUENCY)->setData(Qt::DisplayRole, QVariant((qlonglong)frequency));
|
|
|
|
m_peakTable->item(peak, COL_POWER)->setData(Qt::DisplayRole, power);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qDebug() << "SpectrumMeasurements::setPeak: Attempt to set peak " << peak << " when only " << m_peakTable->rowCount() << " rows in peak table";
|
|
|
|
}
|
2022-09-28 11:59:35 -04:00
|
|
|
}
|