diff --git a/decodedtext.cpp b/decodedtext.cpp index c8128c9..4d36008 100644 --- a/decodedtext.cpp +++ b/decodedtext.cpp @@ -90,7 +90,7 @@ bool DecodedText::tryUnpackCompound(){ } compound_ = QString("%1/%2").arg(parts.at(0), parts.at(1)); - message_ = QString("%1:").arg(compound_); + message_ = QString("%1: ").arg(compound_); return true; } @@ -110,10 +110,10 @@ bool DecodedText::tryUnpackDirected(){ if(parts.length() == 3){ // replace it with the correct unpacked (directed) - message_ = QString("%1: %2%3").arg(parts.at(0), parts.at(1), parts.at(2)); + message_ = QString("%1: %2%3 ").arg(parts.at(0), parts.at(1), parts.at(2)); } else if(parts.length() == 4){ // replace it with the correct unpacked (directed numeric) - message_ = QString("%1: %2%3 %4").arg(parts.at(0), parts.at(1), parts.at(2), parts.at(3)); + message_ = QString("%1: %2%3 %4 ").arg(parts.at(0), parts.at(1), parts.at(2), parts.at(3)); } else { // replace it with the correct unpacked (freetext) message_ = QString(parts.join("")); diff --git a/mainwindow.cpp b/mainwindow.cpp index f57076b..f0cafee 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -164,7 +164,7 @@ namespace { Radio::Frequency constexpr default_frequency {14074000}; QRegExp message_alphabet {"[- A-Za-z0-9+./?:!^]*"}; - QRegExp message_input_alphabet {"[- A-Za-z0-9+./?\\n:!^@&|]*"}; // @&| are used for commands but are never transmitted + QRegExp message_input_alphabet {"[- A-Za-z0-9+./?\\n:!^@&|$]*"}; // @&|$ are used for commands but are never transmitted // 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"}; @@ -1027,7 +1027,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, ui->dxCallEntry->clear(); ui->dxGridEntry->clear(); auto f = findFreeFreqOffset(500, 2000, 50); - setFreq4(f, f); + setFreqForRestore(f, false); ui->cbVHFcontest->setChecked(false); // this needs to always be false ui->spotButton->setChecked(m_config.spot_to_psk_reporter()); @@ -1043,21 +1043,18 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect(clearAction1, &QAction::triggered, this, [this](){ this->on_clearAction_triggered(ui->textEditRX); }); ui->textEditRX->setContextMenuPolicy(Qt::ActionsContextMenu); ui->textEditRX->addAction(clearAction1); - ui->textEditRX->addAction(clearActionSep); ui->textEditRX->addAction(clearActionAll); auto clearAction2 = new QAction(QIcon::fromTheme("edit-clear"), QString("Clear"), ui->extFreeTextMsgEdit); connect(clearAction2, &QAction::triggered, this, [this](){ this->on_clearAction_triggered(ui->extFreeTextMsgEdit); }); ui->extFreeTextMsgEdit->setContextMenuPolicy(Qt::ActionsContextMenu); ui->extFreeTextMsgEdit->addAction(clearAction2); - ui->extFreeTextMsgEdit->addAction(clearActionSep); ui->extFreeTextMsgEdit->addAction(clearActionAll); auto clearAction3 = new QAction(QIcon::fromTheme("edit-clear"), QString("Clear"), ui->tableWidgetRXAll); connect(clearAction3, &QAction::triggered, this, [this](){ this->on_clearAction_triggered(ui->tableWidgetRXAll); }); ui->tableWidgetRXAll->setContextMenuPolicy(Qt::ActionsContextMenu); ui->tableWidgetRXAll->addAction(clearAction3); - ui->tableWidgetRXAll->addAction(clearActionSep); ui->tableWidgetRXAll->addAction(clearActionAll); auto clearAction4 = new QAction(QIcon::fromTheme("edit-clear"), QString("Clear"), ui->tableWidgetCalls); @@ -1073,7 +1070,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, menu->addSeparator(); menu->addAction(clearAction4); - menu->addSeparator(); menu->addAction(clearActionAll); menu->popup(ui->tableWidgetCalls->mapToGlobal(point)); @@ -3231,6 +3227,7 @@ void MainWindow::readFromStdout() //readFromStdout ActivityDetail d; d.isLowConfidence = decodedtext.isLowConfidence(); d.isFree = !decodedtext.isStandardMessage(); + d.isCompound = decodedtext.isCompoundMessage(); d.bits = decodedtext.bits(); d.firstCall = decodedtext.CQersCall(); if(d.firstCall.isEmpty()){ @@ -3245,7 +3242,7 @@ void MainWindow::readFromStdout() //readFromStdout } d.freq = offset; - d.text = decodedtext.messageWords().first().trimmed(); + d.text = decodedtext.messageWords().isEmpty() ? "" : decodedtext.messageWords().first().trimmed(); d.utcTimestamp = QDateTime::currentDateTimeUtc(); d.snr = decodedtext.snr(); m_bandActivity[offset].append(d); @@ -3333,6 +3330,24 @@ void MainWindow::readFromStdout() //readFromStdout cd.utcTimestamp = d.utcTimestamp; m_callActivity[Radio::base_callsign(cd.call)] = cd; + bool shouldCaptureThirdPartyCallsigns = false; + // check to see if this is a station we've heard 3rd party + if(shouldCaptureThirdPartyCallsigns && Radio::base_callsign(d.to) != Radio::base_callsign(m_config.my_callsign())){ + QString relayCall = QString("%1|%2").arg(Radio::base_callsign(d.from)).arg(Radio::base_callsign(d.to)); + int snr = -100; + if(parts.length() == 4){ + snr = QString(parts.at(3)).toInt(); + } + CallDetail td; + td.through = d.from; + td.call = d.to; + td.grid = ""; + td.snr = snr; + td.freq = d.freq; + td.utcTimestamp = d.utcTimestamp; + m_callActivity[relayCall] = td; + } + int nsec=QDateTime::currentMSecsSinceEpoch()/1000-m_secBandChanged; bool okToPost=(nsec>(4*m_TRperiod)/5); if (okToPost){ @@ -3412,10 +3427,10 @@ void MainWindow::readFromStdout() //readFromStdout // TODO: jsherer - parse decode... RXDetail d; d.isFree = !decodedtext.isStandardMessage(); + d.isCompound = decodedtext.isCompoundMessage(); d.bits = decodedtext.bits(); d.freq = audioFreq; d.text = decodedtext.message(); - qDebug() << d.text; d.utcTimestamp = QDateTime::currentDateTimeUtc(); m_rxFrameQueue.append(d); } @@ -5369,7 +5384,9 @@ void MainWindow::clearActivity(){ // this is now duplicated in three places :( ui->tableWidgetCalls->insertRow(ui->tableWidgetCalls->rowCount()); - ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, new QTableWidgetItem("ALLCALL")); + auto item = new QTableWidgetItem("ALLCALL"); + item->setData(Qt::UserRole, QVariant("ALLCALL")); + ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, item); ui->tableWidgetCalls->setSpan(ui->tableWidgetCalls->rowCount() - 1, 0, 1, ui->tableWidgetCalls->columnCount()); clearTableWidget(ui->tableWidgetRXAll); @@ -5380,9 +5397,17 @@ void MainWindow::clearActivity(){ ui->extFreeTextMsgEdit->clear(); } -int MainWindow::logRxTxMessageText(QDateTime date, bool isFree, QString text, int freq, bool tx, int block){ +int MainWindow::logRxTxMessageText(QDateTime date, QString text, int freq, bool tx, int block){ auto c = ui->textEditRX->textCursor(); + // fixup compound callsigns cache / aliases... + foreach(auto call, m_compoundCallCache.keys()){ + //QRegExp re(QString("").arg(call)); + //text = text.replace(call, m_compoundCallCache[call]); + QRegularExpression re(QString(R"((?findBlockByNumber(block); @@ -5470,7 +5495,7 @@ void MainWindow::createMessageTransmitQueue(QString const& text){ m_txFrameCount = frames.length(); int freq = currentFreq(); - logRxTxMessageText(QDateTime::currentDateTimeUtc(), false, lines.join(""), freq, true); + logRxTxMessageText(QDateTime::currentDateTimeUtc(), lines.join(""), freq, true); } void MainWindow::resetMessageTransmitQueue(){ @@ -5557,6 +5582,7 @@ QPair MainWindow::buildFT8MessageFrames(QString const& QStringList lines; // prepare compound + bool compoundSent = false; bool compound = Radio::is_compound_callsign(m_config.my_callsign()); QString mycall = m_config.my_callsign(); QString basecall = Radio::base_callsign(m_config.my_callsign()); @@ -5625,11 +5651,12 @@ QPair MainWindow::buildFT8MessageFrames(QString const& } if(useDir){ - if(compound){ + if(compound && !compoundSent){ QString compoundFrame = Varicode::packCompoundMessage(basecall, fix, prefix, 0); if(!compoundFrame.isEmpty()){ frames.append(compoundFrame); lines.append(QString("%1: ").arg(mycall)); + compoundSent = true; } } @@ -5637,10 +5664,10 @@ QPair MainWindow::buildFT8MessageFrames(QString const& // TODO: jsherer - would be nice to clean this up and have an object that can just decode the actual transmitted frames instead. if(!line.startsWith(basecall) && !compound){ - lines.append(QString("%1: ").arg(basecall)); + lines.append(QString("%1: ").arg(lookupCallInCompoundCache(basecall))); } - lines.append(line.left(n)); + lines.append(line.left(n) + " "); line = line.mid(n); } @@ -5924,7 +5951,11 @@ QString MainWindow::calculateDistance(QString const& grid) // this function is called by auto_tx_mode, which is called by autoButton.clicked void MainWindow::on_startTxButton_toggled(bool checked) { - if(checked){ + toggleTx(checked); +} + +void MainWindow::toggleTx(bool start){ + if(start){ createMessage(ui->extFreeTextMsgEdit->toPlainText()); startTx(); } else { @@ -6963,8 +6994,10 @@ void MainWindow::on_clearAction_triggered(QObject * sender){ m_callActivity.clear(); clearTableWidget((ui->tableWidgetCalls)); + auto item = new QTableWidgetItem("ALLCALL"); + item->setData(Qt::UserRole, QVariant("ALLCALL")); ui->tableWidgetCalls->insertRow(ui->tableWidgetCalls->rowCount()); - ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, new QTableWidgetItem("ALLCALL")); + ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, item); ui->tableWidgetCalls->setSpan(ui->tableWidgetCalls->rowCount() - 1, 0, 1, ui->tableWidgetCalls->columnCount()); } @@ -7018,7 +7051,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){ bool isAllCall = call == "ALLCALL"; - auto sendReplyAction = menu->addAction("Reply to Callsign"); + auto sendReplyAction = menu->addAction("CALL - Send a message to selected callsign"); connect(sendReplyAction, &QAction::triggered, this, [this](){ @@ -7031,7 +7064,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){ }); - auto sendSNRAction = menu->addAction("Send Signal Report"); + auto sendSNRAction = menu->addAction("SNR - Send a signal report to the selected callsign"); sendSNRAction->setEnabled(m_callActivity.contains(call)); connect(sendSNRAction, &QAction::triggered, this, [this](){ @@ -7046,6 +7079,9 @@ void MainWindow::buildQueryMenu(QMenu * menu){ auto d = m_callActivity[selectedCall]; addMessageText(QString("%1 SNR %2").arg(selectedCall).arg(Varicode::formatSNR(d.snr)), true); + + // perhaps a better name here? + toggleTx(true); }); menu->addSeparator(); @@ -7060,6 +7096,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){ } addMessageText(QString("%1?").arg(selectedCall), true); + toggleTx(true); }); auto qthAction = menu->addAction("@ - What is your QTH message?"); @@ -7072,6 +7109,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){ } addMessageText(QString("%1@").arg(selectedCall), true); + toggleTx(true); }); auto stationAction = menu->addAction("&& - What is your station message?"); @@ -7084,9 +7122,22 @@ void MainWindow::buildQueryMenu(QMenu * menu){ } addMessageText(QString("%1&").arg(selectedCall), true); + toggleTx(true); + }); + + auto heardAction = menu->addAction("$ - What stations are you hearing?"); + heardAction->setDisabled(isAllCall); + connect(heardAction, &QAction::triggered, this, [this](){ + + QString selectedCall = callsignSelected(); + if(selectedCall.isEmpty()){ + return; + } + + addMessageText(QString("%1$").arg(selectedCall), true); + toggleTx(true); }); - //menu->addAction("$ - What stations are you hearing?")->setEnabled(false); //menu->addAction("| - Please relay the following message")->setEnabled(false); menu->addSeparator(); @@ -7100,6 +7151,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){ } addMessageText(QString("%1 AGN?").arg(selectedCall), true); + toggleTx(true); }); auto rrAction = menu->addAction("RR - I acknowledge your last transmission"); @@ -7111,6 +7163,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){ } addMessageText(QString("%1 RR").arg(selectedCall), true); + toggleTx(true); }); @@ -7123,6 +7176,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){ } addMessageText(QString("%1 YES").arg(selectedCall), true); + toggleTx(true); }); auto noAction = menu->addAction("NO - I do not confirm your last inquiry"); @@ -7134,6 +7188,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){ } addMessageText(QString("%1 NO").arg(selectedCall), true); + toggleTx(true); }); auto sevenThreeAction = menu->addAction("73 - I send my best regards / end of contact"); @@ -7145,6 +7200,7 @@ void MainWindow::buildQueryMenu(QMenu * menu){ } addMessageText(QString("%1 73").arg(selectedCall), true); + toggleTx(true); }); } @@ -7192,7 +7248,7 @@ void MainWindow::on_tableWidgetRXAll_cellClicked(int row, int col){ auto item = ui->tableWidgetRXAll->item(row, 0); int offset = item->text().toInt(); - setFreq4(offset, offset); + setFreqForRestore(offset, false); ui->tableWidgetCalls->selectionModel()->select( ui->tableWidgetCalls->selectionModel()->selection(), @@ -7215,13 +7271,17 @@ void MainWindow::on_tableWidgetRXAll_cellDoubleClicked(int row, int col){ if(activityAging && d.utcTimestamp.secsTo(now)/60 >= activityAging){ continue; } + // TODO: jsherer - still ok to skip these here? + if(d.isCompound){ + continue; + } if(activityText.isEmpty()){ firstActivity = d.utcTimestamp; } activityText.append(d.text); } if(!activityText.isEmpty()){ - int block = logRxTxMessageText(firstActivity, true, activityText, offset, false); + int block = logRxTxMessageText(firstActivity, activityText, offset, false); m_rxFrameBlockNumbers[offset] = block; m_rxRecentCache.insert(offset/10*10, new QDateTime(QDateTime::currentDateTimeUtc()), 25); } @@ -7242,14 +7302,20 @@ void MainWindow::on_tableWidgetRXAll_selectionChanged(const QItemSelection &sele } void MainWindow::on_tableWidgetCalls_cellClicked(int row, int col){ + /* auto item = ui->tableWidgetCalls->item(row, 0); - auto call = Radio::base_callsign(item->text()); + auto call = Radio::base_callsign(item->data(Qt::UserRole).toString()); + //auto call = Radio::base_callsign(item->text()); + */ + + auto call = callsignSelected(); + if(!m_callActivity.contains(call)){ return; } auto d = m_callActivity[call]; - setFreqForRestore(d.freq, true); + setFreqForRestore(d.freq, false); ui->tableWidgetRXAll->selectionModel()->select( ui->tableWidgetRXAll->selectionModel()->selection(), @@ -7259,8 +7325,13 @@ void MainWindow::on_tableWidgetCalls_cellClicked(int row, int col){ void MainWindow::on_tableWidgetCalls_cellDoubleClicked(int row, int col){ on_tableWidgetCalls_cellClicked(row, col); + /* auto item = ui->tableWidgetCalls->item(row, 0); - auto call = Radio::base_callsign(item->text()); + auto call = Radio::base_callsign(item->data(Qt::UserRole).toString()); + //auto call = Radio::base_callsign(item->text()); + */ + + auto call = callsignSelected(); addMessageText(call); } @@ -8188,7 +8259,10 @@ void MainWindow::updateButtonDisplay(){ QString MainWindow::callsignSelected(){ if(!ui->tableWidgetCalls->selectedItems().isEmpty()){ auto selectedCalls = ui->tableWidgetCalls->selectedItems(); - return Radio::base_callsign(selectedCalls.first()->text()); + if(!selectedCalls.isEmpty()){ + auto call = selectedCalls.first()->data(Qt::UserRole).toString(); + return Radio::base_callsign(call); + } } if(!ui->tableWidgetRXAll->selectedItems().isEmpty()){ @@ -8311,6 +8385,7 @@ void MainWindow::displayActivity(bool force){ ui->tableWidgetRXAll->insertRow(ui->tableWidgetRXAll->rowCount()); auto offsetItem = new QTableWidgetItem(QString("%1").arg(offset)); + offsetItem->setData(Qt::UserRole, QVariant(offset)); ui->tableWidgetRXAll->setItem(ui->tableWidgetRXAll->rowCount() - 1, 0, offsetItem); auto ageItem = new QTableWidgetItem(QString("(%1)").arg(age)); @@ -8367,7 +8442,9 @@ void MainWindow::displayActivity(bool force){ clearTableWidget(ui->tableWidgetCalls); ui->tableWidgetCalls->insertRow(ui->tableWidgetCalls->rowCount()); - ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, new QTableWidgetItem("ALLCALL")); + auto item = new QTableWidgetItem("ALLCALL"); + item->setData(Qt::UserRole, QVariant("ALLCALL")); + ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, item); ui->tableWidgetCalls->setSpan(ui->tableWidgetCalls->rowCount() - 1, 0, 1, ui->tableWidgetCalls->columnCount()); if(selectedCall == "ALLCALL"){ ui->tableWidgetCalls->selectRow(ui->tableWidgetCalls->rowCount() - 1); @@ -8384,7 +8461,11 @@ void MainWindow::displayActivity(bool force){ } ui->tableWidgetCalls->insertRow(ui->tableWidgetCalls->rowCount()); - ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, new QTableWidgetItem(d.call)); + + QString displayCall = d.through.isEmpty() ? d.call : QString("%1 | %2").arg(d.through).arg(d.call); + auto displayItem = new QTableWidgetItem(displayCall); + displayItem->setData(Qt::UserRole, QVariant((d.call))); + ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 0, displayItem); ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 1, new QTableWidgetItem(QString("(%1)").arg(since(d.utcTimestamp)))); ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 2, new QTableWidgetItem(QString("%1").arg(Varicode::formatSNR(d.snr)))); ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, 3, new QTableWidgetItem(QString("%1").arg(d.grid))); @@ -8406,8 +8487,12 @@ void MainWindow::displayActivity(bool force){ // Recently Directed Activity while(!m_rxFrameQueue.isEmpty()){ - RXDetail d = m_rxFrameQueue.first(); - m_rxFrameQueue.removeFirst(); + RXDetail d = m_rxFrameQueue.dequeue(); + + // TODO: jsherer - is it safe to just ignore printing these? + if(d.isCompound){ + continue; + } bool isLast = d.bits == Varicode::FT8CallLast; @@ -8417,7 +8502,7 @@ void MainWindow::displayActivity(bool force){ int freq = d.freq/10*10; int block = m_rxFrameBlockNumbers.contains(freq) ? m_rxFrameBlockNumbers[freq] : -1; - block = logRxTxMessageText(d.utcTimestamp, d.isFree, d.text, d.freq, false, block); + block = logRxTxMessageText(d.utcTimestamp, d.text, d.freq, false, block); m_rxFrameBlockNumbers[freq] = block; if(isLast){ @@ -8447,7 +8532,7 @@ void MainWindow::displayActivity(bool force){ continue; } - // we're only processing allcall and our callsign at this point + // we're only responding to allcall and our callsign at this point, but we'll log callsigns we've heard if(!isAllCall && d.to != m_config.my_callsign().trimmed() && d.to != Radio::base_callsign(m_config.my_callsign()).trimmed()){ continue; } @@ -8460,11 +8545,11 @@ void MainWindow::displayActivity(bool force){ // construct reply QString reply; - // SNR + // QUERIED SNR if(d.cmd == "?"){ reply = QString("%1 SNR %2").arg(Radio::base_callsign(d.from)).arg(Varicode::formatSNR(d.snr)); } - // QTH + // QUERIED QTH else if(d.cmd == "@" && !isAllCall){ QString qth = m_config.my_qth(); if(qth.isEmpty()){ @@ -8477,9 +8562,27 @@ void MainWindow::displayActivity(bool force){ reply = QString("%1 %2").arg(Radio::base_callsign(d.from)).arg(qth); } - // STATION MESSAGE + // QUERIED STATION MESSAGE else if(d.cmd == "&" && !isAllCall){ reply = QString("%1 %2").arg(Radio::base_callsign(d.from)).arg(m_config.my_station()); + } + // QUERIED STATIONS HEARD + else if(d.cmd == "$" && !isAllCall){ + + auto calls = m_callActivity.keys(); + qSort(calls.begin(), calls.end(), [this](QString const &a, QString const &b){ + auto left = m_callActivity[a]; + auto right = m_callActivity[b]; + return right.snr < left.snr; + }); + + QStringList lines; + foreach(auto call, calls){ + auto d = m_callActivity[call]; + lines.append(QString("%1 SNR %2").arg(Radio::base_callsign(call)).arg(Varicode::formatSNR(d.snr))); + } + reply = lines.join('\n'); + } else { continue; } diff --git a/mainwindow.h b/mainwindow.h index b2fe1d1..29ca8d9 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -123,7 +123,7 @@ public slots: QString lookupCallInCompoundCache(QString const &call); void clearActivity(); - int logRxTxMessageText(QDateTime date, bool isFree, QString text, int freq, bool tx, int block=-1); + int logRxTxMessageText(QDateTime date, QString text, int freq, bool tx, int block=-1); void addMessageText(QString text, bool clear=false); void resetMessage(); void resetMessageUI(); @@ -199,6 +199,7 @@ private slots: void on_txb5_doubleClicked (); void on_txb6_clicked(); void on_startTxButton_toggled(bool checked); + void toggleTx(bool start); void splitAndSendNextMessage(); void on_rbNextFreeTextMsg_toggled (bool status); void on_lookupButton_clicked(); @@ -639,6 +640,7 @@ private: struct CallDetail { QString call; + QString through; QString grid; int freq; QDateTime utcTimestamp; @@ -659,6 +661,7 @@ private: { bool isFree; bool isLowConfidence; + bool isCompound; int bits; QString firstCall; QString secondCall; @@ -672,6 +675,7 @@ private: { bool isFree; bool isLowConfidence; + bool isCompound; int bits; int freq; QString text;