diff --git a/Configuration.cpp b/Configuration.cpp index 2ac22b0b7..ebb6380b3 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -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 (); diff --git a/Configuration.hpp b/Configuration.hpp index 80f5a5cd1..dbf232fd5 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -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; diff --git a/MessageClient.cpp b/MessageClient.cpp index 0629c3026..ddf269564 100644 --- a/MessageClient.cpp +++ b/MessageClient.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -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::max ()}; + quint32 rx_df {std::numeric_limits::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); } } diff --git a/MessageClient.hpp b/MessageClient.hpp index 2396a6ad0..20f39b019 100644 --- a/MessageClient.hpp +++ b/MessageClient.hpp @@ -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; diff --git a/MessageServer.cpp b/MessageServer.cpp index 02d23e460..07b2fb429 100644 --- a/MessageServer.cpp +++ b/MessageServer.cpp @@ -1,6 +1,7 @@ #include "MessageServer.hpp" #include +#include #include #include @@ -16,6 +17,11 @@ #include "moc_MessageServer.cpp" +namespace +{ + auto quint32_max = std::numeric_limits::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_); + } +} diff --git a/MessageServer.hpp b/MessageServer.hpp index 2764889cf..f59c21fe0 100644 --- a/MessageServer.hpp +++ b/MessageServer.hpp @@ -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 diff --git a/NetworkMessage.hpp b/NetworkMessage.hpp index be85d63ee..4628c29b8 100644 --- a/NetworkMessage.hpp +++ b/NetworkMessage.hpp @@ -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 @@ -455,6 +484,7 @@ namespace NetworkMessage LoggedADIF, HighlightCallsign, SwitchConfiguration, + Configure, maximum_message_type_ // ONLY add new message types // immediately before here }; diff --git a/UDPExamples/ClientWidget.cpp b/UDPExamples/ClientWidget.cpp index 7d1601938..ecf0b54fe 100644 --- a/UDPExamples/ClientWidget.cpp +++ b/UDPExamples/ClientWidget.cpp @@ -1,8 +1,11 @@ #include "ClientWidget.hpp" +#include #include #include +#include #include +#include #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::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 (&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 (&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 (&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); - } } } diff --git a/UDPExamples/ClientWidget.hpp b/UDPExamples/ClientWidget.hpp index 9aa8d35a0..983ddd874 100644 --- a/UDPExamples/ClientWidget.hpp +++ b/UDPExamples/ClientWidget.hpp @@ -1,11 +1,11 @@ #ifndef WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__ #define WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__ +#include #include #include #include #include -#include #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_; }; diff --git a/UDPExamples/MessageAggregatorMainWindow.cpp b/UDPExamples/MessageAggregatorMainWindow.cpp index 34c674f2c..6245918a9 100644 --- a/UDPExamples/MessageAggregatorMainWindow.cpp +++ b/UDPExamples/MessageAggregatorMainWindow.cpp @@ -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); } } diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index 98ff49e6e..7caeb3038 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -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_) { diff --git a/widgets/RestrictedSpinBox.cpp b/widgets/RestrictedSpinBox.cpp index d3201b331..3d18ce488 100644 --- a/widgets/RestrictedSpinBox.cpp +++ b/widgets/RestrictedSpinBox.cpp @@ -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 ()); + } +} diff --git a/widgets/RestrictedSpinBox.hpp b/widgets/RestrictedSpinBox.hpp index f856e90ce..e984c3770 100644 --- a/widgets/RestrictedSpinBox.hpp +++ b/widgets/RestrictedSpinBox.hpp @@ -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 diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 529dab19d..b11e7f9bb 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -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::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 (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); +} diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index ca545ec35..514dbb30a 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -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 m_pwrBandTuneMemory; // Remembers power level by band for tuning QByteArray m_geometryNoControls; QVector m_phaseEqCoefficients; + bool m_block_udp_status_updates; //---------------------------------------------------- private functions void readSettings();