From eabd3e605da2d5c3646daba6c8c3a8cfb846235e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 19 Apr 2026 10:09:10 +0000 Subject: [PATCH] freqdisplay: replace MDI-manipulation transparency with separate overlay window Agent-Logs-Url: https://github.com/srcejon/sdrangel/sessions/758a0e0b-447d-42b8-811b-97c35b453955 Co-authored-by: srcejon <57259258+srcejon@users.noreply.github.com> --- .../feature/freqdisplay/freqdisplaygui.cpp | 199 ++++++++++-------- plugins/feature/freqdisplay/freqdisplaygui.h | 37 +++- 2 files changed, 140 insertions(+), 96 deletions(-) diff --git a/plugins/feature/freqdisplay/freqdisplaygui.cpp b/plugins/feature/freqdisplay/freqdisplaygui.cpp index ce17a767e..7cb84431f 100644 --- a/plugins/feature/freqdisplay/freqdisplaygui.cpp +++ b/plugins/feature/freqdisplay/freqdisplaygui.cpp @@ -16,12 +16,15 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include #include +#include #include -#include +#include #include #include #include +#include #include "channel/channelwebapiutils.h" #include "gui/buttonswitch.h" @@ -59,8 +62,69 @@ QString textForSpeech(const QString& displayText) return s; } #endif +} // anonymous namespace + +// --- FreqDisplayOverlay --------------------------------------------------- + +FreqDisplayOverlay::FreqDisplayOverlay(QWidget* parent) + : QWidget(parent, Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool), + m_dragging(false) +{ + setAttribute(Qt::WA_TranslucentBackground); + setAttribute(Qt::WA_DeleteOnClose, false); + + m_label = new QLabel(this); + m_label->setAlignment(Qt::AlignCenter); + m_label->setWordWrap(true); + m_label->setStyleSheet("color: white;"); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_label); + setLayout(layout); } +void FreqDisplayOverlay::contextMenuEvent(QContextMenuEvent* event) +{ + QMenu menu(this); + QAction* exitAction = menu.addAction(tr("Exit transparent mode")); + if (menu.exec(event->globalPos()) == exitAction) { + emit exitTransparentMode(); + } +} + +void FreqDisplayOverlay::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + emit resized(); +} + +void FreqDisplayOverlay::mousePressEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) + { + m_dragging = true; + m_dragStartPos = event->globalPos() - pos(); + } + QWidget::mousePressEvent(event); +} + +void FreqDisplayOverlay::mouseMoveEvent(QMouseEvent* event) +{ + if (m_dragging && (event->buttons() & Qt::LeftButton)) { + move(event->globalPos() - m_dragStartPos); + } + QWidget::mouseMoveEvent(event); +} + +void FreqDisplayOverlay::mouseReleaseEvent(QMouseEvent* event) +{ + m_dragging = false; + QWidget::mouseReleaseEvent(event); +} + +// --- FreqDisplayGUI ------------------------------------------------------- + FreqDisplayGUI* FreqDisplayGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature) { return new FreqDisplayGUI(pluginAPI, featureUISet, feature); @@ -105,7 +169,7 @@ FreqDisplayGUI::FreqDisplayGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, m_freqDisplay(reinterpret_cast(feature)), m_availableChannelOrFeatureHandler(QStringList(), rxTxChannelKinds), m_doApplySettings(true), - m_savedMdi(nullptr) + m_overlay(nullptr) { (void) pluginAPI; (void) featureUISet; @@ -113,9 +177,6 @@ FreqDisplayGUI::FreqDisplayGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, m_feature = feature; setAttribute(Qt::WA_DeleteOnClose, true); - // Capture the stylesheet that FeatureGUI set in its constructor so that - // applyTransparency() can restore it when transparency is disabled. - m_normalStyleSheet = styleSheet(); RollupContents *rollupContents = getRollupContents(); ui->setupUi(rollupContents); @@ -159,6 +220,7 @@ FreqDisplayGUI::~FreqDisplayGUI() m_speech = nullptr; } #endif + delete m_overlay; delete ui; } @@ -308,6 +370,9 @@ void FreqDisplayGUI::updateFrequencyText() { auto setLabelText = [this](const QString& text) { ui->frequencyValue->setText(text); + if (m_overlay) { + m_overlay->label()->setText(text); + } #ifdef QT_TEXTTOSPEECH_FOUND if (m_settings.m_speechEnabled && m_speech && (text != m_previousDisplayText)) { @@ -410,19 +475,20 @@ void FreqDisplayGUI::updateFrequencyText() void FreqDisplayGUI::updateFrequencyFont() { - const int availableWidth = ui->frequencyValue->width(); - const int availableHeight = ui->frequencyValue->height(); + QLabel* label = m_overlay ? m_overlay->label() : ui->frequencyValue; + const int availableWidth = label->width(); + const int availableHeight = label->height(); if (availableWidth <= 0 || availableHeight <= 0) { return; } - const QString text = ui->frequencyValue->text(); + const QString text = label->text(); if (text.isEmpty()) { return; } // Build a font with the user-chosen family (or the widget's current family if none saved) - QFont font = ui->frequencyValue->font(); + QFont font = label->font(); if (!m_settings.m_fontName.isEmpty()) { font.setFamily(m_settings.m_fontName); } @@ -473,102 +539,41 @@ void FreqDisplayGUI::updateFrequencyFont() } } - ui->frequencyValue->setFont(font); + label->setFont(font); } void FreqDisplayGUI::applyTransparency() { - setStyleSheet(m_normalStyleSheet); - - RollupContents *rollupContents = getRollupContents(); - if (m_settings.m_transparentBackground) { - // WA_TranslucentBackground must be set before the native window handle is - // created or recreated. Setting it here (before the deferred lambda's - // setWindowFlags call, which forces native-window recreation) is the - // correct place. For the startup case where the widget has not yet been - // shown, setting it here is also safe. - setAttribute(Qt::WA_TranslucentBackground, true); - - // Detach from the MDI area so that WA_TranslucentBackground operates at the - // OS compositor level. Inside a QMdiArea the attribute only affects Qt's - // internal backing store, which only passes through for QOpenGLWidget children - // (GPU compositing path) but not for ordinary software-rendered siblings or - // external application windows. - if (!m_savedMdi && mdiArea()) + // Only create the overlay if this widget is already visible on screen. + // When called at startup (before the widget has been shown and placed by + // the workspace), skip overlay creation so that the window appears at its + // saved MDI position rather than at (0,0). + if (!m_overlay && isVisible()) { - m_savedMdi = mdiArea(); - // Save position in MDI viewport coordinates and convert to screen - // coordinates before the window is reparented. - m_mdiGeometry = geometry(); - const QPoint globalPos = m_savedMdi->viewport()->mapToGlobal(m_mdiGeometry.topLeft()); - // Defer the reparenting to the next event loop iteration so that any - // in-progress paint or layout event completes first. - QTimer::singleShot(0, this, [this, globalPos]() { - if (!m_savedMdi) { return; } - m_savedMdi->removeSubWindow(this); - // FeatureGUI is always frameless (Qt::FramelessWindowHint is set in - // FeatureGUI's constructor) and removeSubWindow() already sets the - // Qt::Window flag. Only the always-on-top hint needs to be added so - // the overlay floats above the main window. - setWindowFlag(Qt::WindowStaysOnTopHint, true); - move(globalPos); - resize(m_mdiGeometry.size()); - show(); - }); + m_overlay = new FreqDisplayOverlay(); + m_overlay->label()->setText(ui->frequencyValue->text()); + connect(m_overlay, &FreqDisplayOverlay::exitTransparentMode, + this, &FreqDisplayGUI::onExitTransparentMode); + connect(m_overlay, &FreqDisplayOverlay::resized, + this, &FreqDisplayGUI::updateFrequencyFont); + // Position the overlay at the current screen position of FreqDisplayGUI. + m_overlay->move(mapToGlobal(QPoint(0, 0))); + m_overlay->resize(size()); + m_overlay->show(); } - - rollupContents->setTransparentBackground(true); - ui->settingsContainer->setAutoFillBackground(true); - // Hide the controls bar so only frequencyValue fills the window. - // RollupContents::arrangeRollups() is triggered automatically by the - // Hide event and will reposition/resize horizontalWidget to use all - // available space. - ui->settingsContainer->hide(); + hide(); } else { - if (m_savedMdi) + if (m_overlay) { - QMdiArea* savedMdi = m_savedMdi; - const QRect savedMdiGeometry = m_mdiGeometry; - m_savedMdi = nullptr; - // Convert current screen position back to MDI viewport coordinates so - // the window reappears where the user last placed it. - const QPoint currentGlobalPos = mapToGlobal(QPoint(0, 0)); - const QPoint mdiPos = savedMdi->viewport()->mapFromGlobal(currentGlobalPos); - // Defer re-embedding to the next event loop iteration. - QTimer::singleShot(0, this, [this, savedMdi, mdiPos, savedMdiGeometry]() { - // Re-embed in the MDI area first. QMdiArea::addSubWindow() - // reparents this QMdiSubWindow into the MDI viewport using an - // XReparentWindow operation, which does not require any - // window-manager round-trips. Only AFTER the widget is a child - // (non-top-level) is it safe to clear WA_TranslucentBackground - // and WindowStaysOnTopHint: clearing those flags on a visible - // top-level window forces native window destruction + recreation - // plus WM state-change negotiation, which can deadlock on some - // compositors. - savedMdi->addSubWindow(this); - setAttribute(Qt::WA_TranslucentBackground, false); - setWindowFlag(Qt::WindowStaysOnTopHint, false); - show(); - move(mdiPos); - resize(savedMdiGeometry.size()); - // On Windows, clearing WA_TranslucentBackground forces native - // window handle recreation. Schedule a repaint so the FeatureGUI - // border and title bar are drawn correctly after recreation. - update(); - }); + delete m_overlay; + m_overlay = nullptr; } - - rollupContents->setTransparentBackground(false); - ui->settingsContainer->setAutoFillBackground(false); - ui->settingsContainer->setStyleSheet(QString()); - ui->horizontalWidget->setStyleSheet(QString()); - ui->frequencyValue->setStyleSheet(QString()); - // Restore the controls bar that was hidden while in transparent mode. - ui->settingsContainer->show(); + show(); + updateFrequencyFont(); } } @@ -693,6 +698,16 @@ void FreqDisplayGUI::on_transparentBackground_toggled(bool checked) applySettings(); } +void FreqDisplayGUI::onExitTransparentMode() +{ + m_settings.m_transparentBackground = false; + ui->transparentBackground->blockSignals(true); + ui->transparentBackground->setChecked(false); + ui->transparentBackground->blockSignals(false); + applyTransparency(); + applySettings(); +} + void FreqDisplayGUI::resizeEvent(QResizeEvent *event) { FeatureGUI::resizeEvent(event); diff --git a/plugins/feature/freqdisplay/freqdisplaygui.h b/plugins/feature/freqdisplay/freqdisplaygui.h index 0a58f7fd0..0ec98f7cc 100644 --- a/plugins/feature/freqdisplay/freqdisplaygui.h +++ b/plugins/feature/freqdisplay/freqdisplaygui.h @@ -19,6 +19,8 @@ #ifndef INCLUDE_FEATURE_FREQDISPLAYGUI_H_ #define INCLUDE_FEATURE_FREQDISPLAYGUI_H_ +#include +#include #include #include @@ -36,12 +38,40 @@ class PluginAPI; class FeatureUISet; class FreqDisplay; class Feature; -class QMdiArea; namespace Ui { class FreqDisplayGUI; } +/// Frameless, transparent top-level overlay window used when transparent-background +/// mode is active. Shows a single label with the frequency/power text and +/// provides a right-click context menu to exit transparent mode. +class FreqDisplayOverlay : public QWidget +{ + Q_OBJECT +public: + explicit FreqDisplayOverlay(QWidget* parent = nullptr); + + /// Returns the label that displays the frequency/power text. + QLabel* label() { return m_label; } + +signals: + void exitTransparentMode(); + void resized(); + +protected: + void contextMenuEvent(QContextMenuEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + +private: + QLabel* m_label; + bool m_dragging; + QPoint m_dragStartPos; +}; + class FreqDisplayGUI : public FeatureGUI { Q_OBJECT @@ -71,10 +101,8 @@ private: AvailableChannelOrFeatureList m_availableChannels; QTimer m_pollTimer; bool m_doApplySettings; - QString m_normalStyleSheet; ///< Stylesheet set by FeatureGUI, saved so it can be restored when transparency is disabled QString m_previousDisplayText; ///< Last text set on frequencyValue, used to detect changes for speech - QMdiArea* m_savedMdi; ///< MDI area saved when window is detached for transparent-background mode - QRect m_mdiGeometry; ///< Window geometry (in MDI viewport coordinates) saved before detaching + FreqDisplayOverlay* m_overlay; ///< Transparent overlay window shown when transparent-background mode is active #ifdef QT_TEXTTOSPEECH_FOUND QTextToSpeech *m_speech = nullptr; @@ -106,6 +134,7 @@ private slots: void on_freqDecimalPlaces_valueChanged(int value); void on_powerDecimalPlaces_valueChanged(int value); void pollSelectedChannel(); + void onExitTransparentMode(); #ifdef QT_TEXTTOSPEECH_FOUND void speechStateChanged(QTextToSpeech::State state); #endif