diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d8f39223..b6621bb45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -402,6 +402,10 @@ set (all_C_and_CXXSRCS ${all_CXXSRCS} ) +set (message_aggregator_STYLESHEETS + qss/default.qss + ) + set (TOP_LEVEL_RESOURCES shortcuts.txt mouse_commands.txt @@ -890,9 +894,13 @@ set_target_properties (wsjtx PROPERTIES target_link_libraries (wsjtx wsjt_fort wsjt_cxx wsjt_qt ${hamlib_LIBRARIES} ${FFTW3_LIBRARIES}) qt5_use_modules (wsjtx Widgets OpenGL Network Multimedia SerialPort) +add_resources (message_aggregator_RESOURCES /qss ${message_aggregator_STYLESHEETS}) +configure_file (message_aggregator.qrc.in message_aggregator.qrc @ONLY) +qt5_add_resources (message_aggregator_RESOURCES_RCC ${CMAKE_BINARY_DIR}/message_aggregator.qrc) add_executable (message_aggregator ${message_aggregator_CXXSRCS} wsjtx.rc + ${message_aggregator_RESOURCES_RCC} ) target_link_libraries (message_aggregator wsjt_qt) qt5_use_modules (message_aggregator Widgets OpenGL Network) diff --git a/MessageAggregator.cpp b/MessageAggregator.cpp index 05250e989..f2dbbb6b9 100644 --- a/MessageAggregator.cpp +++ b/MessageAggregator.cpp @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -51,6 +52,8 @@ using port_type = MessageServer::port_type; using Frequency = MessageServer::Frequency; +QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"}; + // // Decodes Model - simple data model for all decodes // @@ -191,6 +194,8 @@ public: : QDockWidget {id, parent} , id_ {id} , decodes_table_view_ {new QTableView} + , message_line_edit_ {new QLineEdit} + , halt_tx_button_ {new QPushButton {tr ("&Halt Tx")}} , mode_label_ {new QLabel} , dx_call_label_ {new QLabel} , frequency_label_ {new QLabel} @@ -207,6 +212,21 @@ public: decodes_table_view_->hideColumn (0); content_layout->addWidget (decodes_table_view_); + // set up controls + auto control_layout = new QHBoxLayout; + auto form_layout = new QFormLayout; + form_layout->addRow (tr ("Free text:"), message_line_edit_); + message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this}); + connect (message_line_edit_, &QLineEdit::editingFinished, [this] () { + Q_EMIT do_free_text (id_, message_line_edit_->text ()); + }); + control_layout->addLayout (form_layout); + control_layout->addWidget (halt_tx_button_); + connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) { + Q_EMIT do_halt_tx (id_); + }); + content_layout->addLayout (control_layout); + // set up status area auto status_bar = new QStatusBar; status_bar->addPermanentWidget (mode_label_); @@ -232,7 +252,7 @@ public: } Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call - , QString const& report, QString const& tx_mode) + , QString const& report, QString const& tx_mode, bool transmitting) { if (id == id_) { @@ -240,6 +260,8 @@ public: dx_call_label_->setText ("DX CALL: " + dx_call); frequency_label_->setText ("QRG: " + Radio::pretty_frequency_MHz_string (f)); report_label_->setText ("SNR: " + report); + update_dynamic_property (frequency_label_, "transmitting", transmitting); + halt_tx_button_->setEnabled (transmitting); } } @@ -256,6 +278,8 @@ public: } Q_SIGNAL void do_reply (QModelIndex const&); + Q_SIGNAL void do_halt_tx (QString const& id); + Q_SIGNAL void do_free_text (QString const& id, QString const& text); private: class DecodesFilterModel final @@ -280,6 +304,9 @@ private: QString id_; QTableView * decodes_table_view_; + QLineEdit * message_line_edit_; + QAbstractButton * set_free_text_button_; + QAbstractButton * halt_tx_button_; QLabel * mode_label_; QLabel * dx_call_label_; QLabel * frequency_label_; @@ -406,6 +433,8 @@ private: connect (server_, &MessageServer::status_update, dock, &ClientWidget::update_status); connect (server_, &MessageServer::decode, dock, &ClientWidget::decode_added); 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); connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible); dock_widgets_[id] = dock; server_->replay (id); @@ -444,6 +473,15 @@ int main (int argc, char * argv[]) app.setApplicationName ("WSJT-X Reference UDP Message Aggregator Server"); app.setApplicationVersion ("1.0"); + { + QFile file {":/qss/default.qss"}; + if (!file.open (QFile::ReadOnly)) + { + throw_qstring ("failed to open \"" + file.fileName () + "\": " + file.errorString ()); + } + app.setStyleSheet (file.readAll()); + } + MainWindow window; return app.exec (); } diff --git a/MessageClient.cpp b/MessageClient.cpp index cfea042a8..18ea5298a 100644 --- a/MessageClient.cpp +++ b/MessageClient.cpp @@ -123,6 +123,22 @@ void MessageClient::impl::parse_message (QByteArray const& msg) } break; + case NetworkMessage::HaltTx: + if (check_status (in)) + { + Q_EMIT self_->halt_tx (); + } + break; + + case NetworkMessage::FreeText: + if (check_status (in)) + { + QByteArray message; + in >> message; + Q_EMIT self_->free_text (QString::fromUtf8 (message)); + } + break; + default: // Ignore break; @@ -235,13 +251,14 @@ void MessageClient::send_raw_datagram (QByteArray const& message, QHostAddress c } void MessageClient::status_update (Frequency f, QString const& mode, QString const& dx_call - , QString const& report, QString const& tx_mode) + , QString const& report, QString const& tx_mode, bool transmitting) { if (m_->server_port_ && !m_->server_.isNull ()) { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_}; - out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 (); + out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 () + << transmitting; if (m_->check_status (out)) { m_->writeDatagram (message, m_->server_, m_->server_port_); diff --git a/MessageClient.hpp b/MessageClient.hpp index a1a78d6ef..802ff2578 100644 --- a/MessageClient.hpp +++ b/MessageClient.hpp @@ -46,7 +46,7 @@ public: // outgoing messages Q_SLOT void status_update (Frequency, QString const& mode, QString const& dx_call, QString const& report - , QString const& tx_mode); + , QString const& tx_mode, bool transmitting); Q_SLOT void decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency , QString const& mode, QString const& message); Q_SLOT void clear_decodes (); @@ -69,6 +69,14 @@ public: // all decodes Q_SIGNAL void replay (); + // this signal is emitted if the server has requested transmission + // to halt immediately + Q_SIGNAL void halt_tx (); + + // this signal is emitted if the server has requested a new free + // message text + Q_SIGNAL void free_text (QString const&); + // 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 857ef529d..8b7e69fd2 100644 --- a/MessageServer.cpp +++ b/MessageServer.cpp @@ -142,11 +142,13 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s QByteArray dx_call; QByteArray report; QByteArray tx_mode; - in >> f >> mode >> dx_call >> report >> tx_mode; + bool transmitting; + in >> f >> mode >> dx_call >> report >> tx_mode >> transmitting; if (check_status (in)) { Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call) - , QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)); + , QString::fromUtf8 (report), QString::fromUtf8 (tx_mode) + , transmitting); } } break; @@ -313,3 +315,32 @@ void MessageServer::replay (QString const& id) } } } + +void MessageServer::halt_tx (QString const& id) +{ + auto iter = m_->clients_.find (id); + if (iter != std::end (m_->clients_)) + { + QByteArray message; + NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, id}; + if (m_->check_status (out)) + { + m_->writeDatagram (message, iter.value ().sender_address_, (*iter).sender_port_); + } + } +} + +void MessageServer::free_text (QString const& id, QString const& text) +{ + auto iter = m_->clients_.find (id); + if (iter != std::end (m_->clients_)) + { + QByteArray message; + NetworkMessage::Builder out {&message, NetworkMessage::FreeText, id}; + out << text.toUtf8 (); + if (m_->check_status (out)) + { + m_->writeDatagram (message, iter.value ().sender_address_, (*iter).sender_port_); + } + } +} diff --git a/MessageServer.hpp b/MessageServer.hpp index 5a11bdddb..d2f00d138 100644 --- a/MessageServer.hpp +++ b/MessageServer.hpp @@ -48,11 +48,17 @@ public: // ask the client with identification 'id' to replay all decodes Q_SLOT void replay (QString const& id); + // ask the client with identification 'id' to halt transmitting immediately + Q_SLOT void halt_tx (QString const& id); + + // ask the client with identification 'id' to set the free text message + Q_SLOT void free_text (QString const& id, QString const& text); + // the following signals are emitted when a client broadcasts the // matching message Q_SIGNAL void client_opened (QString const& id); Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call - , QString const& report, QString const& tx_mode); + , QString const& report, QString const& tx_mode, bool transmitting); 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 3ac9fa434..d13c8f920 100644 --- a/NetworkMessage.hpp +++ b/NetworkMessage.hpp @@ -43,6 +43,12 @@ * serialization purposes (currently a quint32 size followed by size * bytes, no terminator is present or counted). * + * The QDataStream format document linked above is not complete for + * the QByteArray serialization format, it is similar to the QString + * serialization format in that it differentiates between empty + * strings and null strings. Empty strings have a length of zero + * whereas null strings have a length field of 0xffffffff. + * * Schema Version 1: * ----------------- * @@ -58,6 +64,7 @@ * DX call utf8 * Report utf8 * Tx Mode utf8 + * Transmitting bool * * Decode Out 2 quint32 * Id (unique key) utf8 @@ -99,6 +106,13 @@ * * Replay In 7 quint32 * Id (unique key) utf8 + * + * Halt Tx In 8 + * Id (unique key) utf8 + * + * Free Text In 9 + * Id (unique key) utf8 + * Text utf8 */ #include @@ -122,6 +136,8 @@ namespace NetworkMessage QSOLogged, Close, Replay, + HaltTx, + FreeText, maximum_message_type_ // ONLY add new message types // immediately before here }; diff --git a/mainwindow.cpp b/mainwindow.cpp index 16103f9e6..5c359af4c 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -187,11 +187,19 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme connect (m_logDlg.data (), &LogQSO::acceptQSO, this, &MainWindow::acceptQSO2); connect (this, &MainWindow::finished, m_logDlg.data (), &LogQSO::close); - // Network message handlers connect (m_messageClient, &MessageClient::reply, this, &MainWindow::replyToCQ); connect (m_messageClient, &MessageClient::replay, this, &MainWindow::replayDecodes); + connect (m_messageClient, &MessageClient::halt_tx, ui->stopTxButton, &QAbstractButton::click); connect (m_messageClient, &MessageClient::error, this, &MainWindow::networkError); + connect (m_messageClient, &MessageClient::free_text, [this] (QString const& text) { + if (0 == ui->tabWidget->currentIndex ()) { + ui->tx5->setCurrentText (text); + ui->txrb5->click (); + } else { + ui->freeTextMsg->setCurrentText (text); + ui->rbFreeText->click (); + }}); on_EraseButton_clicked (); @@ -1022,7 +1030,7 @@ void MainWindow::displayDialFrequency () void MainWindow::statusChanged() { - m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, QString::number (ui->rptSpinBox->value ()), m_modeTx); + m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, QString::number (ui->rptSpinBox->value ()), m_modeTx, m_transmitting); QFile f {m_config.temp_dir ().absoluteFilePath ("wsjtx_status.txt")}; if(f.open(QFile::WriteOnly | QIODevice::Text)) { @@ -1865,6 +1873,7 @@ void MainWindow::guiUpdate() m_transmitting = true; transmitDisplay (true); + m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, QString::number (ui->rptSpinBox->value ()), m_modeTx, m_transmitting); } if(!m_btxok && btxok0 && g_iptt==1) stopTx(); @@ -1996,6 +2005,7 @@ void MainWindow::stopTx() tx_status_label->setText(""); ptt0Timer->start(200); //Sequencer delay monitor (true); + m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, QString::number (ui->rptSpinBox->value ()), m_modeTx, m_transmitting); } void MainWindow::stopTx2() diff --git a/message_aggregator.qrc.in b/message_aggregator.qrc.in new file mode 100644 index 000000000..792aa4464 --- /dev/null +++ b/message_aggregator.qrc.in @@ -0,0 +1,5 @@ + + + @message_aggregator_RESOURCES@ + + diff --git a/qss/default.qss b/qss/default.qss new file mode 100644 index 000000000..f5571d5c4 --- /dev/null +++ b/qss/default.qss @@ -0,0 +1,5 @@ +/* default stylesheet for the message aggregator application */ + +[transmitting="true"] { + background-color: yellow +} diff --git a/qt_helpers.cpp b/qt_helpers.cpp index 2d530534b..62def121b 100644 --- a/qt_helpers.cpp +++ b/qt_helpers.cpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include QString font_as_stylesheet (QFont const& font) { @@ -24,3 +27,11 @@ QString font_as_stylesheet (QFont const& font) .arg (font.styleName ()) .arg (font_weight); } + +void update_dynamic_property (QWidget * widget, char const * property, QVariant const& value) +{ + widget->setProperty (property, value); + widget->style ()->unpolish (widget); + widget->style ()->polish (widget); + widget->update (); +} diff --git a/qt_helpers.hpp b/qt_helpers.hpp index c4ac6944e..ec417dc3c 100644 --- a/qt_helpers.hpp +++ b/qt_helpers.hpp @@ -9,10 +9,10 @@ #include #include #include -#include #include #include -#include + +class QVariant; #define ENUM_QDATASTREAM_OPS_DECL(CLASS, ENUM) \ QDataStream& operator << (QDataStream&, CLASS::ENUM); \ @@ -72,6 +72,10 @@ void throw_qstring (QString const& qs) QString font_as_stylesheet (QFont const&); +// do what is necessary to change a dynamic property and trigger any +// conditional style sheet updates +void update_dynamic_property (QWidget *, char const * property, QVariant const& value); + // Register some useful Qt types with QMetaType Q_DECLARE_METATYPE (QHostAddress);