New UDP messages to close and reconfigure WSJT-X plus more status fields

The Status(1) message acquires the new fields Frequency Tolerance, T/R
Period,  and  Configuration Name.  The  Rx  DF,  Tx DF  fields  become
unsigned (this should be a benign change which is just for correctness
as -ve values have never been possible).

The   Close(6)  message   becomes  bi-directional   allowing  external
applications to gracefully close down WSJT-X instances.

A  new  message SwitchConfiguration(14)  is  provided  that allows  an
external application to  switch the current configuration  of a WSJT-X
instance.

Another  new  message  Configure(15)  is provided  to  allow  external
applications to adjust some key parameters like the mode and submode.

See the  NetworkMessages.hpp header  commentary for full  details. The
UDPExamples/MessageAggregator reference  application has  been updated
to be able to exercise all of the above changes.

Note   that   this   commit   enforces  stricter   checking   on   the
"Settings->Reporting->Allow  UDP  requests"   option,  which  must  be
checked  before  any state  changing  incoming  messages to  a  WSJT-X
instance are processed.
This commit is contained in:
Bill Somerville 2019-06-25 14:35:58 +01:00
parent 7d3b346afa
commit c65d832356
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
15 changed files with 609 additions and 207 deletions

View File

@ -2092,7 +2092,12 @@ void Configuration::impl::accept ()
Q_EMIT self_->udp_server_port_changed (new_port);
}
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_)
{
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
Q_EMIT self_->accept_udp_requests_changed (accept_udp_requests_);
}
n1mm_server_name_ = ui_->n1mm_server_name_line_edit->text ();
n1mm_server_port_ = ui_->n1mm_server_port_spin_box->value ();
broadcast_to_n1mm_ = ui_->enable_n1mm_broadcast_check_box->isChecked ();

View File

@ -271,6 +271,7 @@ public:
//
Q_SIGNAL void udp_server_changed (QString const& udp_server) const;
Q_SIGNAL void udp_server_port_changed (port_type server_port) const;
Q_SIGNAL void accept_udp_requests_changed (bool checked) const;
// signal updates to decode highlighting
Q_SIGNAL void decode_highlighting_changed (DecodeHighlightingModel const&) const;

View File

@ -3,6 +3,7 @@
#include <stdexcept>
#include <vector>
#include <algorithm>
#include <limits>
#include <QUdpSocket>
#include <QHostInfo>
@ -35,6 +36,7 @@ public:
impl (QString const& id, QString const& version, QString const& revision,
port_type server_port, MessageClient * self)
: self_ {self}
, enabled_ {false}
, id_ {id}
, version_ {version}
, revision_ {revision}
@ -79,6 +81,7 @@ public:
Q_SLOT void host_info_results (QHostInfo);
MessageClient * self_;
bool enabled_;
QString id_;
QString version_;
QString revision_;
@ -160,6 +163,12 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
schema_ = in.schema ();
}
if (!enabled_)
{
TRACE_UDP ("message processing disabled for id:" << in.id ());
return;
}
//
// message format is described in NetworkMessage.hpp
//
@ -200,6 +209,15 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
}
break;
case NetworkMessage::Close:
TRACE_UDP ("Close");
if (check_status (in) != Fail)
{
last_message_.clear ();
Q_EMIT self_->close ();
}
break;
case NetworkMessage::Replay:
TRACE_UDP ("Replay");
if (check_status (in) != Fail)
@ -265,14 +283,38 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
{
QByteArray configuration_name;
in >> configuration_name;
TRACE_UDP ("SwitchConfiguration name:" << configuration_name);
if (check_status (in) != Fail && configuration_name.size ())
TRACE_UDP ("Switch Configuration name:" << configuration_name);
if (check_status (in) != Fail)
{
Q_EMIT self_->switch_configuration (QString::fromUtf8 (configuration_name));
}
}
break;
case NetworkMessage::Configure:
{
QByteArray mode;
quint32 frequency_tolerance;
QByteArray submode;
bool fast_mode {false};
quint32 tr_period {std::numeric_limits<quint32>::max ()};
quint32 rx_df {std::numeric_limits<quint32>::max ()};
QByteArray dx_call;
QByteArray dx_grid;
bool generate_messages {false};
in >> mode >> frequency_tolerance >> submode >> fast_mode >> tr_period >> rx_df
>> dx_call >> dx_grid >> generate_messages;
TRACE_UDP ("Configure mode:" << mode << "frequency tolerance:" << frequency_tolerance << "submode:" << submode << "fast mode:" << fast_mode << "T/R period:" << tr_period << "rx df:" << rx_df << "dx call:" << dx_call << "dx grid:" << dx_grid << "generate messages:" << generate_messages);
if (check_status (in) != Fail)
{
Q_EMIT self_->configure (QString::fromUtf8 (mode), frequency_tolerance
, QString::fromUtf8 (submode), fast_mode, tr_period, rx_df
, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
, generate_messages);
}
}
break;
default:
// Ignore
//
@ -450,13 +492,19 @@ void MessageClient::add_blocked_destination (QHostAddress const& a)
}
}
void MessageClient::enable (bool flag)
{
m_->enabled_ = flag;
}
void MessageClient::status_update (Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode
, bool tx_enabled, bool transmitting, bool decoding
, qint32 rx_df, qint32 tx_df, QString const& de_call
, quint32 rx_df, quint32 tx_df, QString const& de_call
, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode
, bool fast_mode, quint8 special_op_mode
, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name)
{
if (m_->server_port_ && !m_->server_string_.isEmpty ())
@ -466,8 +514,8 @@ void MessageClient::status_update (Frequency f, QString const& mode, QString con
out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 ()
<< tx_enabled << transmitting << decoding << rx_df << tx_df << de_call.toUtf8 ()
<< de_grid.toUtf8 () << dx_grid.toUtf8 () << watchdog_timeout << sub_mode.toUtf8 ()
<< fast_mode << special_op_mode << configuration_name.toUtf8 ();
TRACE_UDP ("frequency:" << f << "mode:" << mode << "DX:" << dx_call << "report:" << report << "Tx mode:" << tx_mode << "tx_enabled:" << tx_enabled << "Tx:" << transmitting << "decoding:" << decoding << "Rx df:" << rx_df << "Tx df:" << tx_df << "DE:" << de_call << "DE grid:" << de_grid << "DX grid:" << dx_grid << "w/d t/o:" << watchdog_timeout << "sub_mode:" << sub_mode << "fast mode:" << fast_mode << "spec op mode:" << special_op_mode << "configuration name:" << configuration_name);
<< fast_mode << special_op_mode << frequency_tolerance << tr_period << configuration_name.toUtf8 ();
TRACE_UDP ("frequency:" << f << "mode:" << mode << "DX:" << dx_call << "report:" << report << "Tx mode:" << tx_mode << "tx_enabled:" << tx_enabled << "Tx:" << transmitting << "decoding:" << decoding << "Rx df:" << rx_df << "Tx df:" << tx_df << "DE:" << de_call << "DE grid:" << de_grid << "DX grid:" << dx_grid << "w/d t/o:" << watchdog_timeout << "sub_mode:" << sub_mode << "fast mode:" << fast_mode << "spec op mode:" << special_op_mode << "frequency tolerance:" << frequency_tolerance << "T/R period:" << tr_period << "configuration name:" << configuration_name);
m_->send_message (out, message);
}
}

View File

@ -47,12 +47,16 @@ public:
// change the server port messages are sent to
Q_SLOT void set_server_port (port_type server_port = 0u);
// enable incoming messages
Q_SLOT void enable (bool);
// outgoing messages
Q_SLOT void status_update (Frequency, QString const& mode, QString const& dx_call, QString const& report
, QString const& tx_mode, bool tx_enabled, bool transmitting, bool decoding
, qint32 rx_df, qint32 tx_df, QString const& de_call, QString const& de_grid
, quint32 rx_df, quint32 tx_df, QString const& de_call, QString const& de_grid
, QString const& dx_grid, bool watchdog_timeout, QString const& sub_mode
, bool fast_mode, quint8 special_op_mode, QString const& configuration_name);
, bool fast_mode, quint8 special_op_mode, quint32 frequency_tolerance
, quint32 tr_period, QString const& configuration_name);
Q_SLOT void decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
, QString const& mode, QString const& message, bool low_confidence
, bool off_air);
@ -89,6 +93,10 @@ public:
Q_SIGNAL void reply (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode
, QString const& message_text, bool low_confidence, quint8 modifiers);
// this signal is emitted if the server has requested this client to
// close down gracefully
Q_SIGNAL void close ();
// this signal is emitted if the server has requested a replay of
// all decodes
Q_SIGNAL void replay ();
@ -105,10 +113,16 @@ public:
// callsign request for the specified call
Q_SIGNAL void highlight_callsign (QString const& callsign, QColor const& bg, QColor const& fg, bool last_only);
// this signal is emitted if the server has requested a switch to a
// new configuration
// this signal is emitted if the server has requested a
// configuration switch
Q_SIGNAL void switch_configuration (QString const& configuration_name);
// this signal is emitted if the server has requested a
// configuration change
Q_SIGNAL void configure (QString const& mode, quint32 frequency_tolerance, QString const& submode
, bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call
, QString const& dx_grid, bool generate_messages);
// this signal is emitted when network errors occur or if a host
// lookup fails
Q_SIGNAL void error (QString const&) const;

View File

@ -1,6 +1,7 @@
#include "MessageServer.hpp"
#include <stdexcept>
#include <limits>
#include <QNetworkInterface>
#include <QUdpSocket>
@ -16,6 +17,11 @@
#include "moc_MessageServer.cpp"
namespace
{
auto quint32_max = std::numeric_limits<quint32>::max ();
}
class MessageServer::impl
: public QUdpSocket
{
@ -238,8 +244,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
bool tx_enabled {false};
bool transmitting {false};
bool decoding {false};
qint32 rx_df {-1};
qint32 tx_df {-1};
quint32 rx_df {quint32_max};
quint32 tx_df {quint32_max};
QByteArray de_call;
QByteArray de_grid;
QByteArray dx_grid;
@ -247,10 +253,12 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
QByteArray sub_mode;
bool fast_mode {false};
quint8 special_op_mode {0};
quint32 frequency_tolerance {quint32_max};
quint32 tr_period {quint32_max};
QByteArray configuration_name;
in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding
>> rx_df >> tx_df >> de_call >> de_grid >> dx_grid >> watchdog_timeout >> sub_mode
>> fast_mode >> special_op_mode >> configuration_name;
>> fast_mode >> special_op_mode >> frequency_tolerance >> tr_period >> configuration_name;
if (check_status (in) != Fail)
{
Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call)
@ -259,7 +267,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
, QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
, QString::fromUtf8 (dx_grid), watchdog_timeout
, QString::fromUtf8 (sub_mode), fast_mode
, special_op_mode, QString::fromUtf8 (configuration_name));
, special_op_mode, frequency_tolerance, tr_period
, QString::fromUtf8 (configuration_name));
}
}
break;
@ -494,6 +503,17 @@ void MessageServer::replay (QString const& id)
}
}
void MessageServer::close (QString const& id)
{
auto iter = m_->clients_.find (id);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Close, id, (*iter).negotiated_schema_number_};
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
}
}
void MessageServer::halt_tx (QString const& id, bool auto_only)
{
auto iter = m_->clients_.find (id);
@ -554,3 +574,18 @@ void MessageServer::switch_configuration (QString const& id, QString const& conf
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
}
}
void MessageServer::configure (QString const& id, QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages)
{
auto iter = m_->clients_.find (id);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Configure, id, (*iter).negotiated_schema_number_};
out << mode.toUtf8 () << frequency_tolerance << submode.toUtf8 () << fast_mode << tr_period << rx_df
<< dx_call.toUtf8 () << dx_grid.toUtf8 () << generate_messages;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
}
}

View File

@ -52,6 +52,9 @@ public:
Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
, QString const& mode, QString const& message, bool low_confidence, quint8 modifiers);
// ask the client with identification 'id' to close down gracefully
Q_SLOT void close (QString const& id);
// ask the client with identification 'id' to replay all decodes
Q_SLOT void replay (QString const& id);
@ -72,18 +75,25 @@ public:
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
, bool last_only = false);
// ask the client with identification 'id' to switch configuration
// ask the client with identification 'id' to switch to
// configuration 'configuration_name'
Q_SLOT void switch_configuration (QString const& id, QString const& configuration_name);
// ask the client with identification 'id' to change configuration
Q_SLOT void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages);
// the following signals are emitted when a client broadcasts the
// matching message
Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision);
Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
, QString const& de_call, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
, quint8 special_op_mode, QString const& configuration_name);
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name);
Q_SIGNAL void client_closed (QString const& id);
Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time
, quint32 delta_frequency, QString const& mode, QString const& message

View File

@ -116,8 +116,8 @@
* Tx Enabled bool
* Transmitting bool
* Decoding bool
* Rx DF qint32
* Tx DF qint32
* Rx DF quint32
* Tx DF quint32
* DE call utf8
* DE grid utf8
* DX grid utf8
@ -125,6 +125,8 @@
* Sub-mode utf8
* Fast mode bool
* Special Operation Mode quint8
* Frequency Tolerance quint32
* T/R Period quint32
* Configuration Name utf8
*
* WSJT-X sends this status message when various internal state
@ -134,20 +136,22 @@
*
* Application start up,
* "Enable Tx" button status changes,
* Dial frequency changes,
* Changes to the "DX Call" field,
* Operating mode, sub-mode or fast mode changes,
* Transmit mode changed (in dual JT9+JT65 mode),
* Changes to the "Rpt" spinner,
* After an old decodes replay sequence (see Replay below),
* When switching between Tx and Rx mode,
* At the start and end of decoding,
* When the Rx DF changes,
* When the Tx DF changes,
* When settings are exited,
* When the DX call or grid changes,
* When the Tx watchdog is set or reset,
* When the configuration name changes.
* dial frequency changes,
* changes to the "DX Call" field,
* operating mode, sub-mode or fast mode changes,
* transmit mode changed (in dual JT9+JT65 mode),
* changes to the "Rpt" spinner,
* after an old decodes replay sequence (see Replay below),
* when switching between Tx and Rx mode,
* at the start and end of decoding,
* when the Rx DF changes,
* when the Tx DF changes,
* when settings are exited,
* when the DX call or grid changes,
* when the Tx watchdog is set or reset,
* when the frequency tolerance is changed,
* when the T/R period is changed,
* when the configuration name changes.
*
* The Special operation mode is an enumeration that indicates the
* setting selected in the WSJT-X "Settings->Advanced->Special
@ -161,6 +165,10 @@
* 5 -> FOX
* 6 -> HOUND
*
* The Frequency Tolerance and T/R period fields may have a value
* of the maximum quint32 value which implies the field is not
* applicable.
*
*
* Decode Out 2 quint32
* Id (unique key) utf8
@ -273,11 +281,12 @@
* button.
*
*
* Close Out 6 quint32
* Close Out/In 6 quint32
* Id (unique key) utf8
*
* Close is sent by a client immediately prior to it shutting
* down gracefully.
* Close is sent by a client immediately prior to it shutting
* down gracefully. When sent by a server it requests the target
* client to close down gracefully.
*
*
* Replay In 7 quint32
@ -418,13 +427,33 @@
* cleared.
*
*
* Switch Configuration In 14 quint32
* SwitchConfiguration In 14 quint32
* Id (unique key) utf8
* Configuration Name utf8
*
* The server may send this message at any time. The message
* specifies the name of the configuration to switch to. The new
* configuration must exist.
*
*
* Configure In 15 quint32
* Id (unique key) utf8
* Mode utf8
* Frequency Tolerance quint32
* Submode utf8
* Fast Mode bool
* T/R Period quint32
* Rx DF quint32
* DX Call utf8
* DX Grid utf8
* Generate Messages bool
*
* The server may send this message at any time. The message
* specifies various configuration options. For utf8 string
* fields an empty value implies no change, for the quint32 Rx DF
* and Frequency Tolerance fields the maximum quint32 value
* implies no change. Invalid or unrecognized values will be
* silently ignored.
*/
#include <QDataStream>
@ -455,6 +484,7 @@ namespace NetworkMessage
LoggedADIF,
HighlightCallsign,
SwitchConfiguration,
Configure,
maximum_message_type_ // ONLY add new message types
// immediately before here
};

View File

@ -1,8 +1,11 @@
#include "ClientWidget.hpp"
#include <limits>
#include <QRegExp>
#include <QColor>
#include <QtWidgets>
#include <QAction>
#include <QDebug>
#include "validators/MaidenheadLocatorValidator.hpp"
@ -11,6 +14,9 @@ namespace
//QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"};
QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"};
QRegularExpression cq_re {"(CQ|CQDX|QRZ)[^A-Z0-9/]+"};
QRegExpValidator message_validator {message_alphabet};
MaidenheadLocatorValidator locator_validator;
quint32 quint32_max {std::numeric_limits<quint32>::max ()};
void update_dynamic_property (QWidget * widget, char const * property, QVariant const& value)
{
@ -21,9 +27,10 @@ namespace
}
}
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id)
: client_id_ {client_id}
, rx_df_ (-1)
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id, QObject * parent)
: QSortFilterProxyModel {parent}
, client_id_ {client_id}
, rx_df_ (quint32_max)
{
}
@ -49,7 +56,7 @@ QVariant ClientWidget::IdFilterModel::data (QModelIndex const& proxy_index, int
break;
case 4: // DF
if (qAbs (QSortFilterProxyModel::data (proxy_index).toInt () - rx_df_) <= 10)
if (qAbs (QSortFilterProxyModel::data (proxy_index).toUInt () - rx_df_) <= 10)
{
return QColor {255, 200, 200};
}
@ -87,7 +94,7 @@ void ClientWidget::IdFilterModel::de_call (QString const& call)
}
}
void ClientWidget::IdFilterModel::rx_df (int df)
void ClientWidget::IdFilterModel::rx_df (quint32 df)
{
if (df != rx_df_)
{
@ -119,27 +126,48 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
, QListWidget const * calls_of_interest, QWidget * parent)
: QDockWidget {make_title (id, version, revision), parent}
, id_ {id}
, done_ {false}
, calls_of_interest_ {calls_of_interest}
, decodes_proxy_model_ {id_}
, decodes_proxy_model_ {id}
, beacons_proxy_model_ {id}
, erase_action_ {new QAction {tr ("&Erase Band Activity"), this}}
, erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), this}}
, erase_both_action_ {new QAction {tr ("Erase &Both"), this}}
, decodes_table_view_ {new QTableView}
, beacons_table_view_ {new QTableView}
, message_line_edit_ {new QLineEdit}
, grid_line_edit_ {new QLineEdit}
, decodes_table_view_ {new QTableView {this}}
, beacons_table_view_ {new QTableView {this}}
, message_line_edit_ {new QLineEdit {this}}
, grid_line_edit_ {new QLineEdit {this}}
, generate_messages_push_button_ {new QPushButton {tr ("&Gen Msgs"), this}}
, auto_off_button_ {nullptr}
, halt_tx_button_ {nullptr}
, de_label_ {new QLabel {this}}
, frequency_label_ {new QLabel {this}}
, tx_df_label_ {new QLabel {this}}
, report_label_ {new QLabel {this}}
, configuration_line_edit_ {new QLineEdit {this}}
, mode_line_edit_ {new QLineEdit {this}}
, frequency_tolerance_spin_box_ {new QSpinBox {this}}
, tx_mode_label_ {new QLabel {this}}
, submode_line_edit_ {new QLineEdit {this}}
, fast_mode_check_box_ {new QCheckBox {this}}
, tr_period_spin_box_ {new QSpinBox {this}}
, rx_df_spin_box_ {new QSpinBox {this}}
, dx_call_line_edit_ {new QLineEdit {this}}
, dx_grid_line_edit_ {new QLineEdit {this}}
, decodes_page_ {new QWidget {this}}
, beacons_page_ {new QWidget {this}}
, content_widget_ {new QFrame {this}}
, status_bar_ {new QStatusBar {this}}
, control_button_box_ {new QDialogButtonBox {this}}
, form_layout_ {new QFormLayout}
, horizontal_layout_ {new QHBoxLayout}
, subform1_layout_ {new QFormLayout}
, subform2_layout_ {new QFormLayout}
, subform3_layout_ {new QFormLayout}
, decodes_layout_ {new QVBoxLayout {decodes_page_}}
, beacons_layout_ {new QVBoxLayout {beacons_page_}}
, content_layout_ {new QVBoxLayout {content_widget_}}
, decodes_stack_ {new QStackedLayout}
, auto_off_button_ {new QPushButton {tr ("&Auto Off")}}
, halt_tx_button_ {new QPushButton {tr ("&Halt Tx")}}
, de_label_ {new QLabel}
, mode_label_ {new QLabel}
, fast_mode_ {false}
, frequency_label_ {new QLabel}
, dx_label_ {new QLabel}
, rx_df_label_ {new QLabel}
, tx_df_label_ {new QLabel}
, report_label_ {new QLabel}
, configuration_line_edit_ {new QLineEdit}
, columns_resized_ {false}
{
// set up widgets
@ -153,12 +181,33 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
decodes_table_view_->insertAction (nullptr, erase_rx_frequency_action_);
decodes_table_view_->insertAction (nullptr, erase_both_action_);
auto form_layout = new QFormLayout;
form_layout->addRow (tr ("Free text:"), message_line_edit_);
form_layout->addRow (tr ("Temporary grid:"), grid_line_edit_);
form_layout->addRow (tr ("Configuration name:"), configuration_line_edit_);
message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this});
grid_line_edit_->setValidator (new MaidenheadLocatorValidator {this});
message_line_edit_->setValidator (&message_validator);
grid_line_edit_->setValidator (&locator_validator);
dx_grid_line_edit_->setValidator (&locator_validator);
tr_period_spin_box_->setRange (5, 30);
tr_period_spin_box_->setSuffix (" s");
rx_df_spin_box_->setRange (200, 5000);
frequency_tolerance_spin_box_->setRange (10, 1000);
frequency_tolerance_spin_box_->setPrefix ("\u00b1");
frequency_tolerance_spin_box_->setSuffix (" Hz");
form_layout_->addRow (tr ("Free text:"), message_line_edit_);
form_layout_->addRow (tr ("Temporary grid:"), grid_line_edit_);
form_layout_->addRow (tr ("Configuration name:"), configuration_line_edit_);
form_layout_->addRow (horizontal_layout_);
subform1_layout_->addRow (tr ("Mode:"), mode_line_edit_);
subform2_layout_->addRow (tr ("Submode:"), submode_line_edit_);
subform3_layout_->addRow (tr ("Fast mode:"), fast_mode_check_box_);
subform1_layout_->addRow (tr ("T/R period:"), tr_period_spin_box_);
subform2_layout_->addRow (tr ("Rx DF:"), rx_df_spin_box_);
subform3_layout_->addRow (tr ("Freq. Tol:"), frequency_tolerance_spin_box_);
subform1_layout_->addRow (tr ("DX call:"), dx_call_line_edit_);
subform2_layout_->addRow (tr ("DX grid:"), dx_grid_line_edit_);
subform3_layout_->addRow (generate_messages_push_button_);
horizontal_layout_->addLayout (subform1_layout_);
horizontal_layout_->addLayout (subform2_layout_);
horizontal_layout_->addLayout (subform3_layout_);
connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) {
Q_EMIT do_free_text (id_, text, false);
});
@ -171,66 +220,99 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
connect (configuration_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT switch_configuration (id_, configuration_line_edit_->text ());
});
connect (mode_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty;
Q_EMIT configure (id_, mode_line_edit_->text (), quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, false);
});
connect (frequency_tolerance_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty;
auto f = frequency_tolerance_spin_box_->specialValueText ().size () ? quint32_max : i;
Q_EMIT configure (id_, empty, f, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, false);
});
connect (submode_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, submode_line_edit_->text (), fast_mode ()
, quint32_max, quint32_max, empty, empty, false);
});
connect (fast_mode_check_box_, &QCheckBox::stateChanged, [this] (int state) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, Qt::Checked == state
, quint32_max, quint32_max, empty, empty, false);
});
connect (tr_period_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, i, quint32_max, empty, empty, false);
});
connect (rx_df_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, quint32_max, i, empty, empty, false);
});
connect (dx_call_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, dx_call_line_edit_->text (), empty, false);
});
connect (dx_grid_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, dx_grid_line_edit_->text (), false);
});
auto decodes_page = new QWidget;
auto decodes_layout = new QVBoxLayout {decodes_page};
decodes_layout->setContentsMargins (QMargins {2, 2, 2, 2});
decodes_layout->addWidget (decodes_table_view_);
decodes_layout->addLayout (form_layout);
decodes_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
decodes_layout_->addWidget (decodes_table_view_);
decodes_layout_->addLayout (form_layout_);
auto beacons_proxy_model = new IdFilterModel {id_};
beacons_proxy_model->setSourceModel (beacons_model);
beacons_table_view_->setModel (beacons_proxy_model);
beacons_proxy_model_.setSourceModel (beacons_model);
beacons_table_view_->setModel (&beacons_proxy_model_);
beacons_table_view_->verticalHeader ()->hide ();
beacons_table_view_->hideColumn (0);
beacons_table_view_->horizontalHeader ()->setStretchLastSection (true);
beacons_table_view_->setContextMenuPolicy (Qt::ActionsContextMenu);
beacons_table_view_->insertAction (nullptr, erase_action_);
auto beacons_page = new QWidget;
auto beacons_layout = new QVBoxLayout {beacons_page};
beacons_layout->setContentsMargins (QMargins {2, 2, 2, 2});
beacons_layout->addWidget (beacons_table_view_);
beacons_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
beacons_layout_->addWidget (beacons_table_view_);
decodes_stack_->addWidget (decodes_page);
decodes_stack_->addWidget (beacons_page);
decodes_stack_->addWidget (decodes_page_);
decodes_stack_->addWidget (beacons_page_);
// stack alternative views
auto content_layout = new QVBoxLayout;
content_layout->setContentsMargins (QMargins {2, 2, 2, 2});
content_layout->addLayout (decodes_stack_);
content_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
content_layout_->addLayout (decodes_stack_);
// set up controls
auto control_button_box = new QDialogButtonBox;
control_button_box->addButton (auto_off_button_, QDialogButtonBox::ActionRole);
control_button_box->addButton (halt_tx_button_, QDialogButtonBox::ActionRole);
auto_off_button_ = control_button_box_->addButton (tr ("&Auto Off"), QDialogButtonBox::ActionRole);
halt_tx_button_ = control_button_box_->addButton (tr ("&Halt Tx"), QDialogButtonBox::ActionRole);
connect (generate_messages_push_button_, &QAbstractButton::clicked, [this] (bool /*checked*/) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, true);
});
connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
Q_EMIT do_halt_tx (id_, true);
});
connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
Q_EMIT do_halt_tx (id_, false);
});
content_layout->addWidget (control_button_box);
content_layout_->addWidget (control_button_box_);
// set up status area
auto status_bar = new QStatusBar;
status_bar->addPermanentWidget (de_label_);
status_bar->addPermanentWidget (mode_label_);
status_bar->addPermanentWidget (frequency_label_);
status_bar->addPermanentWidget (dx_label_);
status_bar->addPermanentWidget (rx_df_label_);
status_bar->addPermanentWidget (tx_df_label_);
status_bar->addPermanentWidget (report_label_);
content_layout->addWidget (status_bar);
connect (this, &ClientWidget::topLevelChanged, status_bar, &QStatusBar::setSizeGripEnabled);
status_bar_->addPermanentWidget (de_label_);
status_bar_->addPermanentWidget (tx_mode_label_);
status_bar_->addPermanentWidget (frequency_label_);
status_bar_->addPermanentWidget (tx_df_label_);
status_bar_->addPermanentWidget (report_label_);
content_layout_->addWidget (status_bar_);
connect (this, &ClientWidget::topLevelChanged, status_bar_, &QStatusBar::setSizeGripEnabled);
// set up central widget
auto content_widget = new QFrame;
content_widget->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken);
content_widget->setLayout (content_layout);
setWidget (content_widget);
content_widget_->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken);
setWidget (content_widget_);
// setMinimumSize (QSize {550, 0});
setFeatures (DockWidgetMovable | DockWidgetFloatable);
setAllowedAreas (Qt::BottomDockWidgetArea);
setFloating (true);
@ -257,6 +339,25 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
}
}
void ClientWidget::dispose ()
{
done_ = true;
close ();
}
void ClientWidget::closeEvent (QCloseEvent *e)
{
if (!done_)
{
Q_EMIT do_close (id_);
e->ignore (); // defer closure until client actually closes
}
else
{
QDockWidget::closeEvent (e);
}
}
ClientWidget::~ClientWidget ()
{
for (int row = 0; row < calls_of_interest_->count (); ++row)
@ -266,16 +367,45 @@ ClientWidget::~ClientWidget ()
}
}
bool ClientWidget::fast_mode () const
{
return fast_mode_check_box_->isChecked ();
}
namespace
{
void update_line_edit (QLineEdit * le, QString const& value, bool allow_empty = true)
{
le->setEnabled (value.size () || allow_empty);
if (!(le->hasFocus () && le->isModified ()))
{
le->setText (value);
}
}
void update_spin_box (QSpinBox * sb, int value, QString const& special_value = QString {})
{
sb->setSpecialValueText (special_value);
bool enable {0 == special_value.size ()};
sb->setEnabled (enable);
if (!sb->hasFocus () && enable)
{
sb->setValue (value);
}
}
}
void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
, QString const& de_call, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
, quint8 special_op_mode, QString const& configuration_name)
, bool watchdog_timeout, QString const& submode, bool fast_mode
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name)
{
if (id == id_)
if (id == id_)
{
fast_mode_ = fast_mode;
fast_mode_check_box_->setChecked (fast_mode);
decodes_proxy_model_.de_call (de_call);
decodes_proxy_model_.rx_df (rx_df);
QString special;
@ -293,26 +423,26 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const&
.arg (de_grid.size () ? '(' + de_grid + ')' : QString {})
.arg (special)
: QString {});
mode_label_->setText (QString {"Mode: %1%2%3%4"}
.arg (mode)
.arg (sub_mode)
.arg (fast_mode && !mode.contains (QRegularExpression {R"(ISCAT|MSK144)"}) ? "fast" : "")
update_line_edit (mode_line_edit_, mode);
update_spin_box (frequency_tolerance_spin_box_, frequency_tolerance
, quint32_max == frequency_tolerance ? QString {"n/a"} : QString {});
update_line_edit (submode_line_edit_, submode, false);
tx_mode_label_->setText (QString {"Tx Mode: %1"}
.arg (tx_mode.isEmpty () || tx_mode == mode ? "" : '(' + tx_mode + ')'));
frequency_label_->setText ("QRG: " + Radio::pretty_frequency_MHz_string (f));
dx_label_->setText (dx_call.size () >= 0 ? QString {"DX: %1%2"}.arg (dx_call)
.arg (dx_grid.size () ? '(' + dx_grid + ')' : QString {}) : QString {});
rx_df_label_->setText (rx_df >= 0 ? QString {"Rx: %1"}.arg (rx_df) : "");
tx_df_label_->setText (tx_df >= 0 ? QString {"Tx: %1"}.arg (tx_df) : "");
update_line_edit (dx_call_line_edit_, dx_call);
update_line_edit (dx_grid_line_edit_, dx_grid);
if (rx_df != quint32_max) update_spin_box (rx_df_spin_box_, rx_df);
update_spin_box (tr_period_spin_box_, tr_period
, quint32_max == tr_period ? QString {"n/a"} : QString {});
tx_df_label_->setText (QString {"Tx: %1"}.arg (tx_df));
report_label_->setText ("SNR: " + report);
update_dynamic_property (frequency_label_, "transmitting", transmitting);
auto_off_button_->setEnabled (tx_enabled);
halt_tx_button_->setEnabled (transmitting);
update_dynamic_property (mode_label_, "decoding", decoding);
update_line_edit (configuration_line_edit_, configuration_name);
update_dynamic_property (mode_line_edit_, "decoding", decoding);
update_dynamic_property (tx_df_label_, "watchdog_timeout", watchdog_timeout);
if (!configuration_line_edit_->hasFocus ())
{
configuration_line_edit_->setText (configuration_name);
}
}
}

View File

@ -1,11 +1,11 @@
#ifndef WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
#define WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
#include <QDockWidget>
#include <QObject>
#include <QSortFilterProxyModel>
#include <QString>
#include <QRegularExpression>
#include <QtWidgets>
#include "MessageServer.hpp"
@ -13,6 +13,20 @@ class QAbstractItemModel;
class QModelIndex;
class QColor;
class QAction;
class QListWidget;
class QFormLayout;
class QVBoxLayout;
class QHBoxLayout;
class QStackedLayout;
class QTableView;
class QLineEdit;
class QAbstractButton;
class QLabel;
class QCheckBox;
class QSpinBox;
class QFrame;
class QStatusBar;
class QDialogButtonBox;
using Frequency = MessageServer::Frequency;
@ -25,16 +39,18 @@ public:
explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
, QString const& id, QString const& version, QString const& revision
, QListWidget const * calls_of_interest, QWidget * parent = nullptr);
void dispose ();
~ClientWidget ();
bool fast_mode () const {return fast_mode_;}
bool fast_mode () const;
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
, QString const& de_call, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
, quint8 special_op_mode, QString const& configuration_name);
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name);
Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime, qint32 snr
, float delta_time, quint32 delta_frequency, QString const& mode
, QString const& message, bool low_confidence, bool off_air);
@ -45,6 +61,7 @@ public:
Q_SLOT void decodes_cleared (QString const& client_id);
Q_SIGNAL void do_clear_decodes (QString const& id, quint8 window = 0);
Q_SIGNAL void do_close (QString const& id);
Q_SIGNAL void do_reply (QModelIndex const&, quint8 modifier);
Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only);
Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool);
@ -53,31 +70,38 @@ public:
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
, bool last_only = false);
Q_SIGNAL void switch_configuration (QString const& id, QString const& configuration_name);
Q_SIGNAL void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages);
private:
QString id_;
QListWidget const * calls_of_interest_;
class IdFilterModel final
: public QSortFilterProxyModel
{
public:
IdFilterModel (QString const& client_id);
IdFilterModel (QString const& client_id, QObject * = nullptr);
void de_call (QString const&);
void rx_df (int);
void rx_df (quint32);
QVariant data (QModelIndex const& proxy_index, int role = Qt::DisplayRole) const override;
protected:
private:
bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override;
private:
QString client_id_;
QString call_;
QRegularExpression base_call_re_;
int rx_df_;
} decodes_proxy_model_;
quint32 rx_df_;
};
void closeEvent (QCloseEvent *) override;
QString id_;
bool done_;
QListWidget const * calls_of_interest_;
IdFilterModel decodes_proxy_model_;
IdFilterModel beacons_proxy_model_;
QAction * erase_action_;
QAction * erase_rx_frequency_action_;
QAction * erase_both_action_;
@ -85,18 +109,39 @@ private:
QTableView * beacons_table_view_;
QLineEdit * message_line_edit_;
QLineEdit * grid_line_edit_;
QStackedLayout * decodes_stack_;
QAbstractButton * generate_messages_push_button_;
QAbstractButton * auto_off_button_;
QAbstractButton * halt_tx_button_;
QLabel * de_label_;
QLabel * mode_label_;
bool fast_mode_;
QLabel * frequency_label_;
QLabel * dx_label_;
QLabel * rx_df_label_;
QLabel * tx_df_label_;
QLabel * report_label_;
QLineEdit * configuration_line_edit_;
QLineEdit * mode_line_edit_;
QSpinBox * frequency_tolerance_spin_box_;
QLabel * tx_mode_label_;
QLineEdit * submode_line_edit_;
QCheckBox * fast_mode_check_box_;
QSpinBox * tr_period_spin_box_;
QSpinBox * rx_df_spin_box_;
QLineEdit * dx_call_line_edit_;
QLineEdit * dx_grid_line_edit_;
QWidget * decodes_page_;
QWidget * beacons_page_;
QFrame * content_widget_;
QStatusBar * status_bar_;
QDialogButtonBox * control_button_box_;
QFormLayout * form_layout_;
QHBoxLayout * horizontal_layout_;
QFormLayout * subform1_layout_;
QFormLayout * subform2_layout_;
QFormLayout * subform3_layout_;
QVBoxLayout * decodes_layout_;
QVBoxLayout * beacons_layout_;
QVBoxLayout * content_layout_;
QStackedLayout * decodes_stack_;
bool columns_resized_;
};

View File

@ -250,6 +250,7 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const&
connect (server_, &MessageServer::WSPR_decode, dock, &ClientWidget::beacon_spot_added);
connect (server_, &MessageServer::decodes_cleared, dock, &ClientWidget::decodes_cleared);
connect (dock, &ClientWidget::do_clear_decodes, server_, &MessageServer::clear_decodes);
connect (dock, &ClientWidget::do_close, server_, &MessageServer::close);
connect (dock, &ClientWidget::do_reply, decodes_model_, &DecodesModel::do_reply);
connect (dock, &ClientWidget::do_halt_tx, server_, &MessageServer::halt_tx);
connect (dock, &ClientWidget::do_free_text, server_, &MessageServer::free_text);
@ -257,6 +258,7 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const&
connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible);
connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign);
connect (dock, &ClientWidget::switch_configuration, server_, &MessageServer::switch_configuration);
connect (dock, &ClientWidget::configure, server_, &MessageServer::configure);
dock_widgets_[id] = dock;
server_->replay (id); // request decodes and status
}
@ -266,7 +268,7 @@ void MessageAggregatorMainWindow::remove_client (QString const& id)
auto iter = dock_widgets_.find (id);
if (iter != std::end (dock_widgets_))
{
(*iter)->close ();
(*iter)->dispose ();
dock_widgets_.erase (iter);
}
}

View File

@ -51,7 +51,8 @@ public:
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
, QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/
, bool /* watchdog_timeout */, QString const& sub_mode, bool /*fast_mode*/
, quint8 /*special_op_mode*/, QString const& /*configuration_name*/)
, quint8 /*special_op_mode*/, quint32 /*frequency_tolerance*/, quint32 /*tr_period*/
, QString const& /*configuration_name*/)
{
if (id == id_)
{

View File

@ -17,3 +17,17 @@ QValidator::State RestrictedSpinBox::validate (QString& input, int& pos) const
}
return valid;
}
void RestrictedSpinBox::fixup (QString& input) const
{
auto iter = std::lower_bound (values ().begin (), values ().end (), valueFromText (input));
HintedSpinBox::fixup (input);
if (iter != values ().end ())
{
input = textFromValue (*iter);
}
else
{
input = textFromValue (values ().back ());
}
}

View File

@ -20,6 +20,7 @@ public:
protected:
// override the base class validation
QValidator::State validate (QString& input, int& pos) const override;
void fixup (QString& input) const override;
};
#endif

View File

@ -9,6 +9,7 @@
#include <iterator>
#include <algorithm>
#include <fftw3.h>
#include <QApplication>
#include <QStringListModel>
#include <QSettings>
#include <QKeyEvent>
@ -201,6 +202,7 @@ namespace
QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>;]*"};
// grid exact match excluding RR73
QRegularExpression grid_regexp {"\\A(?![Rr]{2}73)[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}){0,1}\\z"};
auto quint32_max = std::numeric_limits<quint32>::max ();
bool message_is_73 (int type, QStringList const& msg_parts)
{
@ -410,7 +412,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_config.udp_server_name (), m_config.udp_server_port (),
this}},
psk_Reporter {new PSK_Reporter {m_messageClient, this}},
m_manual {&m_network_manager}
m_manual {&m_network_manager},
m_block_udp_status_updates {false}
{
ui->setupUi(this);
setUnifiedTitleAndToolBarOnMac (true);
@ -501,6 +504,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
});
// Network message handlers
m_messageClient->enable (m_config.accept_udp_requests ());
connect (m_messageClient, &MessageClient::clear_decodes, [this] (quint8 window) {
++window;
if (window & 1)
@ -513,54 +517,51 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
}
});
connect (m_messageClient, &MessageClient::reply, this, &MainWindow::replyToCQ);
connect (m_messageClient, &MessageClient::close, this, &MainWindow::close);
connect (m_messageClient, &MessageClient::replay, this, &MainWindow::replayDecodes);
connect (m_messageClient, &MessageClient::location, this, &MainWindow::locationChange);
connect (m_messageClient, &MessageClient::halt_tx, [this] (bool auto_only) {
if (m_config.accept_udp_requests ()) {
if (auto_only) {
if (ui->autoButton->isChecked ()) {
ui->autoButton->click();
}
} else {
ui->stopTxButton->click();
if (auto_only) {
if (ui->autoButton->isChecked ()) {
ui->autoButton->click();
}
} else {
ui->stopTxButton->click();
}
});
connect (m_messageClient, &MessageClient::error, this, &MainWindow::networkError);
connect (m_messageClient, &MessageClient::free_text, [this] (QString const& text, bool send) {
if (m_config.accept_udp_requests ()) {
tx_watchdog (false);
// send + non-empty text means set and send the free text
// message, !send + non-empty text means set the current free
// text message, send + empty text means send the current free
// text message without change, !send + empty text means clear
// the current free text message
if (0 == ui->tabWidget->currentIndex ()) {
if (!text.isEmpty ()) {
ui->tx5->setCurrentText (text);
}
if (send) {
ui->txb5->click ();
} else if (text.isEmpty ()) {
ui->tx5->setCurrentText (text);
}
} else if (1 == ui->tabWidget->currentIndex ()) {
if (!text.isEmpty ()) {
ui->freeTextMsg->setCurrentText (text);
}
if (send) {
ui->rbFreeText->click ();
} else if (text.isEmpty ()) {
ui->freeTextMsg->setCurrentText (text);
}
tx_watchdog (false);
// send + non-empty text means set and send the free text
// message, !send + non-empty text means set the current free
// text message, send + empty text means send the current free
// text message without change, !send + empty text means clear
// the current free text message
if (0 == ui->tabWidget->currentIndex ()) {
if (!text.isEmpty ()) {
ui->tx5->setCurrentText (text);
}
if (send) {
ui->txb5->click ();
} else if (text.isEmpty ()) {
ui->tx5->setCurrentText (text);
}
} else if (1 == ui->tabWidget->currentIndex ()) {
if (!text.isEmpty ()) {
ui->freeTextMsg->setCurrentText (text);
}
if (send) {
ui->rbFreeText->click ();
} else if (text.isEmpty ()) {
ui->freeTextMsg->setCurrentText (text);
}
QApplication::alert (this);
}
QApplication::alert (this);
});
connect (m_messageClient, &MessageClient::highlight_callsign, ui->decodedTextBrowser, &DisplayText::highlight_callsign);
connect (m_messageClient, &MessageClient::switch_configuration, m_multi_settings, &MultiSettings::select_configuration);
connect (m_messageClient, &MessageClient::configure, this, &MainWindow::remote_configure);
// Hook up WSPR band hopping
connect (ui->band_hopping_schedule_push_button, &QPushButton::clicked
@ -711,6 +712,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure);
connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server);
connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port);
connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable);
// set up configurations menu
connect (m_multi_settings, &MultiSettings::configurationNameChanged, [this] (QString const& name) {
@ -905,20 +907,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
if(m_bFast9) m_bFastMode=true;
ui->cbFast9->setChecked(m_bFast9 or m_bFastMode);
if(m_mode=="FT4") on_actionFT4_triggered();
if(m_mode=="FT8") on_actionFT8_triggered();
if(m_mode=="JT4") on_actionJT4_triggered();
if(m_mode=="JT9") on_actionJT9_triggered();
if(m_mode=="JT65") on_actionJT65_triggered();
if(m_mode=="JT9+JT65") on_actionJT9_JT65_triggered();
if(m_mode=="WSPR") on_actionWSPR_triggered();
if(m_mode=="WSPR-LF") on_actionWSPR_LF_triggered();
if(m_mode=="ISCAT") on_actionISCAT_triggered();
if(m_mode=="MSK144") on_actionMSK144_triggered();
if(m_mode=="QRA64") on_actionQRA64_triggered();
if(m_mode=="Echo") on_actionEcho_triggered();
set_mode (m_mode);
if(m_mode=="Echo") monitor(false); //Don't auto-start Monitor in Echo mode.
if(m_mode=="FreqCal") on_actionFreqCal_triggered();
ui->sbSubmode->setValue (vhf ? m_nSubMode : 0);
if(m_mode=="MSK144") {
@ -1762,19 +1752,7 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog
bool b = vhf && (m_mode=="JT4" or m_mode=="JT65" or m_mode=="ISCAT" or
m_mode=="JT9" or m_mode=="MSK144" or m_mode=="QRA64");
if(b) VHF_features_enabled(b);
if(m_mode=="FT4") on_actionFT4_triggered();
if(m_mode=="FT8") on_actionFT8_triggered();
if(m_mode=="JT4") on_actionJT4_triggered();
if(m_mode=="JT9") on_actionJT9_triggered();
if(m_mode=="JT9+JT65") on_actionJT9_JT65_triggered();
if(m_mode=="JT65") on_actionJT65_triggered();
if(m_mode=="QRA64") on_actionQRA64_triggered();
if(m_mode=="FreqCal") on_actionFreqCal_triggered();
if(m_mode=="ISCAT") on_actionISCAT_triggered();
if(m_mode=="MSK144") on_actionMSK144_triggered();
if(m_mode=="WSPR") on_actionWSPR_triggered();
if(m_mode=="WSPR-LF") on_actionWSPR_LF_triggered();
if(m_mode=="Echo") on_actionEcho_triggered();
set_mode (m_mode);
if(b) VHF_features_enabled(b);
m_config.transceiver_online ();
@ -7190,6 +7168,7 @@ void MainWindow::transmitDisplay (bool transmitting)
void MainWindow::on_sbFtol_valueChanged(int value)
{
m_wideGraph->setTol (value);
statusUpdate ();
}
void::MainWindow::VHF_features_enabled(bool b)
@ -7227,6 +7206,7 @@ void MainWindow::on_sbTR_valueChanged(int value)
if(m_transmitting) {
on_stopTxButton_clicked();
}
statusUpdate ();
}
QChar MainWindow::current_submode () const
@ -7328,11 +7308,6 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de
, QString const& mode, QString const& message_text
, bool /*low_confidence*/, quint8 modifiers)
{
if (!m_config.accept_udp_requests ())
{
return;
}
QString format_string {"%1 %2 %3 %4 %5 %6"};
auto const& time_string = time.toString ("~" == mode || "&" == mode
|| "+" == mode ? "hhmmss" : "hhmm");
@ -7901,8 +7876,18 @@ void MainWindow::on_cbCQTx_toggled(bool b)
void MainWindow::statusUpdate () const
{
if (!ui) return;
if (!ui || m_block_udp_status_updates) return;
auto submode = current_submode ();
auto ftol = ui->sbFtol->value ();
if (!(ui->sbFtol->isVisible () && ui->sbFtol->isEnabled ()))
{
ftol = quint32_max;
}
auto tr_period = ui->sbTR->value ();
if (!(ui->sbTR->isVisible () && ui->sbTR->isEnabled ()))
{
tr_period = quint32_max;
}
m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
QString::number (ui->rptSpinBox->value ()),
m_modeTx, ui->autoButton->isChecked (),
@ -7912,7 +7897,7 @@ void MainWindow::statusUpdate () const
m_hisGrid, m_tx_watchdog,
submode != QChar::Null ? QString {submode} : QString {}, m_bFastMode,
static_cast<quint8> (m_config.special_op_id ()),
m_multi_settings->configuration_name ());
ftol, tr_period, m_multi_settings->configuration_name ());
}
void MainWindow::childEvent (QChildEvent * e)
@ -8754,3 +8739,79 @@ void MainWindow::on_pbBestSP_clicked()
if(!m_bBestSPArmed) ui->pbBestSP->setStyleSheet ("");
if(m_bBestSPArmed) m_dateTimeBestSP=QDateTime::currentDateTimeUtc();
}
void MainWindow::set_mode (QString const& mode)
{
if ("FT4" == mode) on_actionFT4_triggered ();
else if ("FT8" == mode) on_actionFT8_triggered ();
else if ("JT4" == mode) on_actionJT4_triggered ();
else if ("JT9" == mode) on_actionJT9_triggered ();
else if ("JT9+JT65" == mode) on_actionJT9_JT65_triggered ();
else if ("JT65" == mode) on_actionJT65_triggered ();
else if ("QRA64" == mode) on_actionQRA64_triggered ();
else if ("FreqCal" == mode) on_actionFreqCal_triggered ();
else if ("ISCAT" == mode) on_actionISCAT_triggered ();
else if ("MSK144" == mode) on_actionMSK144_triggered ();
else if ("WSPR" == mode) on_actionWSPR_triggered ();
else if ("WSPR-LF" == mode) on_actionWSPR_LF_triggered ();
else if ("Echo" == mode) on_actionEcho_triggered ();
}
void MainWindow::remote_configure (QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages)
{
if (mode.size ())
{
set_mode (mode);
}
if (frequency_tolerance != quint32_max && ui->sbFtol->isVisible ())
{
ui->sbFtol->setValue (frequency_tolerance);
}
if (submode.size () && ui->sbSubmode->isVisible ())
{
ui->sbSubmode->setValue (submode.toUpper ().at (0).toLatin1 () - 'A');
}
if (ui->cbFast9->isVisible () && ui->cbFast9->isChecked () != fast_mode)
{
ui->cbFast9->click ();
}
if (tr_period != quint32_max && ui->sbTR->isVisible ())
{
ui->sbTR->setValue (tr_period);
ui->sbTR->interpretText ();
}
if (rx_df != quint32_max && ui->RxFreqSpinBox->isVisible ())
{
m_block_udp_status_updates = true;
ui->RxFreqSpinBox->setValue (rx_df);
ui->RxFreqSpinBox->interpretText ();
m_block_udp_status_updates = false;
}
if (dx_call.size () && ui->dxCallEntry->isVisible ())
{
ui->dxCallEntry->setText (dx_call);
}
if (dx_grid.size () && ui->dxGridEntry->isVisible ())
{
ui->dxGridEntry->setText (dx_grid);
}
if (generate_messages && ui->genStdMsgsPushButton->isVisible ())
{
ui->genStdMsgsPushButton->click ();
}
if (m_config.udpWindowToFront ())
{
show ();
raise ();
activateWindow ();
}
if (m_config.udpWindowRestore () && isMinimized ())
{
showNormal ();
raise ();
}
tx_watchdog (false);
QApplication::alert (this);
}

View File

@ -313,6 +313,9 @@ private slots:
void on_pbBestSP_clicked();
int setTxMsg(int n);
bool stdCall(QString const& w);
void remote_configure (QString const& mode, quint32 frequency_tolerance, QString const& submode
, bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call
, QString const& dx_grid, bool generate_messages);
private:
Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo,
@ -339,6 +342,7 @@ private:
Q_SIGNAL void toggleShorthand () const;
private:
void set_mode (QString const& mode);
void astroUpdate ();
void writeAllTxt(QString message);
void auto_sequence (DecodedText const& message, unsigned start_tolerance, unsigned stop_tolerance);
@ -680,6 +684,7 @@ private:
QHash<QString, QVariant> m_pwrBandTuneMemory; // Remembers power level by band for tuning
QByteArray m_geometryNoControls;
QVector<double> m_phaseEqCoefficients;
bool m_block_udp_status_updates;
//---------------------------------------------------- private functions
void readSettings();