mirror of
https://github.com/JS8Call-improved/JS8Call-improved
synced 2026-08-13 17:47:36 -04:00
819 lines
29 KiB
C++
819 lines
29 KiB
C++
/**
|
|
* \file WideGraph.cpp
|
|
* @brief implementation of the UI WideGraph class
|
|
*/
|
|
|
|
#include "WideGraph.h"
|
|
#include "Configuration.h"
|
|
#include "JS8_Include/EventFilter.h"
|
|
#include "JS8_Include/SettingsGroup.h"
|
|
#include "JS8_Main/DriftingDateTime.h"
|
|
#include "JS8_Main/JS8MessageBox.h"
|
|
#include "JS8_Main/Varicode.h"
|
|
#include "ui_WideGraph.h"
|
|
|
|
#include <QElapsedTimer>
|
|
#include <QLoggingCategory>
|
|
#include <QMenu>
|
|
#include <QMutexLocker>
|
|
#include <QSettings>
|
|
#include <QSignalBlocker>
|
|
#include <QTimer>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
#include "moc_WideGraph.cpp"
|
|
|
|
Q_DECLARE_LOGGING_CATEGORY(widegraph_js8)
|
|
|
|
namespace {
|
|
auto const user_defined = QObject::tr("User Defined");
|
|
|
|
// Time formats; we're likely only ever to use the second.
|
|
|
|
constexpr QStringView TIME_FORMAT_MINS = u"hh:mm";
|
|
constexpr QStringView TIME_FORMAT_SECS = u"hh:mm:ss";
|
|
|
|
constexpr auto timeFormat(int const period) {
|
|
return period < 60 ? TIME_FORMAT_SECS : TIME_FORMAT_MINS;
|
|
}
|
|
|
|
// Set the spinbox to the value, ensuring that signals are
|
|
// blocked during the set operation and restoring the prior
|
|
// blocked state afterward.
|
|
|
|
void setValueBlocked(int const value, QSpinBox *block) {
|
|
QSignalBlocker blocker(block);
|
|
block->setValue(value);
|
|
};
|
|
|
|
// Set the dial to the value, ensuring that signals are
|
|
// blocked during the set operation and restoring the prior
|
|
// blocked state afterward.
|
|
|
|
void setValueBlocked(int const value, QDial *block) {
|
|
QSignalBlocker blocker(block);
|
|
block->setValue(value);
|
|
};
|
|
|
|
// Set the checkbox to the value, ensuring that signals are
|
|
// blocked during the set operation and restoring the prior
|
|
// blocked state afterward.
|
|
|
|
void setValueBlocked(bool const value, QCheckBox *block) {
|
|
QSignalBlocker blocker(block);
|
|
block->setChecked(value);
|
|
};
|
|
} // namespace
|
|
|
|
WideGraph::WideGraph(QSettings *settings, QWidget *parent)
|
|
: QWidget{parent}, ui{new Ui::WideGraph}, m_settings{settings},
|
|
m_drawTimer{new QTimer(this)}, m_autoSyncTimer{new QTimer(this)},
|
|
m_palettes_path{":/Palettes"}, m_timeFormat{timeFormat(m_TRperiod)} {
|
|
ui->setupUi(this);
|
|
|
|
setMaximumHeight(880);
|
|
|
|
ui->splitter->setChildrenCollapsible(false);
|
|
ui->splitter->setCollapsible(ui->splitter->indexOf(ui->controls_widget),
|
|
false);
|
|
ui->splitter->updateGeometry();
|
|
|
|
// If the escape key is pressed while the filter center spin box has focus,
|
|
// put the default value in the filter center field.
|
|
|
|
ui->filterCenterSpinBox->installEventFilter(new EventFilter::EscapeKeyPress(
|
|
[this](QKeyEvent *) {
|
|
setFilterCenter(1500);
|
|
return true;
|
|
},
|
|
this));
|
|
|
|
// If the escape key is pressed while the filter width spin box has focus,
|
|
// put the default value in the filter width field.
|
|
|
|
ui->filterWidthSpinBox->installEventFilter(new EventFilter::EscapeKeyPress(
|
|
[this](QKeyEvent *) {
|
|
setFilterWidth(2000);
|
|
return true;
|
|
},
|
|
this));
|
|
|
|
// The filter center and width spin boxes are the lead controls; any time
|
|
// the associated dials change, ensure the spin boxes are updated to the
|
|
// value of the dials. Same thing in the reverse direction, but with events
|
|
// blocked to avoid recursion.
|
|
|
|
connect(ui->filterCenterDial, &QDial::valueChanged, ui->filterCenterSpinBox,
|
|
&QSpinBox::setValue);
|
|
connect(ui->filterWidthDial, &QDial::valueChanged, ui->filterWidthSpinBox,
|
|
&QSpinBox::setValue);
|
|
|
|
connect(ui->filterCenterSpinBox,
|
|
QOverload<int>::of(&QSpinBox::valueChanged), this,
|
|
[this](int const value) {
|
|
setValueBlocked(value, ui->filterCenterDial);
|
|
});
|
|
connect(ui->filterWidthSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
|
|
this, [this](int const value) {
|
|
setValueBlocked(value, ui->filterWidthDial);
|
|
});
|
|
|
|
ui->widePlot->setCursor(Qt::CrossCursor);
|
|
ui->widePlot->setMaximumWidth(WF::MaxScreenWidth);
|
|
ui->widePlot->setMaximumHeight(800);
|
|
|
|
ui->widePlot->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(
|
|
ui->widePlot, &CPlotter::customContextMenuRequested, this,
|
|
[this](const QPoint &pos) {
|
|
auto menu = new QMenu(this);
|
|
|
|
auto const f = ui->widePlot->frequencyAt(pos.x());
|
|
|
|
auto offsetAction =
|
|
menu->addAction(QString("Set &Offset to %1 Hz").arg(f));
|
|
connect(offsetAction, &QAction::triggered, this,
|
|
[this, f]() { ui->offsetSpinBox->setValue(f); });
|
|
|
|
menu->addSeparator();
|
|
|
|
if (m_filterEnabled) {
|
|
auto disableAction =
|
|
menu->addAction(QString("&Disable Filter"));
|
|
connect(disableAction, &QAction::triggered, this,
|
|
[this]() { ui->filterCheckBox->setChecked(false); });
|
|
}
|
|
|
|
auto centerAction =
|
|
menu->addAction(QString("Set Filter &Center to %1 Hz").arg(f));
|
|
connect(centerAction, &QAction::triggered, this, [this, f]() {
|
|
ui->filterCenterSpinBox->setValue(f);
|
|
ui->filterCheckBox->setChecked(true);
|
|
});
|
|
|
|
auto widthMenu = menu->addMenu("Set Filter &Width to...");
|
|
auto widths =
|
|
QList<int>{25, 50, 75, 100, 250, 500, 750, 1000, 1500, 2000};
|
|
foreach (auto width, widths) {
|
|
if (width < m_filterMinWidth) {
|
|
continue;
|
|
}
|
|
auto widthAction =
|
|
widthMenu->addAction(QString("%1 Hz").arg(width));
|
|
connect(widthAction, &QAction::triggered, this,
|
|
[this, width]() {
|
|
ui->filterWidthSpinBox->setValue(width);
|
|
ui->filterCheckBox->setChecked(true);
|
|
});
|
|
}
|
|
|
|
menu->popup(ui->widePlot->mapToGlobal(pos));
|
|
});
|
|
|
|
connect(ui->widePlot, &CPlotter::changeFreq, this, &WideGraph::changeFreq);
|
|
|
|
{
|
|
|
|
// Restore user's settings
|
|
SettingsGroup g{m_settings, "WideGraph"};
|
|
restoreGeometry(
|
|
m_settings->value("geometry", saveGeometry()).toByteArray());
|
|
ui->widePlot->setPlotZero(m_settings->value("PlotZero", 0).toInt());
|
|
ui->widePlot->setPlotGain(m_settings->value("PlotGain", 0).toInt());
|
|
ui->widePlot->setPlot2dGain(m_settings->value("Plot2dGain", 0).toInt());
|
|
ui->widePlot->setPlot2dZero(m_settings->value("Plot2dZero", 0).toInt());
|
|
ui->widePlot->setFlatten(m_settings->value("Flatten", true).toBool());
|
|
ui->widePlot->setBinsPerPixel(
|
|
m_settings->value("BinsPerPixel", 2).toInt());
|
|
ui->widePlot->setPercent2D(m_settings->value("Percent2D", 0).toInt());
|
|
ui->zeroSlider->setValue(ui->widePlot->plotZero());
|
|
ui->gainSlider->setValue(ui->widePlot->plotGain());
|
|
ui->gain2dSlider->setValue(ui->widePlot->plot2dGain());
|
|
ui->zero2dSlider->setValue(ui->widePlot->plot2dZero());
|
|
ui->cbFlatten->setChecked(ui->widePlot->flatten());
|
|
ui->bppSpinBox->setValue(ui->widePlot->binsPerPixel());
|
|
ui->sbPercent2dPlot->setValue(ui->widePlot->percent2D());
|
|
m_nsmo = m_settings->value("SmoothYellow", 1).toInt();
|
|
ui->smoSpinBox->setValue(m_nsmo);
|
|
m_waterfallAvg = m_settings->value("WaterfallAvg", 1).toInt();
|
|
ui->waterfallAvgSpinBox->setValue(m_waterfallAvg);
|
|
ui->widePlot->setWaterfallAvg(m_waterfallAvg);
|
|
ui->widePlot->setSpectrum(
|
|
m_settings
|
|
->value("WaterfallSpectrum",
|
|
QVariant::fromValue(WF::Spectrum::Current))
|
|
.value<WF::Spectrum>());
|
|
if (ui->widePlot->spectrum() == WF::Spectrum::Current)
|
|
ui->spec2dComboBox->setCurrentIndex(0);
|
|
if (ui->widePlot->spectrum() == WF::Spectrum::Cumulative)
|
|
ui->spec2dComboBox->setCurrentIndex(1);
|
|
if (ui->widePlot->spectrum() == WF::Spectrum::LinearAvg)
|
|
ui->spec2dComboBox->setCurrentIndex(2);
|
|
ui->widePlot->setStartFreq(m_settings->value("StartFreq", 500).toInt());
|
|
ui->centerSpinBox->setValue(
|
|
m_settings->value("CenterOffset", 1500).toInt());
|
|
ui->fStartSpinBox->setValue(ui->widePlot->startFreq());
|
|
m_waterfallPalette =
|
|
m_settings->value("WaterfallPalette", "Default").toString();
|
|
m_userPalette = WF::Palette{
|
|
m_settings->value("UserPalette").value<WF::Palette::Colours>()};
|
|
ui->controls_widget->setVisible(
|
|
!m_settings->value("HideControls", false).toBool());
|
|
ui->fpsSpinBox->setValue(m_settings->value("WaterfallFPS", 4).toInt());
|
|
ui->decodeAttemptCheckBox->setChecked(
|
|
m_settings->value("DisplayDecodeAttempts", false).toBool());
|
|
ui->autoDriftAutoStopCheckBox->setChecked(
|
|
m_settings->value("StopAutoSyncOnDecode", true).toBool());
|
|
ui->autoDriftStopSpinBox->setValue(
|
|
m_settings->value("StopAutoSyncAfter", 1).toInt());
|
|
|
|
auto splitState = m_settings->value("SplitState").toByteArray();
|
|
if (!splitState.isEmpty()) {
|
|
ui->splitter->restoreState(splitState);
|
|
}
|
|
|
|
setFilterCenter(m_settings->value("FilterCenter", 1500).toInt());
|
|
setFilterWidth(m_settings->value("FilterWidth", 2000).toInt());
|
|
setFilterOpacityPercent(
|
|
m_settings->value("FilterOpacityPercent", 50).toInt());
|
|
setFilterEnabled(m_settings->value("FilterEnabled", true).toBool());
|
|
}
|
|
|
|
for (auto const &file : m_palettes_path.entryList(
|
|
QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
|
|
QDir::AllDirs | QDir::Files,
|
|
QDir::DirsFirst)) {
|
|
auto const item = QFileInfo(file).completeBaseName();
|
|
ui->paletteComboBox->addItem(item);
|
|
if (item == m_waterfallPalette) {
|
|
ui->paletteComboBox->setCurrentIndex(ui->paletteComboBox->count() -
|
|
1);
|
|
}
|
|
}
|
|
|
|
ui->paletteComboBox->addItem(user_defined);
|
|
if (user_defined == m_waterfallPalette) {
|
|
ui->paletteComboBox->setCurrentIndex(ui->paletteComboBox->count() - 1);
|
|
}
|
|
|
|
readPalette();
|
|
|
|
connect(m_drawTimer, &QTimer::timeout, this, [this] {
|
|
auto const fps = std::clamp(ui->fpsSpinBox->value(), 1, 100);
|
|
qint64 const loopMs =
|
|
1000 / (fps * devicePixelRatio()) * m_waterfallAvg;
|
|
QElapsedTimer timer;
|
|
|
|
// Start the elapsed timer and do the drawing, unless we're paused.
|
|
|
|
timer.start();
|
|
|
|
if (!m_paused) {
|
|
QMutexLocker lock(&m_drawLock);
|
|
|
|
// Draw the tr cycle horizontal lines if needed.
|
|
|
|
auto const now = DriftingDateTime::currentDateTimeUtc();
|
|
auto const secondInToday = now.time().msecsSinceStartOfDay() / 1000;
|
|
int const secondInPeriod = secondInToday % m_TRperiod;
|
|
|
|
if (secondInPeriod < m_lastSecondInPeriod) {
|
|
ui->widePlot->drawLine(
|
|
now.toString(m_timeFormat).append(m_band));
|
|
}
|
|
m_lastSecondInPeriod = secondInPeriod;
|
|
|
|
// Draw the data, handing the plotter a copy, and informing them
|
|
// of our current state.
|
|
|
|
ui->widePlot->drawData(m_swide, m_state);
|
|
|
|
// Whatever our state was, the sink is now drained until new data
|
|
// arrives; data we draw until then will duplicate the operation
|
|
// we just performed.
|
|
|
|
m_state = WF::Sink::Drained;
|
|
}
|
|
|
|
// Compute the processing time and adjust loop to hit the next frame.
|
|
|
|
m_drawTimer->start(
|
|
std::max(std::chrono::milliseconds(loopMs - timer.elapsed()),
|
|
std::chrono::milliseconds::zero()));
|
|
});
|
|
|
|
m_drawTimer->setTimerType(Qt::PreciseTimer);
|
|
m_drawTimer->setSingleShot(true);
|
|
m_drawTimer->start(100); // ### Don't change the 100 ms! ###
|
|
}
|
|
|
|
WideGraph::~WideGraph() = default;
|
|
|
|
void WideGraph::closeEvent(QCloseEvent *event) {
|
|
saveSettings();
|
|
QWidget::closeEvent(event);
|
|
}
|
|
|
|
void WideGraph::saveSettings() // saveSettings
|
|
{
|
|
SettingsGroup g{m_settings, "WideGraph"};
|
|
m_settings->setValue("geometry", saveGeometry());
|
|
m_settings->setValue("PlotZero", ui->widePlot->plotZero());
|
|
m_settings->setValue("PlotGain", ui->widePlot->plotGain());
|
|
m_settings->setValue("Plot2dGain", ui->widePlot->plot2dGain());
|
|
m_settings->setValue("Plot2dZero", ui->widePlot->plot2dZero());
|
|
m_settings->setValue("SmoothYellow", ui->smoSpinBox->value());
|
|
m_settings->setValue("Percent2D", ui->widePlot->percent2D());
|
|
m_settings->setValue("WaterfallAvg", ui->waterfallAvgSpinBox->value());
|
|
m_settings->setValue("WaterfallSpectrum",
|
|
QVariant::fromValue(ui->widePlot->spectrum()));
|
|
m_settings->setValue("BinsPerPixel", ui->widePlot->binsPerPixel());
|
|
m_settings->setValue("StartFreq", ui->widePlot->startFreq());
|
|
m_settings->setValue("WaterfallPalette", m_waterfallPalette);
|
|
m_settings->setValue("UserPalette",
|
|
QVariant::fromValue(m_userPalette.colours()));
|
|
m_settings->setValue("Flatten", ui->widePlot->flatten());
|
|
m_settings->setValue("HideControls", ui->controls_widget->isHidden());
|
|
m_settings->setValue("CenterOffset", ui->centerSpinBox->value());
|
|
m_settings->setValue("FilterCenter", m_filterCenter);
|
|
m_settings->setValue("FilterWidth", m_filterWidth);
|
|
m_settings->setValue("FilterEnabled", m_filterEnabled);
|
|
m_settings->setValue("FilterOpacityPercent",
|
|
ui->filterOpacitySpinBox->value());
|
|
m_settings->setValue("SplitState", ui->splitter->saveState());
|
|
m_settings->setValue("WaterfallFPS", ui->fpsSpinBox->value());
|
|
m_settings->setValue("DisplayDecodeAttempts",
|
|
ui->decodeAttemptCheckBox->isChecked());
|
|
m_settings->setValue("StopAutoSyncOnDecode",
|
|
ui->autoDriftAutoStopCheckBox->isChecked());
|
|
m_settings->setValue("StopAutoSyncAfter",
|
|
ui->autoDriftStopSpinBox->value());
|
|
}
|
|
|
|
bool WideGraph::shouldDisplayDecodeAttempts() const {
|
|
return ui->decodeAttemptCheckBox->isChecked();
|
|
}
|
|
|
|
bool WideGraph::isAutoSyncEnabled() const {
|
|
// enabled if we're auto drifting
|
|
// and we are not auto stopping
|
|
// or if we are auto stopping,
|
|
// we have auto sync decodes left
|
|
return ui->autoDriftButton->isChecked() &&
|
|
(!ui->autoDriftAutoStopCheckBox->isChecked() ||
|
|
m_autoSyncDecodesLeft > 0);
|
|
}
|
|
|
|
bool WideGraph::shouldAutoSyncSubmode(int const submode) const {
|
|
return isAutoSyncEnabled() && (submode == Varicode::JS8CallSlow ||
|
|
submode == Varicode::JS8CallNormal
|
|
// || submode == Varicode::JS8CallFast
|
|
// || submode == Varicode::JS8CallTurbo
|
|
// || submode == Varicode::JS8CallUltra
|
|
);
|
|
}
|
|
|
|
void WideGraph::notifyDriftedSignalsDecoded(int const signalsDecoded) {
|
|
// qCDebug(widegraph_js8) << "decoded" << signalsDecoded << "with" <<
|
|
// m_autoSyncDecodesLeft << "left";
|
|
|
|
m_autoSyncDecodesLeft -= signalsDecoded;
|
|
|
|
if (ui->autoDriftAutoStopCheckBox->isChecked() &&
|
|
m_autoSyncDecodesLeft <= 0) {
|
|
ui->autoDriftButton->setChecked(false);
|
|
}
|
|
}
|
|
|
|
void WideGraph::on_autoDriftButton_toggled(bool const checked) {
|
|
if (!m_autoSyncConnected) {
|
|
connect(m_autoSyncTimer, &QTimer::timeout, this, [this]() {
|
|
// if auto drift isn't checked, don't worry about this...
|
|
if (!ui->autoDriftButton->isChecked())
|
|
return;
|
|
|
|
// uncheck after timeout
|
|
if (m_autoSyncTimeLeft == 0) {
|
|
ui->autoDriftButton->setChecked(false);
|
|
return;
|
|
}
|
|
|
|
// set new text and decrement timeleft
|
|
auto const text = ui->autoDriftButton->text();
|
|
auto const newText =
|
|
QString("%1 (%2)")
|
|
.arg(text.left(text.indexOf("(")).trimmed())
|
|
.arg(m_autoSyncTimeLeft--);
|
|
|
|
ui->autoDriftButton->setText(newText);
|
|
});
|
|
|
|
m_autoSyncConnected = true;
|
|
}
|
|
|
|
// if in the future we want to auto sync timeout after a time period
|
|
auto const autoSyncTimeout = false;
|
|
auto text = ui->autoDriftButton->text();
|
|
|
|
if (autoSyncTimeout) {
|
|
if (checked) {
|
|
m_autoSyncTimeLeft = 120;
|
|
m_autoSyncTimer->setInterval(1000);
|
|
m_autoSyncTimer->start();
|
|
ui->autoDriftButton->setText(QString("%1 (%2)")
|
|
.arg(text.replace("Start", "Stop"))
|
|
.arg(m_autoSyncTimeLeft--));
|
|
} else {
|
|
m_autoSyncTimeLeft = 0;
|
|
m_autoSyncTimer->stop();
|
|
ui->autoDriftButton->setText(text.left(text.indexOf("("))
|
|
.trimmed()
|
|
.replace("Stop", "Start"));
|
|
}
|
|
} else {
|
|
if (checked) {
|
|
m_autoSyncDecodesLeft = ui->autoDriftStopSpinBox->value();
|
|
ui->autoDriftButton->setText(text.left(text.indexOf("("))
|
|
.trimmed()
|
|
.replace("Start", "Stop"));
|
|
ui->autoDriftStopSpinBox->setEnabled(false);
|
|
} else {
|
|
m_autoSyncDecodesLeft = 0;
|
|
ui->autoDriftButton->setText(text.left(text.indexOf("("))
|
|
.trimmed()
|
|
.replace("Stop", "Start"));
|
|
ui->autoDriftStopSpinBox->setEnabled(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WideGraph::drawDecodeLine(QColor const &color, int const ia,
|
|
int const ib) {
|
|
ui->widePlot->drawDecodeLine(color, ia, ib);
|
|
}
|
|
|
|
void WideGraph::drawHorizontalLine(QColor const &color, int const x,
|
|
int const width) {
|
|
ui->widePlot->drawHorizontalLine(color, x, width);
|
|
}
|
|
|
|
void WideGraph::dataSink(WF::SPlot const &s, float const df3) {
|
|
QMutexLocker lock(&m_drawLock);
|
|
|
|
// If we need a fresh picture, just copy the entirety of the inbound
|
|
// data. Otherwise, we're somewhere in the process of averaging data,
|
|
// so add to what we've already accumulated.
|
|
|
|
if (m_waterfallNow == 0) {
|
|
m_splot = s;
|
|
} else {
|
|
std::transform(s.begin(), s.end(), m_splot.begin(), m_splot.begin(),
|
|
std::plus<>{});
|
|
}
|
|
|
|
// We can be confident at this point we've got summary data in the
|
|
// sink that needs to be drained to the plotter.
|
|
|
|
m_state |= WF::Sink::Summary;
|
|
|
|
// Either way, that was another round; see if we've hit the point at
|
|
// which we should prepare results to be flushed to the plotter. Note
|
|
// that m_waterfallAvg can change at any time, so we must be defensive
|
|
// here; it could have been a high value last round, and is now a low
|
|
// one.
|
|
|
|
if (++m_waterfallNow >= m_waterfallAvg) {
|
|
// We've now hit the averaging threshold, and are ready to commit
|
|
// data to be flushed to the plotter, which is a bit of a schlep:
|
|
//
|
|
// 1. Each source value must be averaged over the number of
|
|
// accumulations that it took to get to this point.
|
|
// 2. Source values must be reduced from bins to pixels.
|
|
// 3. Values must be converted from power scaled to dB scaled.
|
|
//
|
|
// Fortunately, we can manage that in a single pass, and can work
|
|
// only on the data to be displayed, rather than all of it.
|
|
|
|
auto const bpp = ui->widePlot->binsPerPixel();
|
|
auto sit = m_splot.begin() +
|
|
static_cast<int>(ui->widePlot->startFreq() / df3 + 0.5f);
|
|
auto it = m_swide.begin();
|
|
auto const end =
|
|
it + std::min(m_swide.size(),
|
|
static_cast<std::size_t>(5000.0f / (bpp * df3)));
|
|
auto const avg = [runs = m_waterfallNow](auto const value) {
|
|
return value / runs;
|
|
};
|
|
|
|
for (; it != end; ++it, sit += bpp) {
|
|
*it = 10.0f *
|
|
std::log10(bpp * std::transform_reduce(sit, sit + bpp, 0.0f,
|
|
std::plus<>{}, avg));
|
|
}
|
|
|
|
// Next round, we'll need a fresh picture, and we've now progressed
|
|
// to having current data in the sink.
|
|
|
|
m_waterfallNow = 0;
|
|
m_state |= WF::Sink::Current;
|
|
}
|
|
}
|
|
|
|
void WideGraph::on_bppSpinBox_valueChanged(int const n) {
|
|
ui->widePlot->setBinsPerPixel(n);
|
|
}
|
|
|
|
void WideGraph::on_qsyPushButton_clicked() { emit qsy(freq() - centerFreq()); }
|
|
|
|
void WideGraph::on_offsetSpinBox_valueChanged(int const n) {
|
|
if (n == freq())
|
|
return;
|
|
|
|
// TODO: jsherer - here's where we'd set minimum frequency again (later?)
|
|
auto const newFreq = qMax(0, n);
|
|
|
|
setFreq(newFreq);
|
|
emit changeFreq(newFreq);
|
|
}
|
|
|
|
void WideGraph::on_waterfallAvgSpinBox_valueChanged(int const n) {
|
|
m_waterfallAvg = n;
|
|
ui->widePlot->setWaterfallAvg(n);
|
|
}
|
|
|
|
void WideGraph::keyPressEvent(QKeyEvent *event) {
|
|
switch (event->key()) {
|
|
case Qt::Key_F11:
|
|
emit f11f12(11);
|
|
break;
|
|
case Qt::Key_F12:
|
|
emit f11f12(12);
|
|
break;
|
|
default:
|
|
event->ignore();
|
|
}
|
|
}
|
|
|
|
int WideGraph::freq() const { return ui->widePlot->freq(); }
|
|
|
|
int WideGraph::centerFreq() const { return ui->centerSpinBox->value(); }
|
|
|
|
int WideGraph::nStartFreq() const { return ui->widePlot->startFreq(); }
|
|
|
|
int WideGraph::filterMinimum() const {
|
|
return std::clamp(m_filterCenter - m_filterWidth / 2, 0, 5000);
|
|
}
|
|
|
|
int WideGraph::filterMaximum() const {
|
|
return std::clamp(m_filterCenter + m_filterWidth / 2, 0, 5000);
|
|
}
|
|
|
|
bool WideGraph::filterEnabled() const { return m_filterEnabled; }
|
|
|
|
void WideGraph::setFilterCenter(int const value) {
|
|
m_filterCenter = value;
|
|
setValueBlocked(value, ui->filterCenterSpinBox);
|
|
setValueBlocked(value, ui->filterCenterDial);
|
|
ui->widePlot->setFilter(m_filterCenter, m_filterWidth);
|
|
}
|
|
|
|
void WideGraph::setFilterWidth(int const value) {
|
|
m_filterWidth = value;
|
|
setValueBlocked(value, ui->filterWidthSpinBox);
|
|
setValueBlocked(value, ui->filterWidthDial);
|
|
ui->widePlot->setFilter(m_filterCenter, m_filterWidth);
|
|
}
|
|
|
|
void WideGraph::setFilterMinimumBandwidth(int const width) {
|
|
m_filterMinWidth = width;
|
|
|
|
ui->filterWidthSpinBox->setMinimum(width);
|
|
ui->filterWidthDial->setMinimum(width);
|
|
|
|
setFilterWidth(std::max(m_filterWidth, width));
|
|
}
|
|
|
|
void WideGraph::setFilterEnabled(bool enabled) {
|
|
m_filterEnabled = enabled;
|
|
|
|
ui->filterCenterSyncButton->setEnabled(enabled);
|
|
ui->filterCenterSpinBox->setEnabled(enabled);
|
|
ui->filterCenterDial->setEnabled(enabled);
|
|
ui->filterWidthSpinBox->setEnabled(enabled);
|
|
ui->filterWidthDial->setEnabled(enabled);
|
|
|
|
setValueBlocked(enabled, ui->filterCheckBox);
|
|
|
|
ui->widePlot->setFilterEnabled(enabled);
|
|
}
|
|
|
|
void WideGraph::setFilterOpacityPercent(int const n) {
|
|
setValueBlocked(n, ui->filterOpacitySpinBox);
|
|
ui->widePlot->setFilterOpacity(int((float(n) / 100.0) * 255));
|
|
}
|
|
|
|
void WideGraph::setPeriod(int const ntrperiod) {
|
|
m_TRperiod = ntrperiod;
|
|
m_timeFormat = timeFormat(m_TRperiod);
|
|
}
|
|
|
|
void WideGraph::setFreq(int const audio_qrg) {
|
|
emit setXIT(audio_qrg);
|
|
ui->widePlot->setFreq(audio_qrg);
|
|
ui->offsetSpinBox->setValue(audio_qrg);
|
|
}
|
|
|
|
void WideGraph::setSubMode(int const n) { ui->widePlot->setSubMode(n); }
|
|
|
|
void WideGraph::on_spec2dComboBox_currentIndexChanged(int const index) {
|
|
ui->smoSpinBox->setEnabled(false);
|
|
switch (index) {
|
|
case 0:
|
|
ui->widePlot->setSpectrum(WF::Spectrum::Current);
|
|
break;
|
|
case 1:
|
|
ui->widePlot->setSpectrum(WF::Spectrum::Cumulative);
|
|
break;
|
|
case 2:
|
|
ui->widePlot->setSpectrum(WF::Spectrum::LinearAvg);
|
|
ui->smoSpinBox->setEnabled(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void WideGraph::setDialFreq(float const dialFreq) {
|
|
ui->widePlot->setDialFreq(dialFreq);
|
|
}
|
|
|
|
void WideGraph::setTimeControlsVisible(bool const visible) {
|
|
setControlsVisible(visible, false);
|
|
ui->tabWidget->setCurrentWidget(ui->timingTab);
|
|
}
|
|
|
|
bool WideGraph::timeControlsVisible() const {
|
|
return controlsVisible() && ui->tabWidget->currentWidget() == ui->timingTab;
|
|
}
|
|
|
|
void WideGraph::setControlsVisible(bool const visible, bool const controlTab) {
|
|
if (ui->controls_widget->isVisible() != visible) {
|
|
if (visible) {
|
|
if (m_sizes.isEmpty()) {
|
|
auto const width = ui->splitter->width();
|
|
m_sizes = {width, width / 4};
|
|
}
|
|
ui->splitter->setSizes(m_sizes);
|
|
if (controlTab) {
|
|
ui->tabWidget->setCurrentWidget(ui->controlTab);
|
|
}
|
|
} else {
|
|
m_sizes = ui->splitter->sizes();
|
|
}
|
|
ui->controls_widget->setVisible(visible);
|
|
}
|
|
}
|
|
|
|
bool WideGraph::controlsVisible() const {
|
|
return ui->controls_widget->isVisible();
|
|
}
|
|
|
|
void WideGraph::setBand(QString const &band) {
|
|
m_band = QString(4, ' ').append(band);
|
|
}
|
|
|
|
void WideGraph::on_fStartSpinBox_valueChanged(int const n) {
|
|
ui->widePlot->setStartFreq(n);
|
|
}
|
|
|
|
void WideGraph::readPalette() {
|
|
try {
|
|
ui->widePlot->setColors(
|
|
user_defined == m_waterfallPalette
|
|
? WF::Palette{m_userPalette}.interpolate()
|
|
: WF::Palette{m_palettes_path.absoluteFilePath(
|
|
m_waterfallPalette + ".pal")}
|
|
.interpolate());
|
|
} catch (std::exception const &e) {
|
|
JS8MessageBox::warning_message(this, tr("Read Palette"), e.what());
|
|
}
|
|
}
|
|
|
|
void WideGraph::on_paletteComboBox_activated(int const palette_index) {
|
|
m_waterfallPalette = ui->paletteComboBox->itemText(palette_index);
|
|
readPalette();
|
|
}
|
|
|
|
void WideGraph::on_cbFlatten_toggled(bool const flatten) {
|
|
ui->widePlot->setFlatten(flatten);
|
|
}
|
|
|
|
void WideGraph::on_adjust_palette_push_button_clicked(bool) {
|
|
try {
|
|
if (m_userPalette.design()) {
|
|
m_waterfallPalette = user_defined;
|
|
ui->paletteComboBox->setCurrentText(m_waterfallPalette);
|
|
readPalette();
|
|
}
|
|
} catch (std::exception const &e) {
|
|
JS8MessageBox::warning_message(this, tr("Read Palette"), e.what());
|
|
}
|
|
}
|
|
|
|
void WideGraph::on_gainSlider_valueChanged(int const value) {
|
|
ui->widePlot->setPlotGain(value);
|
|
}
|
|
|
|
void WideGraph::on_zeroSlider_valueChanged(int const value) {
|
|
ui->widePlot->setPlotZero(value);
|
|
}
|
|
|
|
void WideGraph::on_gain2dSlider_valueChanged(int const value) {
|
|
ui->widePlot->setPlot2dGain(value);
|
|
}
|
|
|
|
void WideGraph::on_zero2dSlider_valueChanged(int const value) {
|
|
ui->widePlot->setPlot2dZero(value);
|
|
}
|
|
|
|
void WideGraph::on_smoSpinBox_valueChanged(int const n) { m_nsmo = n; }
|
|
|
|
int WideGraph::smoothYellow() const { return m_nsmo; }
|
|
|
|
void WideGraph::on_sbPercent2dPlot_valueChanged(int const n) {
|
|
ui->widePlot->setPercent2D(n);
|
|
}
|
|
|
|
void WideGraph::on_filterCenterSpinBox_valueChanged(int const value) {
|
|
if (!ui->filterCenterSpinBox->hasFocus())
|
|
setFilterCenter(value);
|
|
}
|
|
|
|
void WideGraph::on_filterCenterSpinBox_editingFinished() {
|
|
setFilterCenter(ui->filterCenterSpinBox->value());
|
|
}
|
|
|
|
void WideGraph::on_filterWidthSpinBox_valueChanged(int const value) {
|
|
if (!ui->filterWidthSpinBox->hasFocus())
|
|
setFilterWidth(value);
|
|
}
|
|
|
|
void WideGraph::on_filterWidthSpinBox_editingFinished() {
|
|
setFilterWidth(ui->filterWidthSpinBox->value());
|
|
}
|
|
|
|
void WideGraph::on_filterCenterSyncButton_clicked() {
|
|
setFilterCenter(ui->offsetSpinBox->value());
|
|
}
|
|
|
|
void WideGraph::on_filterCheckBox_toggled(bool const b) { setFilterEnabled(b); }
|
|
|
|
void WideGraph::on_filterOpacitySpinBox_valueChanged(int const n) {
|
|
setFilterOpacityPercent(n);
|
|
}
|
|
|
|
void WideGraph::on_driftSpinBox_valueChanged(int const n) {
|
|
if (n != DriftingDateTime::drift())
|
|
emit want_new_drift(n);
|
|
}
|
|
|
|
void WideGraph::on_driftSyncButton_clicked() {
|
|
auto const now = QDateTime::currentDateTimeUtc();
|
|
qint64 const pos = m_TRperiod - (now.time().second() % m_TRperiod);
|
|
qint64 const neg = (now.time().second() % m_TRperiod) - m_TRperiod;
|
|
qint64 const sec = abs(neg) < pos ? neg : pos;
|
|
|
|
emit want_new_drift(sec * 1000);
|
|
}
|
|
|
|
void WideGraph::on_driftSyncEndButton_clicked() {
|
|
auto const now = QDateTime::currentDateTimeUtc();
|
|
qint64 const pos = m_TRperiod - (now.time().second() % m_TRperiod);
|
|
qint64 const neg = (now.time().second() % m_TRperiod) - m_TRperiod;
|
|
qint64 const sec = abs(neg) < pos ? neg + 2 : pos - 2;
|
|
|
|
emit want_new_drift(sec * 1000);
|
|
}
|
|
|
|
void WideGraph::on_driftSyncMinuteButton_clicked() {
|
|
auto const now = QDateTime::currentDateTimeUtc();
|
|
qint64 const val = now.time().second();
|
|
qint64 const sec = val < 30 ? -val : 60 - val;
|
|
|
|
emit want_new_drift(sec * 1000);
|
|
}
|
|
|
|
void WideGraph::on_driftSyncResetButton_clicked() { emit want_new_drift(0); }
|
|
|
|
void WideGraph::onDriftChanged(qint64 const n) {
|
|
qCDebug(widegraph_js8) << "Incoming new drift milliseconds:" << n
|
|
<< ", computer clock time:"
|
|
<< QDateTime::currentDateTimeUtc()
|
|
<< ", drifted time:"
|
|
<< DriftingDateTime::currentDateTimeUtc();
|
|
|
|
if (ui->driftSpinBox->value() != n)
|
|
ui->driftSpinBox->setValue(n);
|
|
}
|
|
|
|
Q_LOGGING_CATEGORY(widegraph_js8, "widegraph.js8", QtWarningMsg)
|