js8call/JS8_UI/widegraph.cpp
Chris-AC9KH 356bdc0f50 Code cleanup - move loose source and header files in source tree to group folders
Rename all header files with .h extension
Fix function declaration in HamlibTransceiver.h that overrode member function but was not marked override
Remove unused variable in Modulator.cpp
Remove unused files from source tree
Remove duplicate LazyFillComboBox files
Reformat codebase to LLVM C++ standard
2026-01-02 16:46:38 -06:00

812 lines
29 KiB
C++

#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)