Removed legacy UDPExamples
This commit is contained in:
		
							parent
							
								
									573fa4f8f9
								
							
						
					
					
						commit
						f6138f4c4b
					
				| @ -1,135 +0,0 @@ | ||||
| #include "BeaconsModel.hpp" | ||||
| 
 | ||||
| #include <QStandardItem> | ||||
| #include <QFont> | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
|   char const * const headings[] = { | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "Client"), | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "Time"), | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "Snr"), | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "DT"), | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "Frequency"), | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "Drift"), | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "Grid"), | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "Power"), | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "Live"), | ||||
|     QT_TRANSLATE_NOOP ("BeaconsModel", "Callsign"), | ||||
|   }; | ||||
| 
 | ||||
|   QString live_string (bool off_air) | ||||
|   { | ||||
|     return off_air ? QT_TRANSLATE_NOOP ("BeaconsModel", "no") : QT_TRANSLATE_NOOP ("BeaconsModel", "yes"); | ||||
|   } | ||||
| 
 | ||||
|   QFont text_font {"Courier", 10}; | ||||
| 
 | ||||
|   QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time | ||||
|                                    , Frequency frequency, qint32 drift, QString const& callsign | ||||
|                                    , QString const& grid, qint32 power, bool off_air) | ||||
|   { | ||||
|     auto time_item = new QStandardItem {time.toString ("hh:mm")}; | ||||
|     time_item->setData (time); | ||||
|     time_item->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto snr_item = new QStandardItem {QString::number (snr)}; | ||||
|     snr_item->setData (snr); | ||||
|     snr_item->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto dt = new QStandardItem {QString::number (delta_time)}; | ||||
|     dt->setData (delta_time); | ||||
|     dt->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto freq = new QStandardItem {Radio::pretty_frequency_MHz_string (frequency)}; | ||||
|     freq->setData (frequency); | ||||
|     freq->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto dri = new QStandardItem {QString::number (drift)}; | ||||
|     dri->setData (drift); | ||||
|     dri->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto gd = new QStandardItem {grid}; | ||||
|     gd->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto pwr = new QStandardItem {QString::number (power)}; | ||||
|     pwr->setData (power); | ||||
|     pwr->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto live = new QStandardItem {live_string (off_air)}; | ||||
|     live->setTextAlignment (Qt::AlignHCenter); | ||||
| 
 | ||||
|     QList<QStandardItem *> row { | ||||
|       new QStandardItem {client_id}, time_item, snr_item, dt, freq, dri, gd, pwr, live, new QStandardItem {callsign}}; | ||||
|     Q_FOREACH (auto& item, row) | ||||
|       { | ||||
|         item->setEditable (false); | ||||
|         item->setFont (text_font); | ||||
|         item->setTextAlignment (item->textAlignment () | Qt::AlignVCenter); | ||||
|       } | ||||
|     return row; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| BeaconsModel::BeaconsModel (QObject * parent) | ||||
|   : QStandardItemModel {0, sizeof (headings) / sizeof (headings[0]), parent} | ||||
| { | ||||
|   int column {0}; | ||||
|   for (auto const& heading : headings) | ||||
|     { | ||||
|       setHeaderData (column++, Qt::Horizontal, tr (heading)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time | ||||
|                                     , Frequency frequency, qint32 drift, QString const& callsign | ||||
|                                     , QString const& grid, qint32 power, bool off_air) | ||||
| { | ||||
|   if (!is_new) | ||||
|     { | ||||
|       int target_row {-1}; | ||||
|       for (auto row = 0; row < rowCount (); ++row) | ||||
|         { | ||||
|           if (data (index (row, 0)).toString () == client_id) | ||||
|             { | ||||
|               auto row_time = item (row, 1)->data ().toTime (); | ||||
|               if (row_time == time | ||||
|                   && item (row, 2)->data ().toInt () == snr | ||||
|                   && item (row, 3)->data ().toFloat () == delta_time | ||||
|                   && item (row, 4)->data ().value<Frequency> () == frequency | ||||
|                   && data (index (row, 5)).toInt () == drift | ||||
|                   && data (index (row, 7)).toString () == grid | ||||
|                   && data (index (row, 8)).toInt () == power | ||||
|                   && data (index (row, 6)).toString () == live_string (off_air) | ||||
|                   && data (index (row, 9)).toString () == callsign) | ||||
|                 { | ||||
|                   return; | ||||
|                 } | ||||
|               if (time <= row_time) | ||||
|                 { | ||||
|                   target_row = row; // last row with same time
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|       if (target_row >= 0) | ||||
|         { | ||||
|           insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air)); | ||||
|           return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|   appendRow (make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air)); | ||||
| } | ||||
| 
 | ||||
| void BeaconsModel::clear_decodes (QString const& client_id) | ||||
| { | ||||
|   for (auto row = rowCount () - 1; row >= 0; --row) | ||||
|     { | ||||
|       if (data (index (row, 0)).toString () == client_id) | ||||
|         { | ||||
|           removeRow (row); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #include "moc_BeaconsModel.cpp" | ||||
| @ -1,38 +0,0 @@ | ||||
| #ifndef WSJTX_UDP_BEACONS_MODEL_HPP__ | ||||
| #define WSJTX_UDP_BEACONS_MODEL_HPP__ | ||||
| 
 | ||||
| #include <QStandardItemModel> | ||||
| 
 | ||||
| #include "MessageServer.hpp" | ||||
| 
 | ||||
| using Frequency = MessageServer::Frequency; | ||||
| 
 | ||||
| class QString; | ||||
| class QTime; | ||||
| 
 | ||||
| //
 | ||||
| // Beacons Model - simple data model for all beacon spots
 | ||||
| //
 | ||||
| // The model is a basic table with uniform row format. Rows consist of
 | ||||
| // QStandardItem instances containing the string representation of the
 | ||||
| // column data  and if the underlying  field is not a  string then the
 | ||||
| // UserRole+1 role contains the underlying data item.
 | ||||
| //
 | ||||
| // Two slots are provided to add a new decode and remove all spots for
 | ||||
| // a client.
 | ||||
| //
 | ||||
| class BeaconsModel | ||||
|   : public QStandardItemModel | ||||
| { | ||||
|   Q_OBJECT; | ||||
| 
 | ||||
| public: | ||||
|   explicit BeaconsModel (QObject * parent = nullptr); | ||||
| 
 | ||||
|   Q_SLOT void add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time | ||||
|                                , Frequency frequency, qint32 drift, QString const& callsign, QString const& grid | ||||
|                                , qint32 power, bool off_air); | ||||
|   Q_SLOT void clear_decodes (QString const& client_id); | ||||
| }; | ||||
| 
 | ||||
| #endif | ||||
| @ -1,310 +0,0 @@ | ||||
| #include "ClientWidget.hpp" | ||||
| 
 | ||||
| #include <QRegExp> | ||||
| #include <QColor> | ||||
| 
 | ||||
| #include "MaidenheadLocatorValidator.hpp" | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
|   //QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"};
 | ||||
|   QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"}; | ||||
|   QRegularExpression cq_re {"(CQ|CQDX|QRZ)[^A-Z0-9/]+"}; | ||||
| 
 | ||||
|   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 (); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id) | ||||
|   : client_id_ {client_id} | ||||
|   , rx_df_ (-1) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| QVariant ClientWidget::IdFilterModel::data (QModelIndex const& proxy_index, int role) const | ||||
| { | ||||
|   if (role == Qt::BackgroundRole) | ||||
|     { | ||||
|       switch (proxy_index.column ()) | ||||
|         { | ||||
|         case 8:                 // message
 | ||||
|           { | ||||
|             auto message = QSortFilterProxyModel::data (proxy_index).toString (); | ||||
|             if (base_call_re_.pattern ().size () | ||||
|                 && message.contains (base_call_re_)) | ||||
|               { | ||||
|                 return QColor {255,200,200}; | ||||
|               } | ||||
|             if (message.contains (cq_re)) | ||||
|               { | ||||
|                 return QColor {200, 255, 200}; | ||||
|               } | ||||
|           } | ||||
|           break; | ||||
| 
 | ||||
|         case 4:                 // DF
 | ||||
|           if (qAbs (QSortFilterProxyModel::data (proxy_index).toInt () - rx_df_) <= 10) | ||||
|             { | ||||
|               return QColor {255, 200, 200}; | ||||
|             } | ||||
|           break; | ||||
| 
 | ||||
|         default: | ||||
|           break; | ||||
|         } | ||||
|     } | ||||
|   return QSortFilterProxyModel::data (proxy_index, role); | ||||
| } | ||||
| 
 | ||||
| bool ClientWidget::IdFilterModel::filterAcceptsRow (int source_row | ||||
|                                                     , QModelIndex const& source_parent) const | ||||
| { | ||||
|   auto source_index_col0 = sourceModel ()->index (source_row, 0, source_parent); | ||||
|   return sourceModel ()->data (source_index_col0).toString () == client_id_; | ||||
| } | ||||
| 
 | ||||
| void ClientWidget::IdFilterModel::de_call (QString const& call) | ||||
| { | ||||
|   if (call != call_) | ||||
|     { | ||||
|       beginResetModel (); | ||||
|       if (call.size ()) | ||||
|         { | ||||
|           base_call_re_.setPattern ("[^A-Z0-9]*" + Radio::base_callsign (call) + "[^A-Z0-9]*"); | ||||
|         } | ||||
|       else | ||||
|         { | ||||
|           base_call_re_.setPattern (QString {}); | ||||
|         } | ||||
|       call_ = call; | ||||
|       endResetModel (); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ClientWidget::IdFilterModel::rx_df (int df) | ||||
| { | ||||
|   if (df != rx_df_) | ||||
|     { | ||||
|       beginResetModel (); | ||||
|       rx_df_ = df; | ||||
|       endResetModel (); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
|   QString make_title (QString const& id, QString const& version, QString const& revision) | ||||
|   { | ||||
|     QString title {id}; | ||||
|     if (version.size ()) | ||||
|       { | ||||
|         title += QString {" v%1"}.arg (version); | ||||
|       } | ||||
|     if (revision.size ()) | ||||
|       { | ||||
|         title += QString {" (%1)"}.arg (revision); | ||||
|       } | ||||
|     return title; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model | ||||
|                             , QString const& id, QString const& version, QString const& revision | ||||
|                             , QListWidget const * calls_of_interest, QWidget * parent) | ||||
|   : QDockWidget {make_title (id, version, revision), parent} | ||||
|   , id_ {id} | ||||
|   , calls_of_interest_ {calls_of_interest} | ||||
|   , decodes_proxy_model_ {id_} | ||||
|   , decodes_table_view_ {new QTableView} | ||||
|   , beacons_table_view_ {new QTableView} | ||||
|   , message_line_edit_ {new QLineEdit} | ||||
|   , grid_line_edit_ {new QLineEdit} | ||||
|   , 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} | ||||
|   , columns_resized_ {false} | ||||
| { | ||||
|   // set up widgets
 | ||||
|   decodes_proxy_model_.setSourceModel (decodes_model); | ||||
|   decodes_table_view_->setModel (&decodes_proxy_model_); | ||||
|   decodes_table_view_->verticalHeader ()->hide (); | ||||
|   decodes_table_view_->hideColumn (0); | ||||
|   decodes_table_view_->horizontalHeader ()->setStretchLastSection (true); | ||||
| 
 | ||||
|   auto form_layout = new QFormLayout; | ||||
|   form_layout->addRow (tr ("Free text:"), message_line_edit_); | ||||
|   form_layout->addRow (tr ("Temporary grid:"), grid_line_edit_); | ||||
|   message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this}); | ||||
|   grid_line_edit_->setValidator (new MaidenheadLocatorValidator {this}); | ||||
|   connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) { | ||||
|       Q_EMIT do_free_text (id_, text, false); | ||||
|     }); | ||||
|   connect (message_line_edit_, &QLineEdit::editingFinished, [this] () { | ||||
|       Q_EMIT do_free_text (id_, message_line_edit_->text (), true); | ||||
|     }); | ||||
|   connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () { | ||||
|       Q_EMIT location (id_, grid_line_edit_->text ()); | ||||
|   }); | ||||
| 
 | ||||
|   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); | ||||
| 
 | ||||
|   auto beacons_proxy_model = new IdFilterModel {id_}; | ||||
|   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); | ||||
| 
 | ||||
|   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_); | ||||
| 
 | ||||
|   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_); | ||||
| 
 | ||||
|   // 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); | ||||
|   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); | ||||
| 
 | ||||
|   // 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); | ||||
| 
 | ||||
|   // set up central widget
 | ||||
|   auto content_widget = new QFrame; | ||||
|   content_widget->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken); | ||||
|   content_widget->setLayout (content_layout); | ||||
|   setWidget (content_widget); | ||||
|   // setMinimumSize (QSize {550, 0});
 | ||||
|   setFeatures (DockWidgetMovable | DockWidgetFloatable); | ||||
|   setAllowedAreas (Qt::BottomDockWidgetArea); | ||||
|   setFloating (true); | ||||
| 
 | ||||
|   // connect up table view signals
 | ||||
|   connect (decodes_table_view_, &QTableView::doubleClicked, this, [this] (QModelIndex const& index) { | ||||
|       Q_EMIT do_reply (decodes_proxy_model_.mapToSource (index), QApplication::keyboardModifiers () >> 24); | ||||
|     }); | ||||
| 
 | ||||
|   // tell new client about calls of interest
 | ||||
|   for (int row = 0; row < calls_of_interest_->count (); ++row) | ||||
|     { | ||||
|       Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text (), QColor {Qt::blue}, QColor {Qt::yellow}); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ClientWidget::~ClientWidget () | ||||
| { | ||||
|   for (int row = 0; row < calls_of_interest_->count (); ++row) | ||||
|     { | ||||
|       // tell client to forget calls of interest
 | ||||
|       Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text ()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
|                                   , QString const& de_call, QString const& de_grid, QString const& dx_grid | ||||
|                                   , bool watchdog_timeout, QString const& sub_mode, bool fast_mode) | ||||
| { | ||||
|   if (id == id_) | ||||
|     { | ||||
|       fast_mode_ = fast_mode; | ||||
|       decodes_proxy_model_.de_call (de_call); | ||||
|       decodes_proxy_model_.rx_df (rx_df); | ||||
|       de_label_->setText (de_call.size () >= 0 ? QString {"DE: %1%2"}.arg (de_call) | ||||
|                           .arg (de_grid.size () ? '(' + de_grid + ')' : QString {}) : 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" : "") | ||||
|            .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) : ""); | ||||
|       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_dynamic_property (tx_df_label_, "watchdog_timeout", watchdog_timeout); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/ | ||||
|                                  , float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/ | ||||
|                                  , QString const& /*message*/, bool /*low_confidence*/, bool /*off_air*/) | ||||
| { | ||||
|   if (client_id == id_ && !columns_resized_) | ||||
|     { | ||||
|       decodes_stack_->setCurrentIndex (0); | ||||
|       decodes_table_view_->resizeColumnsToContents (); | ||||
|       columns_resized_ = true; | ||||
|     } | ||||
|   decodes_table_view_->scrollToBottom (); | ||||
| } | ||||
| 
 | ||||
| void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/ | ||||
|                                       , float /*delta_time*/, Frequency /*delta_frequency*/, qint32 /*drift*/ | ||||
|                                       , QString const& /*callsign*/, QString const& /*grid*/, qint32 /*power*/ | ||||
|                                       , bool /*off_air*/) | ||||
| { | ||||
|   if (client_id == id_ && !columns_resized_) | ||||
|     { | ||||
|       decodes_stack_->setCurrentIndex (1); | ||||
|       beacons_table_view_->resizeColumnsToContents (); | ||||
|       columns_resized_ = true; | ||||
|     } | ||||
|   beacons_table_view_->scrollToBottom (); | ||||
| } | ||||
| 
 | ||||
| void ClientWidget::clear_decodes (QString const& client_id) | ||||
| { | ||||
|   if (client_id == id_) | ||||
|     { | ||||
|       columns_resized_ = false; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #include "moc_ClientWidget.cpp" | ||||
| @ -1,94 +0,0 @@ | ||||
| #ifndef WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__ | ||||
| #define WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__ | ||||
| 
 | ||||
| #include <QObject> | ||||
| #include <QSortFilterProxyModel> | ||||
| #include <QString> | ||||
| #include <QRegularExpression> | ||||
| #include <QtWidgets> | ||||
| 
 | ||||
| #include "MessageServer.hpp" | ||||
| 
 | ||||
| class QAbstractItemModel; | ||||
| class QModelIndex; | ||||
| class QColor; | ||||
| 
 | ||||
| using Frequency = MessageServer::Frequency; | ||||
| 
 | ||||
| class ClientWidget | ||||
|   : public QDockWidget | ||||
| { | ||||
|   Q_OBJECT; | ||||
| 
 | ||||
| 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); | ||||
|   ~ClientWidget (); | ||||
| 
 | ||||
|   bool fast_mode () const {return fast_mode_;} | ||||
| 
 | ||||
|   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 | ||||
|                              , QString const& de_call, QString const& de_grid, QString const& dx_grid | ||||
|                              , bool watchdog_timeout, QString const& sub_mode, bool fast_mode); | ||||
|   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); | ||||
|   Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime, qint32 snr | ||||
|                                  , float delta_time, Frequency delta_frequency, qint32 drift | ||||
|                                  , QString const& callsign, QString const& grid, qint32 power | ||||
|                                  , bool off_air); | ||||
|   Q_SLOT void clear_decodes (QString const& client_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); | ||||
|   Q_SIGNAL void location (QString const& id, QString const& text); | ||||
|   Q_SIGNAL void highlight_callsign (QString const& id, QString const& call | ||||
|                                     , QColor const& bg = QColor {}, QColor const& fg = QColor {} | ||||
|                                     , bool last_only = false); | ||||
| 
 | ||||
| private: | ||||
|   QString id_; | ||||
|   QListWidget const * calls_of_interest_; | ||||
|   class IdFilterModel final | ||||
|     : public QSortFilterProxyModel | ||||
|   { | ||||
|   public: | ||||
|     IdFilterModel (QString const& client_id); | ||||
| 
 | ||||
|     void de_call (QString const&); | ||||
|     void rx_df (int); | ||||
| 
 | ||||
|     QVariant data (QModelIndex const& proxy_index, int role = Qt::DisplayRole) const override; | ||||
| 
 | ||||
|   protected: | ||||
|     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_; | ||||
|   QTableView * decodes_table_view_; | ||||
|   QTableView * beacons_table_view_; | ||||
|   QLineEdit * message_line_edit_; | ||||
|   QLineEdit * grid_line_edit_; | ||||
|   QStackedLayout * decodes_stack_; | ||||
|   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_; | ||||
|   bool columns_resized_; | ||||
| }; | ||||
| 
 | ||||
| #endif | ||||
| @ -1,153 +0,0 @@ | ||||
| #include "DecodesModel.hpp" | ||||
| 
 | ||||
| #include <QStandardItem> | ||||
| #include <QModelIndex> | ||||
| #include <QTime> | ||||
| #include <QString> | ||||
| #include <QFont> | ||||
| #include <QList> | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
|   char const * const headings[] = { | ||||
|     QT_TRANSLATE_NOOP ("DecodesModel", "Client"), | ||||
|     QT_TRANSLATE_NOOP ("DecodesModel", "Time"), | ||||
|     QT_TRANSLATE_NOOP ("DecodesModel", "Snr"), | ||||
|     QT_TRANSLATE_NOOP ("DecodesModel", "DT"), | ||||
|     QT_TRANSLATE_NOOP ("DecodesModel", "DF"), | ||||
|     QT_TRANSLATE_NOOP ("DecodesModel", "Md"), | ||||
|     QT_TRANSLATE_NOOP ("DecodesModel", "Confidence"), | ||||
|     QT_TRANSLATE_NOOP ("DecodesModel", "Live"), | ||||
|     QT_TRANSLATE_NOOP ("DecodesModel", "Message"), | ||||
|   }; | ||||
| 
 | ||||
|   QString confidence_string (bool low_confidence) | ||||
|   { | ||||
|     return low_confidence ? QT_TRANSLATE_NOOP ("DecodesModel", "low") : QT_TRANSLATE_NOOP ("DecodesModel", "high"); | ||||
|   } | ||||
| 
 | ||||
|   QString live_string (bool off_air) | ||||
|   { | ||||
|     return off_air ? QT_TRANSLATE_NOOP ("DecodesModel", "no") : QT_TRANSLATE_NOOP ("DecodesModel", "yes"); | ||||
|   } | ||||
| 
 | ||||
|   QFont text_font {"Courier", 10}; | ||||
| 
 | ||||
|   QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time | ||||
|                                    , quint32 delta_frequency, QString const& mode, QString const& message | ||||
|                                    , bool low_confidence, bool off_air, bool is_fast) | ||||
|   { | ||||
|     auto time_item = new QStandardItem {time.toString (is_fast || "~" == mode ? "hh:mm:ss" : "hh:mm")}; | ||||
|     time_item->setData (time); | ||||
|     time_item->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto snr_item = new QStandardItem {QString::number (snr)}; | ||||
|     snr_item->setData (snr); | ||||
|     snr_item->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto dt = new QStandardItem {QString::number (delta_time)}; | ||||
|     dt->setData (delta_time); | ||||
|     dt->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto df = new QStandardItem {QString::number (delta_frequency)}; | ||||
|     df->setData (delta_frequency); | ||||
|     df->setTextAlignment (Qt::AlignRight); | ||||
| 
 | ||||
|     auto md = new QStandardItem {mode}; | ||||
|     md->setTextAlignment (Qt::AlignHCenter); | ||||
| 
 | ||||
|     auto confidence = new QStandardItem {confidence_string (low_confidence)}; | ||||
|     confidence->setTextAlignment (Qt::AlignHCenter); | ||||
| 
 | ||||
|     auto live = new QStandardItem {live_string (off_air)}; | ||||
|     live->setTextAlignment (Qt::AlignHCenter); | ||||
| 
 | ||||
|     QList<QStandardItem *> row { | ||||
|       new QStandardItem {client_id}, time_item, snr_item, dt, df, md, confidence, live, new QStandardItem {message}}; | ||||
|     Q_FOREACH (auto& item, row) | ||||
|       { | ||||
|         item->setEditable (false); | ||||
|         item->setFont (text_font); | ||||
|         item->setTextAlignment (item->textAlignment () | Qt::AlignVCenter); | ||||
|       } | ||||
|     return row; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| DecodesModel::DecodesModel (QObject * parent) | ||||
|   : QStandardItemModel {0, sizeof (headings) / sizeof (headings[0]), parent} | ||||
| { | ||||
|   int column {0}; | ||||
|   for (auto const& heading : headings) | ||||
|     { | ||||
|       setHeaderData (column++, Qt::Horizontal, tr (heading)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time | ||||
|                                , quint32 delta_frequency, QString const& mode, QString const& message | ||||
|                                , bool low_confidence, bool off_air, bool is_fast) | ||||
| { | ||||
|   if (!is_new) | ||||
|     { | ||||
|       int target_row {-1}; | ||||
|       for (auto row = 0; row < rowCount (); ++row) | ||||
|         { | ||||
|           if (data (index (row, 0)).toString () == client_id) | ||||
|             { | ||||
|               auto row_time = item (row, 1)->data ().toTime (); | ||||
|               if (row_time == time | ||||
|                   && item (row, 2)->data ().toInt () == snr | ||||
|                   && item (row, 3)->data ().toFloat () == delta_time | ||||
|                   && item (row, 4)->data ().toUInt () == delta_frequency | ||||
|                   && data (index (row, 5)).toString () == mode | ||||
|                   && data (index (row, 7)).toString () == confidence_string (low_confidence) | ||||
|                   && data (index (row, 6)).toString () == live_string (off_air) | ||||
|                   && data (index (row, 8)).toString () == message) | ||||
|                 { | ||||
|                   return; | ||||
|                 } | ||||
|               if (time <= row_time) | ||||
|                 { | ||||
|                   target_row = row; // last row with same time
 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|       if (target_row >= 0) | ||||
|         { | ||||
|           insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, delta_frequency, mode | ||||
|                                                , message, low_confidence, off_air, is_fast)); | ||||
|           return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|   appendRow (make_row (client_id, time, snr, delta_time, delta_frequency, mode, message, low_confidence | ||||
|                        , off_air, is_fast)); | ||||
| } | ||||
| 
 | ||||
| void DecodesModel::clear_decodes (QString const& client_id) | ||||
| { | ||||
|   for (auto row = rowCount () - 1; row >= 0; --row) | ||||
|     { | ||||
|       if (data (index (row, 0)).toString () == client_id) | ||||
|         { | ||||
|           removeRow (row); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void DecodesModel::do_reply (QModelIndex const& source, quint8 modifiers) | ||||
| { | ||||
|   auto row = source.row (); | ||||
|   Q_EMIT reply (data (index (row, 0)).toString () | ||||
|                 , item (row, 1)->data ().toTime () | ||||
|                 , item (row, 2)->data ().toInt () | ||||
|                 , item (row, 3)->data ().toFloat () | ||||
|                 , item (row, 4)->data ().toInt () | ||||
|                 , data (index (row, 5)).toString () | ||||
|                 , data (index (row, 8)).toString () | ||||
|                 , confidence_string (true) == data (index (row, 7)).toString () | ||||
|                 , modifiers); | ||||
| } | ||||
| 
 | ||||
| #include "moc_DecodesModel.cpp" | ||||
| @ -1,44 +0,0 @@ | ||||
| #ifndef WSJTX_UDP_DECODES_MODEL_HPP__ | ||||
| #define WSJTX_UDP_DECODES_MODEL_HPP__ | ||||
| 
 | ||||
| #include <QStandardItemModel> | ||||
| 
 | ||||
| #include "MessageServer.hpp" | ||||
| 
 | ||||
| using Frequency = MessageServer::Frequency; | ||||
| 
 | ||||
| class QTime; | ||||
| class QString; | ||||
| class QModelIndex; | ||||
| 
 | ||||
| //
 | ||||
| // Decodes Model - simple data model for all decodes
 | ||||
| //
 | ||||
| // The model is a basic table with uniform row format. Rows consist of
 | ||||
| // QStandardItem instances containing the string representation of the
 | ||||
| // column data  and if the underlying  field is not a  string then the
 | ||||
| // UserRole+1 role contains the underlying data item.
 | ||||
| //
 | ||||
| // Three slots  are provided to add  a new decode, remove  all decodes
 | ||||
| // for a client  and, to build a  reply to CQ message for  a given row
 | ||||
| // which is emitted as a signal respectively.
 | ||||
| //
 | ||||
| class DecodesModel | ||||
|   : public QStandardItemModel | ||||
| { | ||||
|   Q_OBJECT; | ||||
| 
 | ||||
| public: | ||||
|   explicit DecodesModel (QObject * parent = nullptr); | ||||
| 
 | ||||
|   Q_SLOT void add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time | ||||
|                           , quint32 delta_frequency, QString const& mode, QString const& message | ||||
|                           , bool low_confidence, bool off_air, bool is_fast); | ||||
|   Q_SLOT void clear_decodes (QString const& client_id); | ||||
|   Q_SLOT void do_reply (QModelIndex const& source, quint8 modifiers); | ||||
| 
 | ||||
|   Q_SIGNAL 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); | ||||
| }; | ||||
| 
 | ||||
| #endif | ||||
| @ -1,90 +0,0 @@ | ||||
| //
 | ||||
| // MessageAggregator - an example application that utilizes the WSJT-X
 | ||||
| //                     messaging facility
 | ||||
| //
 | ||||
| // This  application is  only  provided as  a  simple GUI  application
 | ||||
| // example to demonstrate the WSJT-X messaging facility. It allows the
 | ||||
| // user to set  the server details either as a  unicast UDP server or,
 | ||||
| // if a  multicast group address  is provided, as a  multicast server.
 | ||||
| // The benefit of the multicast server is that multiple servers can be
 | ||||
| // active at  once each  receiving all  WSJT-X broadcast  messages and
 | ||||
| // each able to  respond to individual WSJT_X clients.  To utilize the
 | ||||
| // multicast  group features  each  WSJT-X client  must  set the  same
 | ||||
| // multicast  group address  as  the UDP  server  address for  example
 | ||||
| // 239.255.0.0 for a site local multicast group.
 | ||||
| //
 | ||||
| // The  UI is  a small  panel  to input  the service  port number  and
 | ||||
| // optionally  the  multicast  group  address.   Below  that  a  table
 | ||||
| // representing  the  log  entries   where  any  QSO  logged  messages
 | ||||
| // broadcast  from WSJT-X  clients are  displayed. The  bottom of  the
 | ||||
| // application main  window is a  dock area  where a dock  window will
 | ||||
| // appear for each WSJT-X client, this  window contains a table of the
 | ||||
| // current decode  messages broadcast  from that  WSJT-X client  and a
 | ||||
| // status line showing  the status update messages  broadcast from the
 | ||||
| // WSJT-X client. The dock windows may  be arranged in a tab bar, side
 | ||||
| // by side,  below each  other or, completely  detached from  the dock
 | ||||
| // area as floating windows. Double clicking the dock window title bar
 | ||||
| // or  dragging and  dropping with  the mouse  allows these  different
 | ||||
| // arrangements.
 | ||||
| //
 | ||||
| // The application  also provides a  simple menu bar including  a view
 | ||||
| // menu that allows each dock window to be hidden or revealed.
 | ||||
| //
 | ||||
| 
 | ||||
| #include <clocale> | ||||
| #include <iostream> | ||||
| #include <exception> | ||||
| 
 | ||||
| #include <QFile> | ||||
| #include <QApplication> | ||||
| #include <QMessageBox> | ||||
| #include <QObject> | ||||
| 
 | ||||
| #include "MessageAggregatorMainWindow.hpp" | ||||
| 
 | ||||
| // deduce the size of an array
 | ||||
| template<class T, size_t N> | ||||
| inline | ||||
| size_t size (T (&)[N]) {return N;} | ||||
| 
 | ||||
| int main (int argc, char * argv[]) | ||||
| { | ||||
|   QApplication app {argc, argv}; | ||||
|   try | ||||
|     { | ||||
|       setlocale (LC_NUMERIC, "C"); // ensure number forms are in
 | ||||
|                                    // consistent format, do this after
 | ||||
|                                    // instantiating QApplication so
 | ||||
|                                    // that GUI has correct l18n
 | ||||
| 
 | ||||
|       app.setApplicationName ("WSJT-X Reference UDP Message Aggregator Server"); | ||||
|       app.setApplicationVersion ("1.0"); | ||||
| 
 | ||||
|       QObject::connect (&app, SIGNAL (lastWindowClosed ()), &app, SLOT (quit ())); | ||||
| 
 | ||||
|       { | ||||
|         QFile file {":/qss/default.qss"}; | ||||
|         if (!file.open (QFile::ReadOnly)) | ||||
|           { | ||||
|             throw std::runtime_error { | ||||
|               QString {"failed to open \"" + file.fileName () + "\": " + file.errorString ()} | ||||
|               .toLocal8Bit ().constData ()}; | ||||
|           } | ||||
|         app.setStyleSheet (file.readAll()); | ||||
|       } | ||||
| 
 | ||||
|       MessageAggregatorMainWindow window; | ||||
|       return app.exec (); | ||||
|     } | ||||
|   catch (std::exception const & e) | ||||
|     { | ||||
|       QMessageBox::critical (nullptr, app.applicationName (), e.what ()); | ||||
|       std::cerr << "Error: " << e.what () << '\n'; | ||||
|     } | ||||
|   catch (...) | ||||
|     { | ||||
|       QMessageBox::critical (nullptr, app.applicationName (), QObject::tr ("Unexpected error")); | ||||
|       std::cerr << "Unexpected error\n"; | ||||
|     } | ||||
|   return -1; | ||||
| } | ||||
| @ -1,284 +0,0 @@ | ||||
| #include "MessageAggregatorMainWindow.hpp" | ||||
| 
 | ||||
| #include <QtWidgets> | ||||
| #include <QDateTime> | ||||
| 
 | ||||
| #include "DecodesModel.hpp" | ||||
| #include "BeaconsModel.hpp" | ||||
| #include "ClientWidget.hpp" | ||||
| 
 | ||||
| using port_type = MessageServer::port_type; | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
|   char const * const headings[] = { | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Time On"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Time Off"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Callsign"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Grid"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Name"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Frequency"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Mode"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Sent"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Rec'd"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Power"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Operator"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "My Call"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "My Grid"), | ||||
|     QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Comments"), | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| MessageAggregatorMainWindow::MessageAggregatorMainWindow () | ||||
|   : log_ {new QStandardItemModel {0, 14, this}} | ||||
|   , decodes_model_ {new DecodesModel {this}} | ||||
|   , beacons_model_ {new BeaconsModel {this}} | ||||
|   , server_ {new MessageServer {this}} | ||||
|   , multicast_group_line_edit_ {new QLineEdit} | ||||
|   , log_table_view_ {new QTableView} | ||||
|   , add_call_of_interest_action_ {new QAction {tr ("&Add callsign"), this}} | ||||
|   , delete_call_of_interest_action_ {new QAction {tr ("&Delete callsign"), this}} | ||||
|   , last_call_of_interest_action_ {new QAction {tr ("&Highlight last only"), this}} | ||||
|   , call_of_interest_bg_colour_action_ {new QAction {tr ("&Background colour"), this}} | ||||
|   , call_of_interest_fg_colour_action_ {new QAction {tr ("&Foreground colour"), this}} | ||||
| { | ||||
|   // logbook
 | ||||
|   int column {0}; | ||||
|   for (auto const& heading : headings) | ||||
|     { | ||||
|       log_->setHeaderData (column++, Qt::Horizontal, tr (heading)); | ||||
|     } | ||||
|   connect (server_, &MessageServer::qso_logged, this, &MessageAggregatorMainWindow::log_qso); | ||||
| 
 | ||||
|   // menu bar
 | ||||
|   auto file_menu = menuBar ()->addMenu (tr ("&File")); | ||||
| 
 | ||||
|   auto exit_action = new QAction {tr ("E&xit"), this}; | ||||
|   exit_action->setShortcuts (QKeySequence::Quit); | ||||
|   exit_action->setToolTip (tr ("Exit the application")); | ||||
|   file_menu->addAction (exit_action); | ||||
|   connect (exit_action, &QAction::triggered, this, &MessageAggregatorMainWindow::close); | ||||
| 
 | ||||
|   view_menu_ = menuBar ()->addMenu (tr ("&View")); | ||||
| 
 | ||||
|   // central layout
 | ||||
|   auto central_layout = new QVBoxLayout; | ||||
| 
 | ||||
|   // server details
 | ||||
|   auto port_spin_box = new QSpinBox; | ||||
|   port_spin_box->setMinimum (1); | ||||
|   port_spin_box->setMaximum (std::numeric_limits<port_type>::max ()); | ||||
|   auto group_box_layout = new QFormLayout; | ||||
|   group_box_layout->addRow (tr ("Port number:"), port_spin_box); | ||||
|   group_box_layout->addRow (tr ("Multicast Group (blank for unicast server):"), multicast_group_line_edit_); | ||||
|   auto group_box = new QGroupBox {tr ("Server Details")}; | ||||
|   group_box->setLayout (group_box_layout); | ||||
|   central_layout->addWidget (group_box); | ||||
| 
 | ||||
|   log_table_view_->setModel (log_); | ||||
|   log_table_view_->verticalHeader ()->hide (); | ||||
|   central_layout->addWidget (log_table_view_); | ||||
| 
 | ||||
|   // central widget
 | ||||
|   auto central_widget = new QWidget; | ||||
|   central_widget->setLayout (central_layout); | ||||
| 
 | ||||
|   // main window setup
 | ||||
|   setCentralWidget (central_widget); | ||||
|   setDockOptions (AnimatedDocks | AllowNestedDocks | AllowTabbedDocks); | ||||
|   setTabPosition (Qt::BottomDockWidgetArea, QTabWidget::North); | ||||
| 
 | ||||
|   QDockWidget * calls_dock {new QDockWidget {tr ("Calls of Interest"), this}}; | ||||
|   calls_dock->setAllowedAreas (Qt::RightDockWidgetArea); | ||||
|   calls_of_interest_ = new QListWidget {calls_dock}; | ||||
|   calls_of_interest_->setContextMenuPolicy (Qt::ActionsContextMenu); | ||||
|   calls_of_interest_->insertAction (nullptr, add_call_of_interest_action_); | ||||
|   connect (add_call_of_interest_action_, &QAction::triggered, [this] () { | ||||
|       auto item = new QListWidgetItem {}; | ||||
|       item->setFlags (item->flags () | Qt::ItemIsEditable); | ||||
|       item->setData (Qt::UserRole, QString {}); | ||||
|       calls_of_interest_->addItem (item); | ||||
|       calls_of_interest_->editItem (item); | ||||
|     }); | ||||
|   calls_of_interest_->insertAction (nullptr, delete_call_of_interest_action_); | ||||
|   connect (delete_call_of_interest_action_, &QAction::triggered, [this] () { | ||||
|       for (auto item : calls_of_interest_->selectedItems ()) | ||||
|         { | ||||
|           auto old_call = item->data (Qt::UserRole); | ||||
|           if (old_call.isValid ()) change_highlighting (old_call.toString ()); | ||||
|           delete item; | ||||
|         } | ||||
|     }); | ||||
|   calls_of_interest_->insertAction (nullptr, last_call_of_interest_action_); | ||||
|   connect (last_call_of_interest_action_, &QAction::triggered, [this] () { | ||||
|       for (auto item : calls_of_interest_->selectedItems ()) | ||||
|         { | ||||
|           auto old_call = item->data (Qt::UserRole); | ||||
|           change_highlighting (old_call.toString ()); | ||||
|           change_highlighting (old_call.toString () | ||||
|                                , item->background ().color (), item->foreground ().color (), true); | ||||
|           delete item; | ||||
|         } | ||||
|     }); | ||||
|   calls_of_interest_->insertAction (nullptr, call_of_interest_bg_colour_action_); | ||||
|   connect (call_of_interest_bg_colour_action_, &QAction::triggered, [this] () { | ||||
|       for (auto item : calls_of_interest_->selectedItems ()) | ||||
|         { | ||||
|           auto old_call = item->data (Qt::UserRole); | ||||
|           auto new_colour = QColorDialog::getColor (item->background ().color () | ||||
|                                                     , this, tr ("Select background color")); | ||||
|           if (new_colour.isValid ()) | ||||
|             { | ||||
|               change_highlighting (old_call.toString (), new_colour, item->foreground ().color ()); | ||||
|               item->setBackground (new_colour); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|   calls_of_interest_->insertAction (nullptr, call_of_interest_fg_colour_action_); | ||||
|   connect (call_of_interest_fg_colour_action_, &QAction::triggered, [this] () { | ||||
|       for (auto item : calls_of_interest_->selectedItems ()) | ||||
|         { | ||||
|           auto old_call = item->data (Qt::UserRole); | ||||
|           auto new_colour = QColorDialog::getColor (item->foreground ().color () | ||||
|                                                     , this, tr ("Select foreground color")); | ||||
|           if (new_colour.isValid ()) | ||||
|             { | ||||
|               change_highlighting (old_call.toString (), item->background ().color (), new_colour); | ||||
|               item->setForeground (new_colour); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|   connect (calls_of_interest_, &QListWidget::itemChanged, [this] (QListWidgetItem * item) { | ||||
|       auto old_call = item->data (Qt::UserRole); | ||||
|       auto new_call = item->text ().toUpper (); | ||||
|       if (new_call != old_call) | ||||
|         { | ||||
|           // tell all clients
 | ||||
|           if (old_call.isValid ()) | ||||
|             { | ||||
|               change_highlighting (old_call.toString ()); | ||||
|             } | ||||
|           item->setData (Qt::UserRole, new_call); | ||||
|           item->setText (new_call); | ||||
|           auto bg = item->listWidget ()->palette ().text ().color (); | ||||
|           auto fg = item->listWidget ()->palette ().base ().color (); | ||||
|           item->setBackground (bg); | ||||
|           item->setForeground (fg); | ||||
|           change_highlighting (new_call, bg, fg); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|   calls_dock->setWidget (calls_of_interest_); | ||||
|   addDockWidget (Qt::RightDockWidgetArea, calls_dock); | ||||
|   view_menu_->addAction (calls_dock->toggleViewAction ()); | ||||
|   view_menu_->addSeparator (); | ||||
| 
 | ||||
|   // connect up server
 | ||||
|   connect (server_, &MessageServer::error, [this] (QString const& message) { | ||||
|       QMessageBox::warning (this, QApplication::applicationName (), tr ("Network Error"), message); | ||||
|     }); | ||||
|   connect (server_, &MessageServer::client_opened, this, &MessageAggregatorMainWindow::add_client); | ||||
|   connect (server_, &MessageServer::client_closed, this, &MessageAggregatorMainWindow::remove_client); | ||||
|   connect (server_, &MessageServer::client_closed, decodes_model_, &DecodesModel::clear_decodes); | ||||
|   connect (server_, &MessageServer::client_closed, beacons_model_, &BeaconsModel::clear_decodes); | ||||
|   connect (server_, &MessageServer::decode, [this] (bool is_new, QString const& id, QTime time | ||||
|                                                     , qint32 snr, float delta_time | ||||
|                                                     , quint32 delta_frequency, QString const& mode | ||||
|                                                     , QString const& message, bool low_confidence | ||||
|                                                     , bool off_air) { | ||||
|              decodes_model_->add_decode (is_new, id, time, snr, delta_time, delta_frequency, mode, message | ||||
|                                          , low_confidence, off_air, dock_widgets_[id]->fast_mode ());}); | ||||
|   connect (server_, &MessageServer::WSPR_decode, beacons_model_, &BeaconsModel::add_beacon_spot); | ||||
|   connect (server_, &MessageServer::clear_decodes, decodes_model_, &DecodesModel::clear_decodes); | ||||
|   connect (server_, &MessageServer::clear_decodes, beacons_model_, &BeaconsModel::clear_decodes); | ||||
|   connect (decodes_model_, &DecodesModel::reply, server_, &MessageServer::reply); | ||||
| 
 | ||||
|   // UI behaviour
 | ||||
|   connect (port_spin_box, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged) | ||||
|            , [this] (port_type port) {server_->start (port);}); | ||||
|   connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this, port_spin_box] () { | ||||
|       server_->start (port_spin_box->value (), QHostAddress {multicast_group_line_edit_->text ()}); | ||||
|     }); | ||||
| 
 | ||||
|   port_spin_box->setValue (2237); // start up in unicast mode
 | ||||
|   show (); | ||||
| } | ||||
| 
 | ||||
| void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call | ||||
|                                            , QString const& dx_grid, Frequency dial_frequency, QString const& mode | ||||
|                                            , QString const& report_sent, QString const& report_received | ||||
|                                            , QString const& tx_power, QString const& comments | ||||
|                                            , QString const& name, QDateTime time_on, QString const& operator_call | ||||
|                                            , QString const& my_call, QString const& my_grid) | ||||
| { | ||||
|   QList<QStandardItem *> row; | ||||
|   row << new QStandardItem {time_on.toString ("dd-MMM-yyyy hh:mm:ss")} | ||||
|   << new QStandardItem {time_off.toString ("dd-MMM-yyyy hh:mm:ss")} | ||||
|   << new QStandardItem {dx_call} | ||||
|   << new QStandardItem {dx_grid} | ||||
|   << new QStandardItem {name} | ||||
|   << new QStandardItem {Radio::frequency_MHz_string (dial_frequency)} | ||||
|   << new QStandardItem {mode} | ||||
|   << new QStandardItem {report_sent} | ||||
|   << new QStandardItem {report_received} | ||||
|   << new QStandardItem {tx_power} | ||||
|   << new QStandardItem {operator_call} | ||||
|   << new QStandardItem {my_call} | ||||
|   << new QStandardItem {my_grid} | ||||
|   << new QStandardItem {comments}; | ||||
|   log_->appendRow (row); | ||||
|   log_table_view_->resizeColumnsToContents (); | ||||
|   log_table_view_->horizontalHeader ()->setStretchLastSection (true); | ||||
|   log_table_view_->scrollToBottom (); | ||||
| } | ||||
| 
 | ||||
| void MessageAggregatorMainWindow::add_client (QString const& id, QString const& version, QString const& revision) | ||||
| { | ||||
|   auto dock = new ClientWidget {decodes_model_, beacons_model_, id, version, revision, calls_of_interest_, this}; | ||||
|   dock->setAttribute (Qt::WA_DeleteOnClose); | ||||
|   auto view_action = dock->toggleViewAction (); | ||||
|   view_action->setEnabled (true); | ||||
|   view_menu_->addAction (view_action); | ||||
|   addDockWidget (Qt::BottomDockWidgetArea, dock); | ||||
|   connect (server_, &MessageServer::status_update, dock, &ClientWidget::update_status); | ||||
|   connect (server_, &MessageServer::decode, dock, &ClientWidget::decode_added); | ||||
|   connect (server_, &MessageServer::WSPR_decode, dock, &ClientWidget::beacon_spot_added); | ||||
|   connect (server_, &MessageServer::clear_decodes, dock, &ClientWidget::clear_decodes); | ||||
|   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 (dock, &ClientWidget::location, server_, &MessageServer::location); | ||||
|   connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible); | ||||
|   connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign); | ||||
|   dock_widgets_[id] = dock; | ||||
|   server_->replay (id);         // request decodes and status
 | ||||
| } | ||||
| 
 | ||||
| void MessageAggregatorMainWindow::remove_client (QString const& id) | ||||
| { | ||||
|   auto iter = dock_widgets_.find (id); | ||||
|   if (iter != std::end (dock_widgets_)) | ||||
|     { | ||||
|       (*iter)->close (); | ||||
|       dock_widgets_.erase (iter); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| MessageAggregatorMainWindow::~MessageAggregatorMainWindow () | ||||
| { | ||||
|   for (auto client : dock_widgets_) | ||||
|     { | ||||
|       delete client; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void MessageAggregatorMainWindow::change_highlighting (QString const& call, QColor const& bg, QColor const& fg | ||||
|                                                        , bool last_only) | ||||
| { | ||||
|   for (auto id : dock_widgets_.keys ()) | ||||
|     { | ||||
|       server_->highlight_callsign (id, call, bg, fg, last_only); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #include "moc_MessageAggregatorMainWindow.cpp" | ||||
| @ -1,62 +0,0 @@ | ||||
| #ifndef WSJTX_MESSAGE_AGGREGATOR_MAIN_WINDOW_MODEL_HPP__ | ||||
| #define WSJTX_MESSAGE_AGGREGATOR_MAIN_WINDOW_MODEL_HPP__ | ||||
| 
 | ||||
| #include <QMainWindow> | ||||
| #include <QHash> | ||||
| #include <QString> | ||||
| 
 | ||||
| #include "MessageServer.hpp" | ||||
| 
 | ||||
| class QDateTime; | ||||
| class QStandardItemModel; | ||||
| class QMenu; | ||||
| class DecodesModel; | ||||
| class BeaconsModel; | ||||
| class QLineEdit; | ||||
| class QTableView; | ||||
| class ClientWidget; | ||||
| class QListWidget; | ||||
| 
 | ||||
| using Frequency = MessageServer::Frequency; | ||||
| 
 | ||||
| class MessageAggregatorMainWindow | ||||
|   : public QMainWindow | ||||
| { | ||||
|   Q_OBJECT; | ||||
| 
 | ||||
| public: | ||||
|   MessageAggregatorMainWindow (); | ||||
|   ~MessageAggregatorMainWindow (); | ||||
| 
 | ||||
|   Q_SLOT void log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call, QString const& dx_grid | ||||
|                        , Frequency dial_frequency, QString const& mode, QString const& report_sent | ||||
|                        , QString const& report_received, QString const& tx_power, QString const& comments | ||||
|                        , QString const& name, QDateTime time_on, QString const& operator_call | ||||
|                        , QString const& my_call, QString const& my_grid); | ||||
| 
 | ||||
| private: | ||||
|   void add_client (QString const& id, QString const& version, QString const& revision); | ||||
|   void remove_client (QString const& id); | ||||
|   void change_highlighting (QString const& call, QColor const& bg = QColor {}, QColor const& fg = QColor {}, | ||||
|                             bool last_only = false); | ||||
| 
 | ||||
|   // maps client id to widgets
 | ||||
|   using ClientsDictionary = QHash<QString, ClientWidget *>; | ||||
|   ClientsDictionary dock_widgets_; | ||||
| 
 | ||||
|   QStandardItemModel * log_; | ||||
|   QMenu * view_menu_; | ||||
|   DecodesModel * decodes_model_; | ||||
|   BeaconsModel * beacons_model_; | ||||
|   MessageServer * server_; | ||||
|   QLineEdit * multicast_group_line_edit_; | ||||
|   QTableView * log_table_view_; | ||||
|   QListWidget * calls_of_interest_; | ||||
|   QAction * add_call_of_interest_action_; | ||||
|   QAction * delete_call_of_interest_action_; | ||||
|   QAction * last_call_of_interest_action_; | ||||
|   QAction * call_of_interest_bg_colour_action_; | ||||
|   QAction * call_of_interest_fg_colour_action_; | ||||
| }; | ||||
| 
 | ||||
| #endif | ||||
| @ -1,245 +0,0 @@ | ||||
| //
 | ||||
| // UDPDaemon - an example console application that utilizes the WSJT-X
 | ||||
| //             messaging facility
 | ||||
| //
 | ||||
| // This application is  only provided as a  simple console application
 | ||||
| // example to  demonstrate the  WSJT-X messaging facility.   It allows
 | ||||
| // the user to  set the server details either as  a unicast UDP server
 | ||||
| // or,  if a  multicast  group  address is  provided,  as a  multicast
 | ||||
| // server.   The benefit  of  the multicast  server  is that  multiple
 | ||||
| // servers can be  active at once each receiving  all WSJT-X broadcast
 | ||||
| // messages and each able to respond to individual WSJT_X clients.  To
 | ||||
| // utilize the  multicast group features  each WSJT-X client  must set
 | ||||
| // the  same multicast  group address  as the  UDP server  address for
 | ||||
| // example 239.255.0.0 for a site local multicast group.
 | ||||
| //
 | ||||
| //
 | ||||
| 
 | ||||
| #include <iostream> | ||||
| #include <exception> | ||||
| 
 | ||||
| #include <QCoreApplication> | ||||
| #include <QCommandLineParser> | ||||
| #include <QDateTime> | ||||
| #include <QTime> | ||||
| #include <QHash> | ||||
| #include <QDebug> | ||||
| 
 | ||||
| #include "MessageServer.hpp" | ||||
| #include "Radio.hpp" | ||||
| 
 | ||||
| #include "qt_helpers.hpp" | ||||
| 
 | ||||
| using port_type = MessageServer::port_type; | ||||
| using Frequency = MessageServer::Frequency; | ||||
| 
 | ||||
| class Client | ||||
|   : public QObject | ||||
| { | ||||
|   Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|   explicit Client (QString const& id, QObject * parent = nullptr) | ||||
|     : QObject {parent} | ||||
|     , id_ {id} | ||||
|     , dial_frequency_ {0u} | ||||
|   { | ||||
|   } | ||||
| 
 | ||||
|   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*/ | ||||
|                              , QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/ | ||||
|                              , bool /* watchdog_timeout */, QString const& sub_mode, bool /*fast_mode*/) | ||||
|   { | ||||
|     if (id == id_) | ||||
|       { | ||||
|         if (f != dial_frequency_) | ||||
|           { | ||||
|             std::cout << tr ("%1: Dial frequency changed to %2").arg (id_).arg (f).toStdString () << std::endl; | ||||
|             dial_frequency_ = f; | ||||
|           } | ||||
|         if (mode + sub_mode != mode_) | ||||
|           { | ||||
|             std::cout << tr ("%1: Mode changed to %2").arg (id_).arg (mode + sub_mode).toStdString () << std::endl; | ||||
|             mode_ = mode + sub_mode; | ||||
|           } | ||||
|       } | ||||
|   } | ||||
| 
 | ||||
|   Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime time, qint32 snr | ||||
|                             , float delta_time, quint32 delta_frequency, QString const& mode | ||||
|                             , QString const& message, bool low_confidence, bool off_air) | ||||
|   { | ||||
|     if (client_id == id_) | ||||
|       { | ||||
|         qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr | ||||
|                   << "Dt:" << delta_time << "Df:" << delta_frequency | ||||
|                   << "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high") | ||||
|                   << "On air:" << !off_air; | ||||
|         std::cout << tr ("%1: Decoded %2").arg (id_).arg (message).toStdString () << std::endl; | ||||
|       } | ||||
|   } | ||||
| 
 | ||||
|   Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime time, qint32 snr | ||||
|       , float delta_time, Frequency delta_frequency, qint32 drift, QString const& callsign | ||||
|                                  , QString const& grid, qint32 power, bool off_air) | ||||
|   { | ||||
|     if (client_id == id_) | ||||
|       { | ||||
|         qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr | ||||
|                   << "Dt:" << delta_time << "Df:" << delta_frequency | ||||
|                   << "drift:" << drift; | ||||
|         std::cout << tr ("%1: WSPR decode %2 grid %3 power: %4").arg (id_).arg (callsign).arg (grid).arg (power).toStdString () | ||||
|                   << "On air:" << !off_air << std::endl; | ||||
|       } | ||||
|   } | ||||
| 
 | ||||
|   Q_SLOT void qso_logged (QString const&client_id, QDateTime time_off, QString const& dx_call, QString const& dx_grid | ||||
|                           , Frequency dial_frequency, QString const& mode, QString const& report_sent | ||||
|                           , QString const& report_received, QString const& tx_power | ||||
|                           , QString const& comments, QString const& name, QDateTime time_on | ||||
|                           , QString const& operator_call, QString const& my_call, QString const& my_grid) | ||||
|   { | ||||
|       if (client_id == id_) | ||||
|       { | ||||
|         qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:" << dx_call << "grid:" << dx_grid | ||||
|                   << "freq:" << dial_frequency << "mode:" << mode << "rpt_sent:" << report_sent | ||||
|                   << "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments | ||||
|                   << "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call | ||||
|                   << "my_grid:" << my_grid; | ||||
|         std::cout << QByteArray {80, '-'}.data () << '\n'; | ||||
|         std::cout << tr ("%1: Logged %2 grid: %3 power: %4 sent: %5 recd: %6 freq: %7 time_off: %8 op: %9 my_call: %10 my_grid: %11") | ||||
|           .arg (id_).arg (dx_call).arg (dx_grid).arg (tx_power).arg (report_sent).arg (report_received) | ||||
|           .arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call) | ||||
|           .arg (my_call).arg (my_grid).toStdString () | ||||
|                   << std::endl; | ||||
|       } | ||||
|   } | ||||
| 
 | ||||
|   Q_SLOT void logged_ADIF (QString const&client_id, QByteArray const& ADIF) | ||||
|   { | ||||
|       if (client_id == id_) | ||||
|       { | ||||
|         qDebug () << "ADIF:" << ADIF; | ||||
|         std::cout << QByteArray {80, '-'}.data () << '\n'; | ||||
|         std::cout << ADIF.data () << std::endl; | ||||
|       } | ||||
|   } | ||||
| 
 | ||||
| private: | ||||
|   QString id_; | ||||
|   Frequency dial_frequency_; | ||||
|   QString mode_; | ||||
| }; | ||||
| 
 | ||||
| class Server | ||||
|   : public QObject | ||||
| { | ||||
|   Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|   Server (port_type port, QHostAddress const& multicast_group) | ||||
|     : server_ {new MessageServer {this}} | ||||
|   { | ||||
|     // connect up server
 | ||||
|     connect (server_, &MessageServer::error, [this] (QString const& message) { | ||||
|         std::cerr << tr ("Network Error: %1").arg ( message).toStdString () << std::endl; | ||||
|       }); | ||||
|     connect (server_, &MessageServer::client_opened, this, &Server::add_client); | ||||
|     connect (server_, &MessageServer::client_closed, this, &Server::remove_client); | ||||
| 
 | ||||
|     server_->start (port, multicast_group); | ||||
|   } | ||||
| 
 | ||||
| private: | ||||
|   void add_client (QString const& id, QString const& version, QString const& revision) | ||||
|   { | ||||
|     auto client = new Client {id}; | ||||
|     connect (server_, &MessageServer::status_update, client, &Client::update_status); | ||||
|     connect (server_, &MessageServer::decode, client, &Client::decode_added); | ||||
|     connect (server_, &MessageServer::WSPR_decode, client, &Client::beacon_spot_added); | ||||
|     connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged); | ||||
|     connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF); | ||||
|     clients_[id] = client; | ||||
|     server_->replay (id); | ||||
|     std::cout << "Discovered WSJT-X instance: " << id.toStdString (); | ||||
|     if (version.size ()) | ||||
|       { | ||||
|         std::cout << " v" << version.toStdString (); | ||||
|       } | ||||
|     if (revision.size ()) | ||||
|       { | ||||
|         std::cout << " (" << revision.toStdString () << ")"; | ||||
|       } | ||||
|     std::cout << std::endl; | ||||
|   } | ||||
| 
 | ||||
|   void remove_client (QString const& id) | ||||
|   { | ||||
|     auto iter = clients_.find (id); | ||||
|     if (iter != std::end (clients_)) | ||||
|       { | ||||
|         clients_.erase (iter); | ||||
|         (*iter)->deleteLater (); | ||||
|       } | ||||
|     std::cout << "Removed WSJT-X instance: " << id.toStdString () << std::endl; | ||||
|   } | ||||
| 
 | ||||
|   MessageServer * server_; | ||||
| 
 | ||||
|   // maps client id to clients
 | ||||
|   QHash<QString, Client *> clients_; | ||||
| }; | ||||
| 
 | ||||
| #include "UDPDaemon.moc" | ||||
| 
 | ||||
| int main (int argc, char * argv[]) | ||||
| { | ||||
|   QCoreApplication app {argc, argv}; | ||||
|   try | ||||
|     { | ||||
|       setlocale (LC_NUMERIC, "C"); // ensure number forms are in
 | ||||
|                                    // consistent format, do this after
 | ||||
|                                    // instantiating QApplication so
 | ||||
|                                    // that GUI has correct l18n
 | ||||
| 
 | ||||
|       app.setApplicationName ("WSJT-X UDP Message Server Daemon"); | ||||
|       app.setApplicationVersion ("1.0"); | ||||
| 
 | ||||
|       QCommandLineParser parser; | ||||
|       parser.setApplicationDescription ("\nWSJT-X UDP Message Server Daemon."); | ||||
|       auto help_option = parser.addHelpOption (); | ||||
|       auto version_option = parser.addVersionOption (); | ||||
| 
 | ||||
|       QCommandLineOption port_option (QStringList {"p", "port"}, | ||||
|                                       app.translate ("UDPDaemon", | ||||
|                                                      "Where <PORT> is the UDP service port number to listen on.\n" | ||||
|                                                      "The default service port is 2237."), | ||||
|                                       app.translate ("UDPDaemon", "PORT"), | ||||
|                                       "2237"); | ||||
|       parser.addOption (port_option); | ||||
| 
 | ||||
|       QCommandLineOption multicast_addr_option (QStringList {"g", "multicast-group"}, | ||||
|                                                 app.translate ("UDPDaemon", | ||||
|                                                                "Where <GROUP> is the multicast group to join.\n" | ||||
|                                                                "The default is a unicast server listening on all interfaces."), | ||||
|                                                 app.translate ("UDPDaemon", "GROUP")); | ||||
|       parser.addOption (multicast_addr_option); | ||||
| 
 | ||||
|       parser.process (app); | ||||
| 
 | ||||
|       Server server {static_cast<port_type> (parser.value (port_option).toUInt ()), QHostAddress {parser.value (multicast_addr_option)}}; | ||||
| 
 | ||||
|       return app.exec (); | ||||
|     } | ||||
|   catch (std::exception const & e) | ||||
|     { | ||||
|       std::cerr << "Error: " << e.what () << '\n'; | ||||
|     } | ||||
|   catch (...) | ||||
|     { | ||||
|       std::cerr << "Unexpected error\n"; | ||||
|     } | ||||
|   return -1; | ||||
| } | ||||
| @ -1,5 +0,0 @@ | ||||
| <!DOCTYPE RCC> | ||||
| <RCC version="1.0"> | ||||
|   <qresource>@message_aggregator_RESOURCES@ | ||||
|   </qresource> | ||||
| </RCC> | ||||
| @ -1 +0,0 @@ | ||||
| IDI_ICON1       ICON    DISCARDABLE     "../icons/windows-icons/ft8call.ico" | ||||
| @ -1,13 +0,0 @@ | ||||
| /* default stylesheet for the message aggregator application */ | ||||
| 
 | ||||
| [transmitting="true"] { | ||||
|        background-color: yellow | ||||
| } | ||||
| 
 | ||||
| [decoding="true"] { | ||||
|        background-color: cyan | ||||
| } | ||||
| 
 | ||||
| [watchdog_timeout="true"] { | ||||
|        background-color: red | ||||
| } | ||||
| @ -1 +0,0 @@ | ||||
| IDI_ICON1       ICON    DISCARDABLE     "../icons/windows-icons/ft8call.ico" | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Jordan Sherer
						Jordan Sherer