Merge branch 'develop' into feat-boost-log

This commit is contained in:
Bill Somerville 2020-11-12 13:36:25 +00:00
commit 9ea903b259
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
50 changed files with 8148 additions and 6575 deletions

View File

@ -235,6 +235,7 @@ set (wsjt_qt_CXXSRCS
logbook/Multiplier.cpp
Network/NetworkAccessManager.cpp
widgets/LazyFillComboBox.cpp
widgets/CheckableItemComboBox.cpp
)
set (wsjt_qtmm_CXXSRCS
@ -1158,6 +1159,11 @@ target_link_libraries (ft4sim_mult wsjt_fort wsjt_cxx)
add_executable (fst4sim lib/fst4/fst4sim.f90)
target_link_libraries (fst4sim wsjt_fort wsjt_cxx)
if (WIN32)
set_target_properties (fst4sim PROPERTIES
LINK_FLAGS -Wl,--stack,0x4000000,--heap,0x6000000
)
endif ()
add_executable (ldpcsim240_101 lib/fst4/ldpcsim240_101.f90)
target_link_libraries (ldpcsim240_101 wsjt_fort wsjt_cxx)
@ -1323,7 +1329,7 @@ generate_version_info (jt9_VERSION_RESOURCES
NAME jt9
BUNDLE ${PROJECT_BUNDLE_NAME}
ICON ${WSJTX_ICON_FILE}
FILE_DESCRIPTION "Slow mode decoder"
FILE_DESCRIPTION "jt9 - WSJT-X slow mode decoder"
)
add_executable (record_time_signal Audio/tools/record_time_signal.cpp)
@ -1415,7 +1421,7 @@ else ()
)
if (WIN32)
set_target_properties (wsjtx PROPERTIES
LINK_FLAGS -Wl,--stack,16777216
LINK_FLAGS -Wl,--stack,0x1000000,--heap,0x20000000
)
endif ()
endif ()
@ -1468,7 +1474,7 @@ add_executable (message_aggregator
${message_aggregator_RESOURCES_RCC}
${message_aggregator_VERSION_RESOURCES}
)
target_link_libraries (message_aggregator Qt5::Widgets wsjtx_udp-static)
target_link_libraries (message_aggregator wsjt_qt Qt5::Widgets wsjtx_udp-static)
if (WSJT_CREATE_WINMAIN)
set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)

View File

@ -163,6 +163,10 @@
#include <QFontDialog>
#include <QSerialPortInfo>
#include <QScopedPointer>
#include <QNetworkInterface>
#include <QHostInfo>
#include <QHostAddress>
#include <QStandardItem>
#include <QDebug>
#include "pimpl_impl.hpp"
@ -440,6 +444,12 @@ private:
void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *);
void update_audio_channels (QComboBox const *, int, QComboBox *, bool);
void load_network_interfaces (CheckableItemComboBox *, QStringList current);
Q_SLOT void validate_network_interfaces (QString const&);
QStringList get_selected_network_interfaces (CheckableItemComboBox *);
Q_SLOT void host_info_results (QHostInfo);
void check_multicast (QHostAddress const&);
void find_tab (QWidget *);
void initialize_models ();
@ -493,6 +503,8 @@ private:
Q_SLOT void on_add_macro_line_edit_editingFinished ();
Q_SLOT void delete_macro ();
void delete_selected_macros (QModelIndexList);
Q_SLOT void on_udp_server_line_edit_textChanged (QString const&);
Q_SLOT void on_udp_server_line_edit_editingFinished ();
Q_SLOT void on_save_path_select_push_button_clicked (bool);
Q_SLOT void on_azel_path_select_push_button_clicked (bool);
Q_SLOT void on_calibration_intercept_spin_box_valueChanged (double);
@ -642,7 +654,12 @@ private:
bool use_dynamic_grid_;
QString opCall_;
QString udp_server_name_;
bool udp_server_name_edited_;
int dns_lookup_id_;
port_type udp_server_port_;
QStringList udp_interface_names_;
QString loopback_interface_name_;
int udp_TTL_;
QString n1mm_server_name_;
port_type n1mm_server_port_;
bool broadcast_to_n1mm_;
@ -742,6 +759,8 @@ QString Configuration::opCall() const {return m_->opCall_;}
void Configuration::opCall (QString const& call) {m_->opCall_ = call;}
QString Configuration::udp_server_name () const {return m_->udp_server_name_;}
auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;}
QStringList Configuration::udp_interface_names () const {return m_->udp_interface_names_;}
int Configuration::udp_TTL () const {return m_->udp_TTL_;}
bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;}
QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;}
auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;}
@ -976,6 +995,8 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
, transceiver_command_number_ {0}
, degrade_ {0.} // initialize to zero each run, not
// saved in settings
, udp_server_name_edited_ {false}
, dns_lookup_id_ {-1}
{
ui_->setupUi (this);
@ -1025,6 +1046,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
// this must be done after the default paths above are set
read_settings ();
// set up dynamic loading of audio devices
connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_);
@ -1040,14 +1062,22 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
QGuiApplication::restoreOverrideCursor ();
});
// set up dynamic loading of network interfaces
connect (ui_->udp_interfaces_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_);
QGuiApplication::restoreOverrideCursor ();
});
connect (ui_->udp_interfaces_combo_box, &QComboBox::currentTextChanged, this, &Configuration::impl::validate_network_interfaces);
// set up LoTW users CSV file fetching
connect (&lotw_users_, &LotWUsers::load_finished, [this] () {
ui_->LotW_CSV_fetch_push_button->setEnabled (true);
});
lotw_users_.set_local_file_path (writeable_data_dir_.absoluteFilePath ("lotw-user-activity.csv"));
// load the dictionary if it exists
lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), false);
// load the dictionary if it exists, fetch and load if it doesn't
lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text ());
//
// validation
@ -1316,7 +1346,14 @@ void Configuration::impl::initialize_models ()
ui_->CAT_poll_interval_spin_box->setValue (rig_params_.poll_interval);
ui_->opCallEntry->setText (opCall_);
ui_->udp_server_line_edit->setText (udp_server_name_);
on_udp_server_line_edit_editingFinished ();
ui_->udp_server_port_spin_box->setValue (udp_server_port_);
load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_);
if (!udp_interface_names_.size ())
{
udp_interface_names_ = get_selected_network_interfaces (ui_->udp_interfaces_combo_box);
}
ui_->udp_TTL_spin_box->setValue (udp_TTL_);
ui_->accept_udp_requests_check_box->setChecked (accept_udp_requests_);
ui_->n1mm_server_name_line_edit->setText (n1mm_server_name_);
ui_->n1mm_server_port_spin_box->setValue (n1mm_server_port_);
@ -1494,6 +1531,8 @@ void Configuration::impl::read_settings ()
rig_params_.split_mode = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value<TransceiverFactory::SplitMode> ();
opCall_ = settings_->value ("OpCall", "").toString ();
udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString ();
udp_interface_names_ = settings_->value ("UDPInterface").toStringList ();
udp_TTL_ = settings_->value ("UDPTTL", 1).toInt ();
udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt ();
n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString ();
n1mm_server_port_ = settings_->value ("N1MMServerPort", 2333).toUInt ();
@ -1622,6 +1661,8 @@ void Configuration::impl::write_settings ()
settings_->setValue ("OpCall", opCall_);
settings_->setValue ("UDPServer", udp_server_name_);
settings_->setValue ("UDPServerPort", udp_server_port_);
settings_->setValue ("UDPInterface", QVariant::fromValue (udp_interface_names_));
settings_->setValue ("UDPTTL", udp_TTL_);
settings_->setValue ("N1MMServer", n1mm_server_name_);
settings_->setValue ("N1MMServerPort", n1mm_server_port_);
settings_->setValue ("BroadcastToN1MM", broadcast_to_n1mm_);
@ -1824,6 +1865,12 @@ bool Configuration::impl::validate ()
return false;
}
if (dns_lookup_id_ > -1)
{
MessageBox::information_message (this, tr ("Pending DNS lookup, please try again later"));
return false;
}
return true;
}
@ -2042,20 +2089,30 @@ void Configuration::impl::accept ()
pwrBandTxMemory_ = ui_->checkBoxPwrBandTxMemory->isChecked ();
pwrBandTuneMemory_ = ui_->checkBoxPwrBandTuneMemory->isChecked ();
opCall_=ui_->opCallEntry->text();
auto new_server = ui_->udp_server_line_edit->text ();
if (new_server != udp_server_name_)
auto new_server = ui_->udp_server_line_edit->text ().trimmed ();
auto new_interfaces = get_selected_network_interfaces (ui_->udp_interfaces_combo_box);
if (new_server != udp_server_name_ || new_interfaces != udp_interface_names_)
{
udp_server_name_ = new_server;
Q_EMIT self_->udp_server_changed (new_server);
udp_interface_names_ = new_interfaces;
Q_EMIT self_->udp_server_changed (udp_server_name_, udp_interface_names_);
}
auto new_port = ui_->udp_server_port_spin_box->value ();
if (new_port != udp_server_port_)
{
udp_server_port_ = new_port;
Q_EMIT self_->udp_server_port_changed (new_port);
Q_EMIT self_->udp_server_port_changed (udp_server_port_);
}
auto new_TTL = ui_->udp_TTL_spin_box->value ();
if (new_TTL != udp_TTL_)
{
udp_TTL_ = new_TTL;
Q_EMIT self_->udp_TTL_changed (udp_TTL_);
}
if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_)
{
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
@ -2111,6 +2168,12 @@ void Configuration::impl::accept ()
void Configuration::impl::reject ()
{
if (dns_lookup_id_ > -1)
{
QHostInfo::abortHostLookup (dns_lookup_id_);
dns_lookup_id_ = -1;
}
initialize_models (); // reverts to settings as at exec ()
// check if the Transceiver instance changed, in which case we need
@ -2325,6 +2388,72 @@ void Configuration::impl::on_add_macro_push_button_clicked (bool /* checked */)
}
}
void Configuration::impl::on_udp_server_line_edit_textChanged (QString const&)
{
udp_server_name_edited_ = true;
}
void Configuration::impl::on_udp_server_line_edit_editingFinished ()
{
if (udp_server_name_edited_)
{
auto const& server = ui_->udp_server_line_edit->text ().trimmed ();
QHostAddress ha {server};
if (server.size () && ha.isNull ())
{
// queue a host address lookup
// qDebug () << "server host DNS lookup:" << server;
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
dns_lookup_id_ = QHostInfo::lookupHost (server, this, &Configuration::impl::host_info_results);
#else
dns_lookup_id_ = QHostInfo::lookupHost (server, this, SLOT (host_info_results (QHostInfo)));
#endif
}
else
{
check_multicast (ha);
}
}
}
void Configuration::impl::host_info_results (QHostInfo host_info)
{
if (host_info.lookupId () != dns_lookup_id_) return;
dns_lookup_id_ = -1;
if (QHostInfo::NoError != host_info.error ())
{
MessageBox::critical_message (this, tr ("UDP server DNS lookup failed"), host_info.errorString ());
}
else
{
auto const& server_addresses = host_info.addresses ();
// qDebug () << "message server addresses:" << server_addresses;
if (server_addresses.size ())
{
check_multicast (server_addresses[0]);
}
}
}
void Configuration::impl::check_multicast (QHostAddress const& ha)
{
auto is_multicast = is_multicast_address (ha);
ui_->udp_interfaces_label->setVisible (is_multicast);
ui_->udp_interfaces_combo_box->setVisible (is_multicast);
ui_->udp_TTL_label->setVisible (is_multicast);
ui_->udp_TTL_spin_box->setVisible (is_multicast);
if (isVisible ())
{
if (is_MAC_ambiguous_multicast_address (ha))
{
MessageBox::warning_message (this, tr ("MAC-ambiguous multicast groups addresses not supported"));
find_tab (ui_->udp_server_line_edit);
ui_->udp_server_line_edit->clear ();
}
}
udp_server_name_edited_ = false;
}
void Configuration::impl::delete_frequencies ()
{
auto selection_model = ui_->frequencies_table_view->selectionModel ();
@ -2844,6 +2973,85 @@ void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * com
combo_box->setCurrentIndex (current_index);
}
// load the available network interfaces into the selection combo box
void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo_box, QStringList current)
{
combo_box->clear ();
for (auto const& net_if : QNetworkInterface::allInterfaces ())
{
auto flags = QNetworkInterface::IsUp | QNetworkInterface::CanMulticast;
if ((net_if.flags () & flags) == flags)
{
if (net_if.flags () & QNetworkInterface::IsLoopBack)
{
loopback_interface_name_ = net_if.name ();
}
auto item = combo_box->addCheckItem (net_if.humanReadableName ()
, net_if.name ()
, current.contains (net_if.name ()) ? Qt::Checked : Qt::Unchecked);
auto tip = QString {"name(index): %1(%2) - %3"}.arg (net_if.name ()).arg (net_if.index ())
.arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down");
auto hw_addr = net_if.hardwareAddress ();
if (hw_addr.size ())
{
tip += QString {"\nhw: %1"}.arg (net_if.hardwareAddress ());
}
auto aes = net_if.addressEntries ();
if (aes.size ())
{
tip += "\naddresses:";
for (auto const& ae : aes)
{
tip += QString {"\n ip: %1/%2"}.arg (ae.ip ().toString ()).arg (ae.prefixLength ());
}
}
item->setToolTip (tip);
}
}
}
// get the select network interfaces from the selection combo box
void Configuration::impl::validate_network_interfaces (QString const& /*text*/)
{
auto model = static_cast<QStandardItemModel *> (ui_->udp_interfaces_combo_box->model ());
bool has_checked {false};
int loopback_row {-1};
for (int row = 0; row < model->rowCount (); ++row)
{
if (model->item (row)->data ().toString () == loopback_interface_name_)
{
loopback_row = row;
}
else if (Qt::Checked == model->item (row)->checkState ())
{
has_checked = true;
}
}
if (loopback_row >= 0)
{
if (!has_checked)
{
model->item (loopback_row)->setCheckState (Qt::Checked);
}
model->item (loopback_row)->setEnabled (has_checked);
}
}
// get the select network interfaces from the selection combo box
QStringList Configuration::impl::get_selected_network_interfaces (CheckableItemComboBox * combo_box)
{
QStringList interfaces;
auto model = static_cast<QStandardItemModel *> (combo_box->model ());
for (int row = 0; row < model->rowCount (); ++row)
{
if (Qt::Checked == model->item (row)->checkState ())
{
interfaces << model->item (row)->data ().toString ();
}
}
return interfaces;
}
// enable only the channels that are supported by the selected audio device
void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both)
{

View File

@ -3,6 +3,7 @@
#include <QObject>
#include <QFont>
#include <QString>
#include "Radio.hpp"
#include "models/IARURegions.hpp"
@ -14,14 +15,12 @@
class QSettings;
class QWidget;
class QAudioDeviceInfo;
class QString;
class QDir;
class QNetworkAccessManager;
class Bands;
class FrequencyList_v2;
class StationList;
class QStringListModel;
class QHostAddress;
class LotWUsers;
class DecodeHighlightingModel;
class LogBook;
@ -152,6 +151,8 @@ public:
void opCall (QString const&);
QString udp_server_name () const;
port_type udp_server_port () const;
QStringList udp_interface_names () const;
int udp_TTL () const;
QString n1mm_server_name () const;
port_type n1mm_server_port () const;
bool valid_n1mm_info () const;
@ -273,8 +274,9 @@ public:
//
// This signal is emitted when the UDP server changes
//
Q_SIGNAL void udp_server_changed (QString const& udp_server) const;
Q_SIGNAL void udp_server_changed (QString& udp_server_name, QStringList const& network_interfaces) const;
Q_SIGNAL void udp_server_port_changed (port_type server_port) const;
Q_SIGNAL void udp_TTL_changed (int TTL) const;
Q_SIGNAL void accept_udp_requests_changed (bool checked) const;
// signal updates to decode highlighting

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>554</width>
<height>556</height>
<height>560</height>
</rect>
</property>
<property name="windowTitle">
@ -1864,12 +1864,6 @@ and DX Grid fields when a 73 or free text message is sent.</string>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="udp_server_line_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Optional hostname of network service to receive decodes.&lt;/p&gt;&lt;p&gt;Formats:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv4 address&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv6 address&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv4 multicast group address&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv6 multicast group address&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Clearing this field will disable the broadcasting of UDP status updates.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@ -1891,13 +1885,53 @@ and DX Grid fields when a 73 or free text message is sent.</string>
<item row="1" column="1">
<widget class="QSpinBox" name="udp_server_port_spin_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter the service port number of the UDP server that WSJT-X should send updates to. If this is zero no updates will be broadcast.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter the service port number of the UDP server that WSJT-X should send updates to. If this is zero no updates will be sent.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>65534</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="udp_interfaces_label">
<property name="text">
<string>Outgoing interfaces:</string>
</property>
<property name="buddy">
<cstring>udp_interfaces_combo_box</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="CheckableItemComboBox" name="udp_interfaces_combo_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When sending updates to a multicast group address it is necessary to specify which network interface(s) to send them to. If the loop-back interface is multicast capable then at least that one will be selected.&lt;/p&gt;&lt;p&gt;For most users the loop-back interface is all that is needed, that will allow multiple other applications on the same machine to interoperate with WSJT-X. If applications running on other hosts are to receive status updates then a suitable network interface should be used.&lt;/p&gt;&lt;p&gt;On some Linux systems it may be necessary to enable multicast on the loop-back network interface.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="udp_TTL_spin_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sets the number or router hops that multicast datagrams are allowed to make. Almost everyone should set this to 1 to keep outgoing multicast traffic withn the local subnet.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="udp_TTL_label">
<property name="text">
<string>Multicast TTL:</string>
</property>
<property name="buddy">
<cstring>udp_TTL_spin_box</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
@ -3002,6 +3036,11 @@ Right click for insert and delete options.</string>
<extends>QComboBox</extends>
<header>widgets/LazyFillComboBox.hpp</header>
</customwidget>
<customwidget>
<class>CheckableItemComboBox</class>
<extends>QComboBox</extends>
<header>widgets/CheckableItemComboBox.hpp</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>configuration_tabs</tabstop>
@ -3084,6 +3123,8 @@ Right click for insert and delete options.</string>
<tabstop>psk_reporter_tcpip_check_box</tabstop>
<tabstop>udp_server_line_edit</tabstop>
<tabstop>udp_server_port_spin_box</tabstop>
<tabstop>udp_interfaces_combo_box</tabstop>
<tabstop>udp_TTL_spin_box</tabstop>
<tabstop>accept_udp_requests_check_box</tabstop>
<tabstop>udpWindowToFront</tabstop>
<tabstop>udpWindowRestore</tabstop>
@ -3101,8 +3142,8 @@ Right click for insert and delete options.</string>
<tabstop>include_WAE_check_box</tabstop>
<tabstop>rescan_log_push_button</tabstop>
<tabstop>LotW_CSV_URL_line_edit</tabstop>
<tabstop>LotW_days_since_upload_spin_box</tabstop>
<tabstop>LotW_CSV_fetch_push_button</tabstop>
<tabstop>LotW_days_since_upload_spin_box</tabstop>
<tabstop>sbNtrials</tabstop>
<tabstop>sbAggressive</tabstop>
<tabstop>cbTwoPass</tabstop>
@ -3118,11 +3159,11 @@ Right click for insert and delete options.</string>
<tabstop>rbHound</tabstop>
<tabstop>rbNA_VHF_Contest</tabstop>
<tabstop>rbField_Day</tabstop>
<tabstop>Field_Day_Exchange</tabstop>
<tabstop>rbEU_VHF_Contest</tabstop>
<tabstop>rbRTTY_Roundup</tabstop>
<tabstop>RTTY_Exchange</tabstop>
<tabstop>rbWW_DIGI</tabstop>
<tabstop>Field_Day_Exchange</tabstop>
<tabstop>RTTY_Exchange</tabstop>
</tabstops>
<resources/>
<connections>
@ -3192,13 +3233,13 @@ Right click for insert and delete options.</string>
</connection>
</connections>
<buttongroups>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="special_op_activity_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="split_mode_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="special_op_activity_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="split_mode_button_group"/>
</buttongroups>
</ui>

View File

@ -6,16 +6,16 @@
#include <limits>
#include <QUdpSocket>
#include <QNetworkInterface>
#include <QHostInfo>
#include <QTimer>
#include <QQueue>
#include <QByteArray>
#include <QHostAddress>
#include <QColor>
#include <QDebug>
#include "NetworkMessage.hpp"
#include "qt_helpers.hpp"
#include "pimpl_impl.hpp"
#include "moc_MessageClient.cpp"
@ -34,14 +34,16 @@ class MessageClient::impl
public:
impl (QString const& id, QString const& version, QString const& revision,
port_type server_port, MessageClient * self)
port_type server_port, int TTL, MessageClient * self)
: self_ {self}
, dns_lookup_id_ {-1}
, enabled_ {false}
, id_ {id}
, version_ {version}
, revision_ {revision}
, dns_lookup_id_ {-1}
, server_port_ {server_port}
, TTL_ {TTL}
, schema_ {2} // use 2 prior to negotiation not 1 which is broken
, heartbeat_timer_ {new QTimer {this}}
{
@ -49,9 +51,6 @@ public:
connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams);
heartbeat_timer_->start (NetworkMessage::pulse * 1000);
// bind to an ephemeral port
bind ();
}
~impl ()
@ -65,35 +64,37 @@ public:
enum StreamStatus {Fail, Short, OK};
void parse_message (QByteArray const& msg);
void set_server (QString const& server_name, QStringList const& network_interface_names);
Q_SLOT void host_info_results (QHostInfo);
void start ();
void parse_message (QByteArray const&);
void pending_datagrams ();
void heartbeat ();
void closedown ();
StreamStatus check_status (QDataStream const&) const;
void send_message (QByteArray const&);
void send_message (QDataStream const& out, QByteArray const& message)
void send_message (QByteArray const&, bool queue_if_pending = true);
void send_message (QDataStream const& out, QByteArray const& message, bool queue_if_pending = true)
{
if (OK == check_status (out))
{
send_message (message);
}
else
{
Q_EMIT self_->error ("Error creating UDP message");
}
if (OK == check_status (out))
{
send_message (message, queue_if_pending);
}
else
{
Q_EMIT self_->error ("Error creating UDP message");
}
}
Q_SLOT void host_info_results (QHostInfo);
MessageClient * self_;
int dns_lookup_id_;
bool enabled_;
QString id_;
QString version_;
QString revision_;
QString server_string_;
port_type server_port_;
int dns_lookup_id_;
QHostAddress server_;
port_type server_port_;
int TTL_;
std::vector<QNetworkInterface> network_interfaces_;
quint32 schema_;
QTimer * heartbeat_timer_;
std::vector<QHostAddress> blocked_addresses_;
@ -105,37 +106,99 @@ public:
#include "MessageClient.moc"
void MessageClient::impl::set_server (QString const& server_name, QStringList const& network_interface_names)
{
// qDebug () << "MessageClient server:" << server_name << "port:" << server_port_ << "interfaces:" << network_interface_names;
server_.setAddress (server_name);
network_interfaces_.clear ();
for (auto const& net_if_name : network_interface_names)
{
network_interfaces_.push_back (QNetworkInterface::interfaceFromName (net_if_name));
}
if (server_.isNull () && server_name.size ()) // DNS lookup required
{
// queue a host address lookup
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, &MessageClient::impl::host_info_results);
#else
dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, SLOT (host_info_results (QHostInfo)));
#endif
}
else
{
start ();
}
}
void MessageClient::impl::host_info_results (QHostInfo host_info)
{
if (host_info.lookupId () != dns_lookup_id_) return;
dns_lookup_id_ = -1;
if (QHostInfo::NoError != host_info.error ())
{
Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ());
pending_messages_.clear (); // discard
Q_EMIT self_->error ("UDP server DNS lookup failed: " + host_info.errorString ());
}
else if (host_info.addresses ().size ())
else
{
auto server = host_info.addresses ()[0];
if (blocked_addresses_.end () == std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server))
auto const& server_addresses = host_info.addresses ();
if (server_addresses.size ())
{
server_ = server;
TRACE_UDP ("resulting server:" << server);
// send initial heartbeat which allows schema negotiation
heartbeat ();
// clear any backlog
while (pending_messages_.size ())
{
send_message (pending_messages_.dequeue ());
}
server_ = server_addresses[0];
}
else
}
start ();
}
void MessageClient::impl::start ()
{
if (server_.isNull ())
{
Q_EMIT self_->close ();
pending_messages_.clear (); // discard
return;
}
if (is_broadcast_address (server_))
{
Q_EMIT self_->error ("IPv4 broadcast not supported, please specify the loop-back address, a server host address, or multicast group address");
pending_messages_.clear (); // discard
return;
}
if (blocked_addresses_.end () != std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server_))
{
Q_EMIT self_->error ("UDP server blocked, please try another");
pending_messages_.clear (); // discard
return;
}
TRACE_UDP ("Trying server:" << server_.toString ());
QHostAddress interface_addr {IPv6Protocol == server_.protocol () ? QHostAddress::AnyIPv6 : QHostAddress::AnyIPv4};
if (localAddress () != interface_addr)
{
if (UnconnectedState != state () || state ())
{
Q_EMIT self_->error ("UDP server blocked, please try another");
pending_messages_.clear (); // discard
close ();
}
// bind to an ephemeral port on the selected interface and set
// up for sending datagrams
bind (interface_addr);
// qDebug () << "Bound to UDP port:" << localPort () << "on:" << localAddress ();
// set multicast TTL to limit scope when sending to multicast
// group addresses
setSocketOption (MulticastTtlOption, TTL_);
}
// send initial heartbeat which allows schema negotiation
heartbeat ();
// clear any backlog
while (pending_messages_.size ())
{
send_message (pending_messages_.dequeue (), false);
}
}
@ -363,14 +426,11 @@ void MessageClient::impl::heartbeat ()
if (server_port_ && !server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id_, schema_};
hb << NetworkMessage::Builder::schema_number // maximum schema number accepted
<< version_.toUtf8 () << revision_.toUtf8 ();
if (OK == check_status (hb))
{
TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_);
writeDatagram (message, server_, server_port_);
}
NetworkMessage::Builder out {&message, NetworkMessage::Heartbeat, id_, schema_};
out << NetworkMessage::Builder::schema_number // maximum schema number accepted
<< version_.toUtf8 () << revision_.toUtf8 ();
TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_);
send_message (out, message, false);
}
}
@ -380,15 +440,12 @@ void MessageClient::impl::closedown ()
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Close, id_, schema_};
if (OK == check_status (out))
{
TRACE_UDP ("");
writeDatagram (message, server_, server_port_);
}
TRACE_UDP ("");
send_message (out, message, false);
}
}
void MessageClient::impl::send_message (QByteArray const& message)
void MessageClient::impl::send_message (QByteArray const& message, bool queue_if_pending)
{
if (server_port_)
{
@ -396,11 +453,25 @@ void MessageClient::impl::send_message (QByteArray const& message)
{
if (message != last_message_) // avoid duplicates
{
writeDatagram (message, server_, server_port_);
if (is_multicast_address (server_))
{
// send datagram on each selected network interface
std::for_each (network_interfaces_.begin (), network_interfaces_.end ()
, [&] (QNetworkInterface const& net_if) {
setMulticastInterface (net_if);
// qDebug () << "Multicast UDP datagram sent to:" << server_ << "port:" << server_port_ << "on:" << multicastInterface ().humanReadableName ();
writeDatagram (message, server_, server_port_);
});
}
else
{
// qDebug () << "Unicast UDP datagram sent to:" << server_ << "port:" << server_port_;
writeDatagram (message, server_, server_port_);
}
last_message_ = message;
}
}
else
else if (queue_if_pending)
{
pending_messages_.enqueue (message);
}
@ -433,9 +504,11 @@ auto MessageClient::impl::check_status (QDataStream const& stream) const -> Stre
}
MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision,
QString const& server, port_type server_port, QObject * self)
QString const& server_name, port_type server_port,
QStringList const& network_interface_names,
int TTL, QObject * self)
: QObject {self}
, m_ {id, version, revision, server_port, this}
, m_ {id, version, revision, server_port, TTL, this}
{
connect (&*m_
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
@ -454,8 +527,8 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString
#endif
Q_EMIT error (m_->errorString ());
}
});
set_server (server);
});
m_->set_server (server_name, network_interface_names);
}
QHostAddress MessageClient::server_address () const
@ -468,20 +541,9 @@ auto MessageClient::server_port () const -> port_type
return m_->server_port_;
}
void MessageClient::set_server (QString const& server)
void MessageClient::set_server (QString const& server_name, QStringList const& network_interface_names)
{
m_->server_.clear ();
m_->server_string_ = server;
if (server.size ())
{
// queue a host address lookup
TRACE_UDP ("server host DNS lookup:" << server);
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, &MessageClient::impl::host_info_results);
#else
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
#endif
}
m_->set_server (server_name, network_interface_names);
}
void MessageClient::set_server_port (port_type server_port)
@ -489,6 +551,12 @@ void MessageClient::set_server_port (port_type server_port)
m_->server_port_ = server_port;
}
void MessageClient::set_TTL (int TTL)
{
m_->TTL_ = TTL;
m_->setSocketOption (QAbstractSocket::MulticastTtlOption, m_->TTL_);
}
void MessageClient::enable (bool flag)
{
m_->enabled_ = flag;
@ -504,7 +572,7 @@ void MessageClient::status_update (Frequency f, QString const& mode, QString con
, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name)
{
if (m_->server_port_ && !m_->server_string_.isEmpty ())
if (m_->server_port_ && !m_->server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_};
@ -521,7 +589,7 @@ void MessageClient::decode (bool is_new, QTime time, qint32 snr, float delta_tim
, QString const& mode, QString const& message_text, bool low_confidence
, bool off_air)
{
if (m_->server_port_ && !m_->server_string_.isEmpty ())
if (m_->server_port_ && !m_->server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_, m_->schema_};
@ -536,7 +604,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt
, qint32 drift, QString const& callsign, QString const& grid, qint32 power
, bool off_air)
{
if (m_->server_port_ && !m_->server_string_.isEmpty ())
if (m_->server_port_ && !m_->server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::WSPRDecode, m_->id_, m_->schema_};
@ -549,7 +617,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt
void MessageClient::decodes_cleared ()
{
if (m_->server_port_ && !m_->server_string_.isEmpty ())
if (m_->server_port_ && !m_->server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Clear, m_->id_, m_->schema_};
@ -566,7 +634,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
, QString const& my_grid, QString const& exchange_sent
, QString const& exchange_rcvd, QString const& propmode)
{
if (m_->server_port_ && !m_->server_string_.isEmpty ())
if (m_->server_port_ && !m_->server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_, m_->schema_};
@ -581,7 +649,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
void MessageClient::logged_ADIF (QByteArray const& ADIF_record)
{
if (m_->server_port_ && !m_->server_string_.isEmpty ())
if (m_->server_port_ && !m_->server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_};

View File

@ -5,6 +5,7 @@
#include <QTime>
#include <QDateTime>
#include <QString>
#include <QHostAddress>
#include "Radio.hpp"
#include "pimpl_h.hpp"
@ -34,19 +35,25 @@ public:
//
// messages will be silently dropped until a server host lookup is complete
MessageClient (QString const& id, QString const& version, QString const& revision,
QString const& server, port_type server_port, QObject * parent = nullptr);
QString const& server_name, port_type server_port,
QStringList const& network_interface_names,
int TTL, QObject * parent = nullptr);
// query server details
QHostAddress server_address () const;
port_type server_port () const;
// initiate a new server host lookup or is the server name is empty
// the sending of messages is disabled
Q_SLOT void set_server (QString const& server = QString {});
// initiate a new server host lookup or if the server name is empty
// the sending of messages is disabled, if an interface is specified
// then that interface is used for outgoing datagrams
Q_SLOT void set_server (QString const& server_name, QStringList const& network_interface_names);
// change the server port messages are sent to
Q_SLOT void set_server_port (port_type server_port = 0u);
// change the server port messages are sent to
Q_SLOT void set_TTL (int TTL);
// enable incoming messages
Q_SLOT void enable (bool);

View File

@ -28,13 +28,13 @@ namespace NetworkMessage
{
setVersion (QDataStream::Qt_5_0); // Qt schema version
}
#if QT_VERSION >= 0x050200
#if QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
else if (schema <= 2)
{
setVersion (QDataStream::Qt_5_2); // Qt schema version
}
#endif
#if QT_VERSION >= 0x050400
#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
else if (schema <= 3)
{
setVersion (QDataStream::Qt_5_4); // Qt schema version
@ -73,13 +73,13 @@ namespace NetworkMessage
{
parent->setVersion (QDataStream::Qt_5_0);
}
#if QT_VERSION >= 0x050200
#if QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
else if (schema_ <= 2)
{
parent->setVersion (QDataStream::Qt_5_2);
}
#endif
#if QT_VERSION >= 0x050400
#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
else if (schema_ <= 3)
{
parent->setVersion (QDataStream::Qt_5_4);

View File

@ -540,9 +540,9 @@ namespace NetworkMessage
// increment this if a newer Qt schema is required and add decode
// logic to the Builder and Reader class implementations
#if QT_VERSION >= 0x050400
#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
static quint32 constexpr schema_number {3};
#elif QT_VERSION >= 0x050200
#elif QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
static quint32 constexpr schema_number {2};
#else
// Schema 1 (Qt_5_0) is broken

View File

@ -84,7 +84,7 @@ public:
{
if (!socket_
|| QAbstractSocket::UnconnectedState == socket_->state ()
|| (socket_->socketType () != config_->psk_reporter_tcpip () ? QAbstractSocket::TcpSocket : QAbstractSocket::UdpSocket))
|| (socket_->socketType () != (config_->psk_reporter_tcpip () ? QAbstractSocket::TcpSocket : QAbstractSocket::UdpSocket)))
{
// we need to create the appropriate socket
if (socket_

View File

@ -211,7 +211,7 @@ void WSPRNet::networkReply (QNetworkReply * reply)
}
}
bool WSPRNet::decodeLine (QString const& line, SpotQueue::value_type& query)
bool WSPRNet::decodeLine (QString const& line, SpotQueue::value_type& query) const
{
auto const& rx_match = wspr_re.match (line);
if (rx_match.hasMatch ()) {
@ -271,7 +271,23 @@ bool WSPRNet::decodeLine (QString const& line, SpotQueue::value_type& query)
return true;
}
auto WSPRNet::urlEncodeNoSpot () -> SpotQueue::value_type
QString WSPRNet::encode_mode () const
{
if (m_mode == "WSPR") return "2";
if (m_mode == "WSPR-15") return "15";
if (m_mode == "FST4W")
{
auto tr = static_cast<int> ((TR_period_ / 60.)+.5);
if (2 == tr || 15 == tr)
{
tr += 1; // distinguish from WSPR-2 and WSPR-15
}
return QString::number (tr);
}
return "";
}
auto WSPRNet::urlEncodeNoSpot () const -> SpotQueue::value_type
{
SpotQueue::value_type query;
query.addQueryItem ("function", "wsprstat");
@ -282,28 +298,18 @@ auto WSPRNet::urlEncodeNoSpot () -> SpotQueue::value_type
query.addQueryItem ("tqrg", m_tfreq);
query.addQueryItem ("dbm", m_dbm);
query.addQueryItem ("version", m_vers);
if (m_mode == "WSPR") query.addQueryItem ("mode", "2");
if (m_mode == "WSPR-15") query.addQueryItem ("mode", "15");
if (m_mode == "FST4W")
{
query.addQueryItem ("mode", QString::number (static_cast<int> ((TR_period_ / 60.)+.5)));
}
query.addQueryItem ("mode", encode_mode ());
return query;;
}
auto WSPRNet::urlEncodeSpot (SpotQueue::value_type& query) -> SpotQueue::value_type
auto WSPRNet::urlEncodeSpot (SpotQueue::value_type& query) const -> SpotQueue::value_type
{
query.addQueryItem ("version", m_vers);
query.addQueryItem ("rcall", m_call);
query.addQueryItem ("rgrid", m_grid);
query.addQueryItem ("rqrg", m_rfreq);
if (m_mode == "WSPR") query.addQueryItem ("mode", "2");
if (m_mode == "WSPR-15") query.addQueryItem ("mode", "15");
if (m_mode == "FST4W")
{
query.addQueryItem ("mode", QString::number (static_cast<int> ((TR_period_ / 60.)+.5)));
}
return query;
query.addQueryItem ("mode", encode_mode ());
return query;
}
void WSPRNet::work()

View File

@ -34,9 +34,10 @@ public slots:
void abortOutstandingRequests ();
private:
bool decodeLine (QString const& line, SpotQueue::value_type& query);
SpotQueue::value_type urlEncodeNoSpot ();
SpotQueue::value_type urlEncodeSpot (SpotQueue::value_type& spot);
bool decodeLine (QString const& line, SpotQueue::value_type& query) const;
SpotQueue::value_type urlEncodeNoSpot () const;
SpotQueue::value_type urlEncodeSpot (SpotQueue::value_type& spot) const;
QString encode_mode () const;
QNetworkAccessManager * network_manager_;
QList<QNetworkReply *> m_outstandingRequests;

View File

@ -14,10 +14,16 @@ namespace Radio
double constexpr MHz_factor {1.e6};
int constexpr frequency_precsion {6};
// valid callsign alphabet
QRegularExpression callsign_alphabet_re {R"(^[A-Z0-9/]{3,11}$)"};
// very loose validation - callsign must contain a letter next to
// a number
QRegularExpression valid_callsign_regexp {R"(\d[[:alpha:]]|[[:alpha:]]\d)"};
// standard callsign
QRegularExpression strict_standard_callsign_re {R"(^([A-Z][0-9]?|[0-9][A-Z])[0-9][A-Z]{0,3}$)"};
// suffixes that are often used and should not be interpreted as a
// DXCC Entity prefix used as a suffix
QRegularExpression non_prefix_suffix {R"(\A([0-9AMPQR]|QRP|F[DF]|[AM]M|L[HT]|LGT)\z)"};
@ -112,6 +118,12 @@ namespace Radio
return callsign.contains ('/');
}
bool is_77bit_nonstandard_callsign (QString const& callsign)
{
return callsign.contains (callsign_alphabet_re)
&& !callsign.contains (strict_standard_callsign_re);
}
// split on first '/' and return the larger portion or the whole if
// there is no '/'
QString base_callsign (QString callsign)

View File

@ -53,6 +53,7 @@ namespace Radio
//
bool UDP_EXPORT is_callsign (QString const&);
bool UDP_EXPORT is_compound_callsign (QString const&);
bool is_77bit_nonstandard_callsign (QString const&);
QString UDP_EXPORT base_callsign (QString);
QString UDP_EXPORT effective_prefix (QString);
}

View File

@ -25,10 +25,13 @@ namespace
QFont text_font {"Courier", 10};
QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time
QList<QStandardItem *> make_row (MessageServer::ClientKey const& key, QTime time, qint32 snr, float delta_time
, Frequency frequency, qint32 drift, QString const& callsign
, QString const& grid, qint32 power, bool off_air)
{
auto client_item = new QStandardItem {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())};
client_item->setData (QVariant::fromValue (key));
auto time_item = new QStandardItem {time.toString ("hh:mm")};
time_item->setData (time);
time_item->setTextAlignment (Qt::AlignRight);
@ -60,7 +63,7 @@ namespace
live->setTextAlignment (Qt::AlignHCenter);
QList<QStandardItem *> row {
new QStandardItem {client_id}, time_item, snr_item, dt, freq, dri, gd, pwr, live, new QStandardItem {callsign}};
client_item, time_item, snr_item, dt, freq, dri, gd, pwr, live, new QStandardItem {callsign}};
Q_FOREACH (auto& item, row)
{
item->setEditable (false);
@ -81,7 +84,7 @@ BeaconsModel::BeaconsModel (QObject * parent)
}
}
void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
void BeaconsModel::add_beacon_spot (bool is_new, ClientKey const& key, QTime time, qint32 snr, float delta_time
, Frequency frequency, qint32 drift, QString const& callsign
, QString const& grid, qint32 power, bool off_air)
{
@ -90,7 +93,7 @@ void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime
int target_row {-1};
for (auto row = 0; row < rowCount (); ++row)
{
if (data (index (row, 0)).toString () == client_id)
if (item (row, 0)->data ().value<ClientKey> () == key)
{
auto row_time = item (row, 1)->data ().toTime ();
if (row_time == time
@ -113,19 +116,19 @@ void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime
}
if (target_row >= 0)
{
insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air));
insertRow (target_row + 1, make_row (key, 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));
appendRow (make_row (key, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air));
}
void BeaconsModel::decodes_cleared (QString const& client_id)
void BeaconsModel::decodes_cleared (ClientKey const& key)
{
for (auto row = rowCount () - 1; row >= 0; --row)
{
if (data (index (row, 0)).toString () == client_id)
if (item (row, 0)->data ().value<ClientKey> () == key)
{
removeRow (row);
}

View File

@ -26,13 +26,15 @@ class BeaconsModel
{
Q_OBJECT;
using ClientKey = MessageServer::ClientKey;
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
Q_SLOT void add_beacon_spot (bool is_new, ClientKey const&, 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 decodes_cleared (QString const& client_id);
Q_SLOT void decodes_cleared (ClientKey const&);
};
#endif

View File

@ -27,9 +27,9 @@ namespace
}
}
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id, QObject * parent)
ClientWidget::IdFilterModel::IdFilterModel (ClientKey const& key, QObject * parent)
: QSortFilterProxyModel {parent}
, client_id_ {client_id}
, key_ {key}
, rx_df_ (quint32_max)
{
}
@ -73,7 +73,7 @@ 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_;
return sourceModel ()->data (source_index_col0).value<ClientKey> () == key_;
}
void ClientWidget::IdFilterModel::de_call (QString const& call)
@ -106,9 +106,9 @@ void ClientWidget::IdFilterModel::rx_df (quint32 df)
namespace
{
QString make_title (QString const& id, QString const& version, QString const& revision)
QString make_title (MessageServer::ClientKey const& key, QString const& version, QString const& revision)
{
QString title {id};
QString title {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())};
if (version.size ())
{
title += QString {" v%1"}.arg (version);
@ -122,14 +122,14 @@ namespace
}
ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
, QString const& id, QString const& version, QString const& revision
, ClientKey const& key, QString const& version, QString const& revision
, QListWidget const * calls_of_interest, QWidget * parent)
: QDockWidget {make_title (id, version, revision), parent}
, id_ {id}
: QDockWidget {make_title (key, version, revision), parent}
, key_ {key}
, done_ {false}
, calls_of_interest_ {calls_of_interest}
, decodes_proxy_model_ {id}
, beacons_proxy_model_ {id}
, decodes_proxy_model_ {key}
, beacons_proxy_model_ {key}
, 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}}
@ -209,56 +209,56 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
horizontal_layout_->addLayout (subform3_layout_);
connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) {
Q_EMIT do_free_text (id_, text, false);
Q_EMIT do_free_text (key_, text, false);
});
connect (message_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT do_free_text (id_, message_line_edit_->text (), true);
Q_EMIT do_free_text (key_, message_line_edit_->text (), true);
});
connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT location (id_, grid_line_edit_->text ());
Q_EMIT location (key_, grid_line_edit_->text ());
});
connect (configuration_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT switch_configuration (id_, configuration_line_edit_->text ());
Q_EMIT switch_configuration (key_, 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 ()
Q_EMIT configure (key_, mode_line_edit_->text (), quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, false);
});
connect (frequency_tolerance_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty;
auto f = frequency_tolerance_spin_box_->specialValueText ().size () ? quint32_max : i;
Q_EMIT configure (id_, empty, f, empty, fast_mode ()
Q_EMIT configure (key_, 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 ()
Q_EMIT configure (key_, 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
Q_EMIT configure (key_, empty, quint32_max, empty, Qt::Checked == state
, quint32_max, quint32_max, empty, empty, false);
});
connect (tr_period_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
, i, quint32_max, empty, empty, false);
});
connect (rx_df_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
Q_EMIT configure (key_, 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 ()
Q_EMIT configure (key_, 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 ()
Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, dx_grid_line_edit_->text (), false);
});
@ -289,14 +289,14 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
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 ()
Q_EMIT configure (key_, 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);
Q_EMIT do_halt_tx (key_, true);
});
connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
Q_EMIT do_halt_tx (id_, false);
Q_EMIT do_halt_tx (key_, false);
});
content_layout_->addWidget (control_button_box_);
@ -318,13 +318,13 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
// connect context menu actions
connect (erase_action_, &QAction::triggered, [this] (bool /*checked*/) {
Q_EMIT do_clear_decodes (id_);
Q_EMIT do_clear_decodes (key_);
});
connect (erase_rx_frequency_action_, &QAction::triggered, [this] (bool /*checked*/) {
Q_EMIT do_clear_decodes (id_, 1);
Q_EMIT do_clear_decodes (key_, 1);
});
connect (erase_both_action_, &QAction::triggered, [this] (bool /*checked*/) {
Q_EMIT do_clear_decodes (id_, 2);
Q_EMIT do_clear_decodes (key_, 2);
});
// connect up table view signals
@ -335,7 +335,7 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
// 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});
Q_EMIT highlight_callsign (key_, calls_of_interest_->item (row)->text (), QColor {Qt::blue}, QColor {Qt::yellow});
}
}
@ -349,7 +349,7 @@ void ClientWidget::closeEvent (QCloseEvent *e)
{
if (!done_)
{
Q_EMIT do_close (id_);
Q_EMIT do_close (key_);
e->ignore (); // defer closure until client actually closes
}
else
@ -363,7 +363,7 @@ 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 ());
Q_EMIT highlight_callsign (key_, calls_of_interest_->item (row)->text ());
}
}
@ -395,7 +395,7 @@ namespace
}
}
void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
void ClientWidget::update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
, QString const& de_call, QString const& de_grid, QString const& dx_grid
@ -403,7 +403,7 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const&
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name)
{
if (id == id_)
if (key == key_)
{
fast_mode_check_box_->setChecked (fast_mode);
decodes_proxy_model_.de_call (de_call);
@ -447,11 +447,11 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const&
}
}
void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
void ClientWidget::decode_added (bool /*is_new*/, ClientKey const& key, 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_)
if (key == key_ && !columns_resized_)
{
decodes_stack_->setCurrentIndex (0);
decodes_table_view_->resizeColumnsToContents ();
@ -460,12 +460,12 @@ void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTim
decodes_table_view_->scrollToBottom ();
}
void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
void ClientWidget::beacon_spot_added (bool /*is_new*/, ClientKey const& key, 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_)
if (key == key_ && !columns_resized_)
{
decodes_stack_->setCurrentIndex (1);
beacons_table_view_->resizeColumnsToContents ();
@ -474,9 +474,9 @@ void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id,
beacons_table_view_->scrollToBottom ();
}
void ClientWidget::decodes_cleared (QString const& client_id)
void ClientWidget::decodes_cleared (ClientKey const& key)
{
if (client_id == id_)
if (key == key_)
{
columns_resized_ = false;
}

View File

@ -35,42 +35,44 @@ class ClientWidget
{
Q_OBJECT;
using ClientKey = MessageServer::ClientKey;
public:
explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
, QString const& id, QString const& version, QString const& revision
, ClientKey const& key, QString const& version, QString const& revision
, QListWidget const * calls_of_interest, QWidget * parent = nullptr);
void dispose ();
~ClientWidget ();
bool fast_mode () const;
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
Q_SLOT void update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled
, 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, 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
Q_SLOT void decode_added (bool is_new, ClientKey const& key, 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
Q_SLOT void beacon_spot_added (bool is_new, ClientKey const& key, 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 decodes_cleared (QString const& client_id);
Q_SLOT void decodes_cleared (ClientKey const& key);
Q_SIGNAL void do_clear_decodes (QString const& id, quint8 window = 0);
Q_SIGNAL void do_close (QString const& id);
Q_SIGNAL void do_clear_decodes (ClientKey const& key, quint8 window = 0);
Q_SIGNAL void do_close (ClientKey const& key);
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
Q_SIGNAL void do_halt_tx (ClientKey const& key, bool auto_only);
Q_SIGNAL void do_free_text (ClientKey const& key, QString const& text, bool);
Q_SIGNAL void location (ClientKey const& key, QString const& text);
Q_SIGNAL void highlight_callsign (ClientKey const& key, QString const& call
, 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
Q_SIGNAL void switch_configuration (ClientKey const& key, QString const& configuration_name);
Q_SIGNAL void configure (ClientKey const& key, 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);
@ -79,7 +81,7 @@ private:
: public QSortFilterProxyModel
{
public:
IdFilterModel (QString const& client_id, QObject * = nullptr);
IdFilterModel (ClientKey const& key, QObject * = nullptr);
void de_call (QString const&);
void rx_df (quint32);
@ -88,7 +90,7 @@ private:
private:
bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override;
QString client_id_;
ClientKey key_;
QString call_;
QRegularExpression base_call_re_;
quint32 rx_df_;
@ -96,7 +98,7 @@ private:
void closeEvent (QCloseEvent *) override;
QString id_;
ClientKey key_;
bool done_;
QListWidget const * calls_of_interest_;
IdFilterModel decodes_proxy_model_;

View File

@ -2,6 +2,7 @@
#include <QStandardItem>
#include <QModelIndex>
#include <QVariant>
#include <QTime>
#include <QString>
#include <QFont>
@ -33,10 +34,13 @@ namespace
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)
QList<QStandardItem *> make_row (MessageServer::ClientKey const& key, 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 client_item = new QStandardItem {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())};
client_item->setData (QVariant::fromValue (key));
auto time_item = new QStandardItem {time.toString (is_fast || "~" == mode ? "hh:mm:ss" : "hh:mm")};
time_item->setData (time);
time_item->setTextAlignment (Qt::AlignRight);
@ -63,7 +67,7 @@ namespace
live->setTextAlignment (Qt::AlignHCenter);
QList<QStandardItem *> row {
new QStandardItem {client_id}, time_item, snr_item, dt, df, md, confidence, live, new QStandardItem {message}};
client_item, time_item, snr_item, dt, df, md, confidence, live, new QStandardItem {message}};
Q_FOREACH (auto& item, row)
{
item->setEditable (false);
@ -84,7 +88,7 @@ DecodesModel::DecodesModel (QObject * parent)
}
}
void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
void DecodesModel::add_decode (bool is_new, ClientKey const& key, 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)
{
@ -93,7 +97,7 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time
int target_row {-1};
for (auto row = 0; row < rowCount (); ++row)
{
if (data (index (row, 0)).toString () == client_id)
if (item (row, 0)->data ().value<ClientKey> () == key)
{
auto row_time = item (row, 1)->data ().toTime ();
if (row_time == time
@ -115,21 +119,21 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time
}
if (target_row >= 0)
{
insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, delta_frequency, mode
insertRow (target_row + 1, make_row (key, 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
appendRow (make_row (key, time, snr, delta_time, delta_frequency, mode, message, low_confidence
, off_air, is_fast));
}
void DecodesModel::decodes_cleared (QString const& client_id)
void DecodesModel::decodes_cleared (ClientKey const& key)
{
for (auto row = rowCount () - 1; row >= 0; --row)
{
if (data (index (row, 0)).toString () == client_id)
if (item (row, 0)->data ().value<ClientKey> () == key)
{
removeRow (row);
}
@ -139,7 +143,7 @@ void DecodesModel::decodes_cleared (QString const& client_id)
void DecodesModel::do_reply (QModelIndex const& source, quint8 modifiers)
{
auto row = source.row ();
Q_EMIT reply (data (index (row, 0)).toString ()
Q_EMIT reply (item (row, 0)->data ().value<ClientKey> ()
, item (row, 1)->data ().toTime ()
, item (row, 2)->data ().toInt ()
, item (row, 3)->data ().toFloat ()

View File

@ -5,8 +5,6 @@
#include "MessageServer.hpp"
using Frequency = MessageServer::Frequency;
class QTime;
class QString;
class QModelIndex;
@ -28,16 +26,18 @@ class DecodesModel
{
Q_OBJECT;
using ClientKey = MessageServer::ClientKey;
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
Q_SLOT void add_decode (bool is_new, ClientKey const&, QTime, 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 decodes_cleared (QString const& client_id);
Q_SLOT void decodes_cleared (ClientKey const&);
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
Q_SIGNAL void reply (ClientKey const&, QTime, qint32 snr, float delta_time, quint32 delta_frequency
, QString const& mode, QString const& message, bool low_confidence, quint8 modifiers);
};

View File

@ -2,6 +2,8 @@
#include <QtWidgets>
#include <QDateTime>
#include <QNetworkInterface>
#include <QSet>
#include "DecodesModel.hpp"
#include "BeaconsModel.hpp"
@ -37,8 +39,10 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
, 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}
, port_spin_box_ {new QSpinBox {this}}
, multicast_group_line_edit_ {new QLineEdit {this}}
, network_interfaces_combo_box_ {new CheckableItemComboBox {this}}
, log_table_view_ {new QTableView {this}}
, 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}}
@ -68,16 +72,71 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
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 ());
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 ("Port number:"), port_spin_box_);
group_box_layout->addRow (tr ("Multicast Group (blank for unicast server):"), multicast_group_line_edit_);
group_box_layout->addRow (tr ("Network interfaces:"), network_interfaces_combo_box_);
int row;
QFormLayout::ItemRole role;
group_box_layout->getWidgetPosition (network_interfaces_combo_box_, &row, &role);
Q_ASSERT (row >= 0);
network_interfaces_form_label_widget_ = static_cast<QLabel *> (group_box_layout->itemAt (row, QFormLayout::LabelRole)->widget ());
network_interfaces_form_label_widget_->hide ();
network_interfaces_form_label_widget_->buddy ()->hide ();
connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this] {
if (multicast_group_line_edit_->text ().size ())
{
network_interfaces_form_label_widget_->show ();
network_interfaces_form_label_widget_->buddy ()->show ();
}
else
{
network_interfaces_form_label_widget_->hide ();
network_interfaces_form_label_widget_->buddy ()->hide ();
}
});
auto group_box = new QGroupBox {tr ("Server Details")};
group_box->setLayout (group_box_layout);
central_layout->addWidget (group_box);
// populate network interface list
for (auto const& net_if : QNetworkInterface::allInterfaces ())
{
auto flags = QNetworkInterface::IsRunning | QNetworkInterface::CanMulticast;
if ((net_if.flags () & flags) == flags)
{
if (net_if.flags () & QNetworkInterface::IsLoopBack)
{
loopback_interface_name_ = net_if.name ();
}
auto item = network_interfaces_combo_box_->addCheckItem (net_if.humanReadableName ()
, net_if.name ()
, Qt::Unchecked);
auto tip = QString {"name(index): %1(%2) - %3"}
.arg (net_if.name ()).arg (net_if.index ())
.arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down");
auto hw_addr = net_if.hardwareAddress ();
if (hw_addr.size ())
{
tip += QString {"\nhw: %1"}.arg (net_if.hardwareAddress ());
}
auto aes = net_if.addressEntries ();
if (aes.size ())
{
tip += "\naddresses:";
for (auto const& ae : aes)
{
tip += QString {"\n ip: %1/%2"}.arg (ae.ip ().toString ()).arg (ae.prefixLength ());
}
}
item->setToolTip (tip);
}
}
connect (network_interfaces_combo_box_, &QComboBox::currentTextChanged, this, &MessageAggregatorMainWindow::validate_network_interfaces);
validate_network_interfaces (QString {});
log_table_view_->setModel (log_);
log_table_view_->verticalHeader ()->hide ();
central_layout->addWidget (log_table_view_);
@ -184,30 +243,52 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
connect (server_, &MessageServer::client_closed, this, &MessageAggregatorMainWindow::remove_client);
connect (server_, &MessageServer::client_closed, decodes_model_, &DecodesModel::decodes_cleared);
connect (server_, &MessageServer::client_closed, beacons_model_, &BeaconsModel::decodes_cleared);
connect (server_, &MessageServer::decode, [this] (bool is_new, QString const& id, QTime time
connect (server_, &MessageServer::decode, [this] (bool is_new, ClientKey const& key, 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 ());});
decodes_model_->add_decode (is_new, key, time, snr, delta_time
, delta_frequency, mode, message
, low_confidence, off_air
, dock_widgets_[key]->fast_mode ());
});
connect (server_, &MessageServer::WSPR_decode, beacons_model_, &BeaconsModel::add_beacon_spot);
connect (server_, &MessageServer::decodes_cleared, decodes_model_, &DecodesModel::decodes_cleared);
connect (server_, &MessageServer::decodes_cleared, beacons_model_, &BeaconsModel::decodes_cleared);
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 ()});
});
connect (port_spin_box_, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged)
, [this] (int /*port*/) {restart_server ();});
connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this] () {restart_server ();});
connect (network_interfaces_combo_box_, &QComboBox::currentTextChanged, [this] () {restart_server ();});
port_spin_box->setValue (2237); // start up in unicast mode
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
void MessageAggregatorMainWindow::restart_server ()
{
QSet<QString> net_ifs;
if (network_interfaces_combo_box_->isVisible ())
{
auto model = static_cast<QStandardItemModel *> (network_interfaces_combo_box_->model ());
for (int row = 0; row < model->rowCount (); ++row)
{
if (Qt::Checked == model->item (row)->checkState ())
{
net_ifs << model->item (row)->data ().toString ();
}
}
}
server_->start (port_spin_box_->value ()
, QHostAddress {multicast_group_line_edit_->text ()}
, net_ifs);
}
void MessageAggregatorMainWindow::log_qso (ClientKey const& /*key*/, 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
@ -240,9 +321,9 @@ void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time
log_table_view_->scrollToBottom ();
}
void MessageAggregatorMainWindow::add_client (QString const& id, QString const& version, QString const& revision)
void MessageAggregatorMainWindow::add_client (ClientKey const& key, QString const& version, QString const& revision)
{
auto dock = new ClientWidget {decodes_model_, beacons_model_, id, version, revision, calls_of_interest_, this};
auto dock = new ClientWidget {decodes_model_, beacons_model_, key, version, revision, calls_of_interest_, this};
dock->setAttribute (Qt::WA_DeleteOnClose);
auto view_action = dock->toggleViewAction ();
view_action->setEnabled (true);
@ -262,13 +343,13 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const&
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
dock_widgets_[key] = dock;
server_->replay (key); // request decodes and status
}
void MessageAggregatorMainWindow::remove_client (QString const& id)
void MessageAggregatorMainWindow::remove_client (ClientKey const& key)
{
auto iter = dock_widgets_.find (id);
auto iter = dock_widgets_.find (key);
if (iter != std::end (dock_widgets_))
{
(*iter)->dispose ();
@ -287,9 +368,35 @@ MessageAggregatorMainWindow::~MessageAggregatorMainWindow ()
void MessageAggregatorMainWindow::change_highlighting (QString const& call, QColor const& bg, QColor const& fg
, bool last_only)
{
for (auto id : dock_widgets_.keys ())
for (auto key : dock_widgets_.keys ())
{
server_->highlight_callsign (id, call, bg, fg, last_only);
server_->highlight_callsign (key, call, bg, fg, last_only);
}
}
void MessageAggregatorMainWindow::validate_network_interfaces (QString const& /*text*/)
{
auto model = static_cast<QStandardItemModel *> (network_interfaces_combo_box_->model ());
bool has_checked {false};
int loopback_row {-1};
for (int row = 0; row < model->rowCount (); ++row)
{
if (model->item (row)->data ().toString () == loopback_interface_name_)
{
loopback_row = row;
}
else if (Qt::Checked == model->item (row)->checkState ())
{
has_checked = true;
}
}
if (loopback_row >= 0)
{
if (!has_checked)
{
model->item (loopback_row)->setCheckState (Qt::Checked);
}
model->item (loopback_row)->setEnabled (has_checked);
}
}

View File

@ -6,6 +6,7 @@
#include <QString>
#include "MessageServer.hpp"
#include "widgets/CheckableItemComboBox.hpp"
class QDateTime;
class QStandardItemModel;
@ -16,6 +17,8 @@ class QLineEdit;
class QTableView;
class ClientWidget;
class QListWidget;
class QLabel;
class QSpinBox;
using Frequency = MessageServer::Frequency;
@ -24,11 +27,13 @@ class MessageAggregatorMainWindow
{
Q_OBJECT;
using ClientKey = MessageServer::ClientKey;
public:
MessageAggregatorMainWindow ();
~MessageAggregatorMainWindow ();
Q_SLOT void log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call, QString const& dx_grid
Q_SLOT void log_qso (ClientKey const&, 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
@ -36,13 +41,15 @@ public:
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode);
private:
void add_client (QString const& id, QString const& version, QString const& revision);
void remove_client (QString const& id);
void restart_server ();
void add_client (ClientKey const&, QString const& version, QString const& revision);
void remove_client (ClientKey const&);
void change_highlighting (QString const& call, QColor const& bg = QColor {}, QColor const& fg = QColor {},
bool last_only = false);
Q_SLOT void validate_network_interfaces (QString const&);
// maps client id to widgets
using ClientsDictionary = QHash<QString, ClientWidget *>;
using ClientsDictionary = QHash<ClientKey, ClientWidget *>;
ClientsDictionary dock_widgets_;
QStandardItemModel * log_;
@ -50,7 +57,11 @@ private:
DecodesModel * decodes_model_;
BeaconsModel * beacons_model_;
MessageServer * server_;
QSpinBox * port_spin_box_;
QLineEdit * multicast_group_line_edit_;
CheckableItemComboBox * network_interfaces_combo_box_;
QString loopback_interface_name_;
QLabel * network_interfaces_form_label_widget_;
QTableView * log_table_view_;
QListWidget * calls_of_interest_;
QAction * add_call_of_interest_action_;

View File

@ -5,7 +5,6 @@
#include <QNetworkInterface>
#include <QUdpSocket>
#include <QString>
#include <QTimer>
#include <QHash>
@ -32,7 +31,6 @@ public:
: self_ {self}
, version_ {version}
, revision_ {revision}
, port_ {0u}
, clock_ {new QTimer {this}}
{
// register the required types with Qt
@ -78,15 +76,14 @@ public:
MessageServer * self_;
QString version_;
QString revision_;
port_type port_;
QHostAddress multicast_group_address_;
QSet<QString> network_interfaces_;
static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint;
struct Client
{
Client () = default;
Client (QHostAddress const& sender_address, port_type const& sender_port)
: sender_address_ {sender_address}
, sender_port_ {sender_port}
Client (port_type const& sender_port)
: sender_port_ {sender_port}
, negotiated_schema_number_ {2} // not 1 because it's broken
, last_activity_ {QDateTime::currentDateTime ()}
{
@ -94,12 +91,11 @@ public:
Client (Client const&) = default;
Client& operator= (Client const&) = default;
QHostAddress sender_address_;
port_type sender_port_;
quint32 negotiated_schema_number_;
QDateTime last_activity_;
};
QHash<QString, Client> clients_; // maps id to Client
QHash<ClientKey, Client> clients_; // maps id to Client
QTimer * clock_;
};
@ -109,56 +105,39 @@ MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
void MessageServer::impl::leave_multicast_group ()
{
if (!multicast_group_address_.isNull () && BoundState == state ()
#if QT_VERSION >= 0x050600
&& multicast_group_address_.isMulticast ()
#endif
)
if (BoundState == state () && is_multicast_address (multicast_group_address_))
{
for (auto const& interface : QNetworkInterface::allInterfaces ())
for (auto const& if_name : network_interfaces_)
{
if (QNetworkInterface::CanMulticast & interface.flags ())
{
leaveMulticastGroup (multicast_group_address_, interface);
}
leaveMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
}
}
}
void MessageServer::impl::join_multicast_group ()
{
if (BoundState == state ()
&& !multicast_group_address_.isNull ()
#if QT_VERSION >= 0x050600
&& multicast_group_address_.isMulticast ()
#endif
)
if (BoundState == state () && is_multicast_address (multicast_group_address_))
{
auto mcast_iface = multicastInterface ();
if (IPv4Protocol == multicast_group_address_.protocol ()
&& IPv4Protocol != localAddress ().protocol ())
if (network_interfaces_.size ())
{
close ();
bind (QHostAddress::AnyIPv4, port_, bind_mode_);
}
bool joined {false};
for (auto const& interface : QNetworkInterface::allInterfaces ())
{
if (QNetworkInterface::CanMulticast & interface.flags ())
for (auto const& if_name : network_interfaces_)
{
// Windows requires outgoing interface to match
// interface to be joined while joining, at least for
// IPv4 it seems to
setMulticastInterface (interface);
joined |= joinMulticastGroup (multicast_group_address_, interface);
joinMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
}
}
if (!joined)
else
{
multicast_group_address_.clear ();
// find the loop-back interface and join on that
for (auto const& net_if : QNetworkInterface::allInterfaces ())
{
auto flags = QNetworkInterface::IsUp | QNetworkInterface::IsLoopBack | QNetworkInterface::CanMulticast;
if ((net_if.flags () & flags) == flags)
{
joinMulticastGroup (multicast_group_address_, net_if);
break;
}
}
}
setMulticastInterface (mcast_iface);
}
}
@ -189,9 +168,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
auto id = in.id ();
if (OK == check_status (in))
{
if (!clients_.contains (id))
auto client_key = ClientKey {sender, id};
if (!clients_.contains (client_key))
{
auto& client = (clients_[id] = {sender, sender_port});
auto& client = (clients_[client_key] = {sender_port});
QByteArray client_version;
QByteArray client_revision;
@ -212,7 +192,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
<< version_.toUtf8 () << revision_.toUtf8 ();
if (impl::OK == check_status (hb))
{
writeDatagram (message, client.sender_address_, client.sender_port_);
writeDatagram (message, client_key.first, sender_port);
}
else
{
@ -222,10 +202,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
// we don't care if this fails to read
in >> client_version >> client_revision;
}
Q_EMIT self_->client_opened (id, QString::fromUtf8 (client_version),
Q_EMIT self_->client_opened (client_key, QString::fromUtf8 (client_version),
QString::fromUtf8 (client_revision));
}
clients_[id].last_activity_ = QDateTime::currentDateTime ();
clients_[client_key].last_activity_ = QDateTime::currentDateTime ();
//
// message format is described in NetworkMessage.hpp
@ -237,7 +217,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
break;
case NetworkMessage::Clear:
Q_EMIT self_->decodes_cleared (id);
Q_EMIT self_->decodes_cleared (client_key);
break;
case NetworkMessage::Status:
@ -268,7 +248,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
>> 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)
Q_EMIT self_->status_update (client_key, f, QString::fromUtf8 (mode)
, QString::fromUtf8 (dx_call)
, QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)
, tx_enabled, transmitting, decoding, rx_df, tx_df
, QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
@ -296,7 +277,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
>> message >> low_confidence >> off_air;
if (check_status (in) != Fail)
{
Q_EMIT self_->decode (is_new, id, time, snr, delta_time, delta_frequency
Q_EMIT self_->decode (is_new, client_key, time, snr, delta_time, delta_frequency
, QString::fromUtf8 (mode), QString::fromUtf8 (message)
, low_confidence, off_air);
}
@ -320,7 +301,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
>> off_air;
if (check_status (in) != Fail)
{
Q_EMIT self_->WSPR_decode (is_new, id, time, snr, delta_time, frequency, drift
Q_EMIT self_->WSPR_decode (is_new, client_key, time, snr, delta_time, frequency, drift
, QString::fromUtf8 (callsign), QString::fromUtf8 (grid)
, power, off_air);
}
@ -351,8 +332,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
>> exchange_sent >> exchange_rcvd >> prop_mode;
if (check_status (in) != Fail)
{
Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
, dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent)
Q_EMIT self_->qso_logged (client_key, time_off, QString::fromUtf8 (dx_call)
, QString::fromUtf8 (dx_grid)
, dial_frequency, QString::fromUtf8 (mode)
, QString::fromUtf8 (report_sent)
, QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power)
, QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on
, QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call)
@ -363,8 +346,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
break;
case NetworkMessage::Close:
Q_EMIT self_->client_closed (id);
clients_.remove (id);
Q_EMIT self_->client_closed (client_key);
clients_.remove (client_key);
break;
case NetworkMessage::LoggedADIF:
@ -373,7 +356,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
in >> ADIF;
if (check_status (in) != Fail)
{
Q_EMIT self_->logged_ADIF (id, ADIF);
Q_EMIT self_->logged_ADIF (client_key, ADIF);
}
}
break;
@ -406,7 +389,7 @@ void MessageServer::impl::tick ()
{
if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse))
{
Q_EMIT self_->clear_decodes (iter.key ());
Q_EMIT self_->decodes_cleared (iter.key ());
Q_EMIT self_->client_closed (iter.key ());
iter = clients_.erase (iter); // safe while iterating as doesn't rehash
}
@ -448,152 +431,162 @@ MessageServer::MessageServer (QObject * parent, QString const& version, QString
{
}
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address)
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address
, QSet<QString> const& network_interface_names)
{
if (port != m_->port_
|| multicast_group_address != m_->multicast_group_address_)
// qDebug () << "MessageServer::start port:" << port << "multicast addr:" << multicast_group_address.toString () << "network interfaces:" << network_interface_names;
if (port != m_->localPort ()
|| multicast_group_address != m_->multicast_group_address_
|| network_interface_names != m_->network_interfaces_)
{
m_->leave_multicast_group ();
if (impl::BoundState == m_->state ())
if (impl::UnconnectedState != m_->state ())
{
m_->close ();
}
m_->multicast_group_address_ = multicast_group_address;
auto address = m_->multicast_group_address_.isNull ()
|| impl::IPv4Protocol != m_->multicast_group_address_.protocol () ? QHostAddress::Any : QHostAddress::AnyIPv4;
if (port && m_->bind (address, port, m_->bind_mode_))
if (!(multicast_group_address.isNull () || is_multicast_address (multicast_group_address)))
{
m_->port_ = port;
m_->join_multicast_group ();
Q_EMIT error ("Invalid multicast group address");
}
else if (is_MAC_ambiguous_multicast_address (multicast_group_address))
{
Q_EMIT error ("MAC-ambiguous IPv4 multicast group address not supported");
}
else
{
m_->port_ = 0;
m_->multicast_group_address_ = multicast_group_address;
m_->network_interfaces_ = network_interface_names;
QHostAddress local_addr {is_multicast_address (multicast_group_address)
&& impl::IPv4Protocol == multicast_group_address.protocol () ? QHostAddress::AnyIPv4 : QHostAddress::Any};
if (port && m_->bind (local_addr, port, m_->bind_mode_))
{
m_->join_multicast_group ();
}
}
}
}
void MessageServer::clear_decodes (QString const& id, quint8 window)
void MessageServer::clear_decodes (ClientKey const& key, quint8 window)
{
auto iter = m_->clients_.find (id);
auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Clear, id, (*iter).negotiated_schema_number_};
NetworkMessage::Builder out {&message, NetworkMessage::Clear, key.second, (*iter).negotiated_schema_number_};
out << window;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}
void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time
void MessageServer::reply (ClientKey const& key, QTime time, qint32 snr, float delta_time
, quint32 delta_frequency, QString const& mode
, QString const& message_text, bool low_confidence, quint8 modifiers)
{
auto iter = m_->clients_.find (id);
auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Reply, id, (*iter).negotiated_schema_number_};
NetworkMessage::Builder out {&message, NetworkMessage::Reply, key.second, (*iter).negotiated_schema_number_};
out << time << snr << delta_time << delta_frequency << mode.toUtf8 ()
<< message_text.toUtf8 () << low_confidence << modifiers;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}
void MessageServer::replay (QString const& id)
void MessageServer::replay (ClientKey const& key)
{
auto iter = m_->clients_.find (id);
auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Replay, id, (*iter).negotiated_schema_number_};
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
NetworkMessage::Builder out {&message, NetworkMessage::Replay, key.second, (*iter).negotiated_schema_number_};
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}
void MessageServer::close (QString const& id)
void MessageServer::close (ClientKey const& key)
{
auto iter = m_->clients_.find (id);
auto iter = m_->clients_.find (key);
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_);
NetworkMessage::Builder out {&message, NetworkMessage::Close, key.second, (*iter).negotiated_schema_number_};
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}
void MessageServer::halt_tx (QString const& id, bool auto_only)
void MessageServer::halt_tx (ClientKey const& key, bool auto_only)
{
auto iter = m_->clients_.find (id);
auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, id, (*iter).negotiated_schema_number_};
NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, key.second, (*iter).negotiated_schema_number_};
out << auto_only;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}
void MessageServer::free_text (QString const& id, QString const& text, bool send)
void MessageServer::free_text (ClientKey const& key, QString const& text, bool send)
{
auto iter = m_->clients_.find (id);
auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::FreeText, id, (*iter).negotiated_schema_number_};
NetworkMessage::Builder out {&message, NetworkMessage::FreeText, key.second, (*iter).negotiated_schema_number_};
out << text.toUtf8 () << send;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}
void MessageServer::location (QString const& id, QString const& loc)
void MessageServer::location (ClientKey const& key, QString const& loc)
{
auto iter = m_->clients_.find (id);
auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Location, id, (*iter).negotiated_schema_number_};
NetworkMessage::Builder out {&message, NetworkMessage::Location, key.second, (*iter).negotiated_schema_number_};
out << loc.toUtf8 ();
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}
void MessageServer::highlight_callsign (QString const& id, QString const& callsign
void MessageServer::highlight_callsign (ClientKey const& key, QString const& callsign
, QColor const& bg, QColor const& fg, bool last_only)
{
auto iter = m_->clients_.find (id);
auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, id, (*iter).negotiated_schema_number_};
NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, key.second, (*iter).negotiated_schema_number_};
out << callsign.toUtf8 () << bg << fg << last_only;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}
void MessageServer::switch_configuration (QString const& id, QString const& configuration_name)
void MessageServer::switch_configuration (ClientKey const& key, QString const& configuration_name)
{
auto iter = m_->clients_.find (id);
auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, id, (*iter).negotiated_schema_number_};
NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, key.second, (*iter).negotiated_schema_number_};
out << configuration_name.toUtf8 ();
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}
void MessageServer::configure (QString const& id, QString const& mode, quint32 frequency_tolerance
void MessageServer::configure (ClientKey const& key, 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);
auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Configure, id, (*iter).negotiated_schema_number_};
NetworkMessage::Builder out {&message, NetworkMessage::Configure, key.second, (*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_);
m_->send_message (out, message, key.first, (*iter).sender_port_);
}
}

View File

@ -2,6 +2,9 @@
#define MESSAGE_SERVER_HPP__
#include <QObject>
#include <QPair>
#include <QString>
#include <QSet>
#include <QTime>
#include <QDateTime>
#include <QHostAddress>
@ -31,6 +34,7 @@ class UDP_EXPORT MessageServer
public:
using port_type = quint16;
using Frequency = Radio::Frequency;
using ClientKey = QPair<QHostAddress, QString>;
MessageServer (QObject * parent = nullptr,
QString const& version = QString {}, QString const& revision = QString {});
@ -38,77 +42,77 @@ public:
// start or restart the server, if the multicast_group_address
// argument is given it is assumed to be a multicast group address
// which the server will join
Q_SLOT void start (port_type port,
QHostAddress const& multicast_group_address = QHostAddress {});
Q_SLOT void start (port_type port
, QHostAddress const& multicast_group_address = QHostAddress {}
, QSet<QString> const& network_interface_names = QSet<QString> {});
// ask the client to clear one or both of the decode windows
Q_SLOT void clear_decodes (QString const& id, quint8 window = 0);
Q_SLOT void clear_decodes (ClientKey const&, quint8 window = 0);
// ask the client with identification 'id' to make the same action
// as a double click on the decode would
//
// note that the client is not obliged to take any action and only
// takes any action if the decode is present and is a CQ or QRZ message
Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
Q_SLOT void reply (ClientKey const&, 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 to close down gracefully
Q_SLOT void close (ClientKey const&);
// ask the client with identification 'id' to replay all decodes
Q_SLOT void replay (QString const& id);
// ask the client to replay all decodes
Q_SLOT void replay (ClientKey const&);
// ask the client with identification 'id' to halt transmitting
// auto_only just disables auto Tx, otherwise halt is immediate
Q_SLOT void halt_tx (QString const& id, bool auto_only);
// ask the client to halt transmitting auto_only just disables auto
// Tx, otherwise halt is immediate
Q_SLOT void halt_tx (ClientKey const&, bool auto_only);
// ask the client with identification 'id' to set the free text
// message and optionally send it ASAP
Q_SLOT void free_text (QString const& id, QString const& text, bool send);
// ask the client to set the free text message and optionally send
// it ASAP
Q_SLOT void free_text (ClientKey const&, QString const& text, bool send);
// ask the client with identification 'id' to set the location provided
Q_SLOT void location (QString const& id, QString const& location);
// ask the client to set the location provided
Q_SLOT void location (ClientKey const&, QString const& location);
// ask the client with identification 'id' to highlight the callsign
// specified with the given colors
Q_SLOT void highlight_callsign (QString const& id, QString const& callsign
// ask the client to highlight the callsign specified with the given
// colors
Q_SLOT void highlight_callsign (ClientKey const&, QString const& callsign
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
, bool last_only = false);
// 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 to switch to configuration 'configuration_name'
Q_SLOT void switch_configuration (ClientKey const&, 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
// ask the client to change configuration
Q_SLOT void configure (ClientKey const&, 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
Q_SIGNAL void client_opened (ClientKey const&, QString const& version, QString const& revision);
Q_SIGNAL void status_update (ClientKey const&, Frequency, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled
, 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, 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
Q_SIGNAL void client_closed (ClientKey const&);
Q_SIGNAL void decode (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time
, quint32 delta_frequency, QString const& mode, QString const& message
, bool low_confidence, bool off_air);
Q_SIGNAL void WSPR_decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time, Frequency
Q_SIGNAL void WSPR_decode (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time, Frequency
, qint32 drift, QString const& callsign, QString const& grid, qint32 power
, bool off_air);
Q_SIGNAL void qso_logged (QString const& id, QDateTime time_off, QString const& dx_call, QString const& dx_grid
Q_SIGNAL void qso_logged (ClientKey const&, 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
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode);
Q_SIGNAL void decodes_cleared (QString const& id);
Q_SIGNAL void logged_ADIF (QString const& id, QByteArray const& ADIF);
Q_SIGNAL void decodes_cleared (ClientKey const&);
Q_SIGNAL void logged_ADIF (ClientKey const&, QByteArray const& ADIF);
// this signal is emitted when a network error occurs
Q_SIGNAL void error (QString const&) const;
@ -118,4 +122,6 @@ private:
pimpl<impl> m_;
};
Q_DECLARE_METATYPE (MessageServer::ClientKey);
#endif

View File

@ -17,9 +17,14 @@
#include <iostream>
#include <exception>
#include <cstdlib>
#include <QCoreApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QString>
#include <QStringList>
#include <QNetworkInterface>
#include <QDateTime>
#include <QTime>
#include <QHash>
@ -38,15 +43,17 @@ class Client
{
Q_OBJECT
using ClientKey = MessageServer::ClientKey;
public:
explicit Client (QString const& id, QObject * parent = nullptr)
explicit Client (ClientKey const& key, QObject * parent = nullptr)
: QObject {parent}
, id_ {id}
, key_ {key}
, dial_frequency_ {0u}
{
}
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& /*dx_call*/
Q_SLOT void update_status (ClientKey const& key, 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*/
@ -54,77 +61,85 @@ public:
, quint8 /*special_op_mode*/, quint32 /*frequency_tolerance*/, quint32 /*tr_period*/
, QString const& /*configuration_name*/)
{
if (id == id_)
if (key == key_)
{
if (f != dial_frequency_)
{
std::cout << tr ("%1: Dial frequency changed to %2").arg (id_).arg (f).toStdString () << std::endl;
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
<< QString {"Dial frequency changed to %1"}.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;
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
<< QString {"Mode changed to %1"}.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
Q_SLOT void decode_added (bool is_new, ClientKey const& key, 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_)
if (key == key_)
{
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;
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
<< QString {"Decoded %1"}.arg (message).toStdString () << std::endl;
}
}
Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime time, qint32 snr
Q_SLOT void beacon_spot_added (bool is_new, ClientKey const& key, 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_)
if (key == key_)
{
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 ()
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
<< QString {"WSPR decode %1 grid %2 power: %3"}
.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
Q_SLOT void qso_logged (ClientKey const& key, 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
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode)
{
if (client_id == id_)
if (key == key_)
{
qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:" << dx_call << "grid:" << dx_grid
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 << "exchange_sent:" << exchange_sent
<< "exchange_rcvd:" << exchange_rcvd << "prop_mode:" << prop_mode;
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 exchange_sent: %12 exchange_rcvd: %13 comments: %14 prop_mode: %15")
.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).arg (exchange_sent).arg (exchange_rcvd)
.arg (comments).arg (prop_mode).toStdString ()
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
<< QString {"Logged %1 grid: %2 power: %3 sent: %4 recd: %5 freq: %6 time_off: %7 op: %8 my_call: %9 my_grid: %10 exchange_sent: %11 exchange_rcvd: %12 comments: %13 prop_mode: %14"}
.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).arg (exchange_sent).arg (exchange_rcvd)
.arg (comments).arg (prop_mode).toStdString ()
<< std::endl;
}
}
Q_SLOT void logged_ADIF (QString const&client_id, QByteArray const& ADIF)
Q_SLOT void logged_ADIF (ClientKey const& key, QByteArray const& ADIF)
{
if (client_id == id_)
if (key == key_)
{
qDebug () << "ADIF:" << ADIF;
std::cout << QByteArray {80, '-'}.data () << '\n';
@ -133,7 +148,7 @@ public:
}
private:
QString id_;
ClientKey key_;
Frequency dial_frequency_;
QString mode_;
};
@ -143,8 +158,10 @@ class Server
{
Q_OBJECT
using ClientKey = MessageServer::ClientKey;
public:
Server (port_type port, QHostAddress const& multicast_group)
Server (port_type port, QHostAddress const& multicast_group, QStringList const& network_interface_names)
: server_ {new MessageServer {this}}
{
// connect up server
@ -154,21 +171,26 @@ public:
connect (server_, &MessageServer::client_opened, this, &Server::add_client);
connect (server_, &MessageServer::client_closed, this, &Server::remove_client);
server_->start (port, multicast_group);
#if QT_VERSION >= QT_VERSION_CHECK (5, 14, 0)
server_->start (port, multicast_group, QSet<QString> {network_interface_names.begin (), network_interface_names.end ()});
#else
server_->start (port, multicast_group, network_interface_names.toSet ());
#endif
}
private:
void add_client (QString const& id, QString const& version, QString const& revision)
void add_client (ClientKey const& key, QString const& version, QString const& revision)
{
auto client = new Client {id};
auto client = new Client {key};
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 ();
clients_[key] = client;
server_->replay (key);
std::cout << "Discovered WSJT-X instance: " << key.second.toStdString ()
<< '(' << key.first.toString ().toStdString () << ')';
if (version.size ())
{
std::cout << " v" << version.toStdString ();
@ -180,23 +202,60 @@ private:
std::cout << std::endl;
}
void remove_client (QString const& id)
void remove_client (ClientKey const& key)
{
auto iter = clients_.find (id);
auto iter = clients_.find (key);
if (iter != std::end (clients_))
{
clients_.erase (iter);
(*iter)->deleteLater ();
}
std::cout << "Removed WSJT-X instance: " << id.toStdString () << std::endl;
std::cout << "Removed WSJT-X instance: " << key.second.toStdString ()
<< '(' << key.first.toString ().toStdString () << ')' << std::endl;
}
MessageServer * server_;
// maps client id to clients
QHash<QString, Client *> clients_;
// maps client key to clients
QHash<ClientKey, Client *> clients_;
};
void list_interfaces ()
{
for (auto const& net_if : QNetworkInterface::allInterfaces ())
{
if (net_if.flags () & QNetworkInterface::IsUp)
{
std::cout << net_if.humanReadableName ().toStdString () << ":\n"
" id: " << net_if.name ().toStdString () << " (" << net_if.index () << ")\n"
" addr: " << net_if.hardwareAddress ().toStdString () << "\n"
" flags: ";
if (net_if.flags () & QNetworkInterface::IsRunning)
{
std::cout << "Running ";
}
if (net_if.flags () & QNetworkInterface::CanBroadcast)
{
std::cout << "Broadcast ";
}
if (net_if.flags () & QNetworkInterface::CanMulticast)
{
std::cout << "Multicast ";
}
if (net_if.flags () & QNetworkInterface::IsLoopBack)
{
std::cout << "Loop-back ";
}
std::cout << "\n addresses:\n";
for (auto const& ae : net_if.addressEntries ())
{
std::cout << " " << ae.ip ().toString ().toStdString () << '\n';
}
std::cout << '\n';
}
}
}
#include "UDPDaemon.moc"
int main (int argc, char * argv[])
@ -217,6 +276,11 @@ int main (int argc, char * argv[])
auto help_option = parser.addHelpOption ();
auto version_option = parser.addVersionOption ();
QCommandLineOption list_option (QStringList {"l", "list-interfaces"},
app.translate ("UDPDaemon",
"Print the available network interfaces."));
parser.addOption (list_option);
QCommandLineOption port_option (QStringList {"p", "port"},
app.translate ("UDPDaemon",
"Where <PORT> is the UDP service port number to listen on.\n"
@ -232,9 +296,25 @@ int main (int argc, char * argv[])
app.translate ("UDPDaemon", "GROUP"));
parser.addOption (multicast_addr_option);
QCommandLineOption network_interface_option (QStringList {"i", "network-interface"},
app.translate ("UDPDaemon",
"Where <INTERFACE> is the network interface name to join on.\n"
"This option can be passed more than once to specify multiple network interfaces\n"
"The default is use just the loop back interface."),
app.translate ("UDPDaemon", "INTERFACE"));
parser.addOption (network_interface_option);
parser.process (app);
Server server {static_cast<port_type> (parser.value (port_option).toUInt ()), QHostAddress {parser.value (multicast_addr_option)}};
if (parser.isSet (list_option))
{
list_interfaces ();
return EXIT_SUCCESS;
}
Server server {static_cast<port_type> (parser.value (port_option).toUInt ())
, QHostAddress {parser.value (multicast_addr_option).trimmed ()}
, parser.values (network_interface_option)};
return app.exec ();
}

View File

@ -0,0 +1,169 @@
Building WSJT-X on MS Windows
=============================
Here I describe my set up, you may have other preferences due to your
favoured file system layout for tools, utilities, and libraries. Many
variations are possible, use this guide as a template. The aim is to
provide an environment suitable for general development.
Prerequisite Tools and Libraries
================================
Here is an overview list, details are filled out below:
* Qt Open Source Framework
* Cmake build system generator
* FFTW v3 DFT library
* libusb usb device library
* MSYS2 *nix like command line environment
* Hamlib rig control library
* Pkg Config Lite
* Boost C++ libraries
Qt Framework
------------
At the time of writing I recommend using Qt v5.15.0 64-bit, v5.15.1
has a defect that affects us so is best avoided. You need the MinGW
version as we do not support the MSVC version due to lack of a
suitable FOSS Fortran complier. To install the Qt developer SDK you
should download the official Qt on-line installer, this allows you to
install one or more variants of the Qt SDK and also to maintain and
update the installation at a later date. There are many versions and
components within versions available, you only need the base 64-bit
MinGW framework for Qt v5.15.0 and the matching MinGW 8.1.0 64-bit
developer tools, other components can be unchecked within the on-line
installer. The default install location is C:\Qt which is fine, do
not attempt to move the location of the installed libraries if you
decide you want it elsewhere, the installer patches the libraries for
the installed location and it is easier to un-install and re-install
if you wish to change the installed location.
CMake
-----
Download and install a recent version from the official CMake web
site. A default installation is fine. I am currently using v3.18.3,
versions as old as v3.9 should work.
Other tools and libraries without installers
--------------------------------------------
For small libraries that don't have a Windows installer and change
often I locate them under a directory C:\Tools. This location is
arbitrary and just used to aggregate such items in one place, but this
document assumes that location so you may need to adjust paths etc. if
you use a different location.
FFTW v3 DFT library
-------------------
The MS Windows builds of FFTW3 can be downloaded from
http://www.fftw.org/install/windows.html and unzipped into C:\Tools.
You only need the 64-bit package.
libusb library
--------------
This library is available from https://libusb.info/, download the .7z
archive as the .zip archive does not contain the libraries we
require. Unzip this package into C:\Tools.
MSYS2
-----
This utility is available from https://www.msys2.org/. Follow the
download, installation, and initial upgrading instructions there. Once
installed and updated you will need to install some packages, these
are needed to provide the necessary *nix tools and utilities to build
Hamlib from sources.
pacman -S autoconf automake libtool make
Hamlib
------
Currently we statically link Hamlib to avoid clashes with
pre-installed DLLs that may be older versions than we support. Once
Hamlib v1.4 is officially released and commonly available we will move
to dynamic linking. Until then Hamlib must be built from
sources. There is a fork of the official Hamlib project which we keep
up to date with the official project master branch, we recommend
building from the 'integration' branch of that fork. The fork is a git
repository which can be cloned with this command:
mkdir -p ~/src/sf/bsomervi
cd !$
git clone git://git.code.sf.net/u/bsomervi/hamlib hamlib
cd hamlib
git checkout integration
Next you must build Hamlib using the MinGW compiler tools bundled with
Qt. As you will build Hamlib again when there are updates you should
set up you Msys2 command line environment for this. I use a
$HOME/.bash_profile file containing these lines:
dll_paths_64bit=/c/Tools/libusb-1.0.23/MinGW64/dll
export PATH=/c/Qt/Tools/mingw810_64/bin:$dll_paths_64bit:$PATH
Test the amended ~/.bash_profile file by opening a new Msys2 shell and
typing:
which gcc
which libusb-1.0.dll
The first time you checkout the Hamlib sources you must bootstrap the
configuration script, this is done with a script at the root of the
Hamlib sources:
cd ~/src/sf/bsomervi/hamlib
./bootstrap
Now you need to configure and build Hamlib from an Msys2 shell. Create
a build directory outside of the Hamlib sources you have just cloned,
then change working directory to that build directory.
mkdir -p ~/build/hamlib/release
cd !$
~/src/sf/bsomervi/hamlib/configure --disable-shared \
--prefix=$HOME/local/hamlib/mingw64/release \
CFLAGS="-DNDEBUG -g -O2 -fdata-sections -ffunction-sections -I/c/Tools/libusb-1.0.23/include" \
CXXFLAGS="-DNDEBUG -g -O2 -fdata-sections -ffunction-sections" \
LDFLAGS="-Wl,--gc-sections" \
LIBUSB_LIBS="-L/c/Tools/libusb-1.0.23/MinGW64/dll -lusb-1.0"
Then build and install the Hamlib package into a local directory:
make & make install-strip
If you wish you can make a debug configuration build of Hamlib which
can be useful if you intended to contribute to the Hamlib project, or
for tracking down issues:
mkdir -p ~/build/hamlib/debug
cd !$
~/src/sf/bsomervi/hamlib/configure --disable-shared \
--prefix=$HOME/local/hamlib/mingw64/debug \
CFLAGS="-g -O0 -I/c/Tools/libusb-1.0.23/include" \
CXXFLAGS="-g -O0" \
LIBUSB_LIBS="-L/c/Tools/libusb-1.0.23/MinGW64/dll -lusb-1.0"
make && make install
To update the Hamlib sources to the latest commit and rebuild:
cd ~/src/sf/bsomervi/hamlib
git pull
cd ~/build/hamlib/release
make & make install-strip
cd ~/build/hamlib/debug
make && make install
Pkg Config Lite
---------------
This package allows the WSJT-X CMake configuration to locate and learn
the options needed to consume the Hamlib package. You can download it
from https://sourceforge.net/projects/pkgconfiglite/files/0.28-1/ and
unzip it into a convenient location, as with other ancillary tools and
libraries I put these under C:\Tools\.

View File

@ -93,8 +93,8 @@ d). Edit lines as needed. Keeping them in alphabetic order help see dupes.
:sourceforge-jtsdk: https://sourceforge.net/projects/jtsdk[SourceForge JTSDK]
:ubuntu_sdk: https://launchpad.net/~ubuntu-sdk-team/+archive/ppa[Ubuntu SDK Notice]
:win_openssl_packages: https://slproweb.com/products/Win32OpenSSL.html[Windows OpenSSL Packages]
:win32_openssl: https://slproweb.com/download/Win32OpenSSL_Light-1_1_1g.msi[Win32 OpenSSL Light Package]
:win64_openssl: https://slproweb.com/download/Win64OpenSSL_Light-1_1_1g.msi[Win64 OpenSSL Light Package]
:win32_openssl: https://slproweb.com/download/Win32OpenSSL_Light-1_1_1h.msi[Win32 OpenSSL Light Package]
:win64_openssl: https://slproweb.com/download/Win64OpenSSL_Light-1_1_1h.msi[Win64 OpenSSL Light Package]
:writelog: https://writelog.com/[Writelog]
:wsjtx_group: https://groups.io/g/WSJTX[WSJTX Group]
:wsjtx: https://physics.princeton.edu/pulsar/K1JT/wsjtx.html[WSJT-X]

BIN
lib/fsk4hf/.DS_Store vendored

Binary file not shown.

View File

@ -25,11 +25,11 @@ program jt9
integer :: arglen,stat,offset,remain,mode=0,flow=200,fsplit=2700, &
fhigh=4000,nrxfreq=1500,ndepth=1,nexp_decode=0,nQSOProg=0
logical :: read_files = .true., tx9 = .false., display_help = .false., &
bLowSidelobes = .false.
type (option) :: long_options(29) = [ &
bLowSidelobes = .false., nexp_decode_set = .false.
type (option) :: long_options(30) = [ &
option ('help', .false., 'h', 'Display this help message', ''), &
option ('shmem',.true.,'s','Use shared memory for sample data','KEY'), &
option ('tr-period', .true., 'p', 'Tx/Rx period, default SECONDS=60', &
option ('tr-period', .true., 'p', 'Tx/Rx period, default SECONDS=60', &
'SECONDS'), &
option ('executable-path', .true., 'e', &
'Location of subordinate executables (KVASD) default PATH="."', &
@ -46,6 +46,8 @@ program jt9
'Lowest JT9 frequency decoded, default HERTZ=2700', 'HERTZ'), &
option ('rx-frequency', .true., 'f', &
'Receive frequency offset, default HERTZ=1500', 'HERTZ'), &
option ('freq-tolerance', .true., 'F', &
'Receive frequency tolerance, default HERTZ=20', 'HERTZ'), &
option ('patience', .true., 'w', &
'FFTW3 planing patience (0-4), default PATIENCE=1', 'PATIENCE'), &
option ('fft-threads', .true., 'm', &
@ -54,8 +56,8 @@ program jt9
option ('jt4', .false., '4', 'JT4 mode', ''), &
option ('ft4', .false., '5', 'FT4 mode', ''), &
option ('jt65', .false.,'6', 'JT65 mode', ''), &
option ('fst4', .false., '7', 'FST4 mode', ''), &
option ('fst4w', .false., 'W', 'FST4W mode', ''), &
option ('fst4', .false., '7', 'FST4 mode', ''), &
option ('fst4w', .false., 'W', 'FST4W mode', ''), &
option ('ft8', .false., '8', 'FT8 mode', ''), &
option ('jt9', .false., '9', 'JT9 mode', ''), &
option ('qra64', .false., 'q', 'QRA64 mode', ''), &
@ -83,10 +85,11 @@ program jt9
iwspr=0
nsubmode = 0
ntol = 20
TRperiod=60.d0
do
call getopt('hs:e:a:b:r:m:p:d:f:w:t:987654WqTL:S:H:c:G:x:g:X:Q:', &
call getopt('hs:e:a:b:r:m:p:d:f:F:w:t:987654WqTL:S:H:c:G:x:g:X:Q:', &
long_options,c,optarg,arglen,stat,offset,remain,.true.)
if (stat .ne. 0) then
exit
@ -113,6 +116,8 @@ program jt9
read (optarg(:arglen), *) ndepth
case ('f')
read (optarg(:arglen), *) nrxfreq
case ('F')
read (optarg(:arglen), *) ntol
case ('L')
read (optarg(:arglen), *) flow
case ('S')
@ -153,6 +158,7 @@ program jt9
read (optarg(:arglen), *) hisgrid
case ('X')
read (optarg(:arglen), *) nexp_decode
nexp_decode_set = .true.
end select
end do
@ -195,6 +201,18 @@ program jt9
go to 999
endif
if (mode .eq. 241) then
ntol = min (ntol, 100)
else if (mode .eq. 65 + 9) then
ntol = 20
else
ntol = min (ntol, 1000)
end if
if (.not. nexp_decode_set) then
if (mode .eq. 240 .or. mode .eq. 241) then
nexp_decode = 3 * 256 ! single decode off and nb=0
end if
end if
allocate(shared_data)
nflatten=0
do iarg = offset + 1, offset + remain
@ -258,7 +276,7 @@ program jt9
shared_data%params%nfa=flow
shared_data%params%nfsplit=fsplit
shared_data%params%nfb=fhigh
shared_data%params%ntol=20
shared_data%params%ntol=ntol
shared_data%params%kin=64800
if(mode.eq.240) shared_data%params%kin=720000 !### 60 s periods ###
shared_data%params%nzhsym=nhsym

20
lib/wsprd/wspr_params.f90 Normal file
View File

@ -0,0 +1,20 @@
parameter (NN=162)
parameter (NSPS0=8192) !Samples per symbol at 12000 S/s
parameter (NDOWN=32)
parameter (NSPS=NSPS0/NDOWN)
parameter (NZ=NSPS*NN) !Samples in waveform at 12000 S/s
parameter (NZ0=NSPS0*NN) !Samples in waveform at 375 S/s
parameter (NMAX=120*12000) !Samples in waveform at 375 S/s
! Define the sync vector:
integer*1 sync(162)
data sync/ &
1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
0,0/

49
lib/wsprd/wspr_wav.f90 Normal file
View File

@ -0,0 +1,49 @@
subroutine wspr_wav(baud,xdt,h,f0,itone,snrdb,iwave)
! Generate iwave() from itone().
include 'wspr_params.f90'
integer itone(NN)
integer*2 iwave(NMAX)
real*8 twopi,dt,dphi0,dphi1,dphi,phi
real dat(NMAX)
twopi=8.d0*atan(1.d0)
dt=1.d0/12000.d0
baud=375.0/256.0
dat=0.
if(snrdb.lt.90) then
do i=1,NMAX
dat(i)=gran() !Generate gaussian noise
enddo
bandwidth_ratio=2500.0/6000.0
sig=sqrt(2*bandwidth_ratio)*10.0**(0.05*snrdb)
else
sig=1.0
endif
phi=0.d0
k=nint(xdt/dt)
do j=1,NN
dphi=twopi*(f0+h*(itone(j)-1.5)*baud)*dt
do i=1,NSPS0
k=k+1
phi=mod(phi+dphi,twopi)
if(k.gt.0 .and. k.le.NMAX) dat(k)=dat(k) + sig*sin(phi)
enddo
enddo
rms=100.0
if(snrdb.lt.90.0) then
dat=rms*dat;
if(maxval(abs(dat)).gt.32767.0) print*,"Warning - data will be clipped."
else
datpk=maxval(abs(dat))
fac=32767.9/datpk
dat=fac*dat
endif
iwave=nint(dat)
return
end subroutine wspr_wav

113
lib/wsprd/wsprsimf.f90 Normal file
View File

@ -0,0 +1,113 @@
!-------------------------------------------------------------------------------
!
! This file is part of the WSPR application, Weak Signal Propagation Reporter
!
!-------------------------------------------------------------------------------
program wsprsim
use wavhdr
include 'wspr_params.f90'
type(hdr) hwav
character arg*12,fname14*14,fname15*15
character*22 msg,msgsent
complex c0(0:NMAX/NDOWN-1)
complex c(0:NMAX/NDOWN-1)
integer itone(NN)
integer*2 iwave(NMAX)
real*8 fMHz
! Get command-line argument(s)
nargs=iargc()
if(nargs.ne.8) then
print*,'Usage: wsprsim "message" f0 DT fsp del nwav nfiles snr'
print*,'Example: wsprsim "K1ABC FN42 30" 50 0.0 0.1 1.0 1 10 -33'
go to 999
endif
call getarg(1,msg) !Message to be transmitted
call getarg(2,arg)
read(arg,*) f0 !Freq relative to WSPR-band center (Hz)
call getarg(3,arg)
read(arg,*) xdt !Time offset from nominal (s)
call getarg(4,arg)
read(arg,*) fspread !Watterson frequency spread (Hz)
call getarg(5,arg)
read(arg,*) delay !Watterson delay (ms)
call getarg(6,arg)
read(arg,*) nwav !1 for *.wav file, 0 for *.c2 file
call getarg(7,arg)
read(arg,*) nfiles !Number of files
call getarg(8,arg)
read(arg,*) snrdb !SNR_2500
twopi=8.0*atan(1.0)
fs=12000.0/NDOWN
dt=1.0/fs
tt=NSPS*dt
baud=12000.0/8192.0
txt=NZ*dt !Transmission length (s)
bandwidth_ratio=2500.0/(fs/2.0)
sig=sqrt(bandwidth_ratio) * 10.0**(0.05*snrdb)
if(snrdb.gt.90.0) sig=1.0
txt=NN*NSPS0/12000.0
call genwspr(msg,msgsent,itone) !Encode the message, get itone
write(*,1000) f0,xdt,txt,snrdb,fspread,delay,nfiles,msgsent
1000 format('f0:',f9.3,' DT:',f6.2,' txt:',f6.1,' SNR:',f6.1, &
' fspread:',f6.1,' delay:',f6.1,' nfiles:',i3,2x,a22)
! write(*,*) "Channel symbols: "
! write(*,'(162i2)') itone
h=1.0
phi=0.0
c0=0.
k=-1 + nint(xdt/dt)
do j=1,NN
dphi=-twopi*(f0+h*(itone(j)-1.5)*baud)*dt
do i=1,NSPS
k=k+1
phi=mod(phi+dphi,twopi)
if(k.ge.0 .and. k.lt.NMAX/NDOWN) c0(k)=cmplx(cos(phi),sin(phi))
enddo
enddo
call sgran()
do ifile=1,nfiles
c=c0
if(nwav.eq.0) then
if( fspread .ne. 0.0 .or. delay .ne. 0.0 ) then
call watterson(c,NMAX/NDOWN,NN*NSPS,fs,delay,fspread)
endif
c=c*sig
if(snrdb.lt.90) then
do i=0,NMAX/NDOWN-1 !Add gaussian noise at specified SNR
xnoise=gran()
ynoise=gran()
c(i)=c(i) + cmplx(xnoise,ynoise)
enddo
endif
write(fname14,1100) ifile
1100 format('000000_',i4.4,'.c2')
open(10,file=fname14,status='unknown',access='stream')
fMHz=10.1387d0
nmin=2
write(10) fname14,nmin,fMHz,c !Save to *.c2 file
close(10)
write(*,1108) ifile,xdt,f0,snrdb,fname14
1108 format(i4,f7.2,f8.2,f7.1,2x,a14)
else
freq=1500.0+f0
call wspr_wav(baud,xdt,h,freq,itone,snrdb,iwave)
hwav=default_header(12000,NMAX)
write(fname15,1102) ifile
1102 format('000000_',i4.4,'.wav')
open(10,file=fname15,status='unknown',access='stream')
write(10) hwav,iwave !Save to *.wav file
close(10)
write(*,1110) ifile,xdt,f0,snrdb,fname15
1110 format(i4,f7.2,f8.2,f7.1,2x,a15)
endif
enddo
999 end program wsprsim

View File

@ -9,6 +9,7 @@
#include <fftw3.h>
#include <QSharedMemory>
#include <QProcessEnvironment>
#include <QTemporaryFile>
#include <QDateTime>
#include <QLocale>
@ -107,6 +108,8 @@ int main(int argc, char *argv[])
// Multiple instances communicate with jt9 via this
QSharedMemory mem_jt9;
auto const env = QProcessEnvironment::systemEnvironment ();
QApplication a(argc, argv);
try
{
@ -421,7 +424,7 @@ int main(int argc, char *argv[])
}
// run the application UI
MainWindow w(temp_dir, multiple, &multi_settings, &mem_jt9, downSampleFactor, &splash);
MainWindow w(temp_dir, multiple, &multi_settings, &mem_jt9, downSampleFactor, &splash, env);
w.show();
splash.raise ();
QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit()));

View File

@ -130,6 +130,42 @@ namespace std
}
#endif
inline
bool is_broadcast_address (QHostAddress const& host_addr)
{
#if QT_VERSION >= QT_VERSION_CHECK (5, 11, 0)
return host_addr.isBroadcast ();
#else
bool ok;
return host_addr.toIPv4Address (&ok) == 0xffffffffu && ok;
#endif
}
inline
bool is_multicast_address (QHostAddress const& host_addr)
{
#if QT_VERSION >= QT_VERSION_CHECK (5, 6, 0)
return host_addr.isMulticast ();
#else
bool ok;
return (((host_addr.toIPv4Address (&ok) & 0xf0000000u) == 0xe0000000u) && ok)
|| host_addr.toIPv6Address ()[0] == 0xff;
#endif
}
inline
bool is_MAC_ambiguous_multicast_address (QHostAddress const& host_addr)
{
// sub-ranges 224.128.0.0/24, 225.0.0.0/24, 225.128.0.0/24,
// 226.0.0.0/24, 226.128.0.0/24, ..., 239.0.0.0/24, 239.128.0.0/24
// are not supported as they are inefficient due to ambiguous
// mappings to Ethernet MAC addresses. 224.0.0.0/24 alone is allowed
// from these ranges
bool ok;
auto ipv4 = host_addr.toIPv4Address (&ok);
return ok && !((ipv4 & 0xffffff00u) == 0xe0000000) && (ipv4 & 0xf07fff00) == 0xe0000000;
}
// Register some useful Qt types with QMetaType
Q_DECLARE_METATYPE (QHostAddress);

View File

@ -131,6 +131,80 @@ private:
QDateTime dt {QDate {2020, 8, 6}, QTime {14, 15, 22, 501}};
QCOMPARE (qt_truncate_date_time_to (dt, 7500), QDateTime (QDate (2020, 8, 6), QTime (14, 15, 22, 500)));
}
Q_SLOT void is_multicast_address_data ()
{
QTest::addColumn<QString> ("addr");
QTest::addColumn<bool> ("result");
QTest::newRow ("loopback") << "127.0.0.1" << false;
QTest::newRow ("looback IPv6") << "::1" << false;
QTest::newRow ("lowest-") << "223.255.255.255" << false;
QTest::newRow ("lowest") << "224.0.0.0" << true;
QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
QTest::newRow ("lowest IPv6") << "ff00::" << true;
QTest::newRow ("highest") << "239.255.255.255" << true;
QTest::newRow ("highest+") << "240.0.0.0" << false;
QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << true;
}
Q_SLOT void is_multicast_address ()
{
QFETCH (QString, addr);
QFETCH (bool, result);
QCOMPARE (::is_multicast_address (QHostAddress {addr}), result);
}
Q_SLOT void is_MAC_ambiguous_multicast_address_data ()
{
QTest::addColumn<QString> ("addr");
QTest::addColumn<bool> ("result");
QTest::newRow ("loopback") << "127.0.0.1" << false;
QTest::newRow ("looback IPv6") << "::1" << false;
QTest::newRow ("lowest- R1") << "223.255.255.255" << false;
QTest::newRow ("lowest R1") << "224.0.0.0" << false;
QTest::newRow ("highest R1") << "224.0.0.255" << false;
QTest::newRow ("highest+ R1") << "224.0.1.0" << false;
QTest::newRow ("lowest- R1A") << "224.127.255.255" << false;
QTest::newRow ("lowest R1A") << "224.128.0.0" << true;
QTest::newRow ("highest R1A") << "224.128.0.255" << true;
QTest::newRow ("highest+ R1A") << "224.128.1.0" << false;
QTest::newRow ("lowest- R2") << "224.255.255.255" << false;
QTest::newRow ("lowest R2") << "225.0.0.0" << true;
QTest::newRow ("highest R2") << "225.0.0.255" << true;
QTest::newRow ("highest+ R2") << "225.0.1.0" << false;
QTest::newRow ("lowest- R2A") << "225.127.255.255" << false;
QTest::newRow ("lowest R2A") << "225.128.0.0" << true;
QTest::newRow ("highest R2A") << "225.128.0.255" << true;
QTest::newRow ("highest+ R2A") << "225.128.1.0" << false;
QTest::newRow ("lowest- R3") << "238.255.255.255" << false;
QTest::newRow ("lowest R3") << "239.0.0.0" << true;
QTest::newRow ("highest R3") << "239.0.0.255" << true;
QTest::newRow ("highest+ R3") << "239.0.1.0" << false;
QTest::newRow ("lowest- R3A") << "239.127.255.255" << false;
QTest::newRow ("lowest R3A") << "239.128.0.0" << true;
QTest::newRow ("highest R3A") << "239.128.0.255" << true;
QTest::newRow ("highest+ R3A") << "239.128.1.0" << false;
QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
QTest::newRow ("lowest IPv6") << "ff00::" << false;
QTest::newRow ("highest") << "239.255.255.255" << false;
QTest::newRow ("highest+") << "240.0.0.0" << false;
QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
}
Q_SLOT void is_MAC_ambiguous_multicast_address ()
{
QFETCH (QString, addr);
QFETCH (bool, result);
QCOMPARE (::is_MAC_ambiguous_multicast_address (QHostAddress {addr}), result);
}
};
QTEST_MAIN (TestQtHelpers);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
#include "CheckableItemComboBox.hpp"
#include <QStyledItemDelegate>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QLineEdit>
#include <QEvent>
#include <QListView>
class CheckableItemComboBoxStyledItemDelegate
: public QStyledItemDelegate
{
public:
explicit CheckableItemComboBoxStyledItemDelegate (QObject * parent = nullptr)
: QStyledItemDelegate {parent}
{
}
void paint (QPainter * painter, QStyleOptionViewItem const& option, QModelIndex const& index) const override
{
QStyleOptionViewItem& mutable_option = const_cast<QStyleOptionViewItem&> (option);
mutable_option.showDecorationSelected = false;
QStyledItemDelegate::paint (painter, mutable_option, index);
}
};
CheckableItemComboBox::CheckableItemComboBox (QWidget * parent)
: LazyFillComboBox {parent}
, model_ {new QStandardItemModel()}
{
setModel (model_.data ());
setEditable (true);
lineEdit ()->setReadOnly (true);
lineEdit ()->installEventFilter (this);
setItemDelegate (new CheckableItemComboBoxStyledItemDelegate {this});
connect (lineEdit(), &QLineEdit::selectionChanged, lineEdit(), &QLineEdit::deselect);
connect (static_cast<QListView *> (view ()), &QListView::pressed, this, &CheckableItemComboBox::item_pressed);
connect (model_.data (), &QStandardItemModel::dataChanged, this, &CheckableItemComboBox::model_data_changed);
}
QStandardItem * CheckableItemComboBox::addCheckItem (QString const& label, QVariant const& data
, Qt::CheckState checkState)
{
auto * item = new QStandardItem {label};
item->setCheckState (checkState);
item->setData (data);
item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
model_->appendRow (item);
update_text ();
return item;
}
bool CheckableItemComboBox::eventFilter (QObject * object, QEvent * event)
{
if (object == lineEdit() && event->type () == QEvent::MouseButtonPress)
{
showPopup();
return true;
}
return false;
}
void CheckableItemComboBox::update_text()
{
QString text;
for (int i = 0; i < model_->rowCount (); ++i)
{
if (model_->item (i)->checkState () == Qt::Checked)
{
if (text.size ())
{
text+= ", ";
}
text += model_->item (i)->data ().toString ();
}
}
lineEdit ()->setText (text);
}
void CheckableItemComboBox::model_data_changed ()
{
update_text ();
}
void CheckableItemComboBox::item_pressed (QModelIndex const& index)
{
QStandardItem * item = model_->itemFromIndex (index);
item->setCheckState (item->checkState () == Qt::Checked ? Qt::Unchecked : Qt::Checked);
}
#include "widgets/moc_CheckableItemComboBox.cpp"

View File

@ -0,0 +1,37 @@
#ifndef CHECKABLE_ITEM_COMBO_BOX_HPP__
#define CHECKABLE_ITEM_COMBO_BOX_HPP__
#include <QScopedPointer>
#include "LazyFillComboBox.hpp"
class QStandardItemModel;
class QStandardItem;
/**
* @brief QComboBox with support of checkboxes
* http://stackoverflow.com/questions/8422760/combobox-of-checkboxes
*/
class CheckableItemComboBox
: public LazyFillComboBox
{
Q_OBJECT
public:
explicit CheckableItemComboBox (QWidget * parent = nullptr);
QStandardItem * addCheckItem (QString const& label, QVariant const& data, Qt::CheckState checkState);
protected:
bool eventFilter (QObject *, QEvent *) override;
private:
void update_text();
Q_SLOT void model_data_changed ();
Q_SLOT void item_pressed (QModelIndex const&);
private:
QScopedPointer<QStandardItemModel> model_;
};
#endif

View File

@ -10,7 +10,7 @@ class QWidget;
//
// QComboBox derivative that signals show and hide of the pop up list.
//
class LazyFillComboBox final
class LazyFillComboBox
: public QComboBox
{
Q_OBJECT
@ -24,6 +24,7 @@ public:
{
}
#if QT_VERSION >= QT_VERSION_CHECK (5, 12, 0)
void showPopup () override
{
Q_EMIT about_to_show_popup ();
@ -35,6 +36,19 @@ public:
QComboBox::hidePopup ();
Q_EMIT popup_hidden ();
}
#else
void mousePressEvent (QMouseEvent * e) override
{
Q_EMIT about_to_show_popup ();
QComboBox::mousePressEvent (e);
}
void mouseReleaseEvent (QMouseEvent * e) override
{
QComboBox::mouseReleaseEvent (e);
Q_EMIT popup_hidden ();
}
#endif
};
#endif

View File

@ -2,6 +2,8 @@
#ifndef ASTRO_H
#define ASTRO_H
#include <utility>
#include <QDialog>
#include <QScopedPointer>
@ -34,9 +36,17 @@ public:
Correction (Correction const&) = default;
Correction& operator = (Correction const&) = default;
// testing facility used to test Doppler corrections on
// terrestrial links
void reverse ()
{
std::swap (rx, tx);
}
FrequencyDelta rx;
FrequencyDelta tx;
};
Correction astroUpdate(QDateTime const& t,
QString const& mygrid,
QString const& hisgrid,

View File

@ -14,6 +14,7 @@
#include <QStringListModel>
#include <QSettings>
#include <QKeyEvent>
#include <QProcessEnvironment>
#include <QSharedMemory>
#include <QFileDialog>
#include <QTextBlock>
@ -234,8 +235,9 @@ namespace
MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
MultiSettings * multi_settings, QSharedMemory *shdmem,
unsigned downSampleFactor,
QSplashScreen * splash, QWidget *parent) :
QSplashScreen * splash, QProcessEnvironment const& env, QWidget *parent) :
QMainWindow(parent),
m_env {env},
m_network_manager {this},
m_valid {true},
m_splash {splash},
@ -267,6 +269,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_secBandChanged {0},
m_freqNominal {0},
m_freqTxNominal {0},
m_reverse_Doppler {"1" == env.value ("WSJT_REVERSE_DOPPLER", "0")},
m_s6 {0.},
m_tRemaining {0.},
m_TRperiod {60.0},
@ -415,6 +418,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_messageClient {new MessageClient {QApplication::applicationName (),
version (), revision (),
m_config.udp_server_name (), m_config.udp_server_port (),
m_config.udp_interface_names (), m_config.udp_TTL (),
this}},
m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()},
m_manual {&m_network_manager},
@ -783,6 +787,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::udp_TTL_changed, m_messageClient, &MessageClient::set_TTL);
connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable);
connect (&m_config, &Configuration::enumerating_audio_devices, [this] () {
showStatusMessage (tr ("Enumerating audio devices"));
@ -927,9 +932,9 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
, "-a", QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath ())
, "-t", QDir::toNativeSeparators (m_config.temp_dir ().absolutePath ())
};
QProcessEnvironment env {QProcessEnvironment::systemEnvironment ()};
env.insert ("OMP_STACKSIZE", "4M");
proc_jt9.setProcessEnvironment (env);
QProcessEnvironment new_env {m_env};
new_env.insert ("OMP_STACKSIZE", "4M");
proc_jt9.setProcessEnvironment (new_env);
proc_jt9.start(QDir::toNativeSeparators (m_appDir) + QDir::separator () +
"jt9", jt9_args, QIODevice::ReadWrite | QIODevice::Unbuffered);
@ -1813,6 +1818,7 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog
checkMSK144ContestType();
if (m_config.my_callsign () != callsign) {
m_baseCall = Radio::base_callsign (m_config.my_callsign ());
ui->tx1->setEnabled (elide_tx1_not_allowed () || ui->tx1->isEnabled ());
morse_(const_cast<char *> (m_config.my_callsign ().toLatin1().constData()),
const_cast<int *> (icw), &m_ncw, m_config.my_callsign ().length());
}
@ -4228,7 +4234,15 @@ void MainWindow::guiUpdate()
transmitDisplay (true);
statusUpdate ();
}
if(!m_btxok && m_btxok0 && g_iptt==1) stopTx();
if(!m_btxok && m_btxok0 && g_iptt==1)
{
stopTx();
if ("1" == m_env.value ("WSJT_TX_BOTH", "0"))
{
m_txFirst = !m_txFirst;
ui->txFirstCheckBox->setChecked (m_txFirst);
}
}
if(m_startAnother) {
if(m_mode=="MSK144") {
@ -4492,13 +4506,19 @@ void MainWindow::on_txrb1_toggled (bool status)
}
}
bool MainWindow::elide_tx1_not_allowed () const
{
auto const& my_callsign = m_config.my_callsign ();
return
(m_mode=="FT8" && SpecOp::HOUND == m_config.special_op_id())
|| ((m_mode.startsWith ("FT") || "MSK144" == m_mode || "Q65" == m_mode || "FST4" == m_mode)
&& Radio::is_77bit_nonstandard_callsign (my_callsign))
|| (my_callsign != m_baseCall && !shortList (my_callsign));
}
void MainWindow::on_txrb1_doubleClicked ()
{
if(m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id()) return;
// skip Tx1, only allowed if not a type 2 compound callsign
auto const& my_callsign = m_config.my_callsign ();
auto is_compound = my_callsign != m_baseCall;
ui->tx1->setEnabled ((is_compound && shortList (my_callsign)) || !ui->tx1->isEnabled ());
ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ());
if (!ui->tx1->isEnabled ()) {
// leave time for clicks to complete before setting txrb2
QTimer::singleShot (500, ui->txrb2, SLOT (click ()));
@ -4575,11 +4595,7 @@ void MainWindow::on_txb1_clicked()
void MainWindow::on_txb1_doubleClicked()
{
if (m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id()) return;
// skip Tx1, only allowed if not a type 1 compound callsign
auto const& my_callsign = m_config.my_callsign ();
auto is_compound = my_callsign != m_baseCall;
ui->tx1->setEnabled ((is_compound && shortList (my_callsign)) || !ui->tx1->isEnabled ());
ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ());
}
void MainWindow::on_txb2_clicked()
@ -4917,34 +4933,38 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie
m_QSOProgress=ROGER_REPORT;
}
} else { // no grid on end of msg
QString r=message_words.at (3);
if(m_QSOProgress >= ROGER_REPORT && (r=="RRR" || r.toInt()==73 || "RR73" == r)) {
if(m_mode=="FT4" and r=="RR73") m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc();
m_bTUmsg=false;
m_nextCall=""; //### Temporary: disable use of "TU;" message
if(SpecOp::RTTY == m_config.special_op_id() and m_nextCall!="") {
// We're in RTTY contest and have "nextCall" queued up: send a "TU; ..." message
auto const& word_3 = message_words.at (3);
bool word_3_is_int;
auto word_3_as_number = word_3.toInt (&word_3_is_int);
if(m_QSOProgress >= ROGER_REPORT && ("RRR" == word_3
|| (word_3_is_int && word_3_as_number == 73)
|| "RR73" == word_3)) {
if(m_mode=="FT4" and "RR73" == word_3) m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc();
m_bTUmsg=false;
m_nextCall=""; //### Temporary: disable use of "TU;" message
if(SpecOp::RTTY == m_config.special_op_id() and m_nextCall!="") {
// We're in RTTY contest and have "nextCall" queued up: send a "TU; ..." message
logQSOTimer.start(0);
ui->tx3->setText(ui->tx3->text().remove("TU; "));
useNextCall();
QString t="TU; " + ui->tx3->text();
ui->tx3->setText(t);
m_bTUmsg=true;
} else {
if(SpecOp::RTTY == m_config.special_op_id()) {
logQSOTimer.start(0);
ui->tx3->setText(ui->tx3->text().remove("TU; "));
useNextCall();
QString t="TU; " + ui->tx3->text();
ui->tx3->setText(t);
m_bTUmsg=true;
m_ntx=6;
ui->txrb6->setChecked(true);
} else {
if(SpecOp::RTTY == m_config.special_op_id()) {
logQSOTimer.start(0);
m_ntx=6;
ui->txrb6->setChecked(true);
} else {
m_ntx=5;
ui->txrb5->setChecked(true);
}
m_ntx=5;
ui->txrb5->setChecked(true);
}
}
m_QSOProgress = SIGNOFF;
} else if((m_QSOProgress >= REPORT
|| (m_QSOProgress >= REPLYING &&
(m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4")))
&& r.mid(0,1)=="R") {
&& word_3.startsWith ('R')) {
m_ntx=4;
m_QSOProgress = ROGERS;
if(SpecOp::RTTY == m_config.special_op_id()) {
@ -4953,23 +4973,30 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie
if(nRpt>=529 and nRpt<=599) m_xRcvd=t[n-2] + " " + t[n-1];
}
ui->txrb4->setChecked(true);
} else if(m_QSOProgress>=CALLING and
((r.toInt()>=-50 && r.toInt()<=49) or (r.toInt()>=529 && r.toInt()<=599))) {
if(SpecOp::EU_VHF==m_config.special_op_id() or
SpecOp::FIELD_DAY==m_config.special_op_id() or
SpecOp::RTTY==m_config.special_op_id()) {
setTxMsg(2);
m_QSOProgress=REPORT;
} else {
if(r.left(2)=="R-" or r.left(2)=="R+") {
setTxMsg(4);
m_QSOProgress=ROGERS;
} else if (m_QSOProgress >= CALLING)
{
if (word_3_is_int
&& ((word_3_as_number >= -50 && word_3_as_number <= 49)
|| (word_3_as_number >= 529 && word_3_as_number <= 599))) {
if(SpecOp::EU_VHF==m_config.special_op_id() or
SpecOp::FIELD_DAY==m_config.special_op_id() or
SpecOp::RTTY==m_config.special_op_id()) {
setTxMsg(2);
m_QSOProgress=REPORT;
}
else {
setTxMsg (3);
m_QSOProgress = ROGER_REPORT;
}
} else {
setTxMsg(3);
m_QSOProgress=ROGER_REPORT;
if (word_3.startsWith ("R-") || word_3.startsWith ("R+")) {
setTxMsg(4);
m_QSOProgress=ROGERS;
}
}
} else { // nothing for us
}
else
{ // nothing for us
return;
}
}
@ -7424,7 +7451,7 @@ Type 1 Suffixes: /0 /1 /2 /3 /4 /5 /6 /7 /8 /9 /A /P)", {"Courier", 10}});
m_prefixes->raise ();
}
bool MainWindow::shortList(QString callsign)
bool MainWindow::shortList(QString callsign) const
{
int n=callsign.length();
int i1=callsign.indexOf("/");
@ -7669,13 +7696,21 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de
QString format_string {"%1 %2 %3 %4 %5 %6"};
auto const& time_string = time.toString ("~" == mode || "&" == mode
|| "+" == mode ? "hhmmss" : "hhmm");
auto text = message_text;
auto ap_pos = text.lastIndexOf (QRegularExpression {R"((?:\?\s)?a[0-9]$)"});
if (ap_pos >= 0)
{
// beware of decodes ending on shorter version of wanted call so
// add a space
text = text.left (ap_pos).trimmed () + ' ';
}
auto message_line = format_string
.arg (time_string)
.arg (snr, 3)
.arg (delta_time, 4, 'f', 1)
.arg (delta_frequency, 4)
.arg (mode, -2)
.arg (message_text);
.arg (text);
QTextCursor start {ui->decodedTextBrowser->document ()};
start.movePosition (QTextCursor::End);
auto cursor = ui->decodedTextBrowser->document ()->find (message_line, start, QTextDocument::FindBackward);
@ -7688,7 +7723,7 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de
.arg ('-' + QString::number (delta_time, 'f', 1), 4)
.arg (delta_frequency, 4)
.arg (mode, -2)
.arg (message_text), start, QTextDocument::FindBackward);
.arg (text), start, QTextDocument::FindBackward);
}
if (!cursor.isNull ())
{
@ -7703,7 +7738,7 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de
showNormal ();
raise ();
}
if (message_text.contains (QRegularExpression {R"(^(CQ |CQDX |QRZ ))"})) {
if (text.contains (QRegularExpression {R"(^(CQ |CQDX |QRZ ))"})) {
// a message we are willing to accept and auto reply to
m_bDoubleClicked = true;
}
@ -7817,7 +7852,7 @@ void MainWindow::networkError (QString const& e)
, MessageBox::Cancel))
{
// retry server lookup
m_messageClient->set_server (m_config.udp_server_name ());
m_messageClient->set_server (m_config.udp_server_name (), m_config.udp_interface_names ());
}
}
@ -8186,6 +8221,10 @@ void MainWindow::astroUpdate ()
correction.tx = correction.tx / 10 * 10;
}
m_astroCorrection = correction;
if (m_reverse_Doppler)
{
m_astroCorrection.reverse ();
}
}
else
{
@ -9110,15 +9149,15 @@ void MainWindow::write_all(QString txRx, QString message)
QString msg;
QString mode_string;
if (message[4]==' ') {
if (message.size () > 5 && message[4]==' ') {
msg=message.mid(4,-1);
} else {
msg=message.mid(6,-1);
}
if (message[19]=='#') {
if (message.size () > 19 && message[19]=='#') {
mode_string="JT65 ";
} else if (message[19]=='@') {
} else if (message.size () > 19 && message[19]=='@') {
mode_string="JT9 ";
} else {
mode_string=m_mode.leftJustified(6,' ');

View File

@ -64,6 +64,7 @@ namespace Ui {
class MainWindow;
}
class QProcessEnvironment;
class QSharedMemory;
class QSplashScreen;
class QSettings;
@ -104,7 +105,7 @@ public:
explicit MainWindow(QDir const& temp_directory, bool multiple, MultiSettings *,
QSharedMemory *shdmem, unsigned downSampleFactor,
QSplashScreen *,
QSplashScreen *, QProcessEnvironment const&,
QWidget *parent = nullptr);
~MainWindow();
@ -356,7 +357,9 @@ private:
void foxTest();
void setColorHighlighting();
void chkFT4();
bool elide_tx1_not_allowed () const;
QProcessEnvironment const& m_env;
NetworkAccessManager m_network_manager;
bool m_valid;
QSplashScreen * m_splash;
@ -408,6 +411,7 @@ private:
Frequency m_freqNominal;
Frequency m_freqTxNominal;
Astro::Correction m_astroCorrection;
bool m_reverse_Doppler;
double m_s6;
double m_tRemaining;
@ -707,7 +711,7 @@ private:
void stub();
void statusChanged();
void fixStop();
bool shortList(QString callsign);
bool shortList(QString callsign) const;
void transmit (double snr = 99.);
void rigFailure (QString const& reason);
void pskSetLocal ();

View File

@ -10,7 +10,9 @@ SOURCES += \
widgets/AbstractLogWindow.cpp \
widgets/FrequencyLineEdit.cpp widgets/FrequencyDeltaLineEdit.cpp \
widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp \
widgets/HelpTextWindow.cpp widgets/RestrictedSpinBox.cpp
widgets/HelpTextWindow.cpp widgets/RestrictedSpinBox.cpp \
widgets/LazyFillComboBox.cpp widgets/CheckableItemComboBox.cpp
HEADERS += \
widgets/mainwindow.h widgets/plotter.h \
widgets/about.h widgets/widegraph.h \
@ -22,7 +24,8 @@ HEADERS += \
widgets/ExportCabrillo.h widgets/AbstractLogWindow.hpp \
widgets/FoxLogWindow.hpp widgets/CabrilloLogWindow.hpp \
widgets/DateTimeEdit.hpp widgets/HelpTextWindow.hpp \
widgets/RestrictedSpinBox.hpp
widgets/RestrictedSpinBox.hpp \
widgets/LazyFillComboBox.hpp widgets/CheckableItemComboBox.hpp
FORMS += \
widgets/mainwindow.ui widgets/about.ui \