From 219ece573c4a6213a8a8f079ff2dd4aa0ba1d547 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:28:48 +0000 Subject: [PATCH] feat: implement FreqDisplay WebAPI (webapiSettingsGet/PutPatch, format/update, reverseSend) Agent-Logs-Url: https://github.com/srcejon/sdrangel/sessions/373fd0a8-4ef8-4849-8b8e-adb0a988230d Co-authored-by: srcejon <57259258+srcejon@users.noreply.github.com> --- plugins/feature/freqdisplay/freqdisplay.cpp | 289 +++++++++++++++++- plugins/feature/freqdisplay/freqdisplay.h | 61 +++- .../feature/freqdisplay/freqdisplaygui.cpp | 30 ++ plugins/feature/freqdisplay/freqdisplaygui.h | 3 + .../freqdisplay/freqdisplaywebapiadapter.cpp | 6 +- 5 files changed, 385 insertions(+), 4 deletions(-) diff --git a/plugins/feature/freqdisplay/freqdisplay.cpp b/plugins/feature/freqdisplay/freqdisplay.cpp index e9dcb8e83..c83173291 100644 --- a/plugins/feature/freqdisplay/freqdisplay.cpp +++ b/plugins/feature/freqdisplay/freqdisplay.cpp @@ -17,9 +17,19 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include +#include +#include + +#include "SWGFeatureSettings.h" +#include "SWGFreqDisplaySettings.h" + +#include "settings/serializable.h" #include "freqdisplay.h" +MESSAGE_CLASS_DEFINITION(FreqDisplay::MsgConfigureFreqDisplay, Message) + const char* const FreqDisplay::m_featureIdURI = "sdrangel.feature.freqdisplay"; const char* const FreqDisplay::m_featureId = "FreqDisplay"; @@ -29,11 +39,36 @@ FreqDisplay::FreqDisplay(WebAPIAdapterInterface *webAPIAdapterInterface) : qDebug("FreqDisplay::FreqDisplay: webAPIAdapterInterface: %p", webAPIAdapterInterface); setObjectName(m_featureId); m_state = StIdle; + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &FreqDisplay::networkManagerFinished + ); +} + +FreqDisplay::~FreqDisplay() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &FreqDisplay::networkManagerFinished + ); + delete m_networkManager; } bool FreqDisplay::handleMessage(const Message& cmd) { - (void) cmd; + if (MsgConfigureFreqDisplay::match(cmd)) + { + MsgConfigureFreqDisplay& cfg = (MsgConfigureFreqDisplay&) cmd; + qDebug() << "FreqDisplay::handleMessage: MsgConfigureFreqDisplay"; + applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce()); + return true; + } + return false; } @@ -54,9 +89,261 @@ bool FreqDisplay::deserialize(const QByteArray& data) void FreqDisplay::applySettings(const FreqDisplaySettings& settings, const QStringList& settingsKeys, bool force) { + qDebug() << "FreqDisplay::applySettings:" << " force: " << force; + + if (settings.m_useReverseAPI) + { + bool fullUpdate = (settingsKeys.contains("useReverseAPI") && settings.m_useReverseAPI) || + settingsKeys.contains("reverseAPIAddress") || + settingsKeys.contains("reverseAPIPort") || + settingsKeys.contains("reverseAPIFeatureSetIndex") || + settingsKeys.contains("reverseAPIFeatureIndex"); + webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); + } + if (force) { m_settings = settings; } else { m_settings.applySettings(settingsKeys, settings); } } + +int FreqDisplay::webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setFreqDisplaySettings(new SWGSDRangel::SWGFreqDisplaySettings()); + response.getFreqDisplaySettings()->init(); + webapiFormatFeatureSettings(response, m_settings); + return 200; +} + +int FreqDisplay::webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + FreqDisplaySettings settings = m_settings; + webapiUpdateFeatureSettings(settings, featureSettingsKeys, response); + + MsgConfigureFreqDisplay *msg = MsgConfigureFreqDisplay::create(settings, featureSettingsKeys, force); + m_inputMessageQueue.push(msg); + + qDebug("FreqDisplay::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) + { + MsgConfigureFreqDisplay *msgToGUI = MsgConfigureFreqDisplay::create(settings, featureSettingsKeys, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatFeatureSettings(response, settings); + return 200; +} + +void FreqDisplay::webapiFormatFeatureSettings( + SWGSDRangel::SWGFeatureSettings& response, + const FreqDisplaySettings& settings) +{ + if (response.getFreqDisplaySettings()->getTitle()) { + *response.getFreqDisplaySettings()->getTitle() = settings.m_title; + } else { + response.getFreqDisplaySettings()->setTitle(new QString(settings.m_title)); + } + + response.getFreqDisplaySettings()->setRgbColor(settings.m_rgbColor); + + if (response.getFreqDisplaySettings()->getSelectedChannel()) { + *response.getFreqDisplaySettings()->getSelectedChannel() = settings.m_selectedChannel; + } else { + response.getFreqDisplaySettings()->setSelectedChannel(new QString(settings.m_selectedChannel)); + } + + if (response.getFreqDisplaySettings()->getFontName()) { + *response.getFreqDisplaySettings()->getFontName() = settings.m_fontName; + } else { + response.getFreqDisplaySettings()->setFontName(new QString(settings.m_fontName)); + } + + response.getFreqDisplaySettings()->setTransparentBackground(settings.m_transparentBackground ? 1 : 0); + response.getFreqDisplaySettings()->setDisplayMode(static_cast(settings.m_displayMode)); + response.getFreqDisplaySettings()->setSpeechEnabled(settings.m_speechEnabled ? 1 : 0); + response.getFreqDisplaySettings()->setFrequencyUnits(static_cast(settings.m_frequencyUnits)); + response.getFreqDisplaySettings()->setShowUnits(settings.m_showUnits ? 1 : 0); + response.getFreqDisplaySettings()->setPowerDecimalPlaces(settings.m_powerDecimalPlaces); + response.getFreqDisplaySettings()->setTextcolor(settings.m_textColor.rgba()); + response.getFreqDisplaySettings()->setDropShadowEnabled(settings.m_dropShadowEnabled ? 1 : 0); + response.getFreqDisplaySettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getFreqDisplaySettings()->getReverseApiAddress()) { + *response.getFreqDisplaySettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getFreqDisplaySettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getFreqDisplaySettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getFreqDisplaySettings()->setReverseApiFeatureSetIndex(settings.m_reverseAPIFeatureSetIndex); + response.getFreqDisplaySettings()->setReverseApiFeatureIndex(settings.m_reverseAPIFeatureIndex); + + if (settings.m_rollupState) + { + if (response.getFreqDisplaySettings()->getRollupState()) + { + settings.m_rollupState->formatTo(response.getFreqDisplaySettings()->getRollupState()); + } + else + { + SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState(); + settings.m_rollupState->formatTo(swgRollupState); + response.getFreqDisplaySettings()->setRollupState(swgRollupState); + } + } +} + +void FreqDisplay::webapiUpdateFeatureSettings( + FreqDisplaySettings& settings, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response) +{ + if (featureSettingsKeys.contains("title")) { + settings.m_title = *response.getFreqDisplaySettings()->getTitle(); + } + if (featureSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getFreqDisplaySettings()->getRgbColor(); + } + if (featureSettingsKeys.contains("selectedChannel")) { + settings.m_selectedChannel = *response.getFreqDisplaySettings()->getSelectedChannel(); + } + if (featureSettingsKeys.contains("fontName")) { + settings.m_fontName = *response.getFreqDisplaySettings()->getFontName(); + } + if (featureSettingsKeys.contains("transparentBackground")) { + settings.m_transparentBackground = response.getFreqDisplaySettings()->getTransparentBackground() != 0; + } + if (featureSettingsKeys.contains("displayMode")) { + settings.m_displayMode = static_cast(response.getFreqDisplaySettings()->getDisplayMode()); + } + if (featureSettingsKeys.contains("speechEnabled")) { + settings.m_speechEnabled = response.getFreqDisplaySettings()->getSpeechEnabled() != 0; + } + if (featureSettingsKeys.contains("frequencyUnits")) { + settings.m_frequencyUnits = static_cast(response.getFreqDisplaySettings()->getFrequencyUnits()); + } + if (featureSettingsKeys.contains("showUnits")) { + settings.m_showUnits = response.getFreqDisplaySettings()->getShowUnits() != 0; + } + if (featureSettingsKeys.contains("powerDecimalPlaces")) { + settings.m_powerDecimalPlaces = response.getFreqDisplaySettings()->getPowerDecimalPlaces(); + } + if (featureSettingsKeys.contains("textColor")) { + settings.m_textColor = QColor::fromRgba(response.getFreqDisplaySettings()->getTextcolor()); + } + if (featureSettingsKeys.contains("dropShadowEnabled")) { + settings.m_dropShadowEnabled = response.getFreqDisplaySettings()->getDropShadowEnabled() != 0; + } + if (featureSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getFreqDisplaySettings()->getUseReverseApi() != 0; + } + if (featureSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getFreqDisplaySettings()->getReverseApiAddress(); + } + if (featureSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getFreqDisplaySettings()->getReverseApiPort(); + } + if (featureSettingsKeys.contains("reverseAPIFeatureSetIndex")) { + settings.m_reverseAPIFeatureSetIndex = response.getFreqDisplaySettings()->getReverseApiFeatureSetIndex(); + } + if (featureSettingsKeys.contains("reverseAPIFeatureIndex")) { + settings.m_reverseAPIFeatureIndex = response.getFreqDisplaySettings()->getReverseApiFeatureIndex(); + } + if (settings.m_rollupState && featureSettingsKeys.contains("rollupState")) { + settings.m_rollupState->updateFrom(featureSettingsKeys, response.getFreqDisplaySettings()->getRollupState()); + } +} + +void FreqDisplay::webapiReverseSendSettings(const QStringList& featureSettingsKeys, const FreqDisplaySettings& settings, bool force) +{ + SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings(); + swgFeatureSettings->setFeatureType(new QString("FreqDisplay")); + swgFeatureSettings->setFreqDisplaySettings(new SWGSDRangel::SWGFreqDisplaySettings()); + SWGSDRangel::SWGFreqDisplaySettings *swgFreqDisplaySettings = swgFeatureSettings->getFreqDisplaySettings(); + + if (featureSettingsKeys.contains("title") || force) { + swgFreqDisplaySettings->setTitle(new QString(settings.m_title)); + } + if (featureSettingsKeys.contains("rgbColor") || force) { + swgFreqDisplaySettings->setRgbColor(settings.m_rgbColor); + } + if (featureSettingsKeys.contains("selectedChannel") || force) { + swgFreqDisplaySettings->setSelectedChannel(new QString(settings.m_selectedChannel)); + } + if (featureSettingsKeys.contains("fontName") || force) { + swgFreqDisplaySettings->setFontName(new QString(settings.m_fontName)); + } + if (featureSettingsKeys.contains("transparentBackground") || force) { + swgFreqDisplaySettings->setTransparentBackground(settings.m_transparentBackground ? 1 : 0); + } + if (featureSettingsKeys.contains("displayMode") || force) { + swgFreqDisplaySettings->setDisplayMode(static_cast(settings.m_displayMode)); + } + if (featureSettingsKeys.contains("speechEnabled") || force) { + swgFreqDisplaySettings->setSpeechEnabled(settings.m_speechEnabled ? 1 : 0); + } + if (featureSettingsKeys.contains("frequencyUnits") || force) { + swgFreqDisplaySettings->setFrequencyUnits(static_cast(settings.m_frequencyUnits)); + } + if (featureSettingsKeys.contains("showUnits") || force) { + swgFreqDisplaySettings->setShowUnits(settings.m_showUnits ? 1 : 0); + } + if (featureSettingsKeys.contains("powerDecimalPlaces") || force) { + swgFreqDisplaySettings->setPowerDecimalPlaces(settings.m_powerDecimalPlaces); + } + if (featureSettingsKeys.contains("textColor") || force) { + swgFreqDisplaySettings->setTextcolor(settings.m_textColor.rgba()); + } + if (featureSettingsKeys.contains("dropShadowEnabled") || force) { + swgFreqDisplaySettings->setDropShadowEnabled(settings.m_dropShadowEnabled ? 1 : 0); + } + + QString featureSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIFeatureSetIndex) + .arg(settings.m_reverseAPIFeatureIndex); + m_networkRequest.setUrl(QUrl(featureSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgFeatureSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgFeatureSettings; +} + +void FreqDisplay::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "FreqDisplay::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("FreqDisplay::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/feature/freqdisplay/freqdisplay.h b/plugins/feature/freqdisplay/freqdisplay.h index 9957f6b09..13f8e4c44 100644 --- a/plugins/feature/freqdisplay/freqdisplay.h +++ b/plugins/feature/freqdisplay/freqdisplay.h @@ -19,20 +19,53 @@ #ifndef INCLUDE_FEATURE_FREQDISPLAY_H_ #define INCLUDE_FEATURE_FREQDISPLAY_H_ +#include #include #include "feature/feature.h" +#include "util/message.h" #include "freqdisplaysettings.h" class WebAPIAdapterInterface; +class QNetworkAccessManager; +class QNetworkReply; + +namespace SWGSDRangel { + class SWGFeatureSettings; +} class FreqDisplay : public Feature { Q_OBJECT public: + class MsgConfigureFreqDisplay : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const FreqDisplaySettings& getSettings() const { return m_settings; } + const QStringList& getSettingsKeys() const { return m_settingsKeys; } + bool getForce() const { return m_force; } + + static MsgConfigureFreqDisplay* create(const FreqDisplaySettings& settings, const QStringList& settingsKeys, bool force) { + return new MsgConfigureFreqDisplay(settings, settingsKeys, force); + } + + private: + FreqDisplaySettings m_settings; + QStringList m_settingsKeys; + bool m_force; + + MsgConfigureFreqDisplay(const FreqDisplaySettings& settings, const QStringList& settingsKeys, bool force) : + Message(), + m_settings(settings), + m_settingsKeys(settingsKeys), + m_force(force) + { } + }; + FreqDisplay(WebAPIAdapterInterface *webAPIAdapterInterface); - ~FreqDisplay() override = default; + ~FreqDisplay() override; void destroy() override { delete this; } bool handleMessage(const Message& cmd) override; @@ -44,6 +77,25 @@ public: QByteArray serialize() const override; bool deserialize(const QByteArray& data) override; + int webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) override; + + int webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) override; + + static void webapiFormatFeatureSettings( + SWGSDRangel::SWGFeatureSettings& response, + const FreqDisplaySettings& settings); + + static void webapiUpdateFeatureSettings( + FreqDisplaySettings& settings, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response); + const FreqDisplaySettings& getSettings() const { return m_settings; } void applySettings(const FreqDisplaySettings& settings, const QStringList& settingsKeys, bool force = false); @@ -52,6 +104,13 @@ public: private: FreqDisplaySettings m_settings; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void webapiReverseSendSettings(const QStringList& featureSettingsKeys, const FreqDisplaySettings& settings, bool force); + +private slots: + void networkManagerFinished(QNetworkReply *reply); }; #endif // INCLUDE_FEATURE_FREQDISPLAY_H_ diff --git a/plugins/feature/freqdisplay/freqdisplaygui.cpp b/plugins/feature/freqdisplay/freqdisplaygui.cpp index 89a5617fa..6b4c5f224 100644 --- a/plugins/feature/freqdisplay/freqdisplaygui.cpp +++ b/plugins/feature/freqdisplay/freqdisplaygui.cpp @@ -110,6 +110,35 @@ void FreqDisplayGUI::destroy() delete this; } +bool FreqDisplayGUI::handleMessage(const Message& message) +{ + if (FreqDisplay::MsgConfigureFreqDisplay::match(message)) + { + qDebug("FreqDisplayGUI::handleMessage: FreqDisplay::MsgConfigureFreqDisplay"); + const FreqDisplay::MsgConfigureFreqDisplay& cfg = (FreqDisplay::MsgConfigureFreqDisplay&) message; + m_settings = cfg.getSettings(); + m_doApplySettings = false; + displaySettings(); + m_doApplySettings = true; + updateFrequencyText(); + return true; + } + + return false; +} + +void FreqDisplayGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop())) + { + if (handleMessage(*message)) { + delete message; + } + } +} + void FreqDisplayGUI::resetToDefaults() { m_settings.resetToDefaults(); @@ -175,6 +204,7 @@ FreqDisplayGUI::FreqDisplayGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, m_freqDisplay->setMessageQueueToGUI(&m_inputMessageQueue); m_settings = m_freqDisplay->getSettings(); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); connect( &m_availableChannelOrFeatureHandler, diff --git a/plugins/feature/freqdisplay/freqdisplaygui.h b/plugins/feature/freqdisplay/freqdisplaygui.h index a661f3739..845bab0c5 100644 --- a/plugins/feature/freqdisplay/freqdisplaygui.h +++ b/plugins/feature/freqdisplay/freqdisplaygui.h @@ -92,6 +92,8 @@ public: static FreqDisplayGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature); void destroy() override; + bool handleMessage(const Message& message) override; + void resetToDefaults() override; QByteArray serialize() const override; bool deserialize(const QByteArray& data) override; @@ -162,6 +164,7 @@ private: private slots: void onMenuDialogCalled(const QPoint &p); void onWidgetRolled(QWidget* widget, bool rollDown); + void handleInputMessages(); void channelsOrFeaturesChanged(const QStringList& renameFrom, const QStringList& renameTo, const QStringList& removed, const QStringList& added); void on_channels_currentIndexChanged(int index); void on_displayMode_currentIndexChanged(int index); diff --git a/plugins/feature/freqdisplay/freqdisplaywebapiadapter.cpp b/plugins/feature/freqdisplay/freqdisplaywebapiadapter.cpp index 7ac105b0a..c866716b1 100644 --- a/plugins/feature/freqdisplay/freqdisplaywebapiadapter.cpp +++ b/plugins/feature/freqdisplay/freqdisplaywebapiadapter.cpp @@ -17,6 +17,8 @@ /////////////////////////////////////////////////////////////////////////////////// #include "SWGFeatureSettings.h" +#include "SWGFreqDisplaySettings.h" +#include "freqdisplay.h" #include "freqdisplaywebapiadapter.h" int FreqDisplayWebAPIAdapter::webapiSettingsGet( @@ -26,7 +28,7 @@ int FreqDisplayWebAPIAdapter::webapiSettingsGet( (void) errorMessage; response.setFreqDisplaySettings(new SWGSDRangel::SWGFreqDisplaySettings()); response.getFreqDisplaySettings()->init(); - //FreqDisplay::webapiFormatFeatureSettings(response, m_settings); + FreqDisplay::webapiFormatFeatureSettings(response, m_settings); return 200; } @@ -39,7 +41,7 @@ int FreqDisplayWebAPIAdapter::webapiSettingsPutPatch( { (void) force; // no action (void) errorMessage; - //FreqDisplay::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response); + FreqDisplay::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response); return 200; }