highlighting callsigns, annotating callsigns, sort hounds on more criteria

This commit is contained in:
Brian Moran 2024-08-02 16:46:22 -07:00
parent 9a7ae401e7
commit 8e6ca93259
10 changed files with 310 additions and 63 deletions

View File

@ -392,7 +392,22 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
} }
break; break;
default: case NetworkMessage::AnnotationInfo: {
QByteArray dx_call;
bool sort_order_provided{false};
quint32 sort_order{std::numeric_limits<quint32>::max()};
in >> dx_call >> sort_order_provided >> sort_order;
TRACE_UDP ("External Callsign Info:" << dx_call << "sort_order_provided:" << sort_order_provided
<< "sort_order:" << sort_order);
if (sort_order > 50000) sort_order = 50000;
if (check_status(in) != Fail) {
Q_EMIT
self_->annotation_info(QString::fromUtf8(dx_call), sort_order_provided, sort_order);
}
}
break;
default:
// Ignore // Ignore
// //
// Note that although server heartbeat messages are not // Note that although server heartbeat messages are not

View File

@ -123,7 +123,10 @@ public:
, bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call , bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call
, QString const& dx_grid, bool generate_messages); , QString const& dx_grid, bool generate_messages);
// this signal is emitted when network errors occur or if a host // this signal is emitted if the server has sent information about a callsign
Q_SIGNAL void annotation_info (QString const& dx_call, bool sort_order_provided, quint32 sort_order);
// this signal is emitted when network errors occur or if a host
// lookup fails // lookup fails
Q_SIGNAL void error (QString const&) const; Q_SIGNAL void error (QString const&) const;

View File

@ -462,6 +462,14 @@
* decoding may be impacted. A rough rule of thumb might be too * decoding may be impacted. A rough rule of thumb might be too
* limit the number of active highlighting requests to no more * limit the number of active highlighting requests to no more
* than 100. * than 100.
*
* Using a callsign of "CLEARALL!" and anything for the
* color values will clear the internal highlighting data. It will
* NOT remove the highlighting on the screen, however. The exclamation
* symbol is used to avoid accidental clearing of all highlighting
* data via a decoded callsign, since an exclamation symbol is not
* a valid character in a callsign.
* *
* The "Highlight last" field allows the sender to request that * The "Highlight last" field allows the sender to request that
* all instances of "Callsign" in the last period only, instead * all instances of "Callsign" in the last period only, instead
@ -494,7 +502,33 @@
* fields an empty value implies no change, for the quint32 Rx DF * fields an empty value implies no change, for the quint32 Rx DF
* and Frequency Tolerance fields the maximum quint32 value * and Frequency Tolerance fields the maximum quint32 value
* implies no change. Invalid or unrecognized values will be * implies no change. Invalid or unrecognized values will be
* silently ignored. * silently ignored. NOTE that if a mode/submode change occurs and
* the current frequency is NOT in the frequency table for that
* mode, a frequency change (to the default frequency for that band
* and mode) may occur.
*
* AnnotationInfo In 16 quint32
* Id (unique key) utf8
* DX Call utf8
* Sort Order Provided bool
* Sort Order quint32
*
* The server may send this message at any time. Sort orders can be used
* for sorting hound callers when in Fox mode. A typical usage is to
* "score" callsigns based on number of bands and/or modes worked using
* an external logging program during a DXpedition, to be able to give
* preference to calls that have not been worked before on any other
* band or mode. An external program can watch decodes from wsjt-x,
* then use this message to annotate the calls with a sort order. The
* hound queue can be displayed by that sort order. *
*
* If 'sort order provided' is true, the message also specifies a numeric
* sort order for the DX call.
*
* Invalid or unrecognized values will be silently ignored. A sort-order of
* ffffffff will remove the sort-order value from the internal table.
* Callsigns without a sort order will be valued at zero for sorting purposes
* in the hound display.
*/ */
#include <QDataStream> #include <QDataStream>
@ -526,6 +560,7 @@ namespace NetworkMessage
HighlightCallsign, HighlightCallsign,
SwitchConfiguration, SwitchConfiguration,
Configure, Configure,
AnnotationInfo,
maximum_message_type_ // ONLY add new message types maximum_message_type_ // ONLY add new message types
// immediately before here // immediately before here
}; };

View File

@ -40,6 +40,26 @@ void ActiveStations::changeFont (QFont const& font)
updateGeometry (); updateGeometry ();
} }
void ActiveStations::clearStations() {
m_textbuffer.clear();
m_decodes_by_frequency.clear();
}
void ActiveStations::addLine(QString line) {
QString m_textbuffer = "";
// "012700 -1 0.2 210 ~ KJ7COA JA2HGF -14"
unsigned freq = line.mid(16, 4).toUInt();
m_decodes_by_frequency[freq] = line;
// show them in frequency order
QMap<int, QString>::const_iterator i = m_decodes_by_frequency.constBegin();
m_textbuffer.clear();
while (i != m_decodes_by_frequency.constEnd()) {
m_textbuffer.append(i.value());
++i;
}
this->displayRecentStations(m_mode, m_textbuffer);
}
void ActiveStations::read_settings () void ActiveStations::read_settings ()
{ {
SettingsGroup group {settings_, "ActiveStations"}; SettingsGroup group {settings_, "ActiveStations"};
@ -60,8 +80,7 @@ void ActiveStations::write_settings ()
settings_->setValue("WantedOnly",ui->cbWantedOnly->isChecked()); settings_->setValue("WantedOnly",ui->cbWantedOnly->isChecked());
} }
void ActiveStations::displayRecentStations(QString mode, QString const& t) void ActiveStations::setupUi(QString mode) {
{
if(mode!=m_mode) { if(mode!=m_mode) {
m_mode=mode; m_mode=mode;
ui->cbReadyOnly->setText(" Ready only"); ui->cbReadyOnly->setText(" Ready only");
@ -71,24 +90,37 @@ void ActiveStations::displayRecentStations(QString mode, QString const& t)
ui->cbReadyOnly->setText("* CQ only"); ui->cbReadyOnly->setText("* CQ only");
} else if(m_mode=="Q65-pileup") { } else if(m_mode=="Q65-pileup") {
ui->header_label2->setText(" N Freq Call Grid El Age(h)"); ui->header_label2->setText(" N Freq Call Grid El Age(h)");
ui->cbWantedOnly->setText(QCoreApplication::translate("ActiveStations", "Wanted only", nullptr));
} else if(m_mode=="Fox Mode" || m_mode=="SuperFox Mode" ) {
ui->header_label2->setText(" UTC dB DT Freq " + tr("Message"));
ui->cbWantedOnly->setText(QCoreApplication::translate("ActiveStations", "My call only", nullptr));
this->setClickOK(true);
} else { } else {
ui->header_label2->setText(" N Call Grid Az S/N Freq Tx Age Pts"); ui->header_label2->setText(" N Call Grid Az S/N Freq Tx Age Pts");
ui->label->setText("Rate:"); ui->label->setText("Rate:");
ui->cbWantedOnly->setText(QCoreApplication::translate("ActiveStations", "Wanted only", nullptr));
} }
bool b=(m_mode.left(3)=="Q65"); bool b=(m_mode.left(3)=="Q65");
ui->bandChanges->setVisible(!b); bool is_fox_mode =(m_mode=="Fox Mode");
ui->cbReadyOnly->setVisible(m_mode!="Q65-pileup"); ui->bandChanges->setVisible(!b && !is_fox_mode);
ui->cbWantedOnly->setVisible(m_mode!="Q65-pileup"); ui->cbReadyOnly->setVisible(m_mode != "Q65-pileup" && !is_fox_mode);
ui->label_2->setVisible(!b); ui->cbWantedOnly->setVisible(m_mode != "Q65-pileup"); // this is used for "My call only" in Fox mode
ui->label_3->setVisible(!b); ui->label_2->setVisible(!b && !is_fox_mode);
ui->score->setVisible(!b); ui->label_3->setVisible(!b && !is_fox_mode);
ui->sbMaxRecent->setVisible(!b); ui->score->setVisible(!b && !is_fox_mode);
ui->sbMaxRecent->setVisible(!b && !is_fox_mode);
b=(m_mode!="Q65-pileup"); b=(m_mode!="Q65-pileup" && !is_fox_mode);
ui->sbMaxAge->setVisible(b); ui->sbMaxAge->setVisible(b);
ui->label->setVisible(b); ui->label->setVisible(b);
ui->rate->setVisible(b); ui->rate->setVisible(b);
} }
}
void ActiveStations::displayRecentStations(QString mode, QString const& t)
{
setupUi(mode);
bool bClickOK=m_clickOK; bool bClickOK=m_clickOK;
m_clickOK=false; m_clickOK=false;
ui->RecentStationsPlainTextEdit->setPlainText(t); ui->RecentStationsPlainTextEdit->setPlainText(t);
@ -143,7 +175,10 @@ void ActiveStations::on_textEdit_clicked()
if(text!="") { if(text!="") {
int nline=text.left(2).toInt(); int nline=text.left(2).toInt();
if(QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) nline=-nline; if(QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier)) nline=-nline;
emit callSandP(nline); if ("Fox Mode" != m_mode)
emit callSandP(nline);
else
emit queueActiveWindowHound(text);
} }
} }
} }

View File

@ -3,6 +3,7 @@
#define ARRL_DIGI_H_ #define ARRL_DIGI_H_
#include <QWidget> #include <QWidget>
#include <QMap>
class QSettings; class QSettings;
class QFont; class QFont;
@ -20,6 +21,7 @@ public:
explicit ActiveStations(QSettings *, QFont const&, QWidget * parent = 0); explicit ActiveStations(QSettings *, QFont const&, QWidget * parent = 0);
~ActiveStations(); ~ActiveStations();
void displayRecentStations(QString mode, QString const&); void displayRecentStations(QString mode, QString const&);
void setupUi(QString display_mode);
void changeFont (QFont const&); void changeFont (QFont const&);
int maxRecent(); int maxRecent();
int maxAge(); int maxAge();
@ -30,6 +32,8 @@ public:
void setRate(int n); void setRate(int n);
void setBandChanges(int n); void setBandChanges(int n);
void setScore(int n); void setScore(int n);
void clearStations();
void addLine(QString);
bool m_clickOK=false; bool m_clickOK=false;
bool m_bReadyOnly; bool m_bReadyOnly;
@ -41,14 +45,16 @@ private:
Q_SIGNAL void callSandP(int nline); Q_SIGNAL void callSandP(int nline);
Q_SIGNAL void activeStationsDisplay(); Q_SIGNAL void activeStationsDisplay();
Q_SIGNAL void cursorPositionChanged(); Q_SIGNAL void cursorPositionChanged();
Q_SIGNAL void queueActiveWindowHound(QString text);
Q_SLOT void on_cbReadyOnly_toggled(bool b); Q_SLOT void on_cbReadyOnly_toggled(bool b);
Q_SLOT void on_cbWantedOnly_toggled(bool b); Q_SLOT void on_cbWantedOnly_toggled(bool b);
Q_SLOT void on_textEdit_clicked(); Q_SLOT void on_textEdit_clicked();
// qint64 m_msec0=0;
QString m_mode=""; QString m_mode="";
QSettings * settings_; QSettings * settings_;
QString m_textbuffer=""; // F/H mode band decodes
QMap<int, QString> m_decodes_by_frequency; // store decodes for F/H band awareness by frequency
QScopedPointer<Ui::ActiveStations> ui; QScopedPointer<Ui::ActiveStations> ui;
}; };

View File

@ -560,6 +560,22 @@ void DisplayText::displayHoundToBeCalled(QString t, bool bAtTop, QColor bg, QCol
insertText(t, bg, fg, "", "", bAtTop ? QTextCursor::Start : QTextCursor::End); insertText(t, bg, fg, "", "", bAtTop ? QTextCursor::Start : QTextCursor::End);
} }
void DisplayText::setHighlightedHoundText(QString t) {
QColor bg;
QColor fg;
highlight_types types{Highlight::Call};
set_colours(m_config, &bg, &fg, types);
// t is multiple lines of text, each line is a hound calling
// iterate through each line and highlight the callsign
auto lines = t.split(QChar('\n'), Qt::SkipEmptyParts);
clear();
foreach (auto line, lines)
{
auto fields = line.split(QChar(' '), Qt::SkipEmptyParts);
insertText(line, bg, fg, fields.first(), QString{});
}
}
namespace namespace
{ {
void update_selection (QTextCursor& cursor, QColor const& bg, QColor const& fg) void update_selection (QTextCursor& cursor, QColor const& bg, QColor const& fg)
@ -619,6 +635,11 @@ void DisplayText::highlight_callsign (QString const& callsign, QColor const& bg,
{ {
return; return;
} }
if (callsign == "CLEARALL!") // programmatic means of clearing all highlighting
{
highlighted_calls_.clear();
return;
}
auto regexp = callsign; auto regexp = callsign;
// allow for hashed callsigns and escape any regexp metacharacters // allow for hashed callsigns and escape any regexp metacharacters
QRegularExpression target {QString {"<?"} QRegularExpression target {QString {"<?"}

View File

@ -36,6 +36,7 @@ public:
double TRperiod, bool bSuperfox); double TRperiod, bool bSuperfox);
void displayQSY(QString text); void displayQSY(QString text);
void displayHoundToBeCalled(QString t, bool bAtTop=false, QColor bg = QColor {}, QColor fg = QColor {}); void displayHoundToBeCalled(QString t, bool bAtTop=false, QColor bg = QColor {}, QColor fg = QColor {});
void setHighlightedHoundText(QString text);
void new_period (); void new_period ();
QString CQPriority(){return m_CQPriority;}; QString CQPriority(){return m_CQPriority;};
qint32 m_points; qint32 m_points;

View File

@ -241,6 +241,7 @@ namespace
QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>;$]*"}; QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>;$]*"};
// grid exact match excluding RR73 // grid exact match excluding RR73
QRegularExpression grid_regexp {"\\A(?![Rr]{2}73)[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}){0,1}\\z"}; QRegularExpression grid_regexp {"\\A(?![Rr]{2}73)[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}){0,1}\\z"};
QRegularExpression non_r_db_regexp {"\\A[-+]{1}[0-9]{1,2}\\z"};
auto quint32_max = std::numeric_limits<quint32>::max (); auto quint32_max = std::numeric_limits<quint32>::max ();
constexpr int N_WIDGETS {38}; constexpr int N_WIDGETS {38};
constexpr int default_rx_audio_buffer_frames {-1}; // lets Qt decide constexpr int default_rx_audio_buffer_frames {-1}; // lets Qt decide
@ -697,7 +698,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
// initialize decoded text font and hook up font change signals // initialize decoded text font and hook up font change signals
// defer initialization until after construction otherwise menu fonts do not get set // defer initialization until after construction otherwise menu fonts do not get set
// with 50 ms delay we are on the save side // with 50 ms delay we are on the safe side
QTimer::singleShot (50, this, SLOT (initialize_fonts ())); QTimer::singleShot (50, this, SLOT (initialize_fonts ()));
connect (&m_config, &Configuration::text_font_changed, [this] (QFont const& font) { connect (&m_config, &Configuration::text_font_changed, [this] (QFont const& font) {
set_application_font (font); set_application_font (font);
@ -3103,9 +3104,15 @@ void MainWindow::on_actionActiveStations_triggered()
m_ActiveStationsWidget->activateWindow(); m_ActiveStationsWidget->activateWindow();
configActiveStations(); configActiveStations();
connect(m_ActiveStationsWidget.data(), SIGNAL(callSandP(int)),this,SLOT(callSandP2(int))); connect(m_ActiveStationsWidget.data(), SIGNAL(callSandP(int)),this,SLOT(callSandP2(int)));
// connect up another signal to handle clicks in the Activity window when in Fox mode
connect(m_ActiveStationsWidget.data(), SIGNAL(queueActiveWindowHound(QString)),this,SLOT(queueActiveWindowHound2(QString)),static_cast<Qt::ConnectionType>(Qt::UniqueConnection));
connect(m_ActiveStationsWidget.data(), SIGNAL(activeStationsDisplay()),this,SLOT(ARRL_Digi_Display())); connect(m_ActiveStationsWidget.data(), SIGNAL(activeStationsDisplay()),this,SLOT(ARRL_Digi_Display()));
m_ActiveStationsWidget->setScore(m_score); m_ActiveStationsWidget->setScore(m_score);
if(m_mode=="Q65") m_ActiveStationsWidget->setRate(m_score); if(m_mode=="Q65") m_ActiveStationsWidget->setRate(m_score);
QString as_mode = m_mode;
if(m_mode=="FT8" && SpecOp::FOX==m_specOp) as_mode="Fox Mode"; // TODO - active stations for hound mode?
m_ActiveStationsWidget->setupUi(as_mode);
} }
void MainWindow::on_actionOpen_triggered() //Open File void MainWindow::on_actionOpen_triggered() //Open File
@ -3865,6 +3872,12 @@ void MainWindow::ARRL_Digi_Display()
readWidebandDecodes(); readWidebandDecodes();
return; return;
} }
if (m_mode == "Fox Mode") { // ARRL_Digi_Display can be shown for other modes
if (m_ActiveStationsWidget != NULL) {
m_ActiveStationsWidget->setClickOK(true);
}
return;
}
QMutableMapIterator<QString,RecentCall> icall(m_recentCall); QMutableMapIterator<QString,RecentCall> icall(m_recentCall);
QString deCall,deGrid; QString deCall,deGrid;
int age=0; int age=0;
@ -3935,10 +3948,53 @@ void MainWindow::ARRL_Digi_Display()
t += (t1 + list[k] + "\n"); t += (t1 + list[k] + "\n");
if(i>=maxRecent) break; if(i>=maxRecent) break;
} }
if(m_ActiveStationsWidget!=NULL) m_ActiveStationsWidget->displayRecentStations(m_mode,t); bool is_fox_mode = (m_mode=="FT8" && m_specOp == SpecOp::FOX);
if(m_ActiveStationsWidget!=NULL && !is_fox_mode) m_ActiveStationsWidget->displayRecentStations(m_mode,t);
m_ActiveStationsWidget->setClickOK(true); m_ActiveStationsWidget->setClickOK(true);
} }
void MainWindow::queueActiveWindowHound2(QString line) {
// Active Window shows what's going on outside of current F/H display rules (calling below 1000Hz e.g.)
// TODO should we allow calling a station that's calling another station, not us?
if (m_mode == "FT8" and m_specOp == SpecOp::FOX) {
// process the line to get the callsign
QStringList w = line.split(' ', SkipEmptyParts);
// make sure our call is the first in the list, or the station is CQing (not a directed CQ)
if ( (w.size() > 7) &&
(w[5] == m_config.my_callsign() || w[5] == "<"+m_config.my_callsign()+">" || w[5]=="CQ") &&
( w[7].contains(grid_regexp) || w[7].contains(non_r_db_regexp) )){
QString caller = w[6];
QString grid = "";
QString db = w[1];
int db_i = w[1].toInt();
db = (db_i >=0 ? "+":"") + QStringLiteral("%1").arg(db_i, (db_i >=0 ? 2:3), 10, QLatin1Char('0')); // +00, -01 etc.
// houndcall rpt grid
if (w[7].contains(grid_regexp)) grid = w[7];
if (w[7].contains(non_r_db_regexp)) {
LOG_INFO(QString("%1 called with signal report %2").arg(caller).arg(w[7]));
}
if (caller.length() > 2) {
// make sure it's not already in the queue
for ( QString hs : m_houndQueue) {
if (hs.startsWith(caller)) {
LOG_INFO(QString("%1 already in queue. Skipping").arg(hs));
return;
}
}
QString caller_rpt = (caller+" ").mid(0,12)+db;
if (m_houndQueue.count() < MAX_HOUNDS_IN_QUEUE) {
// add it to the queue
m_houndQueue.enqueue(caller_rpt + " " + grid);
refreshHoundQueueDisplay();
// TODO: remove from active stations window too?
}
}
} else {
LOG_INFO(QString("queueActiveWindowHound2 - skipping %1").arg(line));
}
}
}
void MainWindow::callSandP2(int n) void MainWindow::callSandP2(int n)
{ {
bool bCtrl = (n<0); bool bCtrl = (n<0);
@ -4035,12 +4091,20 @@ void MainWindow::activeWorked(QString call, QString band)
void MainWindow::readFromStdout() //readFromStdout void MainWindow::readFromStdout() //readFromStdout
{ {
bool bDisplayPoints = false; bool bDisplayPoints = false;
QString all_decodes;
if(m_ActiveStationsWidget!=NULL) { if(m_ActiveStationsWidget!=NULL) {
bDisplayPoints=(m_mode=="FT4" or m_mode=="FT8") and bDisplayPoints=(m_mode=="FT4" or m_mode=="FT8") and
(m_specOp==SpecOp::ARRL_DIGI or m_ActiveStationsWidget->isVisible()); (m_specOp==SpecOp::ARRL_DIGI or m_ActiveStationsWidget->isVisible());
} }
while(proc_jt9.canReadLine()) { while(proc_jt9.canReadLine()) {
auto line_read = proc_jt9.readLine (); auto line_read = proc_jt9.readLine ();
if (m_mode == "FT8" and m_specOp == SpecOp::FOX and m_ActiveStationsWidget != NULL) { // see if we should add this to ActiveStations window
QString the_line = QString(line_read);
if (!m_ActiveStationsWidget->wantedOnly() ||
(the_line.contains(" " + m_config.my_callsign() + " ") ||
the_line.contains(" <" + m_config.my_callsign() + "> ")))
all_decodes.append(line_read);
}
if (auto p = std::strpbrk (line_read.constData (), "\n\r")) { if (auto p = std::strpbrk (line_read.constData (), "\n\r")) {
// truncate before line ending chars // truncate before line ending chars
line_read = line_read.left (p - line_read.constData ()); line_read = line_read.left (p - line_read.constData ());
@ -4124,6 +4188,10 @@ void MainWindow::readFromStdout() //readFromStdout
if(m_TRperiod>=60) ntime=4; if(m_TRperiod>=60) ntime=4;
if (line_read.left(ntime) != m_tBlankLine && QString::fromUtf8(line_read.constData()).left(4).contains(QRegularExpression {"\\d\\d\\d\\d"})) { if (line_read.left(ntime) != m_tBlankLine && QString::fromUtf8(line_read.constData()).left(4).contains(QRegularExpression {"\\d\\d\\d\\d"})) {
ui->decodedTextBrowser->new_period (); ui->decodedTextBrowser->new_period ();
if (m_specOp == SpecOp::FOX and m_ActiveStationsWidget != NULL) { // clear the ActiveStations window
m_ActiveStationsWidget->clearStations();
m_ActiveStationsWidget->displayRecentStations("Fox Mode", "");
}
if (m_config.insert_blank () if (m_config.insert_blank ()
&& SpecOp::FOX != m_specOp) { && SpecOp::FOX != m_specOp) {
QString band; QString band;
@ -4439,6 +4507,9 @@ void MainWindow::readFromStdout() //readFromStdout
} }
} }
} }
if (m_mode == "FT8" and m_specOp == SpecOp::FOX and m_ActiveStationsWidget != NULL) {
m_ActiveStationsWidget->addLine(all_decodes);
}
} }
// //
@ -4652,7 +4723,7 @@ void MainWindow::guiUpdate()
m_bTxTime = (t2p >= tx1) and (t2p < tx2); m_bTxTime = (t2p >= tx1) and (t2p < tx2);
if(m_mode=="Echo") m_bTxTime = m_bTxTime and m_bEchoTxOK; if(m_mode=="Echo") m_bTxTime = m_bTxTime and m_bEchoTxOK;
if(m_mode=="FT8" and ui->tx5->currentText().contains("/B ")) { if(m_mode=="FT8" and ui->tx5->currentText().contains("/B ")) {
//FT8 beacon transmissiion from Tx5 only at top of a UTC minute //FT8 beacon transmission from Tx5 only at top of a UTC minute
double t4p=fmod(tsec,4*m_TRperiod); double t4p=fmod(tsec,4*m_TRperiod);
if(t4p >= 30.0) m_bTxTime=false; if(t4p >= 30.0) m_bTxTime=false;
} }
@ -4731,7 +4802,7 @@ void MainWindow::guiUpdate()
ui->TxFreqSpinBox->setValue(750); //SuperFox transmits at 750 Hz ui->TxFreqSpinBox->setValue(750); //SuperFox transmits at 750 Hz
} else { } else {
if (ui->TxFreqSpinBox->value() > 900) { if (ui->TxFreqSpinBox->value() > 900) {
ui->TxFreqSpinBox->setValue(300); ui->TxFreqSpinBox->setValue(500);
} }
} }
} }
@ -7226,7 +7297,7 @@ void MainWindow::on_actionFT8_triggered()
ui->rh_decodes_title_label->setText(tr ("Rx Frequency")); ui->rh_decodes_title_label->setText(tr ("Rx Frequency"));
if(SpecOp::FOX==m_specOp) { if(SpecOp::FOX==m_specOp) {
ui->lh_decodes_title_label->setText(tr ("Stations calling DXpedition %1").arg (m_config.my_callsign())); ui->lh_decodes_title_label->setText(tr ("Stations calling DXpedition %1").arg (m_config.my_callsign()));
ui->lh_decodes_headings_label->setText( "Call Grid dB Freq Dist Age Continent"); ui->lh_decodes_headings_label->setText( "Call Grid dB Freq Dist Age Cont Score");
} else { } else {
ui->lh_decodes_title_label->setText(tr ("Band Activity")); ui->lh_decodes_title_label->setText(tr ("Band Activity"));
ui->lh_decodes_headings_label->setText( " UTC dB DT Freq " + tr ("Message")); ui->lh_decodes_headings_label->setText( " UTC dB DT Freq " + tr ("Message"));
@ -7252,16 +7323,18 @@ void MainWindow::on_actionFT8_triggered()
if(m_config.superFox()) { if(m_config.superFox()) {
ui->TxFreqSpinBox->setValue(750); //SuperFox transmits at 750 Hz ui->TxFreqSpinBox->setValue(750); //SuperFox transmits at 750 Hz
} else { } else {
ui->TxFreqSpinBox->setValue(300); ui->TxFreqSpinBox->setValue(500);
} }
// 01234567890123456789012345678901234567 // 01234567890123456789012345678901234567
displayWidgets(nWidgets("11101000010011100001000000000010000000")); displayWidgets(nWidgets("11101000010011100001000000000011000000"));
ui->cbRxAll->setText(tr("Show Already Worked"));
if(m_config.superFox()) { if(m_config.superFox()) {
ui->labDXped->setText(tr ("Super Fox")); ui->labDXped->setText(tr ("Super Fox"));
} else { } else {
ui->labDXped->setText(tr ("Fox")); ui->labDXped->setText(tr ("Fox"));
} }
on_fox_log_action_triggered(); on_fox_log_action_triggered();
if (m_ActiveStationsWidget) m_ActiveStationsWidget->setClickOK(true); // allow clicks
} }
if(SpecOp::HOUND == m_specOp) { if(SpecOp::HOUND == m_specOp) {
ui->houndButton->setChecked(true); ui->houndButton->setChecked(true);
@ -7272,6 +7345,7 @@ void MainWindow::on_actionFT8_triggered()
ui->cbHoldTxFreq->setChecked(true); ui->cbHoldTxFreq->setChecked(true);
// 01234567890123456789012345678901234567 // 01234567890123456789012345678901234567
displayWidgets(nWidgets("11101000010011000001000000000011000000")); displayWidgets(nWidgets("11101000010011000001000000000011000000"));
ui->cbRxAll->setText(tr("Rx All Freqs"));
if(m_config.superFox()) { if(m_config.superFox()) {
ui->labDXped->setText(tr ("Super Hound")); ui->labDXped->setText(tr ("Super Hound"));
ui->cbRxAll->setEnabled(false); ui->cbRxAll->setEnabled(false);
@ -7288,6 +7362,7 @@ void MainWindow::on_actionFT8_triggered()
ui->txb4->setEnabled(false); ui->txb4->setEnabled(false);
ui->txb5->setEnabled(false); ui->txb5->setEnabled(false);
ui->txb6->setEnabled(false); ui->txb6->setEnabled(false);
if (m_ActiveStationsWidget) m_ActiveStationsWidget->setClickOK(false);
} else { } else {
switch_mode (Modes::FT8); switch_mode (Modes::FT8);
} }
@ -9995,15 +10070,21 @@ QString MainWindow::sortHoundCalls(QString t, int isort, int max_dB)
* 2: Grid * 2: Grid
* 3: SNR (reverse order) * 3: SNR (reverse order)
* 4: Distance (reverse order) * 4: Distance (reverse order)
* 5: Age (reverse order)
* 6: Continent
* 7: User defined (reverse order)
*
*/ */
QMap<QString,QString> map; QMap<QString,QString> map;
QStringList lines,lines2; QStringList lines,lines2;
QString msg,houndCall,t1; QString msg,houndCall,t1;
QString ABC{"ABCDEFGHIJKLMNOPQRSTUVWXYZ _"}; QString ABC{"ABCDEFGHIJKLMNOPQRSTUVWXYZ _"};
QList<int> reverse_sorted{3,4,5,6};
QString Continents{" AF AN AS EU NA OC SA UN "}; // matches what we get from AD1C's country list
QList<int> list; QList<int> list;
int i,j,k,n,nlines; int i,j,k,n,nlines;
bool bReverse=(isort >= 3); bool bReverse = reverse_sorted.contains(isort);
isort=qAbs(isort); isort=qAbs(isort);
// Save only the most recent transmission from each caller. // Save only the most recent transmission from each caller.
@ -10012,7 +10093,7 @@ QString MainWindow::sortHoundCalls(QString t, int isort, int max_dB)
for(i=0; i<nlines; i++) { for(i=0; i<nlines; i++) {
msg=lines.at(i); //key = callsign msg=lines.at(i); //key = callsign
if(msg.mid(13,1)==" ") msg=msg.mid(0,13) + "...." + msg.mid(17); if(msg.mid(13,1)==" ") msg=msg.mid(0,13) + "...." + msg.mid(17);
houndCall=msg.split(" ").at(0); //value = "call grid snr freq dist age" houndCall=msg.split(" ").at(0); //value = "call grid snr freq dist age continent user-defined
map[houndCall]=msg; map[houndCall]=msg;
} }
@ -10020,16 +10101,43 @@ QString MainWindow::sortHoundCalls(QString t, int isort, int max_dB)
t=""; t="";
for(auto a: map.keys()) { for(auto a: map.keys()) {
t1=map[a].split(" ",SkipEmptyParts).at(2); t1=map[a].split(" ",SkipEmptyParts).at(2);
// add the user-defined value to the end of the line. Example:
// JJ0NCC PM97 15 2724 7580 2 AS 67
// PY7ZZ HI21 -13 1673 10549 1 SA -
QString annotated_value_s{" - "}; // default
qint32 annotated_value = 0;
if (m_annotated_callsigns.contains(a)) {
annotated_value = m_annotated_callsigns.value(a);
annotated_value_s = QString::number(annotated_value);
}
map[a] += QString(" %1").arg(annotated_value_s, 8);
int nsnr=t1.toInt(); // get snr int nsnr=t1.toInt(); // get snr
if(nsnr <= max_dB) { // keep only if snr in specified range if(nsnr <= max_dB) { // keep only if snr in specified range
if(isort==1) t += map[a] + "\n"; if(isort==1) t += map[a] + "\n";
if(isort==3 or isort==4) { if (isort==3 or isort==4 or isort==5) { // numeric ones: snr, distance, age
i=2; // sort Hound calls by snr if (isort==3)
if(isort==4) i=4; // sort Hound calls by distance i=2; // sort Hound calls by snr
else
i=isort; // part of the line that we want
t1=map[a].split(" ",SkipEmptyParts).at(i); t1=map[a].split(" ",SkipEmptyParts).at(i);
n=1000*(t1.toInt()+100) + j; // pack (snr or dist) and index j into n n=1000*(t1.toInt()+100) + j; // pack (snr or dist or age) and index j into n
list.insert(j,n); // add n to list at [j] list.insert(j,n); // add n to list at [j]
} }
if (isort == 6) { // sort by continent
i = 6;
QStringList parts = map[a].split(" ", SkipEmptyParts);
if (parts.size() <= i + 1) {
n = j;
} else {
QString cont = map[a].split(" ", SkipEmptyParts).at(i);
int cont_n = Continents.indexOf(" " + cont + " ");
n = 1000 + 1000 * cont_n + j; // index may return -1, so add 1000 to make it positive
}
list.insert(j, n);
}
if(isort==2) { // sort Hound calls by grid if(isort==2) { // sort Hound calls by grid
t1=map[a].split(" ",SkipEmptyParts).at(1); t1=map[a].split(" ",SkipEmptyParts).at(1);
@ -10041,6 +10149,11 @@ QString MainWindow::sortHoundCalls(QString t, int isort, int max_dB)
list.insert(j,n); // add n to list at [j] list.insert(j,n); // add n to list at [j]
} }
if(isort==7) { // annotated value provided by external app
n = 1000 + 1000 * std::max((qint32) 0, annotated_value) + j;
list.insert(j, n);
}
lines2.insert(j,map[a]); // add map[a] to lines2 at [j] lines2.insert(j,map[a]); // add map[a] to lines2 at [j]
j++; j++;
} }
@ -10109,14 +10222,16 @@ void MainWindow::selectHound(QString line, bool bTopQueue)
m_houndCallers=m_houndCallers.remove(line+"\n"); // Remove t from sorted Hound list m_houndCallers=m_houndCallers.remove(line+"\n"); // Remove t from sorted Hound list
m_nSortedHounds--; m_nSortedHounds--;
ui->decodedTextBrowser->setText(m_houndCallers); // Populate left window with Hound callers ui->decodedTextBrowser->setHighlightedHoundText(m_houndCallers); // Populate left window with Hound callers
QString t1=houndCall + " "; QString t1=houndCall + " ";
QString t2=rpt; QString t2=rpt;
QString t1_with_grid; QString t1_with_grid;
if(rpt.mid(0,1) != "-" and rpt.mid(0,1) != "+") t2="+" + rpt; if(rpt.mid(0,1) != "-" and rpt.mid(0,1) != "+") t2="+" + rpt;
if(t2.length()==2) t2=t2.mid(0,1) + "0" + t2.mid(1,1); if(t2.length()==2) t2=t2.mid(0,1) + "0" + t2.mid(1,1);
t1=t1.mid(0,12) + t2; t1=t1.mid(0,12) + t2;
ui->houndQueueTextBrowser->displayHoundToBeCalled(t1, bTopQueue); // Add hound call and rpt to tb4 // display the callers, highlighting calls if necessary
ui->houndQueueTextBrowser->insertText(bTopQueue ? t1 + "\n" : t1, QColor{}, QColor{}, houndCall, "", bTopQueue ? QTextCursor::Start : QTextCursor::End);
t1_with_grid=t1 + " " + houndGrid; // Append the grid t1_with_grid=t1 + " " + houndGrid; // Append the grid
if (bTopQueue) if (bTopQueue)
@ -10131,6 +10246,9 @@ void MainWindow::selectHound(QString line, bool bTopQueue)
QTextCursor cursor = ui->houndQueueTextBrowser->textCursor(); QTextCursor cursor = ui->houndQueueTextBrowser->textCursor();
cursor.setPosition(0); // Scroll to top of list cursor.setPosition(0); // Scroll to top of list
ui->houndQueueTextBrowser->setTextCursor(cursor); ui->houndQueueTextBrowser->setTextCursor(cursor);
cursor = ui->decodedTextBrowser->textCursor();
cursor.setPosition(0); // Highlighting happens in the forward direction
ui->decodedTextBrowser->setTextCursor(cursor);
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -10214,8 +10332,11 @@ void MainWindow::houndCallers()
if(t.length()>30) { if(t.length()>30) {
m_isort=ui->comboBoxHoundSort->currentIndex(); m_isort=ui->comboBoxHoundSort->currentIndex();
QString t1=sortHoundCalls(t,m_isort,m_max_dB); QString t1=sortHoundCalls(t,m_isort,m_max_dB);
ui->decodedTextBrowser->setText(t1); ui->decodedTextBrowser->setHighlightedHoundText(t1);
} }
QTextCursor cursor = ui->decodedTextBrowser->textCursor();
cursor.setPosition(0); // Set scroll at top, in preparation for highlighting messages
ui->decodedTextBrowser->setTextCursor(cursor);
f.close(); f.close();
} }
} }
@ -10252,7 +10373,7 @@ void MainWindow::updateFoxQSOsInProgressDisplay()
QString hc = m_foxQSOinProgress.at(i); QString hc = m_foxQSOinProgress.at(i);
QString status = m_foxQSO[hc].ncall > m_maxStrikes ? QString(" (rx) ") : QString(" "); QString status = m_foxQSO[hc].ncall > m_maxStrikes ? QString(" (rx) ") : QString(" ");
QString str = (hc + " ").left(13) + QString::number(m_foxQSO[hc].ncall) + status; QString str = (hc + " ").left(13) + QString::number(m_foxQSO[hc].ncall) + status;
ui->foxTxListTextBrowser->displayHoundToBeCalled(str); ui->foxTxListTextBrowser->insertText(str, QColor{}, QColor{}, hc, "", QTextCursor::End);
} }
} }
@ -10350,7 +10471,7 @@ list1Done:
m_foxQSO[hc].rcvd = -99; //Have not received R+rpt m_foxQSO[hc].rcvd = -99; //Have not received R+rpt
m_foxQSO[hc].tFoxRrpt = -1; //Have not received R+rpt m_foxQSO[hc].tFoxRrpt = -1; //Have not received R+rpt
m_foxQSO[hc].tFoxTxRR73 = -1; //Have not sent RR73 m_foxQSO[hc].tFoxTxRR73 = -1; //Have not sent RR73
rm_tb4(hc); //Remove this Hound from tb4 refreshHoundQueueDisplay();
if(list2.size()==m_Nslots) { if(list2.size()==m_Nslots) {
break; break;
@ -10480,24 +10601,13 @@ void MainWindow::update_foxLogWindow_rate()
} }
} }
void MainWindow::rm_tb4(QString houndCall) void MainWindow::refreshHoundQueueDisplay()
{ {
if(houndCall=="") return; ui->houndQueueTextBrowser->clear();
QString t=""; for (QString line: m_houndQueue) {
QString tb4=ui->houndQueueTextBrowser->toPlainText(); auto hc = line.mid(0, 12).trimmed();
QStringList list=tb4.split("\n"); ui->houndQueueTextBrowser->insertText(line, QColor{}, QColor{}, hc, "", QTextCursor::End);
int n=list.size();
int j=0;
for (int i=0; i<n; i++) {
if(j>0) t += "\n";
QString line=list.at(i);
if(!line.contains(houndCall + " ")) {
j++;
t += line;
}
} }
t.replace("\n\n","\n");
ui->houndQueueTextBrowser->setText(t);
} }
void MainWindow::doubleClickOnFoxQueue(Qt::KeyboardModifiers modifiers) void MainWindow::doubleClickOnFoxQueue(Qt::KeyboardModifiers modifiers)
@ -10521,14 +10631,9 @@ void MainWindow::doubleClickOnFoxQueue(Qt::KeyboardModifiers modifiers)
} }
} }
m_houndQueue.prepend(houndLine); m_houndQueue.prepend(houndLine);
ui->houndQueueTextBrowser->clear(); refreshHoundQueueDisplay();
for (QString line: m_houndQueue)
{
ui->houndQueueTextBrowser->displayHoundToBeCalled(line.mid(0,16), false);
}
} else } else
{ {
rm_tb4(houndCall);
writeFoxQSO(" Del: " + houndCall); writeFoxQSO(" Del: " + houndCall);
QQueue <QString> tmpQueue; QQueue <QString> tmpQueue;
while (!m_houndQueue.isEmpty()) while (!m_houndQueue.isEmpty())
@ -10538,6 +10643,7 @@ void MainWindow::doubleClickOnFoxQueue(Qt::KeyboardModifiers modifiers)
if (hc != houndCall) tmpQueue.enqueue(t); if (hc != houndCall) tmpQueue.enqueue(t);
} }
m_houndQueue.swap(tmpQueue); m_houndQueue.swap(tmpQueue);
refreshHoundQueueDisplay();
} }
} }
@ -10559,7 +10665,7 @@ void MainWindow::doubleClickOnFoxInProgress(Qt::KeyboardModifiers modifiers)
void MainWindow::foxQueueTopCallCommand() void MainWindow::foxQueueTopCallCommand()
{ {
m_decodedText2 = true; m_decodedText2 = true;
if(SpecOp::FOX==m_specOp && m_decodedText2 && m_houndQueue.count() < 10) if(SpecOp::FOX==m_specOp && m_decodedText2 && m_houndQueue.count() < MAX_HOUNDS_IN_QUEUE)
{ {
QTextCursor cursor = ui->decodedTextBrowser->textCursor(); QTextCursor cursor = ui->decodedTextBrowser->textCursor();
@ -10687,7 +10793,7 @@ void MainWindow::foxTest()
hc1=line.mid(i0+6); hc1=line.mid(i0+6);
int i1=hc1.indexOf(" "); int i1=hc1.indexOf(" ");
hc1=hc1.mid(0,i1); hc1=hc1.mid(0,i1);
rm_tb4(hc1);
writeFoxQSO(" Del: " + hc1); writeFoxQSO(" Del: " + hc1);
QQueue<QString> tmpQueue; QQueue<QString> tmpQueue;
while(!m_houndQueue.isEmpty()) { while(!m_houndQueue.isEmpty()) {
@ -10696,6 +10802,7 @@ void MainWindow::foxTest()
if(hc != hc1) tmpQueue.enqueue(t); if(hc != hc1) tmpQueue.enqueue(t);
} }
m_houndQueue.swap(tmpQueue); m_houndQueue.swap(tmpQueue);
refreshHoundQueueDisplay();
} }
if(line.contains("Rx:")) { if(line.contains("Rx:")) {
msg=line.mid(37+6); msg=line.mid(37+6);
@ -10870,13 +10977,18 @@ void MainWindow::set_mode (QString const& mode)
else if ("Echo" == mode) on_actionEcho_triggered (); else if ("Echo" == mode) on_actionEcho_triggered ();
} }
void MainWindow::configActiveStations() void MainWindow::configActiveStations() {
{ if (m_ActiveStationsWidget != NULL and (m_mode == "Q65" or m_mode == "FT4" or m_mode == "FT8")) {
if(m_ActiveStationsWidget!=NULL and (m_mode=="Q65" or m_mode=="FT4" or m_mode=="FT8")) { if (m_specOp == SpecOp::Q65_PILEUP) {
if(m_specOp==SpecOp::Q65_PILEUP) { m_ActiveStationsWidget->displayRecentStations("Q65-pileup", "");
m_ActiveStationsWidget->displayRecentStations("Q65-pileup","");
} else { } else {
m_ActiveStationsWidget->displayRecentStations(m_mode,""); if (m_specOp == SpecOp::FOX)
if (m_config.superFox())
m_ActiveStationsWidget->displayRecentStations("SuperFox Mode", "");
else
m_ActiveStationsWidget->displayRecentStations("Fox Mode", "");
else
m_ActiveStationsWidget->displayRecentStations(m_mode, "");
} }
} }
} }

View File

@ -57,6 +57,7 @@
#define MAX_NUM_SYMBOLS 250 #define MAX_NUM_SYMBOLS 250
#define TX_SAMPLE_RATE 48000 #define TX_SAMPLE_RATE 48000
#define NRING 3456000 #define NRING 3456000
#define MAX_HOUNDS_IN_QUEUE 10
extern int volatile itone[MAX_NUM_SYMBOLS]; //Audio tones for all Tx symbols extern int volatile itone[MAX_NUM_SYMBOLS]; //Audio tones for all Tx symbols
extern int volatile icw[NUM_CW_SYMBOLS]; //Dits for CW ID extern int volatile icw[NUM_CW_SYMBOLS]; //Dits for CW ID
@ -345,6 +346,8 @@ private slots:
, bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call , bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call
, QString const& dx_grid, bool generate_messages); , QString const& dx_grid, bool generate_messages);
void callSandP2(int nline); void callSandP2(int nline);
void refreshHoundQueueDisplay();
void queueActiveWindowHound2(QString text);
private: private:
Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo, Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo,
@ -702,6 +705,7 @@ private:
QMap<QString,FoxQSO> m_foxQSO; //Key = HoundCall, value = parameters for QSO in progress QMap<QString,FoxQSO> m_foxQSO; //Key = HoundCall, value = parameters for QSO in progress
QMap<QString,QString> m_loggedByFox; //Key = HoundCall, value = logged band QMap<QString,QString> m_loggedByFox; //Key = HoundCall, value = logged band
QMap<QString,qint32> m_annotated_callsigns; //Key = HoundCall, value = provided by api call
struct FixupQSO //Info for fixing Fox's log from file "FoxQSO.txt" struct FixupQSO //Info for fixing Fox's log from file "FoxQSO.txt"
{ {

View File

@ -2375,6 +2375,21 @@ Double-click to reset to the standard 73 message</string>
<string>Distance</string> <string>Distance</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>Age</string>
</property>
</item>
<item>
<property name="text">
<string>Continent</string>
</property>
</item>
<item>
<property name="text">
<string>Score</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item row="1" column="1" rowspan="10"> <item row="1" column="1" rowspan="10">