Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ set(GUI_SOURCES
src/gui/DvkPanel.cpp
src/gui/AmpApplet.cpp
src/gui/MeterApplet.cpp
src/gui/RadeApplet.cpp
src/gui/HealthApplet.cpp
src/gui/PersistentDialog.cpp
src/gui/ProfileManagerDialog.cpp
Expand Down
8 changes: 8 additions & 0 deletions src/gui/AppletPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
#include "ShackSwitchApplet.h"
#include "MeterApplet.h"
#include "HealthApplet.h"
#ifdef HAVE_RADE
#include "RadeApplet.h"
#endif
#ifdef HAVE_MQTT
#include "MqttApplet.h"
#endif
Expand Down Expand Up @@ -852,6 +855,11 @@ AppletPanel::AppletPanel(QWidget* parent) : QWidget(parent)
m_meterApplet = new MeterApplet;
m_appletOrder.append(makeEntry("MTR", "Meters", m_meterApplet, false, m_drawer, m_drawerLayout));

#ifdef HAVE_RADE
m_radeApplet = new RadeApplet;
m_appletOrder.append(makeEntry("RADE", "RADE Status", m_radeApplet, false, m_drawer, m_drawerLayout));
#endif

m_healthApplet = new HealthApplet;
m_appletOrder.append(makeEntry("HLTH", "Antenna Health", m_healthApplet, false, m_drawer, m_drawerLayout));

Expand Down
9 changes: 9 additions & 0 deletions src/gui/AppletPanel.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ class MeterApplet;
class HealthApplet;
class MqttApplet;
class FavoritesPickerDialog;
#ifdef HAVE_RADE
class RadeApplet;
#endif

// AppletPanel — right-side panel with a row of toggle buttons at the top,
// an S-Meter gauge below them, and a scrollable stack of applets.
Expand Down Expand Up @@ -119,6 +122,9 @@ class AppletPanel : public QWidget {
ShackSwitchApplet* ssApplet() { return m_ssApplet; }
MeterApplet* meterApplet() { return m_meterApplet; }
HealthApplet* healthApplet() { return m_healthApplet; }
#ifdef HAVE_RADE
RadeApplet* radeApplet() { return m_radeApplet; }
#endif
#ifdef HAVE_MQTT
MqttApplet* mqttApplet() { return m_mqttApplet; }
#endif
Expand Down Expand Up @@ -278,6 +284,9 @@ class AppletPanel : public QWidget {
ShackSwitchApplet* m_ssApplet{nullptr};
MeterApplet* m_meterApplet{nullptr};
HealthApplet* m_healthApplet{nullptr};
#ifdef HAVE_RADE
RadeApplet* m_radeApplet{nullptr};
#endif
#ifdef HAVE_MQTT
MqttApplet* m_mqttApplet{nullptr};
#endif
Expand Down
48 changes: 47 additions & 1 deletion src/gui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
#include "core/SpotModeResolver.h"
#ifdef HAVE_RADE
#include "core/RADEEngine.h"
#include "RadeApplet.h"
#endif
#if defined(Q_OS_MAC)
#include "core/VirtualAudioBridge.h"
Expand Down Expand Up @@ -17372,6 +17373,19 @@ void MainWindow::activateRADE(int sliceId)
}
}

if (auto* applet = m_appletPanel->radeApplet()) {
applet->setRadeActive(true);
applet->setRadeSynced(false);
connect(m_radeEngine, &RADEEngine::syncChanged,
applet, &RadeApplet::setRadeSynced);
connect(m_radeEngine, &RADEEngine::snrChanged,
applet, &RadeApplet::setRadeSnr);
connect(m_radeEngine, &RADEEngine::freqOffsetChanged,
applet, &RadeApplet::setRadeFreqOffset);
connect(m_radeEngine, &RADEEngine::eooCallsignReceived,
applet, &RadeApplet::setRadeCallsign, Qt::QueuedConnection);
}

// Store far-end callsign received in EOO frame for display / future use.
connect(m_radeEngine, &RADEEngine::eooCallsignReceived,
this, [this](const QString& callsign) {
Expand Down Expand Up @@ -17424,6 +17438,17 @@ void MainWindow::deactivateRADE()
m_radioModel.setDigitalVoiceTxSlice(-1);
m_audio->clearTxAccumulators(); // flush stale RADE modem data
m_appletPanel->phoneCwApplet()->setRadeActive(false);

if (auto* applet = m_appletPanel->radeApplet()) {
applet->setRadeActive(false);
if (m_radeEngine) {
disconnect(m_radeEngine, &RADEEngine::syncChanged, applet, nullptr);
disconnect(m_radeEngine, &RADEEngine::snrChanged, applet, nullptr);
disconnect(m_radeEngine, &RADEEngine::freqOffsetChanged, applet, nullptr);
disconnect(m_radeEngine, &RADEEngine::eooCallsignReceived, applet, nullptr);
}
}

// For hardware mics, reset to full gain — the radio controls hardware levels.
// PC mic keeps its PcMicGain so SSB sessions are unaffected.
if (m_radioModel.transmitModel().micSelection() != "PC") {
Expand Down Expand Up @@ -17512,6 +17537,13 @@ void MainWindow::activateFdvDisplay(int sliceId)
}
}

#ifdef HAVE_RADE
if (auto* applet = m_appletPanel->radeApplet()) {
applet->setRadeActive(true, QStringLiteral("FreeDV"));
applet->setRadeSynced(false);
}
#endif

m_fdvSnrMeterIndex = m_radioModel.meterModel()
.findMeter("EXT_WVF", "FreeDV_SNR");

Expand All @@ -17538,6 +17570,11 @@ void MainWindow::deactivateFdvDisplay()
}
}

#ifdef HAVE_RADE
if (auto* applet = m_appletPanel->radeApplet())
applet->setRadeActive(false);
#endif

disconnect(&m_radioModel.meterModel(), &MeterModel::meterUpdated,
this, &MainWindow::onFdvMeterUpdated);
disconnect(&m_radioModel, &RadioModel::metersChanged,
Expand Down Expand Up @@ -17565,9 +17602,18 @@ void MainWindow::onFdvMeterUpdated(int index, float value)
if (synced != m_fdvSynced) {
m_fdvSynced = synced;
vfo->setRadeSynced(synced);
#ifdef HAVE_RADE
if (auto* applet = m_appletPanel->radeApplet())
applet->setRadeSynced(synced);
#endif
}
if (synced)
if (synced) {
vfo->setRadeSnr(value);
#ifdef HAVE_RADE
if (auto* applet = m_appletPanel->radeApplet())
applet->setRadeSnr(value);
#endif
}
}

void MainWindow::onFdvMetersChanged()
Expand Down
164 changes: 164 additions & 0 deletions src/gui/RadeApplet.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#ifdef HAVE_RADE

#include "RadeApplet.h"
#include "core/ThemeManager.h"

#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>

namespace {
// Base style for muted-grey informational labels (SNR value, offset, "SNR" caption).
// Signal-specific colors (#e0e040 / #00ff88) are substituted into this template
// by setRadeSnr; all other uses take the grey directly.
constexpr auto kMutedStyle =
"QLabel { color: #8090a0; font-size: 10px;"
" background: transparent; border: none; padding: 0; margin: 0; }";
} // namespace

namespace AetherSDR {

RadeApplet::RadeApplet(QWidget* parent)
: QWidget(parent)
{
theme::setContainer(this, QStringLiteral("applet/rade"));

auto* vbox = new QVBoxLayout(this);
vbox->setContentsMargins(4, 4, 4, 4);
vbox->setSpacing(2);

m_inactiveLabel = new QLabel(tr("RADE inactive"));
m_inactiveLabel->setStyleSheet(
"QLabel { color: #506070; font-size: 10px; "
"background: transparent; border: none; }");
m_inactiveLabel->setAlignment(Qt::AlignCenter);
vbox->addWidget(m_inactiveLabel);

m_dataRows = new QWidget;
m_dataRows->setAttribute(Qt::WA_TranslucentBackground);
auto* dataVbox = new QVBoxLayout(m_dataRows);
dataVbox->setContentsMargins(0, 0, 0, 0);
dataVbox->setSpacing(3);

m_statusLabel = new QLabel;
m_statusLabel->setTextFormat(Qt::RichText);
ThemeManager::instance().applyStyleSheet(m_statusLabel,
"QLabel { color: {{color.accent}}; font-size: 10px; font-weight: bold;"
" background: transparent; border: none; padding: 0; margin: 0; }");
dataVbox->addWidget(m_statusLabel);

{
auto* row = new QHBoxLayout;
row->setContentsMargins(0, 0, 0, 0);
row->setSpacing(4);

auto* snrLbl = new QLabel(tr("SNR"));
snrLbl->setStyleSheet(kMutedStyle);

m_snrLabel = new QLabel(QStringLiteral("---"));
m_snrLabel->setStyleSheet(kMutedStyle);

row->addWidget(snrLbl);
row->addWidget(m_snrLabel);
row->addStretch();
dataVbox->addLayout(row);
}

m_callsignLabel = new QLabel;
ThemeManager::instance().applyStyleSheet(m_callsignLabel,
"QLabel { color: {{color.accent}}; font-size: 10px; font-weight: bold;"
" background: transparent; border: none; padding: 0; margin: 0; }");
m_callsignLabel->hide();
dataVbox->addWidget(m_callsignLabel);

m_offsetLabel = new QLabel;
m_offsetLabel->setStyleSheet(kMutedStyle);
m_offsetLabel->hide();
dataVbox->addWidget(m_offsetLabel);

dataVbox->addStretch();
m_dataRows->hide();
vbox->addWidget(m_dataRows);
vbox->addStretch();
}

void RadeApplet::setRadeActive(bool on, const QString& label)
{
m_active = on;
m_modeLabel = label.isEmpty() ? QStringLiteral("RADE") : label;

m_inactiveLabel->setVisible(!on);
m_dataRows->setVisible(on);

if (!on) {
m_statusLabel->setText({});
m_snrLabel->setText(QStringLiteral("---"));
m_snrLabel->setStyleSheet(kMutedStyle);
m_callsignLabel->hide();
m_callsignLabel->clear();
m_offsetLabel->hide();
} else {
// Show initial unsynced state — syncChanged fires once audio starts flowing
const QString led = QStringLiteral("<font color='#505050'>○</font>");
m_statusLabel->setText(m_modeLabel + QLatin1Char(' ') + led);
m_callsignLabel->clear();
m_callsignLabel->hide();
m_offsetLabel->hide();
}
}

void RadeApplet::setRadeSynced(bool synced)
{
if (!m_active) return;

const QString led = synced
? QStringLiteral("<font color='#00ff88'>●</font>")
: QStringLiteral("<font color='#505050'>○</font>");
m_statusLabel->setText(m_modeLabel + QLatin1Char(' ') + led);

if (!synced) {
m_snrLabel->setStyleSheet(kMutedStyle);
m_snrLabel->setText(QStringLiteral("---"));
m_offsetLabel->hide();
} else {
// New sync — clear callsign from previous transmission
m_callsignLabel->clear();
m_callsignLabel->hide();
}
}

void RadeApplet::setRadeSnr(float snrDb)
{
if (!m_active) return;
const QString color = (snrDb < 5.0f) ? QStringLiteral("#e0e040")
: QStringLiteral("#00ff88");
m_snrLabel->setStyleSheet(
QString("QLabel { color: %1; font-size: 10px;"
" background: transparent; border: none; padding: 0; margin: 0; }")
.arg(color));
m_snrLabel->setText(QString("%1dB").arg(qRound(snrDb)));
}

void RadeApplet::setRadeFreqOffset(float hz)
{
if (!m_active) return;
const QString sign = (hz >= 0) ? QStringLiteral("+") : QString{};
m_offsetLabel->setText(
QString("%1%2Hz").arg(sign).arg(static_cast<int>(hz)));
m_offsetLabel->show();
}

void RadeApplet::setRadeCallsign(const QString& callsign)
{
if (callsign.isEmpty()) {
m_callsignLabel->clear();
m_callsignLabel->hide();
} else {
m_callsignLabel->setText(callsign);
m_callsignLabel->show();
}
}

} // namespace AetherSDR

#endif // HAVE_RADE
39 changes: 39 additions & 0 deletions src/gui/RadeApplet.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#ifdef HAVE_RADE

#include <QWidget>

class QLabel;

namespace AetherSDR {

// Mirrors VfoWidget RADE status in the sidebar. Signal-driven from RADEEngine — no separate timer.
class RadeApplet : public QWidget {
Q_OBJECT
public:
explicit RadeApplet(QWidget* parent = nullptr);

public slots:
void setRadeActive(bool on, const QString& label = {});

void setRadeSynced(bool synced);
void setRadeSnr(float snrDb);
void setRadeFreqOffset(float hz);
void setRadeCallsign(const QString& callsign);

private:
QLabel* m_statusLabel{nullptr};
QLabel* m_snrLabel{nullptr};
QLabel* m_callsignLabel{nullptr};
QLabel* m_offsetLabel{nullptr};
QWidget* m_dataRows{nullptr};
QLabel* m_inactiveLabel{nullptr};

QString m_modeLabel;
bool m_active{false};
};

} // namespace AetherSDR

#endif // HAVE_RADE
Loading