1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-06-12 10:48:42 -04:00

Add initial freqdisplay feature plugin implementation

Agent-Logs-Url: https://github.com/srcejon/sdrangel/sessions/3b53c052-7f2b-4597-b509-d7cc17f3b0b0

Co-authored-by: srcejon <57259258+srcejon@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-18 15:13:46 +00:00
committed by GitHub
parent 82b43ff2f4
commit b2909fec64
14 changed files with 763 additions and 0 deletions
+1
View File
@@ -219,6 +219,7 @@ option(ENABLE_FEATURE_SKYMAP "Enable feature sky map plugin" ON)
option(ENABLE_FEATURE_SID "Enable feature sid plugin" ON)
option(ENABLE_FEATURE_MORSEDECODER "Enable feature morsedecoder plugin" ON)
option(ENABLE_FEATURE_DENOISER "Enable feature denoiser plugin" ON)
option(ENABLE_FEATURE_FREQDISPLAY "Enable feature freqdisplay plugin" ON)
# on windows always build external libraries
if(WIN32)
+6
View File
@@ -138,3 +138,9 @@ if (ENABLE_FEATURE_DENOISER AND RNNOISE_FOUND)
else()
message(STATUS "Not building denoiser (ENABLE_FEATURE_DENOISER=${ENABLE_FEATURE_DENOISER} RNNOISE_FOUND=${RNNOISE_FOUND})")
endif()
if (ENABLE_FEATURE_FREQDISPLAY)
add_subdirectory(freqdisplay)
else()
message(STATUS "Not building freqdisplay (ENABLE_FEATURE_FREQDISPLAY=${ENABLE_FEATURE_FREQDISPLAY})")
endif()
@@ -0,0 +1,64 @@
project(freqdisplay)
set(freqdisplay_SOURCES
freqdisplay.cpp
freqdisplaysettings.cpp
freqdisplayplugin.cpp
freqdisplaywebapiadapter.cpp
)
set(freqdisplay_HEADERS
freqdisplay.h
freqdisplaysettings.h
freqdisplayplugin.h
freqdisplaywebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(freqdisplay_SOURCES
${freqdisplay_SOURCES}
freqdisplaygui.cpp
freqdisplaygui.ui
)
set(freqdisplay_HEADERS
${freqdisplay_HEADERS}
freqdisplaygui.h
)
set(TARGET_NAME ${PLUGINS_PREFIX}featurefreqdisplay)
set(TARGET_LIB "Qt::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME ${PLUGINSSRV_PREFIX}featurefreqdisplaysrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
if(NOT Qt6_FOUND)
add_library(${TARGET_NAME} ${freqdisplay_SOURCES})
else()
qt_add_plugin(${TARGET_NAME} CLASS_NAME FreqDisplayPlugin ${freqdisplay_SOURCES})
endif()
if(NOT BUILD_SHARED_LIBS)
set_property(GLOBAL APPEND PROPERTY STATIC_PLUGINS_PROPERTY ${TARGET_NAME})
endif()
target_link_libraries(${TARGET_NAME} PRIVATE
Qt::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER})
endif()
@@ -0,0 +1,45 @@
#include <QDebug>
#include "freqdisplay.h"
const char* const FreqDisplay::m_featureIdURI = "sdrangel.feature.freqdisplay";
const char* const FreqDisplay::m_featureId = "FreqDisplay";
FreqDisplay::FreqDisplay(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface)
{
qDebug("FreqDisplay::FreqDisplay: webAPIAdapterInterface: %p", webAPIAdapterInterface);
setObjectName(m_featureId);
m_state = StIdle;
m_errorMessage = "FreqDisplay error";
}
bool FreqDisplay::handleMessage(const Message& cmd)
{
(void) cmd;
return false;
}
QByteArray FreqDisplay::serialize() const
{
return m_settings.serialize();
}
bool FreqDisplay::deserialize(const QByteArray& data)
{
if (!m_settings.deserialize(data)) {
m_settings.resetToDefaults();
return false;
}
return true;
}
void FreqDisplay::applySettings(const FreqDisplaySettings& settings, const QStringList& settingsKeys, bool force)
{
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
}
+39
View File
@@ -0,0 +1,39 @@
#ifndef INCLUDE_FEATURE_FREQDISPLAY_H_
#define INCLUDE_FEATURE_FREQDISPLAY_H_
#include <QStringList>
#include "feature/feature.h"
#include "freqdisplaysettings.h"
class WebAPIAdapterInterface;
class FreqDisplay : public Feature
{
Q_OBJECT
public:
FreqDisplay(WebAPIAdapterInterface *webAPIAdapterInterface);
~FreqDisplay() override = default;
void destroy() override { delete this; }
bool handleMessage(const Message& cmd) override;
void getIdentifier(QString& id) const override { id = objectName(); }
QString getIdentifier() const override { return objectName(); }
void getTitle(QString& title) const override { title = m_settings.m_title; }
QByteArray serialize() const override;
bool deserialize(const QByteArray& data) override;
const FreqDisplaySettings& getSettings() const { return m_settings; }
void applySettings(const FreqDisplaySettings& settings, const QStringList& settingsKeys, bool force = false);
static const char* const m_featureIdURI;
static const char* const m_featureId;
private:
FreqDisplaySettings m_settings;
};
#endif // INCLUDE_FEATURE_FREQDISPLAY_H_
@@ -0,0 +1,239 @@
#include <QFont>
#include <QResizeEvent>
#include "channel/channelwebapiutils.h"
#include "feature/featureuiset.h"
#include "ui_freqdisplaygui.h"
#include "freqdisplay.h"
#include "freqdisplaygui.h"
FreqDisplayGUI* FreqDisplayGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
return new FreqDisplayGUI(pluginAPI, featureUISet, feature);
}
void FreqDisplayGUI::destroy()
{
delete this;
}
void FreqDisplayGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
updateFrequencyText();
}
QByteArray FreqDisplayGUI::serialize() const
{
return m_settings.serialize();
}
bool FreqDisplayGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
m_feature->setWorkspaceIndex(m_settings.m_workspaceIndex);
displaySettings();
applySettings(true);
updateFrequencyText();
return true;
}
resetToDefaults();
return false;
}
FreqDisplayGUI::FreqDisplayGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
FeatureGUI(parent),
ui(new Ui::FreqDisplayGUI),
m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet),
m_freqDisplay(reinterpret_cast<FreqDisplay*>(feature)),
m_availableChannelOrFeatureHandler(QStringList(), "RT"),
m_doApplySettings(true)
{
(void) m_pluginAPI;
(void) m_featureUISet;
m_feature = feature;
setAttribute(Qt::WA_DeleteOnClose, true);
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
m_freqDisplay->setMessageQueueToGUI(&m_inputMessageQueue);
m_settings = m_freqDisplay->getSettings();
connect(
&m_availableChannelOrFeatureHandler,
&AvailableChannelOrFeatureHandler::channelsOrFeaturesChanged,
this,
&FreqDisplayGUI::channelsOrFeaturesChanged
);
m_availableChannelOrFeatureHandler.scanAvailableChannelsAndFeatures();
connect(ui->channels, qOverload<int>(&QComboBox::currentIndexChanged), this, &FreqDisplayGUI::on_channels_currentIndexChanged);
connect(&m_pollTimer, &QTimer::timeout, this, &FreqDisplayGUI::pollSelectedChannel);
m_pollTimer.start(1000);
displaySettings();
updateFrequencyText();
}
FreqDisplayGUI::~FreqDisplayGUI()
{
delete ui;
}
void FreqDisplayGUI::setWorkspaceIndex(int index)
{
m_settings.m_workspaceIndex = index;
m_feature->setWorkspaceIndex(index);
}
void FreqDisplayGUI::displaySettings()
{
setWindowTitle(m_settings.m_title);
setTitle(m_settings.m_title);
updateChannelList();
}
void FreqDisplayGUI::applySettings(bool force)
{
if (!m_doApplySettings) {
return;
}
QStringList settingsKeys;
settingsKeys.append("title");
settingsKeys.append("selectedChannel");
settingsKeys.append("workspaceIndex");
settingsKeys.append("geometryBytes");
m_freqDisplay->applySettings(m_settings, settingsKeys, force);
}
void FreqDisplayGUI::updateChannelList()
{
m_availableChannels = m_availableChannelOrFeatureHandler.getAvailableChannelOrFeatureList();
ui->channels->blockSignals(true);
ui->channels->clear();
int selectedIndex = -1;
for (int i = 0; i < m_availableChannels.size(); ++i)
{
const QString longId = m_availableChannels.at(i).getLongId();
ui->channels->addItem(longId);
if (longId == m_settings.m_selectedChannel) {
selectedIndex = i;
}
}
if ((selectedIndex < 0) && (m_availableChannels.size() > 0)) {
selectedIndex = 0;
}
if (selectedIndex >= 0)
{
ui->channels->setCurrentIndex(selectedIndex);
m_settings.m_selectedChannel = m_availableChannels.at(selectedIndex).getLongId();
}
else
{
m_settings.m_selectedChannel.clear();
}
ui->channels->blockSignals(false);
}
void FreqDisplayGUI::channelsOrFeaturesChanged(const QStringList& renameFrom, const QStringList& renameTo, const QStringList& removed, const QStringList& added)
{
(void) removed;
(void) added;
for (int i = 0; i < renameFrom.size() && i < renameTo.size(); ++i)
{
if (m_settings.m_selectedChannel == renameFrom.at(i)) {
m_settings.m_selectedChannel = renameTo.at(i);
}
}
updateChannelList();
applySettings();
updateFrequencyText();
}
void FreqDisplayGUI::on_channels_currentIndexChanged(int index)
{
if ((index >= 0) && (index < m_availableChannels.size())) {
m_settings.m_selectedChannel = m_availableChannels.at(index).getLongId();
} else {
m_settings.m_selectedChannel.clear();
}
applySettings();
updateFrequencyText();
}
void FreqDisplayGUI::pollSelectedChannel()
{
updateFrequencyText();
}
void FreqDisplayGUI::updateFrequencyText()
{
if (m_settings.m_selectedChannel.isEmpty())
{
ui->frequencyValue->setText(tr("No channel selected"));
updateFrequencyFont();
return;
}
const int channelListIndex = m_availableChannels.indexOfLongId(m_settings.m_selectedChannel);
if (channelListIndex < 0)
{
ui->frequencyValue->setText(tr("Selected channel unavailable"));
updateFrequencyFont();
return;
}
const auto& selectedChannel = m_availableChannels.at(channelListIndex);
double centerFrequencyHz = 0.0;
int offsetHz = 0;
if (!ChannelWebAPIUtils::getCenterFrequency(selectedChannel.m_superIndex, centerFrequencyHz)
|| !ChannelWebAPIUtils::getFrequencyOffset(selectedChannel.m_superIndex, selectedChannel.m_index, offsetHz))
{
ui->frequencyValue->setText(tr("Frequency unavailable"));
updateFrequencyFont();
return;
}
const qint64 absoluteFrequency = qRound64(centerFrequencyHz) + static_cast<qint64>(offsetHz);
ui->frequencyValue->setText(tr("%1 Hz").arg(QLocale().toString(absoluteFrequency)));
updateFrequencyFont();
}
void FreqDisplayGUI::updateFrequencyFont()
{
const int minDimension = qMin(ui->frequencyValue->width(), ui->frequencyValue->height());
const int pointSize = qMax(10, static_cast<int>(minDimension * 0.22));
QFont font = ui->frequencyValue->font();
font.setPointSize(pointSize);
ui->frequencyValue->setFont(font);
}
void FreqDisplayGUI::resizeEvent(QResizeEvent *event)
{
FeatureGUI::resizeEvent(event);
updateFrequencyFont();
}
@@ -0,0 +1,68 @@
#ifndef INCLUDE_FEATURE_FREQDISPLAYGUI_H_
#define INCLUDE_FEATURE_FREQDISPLAYGUI_H_
#include <QTimer>
#include "availablechannelorfeaturehandler.h"
#include "feature/featuregui.h"
#include "util/messagequeue.h"
#include "freqdisplaysettings.h"
class PluginAPI;
class FeatureUISet;
class FreqDisplay;
class Feature;
namespace Ui {
class FreqDisplayGUI;
}
class FreqDisplayGUI : public FeatureGUI
{
Q_OBJECT
public:
static FreqDisplayGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
void destroy() override;
void resetToDefaults() override;
QByteArray serialize() const override;
bool deserialize(const QByteArray& data) override;
MessageQueue *getInputMessageQueue() override { return &m_inputMessageQueue; }
void setWorkspaceIndex(int index) override;
int getWorkspaceIndex() const override { return m_settings.m_workspaceIndex; }
void setGeometryBytes(const QByteArray& blob) override { m_settings.m_geometryBytes = blob; }
QByteArray getGeometryBytes() const override { return m_settings.m_geometryBytes; }
protected:
void resizeEvent(QResizeEvent *event) override;
private:
Ui::FreqDisplayGUI* ui;
PluginAPI* m_pluginAPI;
FeatureUISet* m_featureUISet;
FreqDisplay* m_freqDisplay;
FreqDisplaySettings m_settings;
MessageQueue m_inputMessageQueue;
AvailableChannelOrFeatureHandler m_availableChannelOrFeatureHandler;
AvailableChannelOrFeatureList m_availableChannels;
QTimer m_pollTimer;
bool m_doApplySettings;
explicit FreqDisplayGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
~FreqDisplayGUI() override;
void displaySettings();
void applySettings(bool force = false);
void updateChannelList();
void updateFrequencyText();
void updateFrequencyFont();
private slots:
void channelsOrFeaturesChanged(const QStringList& renameFrom, const QStringList& renameTo, const QStringList& removed, const QStringList& added);
void on_channels_currentIndexChanged(int index);
void pollSelectedChannel();
};
#endif // INCLUDE_FEATURE_FREQDISPLAYGUI_H_
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FreqDisplayGUI</class>
<widget class="RollupContents" name="FreqDisplayGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>220</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>420</width>
<height>180</height>
</size>
</property>
<property name="windowTitle">
<string>Frequency display</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<height>220</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<layout class="QHBoxLayout" name="channelLayout">
<item>
<widget class="QLabel" name="channelsLabel">
<property name="text">
<string>Channel</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="channels">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="frequencyValue">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="text">
<string>No channel selected</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupContents</class>
<extends>QWidget</extends>
<header>gui/rollupcontents.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,62 @@
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "freqdisplaygui.h"
#endif
#include "freqdisplay.h"
#include "freqdisplayplugin.h"
#include "freqdisplaywebapiadapter.h"
const PluginDescriptor FreqDisplayPlugin::m_pluginDescriptor = {
FreqDisplay::m_featureId,
QStringLiteral("Frequency display"),
QStringLiteral("7.24.0"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
FreqDisplayPlugin::FreqDisplayPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& FreqDisplayPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void FreqDisplayPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerFeature(FreqDisplay::m_featureIdURI, FreqDisplay::m_featureId, this);
}
#ifdef SERVER_MODE
FeatureGUI* FreqDisplayPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
(void) featureUISet;
(void) feature;
return nullptr;
}
#else
FeatureGUI* FreqDisplayPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
return FreqDisplayGUI::create(m_pluginAPI, featureUISet, feature);
}
#endif
Feature* FreqDisplayPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
{
return new FreqDisplay(webAPIAdapterInterface);
}
FeatureWebAPIAdapter* FreqDisplayPlugin::createFeatureWebAPIAdapter() const
{
return new FreqDisplayWebAPIAdapter();
}
@@ -0,0 +1,32 @@
#ifndef INCLUDE_FEATURE_FREQDISPLAYPLUGIN_H
#define INCLUDE_FEATURE_FREQDISPLAYPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class FeatureGUI;
class WebAPIAdapterInterface;
class FreqDisplayPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.feature.freqdisplay")
public:
explicit FreqDisplayPlugin(QObject* parent = nullptr);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const override;
Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const override;
FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const override;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_FEATURE_FREQDISPLAYPLUGIN_H
@@ -0,0 +1,68 @@
#include "util/simpleserializer.h"
#include "freqdisplaysettings.h"
FreqDisplaySettings::FreqDisplaySettings()
{
resetToDefaults();
}
void FreqDisplaySettings::resetToDefaults()
{
m_title = "Frequency display";
m_selectedChannel.clear();
m_workspaceIndex = -1;
m_geometryBytes.clear();
}
QByteArray FreqDisplaySettings::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_title);
s.writeString(2, m_selectedChannel);
s.writeS32(3, m_workspaceIndex);
s.writeBlob(4, m_geometryBytes);
return s.final();
}
bool FreqDisplaySettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() != 1)
{
resetToDefaults();
return false;
}
d.readString(1, &m_title, "Frequency display");
d.readString(2, &m_selectedChannel, "");
d.readS32(3, &m_workspaceIndex, -1);
d.readBlob(4, &m_geometryBytes);
return true;
}
void FreqDisplaySettings::applySettings(const QStringList& settingsKeys, const FreqDisplaySettings& settings)
{
if (settingsKeys.contains("title")) {
m_title = settings.m_title;
}
if (settingsKeys.contains("selectedChannel")) {
m_selectedChannel = settings.m_selectedChannel;
}
if (settingsKeys.contains("workspaceIndex")) {
m_workspaceIndex = settings.m_workspaceIndex;
}
if (settingsKeys.contains("geometryBytes")) {
m_geometryBytes = settings.m_geometryBytes;
}
}
@@ -0,0 +1,24 @@
#ifndef INCLUDE_FEATURE_FREQDISPLAYSETTINGS_H_
#define INCLUDE_FEATURE_FREQDISPLAYSETTINGS_H_
#include <QByteArray>
#include <QString>
#include <QStringList>
struct FreqDisplaySettings
{
QString m_title;
QString m_selectedChannel;
int m_workspaceIndex;
QByteArray m_geometryBytes;
FreqDisplaySettings();
~FreqDisplaySettings() = default;
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
void applySettings(const QStringList& settingsKeys, const FreqDisplaySettings& settings);
};
#endif // INCLUDE_FEATURE_FREQDISPLAYSETTINGS_H_
@@ -0,0 +1 @@
#include "freqdisplaywebapiadapter.h"
@@ -0,0 +1,20 @@
#ifndef INCLUDE_FREQDISPLAY_WEBAPIADAPTER_H
#define INCLUDE_FREQDISPLAY_WEBAPIADAPTER_H
#include "feature/featurewebapiadapter.h"
#include "freqdisplaysettings.h"
class FreqDisplayWebAPIAdapter : public FeatureWebAPIAdapter {
public:
FreqDisplayWebAPIAdapter() = default;
~FreqDisplayWebAPIAdapter() override = default;
QByteArray serialize() const override { return m_settings.serialize(); }
bool deserialize(const QByteArray& data) override { return m_settings.deserialize(data); }
private:
FreqDisplaySettings m_settings;
};
#endif // INCLUDE_FREQDISPLAY_WEBAPIADAPTER_H