Compare commits
31 Commits
v1.0.0-rc1
...
v1.0.0-rc3
| Author | SHA1 | Date | |
|---|---|---|---|
| 1629415dc1 | |||
| 08cf869125 | |||
| 03bd44ae39 | |||
| 14626978c0 | |||
| dd43f0db84 | |||
| 5646268faf | |||
| 5d21fdf1dc | |||
| 5d2ba76c17 | |||
| 7bd86ca177 | |||
| 59b1a3b011 | |||
| ce2c045458 | |||
| 218c5b3d47 | |||
| 27ae28a889 | |||
| 1a2596224a | |||
| 82915540b4 | |||
| 73c6dd50fb | |||
| ee48d8fd86 | |||
| d8a16f4a42 | |||
| 5ef440faf6 | |||
| 9d9ae62526 | |||
| 6b9c9b4ceb | |||
| 1febb18495 | |||
| 43401c3c26 | |||
| 55928be661 | |||
| a5f8593b3d | |||
| 07f1594d0a | |||
| b6dd6aadc8 | |||
| b9b334c6cf | |||
| fec45aa0ad | |||
| 2ef4e90710 | |||
| a0dc7bc013 |
@@ -243,6 +243,7 @@ set (wsjtx_CXXSRCS
|
||||
messagereplydialog.cpp
|
||||
keyeater.cpp
|
||||
APRSISClient.cpp
|
||||
SpotClient.cpp
|
||||
Inbox.cpp
|
||||
messagewindow.cpp
|
||||
mainwindow.cpp
|
||||
|
||||
+109
-43
@@ -23,7 +23,7 @@
|
||||
<string>Select tab to change configuration parameters.</string>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>3</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="general_tab">
|
||||
<attribute name="title">
|
||||
@@ -88,6 +88,16 @@
|
||||
<string>Station Details</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_13">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="callsign_label">
|
||||
<property name="text">
|
||||
<string>My Callsign:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>callsign_line_edit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="grid_label">
|
||||
<property name="text">
|
||||
@@ -121,16 +131,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="callsign_label">
|
||||
<property name="text">
|
||||
<string>My Callsign:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>callsign_line_edit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="groups_label">
|
||||
<property name="text">
|
||||
@@ -278,7 +278,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>724</width>
|
||||
<width>615</width>
|
||||
<height>646</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -2339,7 +2339,7 @@ This is used for reverse ping analysis which is very useful
|
||||
for assessing propagation and system performance.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable spotting to reporting networks (PSKReporter && APRS-IS)</string>
|
||||
<string>Enable spotting to reporting networks (JS8 Network, PSKReporter, APRS-IS, etc)</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
@@ -3983,71 +3983,137 @@ soundcard changes</string>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>configuration_tabs</tabstop>
|
||||
<tabstop>use_dynamic_grid</tabstop>
|
||||
<tabstop>type_2_msg_gen_combo_box</tabstop>
|
||||
<tabstop>monitor_last_used_check_box</tabstop>
|
||||
<tabstop>quick_call_check_box</tabstop>
|
||||
<tabstop>CW_id_after_73_check_box</tabstop>
|
||||
<tabstop>enable_VHF_features_check_box</tabstop>
|
||||
<tabstop>single_decode_check_box</tabstop>
|
||||
<tabstop>decode_at_52s_check_box</tabstop>
|
||||
<tabstop>CW_id_interval_spin_box</tabstop>
|
||||
<tabstop>tabWidget_2</tabstop>
|
||||
<tabstop>callsign_line_edit</tabstop>
|
||||
<tabstop>grid_line_edit</tabstop>
|
||||
<tabstop>groups_line_edit</tabstop>
|
||||
<tabstop>avoid_allcall_checkbox</tabstop>
|
||||
<tabstop>cq_message_line_edit</tabstop>
|
||||
<tabstop>reply_message_line_edit</tabstop>
|
||||
<tabstop>info_message_line_edit</tabstop>
|
||||
<tabstop>scrollArea_2</tabstop>
|
||||
<tabstop>miles_check_box</tabstop>
|
||||
<tabstop>monitor_off_check_box</tabstop>
|
||||
<tabstop>tx_qsy_check_box</tabstop>
|
||||
<tabstop>transmit_directed_check_box</tabstop>
|
||||
<tabstop>heartbeat_anywhere_check_box</tabstop>
|
||||
<tabstop>heartbeat_qso_pause_check_box</tabstop>
|
||||
<tabstop>spellcheck_check_box</tabstop>
|
||||
<tabstop>autoreply_off_check_box</tabstop>
|
||||
<tabstop>relay_disabled_check_box</tabstop>
|
||||
<tabstop>auto_whitelist_line_edit</tabstop>
|
||||
<tabstop>auto_blacklist_line_edit</tabstop>
|
||||
<tabstop>scrollArea</tabstop>
|
||||
<tabstop>callsign_aging_spin_box</tabstop>
|
||||
<tabstop>activity_aging_spin_box</tabstop>
|
||||
<tabstop>tx_watchdog_spin_box</tabstop>
|
||||
<tabstop>rig_combo_box</tabstop>
|
||||
<tabstop>CAT_poll_interval_spin_box</tabstop>
|
||||
<tabstop>tabWidget_3</tabstop>
|
||||
<tabstop>scrollArea_3</tabstop>
|
||||
<tabstop>CAT_port_combo_box</tabstop>
|
||||
<tabstop>CAT_serial_baud_combo_box</tabstop>
|
||||
<tabstop>CAT_default_bit_radio_button</tabstop>
|
||||
<tabstop>CAT_7_bit_radio_button</tabstop>
|
||||
<tabstop>CAT_8_bit_radio_button</tabstop>
|
||||
<tabstop>CAT_default_stop_bit_radio_button</tabstop>
|
||||
<tabstop>CAT_one_stop_bit_radio_button</tabstop>
|
||||
<tabstop>CAT_two_stop_bit_radio_button</tabstop>
|
||||
<tabstop>CAT_handshake_default_radio_button</tabstop>
|
||||
<tabstop>CAT_handshake_none_radio_button</tabstop>
|
||||
<tabstop>CAT_handshake_xon_radio_button</tabstop>
|
||||
<tabstop>CAT_handshake_hardware_radio_button</tabstop>
|
||||
<tabstop>force_DTR_combo_box</tabstop>
|
||||
<tabstop>force_RTS_combo_box</tabstop>
|
||||
<tabstop>scrollArea_4</tabstop>
|
||||
<tabstop>PTT_VOX_radio_button</tabstop>
|
||||
<tabstop>PTT_CAT_radio_button</tabstop>
|
||||
<tabstop>PTT_DTR_radio_button</tabstop>
|
||||
<tabstop>PTT_CAT_radio_button</tabstop>
|
||||
<tabstop>PTT_RTS_radio_button</tabstop>
|
||||
<tabstop>PTT_port_combo_box</tabstop>
|
||||
<tabstop>mode_USB_radio_button</tabstop>
|
||||
<tabstop>mode_none_radio_button</tabstop>
|
||||
<tabstop>mode_data_radio_button</tabstop>
|
||||
<tabstop>TX_source_data_radio_button</tabstop>
|
||||
<tabstop>TX_source_mic_radio_button</tabstop>
|
||||
<tabstop>mode_none_radio_button</tabstop>
|
||||
<tabstop>mode_USB_radio_button</tabstop>
|
||||
<tabstop>mode_data_radio_button</tabstop>
|
||||
<tabstop>split_none_radio_button</tabstop>
|
||||
<tabstop>split_rig_radio_button</tabstop>
|
||||
<tabstop>split_emulate_radio_button</tabstop>
|
||||
<tabstop>split_rig_radio_button</tabstop>
|
||||
<tabstop>split_none_radio_button</tabstop>
|
||||
<tabstop>sbTxDelay</tabstop>
|
||||
<tabstop>ptt_command_line_edit</tabstop>
|
||||
<tabstop>test_CAT_push_button</tabstop>
|
||||
<tabstop>test_PTT_push_button</tabstop>
|
||||
<tabstop>scrollArea_5</tabstop>
|
||||
<tabstop>sound_output_combo_box</tabstop>
|
||||
<tabstop>sound_input_combo_box</tabstop>
|
||||
<tabstop>sound_input_channel_combo_box</tabstop>
|
||||
<tabstop>sound_output_combo_box</tabstop>
|
||||
<tabstop>sound_output_channel_combo_box</tabstop>
|
||||
<tabstop>save_path_select_push_button</tabstop>
|
||||
<tabstop>azel_path_select_push_button</tabstop>
|
||||
<tabstop>checkBoxPwrBandTxMemory</tabstop>
|
||||
<tabstop>checkBoxPwrBandTuneMemory</tabstop>
|
||||
<tabstop>add_macro_line_edit</tabstop>
|
||||
<tabstop>add_macro_push_button</tabstop>
|
||||
<tabstop>delete_macro_push_button</tabstop>
|
||||
<tabstop>macros_list_view</tabstop>
|
||||
<tabstop>prompt_to_log_check_box</tabstop>
|
||||
<tabstop>scrollArea_6</tabstop>
|
||||
<tabstop>opCallEntry</tabstop>
|
||||
<tabstop>log_as_RTTY_check_box</tabstop>
|
||||
<tabstop>udp_server_line_edit</tabstop>
|
||||
<tabstop>udp_server_port_spin_box</tabstop>
|
||||
<tabstop>udpEnable</tabstop>
|
||||
<tabstop>accept_udp_requests_check_box</tabstop>
|
||||
<tabstop>udpWindowToFront</tabstop>
|
||||
<tabstop>udpWindowRestore</tabstop>
|
||||
<tabstop>use_dynamic_grid</tabstop>
|
||||
<tabstop>psk_reporter_check_box</tabstop>
|
||||
<tabstop>aprs_server_line_edit</tabstop>
|
||||
<tabstop>aprs_server_port_spin_box</tabstop>
|
||||
<tabstop>aprs_passcode_line_edit</tabstop>
|
||||
<tabstop>enable_n1mm_broadcast_check_box</tabstop>
|
||||
<tabstop>n1mm_server_name_line_edit</tabstop>
|
||||
<tabstop>n1mm_server_port_spin_box</tabstop>
|
||||
<tabstop>scrollArea_7</tabstop>
|
||||
<tabstop>calibration_slope_ppm_spin_box</tabstop>
|
||||
<tabstop>calibration_intercept_spin_box</tabstop>
|
||||
<tabstop>frequencies_table_view</tabstop>
|
||||
<tabstop>auto_switch_bands_check_box</tabstop>
|
||||
<tabstop>stations_table_view</tabstop>
|
||||
<tabstop>sbNtrials</tabstop>
|
||||
<tabstop>sbAggressive</tabstop>
|
||||
<tabstop>cbTwoPass</tabstop>
|
||||
<tabstop>add_macro_push_button</tabstop>
|
||||
<tabstop>add_macro_line_edit</tabstop>
|
||||
<tabstop>delete_macro_push_button</tabstop>
|
||||
<tabstop>macros_list_view</tabstop>
|
||||
<tabstop>sound_am_path_test_push_button</tabstop>
|
||||
<tabstop>sound_dm_path_reset_push_button</tabstop>
|
||||
<tabstop>sound_am_path_reset_push_button</tabstop>
|
||||
<tabstop>sound_dm_path_select_push_button</tabstop>
|
||||
<tabstop>sound_am_path_select_push_button</tabstop>
|
||||
<tabstop>sound_dm_path_test_push_button</tabstop>
|
||||
<tabstop>sound_cq_path_select_push_button</tabstop>
|
||||
<tabstop>sound_cq_path_test_push_button</tabstop>
|
||||
<tabstop>sound_cq_path_reset_push_button</tabstop>
|
||||
<tabstop>font_push_button</tabstop>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>scrollArea_8</tabstop>
|
||||
<tabstop>tableForegroundButton</tabstop>
|
||||
<tabstop>pbCQmsg</tabstop>
|
||||
<tabstop>tableFontButton</tabstop>
|
||||
<tabstop>pbMyCall</tabstop>
|
||||
<tabstop>tableSelectedRowBackgroundButton</tabstop>
|
||||
<tabstop>tableBackgroundButton</tabstop>
|
||||
<tabstop>scrollArea_9</tabstop>
|
||||
<tabstop>txFontButton</tabstop>
|
||||
<tabstop>rxFontButton</tabstop>
|
||||
<tabstop>txForegroundButton</tabstop>
|
||||
<tabstop>rxBackgroundButton</tabstop>
|
||||
<tabstop>rxForegroundButton</tabstop>
|
||||
<tabstop>scrollArea_10</tabstop>
|
||||
<tabstop>composeBackgroundButton</tabstop>
|
||||
<tabstop>composeForegroundButton</tabstop>
|
||||
<tabstop>composeFontButton</tabstop>
|
||||
<tabstop>sbDegrade</tabstop>
|
||||
<tabstop>sbBandwidth</tabstop>
|
||||
<tabstop>cbx2ToneSpacing</tabstop>
|
||||
<tabstop>cbx4ToneSpacing</tabstop>
|
||||
<tabstop>sbNtrials</tabstop>
|
||||
<tabstop>sbAggressive</tabstop>
|
||||
<tabstop>cbTwoPass</tabstop>
|
||||
<tabstop>cbFox</tabstop>
|
||||
<tabstop>cbHound</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
@@ -4117,12 +4183,12 @@ soundcard changes</string>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="TX_audio_source_button_group"/>
|
||||
<buttongroup name="CAT_stop_bits_button_group"/>
|
||||
<buttongroup name="CAT_handshake_button_group"/>
|
||||
<buttongroup name="split_mode_button_group"/>
|
||||
<buttongroup name="PTT_method_button_group"/>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="TX_audio_source_button_group"/>
|
||||
<buttongroup name="CAT_handshake_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
#include "SpotClient.h"
|
||||
#include "Message.h"
|
||||
|
||||
#include "moc_SpotClient.cpp"
|
||||
|
||||
SpotClient::SpotClient(MessageClient *client, QObject *parent):
|
||||
QObject(parent),
|
||||
m_client { client }
|
||||
{
|
||||
prepare();
|
||||
|
||||
connect(&m_timer, &QTimer::timeout, this, &SpotClient::processSpots);
|
||||
m_timer.setInterval(60 * 1000);
|
||||
m_timer.setSingleShot(false);
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
void SpotClient::prepare(){
|
||||
QHostInfo::lookupHost("spot.js8call.com", this, SLOT(dnsLookupResult(QHostInfo)));
|
||||
}
|
||||
|
||||
void SpotClient::dnsLookupResult(QHostInfo info){
|
||||
if (info.addresses().isEmpty()) {
|
||||
qDebug() << "SpotClient Error:" << info.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
m_address = info.addresses().at(0);
|
||||
qDebug() << "SpotClient Resolve:" << m_address.toString();
|
||||
}
|
||||
|
||||
void SpotClient::setLocalStation(QString callsign, QString grid, QString info, QString version){
|
||||
bool changed = false;
|
||||
|
||||
if(m_call != callsign){
|
||||
m_call = callsign;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if(m_grid != grid){
|
||||
m_grid = grid;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if(m_info != info){
|
||||
m_info = info;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if(m_version != version){
|
||||
m_version = version;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// send local information to network on change, or once every 5 minutes
|
||||
if(changed || m_seq % 5 == 0){
|
||||
enqueueLocalSpot(callsign, grid, info, version);
|
||||
}
|
||||
}
|
||||
|
||||
void SpotClient::enqueueLocalSpot(QString callsign, QString grid, QString info, QString version){
|
||||
auto m = Message("RX.LOCAL", "", {
|
||||
{"CALLSIGN", QVariant(callsign)},
|
||||
{"GRID", QVariant(grid)},
|
||||
{"INFO", QVariant(info)},
|
||||
{"VERSION", QVariant(version)},
|
||||
});
|
||||
|
||||
m_queue.enqueue(m.toJson());
|
||||
}
|
||||
|
||||
void SpotClient::enqueueSpot(QString callsign, QString grid, int frequency, int snr){
|
||||
auto m = Message("RX.SPOT", "", {
|
||||
{"BY", QVariant(QMap<QString, QVariant>{
|
||||
{"CALLSIGN", QVariant(m_call)},
|
||||
{"GRID", QVariant(m_grid)},
|
||||
})},
|
||||
{"CALLSIGN", QVariant(callsign)},
|
||||
{"GRID", QVariant(grid)},
|
||||
{"FREQ", QVariant(frequency)},
|
||||
{"SNR", QVariant(snr)},
|
||||
});
|
||||
|
||||
m_queue.enqueue(m.toJson());
|
||||
}
|
||||
|
||||
void SpotClient::enqueueCmd(QString cmd, QString from, QString to, QString relayPath, QString text, QString grid, QString extra, int frequency, int snr){
|
||||
auto m = Message("RX.DIRECTED", "", {
|
||||
{"BY", QVariant(QMap<QString, QVariant>{
|
||||
{"CALLSIGN", QVariant(m_call)},
|
||||
{"GRID", QVariant(m_grid)},
|
||||
})},
|
||||
{"CMD", QVariant(cmd)},
|
||||
{"FROM", QVariant(from)},
|
||||
{"TO", QVariant(to)},
|
||||
{"PATH", QVariant(relayPath)},
|
||||
{"TEXT", QVariant(text)},
|
||||
{"GRID", QVariant(grid)},
|
||||
{"EXTRA", QVariant(extra)},
|
||||
{"FREQ", QVariant(frequency)},
|
||||
{"SNR", QVariant(snr)},
|
||||
});
|
||||
|
||||
m_queue.enqueue(m.toJson());
|
||||
}
|
||||
|
||||
void SpotClient::processSpots(){
|
||||
if(m_address.isNull()){
|
||||
prepare();
|
||||
return;
|
||||
}
|
||||
|
||||
while(!m_queue.isEmpty()){
|
||||
sendRawSpot(m_queue.dequeue());
|
||||
}
|
||||
|
||||
m_seq++;
|
||||
}
|
||||
|
||||
void SpotClient::sendRawSpot(QByteArray payload){
|
||||
if(!m_address.isNull()){
|
||||
m_client->send_raw_datagram(payload, m_address, 50000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef JS8SPOTCLIENT_H
|
||||
#define JS8SPOTCLIENT_H
|
||||
|
||||
#include "MessageClient.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostInfo>
|
||||
#include <QQueue>
|
||||
#include <QTimer>
|
||||
|
||||
class SpotClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpotClient(MessageClient *client, QObject *parent = nullptr);
|
||||
|
||||
void prepare();
|
||||
void setLocalStation(QString callsign, QString grid, QString info, QString version);
|
||||
void enqueueLocalSpot(QString callsign, QString grid, QString info, QString version);
|
||||
void enqueueCmd(QString cmd, QString from, QString to, QString relayPath, QString text, QString grid, QString extra, int frequency, int snr);
|
||||
void enqueueSpot(QString callsign, QString grid, int frequency, int snr);
|
||||
void sendRawSpot(QByteArray payload);
|
||||
|
||||
public slots:
|
||||
void processSpots();
|
||||
void dnsLookupResult(QHostInfo);
|
||||
|
||||
private:
|
||||
int m_seq;
|
||||
QString m_call;
|
||||
QString m_grid;
|
||||
QString m_info;
|
||||
QString m_version;
|
||||
|
||||
QHostAddress m_address;
|
||||
MessageClient *m_client;
|
||||
QTimer m_timer;
|
||||
QQueue<QByteArray> m_queue;
|
||||
};
|
||||
|
||||
#endif // JS8SPOTCLIENT_H
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
set (WSJTX_VERSION_MAJOR 1)
|
||||
set (WSJTX_VERSION_MINOR 0)
|
||||
set (WSJTX_VERSION_PATCH 0)
|
||||
set (WSJTX_RC 1) # release candidate number, comment out or zero for development versions
|
||||
set (WSJTX_RC 3) # release candidate number, comment out or zero for development versions
|
||||
set (WSJTX_VERSION_IS_RELEASE 0) # set to 1 for final release build
|
||||
|
||||
@@ -81,7 +81,8 @@ SOURCES += \
|
||||
jsc_checker.cpp \
|
||||
Message.cpp \
|
||||
Inbox.cpp \
|
||||
messagewindow.cpp
|
||||
messagewindow.cpp \
|
||||
SpotClient.cpp
|
||||
|
||||
HEADERS += qt_helpers.hpp \
|
||||
pimpl_h.hpp pimpl_impl.hpp \
|
||||
@@ -113,7 +114,8 @@ HEADERS += qt_helpers.hpp \
|
||||
jsc_checker.h \
|
||||
Message.h \
|
||||
Inbox.h \
|
||||
messagewindow.h
|
||||
messagewindow.h \
|
||||
SpotClient.h
|
||||
|
||||
|
||||
INCLUDEPATH += qmake_only
|
||||
@@ -15,7 +15,7 @@ subroutine extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent)
|
||||
do ibyte=1,10
|
||||
itmp=0
|
||||
do ibit=1,8
|
||||
itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*8+ibit))
|
||||
itmp=ishft(itmp,1)+iand(1_1,decoded((ibyte-1)*8+ibit))
|
||||
enddo
|
||||
i1Dec8BitBytes(ibyte)=itmp
|
||||
enddo
|
||||
@@ -31,7 +31,7 @@ subroutine extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent)
|
||||
do ibyte=1,12
|
||||
itmp=0
|
||||
do ibit=1,6
|
||||
itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*6+ibit))
|
||||
itmp=ishft(itmp,1)+iand(1_1,decoded((ibyte-1)*6+ibit))
|
||||
enddo
|
||||
i4Dec6BitWords(ibyte)=itmp
|
||||
enddo
|
||||
|
||||
@@ -13,7 +13,7 @@ subroutine chkcrc12a(decoded,nbadcrc)
|
||||
read(cbits,1002) ncrc12 !Received CRC12
|
||||
1002 format(75x,b12)
|
||||
|
||||
i1Dec8BitBytes(10)=iand(i1Dec8BitBytes(10),128+64+32)
|
||||
i1Dec8BitBytes(10)=iand(i1Dec8BitBytes(10),transfer(128+64+32,0_1))
|
||||
i1Dec8BitBytes(11)=0
|
||||
icrc12=crc12(c_loc(i1Dec8BitBytes),11) !CRC12 computed from 75 msg bits
|
||||
icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here
|
||||
|
||||
@@ -19,7 +19,7 @@ subroutine extractmessage174(decoded,msgreceived,ncrcflag)
|
||||
read(cbits,1002) ncrc12 !Received CRC12
|
||||
1002 format(75x,b12)
|
||||
|
||||
i1Dec8BitBytes(10)=iand(i1Dec8BitBytes(10),128+64+32)
|
||||
i1Dec8BitBytes(10)=iand(i1Dec8BitBytes(10),transfer(128+64+32,0_1))
|
||||
i1Dec8BitBytes(11)=0
|
||||
icrc12=crc12(c_loc(i1Dec8BitBytes),11) !CRC12 computed from 75 msg bits
|
||||
icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here
|
||||
@@ -29,7 +29,7 @@ subroutine extractmessage174(decoded,msgreceived,ncrcflag)
|
||||
do ibyte=1,12
|
||||
itmp=0
|
||||
do ibit=1,6
|
||||
itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*6+ibit))
|
||||
itmp=ishft(itmp,1)+iand(1_1,decoded((ibyte-1)*6+ibit))
|
||||
enddo
|
||||
i4Dec6BitWords(ibyte)=itmp
|
||||
enddo
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ subroutine genft8(msg,mygrid,bcontest,i3bit,msgsent,msgbits,itone)
|
||||
1000 format(12b6.6,b8.8)
|
||||
read(cbits,1001) i1Msg8BitBytes(1:10)
|
||||
1001 format(10b8)
|
||||
i1Msg8BitBytes(10)=iand(i1Msg8BitBytes(10),128+64+32)
|
||||
i1Msg8BitBytes(10)=iand(i1Msg8BitBytes(10),transfer(128+64+32,0_1))
|
||||
i1Msg8BitBytes(11)=0
|
||||
icrc12=crc12(c_loc(i1Msg8BitBytes),11)
|
||||
icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here
|
||||
|
||||
@@ -94,7 +94,7 @@ allocate ( rxdata(N), llr(N) )
|
||||
checksum = xor(checksum, 42) ! TODO: jsherer - could change the crc here
|
||||
! For reference, the next 3 lines show how to check the CRC
|
||||
i1Msg8BitBytes(10)=checksum/256
|
||||
i1Msg8BitBytes(11)=iand (checksum,255)
|
||||
i1Msg8BitBytes(11)=iand(checksum,transfer(255,0_2))
|
||||
checksumok = crc12_check(c_loc (i1Msg8BitBytes), 11)
|
||||
if( checksumok ) write(*,*) 'Good checksum'
|
||||
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ do idb = 0, 30
|
||||
nhashflag=0
|
||||
imsg=0
|
||||
do i=1,16
|
||||
imsg=ishft(imsg,1)+iand(1,decoded(17-i))
|
||||
imsg=ishft(imsg,1)+iand(1_1,decoded(17-i))
|
||||
enddo
|
||||
nrxrpt=iand(imsg,15)
|
||||
nrxhash=(imsg-nrxrpt)/16
|
||||
|
||||
@@ -129,7 +129,7 @@ subroutine msk40decodeframe(c,mycall,hiscall,xsnr,bswl,nhasharray, &
|
||||
|
||||
imsg=0
|
||||
do i=1,16
|
||||
imsg=ishft(imsg,1)+iand(1,decoded(17-i))
|
||||
imsg=ishft(imsg,1)+iand(1_1,decoded(17-i))
|
||||
enddo
|
||||
nrxrpt=iand(imsg,15)
|
||||
nrxhash=(imsg-nrxrpt)/16
|
||||
|
||||
+15
-6
@@ -101,14 +101,17 @@ void ADIF::load()
|
||||
, extractField (record, "BAND")
|
||||
, extractField (record, "MODE")
|
||||
, extractField (record, "SUBMODE")
|
||||
, extractField (record, "QSO_DATE"));
|
||||
, extractField (record, "QSO_DATE")
|
||||
, extractField (record, "NAME")
|
||||
, extractField (record, "COMMENT")
|
||||
);
|
||||
}
|
||||
inputFile.close ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ADIF::add(QString const& call, QString const& band, QString const& mode, QString const& submode, QString const& date)
|
||||
void ADIF::add(QString const& call, QString const& band, QString const& mode, QString const& submode, QString const& date, QString const& name, QString const& comment)
|
||||
{
|
||||
QSO q;
|
||||
q.call = call;
|
||||
@@ -116,6 +119,9 @@ void ADIF::add(QString const& call, QString const& band, QString const& mode, QS
|
||||
q.mode = mode;
|
||||
q.submode = submode;
|
||||
q.date = date;
|
||||
q.name = name;
|
||||
q.comment = comment;
|
||||
|
||||
if (q.call.size ())
|
||||
{
|
||||
_data.insert(q.call,q);
|
||||
@@ -135,13 +141,18 @@ bool ADIF::match(QString const& call, QString const& band) const
|
||||
if ( (band.compare(q.band,Qt::CaseInsensitive) == 0)
|
||||
|| (band=="")
|
||||
|| (q.band==""))
|
||||
{
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QList<ADIF::QSO> ADIF::find(QString const& call) const
|
||||
{
|
||||
return _data.values(call);
|
||||
}
|
||||
|
||||
QList<QString> ADIF::getCallList() const
|
||||
{
|
||||
@@ -154,8 +165,6 @@ QList<QString> ADIF::getCallList() const
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int ADIF::getCount() const
|
||||
{
|
||||
|
||||
+8
-3
@@ -21,10 +21,14 @@ class QDateTime;
|
||||
class ADIF
|
||||
{
|
||||
public:
|
||||
|
||||
struct QSO;
|
||||
|
||||
void init(QString const& filename);
|
||||
void load();
|
||||
void add(QString const& call, QString const& band, QString const& mode, const QString &submode, QString const& date);
|
||||
void add(QString const& call, QString const& band, QString const& mode, const QString &submode, QString const& date, const QString &name, const QString &comment);
|
||||
bool match(QString const& call, QString const& band) const;
|
||||
QList<ADIF::QSO> find(QString const& call) const;
|
||||
QList<QString> getCallList() const;
|
||||
int getCount() const;
|
||||
|
||||
@@ -38,12 +42,13 @@ class ADIF
|
||||
, QString const& operator_call);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
struct QSO
|
||||
{
|
||||
QString call,band,mode,submode,date;
|
||||
QString call,band,mode,submode,date,name,comment;
|
||||
};
|
||||
|
||||
private:
|
||||
QMultiHash<QString, QSO> _data;
|
||||
QString _filename;
|
||||
|
||||
|
||||
+39
-13
@@ -59,25 +59,51 @@ void LogBook::match(/*in*/const QString call,
|
||||
bool &callWorkedBefore,
|
||||
bool &countryWorkedBefore) const
|
||||
{
|
||||
if (call.length() > 0)
|
||||
{
|
||||
QString currentBand = ""; // match any band
|
||||
callWorkedBefore = _log.match(call,currentBand);
|
||||
countryName = _countries.find(call);
|
||||
if(call.isEmpty()){
|
||||
return;
|
||||
}
|
||||
|
||||
if (countryName.length() > 0) // country was found
|
||||
QString currentBand = ""; // match any band
|
||||
callWorkedBefore = _log.match(call, currentBand);
|
||||
countryName = _countries.find(call);
|
||||
|
||||
if (countryName.length() > 0){ // country was found
|
||||
countryWorkedBefore = _worked.getHasWorked(countryName);
|
||||
else
|
||||
{
|
||||
countryName = "where?"; //error: prefix not found
|
||||
countryWorkedBefore = false;
|
||||
}
|
||||
} else {
|
||||
countryName = "where?"; //error: prefix not found
|
||||
countryWorkedBefore = false;
|
||||
}
|
||||
}
|
||||
|
||||
void LogBook::addAsWorked(const QString call, const QString band, const QString mode, const QString submode, const QString date)
|
||||
bool LogBook::findCallDetails(
|
||||
/*in*/
|
||||
const QString call,
|
||||
/*out*/
|
||||
QString &date,
|
||||
QString &name,
|
||||
QString &comment) const
|
||||
{
|
||||
_log.add(call,band,mode,submode,date);
|
||||
if(call.isEmpty()){
|
||||
return false;
|
||||
}
|
||||
|
||||
auto qsos = _log.find(call);
|
||||
if(qsos.isEmpty()){
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach(auto qso, qsos){
|
||||
if(date.isEmpty() && !qso.date.isEmpty()) date = qso.date;
|
||||
if(name.isEmpty() && !qso.name.isEmpty()) name = qso.name;
|
||||
if(comment.isEmpty() && !qso.comment.isEmpty()) comment = qso.comment;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LogBook::addAsWorked(const QString call, const QString band, const QString mode, const QString submode, const QString date, const QString name, const QString comment)
|
||||
{
|
||||
_log.add(call,band,mode,submode,date,name,comment);
|
||||
QString countryName = _countries.find(call);
|
||||
if (countryName.length() > 0)
|
||||
_worked.setAsWorked(countryName);
|
||||
|
||||
+8
-1
@@ -25,7 +25,14 @@ public:
|
||||
/*out*/ QString &countryName,
|
||||
bool &callWorkedBefore,
|
||||
bool &countryWorkedBefore) const;
|
||||
void addAsWorked(const QString call, const QString band, const QString mode, const QString submode, const QString date);
|
||||
bool findCallDetails(
|
||||
/*in*/
|
||||
const QString call,
|
||||
/*out*/
|
||||
QString &date,
|
||||
QString &name,
|
||||
QString &comment) const;
|
||||
void addAsWorked(const QString call, const QString band, const QString mode, const QString submode, const QString date, const QString name, const QString comment);
|
||||
|
||||
private:
|
||||
CountryDat _countries;
|
||||
|
||||
+294
-76
@@ -560,6 +560,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
version (), revision (),
|
||||
m_config.udp_server_name (), m_config.udp_server_port (),
|
||||
this}},
|
||||
m_spotClient { new SpotClient{m_messageClient, this}},
|
||||
m_aprsClient {new APRSISClient{"rotate.aprs2.net", 14580, this}},
|
||||
psk_Reporter {new PSK_Reporter {m_messageClient, this}},
|
||||
m_i3bit {0},
|
||||
@@ -569,6 +570,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
m_txFrameCountEstimate {0},
|
||||
m_previousFreq {0},
|
||||
m_hbPaused { false },
|
||||
m_hbAutoAck { true },
|
||||
m_hbHidden { false },
|
||||
m_hbInterval {0},
|
||||
m_cqInterval {0},
|
||||
@@ -814,6 +816,15 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
});
|
||||
ui->labDialFreqOffset->installEventFilter(ldmp);
|
||||
|
||||
// Hook up callsign label click to open preferenses
|
||||
ui->labCallsign->setCursor(QCursor(Qt::PointingHandCursor));
|
||||
auto clmp = new MousePressEater();
|
||||
connect(clmp, &MousePressEater::mousePressed, this, [this](QObject *, QMouseEvent * e, bool *pProcessed){
|
||||
openSettings(0);
|
||||
if(pProcessed) *pProcessed = true;
|
||||
});
|
||||
ui->labCallsign->installEventFilter(clmp);
|
||||
|
||||
ui->bandComboBox->setVisible(false);
|
||||
ui->bandComboBox->setModel (m_config.frequencies ());
|
||||
ui->bandComboBox->setModelColumn (FrequencyList_v2::frequency_mhz_column);
|
||||
@@ -1261,7 +1272,11 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
connect(ui->tableWidgetRXAll->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, [this](QPoint const &point){
|
||||
QMenu * menu = new QMenu(ui->tableWidgetRXAll);
|
||||
|
||||
buildBandActivitySortByMenu(menu);
|
||||
QMenu * sortByMenu = menu->addMenu("Sort By...");
|
||||
buildBandActivitySortByMenu(sortByMenu);
|
||||
|
||||
QMenu * showColumnsMenu = menu->addMenu("Show Columns...");
|
||||
buildShowColumnsMenu(showColumnsMenu, "band");
|
||||
|
||||
menu->popup(ui->tableWidgetRXAll->horizontalHeader()->mapToGlobal(point));
|
||||
});
|
||||
@@ -1475,7 +1490,11 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
connect(ui->tableWidgetCalls->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, [this](QPoint const &point){
|
||||
QMenu * menu = new QMenu(ui->tableWidgetCalls);
|
||||
|
||||
buildCallActivitySortByMenu(menu);
|
||||
QMenu * sortByMenu = menu->addMenu("Sort By...");
|
||||
buildCallActivitySortByMenu(sortByMenu);
|
||||
|
||||
QMenu * showColumnsMenu = menu->addMenu("Show Columns...");
|
||||
buildShowColumnsMenu(showColumnsMenu, "call");
|
||||
|
||||
menu->popup(ui->tableWidgetCalls->horizontalHeader()->mapToGlobal(point));
|
||||
});
|
||||
@@ -1604,6 +1623,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
gridButtonLayout->setColumnStretch(1, 1);
|
||||
gridButtonLayout->setColumnStretch(2, 1);
|
||||
|
||||
spotSetLocal();
|
||||
pskSetLocal();
|
||||
aprsSetLocal();
|
||||
|
||||
@@ -1619,7 +1639,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
if (!m_valid) throw std::runtime_error {"Fatal initialization exception"};
|
||||
}
|
||||
|
||||
QDate eol(2019, 3, 11);
|
||||
QDate eol(2019, 4, 1);
|
||||
|
||||
void MainWindow::checkExpiryWarningMessage()
|
||||
{
|
||||
@@ -1755,6 +1775,8 @@ void MainWindow::initializeDummyData(){
|
||||
displayTextForFreq("KN4CRD: JY1 ACK -10 \u2301 ", 800, now, false, true, true);
|
||||
displayTextForFreq("KN4CRD: JY1 ACK -12 \u2301 ", 780, now.addSecs(120), false, true, true);
|
||||
|
||||
displayTextForFreq("HELLO\\nBRAVE\\nNEW\\nWORLD \u2301 ", 1500, now, false, true, true);
|
||||
|
||||
displayActivity(true);
|
||||
}
|
||||
|
||||
@@ -1979,6 +2001,7 @@ void MainWindow::writeSettings()
|
||||
m_settings->setValue ("JT65AP", ui->actionEnable_AP_JT65->isChecked ());
|
||||
m_settings->setValue("SortBy", QVariant(m_sortCache));
|
||||
m_settings->setValue("ShowColumns", QVariant(m_showColumnsCache));
|
||||
m_settings->setValue("HBAutoAck", m_hbAutoAck);
|
||||
m_settings->setValue("HBHidden", m_hbHidden);
|
||||
m_settings->setValue("HBInterval", m_hbInterval);
|
||||
m_settings->setValue("CQInterval", m_cqInterval);
|
||||
@@ -2113,7 +2136,8 @@ void MainWindow::readSettings()
|
||||
|
||||
m_sortCache = m_settings->value("SortBy").toMap();
|
||||
m_showColumnsCache = m_settings->value("ShowColumns").toMap();
|
||||
m_hbHidden = m_settings->value("HBHidden", false).toBool();
|
||||
m_hbAutoAck = m_settings->value("HBAutoAck", true).toBool();
|
||||
m_hbHidden = m_settings->value("HBHidden", true).toBool();
|
||||
m_hbInterval = m_settings->value("HBInterval", 0).toInt();
|
||||
m_cqInterval = m_settings->value("CQInterval", 0).toInt();
|
||||
|
||||
@@ -2874,6 +2898,7 @@ void MainWindow::openSettings(int tab){
|
||||
|
||||
void MainWindow::prepareSpotting(){
|
||||
if(m_config.spot_to_reporting_networks ()){
|
||||
spotSetLocal();
|
||||
pskSetLocal();
|
||||
aprsSetLocal();
|
||||
m_aprsClient->setServer(m_config.aprs_server_name(), m_config.aprs_server_port());
|
||||
@@ -2969,6 +2994,9 @@ void MainWindow::on_autoButton_clicked (bool checked)
|
||||
|
||||
void MainWindow::on_autoReplyButton_toggled(bool checked){
|
||||
resetPushButtonToggleText(ui->autoReplyButton);
|
||||
|
||||
// update the HB button immediately
|
||||
updateRepeatButtonDisplay();
|
||||
}
|
||||
|
||||
void MainWindow::on_monitorButton_toggled(bool checked){
|
||||
@@ -4143,8 +4171,9 @@ void MainWindow::readFromStdout() //readFromStdout
|
||||
// Only respond to HEARTBEATS...remember that CQ messages are "Alt" pings
|
||||
if(decodedtext.isHeartbeat()){
|
||||
if(decodedtext.isAlt()){
|
||||
// this is a cq with a standard or compound call, ala "KN4CRD/P: CQCQCQ"
|
||||
cd.cqTimestamp = DriftingDateTime::currentDateTimeUtc();
|
||||
|
||||
// this is a cq with a compound call, ala "KN4CRD/P: CQCQCQ"
|
||||
// it is not processed elsewhere, so we need to just log it here.
|
||||
logCallActivity(cd, true);
|
||||
|
||||
@@ -4447,6 +4476,9 @@ void MainWindow::logCallActivity(CallDetail d, bool spot){
|
||||
if(!d.ackTimestamp.isValid() && old.ackTimestamp.isValid()){
|
||||
d.ackTimestamp = old.ackTimestamp;
|
||||
}
|
||||
if(!d.cqTimestamp.isValid() && old.cqTimestamp.isValid()){
|
||||
d.cqTimestamp = old.cqTimestamp;
|
||||
}
|
||||
m_callActivity[d.call] = d;
|
||||
} else {
|
||||
// create
|
||||
@@ -4503,6 +4535,25 @@ QString MainWindow::lookupCallInCompoundCache(QString const &call){
|
||||
return m_compoundCallCache.value(call, call);
|
||||
}
|
||||
|
||||
void MainWindow::spotReport(int offset, int snr, QString callsign, QString grid){
|
||||
if(!m_config.spot_to_reporting_networks()) return;
|
||||
|
||||
Frequency frequency = m_freqNominal + offset;
|
||||
|
||||
m_spotClient->enqueueSpot(callsign, grid, frequency, snr);
|
||||
}
|
||||
|
||||
void MainWindow::spotCmd(CommandDetail cmd){
|
||||
if(!m_config.spot_to_reporting_networks()) return;
|
||||
|
||||
QString cmdStr = cmd.cmd;
|
||||
if(!cmdStr.trimmed().isEmpty()){
|
||||
cmdStr = Varicode::lstrip(cmd.cmd);
|
||||
}
|
||||
|
||||
m_spotClient->enqueueCmd(cmdStr, cmd.from, cmd.to, cmd.relayPath, cmd.text, cmd.grid, cmd.extra, m_freqNominal + cmd.freq, cmd.snr);
|
||||
}
|
||||
|
||||
void MainWindow::pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid){
|
||||
if(!m_config.spot_to_reporting_networks()) return;
|
||||
|
||||
@@ -4753,12 +4804,15 @@ void MainWindow::guiUpdate()
|
||||
char ft8msgbits[75 + 12]; //packed 75 bit ft8 message plus 12-bit CRC
|
||||
genft8_(message, MyGrid, &bcontest, &m_i3bit, msgsent, const_cast<char *> (ft8msgbits),
|
||||
const_cast<int *> (itone), 22, 6, 22);
|
||||
|
||||
msgibits = m_i3bit;
|
||||
msgsent[22]=0;
|
||||
|
||||
m_currentMessage = QString::fromLatin1(msgsent);
|
||||
m_currentMessage = QString::fromLatin1(msgsent).trimmed();
|
||||
m_currentMessageBits = msgibits;
|
||||
|
||||
emitTones();
|
||||
|
||||
#if TEST_FOX_WAVE_GEN
|
||||
if(ui->turboButton->isChecked()) {
|
||||
|
||||
@@ -5579,22 +5633,32 @@ void MainWindow::on_tx6_editingFinished() //tx6 edited
|
||||
}
|
||||
|
||||
void MainWindow::cacheActivity(QString key){
|
||||
m_callActivityCache[key] = m_callActivity;
|
||||
m_bandActivityCache[key] = m_bandActivity;
|
||||
m_rxTextCache[key] = ui->textEditRX->toHtml();
|
||||
m_callActivityBandCache[key] = m_callActivity;
|
||||
m_bandActivityBandCache[key] = m_bandActivity;
|
||||
m_rxTextBandCache[key] = ui->textEditRX->toHtml();
|
||||
m_heardGraphIncomingBandCache[key] = m_heardGraphIncoming;
|
||||
m_heardGraphOutgoingBandCache[key] = m_heardGraphOutgoing;
|
||||
}
|
||||
|
||||
void MainWindow::restoreActivity(QString key){
|
||||
if(m_callActivityCache.contains(key)){
|
||||
m_callActivity = m_callActivityCache[key];
|
||||
if(m_callActivityBandCache.contains(key)){
|
||||
m_callActivity = m_callActivityBandCache[key];
|
||||
}
|
||||
|
||||
if(m_bandActivityCache.contains(key)){
|
||||
m_bandActivity = m_bandActivityCache[key];
|
||||
if(m_bandActivityBandCache.contains(key)){
|
||||
m_bandActivity = m_bandActivityBandCache[key];
|
||||
}
|
||||
|
||||
if(m_rxTextCache.contains(key)){
|
||||
ui->textEditRX->setHtml(m_rxTextCache[key]);
|
||||
if(m_rxTextBandCache.contains(key)){
|
||||
ui->textEditRX->setHtml(m_rxTextBandCache[key]);
|
||||
}
|
||||
|
||||
if(m_heardGraphIncomingBandCache.contains(key)){
|
||||
m_heardGraphIncoming = m_heardGraphIncomingBandCache[key];
|
||||
}
|
||||
|
||||
if(m_heardGraphOutgoingBandCache.contains(key)){
|
||||
m_heardGraphOutgoing = m_heardGraphOutgoingBandCache[key];
|
||||
}
|
||||
|
||||
displayActivity(true);
|
||||
@@ -5717,7 +5781,7 @@ void MainWindow::displayTextForFreq(QString text, int freq, QDateTime date, bool
|
||||
block = -1;
|
||||
}
|
||||
|
||||
block = writeMessageTextToUI(date, text, freq, isTx, block);
|
||||
block = writeMessageTextToUI(date, text.replace("\\n", "\n"), freq, isTx, block);
|
||||
|
||||
// never cache tx or last lines
|
||||
if(isTx || isLast) {
|
||||
@@ -6409,7 +6473,7 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call,
|
||||
, QString const& my_call, QString const& my_grid, QByteArray const& ADIF)
|
||||
{
|
||||
QString date = QSO_date_on.toString("yyyyMMdd");
|
||||
m_logBook.addAsWorked (m_hisCall, m_config.bands ()->find (m_freqNominal), mode, submode, date);
|
||||
m_logBook.addAsWorked (m_hisCall, m_config.bands ()->find (m_freqNominal), mode, submode, date, name, comments);
|
||||
|
||||
sendNetworkMessage("LOG.QSO", QString(ADIF), {
|
||||
{"UTC.ON", QVariant(QSO_date_on.toMSecsSinceEpoch())},
|
||||
@@ -6998,6 +7062,16 @@ void MainWindow::buildFrequencyMenu(QMenu *menu){
|
||||
}
|
||||
|
||||
void MainWindow::buildHeartbeatMenu(QMenu *menu){
|
||||
auto autoAckHB = menu->addAction(ui->autoReplyButton->isChecked() ? "Send Heartbeat Acknowledgments (ACK)" : "Send Heartbeat Acknowledgments (ACK) (AUTO disabled)");
|
||||
autoAckHB->setEnabled(ui->autoReplyButton->isChecked());
|
||||
autoAckHB->setCheckable(true);
|
||||
autoAckHB->setChecked(m_hbAutoAck);
|
||||
connect(autoAckHB, &QAction::triggered, this, [this, autoAckHB](){
|
||||
m_hbAutoAck = autoAckHB->isChecked();
|
||||
updateRepeatButtonDisplay();
|
||||
});
|
||||
menu->addSeparator();
|
||||
|
||||
if(m_hbInterval > 0){
|
||||
auto startStop = menu->addAction(ui->hbMacroButton->isChecked() ? "Stop Heartbeat Timer" : "Start Heartbeat Timer");
|
||||
connect(startStop, &QAction::triggered, this, [this](){ ui->hbMacroButton->toggle(); });
|
||||
@@ -7117,8 +7191,13 @@ void MainWindow::sendHeartbeat(){
|
||||
processTxQueue();
|
||||
}
|
||||
|
||||
#define SEND_SNR_IN_ACK 1
|
||||
void MainWindow::sendHeartbeatAck(QString to, int snr, QString extra){
|
||||
#if SEND_SNR_IN_ACK
|
||||
auto message = QString("%1 ACK %2 %3").arg(to).arg(Varicode::formatSNR(snr)).arg(extra).trimmed();
|
||||
#else
|
||||
auto message = QString("%1 ACK %2").arg(to).arg(extra).trimmed();
|
||||
#endif
|
||||
|
||||
auto f = m_config.heartbeat_anywhere() ? -1 : findFreeFreqOffset(500, 1000, 50);
|
||||
|
||||
@@ -7267,11 +7346,13 @@ void MainWindow::buildShowColumnsMenu(QMenu *menu, QString tableKey){
|
||||
};
|
||||
|
||||
if(tableKey == "call"){
|
||||
columnKeys.prepend({"Worked Before Flag", "flag"});
|
||||
columnKeys.prepend({"Callsign", "callsign"});
|
||||
columnKeys.append({
|
||||
{"Grid Locator", "grid"},
|
||||
{"Distance", "distance"}
|
||||
{"Distance", "distance"},
|
||||
{"Worked Before", "log"},
|
||||
{"Logged Name", "logName"},
|
||||
{"Logged Comment", "logComment"},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7730,6 +7811,20 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
|
||||
|
||||
if(m_config.transmit_directed()) toggleTx(true);
|
||||
});
|
||||
|
||||
|
||||
auto ditDitAction = menu->addAction(QString("%1 DIT DIT - End of contact / Two bits").arg(call).trimmed());
|
||||
connect(ditDitAction, &QAction::triggered, this, [this](){
|
||||
|
||||
QString selectedCall = callsignSelected();
|
||||
if(selectedCall.isEmpty()){
|
||||
return;
|
||||
}
|
||||
|
||||
addMessageText(QString("%1 DIT DIT").arg(selectedCall), true);
|
||||
|
||||
if(m_config.transmit_directed()) toggleTx(true);
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::buildRelayMenu(QMenu *menu){
|
||||
@@ -7987,6 +8082,9 @@ void MainWindow::on_tableWidgetRXAll_selectionChanged(const QItemSelection &/*se
|
||||
} else {
|
||||
placeholderText = QString("Type your outgoing directed message to %1 here.").arg(selectedCall);
|
||||
|
||||
// when we select a callsign, use it as the qso start time
|
||||
m_dateTimeQSOOn = DriftingDateTime::currentDateTimeUtc();
|
||||
|
||||
// TODO: jsherer - move this to a generic "callsign changed" signal
|
||||
if(m_config.heartbeat_qso_pause()){
|
||||
// don't hb if we select a callsign... (but we should keep track so if we deselect, we restore our hb)
|
||||
@@ -8518,6 +8616,7 @@ void MainWindow::handle_transceiver_update (Transceiver::TransceiverState const&
|
||||
}
|
||||
|
||||
if (m_config.spot_to_reporting_networks ()) {
|
||||
spotSetLocal();
|
||||
pskSetLocal();
|
||||
aprsSetLocal();
|
||||
}
|
||||
@@ -8768,6 +8867,16 @@ bool MainWindow::shortList(QString callsign)
|
||||
return b;
|
||||
}
|
||||
|
||||
void MainWindow::spotSetLocal ()
|
||||
{
|
||||
auto call = m_config.my_callsign();
|
||||
auto grid = m_config.my_grid();
|
||||
auto info = replaceMacros(m_config.my_info(), buildMacroValues(), true);
|
||||
auto ver = QString {"JS8Call v" + version() }.simplified ();
|
||||
qDebug() << "SpotClient Set Local Station:" << call << grid << info << ver;
|
||||
m_spotClient->setLocalStation(call, grid, info, ver);
|
||||
}
|
||||
|
||||
void MainWindow::pskSetLocal ()
|
||||
{
|
||||
auto info = replaceMacros(m_config.my_info(), buildMacroValues(), true);
|
||||
@@ -9078,15 +9187,16 @@ void MainWindow::updateButtonDisplay(){
|
||||
}
|
||||
|
||||
void MainWindow::updateRepeatButtonDisplay(){
|
||||
auto hbBase = m_hbAutoAck && ui->autoReplyButton->isChecked() ? "HB + ACK" : "HB";
|
||||
if(ui->hbMacroButton->isChecked() && m_hbInterval > 0 && m_nextHeartbeat.isValid()){
|
||||
auto secs = DriftingDateTime::currentDateTimeUtc().secsTo(m_nextHeartbeat);
|
||||
if(secs > 0){
|
||||
ui->hbMacroButton->setText(QString("HB (%1)").arg(secs));
|
||||
ui->hbMacroButton->setText(QString("%1 (%2)").arg(hbBase).arg(secs));
|
||||
} else {
|
||||
ui->hbMacroButton->setText(QString("HB (now)"));
|
||||
ui->hbMacroButton->setText(QString("%1 (now)").arg(hbBase));
|
||||
}
|
||||
} else {
|
||||
ui->hbMacroButton->setText("HB");
|
||||
ui->hbMacroButton->setText(hbBase);
|
||||
}
|
||||
|
||||
if(ui->cqMacroButton->isChecked() && m_cqInterval > 0 && m_nextCQ.isValid()){
|
||||
@@ -9872,6 +9982,11 @@ void MainWindow::processCommandActivity() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// PROCESS @JS8NET SPOTS FOR EVERYONE
|
||||
if (d.to == "@JS8NET"){
|
||||
spotCmd(d);
|
||||
}
|
||||
|
||||
// we're only responding to allcall, groupcalls, and our callsign at this point, so we'll end after logging the callsigns we've heard
|
||||
if (!isAllCall && !toMe && !isGroupCall) {
|
||||
continue;
|
||||
@@ -9996,7 +10111,7 @@ void MainWindow::processCommandActivity() {
|
||||
}
|
||||
|
||||
// HACK: if this is an autoreply cmd and relay path is populated and cmd is not MSG or MSG TO:, then swap out the relay path
|
||||
if(Varicode::isCommandAutoreply(d.cmd) && !d.relayPath.isEmpty() && !d.cmd.startsWith(" MSG")){
|
||||
if(Varicode::isCommandAutoreply(d.cmd) && !d.relayPath.isEmpty() && !d.cmd.startsWith(" MSG") && !d.cmd.startsWith(" QUERY")){
|
||||
d.from = d.relayPath;
|
||||
}
|
||||
|
||||
@@ -10076,7 +10191,7 @@ void MainWindow::processCommandActivity() {
|
||||
}
|
||||
|
||||
// PROCESS RELAY
|
||||
else if (d.cmd == ">" && !isAllCall) {
|
||||
else if (d.cmd == ">" && !isAllCall && !m_config.relay_off()) {
|
||||
|
||||
// 1. see if there are any more hops to process
|
||||
// 2. if so, forward
|
||||
@@ -10088,12 +10203,12 @@ void MainWindow::processCommandActivity() {
|
||||
auto match = re.match(text);
|
||||
|
||||
// if the text starts with a callsign, and relay is not disabled, and this is not a group callsign, then relay.
|
||||
if(match.hasMatch() && !m_config.relay_off() && !isGroupCall){
|
||||
if(match.hasMatch() && !isGroupCall){
|
||||
// replace freetext with relayed free text
|
||||
if(match.captured("type") != ">"){
|
||||
text = text.replace(match.capturedStart("type"), match.capturedLength("type"), ">");
|
||||
}
|
||||
reply = QString("%1 DE %2").arg(text).arg(d.from);
|
||||
reply = QString("%1 VIA %2").arg(text).arg(d.from);
|
||||
|
||||
// otherwise, as long as we're not an ACK...alert the user and either send an ACK or Message
|
||||
} else if(!d.text.startsWith("ACK")) {
|
||||
@@ -10132,23 +10247,35 @@ void MainWindow::processCommandActivity() {
|
||||
}
|
||||
|
||||
// HACK: "MSG TO:" should be supported but contains a space :(
|
||||
if(!relayedCmds.isEmpty() && first == " MSG"){
|
||||
auto second = relayedCmds.first();
|
||||
if(second == "TO:"){
|
||||
first = " MSG TO:";
|
||||
relayedCmds.removeFirst();
|
||||
} else if(second.startsWith("TO:")){
|
||||
first = " MSG TO:";
|
||||
relayedCmds.replace(0, second.mid(3));
|
||||
if(!relayedCmds.isEmpty()){
|
||||
if(first == " MSG"){
|
||||
auto second = relayedCmds.first();
|
||||
if(second == "TO:"){
|
||||
first = " MSG TO:";
|
||||
relayedCmds.removeFirst();
|
||||
} else if(second.startsWith("TO:")){
|
||||
first = " MSG TO:";
|
||||
relayedCmds.replace(0, second.mid(3));
|
||||
}
|
||||
} else if (first == " QUERY"){
|
||||
auto second = relayedCmds.first();
|
||||
if(second == "MSGS" || second == "MSGS?"){
|
||||
first = " QUERY MSGS";
|
||||
relayedCmds.removeFirst();
|
||||
}
|
||||
else if(second == "CALL"){
|
||||
first = " QUERY CALL";
|
||||
relayedCmds.removeFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(valid && Varicode::isCommandAutoreply(first)){
|
||||
if(Varicode::isCommandAllowed(first) && Varicode::isCommandAutoreply(first)){
|
||||
CommandDetail rd = {};
|
||||
rd.bits = d.bits;
|
||||
rd.cmd = first;
|
||||
rd.freq = d.freq;
|
||||
rd.from = d.from;
|
||||
rd.from = d.from; // note, MSG and QUERY commands are not set with from as the relay path.
|
||||
rd.relayPath = d.relayPath;
|
||||
rd.text = relayedCmds.join(" "); //d.text;
|
||||
rd.to = d.to;
|
||||
@@ -10212,7 +10339,7 @@ void MainWindow::processCommandActivity() {
|
||||
|
||||
// PROCESS ACTIVE HEARTBEAT
|
||||
// if we have auto reply enabled and we are heartbeating and selcall is not enabled
|
||||
else if (d.cmd == " HB" && ui->autoReplyButton->isChecked() && ui->hbMacroButton->isChecked() && m_hbInterval > 0){
|
||||
else if (d.cmd == " HB" && ui->autoReplyButton->isChecked() && m_hbAutoAck){
|
||||
|
||||
// check to see if we have a message for a station who is heartbeating
|
||||
QString extra;
|
||||
@@ -10272,6 +10399,14 @@ void MainWindow::processCommandActivity() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// PROCESS NACKS
|
||||
else if (d.cmd == " NACK" && !isAllCall){
|
||||
qDebug() << "skipping incoming nack" << d.text;
|
||||
|
||||
// make sure this is explicit
|
||||
continue;
|
||||
}
|
||||
|
||||
// PROCESS BUFFERED CMD
|
||||
else if (d.cmd == " CMD" && !isAllCall){
|
||||
qDebug() << "skipping incoming command" << d.text;
|
||||
@@ -10282,7 +10417,14 @@ void MainWindow::processCommandActivity() {
|
||||
|
||||
// PROCESS BUFFERED QUERY
|
||||
else if (d.cmd == " QUERY" && !isAllCall){
|
||||
auto who = d.from;
|
||||
auto who = d.from; // keep in mind, this is the sender, not the original requestor if relayed
|
||||
auto replyPath = d.from;
|
||||
|
||||
if(d.relayPath.contains(">")){
|
||||
auto path = d.relayPath.split(">");
|
||||
who = path.last();
|
||||
replyPath = d.relayPath;
|
||||
}
|
||||
|
||||
QStringList segs = d.text.split(" ");
|
||||
if(segs.isEmpty()){
|
||||
@@ -10327,8 +10469,8 @@ void MainWindow::processCommandActivity() {
|
||||
inbox.set(mid, msg);
|
||||
|
||||
// and reply
|
||||
reply = QString("%1 MSG %2 DE %3");
|
||||
reply = reply.arg(who);
|
||||
reply = QString("%1 MSG %2 FROM %3");
|
||||
reply = reply.arg(replyPath);
|
||||
reply = reply.arg(text);
|
||||
reply = reply.arg(from);
|
||||
}
|
||||
@@ -10336,23 +10478,35 @@ void MainWindow::processCommandActivity() {
|
||||
|
||||
// PROCESS BUFFERED QUERY MSGS
|
||||
else if (d.cmd == " QUERY MSGS" && ui->autoReplyButton->isChecked()){
|
||||
auto who = d.from;
|
||||
auto who = d.from; // keep in mind, this is the sender, not the original requestor if relayed
|
||||
auto replyPath = d.from;
|
||||
|
||||
if(d.relayPath.contains(">")){
|
||||
auto path = d.relayPath.split(">");
|
||||
who = path.last();
|
||||
replyPath = d.relayPath;
|
||||
}
|
||||
|
||||
// if this is an allcall or a directed call, check to see if we have a stored message for user.
|
||||
// we reply yes if the user would be able to retreive a stored message
|
||||
auto mid = getNextMessageIdForCallsign(who);
|
||||
if(mid != -1){
|
||||
reply = QString("%1 YES MSG ID %2").arg(who).arg(mid);
|
||||
reply = QString("%1 YES MSG ID %2").arg(replyPath).arg(mid);
|
||||
}
|
||||
|
||||
// if this is not an allcall and we have no messages, reply no.
|
||||
if(!isAllCall && reply.isEmpty()){
|
||||
reply = QString("%1 NO").arg(who);
|
||||
reply = QString("%1 NO").arg(replyPath);
|
||||
}
|
||||
}
|
||||
|
||||
// PROCESS BUFFERED QUERY CALL
|
||||
else if (d.cmd == " QUERY CALL" && ui->autoReplyButton->isChecked()){
|
||||
auto replyPath = d.from;
|
||||
if(d.relayPath.contains(">")){
|
||||
replyPath = d.relayPath;
|
||||
}
|
||||
|
||||
auto who = d.text;
|
||||
if(who.isEmpty()){
|
||||
continue;
|
||||
@@ -10372,20 +10526,20 @@ void MainWindow::processCommandActivity() {
|
||||
}
|
||||
|
||||
if(baseCall == cd.call || baseCall == Radio::base_callsign(cd.call)){
|
||||
auto r = QString("%1 ACK %2").arg(cd.call).arg(Varicode::formatSNR(cd.snr));
|
||||
auto r = QString("%1 (%2)").arg(Varicode::formatSNR(cd.snr)).arg(since(cd.utcTimestamp)).trimmed();
|
||||
replies.append(r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!replies.isEmpty()){
|
||||
replies.prepend(QString("%1 YES").arg(d.from));
|
||||
replies.prepend(QString("%1 YES").arg(replyPath));
|
||||
}
|
||||
|
||||
reply = replies.join("\n");
|
||||
reply = replies.join(" ");
|
||||
|
||||
if(!reply.isEmpty()){
|
||||
if(isAllCall){
|
||||
// since all pings are technically @ALLCALL, let's bump the allcall cache here...
|
||||
m_txAllcallCommandCache.insert(d.from, new QDateTime(now), 25);
|
||||
}
|
||||
}
|
||||
@@ -10585,7 +10739,7 @@ int MainWindow::getNextMessageIdForCallsign(QString callsign){
|
||||
|
||||
QStringList MainWindow::parseRelayPathCallsigns(QString from, QString text){
|
||||
QStringList calls;
|
||||
QString callDePattern = {R"(\sDE\s(?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?)\b)"};
|
||||
QString callDePattern = {R"(\sVIA\s(?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?)\b)"};
|
||||
QRegularExpression re(callDePattern);
|
||||
auto iter = re.globalMatch(text);
|
||||
while(iter.hasNext()){
|
||||
@@ -10607,7 +10761,7 @@ void MainWindow::processAlertReplyForCommand(CommandDetail d, QString from, QStr
|
||||
|
||||
QString fromReplace = QString{};
|
||||
foreach(auto call, calls){
|
||||
fromReplace.append(" DE ");
|
||||
fromReplace.append(" VIA ");
|
||||
fromReplace.append(call);
|
||||
}
|
||||
|
||||
@@ -10676,6 +10830,7 @@ void MainWindow::processSpots() {
|
||||
auto dial = dialFrequency();
|
||||
|
||||
// Process spots to be sent...
|
||||
spotSetLocal();
|
||||
pskSetLocal();
|
||||
aprsSetLocal();
|
||||
|
||||
@@ -10687,6 +10842,7 @@ void MainWindow::processSpots() {
|
||||
|
||||
qDebug() << "spotting call to reporting networks" << d.call << d.snr << d.freq;
|
||||
|
||||
spotReport(d.freq, d.snr, d.call, d.grid);
|
||||
pskLogReport("JS8", d.freq, d.snr, d.call, d.grid);
|
||||
aprsLogReport(d.freq, d.snr, d.call, d.grid);
|
||||
|
||||
@@ -10871,6 +11027,7 @@ void MainWindow::displayBandActivity() {
|
||||
|
||||
QList < ActivityDetail > items = m_bandActivity[offset];
|
||||
if (items.length() > 0) {
|
||||
QDateTime timestamp;
|
||||
QStringList text;
|
||||
QString age;
|
||||
int snr = 0;
|
||||
@@ -10922,6 +11079,7 @@ void MainWindow::displayBandActivity() {
|
||||
text.append(item.text);
|
||||
snr = item.snr;
|
||||
age = since(item.utcTimestamp);
|
||||
timestamp = item.utcTimestamp;
|
||||
tdrift = item.tdrift;
|
||||
}
|
||||
|
||||
@@ -10938,11 +11096,13 @@ void MainWindow::displayBandActivity() {
|
||||
offsetItem->setData(Qt::UserRole, QVariant(offset));
|
||||
ui->tableWidgetRXAll->setItem(row, col++, offsetItem);
|
||||
|
||||
auto ageItem = new QTableWidgetItem(QString("%1").arg(age));
|
||||
auto ageItem = new QTableWidgetItem(age);
|
||||
ageItem->setTextAlignment(Qt::AlignCenter | Qt::AlignVCenter);
|
||||
ageItem->setToolTip(timestamp.toString());
|
||||
ui->tableWidgetRXAll->setItem(row, col++, ageItem);
|
||||
|
||||
auto snrItem = new QTableWidgetItem(QString("%1 dB").arg(Varicode::formatSNR(snr)));
|
||||
auto snrText = Varicode::formatSNR(snr);
|
||||
auto snrItem = new QTableWidgetItem(snrText.isEmpty() ? "" : QString("%1 dB").arg(snrText));
|
||||
snrItem->setTextAlignment(Qt::AlignCenter | Qt::AlignVCenter);
|
||||
ui->tableWidgetRXAll->setItem(row, col++, snrItem);
|
||||
|
||||
@@ -11167,7 +11327,12 @@ void MainWindow::displayCallActivity() {
|
||||
|
||||
// icon flags (flag -> star -> empty)
|
||||
bool hasMessage = m_rxInboxCountCache.value(d.call, 0) > 0;
|
||||
bool hasAck = d.ackTimestamp.isValid();
|
||||
|
||||
// display telephone icon if called cq in the past 5 minutes
|
||||
bool hasCQ = d.cqTimestamp.isValid() && d.cqTimestamp.secsTo(now) / 60 < 5;
|
||||
|
||||
// display star if they've acked a message from us
|
||||
bool hasACK = d.ackTimestamp.isValid();
|
||||
|
||||
if (!isCallSelected && !hasMessage && callsignAging && d.utcTimestamp.secsTo(now) / 60 >= callsignAging) {
|
||||
continue;
|
||||
@@ -11185,32 +11350,33 @@ void MainWindow::displayCallActivity() {
|
||||
QString displayCall = d.call;
|
||||
#endif
|
||||
|
||||
QString flag;
|
||||
if(m_logBook.hasWorkedBefore(d.call, "")){
|
||||
// unicode checkmark
|
||||
flag = "\u2713";
|
||||
}
|
||||
|
||||
auto iconItem = new QTableWidgetItem(hasMessage ? "\u2691" : hasAck ? "\u2605" : "");
|
||||
iconItem->setData(Qt::UserRole, QVariant((d.call)));
|
||||
|
||||
auto iconItem = new QTableWidgetItem(hasMessage ? "\u2691" : hasACK ? "\u2605" : hasCQ ? "\u260E" : "");
|
||||
iconItem->setData(Qt::UserRole, QVariant(d.call));
|
||||
iconItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
ui->tableWidgetCalls->setItem(row, col++, iconItem);
|
||||
if(hasMessage || hasAck){
|
||||
if(hasMessage || hasACK || hasCQ){
|
||||
showIconColumn = true;
|
||||
}
|
||||
|
||||
auto displayItem = new QTableWidgetItem(displayCall);
|
||||
displayItem->setData(Qt::UserRole, QVariant(d.call));
|
||||
displayItem->setToolTip(generateCallDetail(displayCall));
|
||||
|
||||
ui->tableWidgetCalls->setItem(row, col++, displayItem);
|
||||
|
||||
auto flagItem = new QTableWidgetItem(flag);
|
||||
flagItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
ui->tableWidgetCalls->setItem(row, col++, flagItem);
|
||||
#if ONLY_SHOW_HEARD_CALLSIGNS
|
||||
if(d.utcTimestamp.isValid()){
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(QString("%1").arg(since(d.utcTimestamp))));
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(QString("%1 dB").arg(Varicode::formatSNR(d.snr))));
|
||||
#else
|
||||
if(true){
|
||||
#endif
|
||||
auto ageItem = new QTableWidgetItem(since(d.utcTimestamp));
|
||||
ageItem->setTextAlignment(Qt::AlignCenter | Qt::AlignVCenter);
|
||||
ageItem->setToolTip(d.utcTimestamp.toString());
|
||||
ui->tableWidgetCalls->setItem(row, col++, ageItem);
|
||||
|
||||
auto snrText = Varicode::formatSNR(d.snr);
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(snrText.isEmpty() ? "" : QString("%1 dB").arg(snrText)));
|
||||
|
||||
auto offsetItem = new QTableWidgetItem(QString("%1 Hz").arg(d.freq));
|
||||
offsetItem->setData(Qt::UserRole, QVariant(d.freq));
|
||||
@@ -11224,7 +11390,38 @@ void MainWindow::displayCallActivity() {
|
||||
|
||||
auto distanceItem = new QTableWidgetItem(calculateDistance(d.grid));
|
||||
distanceItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, col++, distanceItem);
|
||||
ui->tableWidgetCalls->setItem(row, col++, distanceItem);
|
||||
|
||||
QString flag;
|
||||
if(m_logBook.hasWorkedBefore(d.call, "")){
|
||||
// unicode checkmark
|
||||
flag = "\u2713";
|
||||
}
|
||||
auto workedBeforeItem = new QTableWidgetItem(flag);
|
||||
workedBeforeItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
ui->tableWidgetCalls->setItem(row, col++, workedBeforeItem);
|
||||
|
||||
QString logDetailDate;
|
||||
QString logDetailName;
|
||||
QString logDetailComment;
|
||||
|
||||
if(showColumn("call", "log") || showColumn("call", "logName") || showColumn("call", "logComment")){
|
||||
m_logBook.findCallDetails(d.call, logDetailDate, logDetailName, logDetailComment);
|
||||
}
|
||||
|
||||
if(!logDetailDate.isEmpty()){
|
||||
auto lastLogged = QDate::fromString(logDetailDate, "yyyyMMdd");
|
||||
|
||||
workedBeforeItem->setToolTip(QString("Last Logged: %1").arg(lastLogged.toString()));
|
||||
}
|
||||
|
||||
auto logNameItem = new QTableWidgetItem(logDetailName);
|
||||
logNameItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
ui->tableWidgetCalls->setItem(row, col++, logNameItem);
|
||||
|
||||
auto logCommentItem = new QTableWidgetItem(logDetailComment);
|
||||
logCommentItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
ui->tableWidgetCalls->setItem(row, col++, logCommentItem);
|
||||
|
||||
} else {
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // age
|
||||
@@ -11233,8 +11430,9 @@ void MainWindow::displayCallActivity() {
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // tdrift
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // grid
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // distance
|
||||
|
||||
//ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(""));
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // worked before
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // log name
|
||||
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // log comment
|
||||
}
|
||||
|
||||
if (isCallSelected) {
|
||||
@@ -11277,13 +11475,15 @@ void MainWindow::displayCallActivity() {
|
||||
// Hide columns
|
||||
ui->tableWidgetCalls->setColumnHidden(0, !showIconColumn);
|
||||
ui->tableWidgetCalls->setColumnHidden(1, !showColumn("call", "callsign"));
|
||||
ui->tableWidgetCalls->setColumnHidden(2, !showColumn("call", "flag"));
|
||||
ui->tableWidgetCalls->setColumnHidden(3, !showColumn("call", "timestamp"));
|
||||
ui->tableWidgetCalls->setColumnHidden(4, !showColumn("call", "snr"));
|
||||
ui->tableWidgetCalls->setColumnHidden(5, !showColumn("call", "offset"));
|
||||
ui->tableWidgetCalls->setColumnHidden(6, !showColumn("call", "tdrift", false));
|
||||
ui->tableWidgetCalls->setColumnHidden(7, !showColumn("call", "grid", false));
|
||||
ui->tableWidgetCalls->setColumnHidden(8, !showColumn("call", "distance", false));
|
||||
ui->tableWidgetCalls->setColumnHidden(2, !showColumn("call", "timestamp"));
|
||||
ui->tableWidgetCalls->setColumnHidden(3, !showColumn("call", "snr"));
|
||||
ui->tableWidgetCalls->setColumnHidden(4, !showColumn("call", "offset"));
|
||||
ui->tableWidgetCalls->setColumnHidden(5, !showColumn("call", "tdrift", false));
|
||||
ui->tableWidgetCalls->setColumnHidden(6, !showColumn("call", "grid", false));
|
||||
ui->tableWidgetCalls->setColumnHidden(7, !showColumn("call", "distance", false));
|
||||
ui->tableWidgetCalls->setColumnHidden(8, !showColumn("call", "log"));
|
||||
ui->tableWidgetCalls->setColumnHidden(9, !showColumn("call", "logName"));
|
||||
ui->tableWidgetCalls->setColumnHidden(10, !showColumn("call", "logComment"));
|
||||
|
||||
// Resize the table columns
|
||||
ui->tableWidgetCalls->resizeColumnToContents(0);
|
||||
@@ -11294,6 +11494,8 @@ void MainWindow::displayCallActivity() {
|
||||
ui->tableWidgetCalls->resizeColumnToContents(5);
|
||||
ui->tableWidgetCalls->resizeColumnToContents(6);
|
||||
ui->tableWidgetCalls->resizeColumnToContents(7);
|
||||
ui->tableWidgetCalls->resizeColumnToContents(8);
|
||||
ui->tableWidgetCalls->resizeColumnToContents(9);
|
||||
|
||||
// Reset the scroll position
|
||||
ui->tableWidgetCalls->verticalScrollBar()->setValue(currentScrollPos);
|
||||
@@ -11326,6 +11528,22 @@ void MainWindow::emitPTT(bool on){
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::emitTones(){
|
||||
if(!m_config.udpEnabled()){
|
||||
return;
|
||||
}
|
||||
|
||||
// emit tone numbers to network
|
||||
QVariantList t;
|
||||
for(int i = 0; i < NUM_FT8_SYMBOLS; i++){
|
||||
t.append(QVariant((int)itone[i]));
|
||||
}
|
||||
|
||||
sendNetworkMessage("TX.FRAME", "", {
|
||||
{"TONES", t}
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::networkMessage(Message const &message)
|
||||
{
|
||||
if(!m_config.udpEnabled()){
|
||||
|
||||
+13
-3
@@ -44,6 +44,7 @@
|
||||
#include "qpriorityqueue.h"
|
||||
#include "varicode.h"
|
||||
#include "MessageClient.hpp"
|
||||
#include "SpotClient.h"
|
||||
#include "APRSISClient.h"
|
||||
#include "keyeater.h"
|
||||
|
||||
@@ -383,6 +384,7 @@ private slots:
|
||||
void on_cbFirst_toggled(bool b);
|
||||
void on_cbAutoSeq_toggled(bool b);
|
||||
void emitPTT(bool on);
|
||||
void emitTones();
|
||||
void networkMessage(Message const &message);
|
||||
void sendNetworkMessage(QString const &type, QString const &message);
|
||||
void sendNetworkMessage(QString const &type, QString const &message, const QMap<QString, QVariant> ¶ms);
|
||||
@@ -712,6 +714,7 @@ private:
|
||||
QString through;
|
||||
QString grid;
|
||||
int freq;
|
||||
QDateTime cqTimestamp;
|
||||
QDateTime ackTimestamp;
|
||||
QDateTime utcTimestamp;
|
||||
int snr;
|
||||
@@ -826,9 +829,11 @@ private:
|
||||
|
||||
QMap<QString, int> m_rxInboxCountCache; // call -> count
|
||||
|
||||
QMap<QString, QMap<QString, CallDetail>> m_callActivityCache; // band -> call activity
|
||||
QMap<QString, QMap<int, QList<ActivityDetail>>> m_bandActivityCache; // band -> band activity
|
||||
QMap<QString, QString> m_rxTextCache; // band -> rx text
|
||||
QMap<QString, QMap<QString, CallDetail>> m_callActivityBandCache; // band -> call activity
|
||||
QMap<QString, QMap<int, QList<ActivityDetail>>> m_bandActivityBandCache; // band -> band activity
|
||||
QMap<QString, QString> m_rxTextBandCache; // band -> rx text
|
||||
QMap<QString, QMap<QString, QSet<QString>>> m_heardGraphOutgoingBandCache; // band -> heard in
|
||||
QMap<QString, QMap<QString, QSet<QString>>> m_heardGraphIncomingBandCache; // band -> heard out
|
||||
|
||||
JSCChecker * m_checker;
|
||||
|
||||
@@ -856,6 +861,7 @@ private:
|
||||
QQueue<QString> m_foxQSOinProgress; //QSOs in progress: Fox has sent a report
|
||||
QQueue<qint64> m_foxRateQueue;
|
||||
|
||||
bool m_hbAutoAck;
|
||||
bool m_hbHidden;
|
||||
int m_hbInterval;
|
||||
int m_cqInterval;
|
||||
@@ -891,6 +897,7 @@ private:
|
||||
QProgressDialog m_optimizingProgress;
|
||||
MessageClient * m_messageClient;
|
||||
PSK_Reporter *psk_Reporter;
|
||||
SpotClient *m_spotClient;
|
||||
APRSISClient * m_aprsClient;
|
||||
DisplayManual m_manual;
|
||||
QHash<QString, QVariant> m_pwrBandTxMemory; // Remembers power level by band
|
||||
@@ -915,8 +922,11 @@ private:
|
||||
bool shortList(QString callsign);
|
||||
void transmit (double snr = 99.);
|
||||
void rigFailure (QString const& reason);
|
||||
void spotSetLocal();
|
||||
void pskSetLocal ();
|
||||
void aprsSetLocal ();
|
||||
void spotReport(int offset, int snr, QString callsign, QString grid);
|
||||
void spotCmd(CommandDetail cmd);
|
||||
void pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid);
|
||||
void aprsLogReport(int offset, int snr, QString callsign, QString grid);
|
||||
Radio::Frequency dialFrequency();
|
||||
|
||||
+26
-10
@@ -203,7 +203,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Current Frequency</string>
|
||||
<string>Set Current Frequency</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
@@ -297,7 +297,7 @@ QPushButton[oob="true"] {
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Current frequency offset</string>
|
||||
<string>Set Current Frequency Offset</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QLabel {
|
||||
@@ -401,6 +401,12 @@ color : white;
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="mouseTracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Set Callsign and Grid</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QLabel {
|
||||
font-family: MS Shell Dlg 2;
|
||||
@@ -1242,14 +1248,6 @@ QTextEdit[transmitting="true"] {
|
||||
<string>Callsigns</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>✓</string>
|
||||
</property>
|
||||
<property name="textAlignment">
|
||||
<set>AlignCenter</set>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Age</string>
|
||||
@@ -1280,6 +1278,24 @@ QTextEdit[transmitting="true"] {
|
||||
<string>Distance</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>✓</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Worked Before</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Comment</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
|
||||
+8
-7
@@ -50,8 +50,9 @@ QMap<QString, int> directed_cmds = {
|
||||
{" SNR?", 0 }, // query snr
|
||||
{"?", 0 }, // compat
|
||||
|
||||
//{" ", 1 }, // unused
|
||||
//{" ", 2 }, // unused
|
||||
{" DIT DIT", 1 }, // unused
|
||||
|
||||
{" NACK", 2 }, // negative acknowledge
|
||||
|
||||
{" HEARING?", 3 }, // query station calls heard
|
||||
|
||||
@@ -104,7 +105,7 @@ QMap<QString, int> directed_cmds = {
|
||||
};
|
||||
|
||||
// commands allowed to be processed
|
||||
QSet<int> allowed_cmds = {-1, 0, /*1,*/ /*2,*/ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||
QSet<int> allowed_cmds = {-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||
|
||||
// commands that result in an autoreply (which can be relayed)
|
||||
QSet<int> autoreply_cmds = {0, 3, 4, 6, 9, 10, 11, 12, 13, 16, 30};
|
||||
@@ -129,10 +130,10 @@ QMap<int, int> checksum_cmds = {
|
||||
};
|
||||
|
||||
QString callsign_pattern = QString("(?<callsign>[@]?[A-Z0-9/]+)");
|
||||
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|QSL[?]|HW CPY[?]|APRS[:]|MSG TO[:]|SNR[?]|INFO[?]|GRID[?]|STATUS[?]|QUERY MSGS[?]|HEARING[?]|(?:(?:STATUS|HEARING|QUERY CALL|QUERY MSGS|QUERY|CMD|MSG|ACK|73|YES|NO|SNR|QSL|RR|SK|FB|INFO|GRID)(?=[ ]|$))|[?> ]))?");
|
||||
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|QSL[?]|HW CPY[?]|APRS[:]|MSG TO[:]|SNR[?]|INFO[?]|GRID[?]|STATUS[?]|QUERY MSGS[?]|HEARING[?]|(?:(?:STATUS|HEARING|QUERY CALL|QUERY MSGS|QUERY|CMD|MSG|NACK|ACK|73|YES|NO|SNR|QSL|RR|SK|FB|INFO|GRID|DIT DIT)(?=[ ]|$))|[?> ]))?");
|
||||
QString optional_grid_pattern = QString("(?<grid>\\s?[A-R]{2}[0-9]{2})?");
|
||||
QString optional_extended_grid_pattern = QString("^(?<grid>\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?");
|
||||
QString optional_num_pattern = QString("(?<num>(?<=SNR|ACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
|
||||
QString optional_num_pattern = QString("(?<num>(?<=SNR|\\bACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
|
||||
|
||||
QRegularExpression directed_re("^" +
|
||||
callsign_pattern +
|
||||
@@ -1545,8 +1546,8 @@ QStringList Varicode::unpackDirectedMessage(const QString &text, quint8 *pType){
|
||||
quint32 packed_to = Varicode::bitsToInt(bits.mid(31, 28));
|
||||
quint8 packed_cmd = Varicode::bitsToInt(bits.mid(59, 5));
|
||||
|
||||
bool portable_from = (extra >> 7) & 1 == 1;
|
||||
bool portable_to = (extra >> 6) & 1 == 1;
|
||||
bool portable_from = ((extra >> 7) & 1) == 1;
|
||||
bool portable_to = ((extra >> 6) & 1) == 1;
|
||||
extra = extra % 64;
|
||||
|
||||
QString from = Varicode::unpackCallsign(packed_from, portable_from);
|
||||
|
||||
Reference in New Issue
Block a user