From 7075fded9731abc528c395185e7e45f603df9231 Mon Sep 17 00:00:00 2001 From: Robbie Foust Date: Wed, 20 May 2026 23:37:19 -0400 Subject: [PATCH] Sync WNB panadapter status from radio --- src/gui/MainWindow.cpp | 22 +++++- src/gui/SpectrumOverlayMenu.cpp | 6 ++ src/gui/SpectrumOverlayMenu.h | 1 + src/gui/SpectrumWidget.cpp | 117 ++++++++++++++++++-------------- src/gui/SpectrumWidget.h | 13 +++- src/models/PanadapterModel.cpp | 30 ++++++-- src/models/PanadapterModel.h | 3 + 7 files changed, 135 insertions(+), 57 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 3ffcd463a..aa49f187d 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2635,6 +2635,17 @@ MainWindow::MainWindow(QWidget* parent) connect(pan, &PanadapterModel::wideChanged, sw, &SpectrumWidget::setWideActive); sw->setWideActive(pan->wideActive()); + connect(pan, &PanadapterModel::wnbStateChanged, + sw, &SpectrumWidget::syncWnbState, + Qt::UniqueConnection); + connect(pan, &PanadapterModel::wnbStateChanged, + sw->overlayMenu(), &SpectrumOverlayMenu::syncWnbState, + Qt::UniqueConnection); + sw->syncWnbState(pan->wnbActive(), pan->wnbLevel(), + pan->wnbUpdating()); + sw->overlayMenu()->syncWnbState(pan->wnbActive(), + pan->wnbLevel(), + pan->wnbUpdating()); } return; } @@ -10380,6 +10391,14 @@ void MainWindow::wirePanadapter(PanadapterApplet* applet) connect(pan, &PanadapterModel::wideChanged, sw, &SpectrumWidget::setWideActive); sw->setWideActive(pan->wideActive()); + connect(pan, &PanadapterModel::wnbStateChanged, + sw, &SpectrumWidget::syncWnbState, + Qt::UniqueConnection); + connect(pan, &PanadapterModel::wnbStateChanged, + menu, &SpectrumOverlayMenu::syncWnbState, + Qt::UniqueConnection); + sw->syncWnbState(pan->wnbActive(), pan->wnbLevel(), pan->wnbUpdating()); + menu->syncWnbState(pan->wnbActive(), pan->wnbLevel(), pan->wnbUpdating()); // Route confirmed level changes (min_dbm / max_dbm) from the radio to // this pan's spectrum widget. Using the per-pan PanadapterModel signal @@ -11168,7 +11187,8 @@ void MainWindow::wirePanadapter(PanadapterApplet* applet) this, [this, sw, applet](bool on) { m_radioModel.sendCommand( QString("display pan set %1 wnb=%2").arg(applet->panId()).arg(on ? 1 : 0)); - sw->setWnbActive(on); + // The radio echoes WNB state, level, and normalization progress. + // Let PanadapterModel drive the spectrum indicator and related menus. auto& s = AppSettings::instance(); s.setValue(sw->settingsKey("DisplayWnbEnabled"), on ? "True" : "False"); s.save(); diff --git a/src/gui/SpectrumOverlayMenu.cpp b/src/gui/SpectrumOverlayMenu.cpp index 67d01b86c..d2f384b16 100644 --- a/src/gui/SpectrumOverlayMenu.cpp +++ b/src/gui/SpectrumOverlayMenu.cpp @@ -1263,8 +1263,14 @@ void SpectrumOverlayMenu::toggleDisplayPanel() } void SpectrumOverlayMenu::setWnbState(bool on, int level) +{ + syncWnbState(on, level, false); +} + +void SpectrumOverlayMenu::syncWnbState(bool on, int level, bool updating) { QSignalBlocker b1(m_wnbBtn), b2(m_wnbSlider); + Q_UNUSED(updating); m_wnbBtn->setChecked(on); m_wnbSlider->setValue(level); m_wnbLabel->setText(QString::number(level)); diff --git a/src/gui/SpectrumOverlayMenu.h b/src/gui/SpectrumOverlayMenu.h index 4688a4ff1..02a25bf05 100644 --- a/src/gui/SpectrumOverlayMenu.h +++ b/src/gui/SpectrumOverlayMenu.h @@ -62,6 +62,7 @@ class SpectrumOverlayMenu : public QWidget { // Connect/disconnect the ANT panel to a slice model. void setSlice(SliceModel* slice); void setWnbState(bool on, int level); + void syncWnbState(bool on, int level, bool updating); void setRfGain(int gain); void setRfGainRange(int low, int high, int step); void syncNoiseFloorPosition(int pos); diff --git a/src/gui/SpectrumWidget.cpp b/src/gui/SpectrumWidget.cpp index aa0ae26a0..413d680e4 100644 --- a/src/gui/SpectrumWidget.cpp +++ b/src/gui/SpectrumWidget.cpp @@ -4551,13 +4551,17 @@ void SpectrumWidget::renderGpuFrame(QRhiCommandBuffer* cb) if (m_centerMhz != m_lastDetectCenter || m_bandwidthMhz != m_lastDetectBw || m_refLevel != m_lastDetectRef || m_dynamicRange != m_lastDetectDyn || m_spectrumFrac != m_lastDetectFrac || - m_wnbActive != m_lastDetectWnb || m_rfGainValue != m_lastDetectRfGain || + m_wnbActive != m_lastDetectWnb || + m_wnbUpdating != m_lastDetectWnbUpdating || + m_rfGainValue != m_lastDetectRfGain || m_wideActive != m_lastDetectWide) { markOverlayDirty(); m_lastDetectCenter = m_centerMhz; m_lastDetectBw = m_bandwidthMhz; m_lastDetectRef = m_refLevel; m_lastDetectDyn = m_dynamicRange; m_lastDetectFrac = m_spectrumFrac; - m_lastDetectWnb = m_wnbActive; m_lastDetectRfGain = m_rfGainValue; + m_lastDetectWnb = m_wnbActive; + m_lastDetectWnbUpdating = m_wnbUpdating; + m_lastDetectRfGain = m_rfGainValue; m_lastDetectWide = m_wideActive; } } @@ -4726,50 +4730,56 @@ void SpectrumWidget::renderGpuFrame(QRhiCommandBuffer* cb) if (m_wnbActive || m_rfGainValue != 0 || showProp || m_wideActive) { QFont indFont(p.font().family(), 14, QFont::Bold); p.setFont(indFont); - p.setPen(QColor(0xc8, 0xd8, 0xe8, 180)); + const QColor indicatorColor(0xc8, 0xd8, 0xe8, 180); + const QColor wnbDimColor(0xc8, 0xd8, 0xe8, 84); const QFontMetrics fm(indFont); - int y = specRect.top() + fm.ascent() + 4; - // Build combined label (left to right: prop, WNB, RF gain, WIDE), right-align - QString label; - if (showProp) { - label += QString("K%1 A%2 SFI %3") - .arg(m_propKIndex, 0, 'f', 2) - .arg(m_propAIndex) - .arg(m_propSfi); - } - if (m_wnbActive) { - if (!label.isEmpty()) { label += QStringLiteral(" "); } - label += QStringLiteral("WNB"); + const int y = specRect.top() + fm.ascent() + 4; + const int rightEdge = specRect.right() - DBM_STRIP_W - 8; + int x = rightEdge; + int leftEdge = rightEdge; + auto drawSegment = [&](const QString& text, const QColor& color) { + const int textWidth = fm.horizontalAdvance(text); + x -= textWidth; + leftEdge = x; + p.setPen(color); + p.drawText(x, y, text); + x -= 10; + }; + + if (m_wideActive) { + drawSegment(QStringLiteral("WIDE"), indicatorColor); } if (m_rfGainValue != 0) { - if (!label.isEmpty()) { label += QStringLiteral(" "); } - label += QStringLiteral("%1%2 dB") - .arg(m_rfGainValue > 0 ? "+" : "").arg(m_rfGainValue); + drawSegment( + QStringLiteral("%1%2 dB") + .arg(m_rfGainValue > 0 ? "+" : "") + .arg(m_rfGainValue), + indicatorColor); } - if (m_wideActive) { - if (!label.isEmpty()) { label += QStringLiteral(" "); } - label += QStringLiteral("WIDE"); + if (m_wnbActive) { + drawSegment(QStringLiteral("WNB"), + m_wnbUpdating ? wnbDimColor : indicatorColor); } - int x = specRect.right() - DBM_STRIP_W - 8 - fm.horizontalAdvance(label); - p.drawText(x, y, label); - - // Bounding rect of the full strip — used to suppress - // single-click-to-tune when clicking on these indicators (#1564). - m_indicatorStripRect = QRect(x, y - fm.ascent(), - fm.horizontalAdvance(label), - fm.height()); - - // Store click rect for the prop portion only if (showProp) { - QString propText = QString("K%1 A%2 SFI %3") + const QString propText = QString("K%1 A%2 SFI %3") .arg(m_propKIndex, 0, 'f', 2) .arg(m_propAIndex) .arg(m_propSfi); - int propW = fm.horizontalAdvance(propText); + const int propW = fm.horizontalAdvance(propText); + x -= propW; + leftEdge = x; + p.setPen(indicatorColor); + p.drawText(x, y, propText); m_propClickRect = QRect(x, y - fm.ascent(), propW, fm.height()); } else { m_propClickRect = QRect(); } + + // Bounding rect of the full strip — used to suppress + // single-click-to-tune when clicking on these indicators (#1564). + m_indicatorStripRect = QRect(leftEdge, y - fm.ascent(), + rightEdge - leftEdge, + fm.height()); } else { m_indicatorStripRect = QRect(); } @@ -5413,49 +5423,54 @@ void SpectrumWidget::paintEvent(QPaintEvent* ev) indFont.setPointSize(18); indFont.setBold(true); p.setFont(indFont); - p.setPen(QColor(255, 255, 255, 84)); + const QColor indicatorColor(255, 255, 255, 84); + const QColor wnbActiveColor(0xc8, 0xd8, 0xe8, 180); + const QColor wnbDimColor(0xc8, 0xd8, 0xe8, 84); const QFontMetrics fm(indFont); const int rightEdge = specRect.right() - DBM_STRIP_W - 6; const int topY = specRect.top() + fm.ascent() + 2; int x = rightEdge; + int leftEdge = rightEdge; + auto drawSegment = [&](const QString& text, const QColor& color) { + const int textWidth = fm.horizontalAdvance(text); + x -= textWidth; + leftEdge = x; + p.setPen(color); + p.drawText(x, topY, text); + x -= 10; + }; // WIDE (rightmost) if (m_wideActive) { - int ww = fm.horizontalAdvance("WIDE"); - x -= ww; - p.drawText(x, topY, "WIDE"); - x -= 10; + drawSegment(QStringLiteral("WIDE"), indicatorColor); } // RF Gain (to the left of WIDE) if (m_rfGainValue != 0) { - QString gainStr = (m_rfGainValue > 0) + const QString gainStr = (m_rfGainValue > 0) ? QString("+%1dB").arg(m_rfGainValue) : QString("%1dB").arg(m_rfGainValue); - int gw = fm.horizontalAdvance(gainStr); - x -= gw; - p.drawText(x, topY, gainStr); - x -= 10; // gap between labels + drawSegment(gainStr, indicatorColor); } // WNB (to the left of RF Gain) if (m_wnbActive) { - int ww = fm.horizontalAdvance("WNB"); - x -= ww; - p.drawText(x, topY, "WNB"); - x -= 10; + drawSegment(QStringLiteral("WNB"), + m_wnbUpdating ? wnbDimColor : wnbActiveColor); } // Prop forecast (leftmost: "K3 A12 SFI 110") if (showProp) { - QString propStr = QString("K%1 A%2 SFI %3") + const QString propStr = QString("K%1 A%2 SFI %3") .arg(m_propKIndex, 0, 'f', 2) .arg(m_propAIndex) .arg(m_propSfi); - int pw = fm.horizontalAdvance(propStr); + const int pw = fm.horizontalAdvance(propStr); x -= pw; + leftEdge = x; + p.setPen(indicatorColor); p.drawText(x, topY, propStr); m_propClickRect = QRect(x, topY - fm.ascent(), pw, fm.height()); } else { @@ -5464,8 +5479,8 @@ void SpectrumWidget::paintEvent(QPaintEvent* ev) // Bounding rect of the full strip (prop + WNB + RF Gain + WIDE) — // used to suppress single-click-to-tune within (#1564). - m_indicatorStripRect = QRect(x, topY - fm.ascent(), - rightEdge - x, fm.height()); + m_indicatorStripRect = QRect(leftEdge, topY - fm.ascent(), + rightEdge - leftEdge, fm.height()); } else { m_indicatorStripRect = QRect(); } diff --git a/src/gui/SpectrumWidget.h b/src/gui/SpectrumWidget.h index 09f0fc417..aef33e4fd 100644 --- a/src/gui/SpectrumWidget.h +++ b/src/gui/SpectrumWidget.h @@ -189,9 +189,18 @@ class SpectrumWidget : public SPECTRUM_BASE_CLASS { // WNB and RF gain state for on-screen indicators. bool wnbActive() const { return m_wnbActive; } + bool wnbUpdating() const { return m_wnbUpdating; } int rfGainValue() const { return m_rfGainValue; } bool wideActive() const { return m_wideActive; } - void setWnbActive(bool on) { m_wnbActive = on; markOverlayDirty(); } + void setWnbActive(bool on) { syncWnbState(on, 0, false); } + void syncWnbState(bool on, int level, bool updating) { + Q_UNUSED(level); + if (m_wnbActive != on || m_wnbUpdating != updating) { + m_wnbActive = on; + m_wnbUpdating = updating; + markOverlayDirty(); + } + } void setRfGain(int gain) { if (m_rfGainValue != gain) { m_rfGainValue = gain; @@ -800,6 +809,7 @@ class SpectrumWidget : public SPECTRUM_BASE_CLASS { // On-screen indicators (WNB, RF Gain) bool m_wnbActive{false}; + bool m_wnbUpdating{false}; int m_rfGainValue{0}; bool m_wideActive{false}; @@ -849,6 +859,7 @@ class SpectrumWidget : public SPECTRUM_BASE_CLASS { float m_lastDetectDyn{0}; float m_lastDetectFrac{0}; bool m_lastDetectWnb{false}; + bool m_lastDetectWnbUpdating{false}; int m_lastDetectRfGain{0}; bool m_lastDetectWide{false}; diff --git a/src/models/PanadapterModel.cpp b/src/models/PanadapterModel.cpp index a8f9dbf4f..5f793050b 100644 --- a/src/models/PanadapterModel.cpp +++ b/src/models/PanadapterModel.cpp @@ -1,6 +1,7 @@ #include "PanadapterModel.h" #include "core/PerfTelemetry.h" #include +#include namespace AetherSDR { @@ -79,15 +80,36 @@ void PanadapterModel::applyPanStatus(const QMap& kvs) m_preamp = pre; } } + // FlexLib v4.2.18 exposes wnb_updating on display pan status while the + // radio normalizes the SCU-level WNB threshold; keep it distinct from + // the per-pan WNB enable flag. + bool wnbStateDirty = false; if (kvs.contains("wnb")) { - bool w = kvs["wnb"].toInt() != 0; - int lvl = kvs.value("wnb_level", QString::number(m_wnbLevel)).toInt(); - if (w != m_wnbActive || lvl != m_wnbLevel) { + const bool w = kvs["wnb"].toInt() != 0; + if (w != m_wnbActive) { m_wnbActive = w; + wnbStateDirty = true; + } + } + if (kvs.contains("wnb_level")) { + bool ok = false; + const int lvl = std::clamp(kvs["wnb_level"].toInt(&ok), 0, 100); + if (ok && lvl != m_wnbLevel) { m_wnbLevel = lvl; - emit wnbChanged(m_wnbActive, m_wnbLevel); + wnbStateDirty = true; } } + if (kvs.contains("wnb_updating")) { + const bool updating = kvs["wnb_updating"].toInt() != 0; + if (updating != m_wnbUpdating) { + m_wnbUpdating = updating; + wnbStateDirty = true; + } + } + if (wnbStateDirty) { + emit wnbChanged(m_wnbActive, m_wnbLevel); + emit wnbStateChanged(m_wnbActive, m_wnbLevel, m_wnbUpdating); + } if (kvs.contains("wide")) { bool wide = kvs["wide"].toInt() != 0; if (wide != m_wideActive) { diff --git a/src/models/PanadapterModel.h b/src/models/PanadapterModel.h index 9b7069093..e13e8d438 100644 --- a/src/models/PanadapterModel.h +++ b/src/models/PanadapterModel.h @@ -38,6 +38,7 @@ class PanadapterModel : public QObject { void setRfGainInfo(int low, int high, int step); bool wnbActive() const { return m_wnbActive; } int wnbLevel() const { return m_wnbLevel; } + bool wnbUpdating() const { return m_wnbUpdating; } bool wideActive() const { return m_wideActive; } int fps() const { return m_fps; } int waterfallLineDuration() const { return m_waterfallLineDuration; } @@ -68,6 +69,7 @@ class PanadapterModel : public QObject { void rfGainChanged(int gain); void rfGainInfoChanged(int low, int high, int step); void wnbChanged(bool active, int level); + void wnbStateChanged(bool active, int level, bool updating); void wideChanged(bool active); void fpsChanged(int fps); void fpsReported(int fps); @@ -90,6 +92,7 @@ class PanadapterModel : public QObject { int m_rfGainHigh{32}; int m_rfGainStep{8}; bool m_wnbActive{false}; + bool m_wnbUpdating{false}; bool m_wideActive{false}; int m_wnbLevel{50}; int m_fps{-1};