From 37485820b59c16b63f49b433c5a346a50fd17394 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 7 Oct 2026 21:42:40 +0200 Subject: [PATCH] chatgui: latch radio PTT on double click and release on next press --- tools/chatgui/src/radiopanel.cpp | 47 ++++++++++++++++++++++++++++++-- tools/chatgui/src/radiopanel.h | 4 +++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/tools/chatgui/src/radiopanel.cpp b/tools/chatgui/src/radiopanel.cpp index 6f7aab12..7e130d15 100644 --- a/tools/chatgui/src/radiopanel.cpp +++ b/tools/chatgui/src/radiopanel.cpp @@ -16,6 +16,7 @@ extern "C" { #include #include #include +#include #include RadioPanel::RadioPanel(QWidget* parent) : QWidget(parent) { @@ -52,13 +53,18 @@ RadioPanel::RadioPanel(QWidget* parent) : QWidget(parent) { m_ptt = new QPushButton(QStringLiteral("PTT"), this); m_ptt->setObjectName(QStringLiteral("radioPttButton")); m_ptt->setAccessibleName(QCoreApplication::translate("ChatGui", "Hold to talk")); - m_ptt->setToolTip(QCoreApplication::translate("ChatGui", "Hold the button or a global shortcut to transmit")); + m_ptt->installEventFilter(this); m_ptt->setFixedSize(64, 34); m_ptt->setStyleSheet("QPushButton[transmitting=\"true\"] { background: #27ae60; color: white; }"); row->addWidget(m_ptt); auto* engine = RadioAudioEngine::instance(); - connect(m_ptt, &QPushButton::pressed, engine, &RadioAudioEngine::talkBegin); - connect(m_ptt, &QPushButton::released, engine, &RadioAudioEngine::talkEnd); + connect(m_ptt, &QPushButton::pressed, this, [this, engine]() { + if (m_pttLatched) setPttLatched(false, "button-pressed"); + else engine->talkBegin(); + }); + connect(m_ptt, &QPushButton::released, this, [this, engine]() { + if (!m_pttLatched) engine->talkEnd(); + }); connect(m_vad, &QPushButton::clicked, this, [this](bool checked) { RadioAudioEngine::instance()->applySettings({{QStringLiteral("radio_vad_enabled"), checked ? 1 : 0}}); updateState(); @@ -69,12 +75,47 @@ RadioPanel::RadioPanel(QWidget* parent) : QWidget(parent) { updateDevices(); } +RadioPanel::~RadioPanel() { + if (m_pttLatched) setPttLatched(false, "panel-destroyed"); +} + +/* Второе нажатие Qt приходит как DoubleClick: не отдаём его обычному переключателю pressed. */ +bool RadioPanel::eventFilter(QObject* watched, QEvent* event) { + if (watched == m_ptt && event->type() == QEvent::MouseButtonDblClick && m_ptt->isEnabled()) { + auto* mouse = static_cast(event); + if (mouse->button() == Qt::LeftButton && m_ptt->rect().contains(mouse->pos())) { + setPttLatched(true, "double-click"); + return true; + } + } + return QWidget::eventFilter(watched, event); +} + +/* Фиксация владеет только кнопочным PTT; хоткей и автоматическая передача остаются независимыми. */ +void RadioPanel::setPttLatched(bool latched, const char* reason) { + if (m_pttLatched == latched) return; + m_pttLatched = latched; + auto* engine = RadioAudioEngine::instance(); + DEBUG_INFO(DEBUG_CATEGORY_RADIO, "RadioPanel: PTT %s group=%016llx reason=%s", + latched ? "latched (click to release)" : "latch released", (unsigned long long)engine->groupId(), reason); + if (latched) engine->talkBegin(); + else engine->talkEnd(); + updateState(); +} + void RadioPanel::updateState() { auto* engine = RadioAudioEngine::instance(); + if (m_pttLatched && (!engine->isActive() || !engine->captureAvailable())) + setPttLatched(false, engine->isActive() ? "microphone-unavailable" : "radio-stopped"); QSignalBlocker blocker(m_vad); m_vad->setChecked(engine->vadEnabled()); m_vad->setEnabled(engine->isActive() && engine->settingsReady() && !engine->settingsBusy()); m_ptt->setEnabled(engine->isActive() && engine->captureAvailable()); + m_ptt->setText(m_pttLatched ? QStringLiteral("PTT \U0001F512") : QStringLiteral("PTT")); + m_ptt->setToolTip(m_pttLatched ? QCoreApplication::translate("ChatGui", "PTT locked. Click to release.") + : QCoreApplication::translate("ChatGui", "Hold to talk, or double-click to lock PTT. Click again to release. Global shortcuts also work.")); + m_ptt->setAccessibleName(m_pttLatched ? QCoreApplication::translate("ChatGui", "PTT locked. Click to release.") + : QCoreApplication::translate("ChatGui", "Hold to talk")); bool transmitting = engine->isTransmitting(); if (m_ptt->property("transmitting").toBool() != transmitting || !m_ptt->property("transmitting").isValid()) { m_ptt->setProperty("transmitting", transmitting); diff --git a/tools/chatgui/src/radiopanel.h b/tools/chatgui/src/radiopanel.h index 7f702cdb..a7d15077 100644 --- a/tools/chatgui/src/radiopanel.h +++ b/tools/chatgui/src/radiopanel.h @@ -12,7 +12,10 @@ class RadioPanel : public QWidget { Q_OBJECT public: explicit RadioPanel(QWidget* parent = nullptr); + ~RadioPanel() override; private: + bool eventFilter(QObject* watched, QEvent* event) override; + void setPttLatched(bool latched, const char* reason); void updateState(); void updateDevices(); void populateDevices(QToolButton* button, bool capture); @@ -20,4 +23,5 @@ private: QToolButton *m_input, *m_output; QPushButton *m_vad, *m_ptt; QLabel* m_status; + bool m_pttLatched = false; };