1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-05-14 13:22:16 -04:00

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>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-19 10:09:10 +00:00 committed by GitHub
parent 75eae6f663
commit eabd3e605d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 140 additions and 96 deletions

View File

@ -16,12 +16,15 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QContextMenuEvent>
#include <QFont>
#include <QLabel>
#include <QLocale>
#include <QMdiArea>
#include <QMenu>
#include <QResizeEvent>
#include <QSpinBox>
#include <QTimer>
#include <QVBoxLayout>
#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<FreqDisplay*>(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);

View File

@ -19,6 +19,8 @@
#ifndef INCLUDE_FEATURE_FREQDISPLAYGUI_H_
#define INCLUDE_FEATURE_FREQDISPLAYGUI_H_
#include <QLabel>
#include <QPoint>
#include <QTimer>
#include <QFontComboBox>
@ -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