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:
parent
75eae6f663
commit
eabd3e605d
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user