From 92da3d1b7f0914fd76011452d1e8509b8169346b Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Fri, 1 Sep 2017 05:23:32 +0000 Subject: [PATCH] Merged from trunk: ------------------------------------------------------------------------ r8052 | k1jt | 2017-08-31 16:04:12 +0100 (Thu, 31 Aug 2017) | 1 line Additions and corrections to User Guide. ------------------------------------------------------------------------ r8053 | k1jt | 2017-08-31 20:22:44 +0100 (Thu, 31 Aug 2017) | 2 lines Update the text for some keyboard shortcuts. ------------------------------------------------------------------------ r8054 | k1jt | 2017-08-31 21:16:38 +0100 (Thu, 31 Aug 2017) | 2 lines Fix a comment in ft8_decode.f90. ------------------------------------------------------------------------ r8055 | k1jt | 2017-08-31 21:23:18 +0100 (Thu, 31 Aug 2017) | 2 lines A few more User-Guide updates. ------------------------------------------------------------------------ r8056 | bsomervi | 2017-09-01 06:11:57 +0100 (Fri, 01 Sep 2017) | 15 lines Many improvements to decode double click and auoto-sequencing behaviour Double-clicks on 73 messages fixed. Fixed issue with incorrect decode being selected by double-click. Refined auto-sequencing sign off handling, any 73 or rr73 free text near Rx or Tx frequency taken as a 73 from QSO partner but if a standard message then must be from QSO partner. Moved functionality from MainWindow to DecodedText class. More efficient picking of messages from decoded text windows by extracting the text block directly. Tighten up use of RR73 and skipping Tx1, these are now disabled for relevant compound calls where they would break the QSO exchange. ------------------------------------------------------------------------ git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx-1.8@8057 ab8295b8-cf94-4d9e-aec4-7959e3be5d79 --- decodedtext.cpp | 79 +++++--- decodedtext.h | 8 +- displaytext.cpp | 127 +++++++------ doc/user_guide/en/make-qso.adoc | 39 +++- doc/user_guide/en/new_features.adoc | 3 +- doc/user_guide/en/tutorial-example3.adoc | 10 +- doc/user_guide/en/vhf-features.adoc | 20 +- lib/ft8_decode.f90 | 4 +- lib/stdmsg.f90 | 2 +- mainwindow.cpp | 231 +++++++++++------------ mainwindow.h | 4 +- mainwindow.ui | 14 +- shortcuts.txt | 4 +- 13 files changed, 296 insertions(+), 249 deletions(-) diff --git a/decodedtext.cpp b/decodedtext.cpp index 1527d4354..4cd085f27 100644 --- a/decodedtext.cpp +++ b/decodedtext.cpp @@ -7,6 +7,11 @@ extern "C" { bool stdmsg_(const char* msg, int len); } +namespace +{ + QRegularExpression words_re {R"(^(?:(?(?:CQ|DE|QRZ)(?:\s?DX|\s(?:[A-Z]{2}|\d{3}))|[A-Z0-9/]+)\s)(?:(?[A-Z0-9/]+)(?:\sR\s)?(?:\s(?[-+A-Z0-9]+)(?:\s(?OOO))?)?)?)"}; +} + DecodedText::DecodedText (QString const& the_string) : string_ {the_string} , padding_ {the_string.indexOf (" ") > 4 ? 2 : 0} // allow for @@ -14,28 +19,39 @@ DecodedText::DecodedText (QString const& the_string) , message_ {string_.mid (column_qsoText + padding_).trimmed ()} , is_standard_ {false} { + string_ = string_.left (column_qsoText + padding_ + 25); if (message_.length() >= 1) { - message_ = message_.left (22).remove (QRegularExpression {"[<>]"}); + message_ = message_.left (21).remove (QRegularExpression {"[<>]"}); int i1 = message_.indexOf ('\r'); if (i1 > 0) { message_ = message_.left (i1 - 1); } + if (message_.contains (QRegularExpression {"^(CQ|QRZ)\\s"})) + { + // TODO this magic position 16 is guaranteed to be after the + // last space in a decoded CQ or QRZ message but before any + // appended DXCC entity name or worked before information + auto eom_pos = message_.indexOf (' ', 16); + // we always want at least the characters to position 16 + if (eom_pos < 16) eom_pos = message_.size () - 1; + // remove DXCC entity and worked B4 status. TODO need a better way to do this + message_ = message_.left (eom_pos + 1); + } // stdmsg is a fortran routine that packs the text, unpacks it and compares the result is_standard_ = stdmsg_ ((message_ + " ").toLatin1 ().constData (),22); } }; -void DecodedText::removeAddedInfo () +QStringList DecodedText::messageWords () const { - if (string_.indexOf (" CQ ") > 0) { - // TODO this magic 37 characters is also referenced in DisplayText::_appendDXCCWorkedB4() - auto eom_pos = string_.indexOf (' ', 37); - if (eom_pos < 37) eom_pos = string_.size () - 1; // we always want at least the characters - // to position 37 - string_ = string_.left (eom_pos + 1); // remove DXCC entity and worked B4 status. TODO need a better way to do this - } + if (is_standard_) + { + // extract up to the first four message words + return words_re.match (message_).capturedTexts (); + } + return message_.split (' '); // simple word split for free text messages } QString DecodedText::CQersCall() const @@ -132,35 +148,38 @@ bool DecodedText::report(QString const& myBaseCall, QString const& dxBaseCall, / // get the first text word, usually the call QString DecodedText::call() const { - auto call = string_; - call = call.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").mid (column_qsoText + padding_); - int i = call.indexOf(" "); - return call.mid(0,i); + return words_re.match (message_).captured ("word1"); } // get the second word, most likely the de call and the third word, most likely grid void DecodedText::deCallAndGrid(/*out*/QString& call, QString& grid) const { - auto msg = string_; - if(msg.mid(4,1)!=" ") msg=msg.mid(0,4)+msg.mid(6,-1); //Remove seconds from UTC - msg = msg.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").mid (column_qsoText + padding_); - int i1 = msg.indexOf (" "); - call = msg.mid (i1 + 1); - int i2 = call.indexOf (" "); - if (" R " == call.mid (i2, 3)) // MSK144 contest mode report - { - grid = call.mid (i2 + 3, 4); - } - else - { - grid = call.mid (i2 + 1, 4); - } - call = call.left (i2).replace (">", ""); + auto const& match = words_re.match (message_); + call = match.captured ("word2"); + grid = match.captured ("word3"); + + // auto msg = string_; + // if(msg.mid(4,1)!=" ") msg=msg.mid(0,4)+msg.mid(6,-1); //Remove seconds from UTC + // msg = msg.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").mid (column_qsoText + padding_); + // int i1 = msg.indexOf (" "); + // call = msg.mid (i1 + 1); + // int i2 = call.indexOf (" "); + // if (" R " == call.mid (i2, 3)) // MSK144 contest mode report + // { + // grid = call.mid (i2 + 3, 4); + // } + // else + // { + // grid = call.mid (i2 + 1, 4); + // } + // call = call.left (i2).replace (">", ""); } -int DecodedText::timeInSeconds() const +unsigned DecodedText::timeInSeconds() const { - return 60*string_.mid(column_time,2).toInt() + string_.mid(2,2).toInt(); + return 3600 * string_.mid (column_time, 2).toUInt () + + 60 * string_.mid (column_time + 2, 2).toUInt() + + (padding_ ? string_.mid (column_time + 2 + padding_, 2).toUInt () : 0U); } /* diff --git a/decodedtext.h b/decodedtext.h index 49b054c71..18859026a 100644 --- a/decodedtext.h +++ b/decodedtext.h @@ -14,8 +14,10 @@ /* -0123456789012345678901234567890123456789 +012345678901234567890123456789012345678901 ^ ^ ^ ^ ^ ^ +2343 -11 0.8 1259 # CQ VP2X/GM4WJS GL33 +2343 -11 0.8 1259 # CQ 999 VP2V/GM4WJS 2343 -11 0.8 1259 # YV6BFE F6GUU R-08 2343 -19 0.3 718 # VE6WQ SQ2NIJ -14 2343 -7 0.3 815 # KK4DSD W7VP -16 @@ -30,7 +32,7 @@ public: explicit DecodedText (QString const&); QString string() const { return string_; }; - void removeAddedInfo (); + QStringList messageWords () const; int indexOf(QString s) const { return string_.indexOf(s); }; int indexOf(QString s, int i) const { return string_.indexOf(s,i); }; QString mid(int f, int t) const { return string_.mid(f,t); }; @@ -58,7 +60,7 @@ public: // get the second word, most likely the de call and the third word, most likely grid void deCallAndGrid(/*out*/QString& call, QString& grid) const; - int timeInSeconds() const; + unsigned timeInSeconds() const; // returns a string of the SNR field with a leading + or - followed by two digits QString report() const; diff --git a/displaytext.cpp b/displaytext.cpp index fa62940d6..096e46cfc 100644 --- a/displaytext.cpp +++ b/displaytext.cpp @@ -85,77 +85,82 @@ QString DisplayText::appendDXCCWorkedB4(QString message, QString const& callsign QColor color_DXCC, QColor color_NewCall) { - QString call = callsign; - QString countryName; - bool callWorkedBefore; - bool countryWorkedBefore; + // allow for seconds + unsigned padding {message.indexOf (" ") > 4 ? 2U : 0U}; + QString call = callsign; + QString countryName; + bool callWorkedBefore; + bool countryWorkedBefore; - if(call.length()==2) { - int i0=message.indexOf("CQ "+call); - call=message.mid(i0+6,-1); - i0=call.indexOf(" "); - call=call.mid(0,i0); - } - if(call.length()<3) return message; - if(!call.contains(QRegExp("[0-9]|[A-Z]"))) return message; + if(call.length()==2) { + int i0=message.indexOf("CQ "+call); + call=message.mid(i0+6,-1); + i0=call.indexOf(" "); + call=call.mid(0,i0); + } + if(call.length()<3) return message; + if(!call.contains(QRegExp("[0-9]|[A-Z]"))) return message; - logBook.match(/*in*/call,/*out*/countryName,callWorkedBefore,countryWorkedBefore); - int charsAvail = 48; + logBook.match(/*in*/call,/*out*/countryName,callWorkedBefore,countryWorkedBefore); + int charsAvail = 52 + padding; - // the decoder (seems) to always generate 41 chars. For a normal CQ call, the last five are spaces - // TODO this magic 37 characters is also referenced in MainWindow::doubleClickOnCall() - int nmin=37; - int i=message.indexOf(" CQ "); - int k=message.mid(i+4,3).toInt(); - if(k>0 and k<999) nmin += 4; - int s3 = message.indexOf(" ",nmin); - if (s3 < nmin) s3 = nmin; // always want at least the characters to position 35 - s3 += 1; // convert the index into a character count - message = message.left(s3); // reduce trailing white space - charsAvail -= s3; - if (charsAvail > 4) + // the decoder (seems) to always generate 41 chars. For a normal CQ + // call, the last five are spaces + // + // A maximum length call is "QRZ VP2X/GM4WJS IO91" "CQ AA ..." or CQ + // nnn ..." don't allow grid squares so are not longer. Here we align + // the added info at least after the longest CQ/QRZ message plus one + // space so that it can be stripped off algorithmically later. + // + int nmin = 46 + padding; + int s3 = message.indexOf (" ", nmin); + if (s3 < nmin) s3 = nmin; // always want at least the characters to position 45 + s3 += 1; // convert the index into a character count + message = message.left(s3); // reduce trailing white space + charsAvail -= s3; + if (charsAvail > 4) { - if (!countryWorkedBefore) // therefore not worked call either + if (!countryWorkedBefore) // therefore not worked call either { - message += "!"; - *bg = color_DXCC; + message += "!"; + *bg = color_DXCC; } + else + if (!callWorkedBefore) // but have worked the country + { + message += "~"; + *bg = color_NewCall; + } else - if (!callWorkedBefore) // but have worked the country - { - message += "~"; - *bg = color_NewCall; - } - else - { - message += " "; // have worked this call before - *bg = color_CQ; - } - charsAvail -= 1; + { + message += " "; // have worked this call before + *bg = color_CQ; + } + charsAvail -= 1; - // do some obvious abbreviations - countryName.replace ("Islands", "Is."); - countryName.replace ("Island", "Is."); - countryName.replace ("North ", "N. "); - countryName.replace ("Northern ", "N. "); - countryName.replace ("South ", "S. "); - countryName.replace ("East ", "E. "); - countryName.replace ("Eastern ", "E. "); - countryName.replace ("West ", "W. "); - countryName.replace ("Western ", "W. "); - countryName.replace ("Central ", "C. "); - countryName.replace (" and ", " & "); - countryName.replace ("Republic", "Rep."); - countryName.replace ("United States", "U.S.A."); - countryName.replace ("Fed. Rep. of ", ""); - countryName.replace ("French ", "Fr."); - countryName.replace ("Asiatic", "AS"); - countryName.replace ("European", "EU"); - countryName.replace ("African", "AF"); + // do some obvious abbreviations + countryName.replace ("Islands", "Is."); + countryName.replace ("Island", "Is."); + countryName.replace ("North ", "N. "); + countryName.replace ("Northern ", "N. "); + countryName.replace ("South ", "S. "); + countryName.replace ("East ", "E. "); + countryName.replace ("Eastern ", "E. "); + countryName.replace ("West ", "W. "); + countryName.replace ("Western ", "W. "); + countryName.replace ("Central ", "C. "); + countryName.replace (" and ", " & "); + countryName.replace ("Republic", "Rep."); + countryName.replace ("United States", "U.S.A."); + countryName.replace ("Fed. Rep. of ", ""); + countryName.replace ("French ", "Fr."); + countryName.replace ("Asiatic", "AS"); + countryName.replace ("European", "EU"); + countryName.replace ("African", "AF"); - message += countryName; + message += countryName; } - return message; + return message; } void DisplayText::displayDecodedText(DecodedText const& decodedText, QString const& myCall, diff --git a/doc/user_guide/en/make-qso.adoc b/doc/user_guide/en/make-qso.adoc index 1f52ad075..e42da72c5 100644 --- a/doc/user_guide/en/make-qso.adoc +++ b/doc/user_guide/en/make-qso.adoc @@ -36,6 +36,14 @@ audible (to someone with very good hearing) around –15 dB. Thresholds for decodability are around -20 dB for FT8, -23 dB for JT4, –25 dB for JT65, –27 dB for JT9. +NOTE: Several options are available for circumstances where fast QSOs +are desirable. Double-click the *Tx1* control under _Now_ or _Next_ +to toggle use of the Tx2 message rather than Tx1 to start a QSO. +Similarly, double-click the *Tx4* control to toggle between sending +`RRR` and `RR73` in that message. The `RR73` message should be used +only if you are reasonably confident that no repititions will be +required. + === Free-Text Messages Users often add some friendly chit-chat at the end of a QSO. @@ -62,10 +70,30 @@ When calling CQ you may also choose to check the box *Call 1st*. _WSJT-X_ will then respond automatically to the first decoded responder to your CQ. -NOTE: When *Auto-Seq* is enabled the program de-activates *Enable -Tx* at the end of each QSO. We do not want _WSJT-X_ to make fully -automated QSOs. +NOTE: When *Auto-Seq* is enabled the program de-activates *Enable Tx* +at the end of each QSO. It is not intended that _WSJT-X_ should make +fully automated QSOs. +=== VHF Contest Mode + +A special *VHF Contest Mode* can be activated for FT8 and MSK144 modes +by checking a box on the *Settings | Advanced* tab. This mode is +configured especially for VHF contests in which four-character grid +locators are the required exchange. When *Contest Mode* is active, +the standard QSO sequence looks like this: + + CQ K1ABC FN42 + K1ABC W9XYZ EN37 + W9XYZ K1ABC R FN42 + K1ABC W9XYZ RRR + W9XYZ K1ABC 73 + +In contest circumstances K1ABC might choose to call CQ again rather +than sending 73 for his third transmission. + +IMPORTANT: Do not use VHF Contest Mode on an HF band or in conditions +where worldwide propagation is available. See +<> for further details. [[COMP-CALL]] === Compound Callsigns @@ -174,10 +202,13 @@ as the following checklist: - Computer clock properly synchronized to UTC within ±1 s +- Audio input and output devices configured for sample rate 48000 Hz, +16 bits + - Radio set to *USB* (upper sideband) mode - Radio filters centered and set to widest available passband (up to 5 kHz). TIP: Remember that in many circumstances FT8, JT4, JT9, JT65, and WSPR do not require high power. Under most HF propagation conditions, QRP -is usually the norm. +is the norm. diff --git a/doc/user_guide/en/new_features.adoc b/doc/user_guide/en/new_features.adoc index d737ebb13..8f413d73c 100644 --- a/doc/user_guide/en/new_features.adoc +++ b/doc/user_guide/en/new_features.adoc @@ -51,4 +51,5 @@ source-code repository can be found at {devsvn}, and most communication among the developers takes place on the email reflector {devmail}. Bug reports and suggestions for new features, improvements to the _WSJT-X_ User Guide, etc., may also be sent to the -{wsjt_yahoo_group} email reflector. +{wsjt_yahoo_group} email reflector. You must join the relevant group +before posting to either email list. diff --git a/doc/user_guide/en/tutorial-example3.adoc b/doc/user_guide/en/tutorial-example3.adoc index 09875df2a..f32ae3aa3 100644 --- a/doc/user_guide/en/tutorial-example3.adoc +++ b/doc/user_guide/en/tutorial-example3.adoc @@ -34,7 +34,8 @@ markers and both spinner controls will follow your selections. - Double-clicking at any frequency on the waterfall does all the things just described and also invokes the decoder in a small range -around that frequency. +around that frequency. To decode a particular signal, double-click +near the left edge of its waterfall trace. - Now double-click on any of the the lines of decoded text in the main window. Unless you have *My Call* set to K1JT or KY7M on the @@ -46,5 +47,12 @@ This behavior is desirable so that you will not inadvertently change your Tx frequency to that of a tail-ender who called you somewhere else in the FT8 subband. +NOTE: The FT8 decoder can often copy several overlapping signals at +nearly the same frequency. However, in crowded band conditions you +will often find it advantageous to move off the frequency of the +station you are calling. Keyboard shortcuts *Shift+F11* and +*Shift+F12* provide an easy way to move your Tx frequency in 60 Hz +steps. + IMPORTANT: When finished with this Tutorial, don't forget to re-enter your own callsign as *My Call* on the *Settings | General* tab. diff --git a/doc/user_guide/en/vhf-features.adoc b/doc/user_guide/en/vhf-features.adoc index cfa077db1..4a595daaa 100644 --- a/doc/user_guide/en/vhf-features.adoc +++ b/doc/user_guide/en/vhf-features.adoc @@ -1,5 +1,8 @@ _WSJT-X_ v1.8 suppports a number of features designed for use -on the VHF and higher bands. These features now include: +on the VHF and higher bands. These features include: + +- *FT8*, a mode designed for making fast QSOs with weak, fading +signals - *JT4*, a mode particularly useful for EME on the microwave bands @@ -279,21 +282,6 @@ messages at 50 or 70 MHz. At these frequencies, most pings are long enough to support standard messages -- which have the advantage of being readable by anyone listening in. -- A special *VHF Contest Mode* for FT8 and MSK144 can be activated by -checking a box on the *Settings | Advanced* tab. This mode is -configured especially for VHF contests in which four-character grid -locators are the required exchange. When *Contest Mode* is active, -the standard QSO sequence looks like this: - - CQ K1ABC FN42 - K1ABC W9XYZ EN37 - W9XYZ K1ABC R FN42 - K1ABC W9XYZ RRR - W9XYZ K1ABC 73 - -In contest circumstances K1ABC might choose to call CQ again rather -than sending 73 for his third transmission. - === Echo Mode *Echo* mode allows you to make sensitive measurements of your own diff --git a/lib/ft8_decode.f90 b/lib/ft8_decode.f90 index 1ed94473b..61984932b 100644 --- a/lib/ft8_decode.f90 +++ b/lib/ft8_decode.f90 @@ -67,8 +67,8 @@ contains ! For now: ! ndepth=1: no subtraction, 1 pass, belief propagation only -! ndepth=2: subtraction, 2 passes, belief propagation only -! ndepth=3: subtraction, 2 passes, bp+osd +! ndepth=2: subtraction, 3 passes, belief propagation only +! ndepth=3: subtraction, 3 passes, bp+osd if(ndepth.eq.1) npass=1 if(ndepth.ge.2) npass=3 do ipass=1,npass diff --git a/lib/stdmsg.f90 b/lib/stdmsg.f90 index b006fda93..97e842c1d 100644 --- a/lib/stdmsg.f90 +++ b/lib/stdmsg.f90 @@ -6,7 +6,7 @@ logical*1 function stdmsg(msg0) call packmsg(msg0,dat,itype) call unpackmsg(dat,msg) - stdmsg=(msg.eq.msg0) .and. (itype.ge.0) + stdmsg=(msg.eq.msg0) .and. (itype.ge.0) .and. itype.ne.6 return end function stdmsg diff --git a/mainwindow.cpp b/mainwindow.cpp index 85f690964..abb2174ab 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -101,8 +101,6 @@ extern "C" { void geniscat_(char* msg, char* msgsent, int itone[], int len1, int len2); - bool stdmsg_(const char* msg, int len); - void azdist_(char* MyGrid, char* HisGrid, double* utch, int* nAz, int* nEl, int* nDmiles, int* nDkm, int* nHotAz, int* nHotABetter, int len1, int len2); @@ -1431,12 +1429,11 @@ void MainWindow::fastSink(qint64 frames) m_logBook,m_config.color_CQ(),m_config.color_MyCall(),m_config.color_DXCC(), m_config.color_NewCall()); m_bDecoded=true; - auto_sequence (message, ui->sbFtol->value (), std::numeric_limits::max ()); + auto_sequence (decodedtext, ui->sbFtol->value (), std::numeric_limits::max ()); if (m_mode != "ISCAT") postDecode (true, decodedtext.string ()); writeAllTxt(message); bool stdMsg = decodedtext.report(m_baseCall, Radio::base_callsign(ui->dxCallEntry->text()),m_rptRcvd); - decodedtext = DecodedText {message.left (4) + message.mid (6, -1)}; if (stdMsg) pskPost (decodedtext); } @@ -2554,16 +2551,13 @@ void MainWindow::decode() //decode() void::MainWindow::fast_decode_done() { float t,tmax=-99.0; - QString msg0; dec_data.params.nagain=false; dec_data.params.ndiskdat=false; // if(m_msg[0][0]==0) m_bDecoded=false; - for(int i=0; i<100; i++) { - if (tmax >= 0.0) auto_sequence (msg0, ui->sbFtol->value (), ui->sbFtol->value ()); - if(m_msg[i][0]==0) break; + for(int i=0; m_msg[i][0] && i<100; i++) { QString message=QString::fromLatin1(m_msg[i]); m_msg[i][0]=0; - if(message.length()>80) message=message.mid(0,80); + if(message.length()>80) message=message.left (80); if(narg[13]/8==narg[12]) message=message.trimmed().replace("<...>",m_calls); //Left (Band activity) window @@ -2576,7 +2570,6 @@ void::MainWindow::fast_decode_done() t=message.mid(10,5).toFloat(); if(t>tmax) { - msg0=message; tmax=t; m_bDecoded=true; } @@ -2585,14 +2578,13 @@ void::MainWindow::fast_decode_done() if(m_mode=="JT9" or m_mode=="MSK144") { // find and extract any report for myCall - QString msg=message.mid(0,4) + message.mid(6,-1); - decodedtext = DecodedText {msg.replace (QChar::LineFeed, "")}; bool stdMsg = decodedtext.report(m_baseCall, Radio::base_callsign(ui->dxCallEntry->text()), m_rptRcvd); // extract details and send to PSKreporter if (stdMsg) pskPost (decodedtext); } + if (tmax >= 0.0) auto_sequence (decodedtext, ui->sbFtol->value (), ui->sbFtol->value ()); } m_startAnother=m_loopall; m_nPick=0; @@ -2736,7 +2728,7 @@ void MainWindow::readFromStdout() //readFromStdout // int snr=decodedtext.string().mid(6,4).toInt(); m_bDoubleClicked=true; m_bAutoReply = true; - processMessage (decodedtext.string (), decodedtext.string ().size ()); + processMessage (decodedtext); ui->cbFirst->setStyleSheet(""); } else { if (for_us or (abs(audioFreq - m_wideGraph->rxFreq()) <= 10)) bDisplayRight=true; @@ -2757,9 +2749,9 @@ void MainWindow::readFromStdout() //readFromStdout if(b65 and m_modeTx!="JT65") on_pbTxMode_clicked(); if(!b65 and m_modeTx=="JT65") on_pbTxMode_clicked(); } - m_QSOText = decodedtext.string (); + m_QSOText = decodedtext.string ().trimmed (); } - if(m_mode=="FT8" or m_mode=="QRA64") auto_sequence (decodedtext.string(), 25, 50); + if(m_mode=="FT8" or m_mode=="QRA64") auto_sequence (decodedtext, 25, 50); postDecode (true, decodedtext.string ()); @@ -2793,40 +2785,50 @@ void MainWindow::readFromStdout() //readFromStdout // another caller and we are going to transmit within // +/- this value of the reply to another caller // -void MainWindow::auto_sequence (QString const& message, unsigned start_tolerance, unsigned stop_tolerance) +void MainWindow::auto_sequence (DecodedText const& message, unsigned start_tolerance, unsigned stop_tolerance) { - auto const& parts = message.split (' ', QString::SkipEmptyParts); - if (parts.size () > 6) { - bool ok; - auto df = parts[3].toInt (&ok); - auto within_tolerance = ok - && (qAbs (ui->RxFreqSpinBox->value () - df) <= int (start_tolerance) - || qAbs (ui->TxFreqSpinBox->value () - df) <= int (start_tolerance)); + auto const& message_words = message.messageWords (); + auto is_73 = message_words.filter (QRegularExpression {"^(73|RR73)$"}).size (); + if (message_words.size () > 2 && (message.isStandardMessage () || is_73)) { + auto df = message.frequencyOffset (); + auto within_tolerance = + (qAbs (ui->RxFreqSpinBox->value () - df) <= int (start_tolerance) + || qAbs (ui->TxFreqSpinBox->value () - df) <= int (start_tolerance)); + bool acceptable_73 = is_73 + && m_QSOProgress >= ROGER_REPORT + && ((message.isStandardMessage () + && (message_words.contains (m_baseCall) + || message_words.contains (m_config.my_callsign ()) + || message_words.contains (ui->dxCallEntry->text ()) + || message_words.contains (Radio::base_callsign (ui->dxCallEntry->text ())) + || message_words.contains ("DE"))) + || !message.isStandardMessage ()); // free text 73/RR73 if (m_auto && (REPLYING == m_QSOProgress || (!ui->tx1->isEnabled () && REPORT == m_QSOProgress)) && qAbs (ui->TxFreqSpinBox->value () - df) <= int (stop_tolerance) - && !parts[5].contains (QRegularExpression {"(^(CQ|QRZ)$)|" + m_baseCall}) - && parts[6].contains (Radio::base_callsign (ui->dxCallEntry->text ()))) { + && !message_words.at (1).contains (QRegularExpression {"(^(CQ|QRZ))|" + m_baseCall}) + && message_words.at (2).contains (Radio::base_callsign (ui->dxCallEntry->text ()))) { // auto stop to avoid accidental QRM auto_tx_mode (false); } else if (m_auto // transmit allowed && ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isChecked() // auto-sequencing allowed && ((!m_bCallingCQ // not calling CQ/QRZ - && !m_sentFirst73 // finished QSO - && ((parts[5].contains (m_baseCall) + && !m_sentFirst73 // not finished QSO + && ((message_words.at (1).contains (m_baseCall) // being called and not already in a QSO - && parts[6].contains (Radio::base_callsign (ui->dxCallEntry->text ()))) + && message_words.at (2).contains (Radio::base_callsign (ui->dxCallEntry->text ()))) // type 2 compound replies || (within_tolerance - && ((m_QSOProgress >= ROGER_REPORT && message_is_73 (0, parts)) - || ("DE" == parts[5] && parts[6].contains (Radio::base_callsign (m_hisCall))))))) + && (acceptable_73 + || ("DE" == message_words.at (1) && message_words.at (2).contains (Radio::base_callsign (m_hisCall))))))) || (m_bCallingCQ && m_bAutoReply - && ((within_tolerance && "DE" == parts[5]) // look for type 2 compound call replies on our Tx and Rx offsets - || parts[5].contains (m_baseCall))))) + // look for type 2 compound call replies on our Tx and Rx offsets + && ((within_tolerance && "DE" == message_words.at (1)) + || message_words.at (1).contains (m_baseCall))))) { - processMessage (message, message.size ()); + processMessage (message); } } } @@ -3478,7 +3480,10 @@ void MainWindow::on_txrb1_toggled (bool status) void MainWindow::on_txrb1_doubleClicked () { - ui->tx1->setEnabled (!ui->tx1->isEnabled ()); + // skip Tx1, only allowed if not a type 2 compound callsign + auto const& my_callsign = m_config.my_callsign (); + auto is_compound = my_callsign != m_baseCall; + ui->tx1->setEnabled ((is_compound && shortList (my_callsign)) || !ui->tx1->isEnabled ()); if (!ui->tx1->isEnabled ()) { // leave time for clicks to complete before setting txrb2 QTimer::singleShot (500, ui->txrb2, SLOT (click ())); @@ -3512,7 +3517,10 @@ void MainWindow::on_txrb4_toggled (bool status) void MainWindow::on_txrb4_doubleClicked () { - m_send_RR73 = !m_send_RR73; + // RR73 only allowed if not a type 2 compound callsign + auto const& my_callsign = m_config.my_callsign (); + auto is_compound = my_callsign != m_baseCall; + m_send_RR73 = !((is_compound && !shortList (my_callsign)) || m_send_RR73); genStdMsgs (m_rpt); } @@ -3551,7 +3559,10 @@ void MainWindow::on_txb1_clicked() void MainWindow::on_txb1_doubleClicked() { - ui->tx1->setEnabled (!ui->tx1->isEnabled ()); + // skip Tx1, only allowed if not a type 1 compound callsign + auto const& my_callsign = m_config.my_callsign (); + auto is_compound = my_callsign != m_baseCall; + ui->tx1->setEnabled ((is_compound && shortList (my_callsign)) || !ui->tx1->isEnabled ()); } void MainWindow::on_txb2_clicked() @@ -3580,7 +3591,10 @@ void MainWindow::on_txb4_clicked() void MainWindow::on_txb4_doubleClicked() { - m_send_RR73 = !m_send_RR73; + // RR73 only allowed if not a type 2 compound callsign + auto const& my_callsign = m_config.my_callsign (); + auto is_compound = my_callsign != m_baseCall; + m_send_RR73 = !((is_compound && !shortList (my_callsign)) || m_send_RR73); genStdMsgs (m_rpt); } @@ -3627,28 +3641,18 @@ void MainWindow::doubleClickOnCall(bool alt, bool ctrl) } else { cursor=ui->decodedTextBrowser2->textCursor(); } - - cursor.select(QTextCursor::LineUnderCursor); - int position {cursor.position()}; - - QString messages; - if(!m_decodedText2) messages= ui->decodedTextBrowser2->toPlainText(); - if(m_decodedText2) messages= ui->decodedTextBrowser->toPlainText(); - if(ui->cbCQTx->isEnabled() && ui->cbCQTx->isChecked()) m_bDoubleClickAfterCQnnn=true; - m_bDoubleClicked=true; - processMessage(messages, position, ctrl, alt); + cursor.setPosition (cursor.selectionStart ()); + DecodedText message {cursor.block ().text ()}; + m_bDoubleClicked = true; + processMessage (message, ctrl, alt); } -void MainWindow::processMessage(QString const& messages, int position, bool ctrl, bool alt) +void MainWindow::processMessage(DecodedText const& message, bool ctrl, bool alt) { - QString t1 = messages.left(position); //contents up to \n on selected line - int i1=t1.lastIndexOf(QChar::LineFeed) + 1; //points to first char of line - QString t2 = messages.mid(i1,position-i1); //selected line - // basic mode sanity checks - auto const& parts = t2.split (' ', QString::SkipEmptyParts); + auto const& parts = message.string ().split (' ', QString::SkipEmptyParts); if (parts.size () < 5) return; - auto const& mode = parts[4].mid(0,1); + auto const& mode = parts.at (4).left (1); if (("JT9+JT65" == m_mode && !("@" == mode || "#" == mode)) || ("JT65" == m_mode && mode != "#") || ("JT9" == m_mode && mode != "@") @@ -3657,22 +3661,21 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl return; } - QString t2a; - int ntsec=3600*t2.mid(0,2).toInt() + 60*t2.mid(2,2).toInt(); - if(m_bFastMode or m_mode=="FT8") { - ntsec = ntsec + t2.mid(4,2).toInt(); - t2a = t2.left (4) + t2.mid (6); //Change hhmmss to hhmm for the message parser - } - t2a = t2.left (44); // strip any quality info trailing the + // QString t2a; + // int ntsec=3600*t2.mid(0,2).toInt() + 60*t2.mid(2,2).toInt(); + // if(m_bFastMode or m_mode=="FT8") { + // ntsec = ntsec + t2.mid(4,2).toInt(); + // t2a = t2.left (4) + t2.mid (6); //Change hhmmss to hhmm for the message parser + // } + //t2a = t2.left (44); // strip any quality info trailing the // decoded message if(m_bFastMode or m_mode=="FT8") { - i1=t2a.indexOf(" CQ "); + auto i1=message.string ().indexOf(" CQ "); if(i1>10) { bool ok; - Frequency kHz {t2a.mid (i1+4,3).toUInt (&ok)}; + Frequency kHz {message.string ().mid (i1+4,3).toUInt (&ok)}; if(ok && kHz <= 999) { - t2a = t2a.mid (0, i1+4) + t2a.mid (i1+8, -1); if (m_config.is_transceiver_online ()) { //QSY Freq for answering CQ nnn setRig (m_freqNominal / 1000000 * 1000000 + 1000 * kHz); @@ -3681,74 +3684,58 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl } } } - DecodedText decodedtext {t2a}; - decodedtext.removeAddedInfo (); - auto t3 = decodedtext.string (); - auto t4 = t3.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").split (" ", QString::SkipEmptyParts); - if(t4.size () < 6) return; //Skip the rest if no decoded text - - int frequency = decodedtext.frequencyOffset(); - if (ui->RxFreqSpinBox->isEnabled () and m_mode != "MSK144") { - ui->RxFreqSpinBox->setValue (frequency); //Set Rx freq - } - - if (decodedtext.isTX()) { + //Skip the rest if no decoded text extracted + int frequency = message.frequencyOffset(); + if (message.isTX()) { if (!m_config.enable_VHF_features() && ctrl && ui->TxFreqSpinBox->isEnabled()) { ui->TxFreqSpinBox->setValue(frequency); //Set Tx freq } return; } - int nmod=ntsec % (2*m_TRperiod); + int nmod = message.timeInSeconds () % (2*m_TRperiod); m_txFirst=(nmod!=0); ui->txFirstCheckBox->setChecked(m_txFirst); + auto const& message_words = message.messageWords (); + if (message_words.size () < 2) return; + QString hiscall; QString hisgrid; - decodedtext.deCallAndGrid(/*out*/hiscall,hisgrid); - bool is_73 = t4.filter (QRegularExpression {"^(73|RR73)$"}).size (); - auto acceptable_73 = - m_QSOProgress >= ROGER_REPORT - && is_73 - && ((decodedtext.isStandardMessage () - && (t4.contains (m_baseCall) - || t4.contains (m_config.my_callsign ()) - || t4.contains (ui->dxCallEntry->text ()) - || t4.contains (Radio::base_callsign (ui->dxCallEntry->text ())) - || t4.contains ("DE"))) - || !decodedtext.isStandardMessage ()); - if ((is_73 && !acceptable_73) - || (!Radio::is_callsign (hiscall) // not interested if not from QSO partner - && !(t4.size () == 7 // unless it is of the form - && (t4.at (5) == m_baseCall // " 73" - || t4.at (5).startsWith (m_baseCall + '/') - || t4.at (5).endsWith ('/' + m_baseCall)) - && t4.at (6) == "73"))) + message.deCallAndGrid(/*out*/hiscall,hisgrid); + auto is_73 = message_words.filter (QRegularExpression {"^(73|RR73)$"}).size (); + if (!is_73 && !message.isStandardMessage ()) + // && (!Radio::is_callsign (hiscall) // not interested if not from QSO partner + // && !(t4.size () == 7 // unless it is of the form + // && (t4.at (5) == m_baseCall // " 73" + // || t4.at (5).startsWith (m_baseCall + '/') + // || t4.at (5).endsWith ('/' + m_baseCall)) + // && t4.at (6) == "73"))) { qDebug () << "Not processing message - hiscall:" << hiscall << "hisgrid:" << hisgrid; return; } // only allow automatic mode changes between JT9 and JT65, and when not transmitting if (!m_transmitting and m_mode == "JT9+JT65") { - if (decodedtext.isJT9()) + if (message.isJT9()) { m_modeTx="JT9"; ui->pbTxMode->setText("Tx JT9 @"); m_wideGraph->setModeTx(m_modeTx); - } else if (decodedtext.isJT65()) { + } else if (message.isJT65()) { m_modeTx="JT65"; ui->pbTxMode->setText("Tx JT65 #"); m_wideGraph->setModeTx(m_modeTx); } - } else if ((decodedtext.isJT9 () and m_modeTx != "JT9" and m_mode != "JT4") or - (decodedtext.isJT65 () and m_modeTx != "JT65" and m_mode != "JT4")) { + } else if ((message.isJT9 () and m_modeTx != "JT9" and m_mode != "JT4") or + (message.isJT65 () and m_modeTx != "JT65" and m_mode != "JT4")) { // if we are not allowing mode change then don't process decode return; } - QString firstcall = decodedtext.call(); + QString firstcall = message.call(); if(!m_bFastMode and (!m_config.enable_VHF_features() or m_mode=="FT8")) { // Don't change Tx freq if in a fast mode, or VHF features enabled; also not if a // station is calling me, unless m_lockTxFreq is true or CTRL is held down. @@ -3768,7 +3755,7 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl auto base_call = Radio::base_callsign (hiscall); // Determine appropriate response to received message - auto dtext = " " + decodedtext.string () + " "; + auto dtext = " " + message.string () + " "; int gen_msg {0}; if(dtext.contains (" " + m_baseCall + " ") || dtext.contains ("<" + m_baseCall + " ") @@ -3776,13 +3763,13 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl || dtext.contains (" " + m_baseCall + "/") || (firstcall == "DE" /*&& ((t4.size () > 7 && t4.at(7) != "73") || t4.size () <= 7)*/)) { - if (t4.size () > 7 // enough fields for a normal msg - && (t4.at (5).contains (m_baseCall) || "DE" == t4.at (5)) - && t4.at (6).contains (qso_partner_base_call) - && !t4.at (7).contains (grid_regexp)) // but no grid on end of msg + if (message_words.size () > 3 // enough fields for a normal message + && (message_words.at (1).contains (m_baseCall) || "DE" == message_words.at (1)) + && message_words.at (2).contains (qso_partner_base_call) + && !message_words.at (3).contains (grid_regexp)) // but no grid on end of msg { - QString r=t4.at (7); - if(m_QSOProgress >= ROGER_REPORT && (r.mid(0,3)=="RRR" || r.toInt()==73 || "RR73" == r)) { + QString r=message_words.at (3); + if(m_QSOProgress >= ROGER_REPORT && (r=="RRR" || r.toInt()==73 || "RR73" == r)) { if(ui->tabWidget->currentIndex()==1) { gen_msg = 5; if (ui->rbGenMsg->isChecked ()) m_ntx=7; @@ -3819,7 +3806,7 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl } } else if (m_QSOProgress >= ROGERS - && t4.size () >= 7 && t4.at (5).contains (m_baseCall) && t4.at (6) == "73") { + && message_words.size () > 2 && message_words.at (1).contains (m_baseCall) && message_words.at (2) == "73") { // 73 back to compound call holder if(ui->tabWidget->currentIndex()==1) { gen_msg = 5; @@ -3833,7 +3820,7 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl m_QSOProgress = SIGNOFF; } else if (!(m_bAutoReply && m_QSOProgress > CALLING)) { - if ((t4.size () >= 9 && t4.at (5).contains (m_baseCall) && t4.at (8) == "OOO") + if ((message_words.size () > 4 && message_words.at (1).contains (m_baseCall) && message_words.at (4) == "OOO") || ((m_mode=="MSK144" or m_mode=="FT8") && m_config.contestMode())) { // EME short code report or MSK144/FT8 contest mode reply, send back Tx3 m_ntx = 3; @@ -3865,7 +3852,7 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl return; } } - else if (firstcall == "DE" && t4.size () >= 8 && t4.at (7) == "73") { + else if (firstcall == "DE" && message_words.size () > 3 && message_words.at (3) == "73") { if (m_QSOProgress >= ROGERS && base_call == qso_partner_base_call && m_currentMessageType) { // 73 back to compound call holder if(ui->tabWidget->currentIndex()==1) { @@ -3898,7 +3885,7 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl } } } - else if (acceptable_73) { + else if (is_73 && !message.isStandardMessage ()) { if(ui->tabWidget->currentIndex()==1) { gen_msg = 5; if (ui->rbGenMsg->isChecked ()) m_ntx=7; @@ -3931,14 +3918,18 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl // if we get here then we are reacting to the message if (m_bAutoReply) m_bCallingCQ = CALLING == m_QSOProgress; + + if (ui->RxFreqSpinBox->isEnabled () and m_mode != "MSK144") { + ui->RxFreqSpinBox->setValue (frequency); //Set Rx freq + } + QString s1 = m_QSOText.trimmed (); - QString s2=t2.trimmed(); - if (s1!=s2 and !decodedtext.isTX()) { - decodedtext = DecodedText {t2}; - ui->decodedTextBrowser2->displayDecodedText(decodedtext, m_baseCall, + QString s2 = message.string ().trimmed(); + if (s1!=s2 and !message.isTX()) { + ui->decodedTextBrowser2->displayDecodedText(message, m_baseCall, false, m_logBook,m_config.color_CQ(), m_config.color_MyCall(), m_config.color_DXCC(),m_config.color_NewCall()); - m_QSOText = decodedtext.string (); + m_QSOText = s1; } if (hiscall != "73" @@ -3960,7 +3951,7 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl lookup(); m_hisGrid = ui->dxGridEntry->text(); - QString rpt = decodedtext.report(); + QString rpt = message.report(); int n=rpt.toInt(); if(m_mode=="MSK144" and m_bShMsgs) { int n=rpt.toInt(); @@ -6118,7 +6109,9 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de // find the linefeed at the end of the line position = ui->decodedTextBrowser->toPlainText().indexOf(QChar::LineFeed,position); m_bDoubleClicked = true; - processMessage (messages, position); + auto start = messages.left (position).lastIndexOf (QChar::LineFeed) + 1; + DecodedText message {messages.mid (start, position - start)}; + processMessage (message); tx_watchdog (false); QApplication::alert (this); } diff --git a/mainwindow.h b/mainwindow.h index b3a87431c..d726c6d9e 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -304,7 +304,7 @@ private: private: void astroUpdate (); void writeAllTxt(QString message); - void auto_sequence (QString const& message, unsigned start_tolerance, unsigned stop_tolerance); + void auto_sequence (DecodedText const& message, unsigned start_tolerance, unsigned stop_tolerance); void hideMenus(bool b); NetworkAccessManager m_network_manager; @@ -591,7 +591,7 @@ private: void pskPost(DecodedText const& decodedtext); void displayDialFrequency (); void transmitDisplay (bool); - void processMessage(QString const& messages, qint32 position, bool ctrl = false, bool alt = false); + void processMessage(DecodedText const&, bool ctrl = false, bool alt = false); void replyToCQ (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode, QString const& message_text); void replayDecodes (); void postDecode (bool is_new, QString const& message); diff --git a/mainwindow.ui b/mainwindow.ui index 6ab1010bc..04948d102 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 876 - 583 + 815 + 548 @@ -1089,7 +1089,7 @@ QLabel[oob="true"] { - <html><head/><body><p>Send this message in next Tx interval</p><p>Double click to toggle the use of the Tx1 message to start a QSO with a station</p></body></html> + <html><head/><body><p>Send this message in next Tx interval</p><p>Double click to toggle the use of the Tx1 message to start a QSO with a station (not allowed for type 1 compound call holders)</p></body></html> @@ -1111,7 +1111,7 @@ QLabel[oob="true"] { - <html><head/><body><p>Switch to this Tx message NOW</p><p>Double click to toggle the use of the Tx1 message to start a QSO with a station</p></body></html> + <html><head/><body><p>Switch to this Tx message NOW</p><p>Double click to toggle the use of the Tx1 message to start a QSO with a station (not allowed for type 1 compund call holders)</p></body></html> Qt::LeftToRight @@ -1227,7 +1227,7 @@ QLabel[oob="true"] { - <html><head/><body><p>Send this message in next Tx interval</p><p>Double-click to toggle between RRR and RR73 messages in Tx4</p><p>RR73 messages should only be used when you are reasonably confident that no message repititions will be required</p></body></html> + <html><head/><body><p>Send this message in next Tx interval</p><p>Double-click to toggle between RRR and RR73 messages in Tx4 (not allowed for type 2 compound call holders)</p><p>RR73 messages should only be used when you are reasonably confident that no message repititions will be required</p></body></html> @@ -1249,7 +1249,7 @@ QLabel[oob="true"] { - <html><head/><body><p>Switch to this Tx message NOW</p><p>Double-click to toggle between RRR and RR73 messages in Tx4</p><p>RR73 messages should only be used when you are reasonably confident that no message repititions will be required</p></body></html> + <html><head/><body><p>Switch to this Tx message NOW</p><p>Double-click to toggle between RRR and RR73 messages in Tx4 (not allowed for type2 compound call holders)</p><p>RR73 messages should only be used when you are reasonably confident that no message repititions will be required</p></body></html> Tx &4 @@ -2335,7 +2335,7 @@ QPushButton[state="ok"] { 0 0 - 876 + 815 21 diff --git a/shortcuts.txt b/shortcuts.txt index f516ad400..6b505203d 100644 --- a/shortcuts.txt +++ b/shortcuts.txt @@ -11,10 +11,10 @@ F7 Display Message Averaging window F11 Move Rx frequency down 1 Hz Ctrl+F11 Move Rx and Tx frequencies down 1 Hz - Shift+F11 Move Tx frequency down 50 Hz (rounded) + Shift+F11 Move Tx frequency down 60 Hz F12 Move Rx frequency up 1 Hz Ctrl+F12 Move Rx and Tx frequencies up 1 Hz - Shift+F12 Move Tx frequency up 50 Hz (rounded) + Shift+F12 Move Tx frequency up 60 Hz Alt+1-6 Set now transmission to this number on Tab 1 Ctl+1-6 Set next transmission to this number on Tab 1 Alt+D Decode again at QSO frequency