1 //---------------------------------------------------------- MainWindow
2 #include "mainwindow.h"
3 
4 #include <cinttypes>
5 #include <cstring>
6 #include <cmath>
7 #include <limits>
8 #include <functional>
9 #include <fstream>
10 #include <iterator>
11 #include <algorithm>
12 #include <fftw3.h>
13 #include <QApplication>
14 #include <QStringListModel>
15 #include <QSettings>
16 #include <QKeyEvent>
17 #include <QProcessEnvironment>
18 #include <QSharedMemory>
19 #include <QFileDialog>
20 #include <QTextBlock>
21 #include <QProgressBar>
22 #include <QLineEdit>
23 #include <QRegExpValidator>
24 #include <QRegExp>
25 #include <QRegularExpression>
26 #include <QDesktopServices>
27 #include <QUrl>
28 #include <QStandardPaths>
29 #include <QDir>
30 #include <QDebug>
31 #include <QtConcurrent/QtConcurrentRun>
32 #include <QProgressDialog>
33 #include <QHostInfo>
34 #include <QVector>
35 #include <QCursor>
36 #include <QToolTip>
37 #include <QAction>
38 #include <QButtonGroup>
39 #include <QActionGroup>
40 #include <QSplashScreen>
41 #include <QUdpSocket>
42 #include <QAbstractItemView>
43 #include <QInputDialog>
44 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
45 #include <QRandomGenerator>
46 #endif
47 
48 // Z
49 #include <QSplitter>
50 #include <QNetworkAccessManager>
51 #include <QNetworkReply>
52 #include <QUrlQuery>
53 #include <QHash>
54 #include <QXmlStreamReader>
55 #include <QTableWidgetItem>
56 #include <QSqlQuery>
57 #include "unfilteredview.h"
58 #include "pskreporterwidget.h"
59 
60 #include "helper_functions.h"
61 #include "revision_utils.hpp"
62 #include "qt_helpers.hpp"
63 #include "Network/NetworkAccessManager.hpp"
64 #include "Audio/soundout.h"
65 #include "Audio/soundin.h"
66 #include "Modulator/Modulator.hpp"
67 #include "Detector/Detector.hpp"
68 #include "plotter.h"
69 #include "echoplot.h"
70 #include "echograph.h"
71 #include "fastplot.h"
72 #include "fastgraph.h"
73 #include "about.h"
74 #include "messageaveraging.h"
75 #include "colorhighlighting.h"
76 #include "widegraph.h"
77 #include "sleep.h"
78 #include "logqso.h"
79 #include "Decoder/decodedtext.h"
80 #include "Radio.hpp"
81 #include "models/Bands.hpp"
82 #include "Transceiver/TransceiverFactory.hpp"
83 #include "models/StationList.hpp"
84 #include "validators/LiveFrequencyValidator.hpp"
85 #include "Network/MessageClient.hpp"
86 #include "Network/wsprnet.h"
87 #include "signalmeter.h"
88 #include "HelpTextWindow.hpp"
89 #include "SampleDownloader.hpp"
90 #include "Audio/BWFFile.hpp"
91 #include "MultiSettings.hpp"
92 #include "validators/MaidenheadLocatorValidator.hpp"
93 #include "validators/CallsignValidator.hpp"
94 #include "EqualizationToolsDialog.hpp"
95 #include "Network/LotWUsers.hpp"
96 #include "logbook/AD1CCty.hpp"
97 #include "models/FoxLog.hpp"
98 #include "models/CabrilloLog.hpp"
99 #include "FoxLogWindow.hpp"
100 #include "CabrilloLogWindow.hpp"
101 #include "ExportCabrillo.h"
102 #include "ui_mainwindow.h"
103 #include "moc_mainwindow.cpp"
104 
105 extern "C" {
106   //----------------------------------------------------- C and Fortran routines
107   void symspec_(struct dec_data *, int* k, double* trperiod, int* nsps, int* ingain,
108                 bool* bLowSidelobes, int* minw, float* px, float s[], float* df3,
109                 int* nhsym, int* npts8, float *m_pxmax, int* npct);
110 
111   void hspec_(short int d2[], int* k, int* nutc0, int* ntrperiod, int* nrxfreq, int* ntol,
112               bool* bmsk144, bool* btrain, double const pcoeffs[], int* ingain,
113               char const * mycall, char const * hiscall, bool* bshmsg, bool* bswl,
114               char const * ddir, float green[],
115               float s[], int* jh, float *pxmax, float *rmsNoGain, char line[],
116               fortran_charlen_t, fortran_charlen_t, fortran_charlen_t, fortran_charlen_t);
117 
118   void genft8_(char* msg, int* i3, int* n3, char* msgsent, char ft8msgbits[],
119                int itone[], fortran_charlen_t, fortran_charlen_t);
120 
121   void genft4_(char* msg, int* ichk, char* msgsent, char ft4msgbits[], int itone[],
122                fortran_charlen_t, fortran_charlen_t);
123 
124   void genfst4_(char* msg, int* ichk, char* msgsent, char fst4msgbits[],
125                  int itone[], int* iwspr, fortran_charlen_t, fortran_charlen_t);
126 
127   void gen_ft8wave_(int itone[], int* nsym, int* nsps, float* bt, float* fsample, float* f0,
128                     float xjunk[], float wave[], int* icmplx, int* nwave);
129 
130   void gen_ft4wave_(int itone[], int* nsym, int* nsps, float* fsample, float* f0,
131                     float xjunk[], float wave[], int* icmplx, int* nwave);
132 
133   void gen_fst4wave_(int itone[], int* nsym, int* nsps, int* nwave, float* fsample,
134                        int* hmod, float* f0, int* icmplx, float xjunk[], float wave[]);
135 
136   void genwave_(int itone[], int* nsym, int* nsps, int* nwave, float* fsample,
137                        int* hmod, float* f0, int* icmplx, float xjunk[], float wave[]);
138 
139   void gen4_(char* msg, int* ichk, char* msgsent, int itone[],
140                int* itext, fortran_charlen_t, fortran_charlen_t);
141 
142   void gen9_(char* msg, int* ichk, char* msgsent, int itone[],
143                int* itext, fortran_charlen_t, fortran_charlen_t);
144 
145   void genmsk_128_90_(char* msg, int* ichk, char* msgsent, int itone[], int* itype,
146                       fortran_charlen_t, fortran_charlen_t);
147 
148   void gen65_(char* msg, int* ichk, char* msgsent, int itone[],
149               int* itext, fortran_charlen_t, fortran_charlen_t);
150 
151   void genq65_(char* msg, int* ichk, char* msgsent, int itone[],
152               int* i3, int* n3, fortran_charlen_t, fortran_charlen_t);
153 
154   void genwspr_(char* msg, char* msgsent, int itone[], fortran_charlen_t, fortran_charlen_t);
155 
156   void azdist_(char* MyGrid, char* HisGrid, double* utch, int* nAz, int* nEl,
157                int* nDmiles, int* nDkm, int* nHotAz, int* nHotABetter,
158                fortran_charlen_t, fortran_charlen_t);
159 
160   void morse_(char* msg, int* icw, int* ncw, fortran_charlen_t);
161 
162   int ptt_(int nport, int ntx, int* iptt, int* nopen);
163 
164   void wspr_downsample_(short int d2[], int* k);
165 
166   int savec2_(char const * fname, int* TR_seconds, double* dial_freq, fortran_charlen_t);
167 
168   void avecho_( short id2[], int* dop, int* nfrit, int* nqual, float* f1,
169                 float* level, float* sigdb, float* snr, float* dfreq,
170                 float* width);
171 
172   void fast_decode_(short id2[], int narg[], double * trperiod,
173                     char msg[], char mycall[], char hiscall[],
174                     fortran_charlen_t, fortran_charlen_t, fortran_charlen_t);
175   void degrade_snr_(short d2[], int* n, float* db, float* bandwidth);
176 
177   void wav12_(short d2[], short d1[], int* nbytes, short* nbitsam2);
178 
179   void refspectrum_(short int d2[], bool* bclearrefspec,
180                     bool* brefspec, bool* buseref, const char* c_fname, fortran_charlen_t);
181 
182   void freqcal_(short d2[], int* k, int* nkhz,int* noffset, int* ntol,
183                 char line[], fortran_charlen_t);
184 
185   void fix_contest_msg_(char* MyGrid, char* msg, fortran_charlen_t, fortran_charlen_t);
186 
187   void calibrate_(char const * data_dir, int* iz, double* a, double* b, double* rms,
188                   double* sigmaa, double* sigmab, int* irc, fortran_charlen_t);
189 
190   void foxgen_();
191 
192   void plotsave_(float swide[], int* m_w , int* m_h1, int* irow);
193 
194   void chkcall_(char* w, char* basc_call, bool cok, int len1, int len2);
195 
196   void get_ft4msg_(int* idecode, char* line, int len);
197 
198   void chk_samples_(int* m_ihsym,int* k, int* m_hsymStop);
199 }
200 
201 int volatile itone[MAX_NUM_SYMBOLS];   //Audio tones for all Tx symbols
202 int volatile itone0[MAX_NUM_SYMBOLS];  //Dummy array, data not actually used
203 int volatile icw[NUM_CW_SYMBOLS];        //Dits for CW ID
204 dec_data_t dec_data;                // for sharing with Fortran
205 
206 int outBufSize;
207 int rc;
208 qint32  g_iptt {0};
209 wchar_t buffer[256];
210 float fast_green[703];
211 float fast_green2[703];
212 float fast_s[44992];                                    //44992=64*703
213 float fast_s2[44992];
214 int   fast_jh {0};
215 int   fast_jhpeak {0};
216 int   fast_jh2 {0};
217 int narg[15];
218 QVector<QColor> g_ColorTbl;
219 
220 using SpecOp = Configuration::SpecialOperatingActivity;
221 
222 namespace
223 {
224   Radio::Frequency constexpr default_frequency {14076000};
225   QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>;]*"};
226   // grid exact match excluding RR73
227   QRegularExpression grid_regexp {"\\A(?![Rr]{2}73)[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}){0,1}\\z"};
228   auto quint32_max = std::numeric_limits<quint32>::max ();
229   constexpr int N_WIDGETS {38};
230   constexpr int default_rx_audio_buffer_frames {-1}; // lets Qt decide
231   constexpr int default_tx_audio_buffer_frames {-1}; // lets Qt decide
232 
message_is_73(int type,QStringList const & msg_parts)233   bool message_is_73 (int type, QStringList const& msg_parts)
234   {
235     return type >= 0
236       && (((type < 6 || 7 == type)
237            && (msg_parts.contains ("73") || msg_parts.contains ("RR73")))
238           || (type == 6 && !msg_parts.filter ("73").isEmpty ()));
239   }
240 
ms_minute_error()241   int ms_minute_error ()
242   {
243     auto const& now = QDateTime::currentDateTimeUtc ();
244     auto const& time = now.time ();
245     auto second = time.second ();
246     return now.msecsTo (now.addSecs (second > 30 ? 60 - second : -second)) - time.msec ();
247   }
248 }
249 
250 //--------------------------------------------------- MainWindow constructor
MainWindow(QDir const & temp_directory,bool multiple,MultiSettings * multi_settings,QSharedMemory * shdmem,unsigned downSampleFactor,QSplashScreen * splash,QProcessEnvironment const & env,QWidget * parent)251 MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
252                        MultiSettings * multi_settings, QSharedMemory *shdmem,
253                        unsigned downSampleFactor,
254                        QSplashScreen * splash, QProcessEnvironment const& env, QWidget *parent) :
255   MultiGeometryWidget {parent},
256   m_env {env},
257   m_network_manager {this},
258   m_valid {true},
259   m_splash {splash},
260   m_revision {revision ()},
261   m_multiple {multiple},
262   m_multi_settings {multi_settings},
263   m_configurations_button {0},
264   m_settings {multi_settings->settings ()},
265   ui(new Ui::MainWindow),
266   m_config {&m_network_manager, temp_directory, m_settings, &m_logBook, this},
267   m_logBook {&m_config},
268   m_WSPR_band_hopping {m_settings, &m_config, this},
269   m_WSPR_tx_next {false},
270   m_rigErrorMessageBox {MessageBox::Critical, tr ("Rig Control Error")
271       , MessageBox::Cancel | MessageBox::Ok | MessageBox::Retry},
272   m_wideGraph (new WideGraph(m_settings)),
273   m_echoGraph (new EchoGraph(m_settings)),
274   m_fastGraph (new FastGraph(m_settings)),
275   // no parent so that it has a taskbar icon
276   m_logDlg (new LogQSO (program_title (), m_settings, &m_config, &m_logBook, nullptr)),
277   m_lastDialFreq {0},
278   m_dialFreqRxWSPR {0},
279   m_detector {new Detector {RX_SAMPLE_RATE, double(NTMAX), downSampleFactor}},
280   m_FFTSize {6192 / 2},         // conservative value to avoid buffer overruns
281   m_soundInput {new SoundInput},
282   m_modulator {new Modulator {TX_SAMPLE_RATE, NTMAX}},
283   m_soundOutput {new SoundOutput},
284   m_rx_audio_buffer_frames {0},
285   m_tx_audio_buffer_frames {0},
286   m_msErase {0},
287   m_secBandChanged {0},
288   m_freqNominal {0},
289   m_freqTxNominal {0},
290   m_reverse_Doppler {"1" == env.value ("WSJT_REVERSE_DOPPLER", "0")},
291   m_tRemaining {0.},
292   m_TRperiod {60.0},
293   m_DTtol {3.0},
294   m_waterfallAvg {1},
295   m_ntx {1},
296   m_gen_message_is_cq {false},
297   m_send_RR73 {false},
298   m_XIT {0},
299   m_sec0 {-1},
300   m_RxLog {1},      //Write Date and Time to RxLog
301   m_nutc0 {999999},
302   m_ntr {0},
303   m_tx {0},
304   m_inGain {0},
305   m_secID {0},
306   m_idleMinutes {0},
307   m_nSubMode {0},
308   m_nclearave {1},
309   m_nWSPRdecodes {0},
310   m_k0 {9999999},
311   m_nPick {0},
312   m_frequency_list_fcal_iter {m_config.frequencies ()->begin ()},
313   m_nTx73 {0},
314   m_btxok {false},
315   m_diskData {false},
316   m_loopall {false},
317   m_txFirst {false},
318   m_auto {false},
319   m_restart {false},
320   m_startAnother {false},
321   m_saveDecoded {false},
322   m_saveAll {false},
323   m_widebandDecode {false},
324   m_dataAvailable {false},
325   m_decodedText2 {false},
326   m_freeText {false},
327   m_sentFirst73 {false},
328   m_currentMessageType {-1},
329   m_lastMessageType {-1},
330   m_bShMsgs {false},
331   m_bSWL {false},
332   m_uploading {false},
333   m_grid6 {false},
334   m_tuneup {false},
335   m_bTxTime {false},
336   m_rxDone {true},
337   m_bSimplex {false},
338   m_bEchoTxOK {false},
339   m_bTransmittedEcho {false},
340   m_bEchoTxed {false},
341   m_bFastDecodeCalled {false},
342   m_bDoubleClickAfterCQnnn {false},
343   m_bRefSpec {false},
344   m_bClearRefSpec {false},
345   m_bTrain {false},
346   m_bAutoReply {false},
347   m_QSOProgress {CALLING},
348   m_ihsym {0},
349   m_nzap {0},
350   m_px {0.0},
351   m_iptt0 {0},
352   m_btxok0 {false},
353   m_nsendingsh {0},
354   m_onAirFreq0 {0.0},
355   m_first_error {true},
356   tx_status_label {tr ("Receiving")},
357   wsprNet {new WSPRNet {&m_network_manager, this}},
358   m_baseCall {Radio::base_callsign (m_config.my_callsign ())},
359   m_appDir {QApplication::applicationDirPath ()},
360   m_cqStr {""},
361   m_palette {"Linrad"},
362   m_mode {"JT9"},
363   m_rpt {"-15"},
364   m_pfx {
365     "1A", "1S",
366       "3A", "3B6", "3B8", "3B9", "3C", "3C0", "3D2", "3D2C",
367       "3D2R", "3DA", "3V", "3W", "3X", "3Y", "3YB", "3YP",
368       "4J", "4L", "4S", "4U1I", "4U1U", "4W", "4X",
369       "5A", "5B", "5H", "5N", "5R", "5T", "5U", "5V", "5W", "5X", "5Z",
370       "6W", "6Y",
371       "7O", "7P", "7Q", "7X",
372       "8P", "8Q", "8R",
373       "9A", "9G", "9H", "9J", "9K", "9L", "9M2", "9M6", "9N",
374       "9Q", "9U", "9V", "9X", "9Y",
375       "A2", "A3", "A4", "A5", "A6", "A7", "A9", "AP",
376       "BS7", "BV", "BV9", "BY",
377       "C2", "C3", "C5", "C6", "C9", "CE", "CE0X", "CE0Y",
378       "CE0Z", "CE9", "CM", "CN", "CP", "CT", "CT3", "CU",
379       "CX", "CY0", "CY9",
380       "D2", "D4", "D6", "DL", "DU",
381       "E3", "E4", "E5", "EA", "EA6", "EA8", "EA9", "EI", "EK",
382       "EL", "EP", "ER", "ES", "ET", "EU", "EX", "EY", "EZ",
383       "F", "FG", "FH", "FJ", "FK", "FKC", "FM", "FO", "FOA",
384       "FOC", "FOM", "FP", "FR", "FRG", "FRJ", "FRT", "FT5W",
385       "FT5X", "FT5Z", "FW", "FY",
386       "M", "MD", "MI", "MJ", "MM", "MU", "MW",
387       "H4", "H40", "HA", "HB", "HB0", "HC", "HC8", "HH",
388       "HI", "HK", "HK0", "HK0M", "HL", "HM", "HP", "HR",
389       "HS", "HV", "HZ",
390       "I", "IS", "IS0",
391       "J2", "J3", "J5", "J6", "J7", "J8", "JA", "JDM",
392       "JDO", "JT", "JW", "JX", "JY",
393       "K", "KC4", "KG4", "KH0", "KH1", "KH2", "KH3", "KH4", "KH5",
394       "KH5K", "KH6", "KH7", "KH8", "KH9", "KL", "KP1", "KP2",
395       "KP4", "KP5",
396       "LA", "LU", "LX", "LY", "LZ",
397       "OA", "OD", "OE", "OH", "OH0", "OJ0", "OK", "OM", "ON",
398       "OX", "OY", "OZ",
399       "P2", "P4", "PA", "PJ2", "PJ7", "PY", "PY0F", "PT0S", "PY0T", "PZ",
400       "R1F", "R1M",
401       "S0", "S2", "S5", "S7", "S9", "SM", "SP", "ST", "SU",
402       "SV", "SVA", "SV5", "SV9",
403       "T2", "T30", "T31", "T32", "T33", "T5", "T7", "T8", "T9", "TA",
404       "TF", "TG", "TI", "TI9", "TJ", "TK", "TL", "TN", "TR", "TT",
405       "TU", "TY", "TZ",
406       "UA", "UA2", "UA9", "UK", "UN", "UR",
407       "V2", "V3", "V4", "V5", "V6", "V7", "V8", "VE", "VK", "VK0H",
408       "VK0M", "VK9C", "VK9L", "VK9M", "VK9N", "VK9W", "VK9X", "VP2E",
409       "VP2M", "VP2V", "VP5", "VP6", "VP6D", "VP8", "VP8G", "VP8H",
410       "VP8O", "VP8S", "VP9", "VQ9", "VR", "VU", "VU4", "VU7",
411       "XE", "XF4", "XT", "XU", "XW", "XX9", "XZ",
412       "YA", "YB", "YI", "YJ", "YK", "YL", "YN", "YO", "YS", "YU", "YV", "YV0",
413       "Z2", "Z3", "ZA", "ZB", "ZC4", "ZD7", "ZD8", "ZD9", "ZF", "ZK1N",
414       "ZK1S", "ZK2", "ZK3", "ZL", "ZL7", "ZL8", "ZL9", "ZP", "ZS", "ZS8"
415       },
416   m_sfx {"P",  "0",  "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9",  "A"},
417   mem_jt9 {shdmem},
418   m_downSampleFactor (downSampleFactor),
419   m_audioThreadPriority (QThread::HighPriority),
420   m_bandEdited {false},
421   m_splitMode {false},
422   m_monitoring {false},
423   m_tx_when_ready {false},
424   m_transmitting {false},
425   m_tune {false},
426   m_tx_watchdog {false},
427   m_block_pwr_tooltip {false},
428   m_PwrBandSetOK {true},
429   m_lastMonitoredFrequency {default_frequency},
430   m_toneSpacing {0.},
431   m_firstDecode {0},
432   m_optimizingProgress {"Optimizing decoder FFTs for your CPU.\n"
433       "Please be patient,\n"
434       "this may take a few minutes", QString {}, 0, 1, this},
435   m_messageClient {new MessageClient {QApplication::applicationName (),
436         version (), revision (),
437         m_config.udp_server_name (), m_config.udp_server_port (),
438         m_config.udp_interface_names (), m_config.udp_TTL (),
439         this}},
440   m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()},
441   m_manual {&m_network_manager},
442   m_block_udp_status_updates {false}
443 {
444   ui->setupUi(this);
445   setUnifiedTitleAndToolBarOnMac (true);
446   createStatusBar();
447   add_child_to_event_filter (this);
448   ui->dxGridEntry->setValidator (new MaidenheadLocatorValidator {this});
449   ui->dxCallEntry->setValidator (new CallsignValidator {this});
450   ui->sbTR->values ({5, 10, 15, 30, 60, 120, 300, 900, 1800});
451   ui->sbTR_FST4W->values ({120, 300, 900, 1800});
452   ui->decodedTextBrowser->set_configuration (&m_config, true);
453   ui->decodedTextBrowser2->set_configuration (&m_config);
454   // Z
455      ui->decodedTextBrowser2->addAction(ui->actionIgnore_station);
456      ui->decodedTextBrowser2->addAction(ui->actionCall_next);
457      ui->decodedTextBrowser2->addAction(ui->actionClear);
458      ui->decodedTextBrowser2->addAction(ui->actionSet_Rx_Freq);
459      ui->decodedTextBrowser2->addAction(ui->actionQRZ_Lookup);
460      ui->decodedTextBrowser2->addAction(ui->actionCopy);
461      ui->decodedTextBrowser->addAction(ui->actionIgnore_station);
462      ui->decodedTextBrowser->addAction(ui->actionCall_next);
463      ui->decodedTextBrowser->addAction(ui->actionClear);
464      ui->decodedTextBrowser->addAction(ui->actionSet_Rx_Freq);
465      ui->decodedTextBrowser->addAction(ui->actionQRZ_Lookup);
466      ui->decodedTextBrowser->addAction(ui->actionCopy);
467 
468   m_optimizingProgress.setWindowModality (Qt::WindowModal);
469   m_optimizingProgress.setAutoReset (false);
470   m_optimizingProgress.setMinimumDuration (15000); // only show after 15s delay
471 
472   // Closedown.
473   connect (ui->actionExit, &QAction::triggered, this, &QMainWindow::close);
474 
475   // parts of the rig error message box that are fixed
476   m_rigErrorMessageBox.setInformativeText (tr ("Do you want to reconfigure the radio interface?"));
477   m_rigErrorMessageBox.setDefaultButton (MessageBox::Ok);
478 
479   // start audio thread and hook up slots & signals for shutdown management
480   // these objects need to be in the audio thread so that invoking
481   // their slots is done in a thread safe way
482   m_soundOutput->moveToThread (&m_audioThread);
483   m_modulator->moveToThread (&m_audioThread);
484   m_soundInput->moveToThread (&m_audioThread);
485   m_detector->moveToThread (&m_audioThread);
486   bool ok;
487   auto buffer_size = env.value ("WSJT_RX_AUDIO_BUFFER_FRAMES", "0").toInt (&ok);
488   m_rx_audio_buffer_frames = ok && buffer_size ? buffer_size : default_rx_audio_buffer_frames;
489   buffer_size = env.value ("WSJT_TX_AUDIO_BUFFER_FRAMES", "0").toInt (&ok);
490   m_tx_audio_buffer_frames = ok && buffer_size ? buffer_size : default_tx_audio_buffer_frames;
491 
492   // hook up sound output stream slots & signals and disposal
493   connect (this, &MainWindow::initializeAudioOutputStream, m_soundOutput, &SoundOutput::setFormat);
494   connect (m_soundOutput, &SoundOutput::error, this, &MainWindow::showSoundOutError);
495   connect (m_soundOutput, &SoundOutput::error, &m_config, &Configuration::invalidate_audio_output_device);
496   // connect (m_soundOutput, &SoundOutput::status, this, &MainWindow::showStatusMessage);
497   connect (this, &MainWindow::outAttenuationChanged, m_soundOutput, &SoundOutput::setAttenuation);
498   connect (&m_audioThread, &QThread::finished, m_soundOutput, &QObject::deleteLater);
499 
500   // hook up Modulator slots and disposal
501   connect (this, &MainWindow::transmitFrequency, m_modulator, &Modulator::setFrequency);
502   connect (this, &MainWindow::endTransmitMessage, m_modulator, &Modulator::stop);
503   connect (this, &MainWindow::tune, m_modulator, &Modulator::tune);
504   connect (this, &MainWindow::sendMessage, m_modulator, &Modulator::start);
505   connect (&m_audioThread, &QThread::finished, m_modulator, &QObject::deleteLater);
506 
507   // hook up the audio input stream signals, slots and disposal
508   connect (this, &MainWindow::startAudioInputStream, m_soundInput, &SoundInput::start);
509   connect (this, &MainWindow::suspendAudioInputStream, m_soundInput, &SoundInput::suspend);
510   connect (this, &MainWindow::resumeAudioInputStream, m_soundInput, &SoundInput::resume);
511   connect (this, &MainWindow::reset_audio_input_stream, m_soundInput, &SoundInput::reset);
512   connect (this, &MainWindow::finished, m_soundInput, &SoundInput::stop);
513   connect(m_soundInput, &SoundInput::error, this, &MainWindow::showSoundInError);
514   connect(m_soundInput, &SoundInput::error, &m_config, &Configuration::invalidate_audio_input_device);
515   // connect(m_soundInput, &SoundInput::status, this, &MainWindow::showStatusMessage);
516   connect (&m_audioThread, &QThread::finished, m_soundInput, &QObject::deleteLater);
517 
518   connect (this, &MainWindow::finished, this, &MainWindow::close);
519 
520   // hook up the detector signals, slots and disposal
521   connect (this, &MainWindow::FFTSize, m_detector, &Detector::setBlockSize);
522   connect(m_detector, &Detector::framesWritten, this, &MainWindow::dataSink);
523   connect (&m_audioThread, &QThread::finished, m_detector, &QObject::deleteLater);
524 
525   // setup the waterfall
526   connect(m_wideGraph.data (), SIGNAL(freezeDecode2(int)),this,SLOT(freezeDecode(int)));
527   connect(m_wideGraph.data (), SIGNAL(f11f12(int)),this,SLOT(bumpFqso(int)));
528   connect(m_wideGraph.data (), SIGNAL(setXIT2(int)),this,SLOT(setXIT(int)));
529 
530   connect (m_fastGraph.data (), &FastGraph::fastPick, this, &MainWindow::fastPick);
531 
532   connect (this, &MainWindow::finished, m_wideGraph.data (), &WideGraph::close);
533   connect (this, &MainWindow::finished, m_echoGraph.data (), &EchoGraph::close);
534   connect (this, &MainWindow::finished, m_fastGraph.data (), &FastGraph::close);
535 
536   // setup the log QSO dialog
537   connect (m_logDlg.data (), &LogQSO::acceptQSO, this, &MainWindow::acceptQSO);
538   connect (this, &MainWindow::finished, m_logDlg.data (), &LogQSO::close);
539 
540   // hook up the log book
__anon4158608c0202(int record_count, QString const& error) 541   connect (&m_logBook, &LogBook::finished_loading, [this] (int record_count, QString const& error) {
542       if (error.size ())
543         {
544           MessageBox::warning_message (this, tr ("Error Scanning ADIF Log"), error);
545         }
546       else
547         {
548           showStatusMessage (tr ("Scanned ADIF log, %1 worked before records created").arg (record_count));
549           qso_total = record_count;
550           updateQsoCounter(false);
551         }
552     });
553 
554   // Network message handlers
555   m_messageClient->enable (m_config.accept_udp_requests ());
__anon4158608c0302(quint8 window) 556   connect (m_messageClient, &MessageClient::clear_decodes, [this] (quint8 window) {
557       ++window;
558       if (window & 1)
559         {
560           ui->decodedTextBrowser->erase ();
561         }
562       if (window & 2)
563         {
564           ui->decodedTextBrowser2->erase ();
565         }
566     });
567   connect (m_messageClient, &MessageClient::reply, this, &MainWindow::replyToCQ);
568   connect (m_messageClient, &MessageClient::close, this, &MainWindow::close);
569   connect (m_messageClient, &MessageClient::replay, this, &MainWindow::replayDecodes);
570   connect (m_messageClient, &MessageClient::location, this, &MainWindow::locationChange);
__anon4158608c0402(bool auto_only) 571   connect (m_messageClient, &MessageClient::halt_tx, [this] (bool auto_only) {
572       if (auto_only) {
573         if (ui->autoButton->isChecked ()) {
574           ui->autoButton->click();
575         }
576       } else {
577         ui->stopTxButton->click();
578       }
579     });
580   connect (m_messageClient, &MessageClient::error, this, &MainWindow::networkError);
__anon4158608c0502(QString const& text, bool send) 581   connect (m_messageClient, &MessageClient::free_text, [this] (QString const& text, bool send) {
582       tx_watchdog (false);
583       // send + non-empty text means set and send the free text
584       // message, !send + non-empty text means set the current free
585       // text message, send + empty text means send the current free
586       // text message without change, !send + empty text means clear
587       // the current free text message
588       if (0 == ui->tabWidget->currentIndex ()) {
589         if (!text.isEmpty ()) {
590           ui->tx5->setCurrentText (text);
591         }
592         if (send) {
593           ui->txb5->click ();
594         } else if (text.isEmpty ()) {
595           ui->tx5->setCurrentText (text);
596         }
597       }
598       QApplication::alert (this);
599     });
600 
601   connect (m_messageClient, &MessageClient::highlight_callsign, ui->decodedTextBrowser, &DisplayText::highlight_callsign);
602   connect (m_messageClient, &MessageClient::switch_configuration, m_multi_settings, &MultiSettings::select_configuration);
603   connect (m_messageClient, &MessageClient::configure, this, &MainWindow::remote_configure);
604 
605   // Hook up WSPR band hopping
606   connect (ui->band_hopping_schedule_push_button, &QPushButton::clicked
607            , &m_WSPR_band_hopping, &WSPRBandHopping::show_dialog);
608   connect (ui->sbTxPercent, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged)
609            , &m_WSPR_band_hopping, &WSPRBandHopping::set_tx_percent);
610 
611   on_EraseButton_clicked ();
612 
613   QActionGroup* modeGroup = new QActionGroup(this);
614   ui->actionFST4->setActionGroup(modeGroup);
615   ui->actionFST4W->setActionGroup(modeGroup);
616   ui->actionFT4->setActionGroup(modeGroup);
617   ui->actionFT8->setActionGroup(modeGroup);
618   ui->actionJT9->setActionGroup(modeGroup);
619   ui->actionJT65->setActionGroup(modeGroup);
620   ui->actionJT4->setActionGroup(modeGroup);
621   ui->actionWSPR->setActionGroup(modeGroup);
622   ui->actionEcho->setActionGroup(modeGroup);
623   ui->actionMSK144->setActionGroup(modeGroup);
624   ui->actionQ65->setActionGroup(modeGroup);
625   ui->actionFreqCal->setActionGroup(modeGroup);
626 
627   QActionGroup* saveGroup = new QActionGroup(this);
628   ui->actionNone->setActionGroup(saveGroup);
629   ui->actionSave_decoded->setActionGroup(saveGroup);
630   ui->actionSave_all->setActionGroup(saveGroup);
631 
632   QActionGroup* DepthGroup = new QActionGroup(this);
633   ui->actionQuickDecode->setActionGroup(DepthGroup);
634   ui->actionMediumDecode->setActionGroup(DepthGroup);
635   ui->actionDeepestDecode->setActionGroup(DepthGroup);
636 
__anon4158608c0602() 637   connect (ui->download_samples_action, &QAction::triggered, [this] () {
638       if (!m_sampleDownloader)
639         {
640           m_sampleDownloader.reset (new SampleDownloader {m_settings, &m_config, &m_network_manager, this});
641         }
642       m_sampleDownloader->show ();
643     });
644 
__anon4158608c0702() 645   connect (ui->view_phase_response_action, &QAction::triggered, [this] () {
646       if (!m_equalizationToolsDialog)
647         {
648           m_equalizationToolsDialog.reset (new EqualizationToolsDialog {m_settings, m_config.writeable_data_dir (), m_phaseEqCoefficients, this});
649           connect (m_equalizationToolsDialog.data (), &EqualizationToolsDialog::phase_equalization_changed,
650                    [this] (QVector<double> const& coeffs) {
651                      m_phaseEqCoefficients = coeffs;
652                    });
653         }
654       m_equalizationToolsDialog->show ();
655     });
656 
__anon4158608c0902(QString const& reason) 657   connect (&m_config.lotw_users (), &LotWUsers::LotW_users_error, this, [this] (QString const& reason) {
658       MessageBox::warning_message (this, tr ("Error Loading LotW Users Data"), reason);
659     }, Qt::QueuedConnection);
660 
661   QButtonGroup* txMsgButtonGroup = new QButtonGroup {this};
662   txMsgButtonGroup->addButton(ui->txrb1,1);
663   txMsgButtonGroup->addButton(ui->txrb2,2);
664   txMsgButtonGroup->addButton(ui->txrb3,3);
665   txMsgButtonGroup->addButton(ui->txrb4,4);
666   txMsgButtonGroup->addButton(ui->txrb5,5);
667   txMsgButtonGroup->addButton(ui->txrb6,6);
668   set_dateTimeQSO(-1);
669   connect(txMsgButtonGroup,SIGNAL(buttonClicked(int)),SLOT(set_ntx(int)));
670   connect (ui->decodedTextBrowser, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnCall2);
671   connect (ui->decodedTextBrowser2, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnCall);
672   connect (ui->textBrowser4, &DisplayText::selectCallsign, this, &MainWindow::doubleClickOnFoxQueue);
673   connect (ui->decodedTextBrowser, &DisplayText::erased, this, &MainWindow::band_activity_cleared);
674   connect (ui->decodedTextBrowser2, &DisplayText::erased, this, &MainWindow::rx_frequency_activity_cleared);
675   // Z
676   connect (ui->decodedTextBrowser2, &DisplayText::leftClick, this, &MainWindow::leftClickHandler);
677   connect (ui->decodedTextBrowser, &DisplayText::leftClick, this, &MainWindow::leftClickHandler);
678 
679   // initialize decoded text font and hook up font change signals
680   // defer initialization until after construction otherwise menu
681   // fonts do not get set
682   QTimer::singleShot (0, this, SLOT (initialize_fonts ()));
__anon4158608c0a02(QFont const& font) 683   connect (&m_config, &Configuration::text_font_changed, [this] (QFont const& font) {
684       set_application_font (font);
685     });
__anon4158608c0b02(QFont const& font) 686   connect (&m_config, &Configuration::decoded_text_font_changed, [this] (QFont const& font) {
687       setDecodedTextFont (font);
688       // Z
689       if (m_unfilteredView) m_unfilteredView->setFont(font);
690       if (m_pskReporterView) m_pskReporterView->setFont(font);
691     });
692 
693   setWindowTitle (program_title () +  " (WSJT-Z MOD v1.10 by SQ9FVE)") ;
694 
695 
696   connect(&proc_jt9, &QProcess::readyReadStandardOutput, this, &MainWindow::readFromStdout);
697 #if QT_VERSION < QT_VERSION_CHECK (5, 6, 0)
698   connect(&proc_jt9, static_cast<void (QProcess::*) (QProcess::ProcessError)> (&QProcess::error),
__anon4158608c0c02(QProcess::ProcessError error) 699           [this] (QProcess::ProcessError error) {
700             subProcessError (&proc_jt9, error);
701           });
702 #else
__anon4158608c0d02(QProcess::ProcessError error) 703   connect(&proc_jt9, &QProcess::errorOccurred, [this] (QProcess::ProcessError error) {
704                                                  subProcessError (&proc_jt9, error);
705                                                });
706 #endif
707   connect(&proc_jt9, static_cast<void (QProcess::*) (int, QProcess::ExitStatus)> (&QProcess::finished),
__anon4158608c0e02(int exitCode, QProcess::ExitStatus status) 708           [this] (int exitCode, QProcess::ExitStatus status) {
709             if (subProcessFailed (&proc_jt9, exitCode, status))
710               {
711                 m_valid = false;          // ensures exit if still
712                                           // constructing
713                 QTimer::singleShot (0, this, SLOT (close ()));
714               }
715           });
__anon4158608c0f02() 716   connect(&p1, &QProcess::started, [this] () {
717                                      showStatusMessage (QString {"Started: %1 \"%2\""}.arg (p1.program ()).arg (p1.arguments ().join ("\" \"")));
718                                    });
719   connect(&p1, &QProcess::readyReadStandardOutput, this, &MainWindow::p1ReadFromStdout);
720 #if QT_VERSION < QT_VERSION_CHECK (5, 6, 0)
721   connect(&p1, static_cast<void (QProcess::*) (QProcess::ProcessError)> (&QProcess::error),
__anon4158608c1002(QProcess::ProcessError error) 722           [this] (QProcess::ProcessError error) {
723             subProcessError (&p1, error);
724           });
725 #else
__anon4158608c1102(QProcess::ProcessError error) 726   connect(&p1, &QProcess::errorOccurred, [this] (QProcess::ProcessError error) {
727                                            subProcessError (&p1, error);
728                                          });
729 #endif
730   connect(&p1, static_cast<void (QProcess::*) (int, QProcess::ExitStatus)> (&QProcess::finished),
__anon4158608c1202(int exitCode, QProcess::ExitStatus status) 731           [this] (int exitCode, QProcess::ExitStatus status) {
732             if (subProcessFailed (&p1, exitCode, status))
733               {
734                 m_valid = false;          // ensures exit if still
735                                           // constructing
736                 QTimer::singleShot (0, this, SLOT (close ()));
737               }
738           });
739 
740 #if QT_VERSION < QT_VERSION_CHECK (5, 6, 0)
741   connect(&p3, static_cast<void (QProcess::*) (QProcess::ProcessError)> (&QProcess::error),
__anon4158608c1302(QProcess::ProcessError error) 742           [this] (QProcess::ProcessError error) {
743 #else
744   connect(&p3, &QProcess::errorOccurred, [this] (QProcess::ProcessError error) {
745 #endif
746 #if !defined(Q_OS_WIN)
747                                            if (QProcess::FailedToStart != error)
748 #else
749                                            if (QProcess::Crashed != error)
750 #endif
751                                              {
752                                                subProcessError (&p3, error);
753                                              }
754                                          });
755   connect(&p3, &QProcess::started, [this] () {
756                                      showStatusMessage (QString {"Started: %1 \"%2\""}.arg (p3.program ()).arg (p3.arguments ().join ("\" \"")));
757                                    });
758   connect(&p3, static_cast<void (QProcess::*) (int, QProcess::ExitStatus)> (&QProcess::finished),
759           [this] (int exitCode, QProcess::ExitStatus status) {
760 #if defined(Q_OS_WIN)
761             // We forgo detecting user_hardware failures with exit
762             // code 1 on Windows. This is because we use CMD.EXE to
763             // run the executable. CMD.EXE returns exit code 1 when it
764             // can't find the target executable.
765             if (exitCode != 1)  // CMD.EXE couldn't find file to execute
766 #else
767             // We forgo detecting user_hardware failures with exit
768             // code 127 non-Windows. This is because we use /bin/sh to
769             // run the executable. /bin/sh returns exit code 127 when it
770             // can't find the target executable.
771             if (exitCode != 127)  // /bin/sh couldn't find file to execute
772 #endif
773               {
774                 subProcessFailed (&p3, exitCode, status);
775               }
776           });
777 
778   // hook up save WAV file exit handling
779   connect (&m_saveWAVWatcher, &QFutureWatcher<QString>::finished, [this] {
780       // extract the promise from the future
781       auto const& result = m_saveWAVWatcher.future ().result ();
782       if (!result.isEmpty ())   // error
783         {
784           MessageBox::critical_message (this, tr("Error Writing WAV File"), result);
785         }
786     });
787 
788   // Hook up working frequencies.
789   ui->bandComboBox->setModel (m_config.frequencies ());
790   ui->bandComboBox->setModelColumn (FrequencyList_v2::frequency_mhz_column);
791 
792   // Enable live band combo box entry validation and action.
793   auto band_validator = new LiveFrequencyValidator {ui->bandComboBox
794                                                     , m_config.bands ()
795                                                     , m_config.frequencies ()
796                                                     , &m_freqNominal
797                                                     , this};
798   ui->bandComboBox->setValidator (band_validator);
799 
800   // Hook up signals.
801   connect (band_validator, &LiveFrequencyValidator::valid, this, &MainWindow::band_changed);
802   connect (ui->bandComboBox->lineEdit (), &QLineEdit::textEdited, [this] (QString const&) {m_bandEdited = true;});
803 
804   // hook up configuration signals
805   connect (&m_config, &Configuration::transceiver_update, this, &MainWindow::handle_transceiver_update);
806   connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure);
807   connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server);
808   connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port);
809   connect (&m_config, &Configuration::udp_TTL_changed, m_messageClient, &MessageClient::set_TTL);
810   connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable);
811   connect (&m_config, &Configuration::enumerating_audio_devices, [this] () {
812                                                                    showStatusMessage (tr ("Enumerating audio devices"));
813                                                                  });
814 
815   // set up configurations menu
816   connect (m_multi_settings, &MultiSettings::configurationNameChanged, [this] (QString const& name) {
817       if ("Default" != name) {
818         config_label.setText (name);
819         config_label.show ();
820       }
821       else {
822         config_label.hide ();
823       }
824       statusUpdate ();
825     });
826   m_multi_settings->create_menu_actions (this, ui->menuConfig);
827   m_configurations_button = m_rigErrorMessageBox.addButton (tr ("Configurations...")
828                                                             , QMessageBox::ActionRole);
829 
830   // set up message text validators
831   ui->tx1->setValidator (new QRegExpValidator {message_alphabet, this});
832   ui->tx2->setValidator (new QRegExpValidator {message_alphabet, this});
833   ui->tx3->setValidator (new QRegExpValidator {message_alphabet, this});
834   ui->tx4->setValidator (new QRegExpValidator {message_alphabet, this});
835   ui->tx5->setValidator (new QRegExpValidator {message_alphabet, this});
836   ui->tx6->setValidator (new QRegExpValidator {message_alphabet, this});
837 
838   // Free text macros model to widget hook up.
839   ui->tx5->setModel (m_config.macros ());
840   connect (ui->tx5->lineEdit(), &QLineEdit::editingFinished,
841            [this] () {on_tx5_currentTextChanged (ui->tx5->lineEdit()->text());});
842   connect(&m_guiTimer, &QTimer::timeout, this, &MainWindow::guiUpdate);
843   m_guiTimer.start(100);   //### Don't change the 100 ms! ###
844 
845   ptt0Timer.setSingleShot(true);
846   connect(&ptt0Timer, &QTimer::timeout, this, &MainWindow::stopTx2);
847 
848   ptt1Timer.setSingleShot(true);
849   connect(&ptt1Timer, &QTimer::timeout, this, &MainWindow::startTx2);
850 
851   p1Timer.setSingleShot(true);
852   connect(&p1Timer, &QTimer::timeout, this, &MainWindow::startP1);
853 
854   logQSOTimer.setSingleShot(true);
855   connect(&logQSOTimer, &QTimer::timeout, this, &MainWindow::on_logQSOButton_clicked);
856 
857   tuneButtonTimer.setSingleShot(true);
858   connect(&tuneButtonTimer, &QTimer::timeout, this, &MainWindow::end_tuning);
859 
860   tuneATU_Timer.setSingleShot(true);
861   connect(&tuneATU_Timer, &QTimer::timeout, this, &MainWindow::stopTuneATU);
862 
863   killFileTimer.setSingleShot(true);
864   connect(&killFileTimer, &QTimer::timeout, this, &MainWindow::killFile);
865 
866   uploadTimer.setSingleShot(true);
867   connect(&uploadTimer, &QTimer::timeout, [this] () {uploadWSPRSpots ();});
868 
869   TxAgainTimer.setSingleShot(true);
870   connect(&TxAgainTimer, SIGNAL(timeout()), this, SLOT(TxAgain()));
871 
872   connect(m_wideGraph.data (), SIGNAL(setFreq3(int,int)),this,
873           SLOT(setFreq4(int,int)));
874 
875   decodeBusy(false);
876 
877   m_msg[0][0]=0;
878   ui->labDXped->setVisible(false);
879   ui->labDXped->setStyleSheet("QLabel {background-color: red; color: white;}");
880 
881   char const * const power[] = {"1 mW","2 mW","5 mW","10 mW","20 mW","50 mW","100 mW","200 mW","500 mW",
882                   "1 W","2 W","5 W","10 W","20 W","50 W","100 W","200 W","500 W","1 kW"};
883   for(auto i = 0u; i < sizeof power / sizeof power[0]; ++i)  { //Initialize dBm values
884     auto dBm = int ((10. * i / 3.) + .5);
885     ui->TxPowerComboBox->addItem (QString {"%1 dBm  %2"}.arg (dBm).arg (power[i]), dBm);
886   }
887 
888   m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc();
889   m_dateTimeSentTx3=QDateTime::currentDateTimeUtc();
890   // Z
891   labAz.setText("");
892   auto t = "UTC   dB   DT Freq    " + tr ("Message");
893   ui->lh_decodes_headings_label->setText(t);
894   ui->rh_decodes_headings_label->setText(t);
895   readSettings();            //Restore user's setup parameters
896   m_audioThread.start (m_audioThreadPriority);
897 
898 #ifdef WIN32
899   if (!m_multiple)
900     {
901       while(true)
902         {
903           int iret=killbyname("jt9.exe");
904           if(iret == 603) break;
905           if(iret != 0)
906             MessageBox::warning_message (this, tr ("Error Killing jt9.exe Process")
907                                          , tr ("KillByName return code: %1")
908                                          .arg (iret));
909         }
910     }
911 #endif
912 
913   {
914     //delete any .quit file that might have been left lying around
915     //since its presence will cause jt9 to exit a soon as we start it
916     //and decodes will hang
917     QFile quitFile {m_config.temp_dir ().absoluteFilePath (".quit")};
918     while (quitFile.exists ())
919       {
920         if (!quitFile.remove ())
921           {
922             MessageBox::query_message (this, tr ("Error removing \"%1\"").arg (quitFile.fileName ())
923                                        , tr ("Click OK to retry"));
924           }
925       }
926   }
927 
928   to_jt9(0,0,0);     //initialize IPC variables
929 
930   QStringList jt9_args {
931     "-s", QApplication::applicationName () // shared memory key,
932                                            // includes rig
933 #ifdef NDEBUG
934       , "-w", "1"               //FFTW patience - release
935 #else
936       , "-w", "1"               //FFTW patience - debug builds for speed
937 #endif
938       // The number  of threads for  FFTW specified here is  chosen as
939       // three because  that gives  the best  throughput of  the large
940       // FFTs used  in jt9.  The count  is the minimum of  (the number
941       // available CPU threads less one) and three.  This ensures that
942       // there is always at least one free CPU thread to run the other
943       // mode decoder in parallel.
944       , "-m", QString::number (qMin (qMax (QThread::idealThreadCount () - 1, 1), 3)) //FFTW threads
945 
946       , "-e", QDir::toNativeSeparators (m_appDir)
947       , "-a", QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath ())
948       , "-t", QDir::toNativeSeparators (m_config.temp_dir ().absolutePath ())
949       };
950   QProcessEnvironment new_env {m_env};
951   new_env.insert ("OMP_STACKSIZE", "4M");
952   proc_jt9.setProcessEnvironment (new_env);
953   proc_jt9.start(QDir::toNativeSeparators (m_appDir) + QDir::separator () +
954           "jt9", jt9_args, QIODevice::ReadWrite | QIODevice::Unbuffered);
955 
956   auto fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("wsjtx_wisdom.dat"))};
957   fftwf_import_wisdom_from_filename (fname.toLocal8Bit ());
958 
959   m_ntx = 6;
960   ui->txrb6->setChecked(true);
961 
962   connect (&m_wav_future_watcher, &QFutureWatcher<void>::finished, this, &MainWindow::diskDat);
963 
964   connect(&watcher3, SIGNAL(finished()),this,SLOT(fast_decode_done()));
965   if (!m_config.audio_input_device ().isNull ())
966     {
967       Q_EMIT startAudioInputStream (m_config.audio_input_device ()
968                                     , m_rx_audio_buffer_frames
969                                     , m_detector, m_downSampleFactor, m_config.audio_input_channel ());
970     }
971   if (!m_config.audio_output_device ().isNull ())
972     {
973       Q_EMIT initializeAudioOutputStream (m_config.audio_output_device ()
974                                           , AudioDevice::Mono == m_config.audio_output_channel () ? 1 : 2
975                                           , m_tx_audio_buffer_frames);
976     }
977   Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value () - m_XIT);
978 
979   enable_DXCC_entity (m_config.DXCC ());  // sets text window proportions and (re)inits the logbook
980 
981   // this must be done before initializing the mode as some modes need
982   // to turn off split on the rig e.g. WSPR
983   m_config.transceiver_online ();
984   bool vhf {m_config.enable_VHF_features ()};
985 
986   ui->txFirstCheckBox->setChecked(m_txFirst);
987   morse_(const_cast<char *> (m_config.my_callsign ().toLatin1().constData()),
988          const_cast<int *> (icw), &m_ncw, m_config.my_callsign ().length());
989   on_actionWide_Waterfall_triggered();
990   ui->cbShMsgs->setChecked(m_bShMsgs);
991   ui->cbSWL->setChecked(m_bSWL);
992   if(m_bFast9) m_bFastMode=true;
993   ui->cbFast9->setChecked(m_bFast9 or m_bFastMode);
994 
995   set_mode (m_mode);
996   if(m_mode=="Echo") monitor(false);   //Don't auto-start Monitor in Echo mode.
997   ui->sbSubmode->setValue (vhf ? m_nSubMode : 0);  //Submodes require VHF features
998   if(m_mode=="MSK144") {
999     Q_EMIT transmitFrequency (1000.0);
1000   } else {
1001     Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value() - m_XIT);
1002   }
1003   m_saveDecoded=ui->actionSave_decoded->isChecked();
1004   m_saveAll=ui->actionSave_all->isChecked();
1005   ui->TxPowerComboBox->setCurrentIndex(int(.3 * m_dBm + .2));
1006   ui->cbUploadWSPR_Spots->setChecked(m_uploadWSPRSpots);
1007   if((m_ndepth&7)==1) ui->actionQuickDecode->setChecked(true);
1008   if((m_ndepth&7)==2) ui->actionMediumDecode->setChecked(true);
1009   if((m_ndepth&7)==3) ui->actionDeepestDecode->setChecked(true);
1010   ui->actionInclude_averaging->setChecked(m_ndepth&16);
1011   ui->actionInclude_correlation->setChecked(m_ndepth&32);
1012   ui->actionEnable_AP_DXcall->setChecked(m_ndepth&64);
1013   ui->actionAuto_Clear_Avg->setChecked(m_ndepth&128);
1014 
1015   m_UTCdisk=-1;
1016   m_fCPUmskrtd=0.0;
1017   m_bFastDone=false;
1018   m_bAltV=false;
1019   m_bNoMoreFiles=false;
1020   m_bDoubleClicked=false;
1021   m_bCallingCQ=false;
1022   m_bCheckedContest=false;
1023   m_bDisplayedOnce=false;
1024   m_wait=0;
1025   m_isort=-3;
1026   m_max_dB=70;
1027   m_CQtype="CQ";
1028   fixStop();
1029   VHF_features_enabled(m_config.enable_VHF_features());
1030   m_wideGraph->setVHF(m_config.enable_VHF_features());
1031 
1032   connect( wsprNet, SIGNAL(uploadStatus(QString)), this, SLOT(uploadResponse(QString)));
1033 
1034   statusChanged();
1035 
1036   m_fastGraph->setMode(m_mode);
1037   m_wideGraph->setMode(m_mode);
1038 
1039   connect (&minuteTimer, &QTimer::timeout, this, &MainWindow::on_the_minute);
1040   minuteTimer.setSingleShot (true);
1041   minuteTimer.start (ms_minute_error () + 60 * 1000);
1042 
1043   connect (&splashTimer, &QTimer::timeout, this, &MainWindow::splash_done);
1044   splashTimer.setSingleShot (true);
1045   splashTimer.start (20 * 1000);
1046   // Z
1047   /*
1048   if(QCoreApplication::applicationVersion().contains("-devel") or
1049      QCoreApplication::applicationVersion().contains("-rc")) {
1050     QTimer::singleShot (0, this, SLOT (not_GA_warning_message ()));
1051   }
1052   */
1053 
1054   ui->pbBestSP->setVisible(m_mode=="FT4");
1055   // Z
1056 
1057   ui->pb_WDReset->setVisible(!m_config.wdResetAnywhere());
1058 
1059   if (!m_infoMessageShown) {
1060           ZMessage();
1061           m_infoMessageShown = true;
1062   }
1063 
1064 /*
1065   if(!ui->cbMenus->isChecked()) {
1066     ui->cbMenus->setChecked(true);
1067     ui->cbMenus->setChecked(false);
1068   }
1069 */
1070 
1071     ui->verticalLayout_3->setAlignment(ui->outAttenuation, Qt::AlignHCenter);
1072     ui->w_callInfo->setVisible(ui->actionCall_info->isChecked());
1073     qrzVisible(false);
1074 
1075 
1076     connect (&m_config, &Configuration::qrz_config_changed, this, &MainWindow::qrzInit);
1077     networkManager = new QNetworkAccessManager(this);
1078 
1079     connect (statusBar(), SIGNAL(clicked(bool)), this, SLOT(tx_watchdog(false)));
1080 
1081     // Initialize QRZ lookup
1082     if (m_config.qrzComUn().length() > 0 && m_config.qrzComPw().length() > 0) {
1083         qrzInit();
1084     }
1085 
1086     on_btn_clearIgnore_clicked();
1087 
1088     if ( m_config.special_op_id() == SpecOp::HOUND ) ui->cb_specialMode->setCurrentIndex(2);
1089     if ( m_config.special_op_id() == SpecOp::FOX ) ui->cb_specialMode->setCurrentIndex(1);
1090 
1091     on_actionDark_mode_triggered();
1092 
1093 
1094 
1095   // this must be the last statement of constructor
1096   if (!m_valid) throw std::runtime_error {"Fatal initialization exception"};
1097 }
1098 
not_GA_warning_message()1099 void MainWindow::not_GA_warning_message ()
1100 {
1101 
1102   MessageBox::critical_message (this,
1103                                 "This is a pre-release version of WSJT-X 2.5.0 made\n"
1104                                 "available for testing purposes.  By design it will\n"
1105                                 "be nonfunctional after Sept 30, 2021.");
1106   auto now = QDateTime::currentDateTimeUtc ();
1107   if (now >= QDateTime {{2021, 9, 30}, {23, 59, 59, 999}, Qt::UTC}) {
1108     Q_EMIT finished ();
1109   }
1110 }
1111 
initialize_fonts()1112 void MainWindow::initialize_fonts ()
1113 {
1114   set_application_font (m_config.text_font ());
1115   setDecodedTextFont (m_config.decoded_text_font ());
1116   // Z
1117   if (m_unfilteredView) m_unfilteredView->setFont(m_config.decoded_text_font ());
1118   if (m_pskReporterView) m_pskReporterView->setFont(m_config.decoded_text_font ());
1119 }
1120 
splash_done()1121 void MainWindow::splash_done ()
1122 {
1123   m_splash && m_splash->close ();
1124 }
1125 
on_the_minute()1126 void MainWindow::on_the_minute ()
1127 {
1128   if (minuteTimer.isSingleShot ())
1129     {
1130       minuteTimer.setSingleShot (false);
1131       minuteTimer.start (60 * 1000); // run free
1132     }
1133   else
1134     {
1135         auto const& ms_error = ms_minute_error ();
1136         if (qAbs (ms_error) > 1000) // keep drift within +-1s
1137         {
1138           minuteTimer.setSingleShot (true);
1139           minuteTimer.start (ms_error + 60 * 1000);
1140         }
1141     }
1142   // Z
1143   if (watchdog () && m_mode!="WSPR" && m_mode!="FST4W" && !(ui->cbAutoCQ->isChecked() && m_QSOProgress == CALLING)) {
1144     if (m_idleMinutes < watchdog ()) ++m_idleMinutes;
1145     update_watchdog_label ();
1146   } else {
1147     tx_watchdog (false);
1148   }
1149 }
1150 
1151 //--------------------------------------------------- MainWindow destructor
~MainWindow()1152 MainWindow::~MainWindow()
1153 {
1154   m_astroWidget.reset ();
1155   auto fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("wsjtx_wisdom.dat"))};
1156   fftwf_export_wisdom_to_filename (fname.toLocal8Bit ());
1157   m_audioThread.quit ();
1158   m_audioThread.wait ();
1159   remove_child_from_event_filter (this);
1160 }
1161 
1162 //-------------------------------------------------------- writeSettings()
writeSettings()1163 void MainWindow::writeSettings()
1164 {
1165   m_settings->beginGroup("MainWindow");
1166   if (ui->actionSWL_Mode->isChecked ())
1167     {
1168       m_settings->setValue ("SWLView", true);
1169       m_settings->setValue ("ShowMenus", ui->cbMenus->isChecked ());
1170       m_settings->setValue ("geometry", geometries ()[0]);
1171       m_settings->setValue ("SWLModeGeometry", saveGeometry ());
1172       m_settings->setValue ("geometryNoControls", geometries ()[2]);
1173     }
1174   else
1175     {
1176       if (ui->cbMenus->isChecked())
1177         {
1178           m_settings->setValue ("SWLView", ui->actionSWL_Mode->isChecked ());
1179           m_settings->setValue ("ShowMenus", true);
1180           m_settings->setValue ("geometry", saveGeometry ());
1181           m_settings->setValue ("SWLModeGeometry", geometries ()[1]);
1182           m_settings->setValue ("geometryNoControls", geometries ()[2]);
1183         }
1184       else
1185         {
1186           m_settings->setValue ("SWLView", ui->actionSWL_Mode->isChecked ());
1187           m_settings->setValue ("ShowMenus", false);
1188           m_settings->setValue ("geometry", geometries ()[0]);
1189           m_settings->setValue ("SWLModeGeometry", geometries ()[1]);
1190           m_settings->setValue ("geometryNoControls", saveGeometry ());
1191         }
1192     }
1193   m_settings->setValue ("state", saveState ());
1194   m_settings->setValue("MRUdir", m_path);
1195   m_settings->setValue("TxFirst",m_txFirst);
1196   m_settings->setValue("DXcall",ui->dxCallEntry->text());
1197   m_settings->setValue("DXgrid",ui->dxGridEntry->text());
1198   m_settings->setValue ("AstroDisplayed", m_astroWidget && m_astroWidget->isVisible());
1199   m_settings->setValue ("MsgAvgDisplayed", m_msgAvgWidget && m_msgAvgWidget->isVisible ());
1200   m_settings->setValue ("FoxLogDisplayed", m_foxLogWindow && m_foxLogWindow->isVisible ());
1201   m_settings->setValue ("ContestLogDisplayed", m_contestLogWindow && m_contestLogWindow->isVisible ());
1202   m_settings->setValue("CallFirst",ui->cbFirst->isChecked());
1203   m_settings->setValue("HoundSort",ui->comboBoxHoundSort->currentIndex());
1204   m_settings->setValue("FoxNlist",ui->sbNlist->value());
1205   m_settings->setValue("FoxNslots",ui->sbNslots->value());
1206   m_settings->setValue("FoxMaxDB_v2",ui->sbMax_dB->value()); // original key abandoned
1207   m_settings->setValue ("SerialNumber",ui->sbSerialNumber->value ());
1208   m_settings->endGroup();
1209 
1210   m_settings->beginGroup("Common");
1211   m_settings->setValue("Mode",m_mode);
1212   m_settings->setValue("SaveNone",ui->actionNone->isChecked());
1213   m_settings->setValue("SaveDecoded",ui->actionSave_decoded->isChecked());
1214   m_settings->setValue("SaveAll",ui->actionSave_all->isChecked());
1215   m_settings->setValue("NDepth",m_ndepth);
1216   m_settings->setValue("RxFreq",ui->RxFreqSpinBox->value());
1217   m_settings->setValue("TxFreq",ui->TxFreqSpinBox->value());
1218   m_settings->setValue("WSPRfreq",ui->WSPRfreqSpinBox->value());
1219   m_settings->setValue("FST4W_RxFreq",ui->sbFST4W_RxFreq->value());
1220   m_settings->setValue("FST4W_FTol",ui->sbFST4W_FTol->value());
1221   m_settings->setValue("FST4_FLow",ui->sbF_Low->value());
1222   m_settings->setValue("FST4_FHigh",ui->sbF_High->value());
1223   m_settings->setValue("SubMode",ui->sbSubmode->value());
1224   m_settings->setValue("DTtol",m_DTtol);
1225   m_settings->setValue("Ftol", ui->sbFtol->value ());
1226   m_settings->setValue("MinSync",m_minSync);
1227   m_settings->setValue ("AutoSeq", ui->cbAutoSeq->isChecked ());
1228   m_settings->setValue ("RxAll", ui->cbRxAll->isChecked ());
1229   m_settings->setValue("ShMsgs",m_bShMsgs);
1230   m_settings->setValue("SWL",ui->cbSWL->isChecked());
1231   m_settings->setValue ("DialFreq", QVariant::fromValue(m_lastMonitoredFrequency));
1232   m_settings->setValue("OutAttenuation", ui->outAttenuation->value ());
1233   m_settings->setValue("NoSuffix",m_noSuffix);
1234   m_settings->setValue("GUItab",ui->tabWidget->currentIndex());
1235   m_settings->setValue("OutBufSize",outBufSize);
1236   m_settings->setValue ("HoldTxFreq", ui->cbHoldTxFreq->isChecked ());
1237   m_settings->setValue("PctTx", ui->sbTxPercent->value ());
1238   m_settings->setValue("RoundRobin",ui->RoundRobin->currentText());
1239   m_settings->setValue("dBm",m_dBm);
1240   m_settings->setValue("RR73",m_send_RR73);
1241   m_settings->setValue ("WSPRPreferType1", ui->WSPR_prefer_type_1_check_box->isChecked ());
1242   m_settings->setValue("UploadSpots",m_uploadWSPRSpots);
1243   m_settings->setValue("NoOwnCall",ui->cbNoOwnCall->isChecked());
1244   m_settings->setValue ("BandHopping", ui->band_hopping_group_box->isChecked ());
1245   m_settings->setValue ("TRPeriod", ui->sbTR->value ());
1246   m_settings->setValue ("MaxDrift", ui->sbMaxDrift->value());
1247   m_settings->setValue ("TRPeriod_FST4W", ui->sbTR_FST4W->value ());
1248   m_settings->setValue("FastMode",m_bFastMode);
1249   m_settings->setValue("Fast9",m_bFast9);
1250   m_settings->setValue ("CQTxfreq", ui->sbCQTxFreq->value ());
1251   m_settings->setValue("pwrBandTxMemory",m_pwrBandTxMemory);
1252   m_settings->setValue("pwrBandTuneMemory",m_pwrBandTuneMemory);
1253   m_settings->setValue ("FT8AP", ui->actionEnable_AP_FT8->isChecked ());
1254   m_settings->setValue ("JT65AP", ui->actionEnable_AP_JT65->isChecked ());
1255   m_settings->setValue ("AutoClearAvg", ui->actionAuto_Clear_Avg->isChecked ());
1256   m_settings->setValue("SplitterState",ui->decodes_splitter->saveState());
1257   m_settings->setValue("Blanker",ui->sbNB->value());
1258 
1259   {
1260     QList<QVariant> coeffs;     // suitable for QSettings
1261     for (auto const& coeff : m_phaseEqCoefficients)
1262       {
1263         coeffs << coeff;
1264       }
1265     m_settings->setValue ("PhaseEqualizationCoefficients", QVariant {coeffs});
1266   }
1267   // Z
1268   m_settings->setValue("ignoreCQXX",ui->le_ignoreCQXX->text());
1269   m_settings->setValue("customAlerts",  ui->le_CustomAlerts->text());
1270   m_settings->setValue ("ignoreCQTargetIndex", ui->cb_ignoreCQTarget->currentIndex());
1271   m_settings->setValue ("filter_callB4", ui->cb_callB4->isChecked() );
1272   m_settings->setValue ("filter_callB4onBand", ui->cb_callB4onBand->isChecked() );
1273   m_settings->setValue ("filter_countryB4", ui->cb_countryB4->isChecked() );
1274   m_settings->setValue ("filter_countryB4onBand", ui->cb_countryB4onBand->isChecked() );
1275   m_settings->setValue ("filter_gridB4", ui->cb_gridB4->isChecked() );
1276   m_settings->setValue ("filter_gridB4onBand", ui->cb_gridB4onBand->isChecked() );
1277   m_settings->setValue ("filter_continentB4", ui->cb_continentB4->isChecked() );
1278   m_settings->setValue ("filter_continentB4onBand", ui->cb_continentB4onBand->isChecked() );
1279   m_settings->setValue ("filter_CQZoneB4", ui->cb_CQZoneB4->isChecked() );
1280   m_settings->setValue ("filter_CQZoneB4onBand", ui->cb_CQZoneB4onBand->isChecked() );
1281   m_settings->setValue ("filter_ITUZoneB4", ui->cb_ITUZoneB4->isChecked() );
1282   m_settings->setValue ("filter_ITUZoneB4onBand", ui->cb_ITUZoneB4onBand->isChecked() );
1283   m_settings->setValue ("alert_callB4", ui->cb_callB4_alert->isChecked() );
1284   m_settings->setValue ("alert_callB4onBand", ui->cb_callB4onBand_alert->isChecked() );
1285   m_settings->setValue ("alert_countryB4", ui->cb_countryB4_alert->isChecked() );
1286   m_settings->setValue ("alert_countryB4onBand", ui->cb_countryB4onBand_alert->isChecked() );
1287   m_settings->setValue ("alert_gridB4", ui->cb_gridB4_alert->isChecked() );
1288   m_settings->setValue ("alert_gridB4onBand", ui->cb_gridB4onBand_alert->isChecked() );
1289   m_settings->setValue ("alert_continentB4", ui->cb_continentB4_alert->isChecked() );
1290   m_settings->setValue ("alert_continentB4onBand", ui->cb_continentB4onBand_alert->isChecked() );
1291   m_settings->setValue ("alert_CQZoneB4", ui->cb_CQZoneB4_alert->isChecked() );
1292   m_settings->setValue ("alert_CQZoneB4onBand", ui->cb_CQZoneB4onBand_alert->isChecked() );
1293   m_settings->setValue ("alert_ITUZoneB4", ui->cb_ITUZoneB4_alert->isChecked() );
1294   m_settings->setValue ("alert_ITUZoneB4onBand", ui->cb_ITUZoneB4onBand_alert->isChecked() );
1295   m_settings->setValue ("filter_pte_prefixFilter", ui->pte_prefixFilter->toPlainText());
1296   m_settings->setValue ("filter_pte_stateFilter", ui->pte_stateFilter->toPlainText());
1297   m_settings->setValue ("filter_MindB", ui->sbMindB->value());
1298   m_settings->setValue ("filter_cb_prefixFilter", ui->cb_prefixFilter->currentIndex());
1299   m_settings->setValue ("filter_cb_stateFilter", ui->cb_stateFilter->currentIndex());
1300   m_settings->setValue ("filter_c_EU", ui->cb_c_EU->isChecked());
1301   m_settings->setValue ("filter_c_AF", ui->cb_c_AF->isChecked());
1302   m_settings->setValue ("filter_c_AN", ui->cb_c_AN->isChecked());
1303   m_settings->setValue ("filter_c_AS", ui->cb_c_AS->isChecked());
1304   m_settings->setValue ("filter_c_NA", ui->cb_c_NA->isChecked());
1305   m_settings->setValue ("filter_c_SA", ui->cb_c_SA->isChecked());
1306   m_settings->setValue ("filter_c_OC", ui->cb_c_OC->isChecked());
1307   m_settings->setValue ("filter_CQDX_Continent", ui->cb_filter_CQDX_Continent->currentIndex());
1308   m_settings->setValue ("AutoIgnore", ui->cb_IgnoreAfterWD->isChecked());
1309   m_settings->setValue ("filter_LOTW", ui->cb_f_LOTW->isChecked());
1310   m_settings->setValue ("CQonlyIncl73", ui->cbCQonlyIncl73->isChecked());
1311   m_settings->setValue ("dockWaterfall", ui->cbDockWF->isChecked());
1312   m_settings->setValue ("showCallInfo", ui->actionCall_info->isChecked());
1313   m_settings->setValue ("filter_enabled", ui->cb_filtering->isChecked());
1314   m_settings->setValue ("darkMode", ui->actionDark_mode->isChecked());
1315   m_settings->setValue ("rawViewDisplayed", m_unfilteredView && m_unfilteredView->isVisible ());
1316   m_settings->setValue ("pskViewDisplayed", m_pskReporterView && m_pskReporterView->isVisible ());
1317   m_settings->setValue ("txFirstLock",  m_TxFirstLock);
1318 
1319   if (m_unfilteredView && m_unfilteredView->isVisible ()) {
1320       m_settings->setValue ("rawViewGeometry", m_unfilteredView->saveGeometry() );
1321   }
1322 
1323   if (m_pskReporterView && m_pskReporterView->isVisible ()) {
1324       m_settings->setValue ("pskViewGeometry", m_pskReporterView->saveGeometry() );
1325   }
1326 
1327   // Misc tab
1328   m_settings->setValue ("autoModeSwitchEnabled", ui->cb_autoModeSwitch->isChecked());
1329   m_settings->setValue ("autoCQCount", ui->sb_autoCQCount->value ());
1330   m_settings->setValue ("autoCallCount", ui->sb_autoCallCount->value ());
1331 
1332 
1333   m_settings->setValue ("bandHopperEnabled", ui->cb_bandHopper->isChecked());
1334   m_settings->setValue ("bandHopper", ui->pte_bandHopper->toPlainText());
1335 
1336 
1337   m_settings->setValue ("AutoCallPriority", ui->cb_autoCallPriority->currentIndex());
1338 
1339   m_settings->setValue ("infoMessageShown", m_infoMessageShown);
1340 
1341   m_settings->setValue( "tx1State",  ui->tx1->isEnabled());
1342 
1343   m_settings->endGroup();
1344 }
1345 
1346 //---------------------------------------------------------- readSettings()
readSettings()1347 void MainWindow::readSettings()
1348 {
1349   ui->cbAutoSeq->setVisible(false);
1350   ui->cbFirst->setVisible(false);
1351   m_settings->beginGroup("MainWindow");
1352   std::array<QByteArray, 3> the_geometries;
1353   the_geometries[0] = m_settings->value ("geometry", saveGeometry ()).toByteArray ();
1354   the_geometries[1] = m_settings->value ("SWLModeGeometry", saveGeometry ()).toByteArray ();
1355   the_geometries[2] = m_settings->value ("geometryNoControls", saveGeometry ()).toByteArray ();
1356   auto SWL_mode = m_settings->value ("SWLView", false).toBool ();
1357   auto show_menus = m_settings->value ("ShowMenus", true).toBool ();
1358   ui->actionSWL_Mode->setChecked (SWL_mode);
1359   ui->cbMenus->setChecked (show_menus);
1360   auto current_view_mode = SWL_mode ? 1 : show_menus ? 0 : 2;
1361   change_layout (current_view_mode);
1362   geometries (current_view_mode, the_geometries);
1363   restoreState (m_settings->value ("state", saveState ()).toByteArray ());
1364   ui->dxCallEntry->setText (m_settings->value ("DXcall", QString {}).toString ());
1365   ui->dxGridEntry->setText (m_settings->value ("DXgrid", QString {}).toString ());
1366   m_path = m_settings->value("MRUdir", m_config.save_directory ().absolutePath ()).toString ();
1367   m_txFirst = m_settings->value("TxFirst",false).toBool();
1368   auto displayAstro = m_settings->value ("AstroDisplayed", false).toBool ();
1369   auto displayMsgAvg = m_settings->value ("MsgAvgDisplayed", false).toBool ();
1370   auto displayFoxLog = m_settings->value ("FoxLogDisplayed", false).toBool ();
1371   auto displayContestLog = m_settings->value ("ContestLogDisplayed", false).toBool ();
1372   ui->cbFirst->setChecked(m_settings->value("CallFirst",true).toBool());
1373   ui->comboBoxHoundSort->setCurrentIndex(m_settings->value("HoundSort",3).toInt());
1374   ui->sbNlist->setValue(m_settings->value("FoxNlist",12).toInt());
1375   m_Nslots=m_settings->value("FoxNslots",5).toInt();
1376   ui->sbNslots->setValue(m_Nslots);
1377   ui->sbMax_dB->setValue(m_settings->value("FoxMaxDB_v2",70).toInt());
1378   ui->sbSerialNumber->setValue (m_settings->value ("SerialNumber", 1).toInt ());
1379   m_settings->endGroup();
1380 
1381   // do this outside of settings group because it uses groups internally
1382   ui->actionAstronomical_data->setChecked (displayAstro);
1383 
1384   m_settings->beginGroup("Common");
1385  // Z
1386   ui->cb_callB4->setChecked(m_settings->value("filter_callB4", false).toBool());
1387   ui->cb_callB4onBand->setChecked(m_settings->value("filter_callB4onBand", false).toBool());
1388   ui->cb_countryB4->setChecked(m_settings->value("filter_countryB4", false).toBool());
1389   ui->cb_countryB4onBand->setChecked(m_settings->value("filter_countryB4onBand", false).toBool());
1390   ui->cb_gridB4->setChecked(m_settings->value("filter_gridB4", false).toBool());
1391   ui->cb_gridB4onBand->setChecked(m_settings->value("filter_gridB4onBand", false).toBool());
1392   ui->cb_continentB4->setChecked(m_settings->value("filter_continentB4", false).toBool());
1393   ui->cb_continentB4onBand->setChecked(m_settings->value("filter_continentB4onBand", false).toBool());
1394   ui->cb_CQZoneB4->setChecked(m_settings->value("filter_CQZoneB4", false).toBool());
1395   ui->cb_CQZoneB4onBand->setChecked(m_settings->value("filter_CQZoneB4onBand", false).toBool());
1396   ui->cb_ITUZoneB4->setChecked(m_settings->value("filter_ITUZoneB4", false).toBool());
1397   ui->cb_ITUZoneB4onBand->setChecked(m_settings->value("filter_ITUZoneB4onBand", false).toBool());
1398   ui->cb_callB4_alert->setChecked(m_settings->value("alert_callB4", false).toBool());
1399   ui->cb_callB4onBand_alert->setChecked(m_settings->value("alert_callB4onBand", false).toBool());
1400   ui->cb_countryB4_alert->setChecked(m_settings->value("alert_countryB4", false).toBool());
1401   ui->cb_countryB4onBand_alert->setChecked(m_settings->value("alert_countryB4onBand", false).toBool());
1402   ui->cb_gridB4_alert->setChecked(m_settings->value("alert_gridB4", false).toBool());
1403   ui->cb_gridB4onBand_alert->setChecked(m_settings->value("alert_gridB4onBand", false).toBool());
1404   ui->cb_continentB4_alert->setChecked(m_settings->value("alert_continentB4", false).toBool());
1405   ui->cb_continentB4onBand_alert->setChecked(m_settings->value("alert_continentB4onBand", false).toBool());
1406   ui->cb_CQZoneB4_alert->setChecked(m_settings->value("alert_CQZoneB4", false).toBool());
1407   ui->cb_CQZoneB4onBand_alert->setChecked(m_settings->value("alert_CQZoneB4onBand", false).toBool());
1408   ui->cb_ITUZoneB4_alert->setChecked(m_settings->value("alert_ITUZoneB4", false).toBool());
1409   ui->cb_ITUZoneB4onBand_alert->setChecked(m_settings->value("alert_ITUZoneB4onBand", false).toBool());
1410   ui->pte_prefixFilter->setPlainText(m_settings->value("filter_pte_prefixFilter", "").toString());
1411   ui->pte_stateFilter->setPlainText(m_settings->value("filter_pte_stateFilter", "").toString());
1412   ui->sbMindB->setValue(m_settings->value("filter_MindB", -30).toInt());
1413   ui->cb_prefixFilter->setCurrentIndex(m_settings->value("filter_cb_prefixFilter", 0).toInt());
1414   ui->cb_stateFilter->setCurrentIndex(m_settings->value("filter_cb_stateFilter", 0).toInt());
1415   ui->cb_c_EU->setChecked(m_settings->value("filter_c_EU", true).toBool());
1416   ui->cb_c_AF->setChecked(m_settings->value("filter_c_AF", true).toBool());
1417   ui->cb_c_AN->setChecked(m_settings->value("filter_c_AN", true).toBool());
1418   ui->cb_c_AS->setChecked(m_settings->value("filter_c_AS", true).toBool());
1419   ui->cb_c_NA->setChecked(m_settings->value("filter_c_NA", true).toBool());
1420   ui->cb_c_SA->setChecked(m_settings->value("filter_c_SA", true).toBool());
1421   ui->cb_c_OC->setChecked(m_settings->value("filter_c_OC", true).toBool());
1422   ui->cb_f_LOTW->setChecked(m_settings->value("filter_LOTW", false).toBool());
1423   ui->cb_filter_CQDX_Continent->setCurrentIndex(m_settings->value("filter_CQDX_Continent", 0).toInt());
1424   ui->le_ignoreCQXX->setText(m_settings->value("ignoreCQXX").toString());
1425   ui->le_CustomAlerts->setText(m_settings->value("customAlerts").toString());
1426   ui->cb_IgnoreAfterWD->setChecked(m_settings->value("AutoIgnore",true).toBool());
1427   ui->cbCQonlyIncl73->setChecked(m_settings->value("CQonlyIncl73", false).toBool());
1428   ui->cbDockWF->setChecked(m_settings->value("dockWaterfall", false).toBool());
1429   ui->actionCall_info->setChecked(m_settings->value("showCallInfo", false).toBool());
1430   ui->actionDark_mode->setChecked(m_settings->value("darkMode", false).toBool());
1431   ui->cb_filtering->setChecked(m_settings->value("filter_enabled", true).toBool());
1432   // Misc tab
1433   ui->cb_autoModeSwitch->setChecked(m_settings->value("autoModeSwitchEnabled", false).toBool());
1434   ui->sb_autoCQCount->setValue(m_settings->value("autoCQCount", 5).toInt());
1435   ui->sb_autoCallCount->setValue(m_settings->value("autoCallCount", 5).toInt());
1436   ui->le_autoCQLeft->setText(m_settings->value("autoCQCount", 5).toString());
1437   ui->le_autoCallLeft->setText(m_settings->value("autoCallCount", 5).toString());
1438   ui->cb_bandHopper->setChecked(m_settings->value("bandHopperEnabled", false).toBool());
1439   ui->pte_bandHopper->setPlainText(m_settings->value("bandHopper", "").toString());
1440   ui->cb_autoCallPriority->setCurrentIndex(m_settings->value ("AutoCallPriority", 0).toInt ());
1441   m_infoMessageShown = m_settings->value("infoMessageShown", false).toBool();
1442   ui->cb_ignoreCQTarget->setCurrentIndex(m_settings->value("ignoreCQTargetIndex", 0).toInt());
1443   ui->tx1->setEnabled(m_settings->value("tx1State", true).toBool());
1444   m_unfilteredViewGeometry = m_settings->value("rawViewGeometry").toByteArray();
1445   m_pskReporterViewGeometry = m_settings->value("pskViewGeometry").toByteArray();
1446   auto showRawView =m_settings->value("rawViewDisplayed", false).toBool();
1447   auto showPskView =m_settings->value("pskViewDisplayed", false).toBool();
1448   m_TxFirstLock = m_settings->value("txFirstLock", false).toBool();
1449 
1450 
1451   m_mode=m_settings->value("Mode","JT9").toString();
1452   ui->actionNone->setChecked(m_settings->value("SaveNone",true).toBool());
1453   ui->actionSave_decoded->setChecked(m_settings->value("SaveDecoded",false).toBool());
1454   ui->actionSave_all->setChecked(m_settings->value("SaveAll",false).toBool());
1455   ui->RxFreqSpinBox->setValue(0); // ensure a change is signaled
1456   ui->RxFreqSpinBox->setValue(m_settings->value("RxFreq",1500).toInt());
1457   ui->sbFST4W_RxFreq->setValue(0);
1458   ui->sbFST4W_RxFreq->setValue(m_settings->value("FST4W_RxFreq",1500).toInt());
1459   ui->sbF_Low->setValue(m_settings->value("FST4_FLow",600).toInt());
1460   ui->sbF_High->setValue(m_settings->value("FST4_FHigh",1400).toInt());
1461   m_nSubMode=m_settings->value("SubMode",0).toInt();
1462   ui->sbSubmode->setValue(m_nSubMode);
1463   ui->sbFtol->setValue (m_settings->value("Ftol", 50).toInt());
1464   ui->sbFST4W_FTol->setValue(m_settings->value("FST4W_FTol",100).toInt());
1465   m_minSync=m_settings->value("MinSync",0).toInt();
1466   ui->syncSpinBox->setValue(m_minSync);
1467   ui->cbAutoSeq->setChecked (m_settings->value ("AutoSeq", false).toBool());
1468   ui->cbRxAll->setChecked (m_settings->value ("RxAll", false).toBool());
1469   m_bShMsgs=m_settings->value("ShMsgs",false).toBool();
1470   m_bSWL=m_settings->value("SWL",false).toBool();
1471   m_bFast9=m_settings->value("Fast9",false).toBool();
1472   m_bFastMode=m_settings->value("FastMode",false).toBool();
1473   ui->sbTR->setValue (m_settings->value ("TRPeriod", 15).toInt());
1474   ui->sbMaxDrift->setValue (m_settings->value ("MaxDrift",0).toInt());
1475   ui->sbTR_FST4W->setValue (m_settings->value ("TRPeriod_FST4W", 15).toInt());
1476   m_lastMonitoredFrequency = m_settings->value ("DialFreq",
1477     QVariant::fromValue<Frequency> (default_frequency)).value<Frequency> ();
1478   ui->WSPRfreqSpinBox->setValue(0); // ensure a change is signaled
1479   ui->WSPRfreqSpinBox->setValue(m_settings->value("WSPRfreq",1500).toInt());
1480   ui->TxFreqSpinBox->setValue(0); // ensure a change is signaled
1481   ui->TxFreqSpinBox->setValue(m_settings->value("TxFreq",1500).toInt());
1482   m_ndepth=m_settings->value("NDepth",3).toInt();
1483   ui->sbTxPercent->setValue (m_settings->value ("PctTx", 20).toInt ());
1484   on_sbTxPercent_valueChanged (ui->sbTxPercent->value ());
1485   ui->RoundRobin->setCurrentText(m_settings->value("RoundRobin",tr("Random")).toString());
1486   m_dBm=m_settings->value("dBm",37).toInt();
1487   m_send_RR73=m_settings->value("RR73",false).toBool();
1488   if(m_send_RR73) {
1489     m_send_RR73=false;
1490     on_txrb4_doubleClicked();
1491   }
1492   ui->WSPR_prefer_type_1_check_box->setChecked (m_settings->value ("WSPRPreferType1", true).toBool ());
1493   m_uploadWSPRSpots=m_settings->value("UploadSpots",false).toBool();
1494   ui->cbNoOwnCall->setChecked(m_settings->value("NoOwnCall",false).toBool());
1495   ui->band_hopping_group_box->setChecked (m_settings->value ("BandHopping", false).toBool());
1496   // setup initial value of tx attenuator
1497   m_block_pwr_tooltip = true;
1498   ui->outAttenuation->setValue (m_settings->value ("OutAttenuation", 0).toInt ());
1499   m_block_pwr_tooltip = false;
1500   ui->sbCQTxFreq->setValue (m_settings->value ("CQTxFreq", 260).toInt());
1501   m_noSuffix=m_settings->value("NoSuffix",false).toBool();
1502   int n=m_settings->value("GUItab",0).toInt();
1503   ui->tabWidget->setCurrentIndex(n);
1504   outBufSize=m_settings->value("OutBufSize",4096).toInt();
1505   ui->cbHoldTxFreq->setChecked (m_settings->value ("HoldTxFreq", false).toBool ());
1506   m_pwrBandTxMemory=m_settings->value("pwrBandTxMemory").toHash();
1507   m_pwrBandTuneMemory=m_settings->value("pwrBandTuneMemory").toHash();
1508   ui->actionEnable_AP_FT8->setChecked (m_settings->value ("FT8AP", false).toBool());
1509   ui->actionEnable_AP_JT65->setChecked (m_settings->value ("JT65AP", false).toBool());
1510   ui->actionAuto_Clear_Avg->setChecked (m_settings->value ("AutoClearAvg", false).toBool());
1511   ui->decodes_splitter->restoreState(m_settings->value("SplitterState").toByteArray());
1512   ui->sbNB->setValue(m_settings->value("Blanker",0).toInt());
1513   {
1514     auto const& coeffs = m_settings->value ("PhaseEqualizationCoefficients"
1515                                             , QList<QVariant> {0., 0., 0., 0., 0.}).toList ();
1516     m_phaseEqCoefficients.clear ();
1517     for (auto const& coeff : coeffs)
1518       {
1519         m_phaseEqCoefficients.append (coeff.value<double> ());
1520       }
1521   }
1522   m_settings->endGroup();
1523 
1524   // use these initialisation settings to tune the audio o/p buffer
1525   // size and audio thread priority
1526   m_settings->beginGroup ("Tune");
1527   m_audioThreadPriority = static_cast<QThread::Priority> (m_settings->value ("Audio/ThreadPriority", QThread::HighPriority).toInt () % 8);
1528   m_settings->endGroup ();
1529 
1530   checkMSK144ContestType();
1531   if(displayMsgAvg) on_actionMessage_averaging_triggered();
1532   if (displayFoxLog) on_fox_log_action_triggered ();
1533   if (displayContestLog) on_contest_log_action_triggered ();
1534 
1535   // Z
1536   if (showRawView) on_actionUnfiltered_View_triggered();
1537   if (showPskView) on_actionPSKReporter_triggered();
1538   if (m_TxFirstLock) ui->txFirstCheckBox->setStyleSheet("background-color: #ff0000;");
1539 }
1540 
checkMSK144ContestType()1541 void MainWindow::checkMSK144ContestType()
1542 {
1543   if(SpecOp::NONE != m_config.special_op_id())
1544     {
1545       if(m_mode=="MSK144" && SpecOp::EU_VHF < m_config.special_op_id())
1546         {
1547           MessageBox::warning_message (this, tr ("Improper mode"),
1548            "Mode will be changed to FT8. MSK144 not available if Field Day, WW Digi, RTTY or Fox/Hound is selected.");
1549           on_actionFT8_triggered();
1550         }
1551     }
1552 }
1553 
set_application_font(QFont const & font)1554 void MainWindow::set_application_font (QFont const& font)
1555 {
1556   qApp->setFont (font);
1557   // Z
1558   qApp->setStyleSheet (qApp->styleSheet () + "* {" + font_as_stylesheet (font) + '}');
1559   for (auto& widget : qApp->topLevelWidgets ())
1560     {
1561       widget->updateGeometry ();
1562     }
1563 }
1564 
setDecodedTextFont(QFont const & font)1565 void MainWindow::setDecodedTextFont (QFont const& font)
1566 {
1567   ui->decodedTextBrowser->setContentFont (font);
1568   ui->decodedTextBrowser2->setContentFont (font);
1569   ui->textBrowser4->setContentFont(font);
1570   ui->textBrowser4->displayFoxToBeCalled(" ");
1571   ui->textBrowser4->setText("");
1572   auto style_sheet = "QLabel {" + font_as_stylesheet (font) + '}';
1573   ui->lh_decodes_headings_label->setStyleSheet (ui->lh_decodes_headings_label->styleSheet () + style_sheet);
1574   ui->rh_decodes_headings_label->setStyleSheet (ui->rh_decodes_headings_label->styleSheet () + style_sheet);
1575   if (m_msgAvgWidget) {
1576     m_msgAvgWidget->changeFont (font);
1577   }
1578   if (m_foxLogWindow) {
1579     m_foxLogWindow->set_log_view_font (font);
1580   }
1581   if (m_contestLogWindow) {
1582     m_contestLogWindow->set_log_view_font (font);
1583   }
1584   updateGeometry ();
1585 }
1586 
fixStop()1587 void MainWindow::fixStop()
1588 {
1589   m_hsymStop=179;
1590   if(m_mode=="WSPR") {
1591     m_hsymStop=396;
1592   } else if(m_mode=="Echo") {
1593     m_hsymStop=9;
1594   } else if (m_mode=="JT4"){
1595     m_hsymStop=176;
1596     if(m_config.decode_at_52s()) m_hsymStop=179;
1597   } else if (m_mode=="JT9"){
1598     m_hsymStop=173;
1599     if(m_config.decode_at_52s()) m_hsymStop=179;
1600   } else if (m_mode=="JT65"){
1601     m_hsymStop=174;
1602     if(m_config.decode_at_52s()) m_hsymStop=179;
1603   } else if (m_mode=="Q65"){
1604     m_hsymStop=48;                                  // 13.8 s
1605     if(m_TRperiod==30) {
1606       m_hsymStop=96;                                // 27.6 s
1607       if(m_config.decode_at_52s()) m_hsymStop=100;  // 28.8 s
1608     }
1609     if(m_TRperiod==60) m_hsymStop=196;              // 56.4 s
1610     if(m_TRperiod==120) m_hsymStop=408;             // 117.5 s
1611     if(m_TRperiod==300) m_hsymStop=1030;            // 296.6 s
1612   } else if (m_mode=="FreqCal"){
1613     m_hsymStop=((int(m_TRperiod/0.288))/8)*8;
1614   } else if (m_mode=="FT8") {
1615     m_hsymStop=50;
1616   } else if (m_mode=="FT4") {
1617   m_hsymStop=21;
1618   } else if(m_mode=="FST4" or m_mode=="FST4W") {
1619     int stop[] = {39,85,187,387,1003,3107,6232};
1620     int stop_EME[] = {48,95,197,396,1012,3107,6232};
1621     int i=0;
1622     if(m_TRperiod==30) i=1;
1623     if(m_TRperiod==60) i=2;
1624     if(m_TRperiod==120) i=3;
1625     if(m_TRperiod==300) i=4;
1626     if(m_TRperiod==900) i=5;
1627     if(m_TRperiod==1800) i=6;
1628     if(m_config.decode_at_52s()) {
1629       m_hsymStop=stop_EME[i];
1630     } else {
1631       m_hsymStop=stop[i];
1632     }
1633   }
1634 }
1635 
1636 //-------------------------------------------------------------- dataSink()
dataSink(qint64 frames)1637 void MainWindow::dataSink(qint64 frames)
1638 {
1639   static float s[NSMAX];
1640   char line[80];
1641   int k(frames);
1642   auto fname {QDir::toNativeSeparators(m_config.writeable_data_dir ().absoluteFilePath ("refspec.dat")).toLocal8Bit ()};
1643 
1644   if(m_diskData) {
1645     dec_data.params.ndiskdat=1;
1646   } else {
1647     dec_data.params.ndiskdat=0;
1648     m_wideGraph->setDiskUTC(-1);
1649   }
1650 
1651   m_bUseRef=m_wideGraph->useRef();
1652   if(!m_diskData) {
1653     refspectrum_(&dec_data.d2[k-m_nsps/2],&m_bClearRefSpec,&m_bRefSpec,
1654                  &m_bUseRef, fname.constData (), fname.size ());
1655   }
1656   m_bClearRefSpec=false;
1657 
1658   if(m_mode=="MSK144" or m_bFast9) {
1659     fastSink(frames);
1660     if(m_bFastMode) return;
1661   }
1662 
1663 // Get power, spectrum, and ihsym
1664   dec_data.params.nfa=m_wideGraph->nStartFreq();
1665   dec_data.params.nfb=m_wideGraph->Fmax();
1666   if(m_mode=="FST4") {
1667     dec_data.params.nfa=ui->sbF_Low->value();
1668     dec_data.params.nfb=ui->sbF_High->value();
1669   }
1670   int nsps=m_nsps;
1671   if(m_bFastMode) nsps=6912;
1672   int nsmo=m_wideGraph->smoothYellow()-1;
1673   bool bLowSidelobes=m_config.lowSidelobes();
1674   int npct=0;
1675   if(m_mode.startsWith("FST4")) npct=ui->sbNB->value();
1676   symspec_(&dec_data,&k,&m_TRperiod,&nsps,&m_inGain,&bLowSidelobes,&nsmo,&m_px,s,
1677            &m_df3,&m_ihsym,&m_npts8,&m_pxmax,&npct);
1678   if(m_mode=="WSPR" or m_mode=="FST4W") wspr_downsample_(dec_data.d2,&k);
1679   if(m_ihsym <=0) return;
1680   if(ui) ui->signal_meter_widget->setValue(m_px,m_pxmax); // Update thermometer
1681   if(m_monitoring || m_diskData) {
1682     m_wideGraph->dataSink2(s,m_df3,m_ihsym,m_diskData);
1683   }
1684   if(m_mode=="MSK144") return;
1685 
1686   fixStop();
1687   if (m_mode == "FreqCal"
1688       // only calculate after 1st chunk, also skip chunk where rig
1689       // changed frequency
1690       && !(m_ihsym % 8) && m_ihsym > 8 && m_ihsym <= m_hsymStop) {
1691     int RxFreq=ui->RxFreqSpinBox->value ();
1692     int nkhz=(m_freqNominal+RxFreq)/1000;
1693     int ftol = ui->sbFtol->value ();
1694     freqcal_(&dec_data.d2[0],&k,&nkhz,&RxFreq,&ftol,&line[0],80);
1695     QString t=QString::fromLatin1(line);
1696     DecodedText decodedtext {t};
1697     ui->decodedTextBrowser->displayDecodedText (decodedtext, m_config.my_callsign (), m_mode, m_config.DXCC (),
1698           m_logBook, m_currentBand, m_config.ppfx ());
1699     if (ui->measure_check_box->isChecked ()) {
1700       // Append results text to file "fmt.all".
1701       QFile f {m_config.writeable_data_dir ().absoluteFilePath ("fmt.all")};
1702       if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) {
1703         QTextStream out(&f);
1704         out << t
1705 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
1706             << Qt::endl
1707 #else
1708             << endl
1709 #endif
1710           ;
1711         f.close();
1712       } else {
1713         MessageBox::warning_message (this, tr ("File Open Error")
1714                                      , tr ("Cannot open \"%1\" for append: %2")
1715                                      .arg (f.fileName ()).arg (f.errorString ()));
1716       }
1717     }
1718     if(m_ihsym==m_hsymStop && ui->actionFrequency_calibration->isChecked()) {
1719       freqCalStep();
1720     }
1721   }
1722 
1723   if(m_ihsym==3*m_hsymStop/4) {
1724     m_dialFreqRxWSPR=m_freqNominal;
1725   }
1726 
1727   if(m_mode=="FT8") {
1728     to_jt9(m_ihsym,-1,-1);     //Allow jt9 to bail out early, if necessary
1729     if(m_ihsym==40 and m_decoderBusy) {
1730       qDebug() << "Clearing hung decoder status";
1731       decodeDone();  //Clear a hung decoder status
1732     }
1733   }
1734 
1735   bool bCallDecoder=false;
1736   if(m_ihsym==m_hsymStop) bCallDecoder=true;
1737   if(m_mode=="FT8" and !m_diskData) {
1738     if(m_ihsym==m_earlyDecode) bCallDecoder=true;
1739     if(m_ihsym==m_earlyDecode2) bCallDecoder=true;
1740   }
1741   if(bCallDecoder) {
1742     if(m_mode=="Echo") {
1743       float snr=0;
1744       int nfrit=0;
1745       int nqual=0;
1746       float f1=1500.0;
1747       float xlevel=0.0;
1748       float sigdb=0.0;
1749       float dfreq=0.0;
1750       float width=0.0;
1751       echocom_.nclearave=m_nclearave;
1752       int nDop=0;
1753       avecho_(dec_data.d2,&nDop,&nfrit,&nqual,&f1,&xlevel,&sigdb,
1754           &snr,&dfreq,&width);
1755       QString t;
1756       t = t.asprintf("%3d %7.1f %7.1f %7.1f %7.1f %3d",echocom_.nsum,xlevel,sigdb,
1757                 dfreq,width,nqual);
1758       t=QDateTime::currentDateTimeUtc().toString("hh:mm:ss  ") + t;
1759       if (ui) ui->decodedTextBrowser->appendText(t);
1760       if(m_echoGraph->isVisible()) m_echoGraph->plotSpec();
1761       m_nclearave=0;
1762 //Don't restart Monitor after an Echo transmission
1763       if(m_bEchoTxed and !m_auto) {
1764         monitor(false);
1765         m_bEchoTxed=false;
1766       }
1767       return;
1768     }
1769     if(m_mode=="FreqCal") {
1770       return;
1771     }
1772     if( m_dialFreqRxWSPR==0) m_dialFreqRxWSPR=m_freqNominal;
1773     m_dataAvailable=true;
1774     dec_data.params.npts8=(m_ihsym*m_nsps)/16;
1775     dec_data.params.newdat=1;
1776     dec_data.params.nagain=0;
1777     dec_data.params.nzhsym=m_hsymStop;
1778     if(m_mode=="FT8" and m_ihsym==m_earlyDecode and !m_diskData) dec_data.params.nzhsym=m_earlyDecode;
1779     if(m_mode=="FT8" and m_ihsym==m_earlyDecode2 and !m_diskData) dec_data.params.nzhsym=m_earlyDecode2;
1780     QDateTime now {QDateTime::currentDateTimeUtc ()};
1781     m_dateTime = now.toString ("yyyy-MMM-dd hh:mm");
1782     if(m_mode!="WSPR") decode(); //Start decoder
1783 
1784     if(m_mode=="FT8" and !m_diskData and (m_ihsym==m_earlyDecode or m_ihsym==m_earlyDecode2)) return;
1785     if (!m_diskData)
1786       {
1787         Q_EMIT reset_audio_input_stream (true); // reports dropped samples
1788       }
1789     if(!m_diskData and (m_saveAll or m_saveDecoded or m_mode=="WSPR")) {
1790       //Always save unless "Save None"; may delete later
1791       if(m_TRperiod < 60) {
1792         int n=fmod(double(now.time().second()),m_TRperiod);
1793         if(n<(m_TRperiod/2)) n=n+m_TRperiod;
1794         auto const& period_start=now.addSecs(-n);
1795         m_fnameWE=m_config.save_directory().absoluteFilePath (period_start.toString("yyMMdd_hhmmss"));
1796       } else {
1797         auto const& period_start = now.addSecs (-(now.time ().minute () % (int(m_TRperiod) / 60)) * 60);
1798         m_fnameWE=m_config.save_directory ().absoluteFilePath (period_start.toString ("yyMMdd_hhmm"));
1799       }
1800       int samples=m_TRperiod*12000;
1801       if(m_mode=="FT4") samples=21*3456;
1802 
1803       // the following is potential a threading hazard - not a good
1804       // idea to pass pointer to be processed in another thread
1805       m_saveWAVWatcher.setFuture (QtConcurrent::run (std::bind (&MainWindow::save_wave_file,
1806             this, m_fnameWE, &dec_data.d2[0], samples, m_config.my_callsign(),
1807             m_config.my_grid(), m_mode, m_nSubMode, m_freqNominal, m_hisCall, m_hisGrid)));
1808       if (m_mode=="WSPR") {
1809         auto c2name {(m_fnameWE + ".c2").toLocal8Bit ()};
1810         int nsec=120;
1811         int nbfo=1500;
1812         double f0m1500=m_freqNominal/1000000.0 + nbfo - 1500;
1813         int err = savec2_(c2name.constData (),&nsec,&f0m1500, c2name.size ());
1814         if (err!=0) MessageBox::warning_message (this, tr ("Error saving c2 file"), c2name);
1815       }
1816     }
1817     if(m_mode=="WSPR") {
1818       QStringList t2;
1819       QStringList depth_args;
1820       t2 << "-f" << QString {"%1"}.arg (m_dialFreqRxWSPR / 1e6, 0, 'f', 6);
1821       if((m_ndepth&7)==1) depth_args << "-qB"; //2 pass w subtract, no Block detection, no shift jittering
1822       if((m_ndepth&7)==2) depth_args << "-C" << "500" << "-o" << "4"; //3 pass, subtract, Block detection, OSD
1823       if((m_ndepth&7)==3) depth_args << "-C" << "500"  << "-o" << "4" << "-d"; //3 pass, subtract, Block detect, OSD, more candidates
1824       QStringList degrade;
1825       degrade << "-d" << QString {"%1"}.arg (m_config.degrade(), 4, 'f', 1);
1826       m_cmndP1.clear ();
1827       if(m_diskData) {
1828         m_cmndP1 << depth_args << "-a"
1829                  << QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath()) << m_path;
1830       } else {
1831         m_cmndP1 << depth_args << "-a"
1832                  << QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath())
1833                  << t2 << m_fnameWE + ".wav";
1834       }
1835       if (ui) ui->DecodeButton->setChecked (true);
1836       p1Timer.start(1000);
1837       m_decoderBusy = true;
1838       statusUpdate ();
1839     }
1840     m_rxDone=true;
1841   }
1842 }
1843 
startP1()1844 void MainWindow::startP1()
1845 {
1846   p1.start (QDir::toNativeSeparators (QDir {QApplication::applicationDirPath ()}.absoluteFilePath ("wsprd")), m_cmndP1);
1847 }
1848 
save_wave_file(QString const & name,short const * data,int samples,QString const & my_callsign,QString const & my_grid,QString const & mode,qint32 sub_mode,Frequency frequency,QString const & his_call,QString const & his_grid) const1849 QString MainWindow::save_wave_file (QString const& name, short const * data, int samples,
1850         QString const& my_callsign, QString const& my_grid, QString const& mode, qint32 sub_mode,
1851         Frequency frequency, QString const& his_call, QString const& his_grid) const
1852 {
1853   //
1854   // This member function runs in a thread and should not access
1855   // members that may be changed in the GUI thread or any other thread
1856   // without suitable synchronization.
1857   //
1858   QAudioFormat format;
1859   format.setCodec ("audio/pcm");
1860   format.setSampleRate (12000);
1861   format.setChannelCount (1);
1862   format.setSampleSize (16);
1863   format.setSampleType (QAudioFormat::SignedInt);
1864   auto source = QString {"%1; %2"}.arg (my_callsign).arg (my_grid);
1865   auto comment = QString {"Mode=%1%2; Freq=%3%4"}
1866                    .arg (mode)
1867                    .arg (QString {(mode.contains ('J') && !mode.contains ('+'))
1868                          || mode.startsWith ("FST4") || mode.startsWith ('Q')
1869                          ? QString {"; Sub Mode="} + QString::number (int (samples / 12000)) + QChar {'A' + sub_mode}
1870                        : QString {}})
1871                    .arg (Radio::frequency_MHz_string (frequency))
1872                    .arg (QString {mode!="WSPR" ? QString {"; DXCall=%1; DXGrid=%2"}
1873          .arg (his_call)
1874          .arg (his_grid).toLocal8Bit () : ""});
1875   BWFFile::InfoDictionary list_info {
1876       {{{'I','S','R','C'}}, source.toLocal8Bit ()},
1877       {{{'I','S','F','T'}}, program_title (revision ()).simplified ().toLocal8Bit ()},
1878       {{{'I','C','R','D'}}, QDateTime::currentDateTimeUtc ()
1879                           .toString ("yyyy-MM-ddTHH:mm:ss.zzzZ").toLocal8Bit ()},
1880       {{{'I','C','M','T'}}, comment.toLocal8Bit ()},
1881         };
1882   auto file_name = name + ".wav";
1883   BWFFile wav {format, file_name, list_info};
1884   if (!wav.open (BWFFile::WriteOnly)
1885       || 0 > wav.write (reinterpret_cast<char const *> (data)
1886                         , sizeof (short) * samples))
1887     {
1888       return file_name + ": " + wav.errorString ();
1889     }
1890   return QString {};
1891 }
1892 
1893 //-------------------------------------------------------------- fastSink()
fastSink(qint64 frames)1894 void MainWindow::fastSink(qint64 frames)
1895 {
1896   int k (frames);
1897   bool decodeNow=false;
1898   if(k < m_k0) {                                 //New sequence ?
1899     memcpy(fast_green2,fast_green,4*703);        //Copy fast_green[] to fast_green2[]
1900     memcpy(fast_s2,fast_s,4*703*64);             //Copy fast_s[] into fast_s2[]
1901     fast_jh2=fast_jh;
1902     if(!m_diskData) memset(dec_data.d2,0,2*30*12000);   //Zero the d2[] array
1903     m_bFastDecodeCalled=false;
1904     m_bDecoded=false;
1905   }
1906 
1907   QDateTime tnow=QDateTime::currentDateTimeUtc();
1908   int ihr=tnow.toString("hh").toInt();
1909   int imin=tnow.toString("mm").toInt();
1910   int isec=tnow.toString("ss").toInt();
1911   isec=isec - fmod(double(isec),m_TRperiod);
1912   int nutc0=10000*ihr + 100*imin + isec;
1913   if(m_diskData) nutc0=m_UTCdisk;
1914   char line[80];
1915   bool bmsk144=((m_mode=="MSK144") and (m_monitoring or m_diskData));
1916   line[0]=0;
1917 
1918   int RxFreq=ui->RxFreqSpinBox->value ();
1919   int nTRpDepth=m_TRperiod + 1000*(m_ndepth & 3);
1920   qint64 ms0 = QDateTime::currentMSecsSinceEpoch();
1921 //  ::memcpy(dec_data.params.mycall, (m_baseCall+"            ").toLatin1(),sizeof dec_data.params.mycall);
1922   ::memcpy(dec_data.params.mycall,(m_config.my_callsign () + "            ").toLatin1(),sizeof dec_data.params.mycall);
1923   QString hisCall {ui->dxCallEntry->text ()};
1924   bool bshmsg=ui->cbShMsgs->isChecked();
1925   bool bswl=ui->cbSWL->isChecked();
1926 //  ::memcpy(dec_data.params.hiscall,(Radio::base_callsign (hisCall) +  "            ").toLatin1 ().constData (), sizeof dec_data.params.hiscall);
1927   ::memcpy(dec_data.params.hiscall,(hisCall + "            ").toLatin1 ().constData (), sizeof dec_data.params.hiscall);
1928   ::memcpy(dec_data.params.mygrid, (m_config.my_grid()+"      ").toLatin1(), sizeof dec_data.params.mygrid);
1929   auto data_dir {m_config.writeable_data_dir ().absolutePath ().toLocal8Bit ()};
1930   float pxmax = 0;
1931   float rmsNoGain = 0;
1932   int ftol = ui->sbFtol->value ();
1933   hspec_(dec_data.d2,&k,&nutc0,&nTRpDepth,&RxFreq,&ftol,&bmsk144,
1934          &m_bTrain,m_phaseEqCoefficients.constData(),&m_inGain,&dec_data.params.mycall[0],
1935          &dec_data.params.hiscall[0],&bshmsg,&bswl,
1936          data_dir.constData (),fast_green,fast_s,&fast_jh,&pxmax,&rmsNoGain,&line[0],12,12,data_dir.size (),80);
1937   float px = fast_green[fast_jh];
1938   QString t;
1939   t = t.asprintf(" Rx noise: %5.1f ",px);
1940   ui->signal_meter_widget->setValue(rmsNoGain,pxmax); // Update thermometer
1941   m_fastGraph->plotSpec(m_diskData,m_UTCdisk);
1942 
1943   if(bmsk144 and (line[0]!=0)) {
1944     QString message {QString::fromLatin1 (line)};
1945     DecodedText decodedtext {message.replace (QChar::LineFeed, "")};
1946     ui->decodedTextBrowser->displayDecodedText (decodedtext, m_config.my_callsign (), m_mode, m_config.DXCC(),
1947          m_logBook, m_currentBand, m_config.ppfx ());
1948     m_bDecoded=true;
1949     auto_sequence (decodedtext, ui->sbFtol->value (), std::numeric_limits<unsigned>::max ());
1950     postDecode (true, decodedtext.string ());
1951 //    writeAllTxt(message);
1952     write_all("Rx",message);
1953     bool stdMsg = decodedtext.report(m_baseCall,
1954                   Radio::base_callsign(ui->dxCallEntry->text()),m_rptRcvd);
1955     if (stdMsg) pskPost (decodedtext);
1956   }
1957 
1958   float fracTR=float(k)/(12000.0*m_TRperiod);
1959   decodeNow=false;
1960   if(fracTR>0.92) {
1961     m_dataAvailable=true;
1962     fast_decode_done();
1963     m_bFastDone=true;
1964   }
1965 
1966   m_k0=k;
1967   if(m_diskData and m_k0 >= dec_data.params.kin - 7 * 512) decodeNow=true;
1968   if(!m_diskData and m_tRemaining<0.35 and !m_bFastDecodeCalled) decodeNow=true;
1969   if(m_mode=="MSK144") decodeNow=false;
1970 
1971   if(decodeNow) {
1972     m_dataAvailable=true;
1973     m_t0=0.0;
1974     m_t1=k/12000.0;
1975     m_kdone=k;
1976     dec_data.params.newdat=1;
1977     if(!m_decoderBusy) {
1978       m_bFastDecodeCalled=true;
1979       decode();
1980     }
1981   }
1982 
1983   if(decodeNow or m_bFastDone) {
1984     if(!m_diskData and (m_saveAll or m_saveDecoded)) {
1985       QDateTime now {QDateTime::currentDateTimeUtc()};
1986       int n=fmod(double(now.time().second()),m_TRperiod);
1987       if(n<(m_TRperiod/2)) n=n+m_TRperiod;
1988       auto const& period_start = now.addSecs (-n);
1989       m_fnameWE = m_config.save_directory ().absoluteFilePath (period_start.toString ("yyMMdd_hhmmss"));
1990       if(m_saveAll or m_bAltV or (m_bDecoded and m_saveDecoded) or (m_mode!="MSK144")) {
1991         m_bAltV=false;
1992         // the following is potential a threading hazard - not a good
1993         // idea to pass pointer to be processed in another thread
1994         m_saveWAVWatcher.setFuture (QtConcurrent::run (std::bind (&MainWindow::save_wave_file,
1995            this, m_fnameWE, &dec_data.d2[0], int(m_TRperiod*12000.0), m_config.my_callsign(),
1996            m_config.my_grid(), m_mode, m_nSubMode, m_freqNominal, m_hisCall, m_hisGrid)));
1997       }
1998       if(m_mode!="MSK144") {
1999         killFileTimer.start (int(750.0*m_TRperiod)); //Kill 3/4 period from now
2000       }
2001     }
2002     m_bFastDone=false;
2003   }
2004   float tsec=0.001*(QDateTime::currentMSecsSinceEpoch() - ms0);
2005   m_fCPUmskrtd=0.9*m_fCPUmskrtd + 0.1*tsec;
2006 }
2007 
showSoundInError(const QString & errorMsg)2008 void MainWindow::showSoundInError(const QString& errorMsg)
2009 {
2010   if (m_splash && m_splash->isVisible ()) m_splash->hide ();
2011   MessageBox::critical_message (this, tr ("Error in Sound Input"), errorMsg);
2012 }
2013 
showSoundOutError(const QString & errorMsg)2014 void MainWindow::showSoundOutError(const QString& errorMsg)
2015 {
2016   if (m_splash && m_splash->isVisible ()) m_splash->hide ();
2017   MessageBox::critical_message (this, tr ("Error in Sound Output"), errorMsg);
2018 }
2019 
showStatusMessage(const QString & statusMsg)2020 void MainWindow::showStatusMessage(const QString& statusMsg)
2021 {
2022   statusBar()->showMessage(statusMsg, 5000);
2023 }
2024 
on_actionSettings_triggered()2025 void MainWindow::on_actionSettings_triggered()               //Setup Dialog
2026 {
2027   // things that might change that we need know about
2028   auto callsign = m_config.my_callsign ();
2029   auto my_grid = m_config.my_grid ();
2030   SpecOp nContest0=m_config.special_op_id();
2031   auto psk_on = m_config.spot_to_psk_reporter ();
2032   if (QDialog::Accepted == m_config.exec ()) {
2033     checkMSK144ContestType();
2034     if (m_config.my_callsign () != callsign) {
2035       m_baseCall = Radio::base_callsign (m_config.my_callsign ());
2036       ui->tx1->setEnabled (elide_tx1_not_allowed () || ui->tx1->isEnabled ());
2037       morse_(const_cast<char *> (m_config.my_callsign ().toLatin1().constData()),
2038              const_cast<int *> (icw), &m_ncw, m_config.my_callsign ().length());
2039     }
2040     if (m_config.my_callsign () != callsign || m_config.my_grid () != my_grid) {
2041       statusUpdate ();
2042     }
2043     on_dxGridEntry_textChanged (m_hisGrid); // recalculate distances in case of units change
2044     enable_DXCC_entity (m_config.DXCC ());  // sets text window proportions and (re)inits the logbook
2045 
2046     pskSetLocal ();
2047     // this will close the connection to PSKReporter if it has been
2048     // disabled
2049     if (psk_on && !m_config.spot_to_psk_reporter ())
2050       {
2051         m_psk_Reporter.sendReport (true);
2052       }
2053 
2054     if(m_config.restart_audio_input () && !m_config.audio_input_device ().isNull ()) {
2055       Q_EMIT startAudioInputStream (m_config.audio_input_device ()
2056                                     , m_rx_audio_buffer_frames
2057                                     , m_detector, m_downSampleFactor
2058                                     , m_config.audio_input_channel ());
2059     }
2060 
2061     if(m_config.restart_audio_output () && !m_config.audio_output_device ().isNull ()) {
2062       Q_EMIT initializeAudioOutputStream (m_config.audio_output_device ()
2063                                           , AudioDevice::Mono == m_config.audio_output_channel () ? 1 : 2
2064                                           , m_tx_audio_buffer_frames);
2065     }
2066 
2067     displayDialFrequency ();
2068     bool vhf {m_config.enable_VHF_features()};
2069     m_wideGraph->setVHF(vhf);
2070     if (!vhf) ui->sbSubmode->setValue (0);
2071 
2072     setup_status_bar (vhf);
2073     bool b = vhf && (m_mode=="JT4" or m_mode=="JT65" or
2074                      m_mode=="JT9" or m_mode=="MSK144" or m_mode=="Q65");
2075     if(b) VHF_features_enabled(b);
2076     set_mode (m_mode);
2077     if(b) VHF_features_enabled(b);
2078 
2079     m_config.transceiver_online ();
2080     if(!m_bFastMode) setXIT (ui->TxFreqSpinBox->value ());
2081     if ((m_config.single_decode () && !m_mode.startsWith ("FST4")) || m_mode=="JT4") {
2082       ui->lh_decodes_title_label->setText(tr ("Single-Period Decodes"));
2083       ui->rh_decodes_title_label->setText(tr ("Average Decodes"));
2084     }
2085 
2086     update_watchdog_label ();
2087     if(!m_splitMode) ui->cbCQTx->setChecked(false);
2088     if(!m_config.enable_VHF_features()) {
2089       ui->actionInclude_averaging->setVisible(false);
2090       ui->actionInclude_correlation->setVisible (false);
2091       ui->actionInclude_averaging->setChecked(false);
2092       ui->actionInclude_correlation->setChecked(false);
2093       ui->actionEnable_AP_JT65->setVisible(false);
2094       ui->actionAuto_Clear_Avg->setVisible(false);
2095     }
2096     if(m_config.special_op_id()!=nContest0) {
2097       ui->tx1->setEnabled(true);
2098       ui->txb1->setEnabled(true);
2099     }
2100     chkFT4();
2101     if(SpecOp::EU_VHF==m_config.special_op_id() and m_config.my_grid().size()<6) {
2102       MessageBox::information_message (this,
2103           "EU VHF Contest messages require a 6-character locator.");
2104     }
2105     if((m_config.special_op_id()==SpecOp::FOX or m_config.special_op_id()==SpecOp::HOUND) and
2106        m_mode!="FT8") {
2107       MessageBox::information_message (this,
2108           "Fox-and-Hound operation is available only in FT8 mode.\nGo back and change your selection.");
2109     }
2110   }
2111 
2112   //Z
2113   ui->pb_WDReset->setVisible(!m_config.wdResetAnywhere());
2114 }
2115 
on_monitorButton_clicked(bool checked)2116 void MainWindow::on_monitorButton_clicked (bool checked)
2117 {
2118   if (!m_transmitting) {
2119     auto prior = m_monitoring;
2120     monitor (checked);
2121     if (checked && !prior) {
2122       if (m_config.monitor_last_used ()) {
2123               // put rig back where it was when last in control
2124         setRig (m_lastMonitoredFrequency);
2125         setXIT (ui->TxFreqSpinBox->value ());
2126       }
2127           // ensure FreqCal triggers
2128       if(m_mode=="FST4W") {
2129         on_sbFST4W_RxFreq_valueChanged(ui->sbFST4W_RxFreq->value());
2130       } else {
2131         on_RxFreqSpinBox_valueChanged (ui->RxFreqSpinBox->value ());
2132       }
2133     }
2134       //Get Configuration in/out of strict split and mode checking
2135     m_config.sync_transceiver (true, checked);
2136   } else {
2137     ui->monitorButton->setChecked (false); // disallow
2138   }
2139 }
2140 
monitor(bool state)2141 void MainWindow::monitor (bool state)
2142 {
2143   ui->monitorButton->setChecked (state);
2144   if (state) {
2145     m_diskData = false; // no longer reading WAV files
2146     if (!m_monitoring) Q_EMIT resumeAudioInputStream ();
2147   } else {
2148     Q_EMIT suspendAudioInputStream ();
2149   }
2150   m_monitoring = state;
2151 }
2152 
on_actionAbout_triggered()2153 void MainWindow::on_actionAbout_triggered()                  //Display "About"
2154 {
2155   CAboutDlg {this}.exec ();
2156 }
2157 
on_autoButton_clicked(bool checked)2158 void MainWindow::on_autoButton_clicked (bool checked)
2159 {
2160   // Z
2161   if (checked) tx_watchdog(false);
2162   m_auto = checked;
2163   if (checked
2164       && ui->cbFirst->isVisible () && ui->cbFirst->isChecked()
2165       && CALLING == m_QSOProgress) {
2166     m_bAutoReply = false;         // ready for next
2167     m_bCallingCQ = true;        // allows tail-enders to be picked up
2168     ui->cbFirst->setStyleSheet ("QCheckBox{color:red}");
2169   } else {
2170     ui->cbFirst->setStyleSheet("");
2171   }
2172   if (!checked) m_bCallingCQ = false;
2173   statusUpdate ();
2174   m_bEchoTxOK=false;
2175   if(m_auto and (m_mode=="Echo")) {
2176     m_nclearave=1;
2177     echocom_.nsum=0;
2178   }
2179   m_tAutoOn=QDateTime::currentMSecsSinceEpoch()/1000;
2180 }
2181 
on_sbTxPercent_valueChanged(int n)2182 void MainWindow::on_sbTxPercent_valueChanged (int n)
2183 {
2184   update_dynamic_property (ui->sbTxPercent, "notx", !n);
2185 }
2186 
auto_tx_mode(bool state)2187 void MainWindow::auto_tx_mode (bool state)
2188 {
2189   // Z
2190   log("AutoTxMode: " + QString::number(state));
2191   if (state) tx_watchdog(false);
2192 
2193   if (!state && ui->cbAutoCQ->isChecked()) return;
2194 
2195   ui->autoButton->setChecked (state);
2196   on_autoButton_clicked (state);
2197 }
2198 
keyPressEvent(QKeyEvent * e)2199 void MainWindow::keyPressEvent (QKeyEvent * e)
2200 {
2201 
2202   if(SpecOp::FOX == m_config.special_op_id()) {
2203     switch (e->key()) {
2204       case Qt::Key_Return:
2205         doubleClickOnCall2(Qt::KeyboardModifier(Qt::ShiftModifier + Qt::ControlModifier + Qt::AltModifier));
2206         return;
2207       case Qt::Key_Enter:
2208         doubleClickOnCall2(Qt::KeyboardModifier(Qt::ShiftModifier + Qt::ControlModifier + Qt::AltModifier));
2209         return;
2210       case Qt::Key_Backspace:
2211         qDebug() << "Key Backspace";
2212         return;
2213     }
2214     QMainWindow::keyPressEvent (e);
2215   }
2216 
2217   if(SpecOp::HOUND == m_config.special_op_id()) {
2218     switch (e->key()) {
2219       case Qt::Key_Return:
2220         auto_tx_mode(true);
2221         return;
2222       case Qt::Key_Enter:
2223         auto_tx_mode(true);
2224         return;
2225     }
2226     QMainWindow::keyPressEvent (e);
2227   }
2228 
2229   int n;
2230   bool bAltF1F6=m_config.alternate_bindings();
2231   switch(e->key())
2232     {
2233   case Qt::Key_B:
2234     if(m_mode=="FT4" && e->modifiers() & Qt::AltModifier) {
2235       on_pbBestSP_clicked();
2236     }
2237   return;
2238     case Qt::Key_C:
2239       if(m_mode=="FT4" && e->modifiers() & Qt::AltModifier) {
2240         bool b=ui->cbFirst->isChecked();
2241         ui->cbFirst->setChecked(!b);
2242       }
2243     return;
2244     case Qt::Key_D:
2245       if(m_mode != "WSPR" && e->modifiers() & Qt::ShiftModifier) {
2246         if(!m_decoderBusy) {
2247           dec_data.params.newdat=0;
2248           dec_data.params.nagain=0;
2249           decode();
2250           return;
2251         }
2252       }
2253       break;
2254     case Qt::Key_F1:
2255       if(bAltF1F6) {
2256         auto_tx_mode(true);
2257         on_txb6_clicked();
2258         return;
2259       } else {
2260         on_actionOnline_User_Guide_triggered();
2261         return;
2262       }
2263     case Qt::Key_F2:
2264       if(bAltF1F6) {
2265         auto_tx_mode(true);
2266         on_txb2_clicked();
2267         return;
2268       } else {
2269         on_actionSettings_triggered();
2270         return;
2271       }
2272     case Qt::Key_F3:
2273       if(bAltF1F6) {
2274         auto_tx_mode(true);
2275         on_txb3_clicked();
2276         return;
2277       } else {
2278         on_actionKeyboard_shortcuts_triggered();
2279         return;
2280       }
2281     case Qt::Key_F4:
2282       if(bAltF1F6) {
2283         auto_tx_mode(true);
2284         on_txb4_clicked();
2285         return;
2286       } else {
2287         clearDX ();
2288         ui->dxCallEntry->setFocus();
2289         return;
2290       }
2291     case Qt::Key_F5:
2292       if(bAltF1F6) {
2293         auto_tx_mode(true);
2294         on_txb5_clicked();
2295         return;
2296       } else {
2297         on_actionSpecial_mouse_commands_triggered();
2298         return;
2299       }
2300     case Qt::Key_F6:
2301       if(bAltF1F6) {
2302         bool b=ui->cbFirst->isChecked();
2303         ui->cbFirst->setChecked(!b);
2304       } else {
2305         if(e->modifiers() & Qt::ShiftModifier) {
2306           on_actionDecode_remaining_files_in_directory_triggered();
2307         } else {
2308           on_actionOpen_next_in_directory_triggered();
2309         }
2310       }
2311       return;
2312     case Qt::Key_F11:
2313       if((e->modifiers() & Qt::ControlModifier) and (e->modifiers() & Qt::ShiftModifier)) {
2314         m_bandEdited = true;
2315         band_changed(m_freqNominal-2000);
2316       } else {
2317         n=11;
2318         if(e->modifiers() & Qt::ControlModifier) n+=100;
2319         if(e->modifiers() & Qt::ShiftModifier) {
2320           int offset=60;
2321           if(m_mode=="FT4") offset=90;
2322           ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()-offset);
2323         } else{
2324           bumpFqso(n);
2325         }
2326       }
2327       return;
2328     case Qt::Key_F12:
2329       if((e->modifiers() & Qt::ControlModifier) and (e->modifiers() & Qt::ShiftModifier)) {
2330         m_bandEdited = true;
2331         band_changed(m_freqNominal+2000);
2332       } else {
2333         n=12;
2334         if(e->modifiers() & Qt::ControlModifier) n+=100;
2335         if(e->modifiers() & Qt::ShiftModifier) {
2336           int offset=60;
2337           if(m_mode=="FT4") offset=90;
2338           ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()+offset);
2339         } else {
2340           bumpFqso(n);
2341         }
2342       }
2343       return;
2344     case Qt::Key_Escape:
2345       m_nextCall="";
2346       on_stopTxButton_clicked();
2347       abortQSO();
2348       return;
2349     case Qt::Key_E:
2350       if((e->modifiers() & Qt::ShiftModifier) and SpecOp::FOX > m_config.special_op_id()) {
2351           ui->txFirstCheckBox->setChecked(false);
2352           return;
2353       }
2354       else if((e->modifiers() & Qt::ControlModifier) and SpecOp::FOX > m_config.special_op_id()) {
2355           ui->txFirstCheckBox->setChecked(true);
2356           return;
2357       }
2358       break;
2359     case Qt::Key_F:
2360       if(e->modifiers() & Qt::ControlModifier) {
2361         if(ui->tabWidget->currentIndex()==0) {
2362           ui->tx5->clearEditText();
2363           ui->tx5->setFocus();
2364         }
2365         return;
2366       }
2367       break;
2368     case Qt::Key_G:
2369       if(e->modifiers() & Qt::AltModifier) {
2370         genStdMsgs (m_rpt, true);
2371         return;
2372       }
2373       break;
2374     case Qt::Key_H:
2375       if(e->modifiers() & Qt::AltModifier) {
2376         on_stopTxButton_clicked();
2377         return;
2378       }
2379       break;
2380     case Qt::Key_L:
2381       if(e->modifiers() & Qt::ControlModifier) {
2382         lookup();
2383         genStdMsgs(m_rpt);
2384         return;
2385       }
2386       break;
2387     case Qt::Key_O:
2388       if(e->modifiers() & Qt::ControlModifier) {
2389           on_actionOpen_triggered();
2390           return;
2391       }
2392       else if(e->modifiers() & Qt::AltModifier) {
2393         bool ok;
2394         auto call = QInputDialog::getText (this, tr ("Change Operator"), tr ("New operator:"),
2395                                            QLineEdit::Normal, m_config.opCall (), &ok);
2396         if (ok) {
2397           m_config.opCall (call);
2398         }
2399         return;
2400       }
2401       break;
2402   case Qt::Key_R:
2403     if(e->modifiers() & Qt::AltModifier) {
2404       if(!m_send_RR73) on_txrb4_doubleClicked();
2405     return;
2406     }
2407     if(e->modifiers() & Qt::ControlModifier) {
2408       if(m_send_RR73) on_txrb4_doubleClicked();
2409       return;
2410     }
2411     break;
2412   case Qt::Key_X:
2413     if(e->modifiers() & Qt::AltModifier) {
2414 //      qDebug() << "Alt-X" << m_mode << m_TRperiod << m_nsps << m_bFast9
2415 //               << tx_duration(m_mode,m_TRperiod,m_nsps,m_bFast9);
2416       return;
2417     }
2418   }
2419 
2420   QMainWindow::keyPressEvent (e);
2421 }
2422 
bumpFqso(int n)2423 void MainWindow::bumpFqso(int n)                                 //bumpFqso()
2424 {
2425   int i;
2426   bool ctrl = (n>=100);
2427   n=n%100;
2428   i=ui->RxFreqSpinBox->value();
2429   bool bTrackTx=ui->TxFreqSpinBox->value() == i;
2430   if(n==11) i--;
2431   if(n==12) i++;
2432   if (ui->RxFreqSpinBox->isEnabled ()) {
2433     ui->RxFreqSpinBox->setValue (i);
2434   }
2435   if(ctrl and m_mode=="WSPR") {
2436     ui->WSPRfreqSpinBox->setValue(i);
2437   } else {
2438     if(ctrl and bTrackTx) {
2439       ui->TxFreqSpinBox->setValue (i);
2440     }
2441   }
2442 }
2443 
displayDialFrequency()2444 void MainWindow::displayDialFrequency ()
2445 {
2446   Frequency dial_frequency {m_rigState.ptt () && m_rigState.split () ?
2447       m_rigState.tx_frequency () : m_rigState.frequency ()};
2448 
2449   // lookup band
2450   auto const& band_name = m_config.bands ()->find (dial_frequency);
2451   if (m_lastBand != band_name)
2452     {
2453       // only change this when necessary as we get called a lot and it
2454       // would trash any user input to the band combo box line edit
2455       ui->bandComboBox->setCurrentText (band_name.size () ? band_name : m_config.bands ()->oob ());
2456       m_wideGraph->setRxBand (band_name);
2457       m_lastBand = band_name;
2458       band_changed(dial_frequency);
2459     }
2460 
2461   // search working frequencies for one we are within 10kHz of (1 Mhz
2462   // of on VHF and up)
2463   bool valid {false};
2464   quint64 min_offset {99999999};
2465   for (auto const& item : *m_config.frequencies ())
2466     {
2467       // we need to do specific checks for above and below here to
2468       // ensure that we can use unsigned Radio::Frequency since we
2469       // potentially use the full 64-bit unsigned range.
2470       auto const& working_frequency = item.frequency_;
2471       auto const& offset = dial_frequency > working_frequency ?
2472         dial_frequency - working_frequency :
2473         working_frequency - dial_frequency;
2474       if (offset < min_offset) {
2475         min_offset = offset;
2476       }
2477     }
2478   if (min_offset < 10000u || (m_config.enable_VHF_features() && min_offset < 1000000u)) {
2479     valid = true;
2480   }
2481 
2482   update_dynamic_property (ui->labDialFreq, "oob", !valid);
2483   ui->labDialFreq->setText (Radio::pretty_frequency_MHz_string (dial_frequency));
2484 }
2485 
statusChanged()2486 void MainWindow::statusChanged()
2487 {
2488   statusUpdate ();
2489   QFile f {m_config.temp_dir ().absoluteFilePath ("wsjtx_status.txt")};
2490   if(f.open(QFile::WriteOnly | QIODevice::Text)) {
2491     QTextStream out(&f);
2492     QString tmpGrid = m_hisGrid;
2493     if (!tmpGrid.size ()) tmpGrid="n/a"; // Not Available
2494     out << qSetRealNumberPrecision (12) << (m_freqNominal / 1.e6)
2495         << ";" << m_mode << ";" << m_hisCall << ";"
2496         << ui->rptSpinBox->value() << ";" << m_mode << ";" << tmpGrid
2497 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
2498         << Qt::endl
2499 #else
2500         << endl
2501 #endif
2502       ;
2503     f.close();
2504   } else {
2505     if (m_splash && m_splash->isVisible ()) m_splash->hide ();
2506     MessageBox::warning_message (this, tr ("Status File Error")
2507                                  , tr ("Cannot open \"%1\" for writing: %2")
2508                                  .arg (f.fileName ()).arg (f.errorString ()));
2509   }
2510   on_dxGridEntry_textChanged(m_hisGrid);
2511 }
2512 
eventFilter(QObject * object,QEvent * event)2513 bool MainWindow::eventFilter (QObject * object, QEvent * event)
2514 {
2515   switch (event->type())
2516     {
2517     case QEvent::KeyPress:
2518       // fall through
2519     case QEvent::MouseButtonPress:
2520       // reset the Tx watchdog
2521       // Z
2522       if (m_config.wdResetAnywhere())
2523       tx_watchdog (false);
2524       break;
2525 
2526     case QEvent::ChildAdded:
2527       // ensure our child widgets get added to our event filter
2528       add_child_to_event_filter (static_cast<QChildEvent *> (event)->child ());
2529       break;
2530 
2531     case QEvent::ChildRemoved:
2532       // ensure our child widgets get d=removed from our event filter
2533       remove_child_from_event_filter (static_cast<QChildEvent *> (event)->child ());
2534       break;
2535 
2536     default: break;
2537     }
2538   return QObject::eventFilter(object, event);
2539 }
2540 
createStatusBar()2541 void MainWindow::createStatusBar()                           //createStatusBar
2542 {
2543   tx_status_label.setAlignment (Qt::AlignHCenter);
2544   tx_status_label.setMinimumSize (QSize  {100, 18});
2545   tx_status_label.setStyleSheet ("QLabel{color: #000000; background-color: #00aa00}");
2546   tx_status_label.setFrameStyle (QFrame::Panel | QFrame::Sunken);
2547   statusBar()->addWidget (&tx_status_label);
2548 
2549   config_label.setAlignment (Qt::AlignHCenter);
2550   config_label.setMinimumSize (QSize {80, 18});
2551   config_label.setFrameStyle (QFrame::Panel | QFrame::Sunken);
2552   statusBar()->addWidget (&config_label);
2553   config_label.hide ();         // only shown for non-default configuration
2554 
2555   mode_label.setAlignment (Qt::AlignHCenter);
2556   mode_label.setMinimumSize (QSize {80, 18});
2557   mode_label.setFrameStyle (QFrame::Panel | QFrame::Sunken);
2558   statusBar()->addWidget (&mode_label);
2559 
2560   last_tx_label.setAlignment (Qt::AlignHCenter);
2561   last_tx_label.setMinimumSize (QSize {150, 18});
2562   last_tx_label.setFrameStyle (QFrame::Panel | QFrame::Sunken);
2563   statusBar()->addWidget (&last_tx_label);
2564 
2565   ndecodes_label.setAlignment (Qt::AlignHCenter);
2566   ndecodes_label.setMinimumSize (QSize {30, 18});
2567   ndecodes_label.setFrameStyle (QFrame::Panel | QFrame::Sunken);
2568   statusBar()->addWidget (&ndecodes_label);
2569 
2570   // Z
2571   labDate.setAlignment (Qt::AlignHCenter);
2572   labDate.setMinimumSize (QSize {30, 18});
2573   labDate.setFrameStyle (QFrame::Panel | QFrame::Sunken);
2574   statusBar()->addWidget (&labDate);
2575 
2576   qso_count.setAlignment (Qt::AlignHCenter);
2577   qso_count.setMinimumSize (QSize {30, 18});
2578   qso_count.setFrameStyle (QFrame::Panel | QFrame::Sunken);
2579   statusBar()->addWidget (&qso_count);
2580 
2581   labAz.setAlignment (Qt::AlignHCenter);
2582   labAz.setMinimumSize (QSize {30, 18});
2583   labAz.setFrameStyle (QFrame::Panel | QFrame::Sunken);
2584   statusBar()->addWidget (&labAz);
2585 
2586   band_hopping_label.setAlignment (Qt::AlignHCenter);
2587   band_hopping_label.setMinimumSize (QSize {90, 18});
2588   band_hopping_label.setFrameStyle (QFrame::Panel | QFrame::Sunken);
2589 
2590   statusBar()->addPermanentWidget(&progressBar);
2591   progressBar.setMinimumSize (QSize {150, 18});
2592   // Z
2593   progressBar.setAlignment(Qt::AlignCenter);
2594 
2595 
2596   statusBar ()->addPermanentWidget (&watchdog_label);
2597   update_watchdog_label ();
2598 }
2599 
setup_status_bar(bool vhf)2600 void MainWindow::setup_status_bar (bool vhf)
2601 {
2602   auto submode = current_submode ();
2603   if (vhf && submode != QChar::Null) {
2604     QString t{m_mode + " " + submode};
2605     if(m_mode=="Q65") t=m_mode + "-" + QString::number(m_TRperiod) + submode;
2606     mode_label.setText (t);
2607   } else {
2608     mode_label.setText (m_mode);
2609   }
2610   if ("JT9" == m_mode) {
2611     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff6ec7}");
2612   } else if ("JT4" == m_mode) {
2613     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #cc99ff}");
2614   } else if ("Echo" == m_mode) {
2615     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #66ffff}");
2616   } else if ("JT65" == m_mode) {
2617     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #66ff66}");
2618   } else if ("Q65" == m_mode) {
2619     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #99ff33}");
2620   } else if ("MSK144" == m_mode) {
2621     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff6666}");
2622   } else if ("FT4" == m_mode) {
2623     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff0099}");
2624   } else if ("FT8" == m_mode) {
2625     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff6699}");
2626   } else if ("FST4" == m_mode) {
2627     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #99ff66}");
2628   } else if ("FST4W" == m_mode) {
2629     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #6699ff}");
2630   } else if ("FreqCal" == m_mode) {
2631     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff9933}");
2632   }
2633   last_tx_label.setText (QString {});
2634   if (m_mode.contains (QRegularExpression {R"(^(Echo))"})) {
2635     if (band_hopping_label.isVisible ()) statusBar ()->removeWidget (&band_hopping_label);
2636   } else if (m_mode=="WSPR") {
2637     mode_label.setStyleSheet ("QLabel{color: #000000; background-color: #ff66ff}");
2638     if (!band_hopping_label.isVisible ()) {
2639       statusBar ()->addWidget (&band_hopping_label);
2640       band_hopping_label.show ();
2641     }
2642   } else {
2643     if (band_hopping_label.isVisible ()) statusBar ()->removeWidget (&band_hopping_label);
2644   }
2645 }
2646 
subProcessFailed(QProcess * process,int exit_code,QProcess::ExitStatus status)2647 bool MainWindow::subProcessFailed (QProcess * process, int exit_code, QProcess::ExitStatus status)
2648 {
2649   if (m_valid && (exit_code || QProcess::NormalExit != status))
2650     {
2651       QStringList arguments;
2652       for (auto argument: process->arguments ())
2653         {
2654           if (argument.contains (' ')) argument = '"' + argument + '"';
2655           arguments << argument;
2656         }
2657       if (m_splash && m_splash->isVisible ()) m_splash->hide ();
2658       MessageBox::critical_message (this, tr ("Subprocess Error")
2659                                     , tr ("Subprocess failed with exit code %1")
2660                                     .arg (exit_code)
2661                                     , tr ("Running: %1\n%2")
2662                                     .arg (process->program () + ' ' + arguments.join (' '))
2663                                     .arg (QString {process->readAllStandardError()}));
2664       return true;
2665     }
2666   return false;
2667 }
2668 
subProcessError(QProcess * process,QProcess::ProcessError)2669 void MainWindow::subProcessError (QProcess * process, QProcess::ProcessError)
2670 {
2671   if (m_valid)
2672     {
2673       QStringList arguments;
2674       for (auto argument: process->arguments ())
2675         {
2676           if (argument.contains (' ')) argument = '"' + argument + '"';
2677           arguments << argument;
2678         }
2679       if (m_splash && m_splash->isVisible ()) m_splash->hide ();
2680       MessageBox::critical_message (this, tr ("Subprocess error")
2681                                     , tr ("Running: %1\n%2")
2682                                     .arg (process->program () + ' ' + arguments.join (' '))
2683                                     .arg (process->errorString ()));
2684       m_valid = false;              // ensures exit if still constructing
2685       QTimer::singleShot (0, this, SLOT (close ()));
2686     }
2687 }
2688 
closeEvent(QCloseEvent * e)2689 void MainWindow::closeEvent(QCloseEvent * e)
2690 {
2691   m_valid = false;              // suppresses subprocess errors
2692   m_config.transceiver_offline ();
2693   writeSettings ();
2694   m_astroWidget.reset ();
2695   m_guiTimer.stop ();
2696   m_prefixes.reset ();
2697   m_shortcuts.reset ();
2698   m_mouseCmnds.reset ();
2699   m_colorHighlighting.reset ();
2700   if(m_mode!="MSK144" and m_mode!="FT8") killFile();
2701   float sw=0.0;
2702   int nw=400;
2703   int nh=100;
2704   int irow=-99;
2705   plotsave_(&sw,&nw,&nh,&irow);
2706   to_jt9(m_ihsym,999,-1);          //Tell jt9 to terminate
2707   if (!proc_jt9.waitForFinished(1000)) proc_jt9.close();
2708   mem_jt9->detach();
2709   Q_EMIT finished ();
2710   QMainWindow::closeEvent (e);
2711 }
2712 
on_stopButton_clicked()2713 void MainWindow::on_stopButton_clicked()                       //stopButton
2714 {
2715   monitor (false);
2716   m_loopall=false;
2717   if(m_bRefSpec) {
2718     MessageBox::information_message (this, tr ("Reference spectrum saved"));
2719     m_bRefSpec=false;
2720   }
2721 }
2722 
on_actionRelease_Notes_triggered()2723 void MainWindow::on_actionRelease_Notes_triggered ()
2724 {
2725   QDesktopServices::openUrl (QUrl {"http://physics.princeton.edu/pulsar/k1jt/Release_Notes.txt"});
2726 }
2727 
on_actionFT8_DXpedition_Mode_User_Guide_triggered()2728 void MainWindow::on_actionFT8_DXpedition_Mode_User_Guide_triggered()
2729 {
2730   QDesktopServices::openUrl (QUrl {"http://physics.princeton.edu/pulsar/k1jt/FT8_DXpedition_Mode.pdf"});
2731 }
2732 
on_actionQSG_FST4_triggered()2733 void MainWindow::on_actionQSG_FST4_triggered()
2734 {
2735   QDesktopServices::openUrl (QUrl {"https://physics.princeton.edu/pulsar/k1jt/FST4_Quick_Start.pdf"});
2736 }
2737 
on_actionQSG_Q65_triggered()2738 void MainWindow::on_actionQSG_Q65_triggered()
2739 {
2740   QDesktopServices::openUrl (QUrl {"https://physics.princeton.edu/pulsar/k1jt/Q65_Quick_Start.pdf"});
2741 }
2742 
on_actionQSG_X250_M3_triggered()2743 void MainWindow::on_actionQSG_X250_M3_triggered()
2744 {
2745   QDesktopServices::openUrl (QUrl {"https://physics.princeton.edu/pulsar/k1jt/WSJTX_2.5.0_MAP65_3.0_Quick_Start.pdf"});
2746 }
2747 
on_actionOnline_User_Guide_triggered()2748 void MainWindow::on_actionOnline_User_Guide_triggered()      //Display manual
2749 {
2750 #if defined (CMAKE_BUILD)
2751   m_manual.display_html_url (QUrl {PROJECT_MANUAL_DIRECTORY_URL}, PROJECT_MANUAL);
2752 #endif
2753 }
2754 
2755 //Display local copy of manual
on_actionLocal_User_Guide_triggered()2756 void MainWindow::on_actionLocal_User_Guide_triggered()
2757 {
2758 #if defined (CMAKE_BUILD)
2759   m_manual.display_html_file (m_config.doc_dir (), PROJECT_MANUAL);
2760 #endif
2761 }
2762 
on_actionWide_Waterfall_triggered()2763 void MainWindow::on_actionWide_Waterfall_triggered()      //Display Waterfalls
2764 {
2765   m_wideGraph->showNormal();
2766 }
2767 
on_actionEcho_Graph_triggered()2768 void MainWindow::on_actionEcho_Graph_triggered()
2769 {
2770   m_echoGraph->showNormal();
2771 }
2772 
on_actionFast_Graph_triggered()2773 void MainWindow::on_actionFast_Graph_triggered()
2774 {
2775   m_fastGraph->showNormal();
2776 }
2777 
on_actionSolve_FreqCal_triggered()2778 void MainWindow::on_actionSolve_FreqCal_triggered()
2779 {
2780   auto data_dir {QDir::toNativeSeparators(m_config.writeable_data_dir().absolutePath()).toLocal8Bit ()};
2781   int iz,irc;
2782   double a,b,rms,sigmaa,sigmab;
2783   calibrate_(data_dir.constData (),&iz,&a,&b,&rms,&sigmaa,&sigmab,&irc,data_dir.size ());
2784   QString t2;
2785   if(irc==-1) t2="Cannot open " + data_dir + "/fmt.all";
2786   if(irc==-2) t2="Cannot open " + data_dir + "/fcal2.out";
2787   if(irc==-3) t2="Insufficient data in fmt.all";
2788   if(irc==-4) t2 = tr ("Invalid data in fmt.all at line %1").arg (iz);
2789   if(irc>0 or rms>1.0) t2="Check fmt.all for possible bad data.";
2790   if (irc < 0 || irc > 0 || rms > 1.) {
2791     MessageBox::warning_message (this, "Calibration Error", t2);
2792   }
2793   else if (MessageBox::Apply == MessageBox::query_message (this
2794                                                            , tr ("Good Calibration Solution")
2795                                                            , tr ("<pre>"
2796                                                                  "%1%L2 ±%L3 ppm\n"
2797                                                                  "%4%L5 ±%L6 Hz\n\n"
2798                                                                  "%7%L8\n"
2799                                                                  "%9%L10 Hz"
2800                                                                  "</pre>")
2801                                                            .arg ("Slope: ", 12).arg (b, 0, 'f', 3).arg (sigmab, 0, 'f', 3)
2802                                                            .arg ("Intercept: ", 12).arg (a, 0, 'f', 2).arg (sigmaa, 0, 'f', 2)
2803                                                            .arg ("N: ", 12).arg (iz)
2804                                                            .arg ("StdDev: ", 12).arg (rms, 0, 'f', 2)
2805                                                            , QString {}
2806                                                            , MessageBox::Cancel | MessageBox::Apply)) {
2807     m_config.set_calibration (Configuration::CalibrationParams {a, b});
2808     if (MessageBox::Yes == MessageBox::query_message (this
2809                                                       , tr ("Delete Calibration Measurements")
2810                                                       , tr ("The \"fmt.all\" file will be renamed as \"fmt.bak\""))) {
2811       // rename fmt.all as we have consumed the resulting calibration
2812       // solution
2813       auto const& backup_file_name = m_config.writeable_data_dir ().absoluteFilePath ("fmt.bak");
2814       QFile::remove (backup_file_name);
2815       QFile::rename (m_config.writeable_data_dir ().absoluteFilePath ("fmt.all"), backup_file_name);
2816     }
2817   }
2818 }
2819 
on_actionCopyright_Notice_triggered()2820 void MainWindow::on_actionCopyright_Notice_triggered()
2821 {
2822   auto const& message = tr("If you make fair use of any part of WSJT-X under terms of the GNU "
2823                            "General Public License, you must display the following copyright "
2824                            "notice prominently in your derivative work:\n\n"
2825                            "\"The algorithms, source code, look-and-feel of WSJT-X and related "
2826                            "programs, and protocol specifications for the modes FSK441, FST4, FT8, "
2827                            "JT4, JT6M, JT9, JT65, JTMS, QRA64, Q65, MSK144 are Copyright (C) "
2828                            "2001-2021 by one or more of the following authors: Joseph Taylor, "
2829                            "K1JT; Bill Somerville, G4WJS; Steven Franke, K9AN; Nico Palermo, "
2830                            "IV3NWV; Greg Beam, KI7MT; Michael Black, W9MDB; Edson Pereira, PY2SDR; "
2831                            "Philip Karn, KA9Q; and other members of the WSJT Development Group.\"");
2832   MessageBox::warning_message(this, message);
2833 }
2834 
2835 // Implement the MultiGeometryWidget::change_layout() operation.
change_layout(std::size_t n)2836 void MainWindow::change_layout (std::size_t n)
2837 {
2838   switch (n)
2839     {
2840     case 1:                     // SWL view
2841       ui->menuBar->show ();
2842       // Z
2843       //  ui->lower_panel_widget->hide ();
2844       trim_view (false);        // ensure we can switch back
2845       break;
2846 
2847     case 2:                     // hide menus view
2848       ui->menuBar->hide ();
2849       // Z
2850       // ui->lower_panel_widget->show ();
2851       trim_view (true);
2852       break;
2853 
2854     default:                    // normal view
2855       ui->menuBar->setVisible (ui->cbMenus->isChecked ());
2856       // Z
2857       // ui->lower_panel_widget->show ();
2858       trim_view (!ui->cbMenus->isChecked ());
2859       break;
2860     }
2861 }
2862 
on_actionSWL_Mode_triggered(bool checked)2863 void MainWindow::on_actionSWL_Mode_triggered (bool checked)
2864 {
2865   select_geometry (checked ? 1 : ui->cbMenus->isChecked () ? 0 : 2);
2866 }
2867 
2868 // This allows the window to shrink by removing certain things
2869 // and reducing space used by controls
trim_view(bool checked)2870 void MainWindow::trim_view (bool checked)
2871 {
2872   int spacing = checked ? 1 : 6;
2873   if (checked) {
2874       statusBar ()->removeWidget (&auto_tx_label);
2875   } else {
2876       statusBar ()->addWidget(&auto_tx_label);
2877   }
2878   if (m_mode != "FreqCal" && m_mode != "WSPR" && m_mode != "FST4W") {
2879     ui->lh_decodes_title_label->setVisible(!checked);
2880     ui->rh_decodes_title_label->setVisible(!checked);
2881   }
2882   ui->lh_decodes_headings_label->setVisible(!checked);
2883   ui->rh_decodes_headings_label->setVisible(!checked);
2884   //ui->gridLayout_5->layout()->setSpacing(spacing);
2885   ui->horizontalLayout_2->layout()->setSpacing(spacing);
2886   //ui->horizontalLayout_5->layout()->setSpacing(spacing);
2887   ui->horizontalLayout_6->layout()->setSpacing(spacing);
2888   //ui->horizontalLayout_7->layout()->setSpacing(spacing);
2889   ui->horizontalLayout_8->layout()->setSpacing(spacing);
2890   ui->horizontalLayout_9->layout()->setSpacing(spacing);
2891   ui->horizontalLayout_10->layout()->setSpacing(spacing);
2892   ui->horizontalLayout_11->layout()->setSpacing(spacing);
2893   ui->horizontalLayout_12->layout()->setSpacing(spacing);
2894   ui->horizontalLayout_13->layout()->setSpacing(spacing);
2895   ui->horizontalLayout_14->layout()->setSpacing(spacing);
2896   //ui->rh_decodes_widget->layout()->setSpacing(spacing);
2897   ui->verticalLayout_2->layout()->setSpacing(spacing);
2898   //ui->verticalLayout_3->layout()->setSpacing(spacing);
2899   ui->verticalLayout_5->layout()->setSpacing(spacing);
2900   ui->verticalLayout_7->layout()->setSpacing(spacing);
2901   ui->verticalLayout_8->layout()->setSpacing(spacing);
2902   ui->tab->layout()->setSpacing(spacing);
2903 }
2904 
on_actionAstronomical_data_toggled(bool checked)2905 void MainWindow::on_actionAstronomical_data_toggled (bool checked)
2906 {
2907   if (checked)
2908     {
2909       m_astroWidget.reset (new Astro {m_settings, &m_config});
2910 
2911       // hook up termination signal
2912       connect (this, &MainWindow::finished, m_astroWidget.data (), &Astro::close);
2913       connect (m_astroWidget.data (), &Astro::tracking_update, [this] {
2914           m_astroCorrection = {};
2915           setRig ();
2916           setXIT (ui->TxFreqSpinBox->value ());
2917           displayDialFrequency ();
2918         });
2919       m_astroWidget->showNormal();
2920       m_astroWidget->raise ();
2921       m_astroWidget->activateWindow ();
2922       m_astroWidget->nominal_frequency (m_freqNominal, m_freqTxNominal);
2923     }
2924   else
2925     {
2926       m_astroWidget.reset ();
2927     }
2928 }
2929 
on_fox_log_action_triggered()2930 void MainWindow::on_fox_log_action_triggered()
2931 {
2932   if (!m_foxLogWindow)
2933     {
2934       m_foxLogWindow.reset (new FoxLogWindow {m_settings, &m_config, m_logBook.fox_log ()});
2935 
2936       // Connect signals from fox log window
2937       connect (this, &MainWindow::finished, m_foxLogWindow.data (), &FoxLogWindow::close);
2938       connect (m_foxLogWindow.data (), &FoxLogWindow::reset_log_model, [this] () {
2939           m_logBook.fox_log ()->reset ();
2940         });
2941     }
2942   m_foxLogWindow->showNormal ();
2943   m_foxLogWindow->raise ();
2944   m_foxLogWindow->activateWindow ();
2945 }
2946 
on_contest_log_action_triggered()2947 void MainWindow::on_contest_log_action_triggered()
2948 {
2949   if (!m_contestLogWindow)
2950     {
2951       m_contestLogWindow.reset (new CabrilloLogWindow {m_settings, &m_config, m_logBook.contest_log ()->model ()});
2952 
2953       // Connect signals from contest log window
2954       connect (this, &MainWindow::finished, m_contestLogWindow.data (), &CabrilloLogWindow::close);
2955     }
2956   m_contestLogWindow->showNormal ();
2957   m_contestLogWindow->raise ();
2958   m_contestLogWindow->activateWindow ();
2959 }
2960 
on_actionColors_triggered()2961 void MainWindow::on_actionColors_triggered()
2962 {
2963   if (!m_colorHighlighting)
2964     {
2965       m_colorHighlighting.reset (new ColorHighlighting {m_settings, m_config.decode_highlighting ()});
2966       connect (&m_config, &Configuration::decode_highlighting_changed, m_colorHighlighting.data (), &ColorHighlighting::set_items);
2967     }
2968   m_colorHighlighting->showNormal ();
2969   m_colorHighlighting->raise ();
2970   m_colorHighlighting->activateWindow ();
2971 }
2972 
on_actionMessage_averaging_triggered()2973 void MainWindow::on_actionMessage_averaging_triggered()
2974 {
2975   if(m_msgAvgWidget == NULL) {
2976     m_msgAvgWidget.reset (new MessageAveraging {m_settings, m_config.decoded_text_font ()});
2977 
2978     // Connect signals from Message Averaging window
2979     connect (this, &MainWindow::finished, m_msgAvgWidget.data (), &MessageAveraging::close);
2980   }
2981   m_msgAvgWidget->showNormal();
2982   m_msgAvgWidget->raise();
2983   m_msgAvgWidget->activateWindow();
2984 }
2985 
on_actionOpen_triggered()2986 void MainWindow::on_actionOpen_triggered()                     //Open File
2987 {
2988   monitor (false);
2989 
2990   QString fname;
2991   fname=QFileDialog::getOpenFileName(this, "Open File", m_path,
2992                                      "WSJT Files (*.wav)");
2993   if(!fname.isEmpty ()) {
2994     m_path=fname;
2995     int i1=fname.lastIndexOf("/");
2996     QString baseName=fname.mid(i1+1);
2997     tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #99ffff}");
2998     tx_status_label.setText(" " + baseName + " ");
2999     on_stopButton_clicked();
3000     m_diskData=true;
3001     read_wav_file (fname);
3002   }
3003 }
3004 
read_wav_file(QString const & fname)3005 void MainWindow::read_wav_file (QString const& fname)
3006 {
3007   // call diskDat() when done
3008   int i0=fname.lastIndexOf("_");
3009   int i1=fname.indexOf(".wav");
3010   m_nutc0=m_UTCdisk;
3011   m_UTCdisk=fname.mid(i0+1,i1-i0-1).toInt();
3012   m_wav_future_watcher.setFuture (QtConcurrent::run ([this, fname] {
3013         auto basename = fname.mid (fname.lastIndexOf ('/') + 1);
3014         auto pos = fname.indexOf (".wav", 0, Qt::CaseInsensitive);
3015         // global variables and threads do not mix well, this needs changing
3016         dec_data.params.nutc = 0;
3017         if (pos > 0) {
3018           if (pos == fname.indexOf ('_', -11) + 7) {
3019             dec_data.params.nutc = fname.mid (pos - 6, 6).toInt ();
3020             m_fileDateTime=fname.mid(pos-13,13);
3021           } else {
3022             dec_data.params.nutc = 100 * fname.mid (pos - 4, 4).toInt ();
3023             m_fileDateTime=fname.mid(pos-11,11);
3024           }
3025         }
3026 
3027         BWFFile file {QAudioFormat {}, fname};
3028         bool ok=file.open (BWFFile::ReadOnly);
3029         if(ok) {
3030           auto bytes_per_frame = file.format ().bytesPerFrame ();
3031           int nsamples=m_TRperiod * RX_SAMPLE_RATE;
3032           qint64 max_bytes = std::min (std::size_t (nsamples),
3033               sizeof (dec_data.d2) / sizeof (dec_data.d2[0]))* bytes_per_frame;
3034           auto n = file.read (reinterpret_cast<char *> (dec_data.d2),
3035                             std::min (max_bytes, file.size ()));
3036           int frames_read = n / bytes_per_frame;
3037         // zero unfilled remaining sample space
3038           std::memset(&dec_data.d2[frames_read],0,max_bytes - n);
3039           if (11025 == file.format ().sampleRate ()) {
3040             short sample_size = file.format ().sampleSize ();
3041             wav12_ (dec_data.d2, dec_data.d2, &frames_read, &sample_size);
3042           }
3043           dec_data.params.kin = frames_read;
3044           dec_data.params.newdat = 1;
3045         } else {
3046           dec_data.params.kin = 0;
3047           dec_data.params.newdat = 0;
3048         }
3049 
3050         if(basename.mid(0,10)=="000000_000" && m_mode == "FT8") {
3051           int isec=15*basename.mid(10,3).toInt();
3052           int ih=isec/3600;
3053           int im=(isec-3600*ih)/60;
3054           isec=isec%60;
3055           dec_data.params.nutc=3600*ih+60*im+isec;
3056         }
3057 
3058       }));
3059 }
3060 
on_actionOpen_next_in_directory_triggered()3061 void MainWindow::on_actionOpen_next_in_directory_triggered()   //Open Next
3062 {
3063   if(m_decoderBusy) return;
3064   monitor (false);
3065   int i,len;
3066   QFileInfo fi(m_path);
3067   QStringList list;
3068   list= fi.dir().entryList().filter(".wav",Qt::CaseInsensitive);
3069   for (i = 0; i < list.size()-1; ++i) {
3070     len=list.at(i).length();
3071     if(list.at(i)==m_path.right(len)) {
3072       int n=m_path.length();
3073       QString fname=m_path.replace(n-len,len,list.at(i+1));
3074       m_path=fname;
3075       int i1=fname.lastIndexOf("/");
3076       QString baseName=fname.mid(i1+1);
3077       tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #99ffff}");
3078       tx_status_label.setText(" " + baseName + " ");
3079       m_diskData=true;
3080       read_wav_file (fname);
3081       if(m_loopall and (i==list.size()-2)) {
3082         m_loopall=false;
3083         m_bNoMoreFiles=true;
3084       }
3085       return;
3086     }
3087   }
3088 }
3089 //Open all remaining files
on_actionDecode_remaining_files_in_directory_triggered()3090 void MainWindow::on_actionDecode_remaining_files_in_directory_triggered()
3091 {
3092   if(m_decoderBusy) return;
3093   m_loopall=true;
3094   on_actionOpen_next_in_directory_triggered();
3095 }
3096 
diskDat()3097 void MainWindow::diskDat()                                   //diskDat()
3098 {
3099   m_wideGraph->setDiskUTC(dec_data.params.nutc);
3100   if(dec_data.params.kin>0) {
3101     int k;
3102     int kstep=m_FFTSize;
3103     m_diskData=true;
3104     float db=m_config.degrade();
3105     float bw=m_config.RxBandwidth();
3106     if(db > 0.0) degrade_snr_(dec_data.d2,&dec_data.params.kin,&db,&bw);
3107     for(int n=1; n<=m_hsymStop; n++) {                      // Do the waterfall spectra
3108 //      k=(n+1)*kstep;           //### Why was this (n+1) ??? ###
3109       k=n*kstep;
3110       if(k > dec_data.params.kin) break;
3111       dec_data.params.npts8=k/8;
3112       dataSink(k);
3113       qApp->processEvents();                                //Update the waterfall
3114     }
3115   } else {
3116     MessageBox::information_message(this, tr("No data read from disk. Wrong file format?"));
3117   }
3118 }
3119 
3120 //Delete ../save/*.wav
on_actionDelete_all_wav_files_in_SaveDir_triggered()3121 void MainWindow::on_actionDelete_all_wav_files_in_SaveDir_triggered()
3122 {
3123   auto button = MessageBox::query_message (this, tr ("Confirm Delete"),
3124                                              tr ("Are you sure you want to delete all *.wav and *.c2 files in \"%1\"?")
3125                                              .arg (QDir::toNativeSeparators (m_config.save_directory ().absolutePath ())));
3126   if (MessageBox::Yes == button) {
3127     Q_FOREACH (auto const& file
3128                , m_config.save_directory ().entryList ({"*.wav", "*.c2"}, QDir::Files | QDir::Writable)) {
3129       m_config.save_directory ().remove (file);
3130     }
3131   }
3132 }
3133 
on_actionNone_triggered()3134 void MainWindow::on_actionNone_triggered()                    //Save None
3135 {
3136   m_saveDecoded=false;
3137   m_saveAll=false;
3138   ui->actionNone->setChecked(true);
3139 }
3140 
on_actionSave_decoded_triggered()3141 void MainWindow::on_actionSave_decoded_triggered()
3142 {
3143   m_saveDecoded=true;
3144   m_saveAll=false;
3145   ui->actionSave_decoded->setChecked(true);
3146 }
3147 
on_actionSave_all_triggered()3148 void MainWindow::on_actionSave_all_triggered()                //Save All
3149 {
3150   m_saveDecoded=false;
3151   m_saveAll=true;
3152   ui->actionSave_all->setChecked(true);
3153 }
3154 
on_actionKeyboard_shortcuts_triggered()3155 void MainWindow::on_actionKeyboard_shortcuts_triggered()
3156 {
3157   if (!m_shortcuts)
3158     {
3159       QFont font;
3160       font.setPointSize (10);
3161       m_shortcuts.reset (new HelpTextWindow {tr ("Keyboard Shortcuts"),
3162                                                //: Keyboard shortcuts help window contents
3163                                                tr (R"(<table cellspacing=1>
3164   <tr><td><b>Esc      </b></td><td>Stop Tx, abort QSO, clear next-call queue</td></tr>
3165   <tr><td><b>F1       </b></td><td>Online User's Guide (Alt: transmit Tx6)</td></tr>
3166   <tr><td><b>Shift+F1  </b></td><td>Copyright Notice</td></tr>
3167   <tr><td><b>Ctrl+F1  </b></td><td>About WSJT-X</td></tr>
3168   <tr><td><b>F2       </b></td><td>Open settings window (Alt: transmit Tx2)</td></tr>
3169   <tr><td><b>F3       </b></td><td>Display keyboard shortcuts (Alt: transmit Tx3)</td></tr>
3170   <tr><td><b>F4       </b></td><td>Clear DX Call, DX Grid, Tx messages 1-4 (Alt: transmit Tx4)</td></tr>
3171   <tr><td><b>Alt+F4   </b></td><td>Exit program</td></tr>
3172   <tr><td><b>F5       </b></td><td>Display special mouse commands (Alt: transmit Tx5)</td></tr>
3173   <tr><td><b>F6       </b></td><td>Open next file in directory (Alt: toggle "Call 1st")</td></tr>
3174   <tr><td><b>Shift+F6 </b></td><td>Decode all remaining files in directory</td></tr>
3175   <tr><td><b>F7       </b></td><td>Display Message Averaging window</td></tr>
3176   <tr><td><b>F11      </b></td><td>Move Rx frequency down 1 Hz</td></tr>
3177   <tr><td><b>Ctrl+F11 </b></td><td>Move identical Rx and Tx frequencies down 1 Hz</td></tr>
3178   <tr><td><b>Shift+F11 </b></td><td>Move Tx frequency down 60 Hz (FT8) or 90 Hz (FT4)</td></tr>
3179   <tr><td><b>Ctrl+Shift+F11 </b></td><td>Move dial frequency down 2000 Hz</td></tr>
3180   <tr><td><b>F12      </b></td><td>Move Rx frequency up 1 Hz</td></tr>
3181   <tr><td><b>Ctrl+F12 </b></td><td>Move identical Rx and Tx frequencies up 1 Hz</td></tr>
3182   <tr><td><b>Shift+F12 </b></td><td>Move Tx frequency up 60 Hz (FT8) or 90 Hz (FT4)</td></tr>
3183   <tr><td><b>Ctrl+Shift+F12 </b></td><td>Move dial frequency up 2000 Hz</td></tr>
3184   <tr><td><b>Alt+1-6  </b></td><td>Set now transmission to this number on Tab 1</td></tr>
3185   <tr><td><b>Ctl+1-6  </b></td><td>Set next transmission to this number on Tab 1</td></tr>
3186   <tr><td><b>Alt+B    </b></td><td>Toggle "Best S+P" status</td></tr>
3187   <tr><td><b>Alt+C    </b></td><td>Toggle "Call 1st" checkbox</td></tr>
3188   <tr><td><b>Alt+D    </b></td><td>Decode again at QSO frequency</td></tr>
3189   <tr><td><b>Shift+D  </b></td><td>Full decode (both windows)</td></tr>
3190   <tr><td><b>Ctrl+E   </b></td><td>Turn on TX even/1st</td></tr>
3191   <tr><td><b>Shift+E  </b></td><td>Turn off TX even/1st</td></tr>
3192   <tr><td><b>Alt+E    </b></td><td>Erase</td></tr>
3193   <tr><td><b>Ctrl+F   </b></td><td>Edit the free text message box</td></tr>
3194   <tr><td><b>Alt+G    </b></td><td>Generate standard messages</td></tr>
3195   <tr><td><b>Alt+H    </b></td><td>Halt Tx</td></tr>
3196   <tr><td><b>Ctrl+L   </b></td><td>Lookup callsign in database, generate standard messages</td></tr>
3197   <tr><td><b>Alt+M    </b></td><td>Monitor</td></tr>
3198   <tr><td><b>Alt+N    </b></td><td>Enable Tx</td></tr>
3199   <tr><td><b>Ctrl+O   </b></td><td>Open a .wav file</td></tr>
3200   <tr><td><b>Alt+O    </b></td><td>Change operator</td></tr>
3201   <tr><td><b>Alt+Q    </b></td><td>Log QSO</td></tr>
3202   <tr><td><b>Ctrl+R   </b></td><td>Set Tx4 message to RRR (not in FT4)</td></tr>
3203   <tr><td><b>Alt+R    </b></td><td>Set Tx4 message to RR73</td></tr>
3204   <tr><td><b>Alt+S    </b></td><td>Stop monitoring</td></tr>
3205   <tr><td><b>Alt+T    </b></td><td>Toggle Tune status</td></tr>
3206   <tr><td><b>Alt+Z    </b></td><td>Clear hung decoder status</td></tr>
3207 </table>)"), font});
3208     }
3209   m_shortcuts->showNormal ();
3210   m_shortcuts->raise ();
3211 }
3212 
on_actionSpecial_mouse_commands_triggered()3213 void MainWindow::on_actionSpecial_mouse_commands_triggered()
3214 {
3215   if (!m_mouseCmnds)
3216     {
3217       QFont font;
3218       font.setPointSize (10);
3219       m_mouseCmnds.reset (new HelpTextWindow {tr ("Special Mouse Commands"),
3220                                                 //: Mouse commands help window contents
3221                                                 tr (R"(<table cellpadding=5>
3222   <tr>
3223     <th align="right">Click on</th>
3224     <th align="left">Action</th>
3225   </tr>
3226   <tr>
3227     <td align="right">Waterfall:</td>
3228     <td><b>Click</b> to set Rx frequency.<br/>
3229         <b>Shift-click</b> to set Tx frequency.<br/>
3230         <b>Ctrl-click</b> or <b>Right-click</b> to set Rx and Tx frequencies.<br/>
3231         <b>Double-click</b> to also decode at Rx frequency.<br/>
3232     </td>
3233   </tr>
3234   <tr>
3235     <td align="right">Decoded text:</td>
3236     <td><b>Double-click</b> to copy second callsign to Dx Call,<br/>
3237         locator to Dx Grid, change Rx and Tx frequency to<br/>
3238         decoded signal's frequency, and generate standard<br/>
3239         messages.<br/>
3240         If <b>Hold Tx Freq</b> is checked or first callsign in message<br/>
3241         is your own call, Tx frequency is not changed unless <br/>
3242         <b>Ctrl</b> is held down.<br/>
3243     </td>
3244   </tr>
3245   <tr>
3246     <td align="right">Erase button:</td>
3247     <td><b>Click</b> to erase QSO window.<br/>
3248         <b>Double-click</b> to erase QSO and Band Activity windows.
3249     </td>
3250   </tr>
3251 </table>)"), font});
3252     }
3253   m_mouseCmnds->showNormal ();
3254   m_mouseCmnds->raise ();
3255 }
3256 
on_DecodeButton_clicked(bool)3257 void MainWindow::on_DecodeButton_clicked (bool /* checked */) //Decode request
3258 {
3259   if(m_mode=="MSK144") {
3260     ui->DecodeButton->setChecked(false);
3261   } else {
3262     if(m_mode!="WSPR" && !m_decoderBusy) {
3263       dec_data.params.newdat=0;
3264       dec_data.params.nagain=1;
3265       decode();
3266     }
3267   }
3268 }
3269 
freezeDecode(int n)3270 void MainWindow::freezeDecode(int n)                          //freezeDecode()
3271 {
3272   if((n%100)==2) {
3273     if(m_mode=="FST4" and m_config.single_decode() and ui->sbFtol->value()>10) ui->sbFtol->setValue(10);
3274     on_DecodeButton_clicked (true);
3275   }
3276 }
3277 
on_ClrAvgButton_clicked()3278 void MainWindow::on_ClrAvgButton_clicked()
3279 {
3280   m_nclearave=1;
3281   if(m_msgAvgWidget != NULL) {
3282     if(m_msgAvgWidget->isVisible()) m_msgAvgWidget->displayAvg("");
3283   }
3284   if(m_mode=="Q65") ndecodes_label.setText("0  0");
3285 }
3286 
msgAvgDecode2()3287 void MainWindow::msgAvgDecode2()
3288 {
3289   on_DecodeButton_clicked (true);
3290 }
3291 
decode()3292 void MainWindow::decode()                                       //decode()
3293 {
3294   if(m_decoderBusy) return;                          //Don't start decoder if it's already busy.
3295   QDateTime now = QDateTime::currentDateTimeUtc ();
3296   if( m_dateTimeLastTX.isValid () ) {
3297     qint64 isecs_since_tx = m_dateTimeLastTX.secsTo(now);
3298     dec_data.params.lapcqonly= (isecs_since_tx > 300);
3299   } else {
3300     m_dateTimeLastTX = now.addSecs(-900);
3301     dec_data.params.lapcqonly=true;
3302   }
3303   if( m_diskData ) {
3304     dec_data.params.lapcqonly=false;
3305   }
3306   if(!m_dataAvailable or m_TRperiod==0.0) return;
3307   ui->DecodeButton->setChecked (true);
3308   if(!dec_data.params.nagain && m_diskData && m_TRperiod >= 60.) {
3309     dec_data.params.nutc=dec_data.params.nutc/100;
3310   }
3311   if(dec_data.params.nagain==0 && dec_data.params.newdat==1 && (!m_diskData)) {
3312     m_dateTimeSeqStart = qt_truncate_date_time_to (QDateTime::currentDateTimeUtc (), m_TRperiod * 1.e3);
3313     auto t = m_dateTimeSeqStart.time ();
3314     dec_data.params.nutc = t.hour () * 100 + t.minute ();
3315     if (m_TRperiod < 60.)
3316       {
3317         dec_data.params.nutc = dec_data.params.nutc * 100 + t.second ();
3318       }
3319   }
3320 
3321   if(m_nPick==1 and !m_diskData) {
3322     QDateTime t=QDateTime::currentDateTimeUtc();
3323     int ihr=t.toString("hh").toInt();
3324     int imin=t.toString("mm").toInt();
3325     int isec=t.toString("ss").toInt();
3326     isec=isec - fmod(double(isec),m_TRperiod);
3327     dec_data.params.nutc=10000*ihr + 100*imin + isec;
3328   }
3329   if(m_nPick==2) dec_data.params.nutc=m_nutc0;
3330   dec_data.params.nQSOProgress = m_QSOProgress;
3331   dec_data.params.nfqso=m_wideGraph->rxFreq();
3332   dec_data.params.nftx = ui->TxFreqSpinBox->value ();
3333   qint32 depth {m_ndepth};
3334   if (!ui->actionInclude_averaging->isVisible ()) depth &= ~16;
3335   if (!ui->actionInclude_correlation->isVisible ()) depth &= ~32;
3336   if (!ui->actionEnable_AP_DXcall->isVisible ()) depth &= ~64;
3337   if (!ui->actionAuto_Clear_Avg->isVisible()) depth &= ~128;
3338   dec_data.params.ndepth=depth;
3339   dec_data.params.n2pass=1;
3340   if(m_config.twoPass()) dec_data.params.n2pass=2;
3341   dec_data.params.nranera=m_config.ntrials();
3342   dec_data.params.naggressive=m_config.aggressive();
3343   dec_data.params.nrobust=0;
3344   dec_data.params.ndiskdat=0;
3345   if(m_diskData) dec_data.params.ndiskdat=1;
3346   dec_data.params.nfa=m_wideGraph->nStartFreq();
3347   dec_data.params.nfSplit=m_wideGraph->Fmin();
3348   dec_data.params.nfb=m_wideGraph->Fmax();
3349   if(m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id() and !ui->cbRxAll->isChecked()) dec_data.params.nfb=1000;
3350   if(m_mode=="FT8" and SpecOp::FOX == m_config.special_op_id() ) dec_data.params.nfqso=200;
3351   dec_data.params.ntol=ui->sbFtol->value ();
3352   if(!m_config.enable_VHF_features()) {
3353     dec_data.params.ntol=20;
3354     dec_data.params.naggressive=0;
3355   }
3356   if(m_mode=="FST4") {
3357     dec_data.params.ntol=ui->sbFtol->value();
3358     if(m_config.single_decode()) {
3359       dec_data.params.nfa=m_wideGraph->rxFreq() - ui->sbFtol->value();
3360       dec_data.params.nfb=m_wideGraph->rxFreq() + ui->sbFtol->value();
3361     } else {
3362       dec_data.params.nfa=ui->sbF_Low->value();
3363       dec_data.params.nfb=ui->sbF_High->value();
3364     }
3365   }
3366   if(m_mode=="FST4W") dec_data.params.ntol=ui->sbFST4W_FTol->value();
3367   if(dec_data.params.nutc < m_nutc0) m_RxLog = 1;       //Date and Time to file "ALL.TXT".
3368   if(dec_data.params.newdat==1 and !m_diskData) m_nutc0=dec_data.params.nutc;
3369   dec_data.params.ntxmode=9;
3370   dec_data.params.nmode=9;
3371   if(m_mode=="JT65") dec_data.params.nmode=65;
3372   if(m_mode=="JT65") dec_data.params.ljt65apon = ui->actionEnable_AP_JT65->isVisible () &&
3373       ui->actionEnable_AP_JT65->isChecked ();
3374   if(m_mode=="Q65") dec_data.params.nmode=66;
3375   if(m_mode=="Q65") dec_data.params.ntxmode=66;
3376   if(m_mode=="JT4") {
3377     dec_data.params.nmode=4;
3378     dec_data.params.ntxmode=4;
3379   }
3380   if(m_mode=="FT8") dec_data.params.nmode=8;
3381   if(m_mode=="FT8") dec_data.params.lft8apon = ui->actionEnable_AP_FT8->isVisible () &&
3382       ui->actionEnable_AP_FT8->isChecked ();
3383   if(m_mode=="FT8") dec_data.params.napwid=50;
3384   if(m_mode=="FT4") {
3385     dec_data.params.nmode=5;
3386     m_BestCQpriority="";
3387   }
3388   if(m_mode=="FST4") dec_data.params.nmode=240;
3389   if(m_mode=="FST4W") dec_data.params.nmode=241;
3390   dec_data.params.ntxmode=dec_data.params.nmode;   // Is this used any more?
3391   dec_data.params.ntrperiod=m_TRperiod;
3392   dec_data.params.nsubmode=m_nSubMode;
3393   dec_data.params.minw=0;
3394   dec_data.params.nclearave=m_nclearave;
3395   if(m_nclearave!=0) {
3396     QFile f(m_config.temp_dir ().absoluteFilePath ("avemsg.txt"));
3397     f.remove();
3398   }
3399   dec_data.params.dttol=m_DTtol;
3400   dec_data.params.emedelay=0.0;
3401   if(m_config.decode_at_52s()) dec_data.params.emedelay=2.5;
3402   dec_data.params.minSync=ui->syncSpinBox->isVisible () ? m_minSync : 0;
3403   dec_data.params.nexp_decode = static_cast<int> (m_config.special_op_id());
3404   if(m_config.single_decode()) dec_data.params.nexp_decode += 32;
3405   if(m_config.enable_VHF_features()) dec_data.params.nexp_decode += 64;
3406   if(m_mode.startsWith("FST4")) dec_data.params.nexp_decode += 256*(ui->sbNB->value()+3);
3407   dec_data.params.max_drift=ui->sbMaxDrift->value();
3408 
3409   ::memcpy(dec_data.params.datetime, m_dateTime.toLatin1()+"    ", sizeof dec_data.params.datetime);
3410   ::memcpy(dec_data.params.mycall, (m_config.my_callsign()+"            ").toLatin1(), sizeof dec_data.params.mycall);
3411   ::memcpy(dec_data.params.mygrid, (m_config.my_grid()+"      ").toLatin1(), sizeof dec_data.params.mygrid);
3412   QString hisCall {ui->dxCallEntry->text ()};
3413   QString hisGrid {ui->dxGridEntry->text ()};
3414   memcpy(dec_data.params.hiscall,(hisCall + "            ").toLatin1 ().constData (), sizeof dec_data.params.hiscall);
3415   memcpy(dec_data.params.hisgrid,(hisGrid + "      ").toLatin1 ().constData (), sizeof dec_data.params.hisgrid);
3416 
3417   //newdat=1  ==> this is new data, must do the big FFT
3418   //nagain=1  ==> decode only at fQSO +/- Tol
3419 
3420   if (auto * to = reinterpret_cast<char *> (mem_jt9->data()))
3421     {
3422       char *from = (char*) dec_data.ipc;
3423       int size=sizeof(struct dec_data);
3424       if(dec_data.params.newdat==0) {
3425         int noffset {offsetof (struct dec_data, params.nutc)};
3426         to += noffset;
3427         from += noffset;
3428         size -= noffset;
3429       }
3430       if(m_mode=="MSK144" or m_bFast9) {
3431         float t0=m_t0;
3432         float t1=m_t1;
3433         qApp->processEvents();                                //Update the waterfall
3434         if(m_nPick > 0) {
3435           t0=m_t0Pick;
3436           t1=m_t1Pick;
3437         }
3438         static short int d2b[360000];
3439         narg[0]=dec_data.params.nutc;
3440         if(m_kdone>int(12000.0*m_TRperiod)) {
3441           m_kdone=int(12000.0*m_TRperiod);
3442         }
3443         narg[1]=m_kdone;
3444         narg[2]=m_nSubMode;
3445         narg[3]=dec_data.params.newdat;
3446         narg[4]=dec_data.params.minSync;
3447         narg[5]=m_nPick;
3448         narg[6]=1000.0*t0;
3449         narg[7]=1000.0*t1;
3450         narg[8]=2;                                //Max decode lines per decode attempt
3451         if(dec_data.params.minSync<0) narg[8]=50;
3452         if(m_mode=="JT9") narg[9]=102;            //Fast JT9
3453         if(m_mode=="MSK144") narg[9]=104;         //MSK144
3454         narg[10]=ui->RxFreqSpinBox->value();
3455         narg[11]=ui->sbFtol->value ();
3456         narg[12]=0;
3457         narg[13]=-1;
3458         narg[14]=m_config.aggressive();
3459         memcpy(d2b,dec_data.d2,2*360000);
3460         watcher3.setFuture (QtConcurrent::run (std::bind (fast_decode_,&d2b[0],
3461                                                           &narg[0],&m_TRperiod,&m_msg[0][0],
3462                                                           dec_data.params.mycall,dec_data.params.hiscall,8000,12,12)));
3463       } else {
3464         mem_jt9->lock ();
3465         memcpy(to, from, qMin(mem_jt9->size(), size));
3466         mem_jt9->unlock ();
3467         to_jt9(m_ihsym,1,-1);                //Send m_ihsym to jt9[.exe] and start decoding
3468         decodeBusy(true);
3469       }
3470     }
3471 }
3472 
fast_decode_done()3473 void::MainWindow::fast_decode_done()
3474 {
3475   float t,tmax=-99.0;
3476   dec_data.params.nagain=false;
3477   dec_data.params.ndiskdat=false;
3478 //  if(m_msg[0][0]==0) m_bDecoded=false;
3479   for(int i=0; m_msg[i][0] && i<100; i++) {
3480     QString message=QString::fromLatin1(m_msg[i]);
3481     m_msg[i][0]=0;
3482     if(message.length()>80) message=message.left (80);
3483     if(narg[13]/8==narg[12]) message=message.trimmed().replace("<...>",m_calls);
3484 
3485 //Left (Band activity) window
3486     DecodedText decodedtext {message.replace (QChar::LineFeed, "")};
3487     if(!m_bFastDone) {
3488       ui->decodedTextBrowser->displayDecodedText (decodedtext, m_config.my_callsign (), m_mode, m_config.DXCC (),
3489          m_logBook, m_currentBand, m_config.ppfx ());
3490     }
3491 
3492     t=message.mid(10,5).toFloat();
3493     if(t>tmax) {
3494       tmax=t;
3495       m_bDecoded=true;
3496     }
3497     postDecode (true, decodedtext.string ());
3498     write_all("Rx",message);
3499 
3500     if(m_mode=="JT9" or m_mode=="MSK144") {
3501 // find and extract any report for myCall
3502       bool stdMsg = decodedtext.report(m_baseCall,
3503                     Radio::base_callsign(ui->dxCallEntry->text()), m_rptRcvd);
3504 
3505 // extract details and send to PSKreporter
3506       if (stdMsg) pskPost (decodedtext);
3507     }
3508     if (tmax >= 0.0) auto_sequence (decodedtext, ui->sbFtol->value (), ui->sbFtol->value ());
3509   }
3510   m_startAnother=m_loopall;
3511   m_nPick=0;
3512   ui->DecodeButton->setChecked (false);
3513   m_bFastDone=false;
3514 }
3515 
to_jt9(qint32 n,qint32 istart,qint32 idone)3516 void MainWindow::to_jt9(qint32 n, qint32 istart, qint32 idone)
3517 {
3518   if (auto * dd = reinterpret_cast<dec_data_t *> (mem_jt9->data()))
3519     {
3520       mem_jt9->lock ();
3521       dd->ipc[0]=n;
3522       if(istart>=0) dd->ipc[1]=istart;
3523       if(idone>=0)  dd->ipc[2]=idone;
3524       mem_jt9->unlock ();
3525     }
3526 }
3527 
decodeDone()3528 void MainWindow::decodeDone ()
3529 {
3530   if(m_mode=="Q65") m_wideGraph->drawRed(0,0);
3531   if ("FST4W" == m_mode)
3532     {
3533       if (m_uploadWSPRSpots
3534           && m_config.is_transceiver_online ()) { // need working rig control
3535 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
3536         uploadTimer.start(QRandomGenerator::global ()->bounded (0, 20000)); // Upload delay
3537 #else
3538         uploadTimer.start(20000 * qrand()/((double)RAND_MAX + 1.0)); // Upload delay
3539 #endif
3540       }
3541     }
3542   auto tnow = QDateTime::currentDateTimeUtc ();
3543   double tdone = fmod(double(tnow.time().second()),m_TRperiod);
3544   int mswait;
3545   if( tdone < 0.5*m_TRperiod ) {
3546     mswait = 1000.0 * ( 0.6 * m_TRperiod - tdone );
3547   } else {
3548     mswait = 1000.0 * ( 1.6 * m_TRperiod - tdone );
3549   }
3550   m_bDecoded=m_nDecodes>0;
3551 //  qDebug() << "aa 3316" << m_saveDecoded << m_saveAll << m_bDecoded << m_nDecodes
3552 //           << m_TRperiod << tdone << mswait;
3553   if(!m_diskData and !m_saveAll) {
3554     if(m_saveDecoded and (m_nDecodes==0)) {
3555 //      qDebug() << "bb 3319" << mswait;
3556       killFileTimer.start(mswait); //Kill at 3/4 period
3557     }
3558   }
3559   if(m_mode!="FT8" or dec_data.params.nzhsym==50) m_nDecodes=0;
3560 
3561   dec_data.params.nagain=0;
3562   dec_data.params.ndiskdat=0;
3563   m_nclearave=0;
3564 //  pause_jt9 ();
3565   ui->DecodeButton->setChecked (false);
3566   decodeBusy(false);
3567   m_RxLog=0;
3568   if(SpecOp::FOX == m_config.special_op_id()) houndCallers();
3569   to_jt9(m_ihsym,-1,1);                //Tell jt9 we know it has finished
3570 
3571   m_startAnother=m_loopall;
3572   if(m_bNoMoreFiles) {
3573     MessageBox::information_message(this, tr("No more files to open."));
3574     m_bNoMoreFiles=false;
3575   }
3576 }
3577 
readFromStdout()3578 void MainWindow::readFromStdout()                             //readFromStdout
3579 {
3580   while(proc_jt9.canReadLine()) {
3581     auto line_read = proc_jt9.readLine ();
3582     if (auto p = std::strpbrk (line_read.constData (), "\n\r")) {
3583       // truncate before line ending chars
3584       line_read = line_read.left (p - line_read.constData ());
3585     }
3586     bool haveFSpread {false};
3587     float fSpread {0.};
3588     if (m_mode.startsWith ("FST4"))
3589       {
3590         auto text = line_read.mid (64, 6).trimmed ();
3591         if (text.size ())
3592           {
3593             fSpread = text.toFloat (&haveFSpread);
3594             line_read = line_read.left (64);
3595           }
3596         auto const& cs = m_config.my_callsign ().toLocal8Bit ();
3597         if ("FST4W" == m_mode && ui->cbNoOwnCall->isChecked ()
3598             && (line_read.contains (" " + cs + " ")
3599                 || line_read.contains ("<" + cs + ">"))) {
3600           continue;
3601         }
3602       }
3603     if (m_mode!="FT8" and m_mode!="FT4" and !m_mode.startsWith ("FST4") and m_mode!="Q65") {
3604       //Pad 22-char msg to at least 37 chars
3605       line_read = line_read.left(44) + "              " + line_read.mid(44);
3606     }
3607     bool bAvgMsg=false;
3608     int navg=0;
3609 
3610     if(line_read.indexOf("<DecodeFinished>") >= 0) {
3611       m_bDecoded =  line_read.mid(20).trimmed().toInt() > 0;
3612       int n=line_read.trimmed().size();
3613       int n2=line_read.trimmed().mid(n-7).toInt();
3614       int n0=n2/1000;
3615       int n1=n2%1000;
3616       if(m_mode=="Q65") {
3617         ndecodes_label.setText(QString {"%1  %2"}.arg (n0).arg (n1));
3618       } else {
3619         if(m_nDecodes==0) ndecodes_label.setText("0");
3620       }
3621       decodeDone ();
3622       return;
3623     } else {
3624       m_nDecodes+=1;
3625       if(m_mode!="Q65") ndecodes_label.setText(QString::number(m_nDecodes));
3626       if(m_mode=="JT4" or m_mode=="JT65" or m_mode=="Q65") {
3627         //### Do something about Q65 here ?  ###
3628         int nf=line_read.indexOf("f");
3629         if(nf>0) {
3630           navg=line_read.mid(nf+1,1).toInt();
3631           if(line_read.indexOf("f*")>0) navg=10;
3632         }
3633         int nd=-1;
3634         if(nf<0) nd=line_read.indexOf("d");
3635         if(nd>0) {
3636           navg=line_read.mid(nd+2,1).toInt();
3637           if(line_read.mid(nd+2,1)=="*") navg=10;
3638         }
3639         int na=-1;
3640         if(nf<0 and nd<0) na=line_read.indexOf("a");
3641         if(na>0) {
3642           navg=line_read.mid(na+2,1).toInt();
3643           if(line_read.mid(na+2,1)=="*") navg=10;
3644         }
3645         int nq=-1;
3646         if(nf<0 and nd<0 and na<0) nq=line_read.indexOf("q");
3647         if(nq>0) {
3648           navg=line_read.mid(nq+2,1).toInt();
3649           if(line_read.mid(nq+2,1)=="*") navg=10;
3650         }
3651         if(navg>=2) bAvgMsg=true;
3652       }
3653       write_all("Rx",line_read.trimmed());
3654       int ntime=6;
3655       if(m_TRperiod>=60) ntime=4;
3656       if (line_read.left(ntime) != m_tBlankLine) {
3657           ui->decodedTextBrowser->new_period ();
3658           if (m_config.insert_blank ()
3659               && SpecOp::FOX != m_config.special_op_id()) {
3660             QString band;
3661             if((QDateTime::currentMSecsSinceEpoch() / 1000 - m_secBandChanged) > 4*int(m_TRperiod)/4) {
3662               band = ' ' + m_config.bands ()->find (m_freqNominal);
3663             }
3664             ui->decodedTextBrowser->insertLineSpacer (band.rightJustified  (40, '-'));
3665             // Z
3666             if (m_unfilteredView && m_unfilteredView->isVisible()) {
3667                 m_unfilteredView->display(band.rightJustified  (40, '-'));
3668             }
3669             m_beeped = false;
3670           }
3671         m_tBlankLine = line_read.left(ntime);
3672       }
3673       if ("FST4W" == m_mode)
3674         {
3675           uploadWSPRSpots (true, line_read);
3676         }
3677       DecodedText decodedtext0 {QString::fromUtf8(line_read.constData())};
3678       DecodedText decodedtext {QString::fromUtf8(line_read.constData()).remove("TU; ")};
3679       // Z
3680       auto isFiltered = callsignFiltered(decodedtext0);
3681 
3682       auto for_us  = decodedtext.string().contains(" " + m_config.my_callsign() + " ") or
3683               decodedtext.string().contains(" "+m_baseCall) or
3684               decodedtext.string().contains(m_baseCall+" ") or
3685               decodedtext.string().contains(" <" + m_config.my_callsign() + "> ");
3686 
3687       if(m_mode=="FT8" and SpecOp::FOX == m_config.special_op_id() and
3688          (decodedtext.string().contains("R+") or decodedtext.string().contains("R-"))) {
3689         auto for_us  = decodedtext.string().contains(" " + m_config.my_callsign() + " ") or
3690             decodedtext.string().contains(" "+m_baseCall) or
3691             decodedtext.string().contains(m_baseCall+" ") or
3692             decodedtext.string().contains(" <" + m_config.my_callsign() + "> ");
3693         if(decodedtext.string().contains(" DE ")) for_us=true;   //Hound with compound callsign
3694         if(for_us) {
3695           QString houndCall,houndGrid;
3696           decodedtext.deCallAndGrid(/*out*/houndCall,houndGrid);
3697           foxRxSequencer(decodedtext.string(),houndCall,houndGrid);
3698         }
3699       }
3700 
3701 //Left (Band activity) window
3702       if(!bAvgMsg) {
3703         if(m_mode=="FT8" and SpecOp::FOX == m_config.special_op_id()) {
3704           if(!m_bDisplayedOnce) {
3705             // This hack sets the font.  Surely there's a better way!
3706             DecodedText dt{"."};
3707             ui->decodedTextBrowser->displayDecodedText (dt, m_config.my_callsign (), m_mode, m_config.DXCC (),
3708                 m_logBook, m_currentBand, m_config.ppfx ());
3709             m_bDisplayedOnce=true;
3710           }
3711         } else {
3712 
3713             DecodedText decodedtext1=decodedtext0;
3714         // Z
3715 
3716             QString deCall;
3717             QString grid;
3718             decodedtext.deCallAndGrid(deCall,grid);
3719 
3720         if (m_unfilteredView && m_unfilteredView->isVisible()) {
3721             QString m = decodedtext0.clean_string().trimmed();
3722             if (m_config.rawViewDXCC()) {
3723                 auto const& entity = m_logBook.countries ()->lookup (deCall);
3724                 auto countryName = entity.entity_name;
3725                 countryName.replace ("Islands", "Is.");
3726                 countryName.replace ("Island", "Is.");
3727                 countryName.replace ("North ", "N. ");
3728                 countryName.replace ("Northern ", "N. ");
3729                 countryName.replace ("South ", "S. ");
3730                 countryName.replace ("East ", "E. ");
3731                 countryName.replace ("Eastern ", "E. ");
3732                 countryName.replace ("West ", "W. ");
3733                 countryName.replace ("Western ", "W. ");
3734                 countryName.replace ("Central ", "C. ");
3735                 countryName.replace (" and ", " & ");
3736                 countryName.replace ("Republic", "Rep.");
3737                 countryName.replace ("United States", "U.S.A.");
3738                 countryName.replace ("Fed. Rep. of ", "");
3739                 countryName.replace ("French ", "Fr.");
3740                 countryName.replace ("Asiatic", "AS");
3741                 countryName.replace ("European", "EU");
3742                 countryName.replace ("African", "AF");
3743 
3744                 m = leftJustifyAppendage(m, countryName);
3745             }
3746             m_unfilteredView->display(m);
3747         }
3748 
3749           if (!isFiltered || for_us)
3750           {
3751 
3752               QString distance;
3753 
3754               if ((m_config.showDistance() || m_config.showBearing())&& grid.contains(grid_regexp)) {
3755                   double utch=0.0;
3756                   int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter;
3757                   azdist_(const_cast <char *> ((m_config.my_grid () + "      ").left (6).toLatin1().constData()),
3758                           const_cast <char *> ((grid + "      ").left (6).toLatin1().constData()),&utch,
3759                           &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6);
3760 
3761                   if (m_config.showDistance()) {
3762                       int nd=nDkm;
3763                       if(m_config.miles()) nd=nDmiles;
3764                       distance = QString::number(nd);
3765                       if(m_config.miles()) distance += " mi";
3766                       if(!m_config.miles()) distance += " km";
3767                   }
3768 
3769                   if (m_config.showBearing()) {
3770                       if (distance.length()) distance += " / ";
3771                       distance += QString::number(nAz) + "°";
3772 
3773                   }
3774               }
3775               QString state = "";
3776 
3777               if (m_config.showState())  {
3778                   state = stateLookup(deCall);
3779               }
3780 
3781               ui->decodedTextBrowser->displayDecodedText(decodedtext1,m_baseCall,m_mode,m_config.DXCC(),
3782                                                          m_logBook,m_currentBand,m_config.ppfx(),
3783                                                          ui->cbCQonly->isVisible() && ui->cbCQonly->isChecked(),
3784                                                          haveFSpread, fSpread, ui->cbCQonlyIncl73->isChecked(), m_config.colourAll(), distance, state);
3785 
3786               if (ui->dxCallEntry->text() == deCall && m_config.highlightDX())
3787               {
3788                   ui->decodedTextBrowser->highlight_callsign(ui->dxCallEntry->text(), QColor(255,0,0), QColor(255,255,255), true);
3789               }
3790 
3791           }
3792 
3793           if(m_bBestSPArmed && m_mode=="FT4" && CALLING == m_QSOProgress) {
3794             QString messagePriority=ui->decodedTextBrowser->CQPriority();
3795             if(messagePriority!="") {
3796               if(messagePriority=="New Call on Band"
3797                  and m_BestCQpriority!="New Call on Band"
3798                  and m_BestCQpriority!="New Multiplier") {
3799                 m_BestCQpriority="New Call on Band";
3800                 m_bDoubleClicked = true;
3801                 processMessage(decodedtext0);
3802               }
3803               if(messagePriority=="New DXCC"
3804                  and m_BestCQpriority!="New DXCC"
3805                  and m_BestCQpriority!="New Multiplier") {
3806                 m_BestCQpriority="New DXCC";
3807                 m_bDoubleClicked = true;
3808                 processMessage(decodedtext0);
3809               }
3810             }
3811           }
3812 
3813         }
3814       }
3815 
3816 //Right (Rx Frequency) window
3817       bool bDisplayRight=bAvgMsg;
3818       int audioFreq=decodedtext.frequencyOffset();
3819       if(m_mode=="FT8" or m_mode=="FT4" or m_mode=="FST4" or m_mode=="Q65") {
3820         int ftol=10;
3821         if(m_mode=="Q65") ftol=ui->sbFtol->value();
3822         auto const& parts = decodedtext.string().remove("<").remove(">")
3823             .split (' ', SkipEmptyParts);
3824         if (parts.size() > 6) {
3825           auto for_us = parts[5].contains (m_baseCall)
3826             || ("DE" == parts[5] && qAbs (ui->RxFreqSpinBox->value () - audioFreq) <= ftol);
3827           if(m_baseCall == m_config.my_callsign())
3828             {
3829               if (m_baseCall != parts[5])
3830                 {
3831                   for_us=false;
3832                 }
3833             }
3834           else
3835             {
3836               if (m_config.my_callsign () != parts[5])
3837                 {
3838                   for_us = false; // same base call as ours but
3839                                   // different prefix or suffix, rare
3840                                   // but can happen with multi station
3841                                   // special events
3842                 }
3843             }
3844           if(m_bCallingCQ && !m_bAutoReply && for_us && ui->cbFirst->isChecked() and
3845              SpecOp::FOX > m_config.special_op_id()) {
3846             m_bDoubleClicked=true;
3847             m_bAutoReply = true;
3848             // Z
3849             if (!isFiltered)
3850             if(SpecOp::FOX != m_config.special_op_id())
3851             {
3852                 processMessage (decodedtext);
3853             }
3854             ui->cbFirst->setStyleSheet("");
3855           }
3856           if(SpecOp::FOX==m_config.special_op_id() and decodedtext.string().contains(" DE ")) for_us=true; //Hound with compound callsign
3857           if(SpecOp::FOX==m_config.special_op_id() and for_us and (audioFreq<1000)) bDisplayRight=true;
3858           if(SpecOp::FOX!=m_config.special_op_id() and (for_us or (abs(audioFreq - m_wideGraph->rxFreq()) <= 10))) bDisplayRight=true;
3859         }
3860       } else {
3861         if((abs(audioFreq - m_wideGraph->rxFreq()) <= 10) and
3862            !m_config.enable_VHF_features()) bDisplayRight=true;
3863       }
3864 
3865       if (bDisplayRight) {
3866         // This msg is within 10 hertz of our tuned frequency, or a JT4 or JT65 avg,
3867         // or contains MyCall
3868         if(!m_bBestSPArmed or m_mode!="FT4") {
3869           ui->decodedTextBrowser2->displayDecodedText (decodedtext0, m_config.my_callsign (), m_mode, m_config.DXCC (),
3870                 m_logBook, m_currentBand, m_config.ppfx ());
3871         }
3872         m_QSOText = decodedtext.string ().trimmed ();
3873       }
3874       // Z
3875       if (!isFiltered || !m_config.udpFiltering())
3876       postDecode (true, decodedtext.string ());
3877 
3878       if(m_mode=="FT8" and SpecOp::HOUND==m_config.special_op_id()) {
3879         if(decodedtext.string().contains(";")) {
3880           QStringList w=decodedtext.string().mid(24).split(" ",SkipEmptyParts);
3881           QString foxCall=w.at(3);
3882           foxCall=foxCall.remove("<").remove(">");
3883           if(w.at(0)==m_config.my_callsign() or w.at(0)==Radio::base_callsign(m_config.my_callsign())) {
3884             //### Check for ui->dxCallEntry->text()==foxCall before logging! ###
3885             ui->stopTxButton->click ();
3886             logQSOTimer.start(0);
3887           }
3888           if((w.at(2)==m_config.my_callsign() or w.at(2)==Radio::base_callsign(m_config.my_callsign()))
3889              and ui->tx3->text().length()>0) {
3890             m_rptRcvd=w.at(4);
3891             m_rptSent=decodedtext.string().mid(7,3);
3892             m_nFoxFreq=decodedtext.string().mid(16,4).toInt();
3893             hound_reply ();
3894           }
3895         } else {
3896           QStringList w=decodedtext.string().mid(24).split(" ",SkipEmptyParts);
3897           if(decodedtext.string().contains("/")) w.append(" +00");  //Add a dummy report
3898           if(w.size()>=3) {
3899             QString foxCall=w.at(1);
3900             if((w.at(0)==m_config.my_callsign() or w.at(0)==Radio::base_callsign(m_config.my_callsign())) and
3901                ui->tx3->text().length()>0) {
3902               if(w.at(2)=="RR73") {
3903                 // Z
3904                 log("w.at(2)=='RR73'");
3905                 ui->stopTxButton->click ();
3906                 logQSOTimer.start(0);
3907               } else {
3908                 if(w.at(1)==Radio::base_callsign(ui->dxCallEntry->text()) and
3909                    (w.at(2).mid(0,1)=="+" or w.at(2).mid(0,1)=="-")) {
3910                   m_rptRcvd=w.at(2);
3911                   m_rptSent=decodedtext.string().mid(7,3);
3912                   m_nFoxFreq=decodedtext.string().mid(16,4).toInt();
3913                   hound_reply ();
3914                 }
3915               }
3916             }
3917           }
3918         }
3919       }
3920 
3921 //### I think this is where we are preventing Hounds from spotting Fox ###
3922       // Z
3923       if (!(isFiltered && m_config.autoCQfiltering()))
3924       if(m_mode!="FT8" or (SpecOp::HOUND != m_config.special_op_id())) {
3925         if(m_mode=="FT8" or m_mode=="FT4" or m_mode=="Q65"
3926            or m_mode=="JT4" or m_mode=="JT65" or m_mode=="JT9" or m_mode=="FST4") {
3927           auto_sequence (decodedtext, 25, 50);
3928         }
3929 
3930 // find and extract any report for myCall, but save in m_rptRcvd only if it's from DXcall
3931         QString rpt;
3932         bool stdMsg = decodedtext.report(m_baseCall,
3933             Radio::base_callsign(ui->dxCallEntry->text()), rpt);
3934         QString deCall;
3935         QString grid;
3936         decodedtext.deCallAndGrid(/*out*/deCall,grid);
3937         {
3938           auto t = Radio::base_callsign (ui->dxCallEntry->text ());
3939           auto const& dx_call = decodedtext.call ();
3940           if (rpt.size ()       // report in message
3941               && (m_baseCall == Radio::base_callsign (dx_call) // for us
3942                   || "DE" == dx_call)                          // probably for us
3943               && (t == deCall   // DX station base call is QSO partner
3944                   || ui->dxCallEntry->text () == deCall // DX station full call is QSO partner
3945                   || !t.size ()))                       // not in QSO
3946             {
3947               m_rptRcvd = rpt;
3948             }
3949         }
3950 // extract details and send to PSKreporter
3951         int nsec=QDateTime::currentMSecsSinceEpoch()/1000-m_secBandChanged;
3952         bool okToPost=(nsec > int(4*m_TRperiod)/5);
3953         if(m_mode=="FST4W" and okToPost) {
3954           line_read=line_read.left(22) + " CQ " + line_read.trimmed().mid(22);
3955           auto p = line_read.lastIndexOf (' ');
3956           DecodedText FST4W_post {QString::fromUtf8 (line_read.left (p).constData ())};
3957           pskPost(FST4W_post);
3958         } else {
3959           if (stdMsg && okToPost) pskPost(decodedtext);
3960         }
3961         if((m_mode=="JT4" or m_mode=="JT65" or m_mode=="Q65") and
3962            m_msgAvgWidget!=NULL) {
3963           if(m_msgAvgWidget->isVisible()) {
3964             QFile f(m_config.temp_dir ().absoluteFilePath ("avemsg.txt"));
3965             if(f.open(QIODevice::ReadOnly | QIODevice::Text)) {
3966               QTextStream s(&f);
3967               QString t=s.readAll();
3968               m_msgAvgWidget->displayAvg(t);
3969             }
3970           }
3971         }
3972       }
3973     }
3974   }
3975 }
3976 
3977 //
3978 // start_tolerance - only respond to "DE ..." and free text 73
3979 //                   messages within +/- this value
3980 //
3981 // stop_tolerance - kill Tx if running station is seen to reply to
3982 //                  another caller and we are going to transmit within
3983 //                  +/- this value of the reply to another caller
3984 //
auto_sequence(DecodedText const & message,unsigned start_tolerance,unsigned stop_tolerance)3985 void MainWindow::auto_sequence (DecodedText const& message, unsigned start_tolerance, unsigned stop_tolerance)
3986 {
3987   auto const& message_words = message.messageWords ();
3988   auto is_73 = message_words.filter (QRegularExpression {"^(73|RR73)$"}).size();
3989   bool is_OK=false;
3990   if(m_mode=="MSK144" and message.clean_string ().indexOf(ui->dxCallEntry->text()+" R ")>0) is_OK=true;
3991   if (message_words.size () > 2 && (message.isStandardMessage() || (is_73 or is_OK))) {
3992     auto df = message.frequencyOffset ();
3993     auto within_tolerance = (qAbs (ui->RxFreqSpinBox->value () - df) <= int (start_tolerance)
3994        || qAbs (ui->TxFreqSpinBox->value () - df) <= int (start_tolerance));
3995     bool acceptable_73 = is_73
3996       && m_QSOProgress >= ROGER_REPORT
3997       && ((message.isStandardMessage ()
3998            && (message_words.contains (m_baseCall)
3999                || message_words.contains (m_config.my_callsign ())
4000                || message_words.contains (ui->dxCallEntry->text ())
4001                || message_words.contains (Radio::base_callsign (ui->dxCallEntry->text ()))
4002                || message_words.contains ("DE")))
4003           || !message.isStandardMessage ()); // free text 73/RR73
4004 
4005     QStringList w=message.clean_string ().mid(22).remove("<").remove(">").split(" ",SkipEmptyParts);
4006     QString w2;
4007     int nrpt=0;
4008     if (w.size () > 2)
4009       {
4010         w2=w.at(2);
4011         if(w.size()>3) {
4012           nrpt=w2.toInt();
4013           if(w2=="R") nrpt=w.at(3).toInt();
4014         }
4015       }
4016     bool bEU_VHF=(nrpt>=520001 and nrpt<=594000);
4017     if(bEU_VHF and message.clean_string ().contains("<"+m_config.my_callsign() + "> ")) {
4018       m_xRcvd=message.clean_string ().trimmed().right(13);
4019     }
4020 
4021     //Z
4022     QString hiscall;
4023     QString hisgrid;
4024     message.deCallAndGrid(/*out*/hiscall,hisgrid);
4025 
4026     if (m_auto
4027         && (m_QSOProgress==REPLYING  or (!ui->tx1->isEnabled () and m_QSOProgress==REPORT))
4028         && qAbs (ui->TxFreqSpinBox->value () - df) <= int (stop_tolerance)
4029         && message_words.at (1) != "DE"
4030         && !message_words.at (1).contains (QRegularExpression {"(^(CQ|QRZ))|" + m_baseCall})
4031         && message_words.at (2).contains (Radio::base_callsign (ui->dxCallEntry->text ()))) {
4032       // auto stop to avoid accidental QRM
4033         // Z
4034         log("Automatic TX halt");
4035       ui->stopTxButton->click (); // halt any transmission
4036       // Z
4037       if (ui->cbAutoCQ->isChecked() || ui->cbAutoCall->isChecked()) clearDX();
4038     } else if (m_auto             // transmit allowed
4039         && ui->cbAutoSeq->isChecked () // auto-sequencing allowed
4040         && ((!m_bCallingCQ      // not calling CQ/QRZ
4041         && !m_sentFirst73       // not finished QSO
4042         && ((message_words.at (1).contains (m_baseCall)
4043                   // being called and not already in a QSO
4044         && (message_words.at(2).contains(Radio::base_callsign(ui->dxCallEntry->text())) or bEU_VHF))
4045                  // type 2 compound replies
4046         || (within_tolerance &&
4047             (acceptable_73 ||
4048             ("DE" == message_words.at (1) &&
4049              w2.contains(Radio::base_callsign (m_hisCall)))))))
4050             || (m_bCallingCQ && m_bAutoReply
4051                 // look for type 2 compound call replies on our Tx and Rx offsets
4052                 && ((within_tolerance && "DE" == message_words.at (1))
4053                     || message_words.at (1).contains (m_baseCall))))) {
4054       if(SpecOp::FOX != m_config.special_op_id())
4055       {
4056           log("Processing response");
4057           processMessage (message);
4058           // tx_watchdog(false);
4059       }
4060       // Z
4061     } else if (ui->cbAutoSeq->isChecked()
4062                && message_words.at (1).contains (m_baseCall)
4063                && (ui->cbAutoCQ->isChecked() || ui->cbAutoCall->isChecked())
4064                && m_QSOProgress == CALLING
4065                && !(message_words.filter (QRegularExpression {"^(73)$"}).size())
4066                && (m_config.processTailenders() || m_lastCall == hiscall)
4067                )
4068     {
4069         log("Processing tail-end response");
4070         processMessage (message);
4071         if (!ui->dxCallEntry->text().isEmpty()) auto_tx_mode(true);
4072     }
4073   }
4074 }
4075 
pskPost(DecodedText const & decodedtext)4076 void MainWindow::pskPost (DecodedText const& decodedtext)
4077 {
4078   if (m_diskData || !m_config.spot_to_psk_reporter() || decodedtext.isLowConfidence ()) return;
4079 
4080   QString msgmode=m_mode;
4081   QString deCall;
4082   QString grid;
4083   decodedtext.deCallAndGrid(/*out*/deCall,grid);
4084   int audioFrequency = decodedtext.frequencyOffset();
4085   if(m_mode=="FT8" or m_mode=="MSK144" or m_mode=="FT4") {
4086     audioFrequency=decodedtext.string().mid(16,4).toInt();
4087   }
4088   int snr = decodedtext.snr();
4089   Frequency frequency = m_freqNominal + audioFrequency;
4090   if(grid.contains (grid_regexp)) {
4091 //    qDebug() << "To PSKreporter:" << deCall << grid << frequency << msgmode << snr;
4092     if (!m_psk_Reporter.addRemoteStation (deCall, grid, frequency, msgmode, snr))
4093       {
4094         showStatusMessage (tr ("Spotting to PSK Reporter unavailable"));
4095       }
4096   }
4097 }
4098 
killFile()4099 void MainWindow::killFile ()
4100 {
4101 //  qDebug() << "cc 3725" << m_saveDecoded << m_saveAll << m_bDecoded << m_nDecodes << m_fnameWE;
4102   if (m_fnameWE.size () && !(m_saveAll || (m_saveDecoded && m_bDecoded))) {
4103     QFile f1 {m_fnameWE + ".wav"};
4104     if(f1.exists()) f1.remove();
4105     if(m_mode=="WSPR" or m_mode=="FST4W") {
4106       QFile f2 {m_fnameWE + ".c2"};
4107       if(f2.exists()) f2.remove();
4108     }
4109   }
4110 }
4111 
on_EraseButton_clicked()4112 void MainWindow::on_EraseButton_clicked ()
4113 {
4114   qint64 ms=QDateTime::currentMSecsSinceEpoch();
4115   ui->decodedTextBrowser2->erase ();
4116   if(m_mode=="WSPR" or m_mode=="Echo" or m_mode=="FST4W") {
4117     ui->decodedTextBrowser->erase ();
4118   } else {
4119     if((ms-m_msErase)<500) {
4120       ui->decodedTextBrowser->erase ();
4121       if (m_unfilteredView) m_unfilteredView->erase();
4122     }
4123   }
4124   m_msErase=ms;
4125 }
4126 
band_activity_cleared()4127 void MainWindow::band_activity_cleared ()
4128 {
4129   m_messageClient->decodes_cleared ();
4130   QFile f(m_config.temp_dir ().absoluteFilePath ("decoded.txt"));
4131   if(f.exists()) f.remove();
4132 }
4133 
rx_frequency_activity_cleared()4134 void MainWindow::rx_frequency_activity_cleared ()
4135 {
4136   m_QSOText.clear();
4137   set_dateTimeQSO(-1);          // G4WJS: why do we do this?
4138 }
4139 
decodeBusy(bool b)4140 void MainWindow::decodeBusy(bool b)                             //decodeBusy()
4141 {
4142   if (!b) {
4143     m_optimizingProgress.reset ();
4144   }
4145   m_decoderBusy=b;
4146   ui->DecodeButton->setEnabled(!b);
4147   ui->actionOpen->setEnabled(!b);
4148   ui->actionOpen_next_in_directory->setEnabled(!b);
4149   ui->actionDecode_remaining_files_in_directory->setEnabled(!b);
4150 
4151   statusUpdate ();
4152 }
4153 
4154 //------------------------------------------------------------- //guiUpdate()
guiUpdate()4155 void MainWindow::guiUpdate()
4156 {
4157   static char message[38];
4158   static char msgsent[38];
4159   double txDuration;
4160 
4161   if(m_TRperiod==0) m_TRperiod=60.0;
4162   txDuration=tx_duration(m_mode,m_TRperiod,m_nsps,m_bFast9);
4163   double tx1=0.0;
4164   double tx2=txDuration;
4165   if(m_mode=="FT8" or m_mode=="FT4") icw[0]=0;              //No CW ID in FT4 or FT8 mode
4166   if((icw[0]>0) and (!m_bFast9)) tx2 += icw[0]*2560.0/48000.0;  //Full length including CW ID
4167   if(tx2>m_TRperiod) tx2=m_TRperiod;
4168   if(!m_txFirst and m_mode!="WSPR" and m_mode!="FST4W") {
4169     tx1 += m_TRperiod;
4170     tx2 += m_TRperiod;
4171   }
4172 
4173   qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000;
4174   int nsec=ms/1000;
4175   double tsec=0.001*ms;
4176   double t2p=fmod(tsec,2*m_TRperiod);
4177   double s6=fmod(tsec,6.0);
4178   int nseq = fmod(double(nsec),m_TRperiod);
4179   m_tRemaining=m_TRperiod - fmod(tsec,m_TRperiod);
4180 
4181   if(m_mode=="Echo") {
4182     tx1=0.0;
4183     tx2=txDuration;
4184     if(m_auto and s6>4.0) m_bEchoTxOK=true;
4185     if(m_transmitting) m_bEchoTxed=true;
4186   }
4187 
4188   if(m_mode=="WSPR" or m_mode=="FST4W") {
4189     if(nseq==0 and m_ntr==0) {                   //Decide whether to Tx or Rx
4190       m_tuneup=false;                              //This is not an ATU tuneup
4191       bool btx = m_auto && m_WSPR_tx_next;         // To Tx, we need m_auto and
4192                                                    // scheduled transmit
4193       m_WSPR_tx_next = false;
4194       if(btx) {
4195         m_ntr=-1;                                  //This says we will have transmitted
4196         ui->pbTxNext->setChecked (false);
4197         m_bTxTime=true;                            //Start a WSPR or FST4W Tx sequence
4198       } else {
4199         // This will be a WSPR or FST4W Rx sequence.
4200         m_ntr=1;                                   //This says we will have received
4201         m_bTxTime=false;                           //Start a WSPR or FST4W Rx sequence
4202       }
4203     }
4204 
4205   } else {
4206     // For all modes other than WSPR and FST4W
4207     m_bTxTime = (t2p >= tx1) and (t2p < tx2);
4208     if(m_mode=="Echo") m_bTxTime = m_bTxTime and m_bEchoTxOK;
4209     if(m_mode=="FT8" and ui->tx5->currentText().contains("/B ")) {
4210       //FT8 beacon transmissiion from Tx5 only at top of a UTC minute
4211       double t4p=fmod(tsec,4*m_TRperiod);
4212       if(t4p >= 30.0) m_bTxTime=false;
4213     }
4214   }
4215   if(m_tune) m_bTxTime=true;                 //"Tune" takes precedence
4216 
4217   if(m_transmitting or m_auto or m_tune) {
4218     m_dateTimeLastTX = QDateTime::currentDateTimeUtc ();
4219 
4220 // Check for "txboth" (FT4 testing purposes only)
4221     QFile f(m_appDir + "/txboth");
4222     if(f.exists() and fmod(tsec,m_TRperiod) < (0.5 + 105.0*576.0/12000.0)) m_bTxTime=true;
4223 
4224 // Don't transmit another mode in the 30 m WSPR sub-band
4225     Frequency onAirFreq = m_freqNominal + ui->TxFreqSpinBox->value();
4226     if ((onAirFreq > 10139900 and onAirFreq < 10140320) and m_mode!="WSPR") {
4227       m_bTxTime=false;
4228       if (m_auto) auto_tx_mode (false);
4229       if(onAirFreq!=m_onAirFreq0) {
4230         m_onAirFreq0=onAirFreq;
4231         auto const& message = tr ("Please choose another Tx frequency."
4232                                   " WSJT-X will not knowingly transmit another"
4233                                   " mode in the WSPR sub-band on 30m.");
4234         QTimer::singleShot (0, [=] { // don't block guiUpdate
4235             MessageBox::warning_message (this, tr ("WSPR Guard Band"), message);
4236           });
4237       }
4238     }
4239 
4240     if(m_mode=="FT8" and SpecOp::FOX==m_config.special_op_id()) {
4241 // Don't allow Fox mode in any of the default FT8 sub-bands.
4242       qint32 ft8Freq[]={1840,3573,7074,10136,14074,18100,21074,24915,28074,50313,70100};
4243       for(int i=0; i<11; i++) {
4244         int kHzdiff=m_freqNominal/1000 - ft8Freq[i];
4245         if(qAbs(kHzdiff) < 4) {
4246           m_bTxTime=false;
4247           if (m_auto) auto_tx_mode (false);
4248           auto const& message = tr ("Please choose another dial frequency."
4249                                     " WSJT-X will not operate in Fox mode"
4250                                     " in the standard FT8 sub-bands.");
4251           QTimer::singleShot (0, [=] {               // don't block guiUpdate
4252             MessageBox::warning_message (this, tr ("Fox Mode warning"), message);
4253           });
4254           break;
4255         }
4256       }
4257     }
4258     // Z
4259     if (watchdog() && m_mode!="WSPR" && m_mode!="FST4W"
4260         && m_idleMinutes >= watchdog ()) {
4261       tx_watchdog (true);       // disable transmit
4262     }
4263 
4264     double fTR=float((ms%int(1000.0*m_TRperiod)))/int(1000.0*m_TRperiod);
4265 
4266     QString txMsg;
4267     if(m_ntx == 1) txMsg=ui->tx1->text();
4268     if(m_ntx == 2) txMsg=ui->tx2->text();
4269     if(m_ntx == 3) txMsg=ui->tx3->text();
4270     if(m_ntx == 4) txMsg=ui->tx4->text();
4271     if(m_ntx == 5) txMsg=ui->tx5->currentText();
4272     if(m_ntx == 6) txMsg=ui->tx6->text();
4273     int msgLength=txMsg.trimmed().length();
4274     if(msgLength==0 and !m_tune) on_stopTxButton_clicked();
4275 
4276     if(g_iptt==0 and ((m_bTxTime and (fTR < 0.75) and (msgLength>0)) or m_tune)) {
4277       //### Allow late starts
4278       icw[0]=m_ncw;
4279       g_iptt = 1;
4280       setRig ();
4281       if(m_mode=="FT8") {
4282         if (SpecOp::FOX == m_config.special_op_id()) {
4283           if (ui->TxFreqSpinBox->value() > 900) {
4284             ui->TxFreqSpinBox->setValue(300);
4285           }
4286         }
4287         else if (SpecOp::HOUND == m_config.special_op_id()) {
4288           if(m_auto && !m_tune) {
4289             if (ui->TxFreqSpinBox->value() < 999 && m_ntx != 3) {
4290               // Hound randomized range: 1000-3000 Hz
4291 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
4292               ui->TxFreqSpinBox->setValue (QRandomGenerator::global ()->bounded (1000, 2999));
4293 #else
4294               ui->TxFreqSpinBox->setValue ((qrand () % 2000) + 1000);
4295 #endif
4296             }
4297           }
4298           if (m_nSentFoxRrpt==2 and m_ntx==3) {
4299             // move off the original Fox frequency on subsequent tries of Tx3
4300             int nfreq=m_nFoxFreq + 300;
4301             if(m_nFoxFreq>600) nfreq=m_nFoxFreq - 300;  //keep nfreq below 900 Hz
4302             ui->TxFreqSpinBox->setValue(nfreq);
4303           }
4304           if (m_nSentFoxRrpt == 1) {
4305             ++m_nSentFoxRrpt;
4306           }
4307         }
4308       }
4309 
4310 
4311 // If HoldTxFreq is not checked, randomize Fox's Tx Freq
4312 // NB: Maybe this should be done no more than once every 5 minutes or so ?
4313       if(m_mode=="FT8" and SpecOp::FOX==m_config.special_op_id() and !ui->cbHoldTxFreq->isChecked()) {
4314 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
4315         ui->TxFreqSpinBox->setValue (QRandomGenerator::global ()->bounded (300, 599));
4316 #else
4317         ui->TxFreqSpinBox->setValue(300.0 + 300.0*double(qrand())/RAND_MAX);
4318 #endif
4319       }
4320 
4321       setXIT (ui->TxFreqSpinBox->value ());
4322       m_config.transceiver_ptt (true); //Assert the PTT
4323       m_tx_when_ready = true;
4324     }
4325 //    if(!m_bTxTime and !m_tune and m_mode!="FT4") m_btxok=false;       //Time to stop transmitting
4326     if(!m_bTxTime and !m_tune) m_btxok=false;       //Time to stop transmitting
4327   }
4328 
4329   if((m_mode=="WSPR" or m_mode=="FST4W") and
4330      ((m_ntr==1 and m_rxDone) or (m_ntr==-1 and nseq>tx2))) {
4331     if(m_monitoring) {
4332       m_rxDone=false;
4333     }
4334     if(m_transmitting) {
4335       WSPR_history(m_freqNominal,-1);
4336       m_bTxTime=false;                        //Time to stop a WSPR or FST4W transmission
4337       m_btxok=false;
4338     }
4339     else if (m_ntr != -1) {
4340       WSPR_scheduling ();
4341       m_ntr=0;                                //This WSPR or FST4W Rx sequence is complete
4342     }
4343   }
4344 
4345 
4346   // Calculate Tx tones when needed
4347   if((g_iptt==1 && m_iptt0==0) || m_restart) {
4348 //----------------------------------------------------------------------
4349     QByteArray ba;
4350     QByteArray ba0;
4351 
4352     if(m_mode=="WSPR") {
4353       ba=WSPR_message().toLatin1();
4354     } else {
4355       if(SpecOp::HOUND == m_config.special_op_id() and m_ntx!=3) {   //Hound transmits only Tx1 or Tx3
4356         m_ntx=1;
4357         ui->txrb1->setChecked(true);
4358       }
4359 
4360       if(m_mode=="FT4" and m_bBestSPArmed) {
4361         m_BestCQpriority="";
4362         m_bBestSPArmed=false;
4363         ui->pbBestSP->setStyleSheet ("");
4364       }
4365 
4366       if(m_ntx == 1) ba=ui->tx1->text().toLocal8Bit();
4367       if(m_ntx == 2) ba=ui->tx2->text().toLocal8Bit();
4368       if(m_ntx == 3) ba=ui->tx3->text().toLocal8Bit();
4369       if(m_ntx == 4) ba=ui->tx4->text().toLocal8Bit();
4370       if(m_ntx == 5) ba=ui->tx5->currentText().toLocal8Bit();
4371       if(m_ntx == 6) ba=ui->tx6->text().toLocal8Bit();
4372     }
4373 
4374     ba2msg(ba,message);
4375     int ichk=0;
4376     if (m_lastMessageSent != m_currentMessage
4377         || m_lastMessageType != m_currentMessageType)
4378       {
4379         m_lastMessageSent = m_currentMessage;
4380         m_lastMessageType = m_currentMessageType;
4381       }
4382     m_currentMessageType = 0;
4383     if(m_tune or m_mode=="Echo") {
4384       itone[0]=0;
4385     } else {
4386       if(m_QSOProgress==REPORT || m_QSOProgress==ROGER_REPORT) m_bSentReport=true;
4387       if(m_bSentReport and (m_QSOProgress<REPORT or m_QSOProgress>ROGER_REPORT)) m_bSentReport=false;
4388       if(m_mode=="JT4") gen4_(message, &ichk , msgsent, const_cast<int *> (itone),
4389                                 &m_currentMessageType, 22, 22);
4390       if(m_mode=="JT9") gen9_(message, &ichk, msgsent, const_cast<int *> (itone),
4391                                 &m_currentMessageType, 22, 22);
4392       if(m_mode=="JT65") gen65_(message, &ichk, msgsent, const_cast<int *> (itone),
4393                                   &m_currentMessageType, 22, 22);
4394       if(m_mode=="WSPR") genwspr_(message, msgsent, const_cast<int *> (itone),
4395                                     22, 22);
4396       if(m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4"
4397          or m_mode=="FST4" or m_mode=="FST4W" || "Q65" == m_mode) {
4398         if(m_mode=="MSK144") {
4399           genmsk_128_90_(message, &ichk, msgsent, const_cast<int *> (itone),
4400                          &m_currentMessageType, 37, 37);
4401           if(m_restart) {
4402             int nsym=144;
4403             if(itone[40]==-40) nsym=40;
4404             m_modulator->set_nsym(nsym);
4405           }
4406         }
4407 
4408         if(m_mode=="FT8") {
4409           if(SpecOp::FOX==m_config.special_op_id() and ui->tabWidget->currentIndex()==1) {
4410             foxTxSequencer();
4411           } else {
4412             int i3=0;
4413             int n3=0;
4414             char ft8msgbits[77];
4415             genft8_(message, &i3, &n3, msgsent, const_cast<char *> (ft8msgbits),
4416                     const_cast<int *> (itone), 37, 37);
4417             int nsym=79;
4418             int nsps=4*1920;
4419             float fsample=48000.0;
4420             float bt=2.0;
4421             float f0=ui->TxFreqSpinBox->value() - m_XIT;
4422             int icmplx=0;
4423             int nwave=nsym*nsps;
4424             gen_ft8wave_(const_cast<int *>(itone),&nsym,&nsps,&bt,&fsample,&f0,foxcom_.wave,
4425                          foxcom_.wave,&icmplx,&nwave);
4426             if(SpecOp::FOX == m_config.special_op_id()) {
4427               //Fox must generate the full Tx waveform, not just an itone[] array.
4428               QString fm = QString::fromStdString(message).trimmed();
4429               foxGenWaveform(0,fm);
4430               foxcom_.nslots=1;
4431               foxcom_.nfreq=ui->TxFreqSpinBox->value();
4432               if(m_config.split_mode()) foxcom_.nfreq = foxcom_.nfreq - m_XIT;  //Fox Tx freq
4433               QString foxCall=m_config.my_callsign() + "         ";
4434               ::memcpy(foxcom_.mycall, foxCall.toLatin1(), sizeof foxcom_.mycall); //Copy Fox callsign into foxcom_
4435               foxgen_();
4436             }
4437           }
4438         }
4439         if(m_mode=="FT4") {
4440           int ichk=0;
4441           char ft4msgbits[77];
4442           genft4_(message, &ichk, msgsent, const_cast<char *> (ft4msgbits),
4443                   const_cast<int *>(itone), 37, 37);
4444           int nsym=103;
4445           int nsps=4*576;
4446           float fsample=48000.0;
4447           float f0=ui->TxFreqSpinBox->value() - m_XIT;
4448           int nwave=(nsym+2)*nsps;
4449           int icmplx=0;
4450           gen_ft4wave_(const_cast<int *>(itone),&nsym,&nsps,&fsample,&f0,foxcom_.wave,
4451                        foxcom_.wave,&icmplx,&nwave);
4452         }
4453         if(m_mode=="FST4" or m_mode=="FST4W") {
4454           int ichk=0;
4455           int iwspr=0;
4456           char fst4msgbits[101];
4457           QString wmsg;
4458           if(m_mode=="FST4W") {
4459             iwspr = 1;
4460             wmsg=WSPR_message();
4461             ba=wmsg.toLatin1();
4462             ba2msg(ba,message);
4463           }
4464           genfst4_(message,&ichk,msgsent,const_cast<char *> (fst4msgbits),
4465                    const_cast<int *>(itone), &iwspr, 37, 37);
4466           int hmod=1;
4467           if(m_config.x2ToneSpacing()) hmod=2;
4468           if(m_config.x4ToneSpacing()) hmod=4;
4469           int nsps=720;
4470           if(m_TRperiod==30) nsps=1680;
4471           if(m_TRperiod==60) nsps=3888;
4472           if(m_TRperiod==120) nsps=8200;
4473           if(m_TRperiod==300) nsps=21504;
4474           if(m_TRperiod==900) nsps=66560;
4475           if(m_TRperiod==1800) nsps=134400;
4476           nsps=4*nsps;                           //48000 Hz sampling
4477           int nsym=160;
4478           float fsample=48000.0;
4479           float dfreq=hmod*fsample/nsps;
4480           float f0=ui->TxFreqSpinBox->value() - m_XIT + 1.5*dfreq;
4481           if(m_mode=="FST4W") f0=ui->WSPRfreqSpinBox->value() - m_XIT + 1.5*dfreq;
4482           int nwave=(nsym+2)*nsps;
4483           int icmplx=0;
4484           gen_fst4wave_(const_cast<int *>(itone),&nsym,&nsps,&nwave,
4485                         &fsample,&hmod,&f0,&icmplx,foxcom_.wave,foxcom_.wave);
4486 
4487           QString t = QString::fromStdString(message).trimmed();
4488         }
4489         if(m_mode=="Q65") {
4490           int i3=-1;
4491           int n3=-1;
4492           genq65_(message,&ichk,msgsent,const_cast<int *>(itone),&i3,&n3,37,37);
4493           int nsps=1800;
4494           if(m_TRperiod==30) nsps=3600;
4495           if(m_TRperiod==60) nsps=7200;
4496           if(m_TRperiod==120) nsps=16000;
4497           if(m_TRperiod==300) nsps=41472;
4498           int nsps4=4*nsps;                           //48000 Hz sampling
4499           int nsym=85;
4500           float fsample=48000.0;
4501           int nwave=(nsym+2)*nsps4;
4502           int icmplx=0;
4503           int hmod=1;
4504           float f0=ui->TxFreqSpinBox->value()-m_XIT;
4505           genwave_(const_cast<int *>(itone),&nsym,&nsps4,&nwave,
4506                    &fsample,&hmod,&f0,&icmplx,foxcom_.wave,foxcom_.wave);
4507         }
4508 
4509         if(SpecOp::EU_VHF==m_config.special_op_id()) {
4510           if(m_ntx==2) m_xSent=ui->tx2->text().right(13);
4511           if(m_ntx==3) m_xSent=ui->tx3->text().right(13);
4512         }
4513 
4514         if(SpecOp::FIELD_DAY==m_config.special_op_id() or SpecOp::RTTY==m_config.special_op_id()) {
4515           if(m_ntx==2 or m_ntx==3) {
4516             QStringList t=ui->tx2->text().split(' ', SkipEmptyParts);
4517             int n=t.size();
4518             m_xSent=t.at(n-2) + " " + t.at(n-1);
4519           }
4520         }
4521       }
4522       msgsent[37]=0;
4523     }
4524 
4525     {
4526       auto temp = m_currentMessage;
4527       m_currentMessage = QString::fromLatin1(msgsent);
4528       if (m_currentMessage != temp) // check if tx message changed
4529       {
4530           statusUpdate ();
4531         }
4532     }
4533     m_bCallingCQ = 6 == m_ntx
4534       || m_currentMessage.contains (QRegularExpression {"^(CQ|QRZ) "});
4535     if(m_mode=="FT8" or m_mode=="FT4") {
4536       if(m_bCallingCQ && ui->cbFirst->isVisible () && ui->cbFirst->isChecked ()) {
4537         ui->cbFirst->setStyleSheet("QCheckBox{color:red}");
4538       } else {
4539         ui->cbFirst->setStyleSheet("");
4540       }
4541     }
4542 
4543     if (m_tune) {
4544       m_currentMessage = "TUNE";
4545       m_currentMessageType = -1;
4546     }
4547     if(m_restart) {
4548       write_all("Tx",m_currentMessage);
4549       if (m_config.TX_messages ()) {
4550         ui->decodedTextBrowser2->displayTransmittedText(m_currentMessage.trimmed(),m_mode,
4551                      ui->TxFreqSpinBox->value(),m_bFastMode,m_TRperiod);
4552         log("TX message: " + m_currentMessage.trimmed());
4553         }
4554     }
4555 
4556     auto t2 = QDateTime::currentDateTimeUtc ().toString ("hhmm");
4557     icw[0] = 0;
4558     auto msg_parts = m_currentMessage.split (' ', SkipEmptyParts);
4559     if (msg_parts.size () > 2) {
4560       // clean up short code forms
4561       msg_parts[0].remove (QChar {'<'});
4562       msg_parts[0].remove (QChar {'>'});
4563       msg_parts[1].remove (QChar {'<'});
4564       msg_parts[1].remove (QChar {'>'});
4565     }
4566     auto is_73 = message_is_73 (m_currentMessageType, msg_parts);
4567     m_sentFirst73 = is_73
4568       && !message_is_73 (m_lastMessageType, m_lastMessageSent.split (' ', SkipEmptyParts));
4569     // Z
4570     if (m_sentFirst73 || is_73) {
4571       m_qsoStop=t2;
4572       if(m_config.id_after_73 ()) {
4573         icw[0] = m_ncw;
4574       }
4575       // Z
4576       if((m_config.prompt_to_log() or m_config.autoLog()
4577           or ui->cbAutoCQ->isChecked() or ui->cbAutoCall->isChecked()) && !m_tune && CALLING != m_QSOProgress)
4578         {
4579           logQSOTimer.start(0);
4580         }
4581     }
4582 
4583     // Z
4584     bool b=(m_mode=="FT8" or m_mode=="FT4") and ui->cbAutoSeq->isChecked ();
4585     if(is_73 and (m_config.disable_TX_on_73() or b)) {
4586       m_nextCall="";  //### Temporary: disable use of "TU;" messages;
4587       if(m_nextCall!="") {
4588         useNextCall();
4589       } else {
4590           // Z
4591           if (!ui->cbAutoCQ->isChecked())
4592         auto_tx_mode (false);
4593         if(b) {
4594           m_ntx=6;
4595           ui->txrb6->setChecked(true);
4596           m_QSOProgress = CALLING;
4597           //  Z
4598           if (m_lastCall == m_hisCall) clearDX();
4599         }
4600       }
4601     }
4602 
4603     if(m_config.id_interval () >0) {
4604       int nmin=(m_sec0-m_secID)/60;
4605       if(m_sec0<m_secID) nmin=m_config.id_interval();
4606       if(nmin >= m_config.id_interval()) {
4607         icw[0]=m_ncw;
4608         m_secID=m_sec0;
4609       }
4610     }
4611 
4612     if ((m_currentMessageType < 6 || 7 == m_currentMessageType)
4613         && msg_parts.length() >= 3
4614         && (msg_parts[1] == m_config.my_callsign () ||
4615             msg_parts[1] == m_baseCall))
4616     {
4617       int i1;
4618       bool ok;
4619       i1 = msg_parts[2].toInt(&ok);
4620       if(ok and i1>=-50 and i1<50)
4621       {
4622         m_rptSent = msg_parts[2];
4623         m_qsoStart = t2;
4624       } else {
4625         if (msg_parts[2].mid (0, 1) == "R")
4626         {
4627           i1 = msg_parts[2].mid (1).toInt (&ok);
4628           if (ok and i1 >= -50 and i1 < 50)
4629           {
4630             m_rptSent = msg_parts[2].mid (1);
4631             m_qsoStart = t2;
4632           }
4633         }
4634       }
4635     }
4636     m_restart=false;
4637 //----------------------------------------------------------------------
4638   } else {
4639     if (!m_auto && m_sentFirst73) {
4640       m_sentFirst73 = false;
4641     }
4642   }
4643 
4644   if (g_iptt == 1 && m_iptt0 == 0) {
4645     auto const& current_message = QString::fromLatin1 (msgsent);
4646     if(watchdog () && m_mode!="WSPR" && m_mode!="FST4W"
4647        && current_message != m_msgSent0) {
4648       tx_watchdog (false);  // in case we are auto sequencing
4649       m_msgSent0 = current_message;
4650     }
4651 
4652     if (m_mode != "FST4W" && m_mode != "WSPR")
4653       {
4654         if(!m_tune) write_all("Tx",m_currentMessage);
4655         if (m_config.TX_messages () && !m_tune && SpecOp::FOX!=m_config.special_op_id())
4656           {
4657             ui->decodedTextBrowser2->displayTransmittedText(current_message.trimmed(),
4658                   m_mode,ui->TxFreqSpinBox->value(),m_bFastMode,m_TRperiod);
4659             log("TX message: " + current_message.trimmed());
4660           }
4661       }
4662 
4663     switch (m_ntx)
4664     {
4665       case 1: m_QSOProgress = REPLYING; break;
4666       case 2: m_QSOProgress = REPORT; break;
4667       case 3: m_QSOProgress = ROGER_REPORT; break;
4668       case 4: m_QSOProgress = ROGERS; break;
4669       case 5: m_QSOProgress = SIGNOFF; break;
4670       case 6: m_QSOProgress = CALLING; break;
4671       default: break;             // determined elsewhere
4672     }
4673     m_transmitting = true;
4674     transmitDisplay (true);
4675     statusUpdate ();
4676   }
4677 
4678   if(!m_btxok && m_btxok0 && g_iptt==1) {
4679     stopTx();
4680     if ("1" == m_env.value ("WSJT_TX_BOTH", "0")) {
4681       m_txFirst = !m_txFirst;
4682       ui->txFirstCheckBox->setChecked (m_txFirst);
4683     }
4684   }
4685 
4686   if(m_startAnother) {
4687     if(m_mode=="MSK144") {
4688       m_wait++;
4689     }
4690     if(m_mode!="MSK144" or m_wait>=4) {
4691       m_wait=0;
4692       m_startAnother=false;
4693       on_actionOpen_next_in_directory_triggered();
4694     }
4695   }
4696 
4697   if(m_mode=="FT8" or m_mode=="MSK144" or m_mode=="FT4") {
4698     if(ui->txrb1->isEnabled() and
4699        (SpecOp::NA_VHF==m_config.special_op_id() or
4700         SpecOp::FIELD_DAY==m_config.special_op_id() or
4701         SpecOp::RTTY==m_config.special_op_id() or
4702         SpecOp::WW_DIGI==m_config.special_op_id()) ) {
4703       //We're in a contest-like mode other than EU_VHF: start QSO with Tx2.
4704       ui->tx1->setEnabled(false);
4705       ui->txb1->setEnabled(false);
4706     }
4707     if(!ui->tx1->isEnabled() and SpecOp::EU_VHF==m_config.special_op_id()) {
4708       //We're in EU_VHF mode: start QSO with Tx1.
4709       ui->tx1->setEnabled(true);
4710       ui->txb1->setEnabled(true);
4711     }
4712   }
4713 
4714 //Once per second (onesec)
4715   if(nsec != m_sec0) {
4716 //    qDebug() << "AAA" << nsec;
4717 
4718     if(m_mode=="FST4") chk_FST4_freq_range();
4719     m_currentBand=m_config.bands()->find(m_freqNominal);
4720     // Z
4721     /*
4722     if( SpecOp::HOUND == m_config.special_op_id() ) {
4723       qint32 tHound=QDateTime::currentMSecsSinceEpoch()/1000 - m_tAutoOn;
4724       //To keep calling Fox, Hound must reactivate Enable Tx at least once every 2 minutes
4725       if(tHound >= 120 and m_ntx==1) auto_tx_mode(false);
4726     }*/
4727 
4728     progressBar.setVisible(true);
4729     progressBar.setFormat ("%v/%m");
4730     if(m_auto and m_mode=="Echo" and m_bEchoTxOK) {
4731       progressBar.setMaximum(3);
4732       progressBar.setValue(int(s6));
4733     }
4734     if(m_mode!="Echo") {
4735       if(m_monitoring or m_transmitting) {
4736         progressBar.setMaximum(m_TRperiod);
4737         int isec=int(fmod(tsec,m_TRperiod));
4738         if(m_TRperiod-int(m_TRperiod)>0.0) {
4739           QString progBarLabel;
4740           progBarLabel = progBarLabel.asprintf("%d/%3.1f",isec,m_TRperiod);
4741           progressBar.setFormat (progBarLabel);
4742         }
4743         progressBar.setValue(isec);
4744         // Z
4745         if (isec == 0) ZProcess();
4746       } else {
4747         progressBar.setValue(0);
4748       }
4749     }
4750 
4751     astroUpdate ();
4752     // Z
4753     QString green = "QProgressBar::chunk {background: #00aa00; border-bottom-right-radius: 5px;border-bottom-left-radius: 5px;border: .px solid black; text-align: center}";
4754     QString red = "QProgressBar::chunk {background: #ff0000; border-bottom-right-radius: 5px;border-bottom-left-radius: 5px;border: .px solid black; text-align: center}";
4755 
4756     if(m_transmitting) {
4757         // Z
4758         progressBar.setStyleSheet(red);
4759       char s[42];
4760       if(SpecOp::FOX==m_config.special_op_id() and ui->tabWidget->currentIndex()==1) {
4761         sprintf(s,"Tx:  %d Slots",foxcom_.nslots);
4762       } else {
4763         sprintf(s,"Tx: %s",msgsent);
4764       }
4765       m_nsendingsh=0;
4766       if(s[4]==64) m_nsendingsh=1;
4767       if(m_nsendingsh==1 or m_currentMessageType==7) {
4768         tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #66ffff}");
4769       } else if(m_nsendingsh==-1 or m_currentMessageType==6) {
4770         tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #ffccff}");
4771       } else {
4772         tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #ffff33}");
4773       }
4774       if(m_tune) {
4775         tx_status_label.setText("Tx: TUNE");
4776       } else {
4777         if(m_mode=="Echo") {
4778           tx_status_label.setText("Tx: ECHO");
4779         } else {
4780           s[40]=0;
4781           QString t{QString::fromLatin1(s)};
4782           if(SpecOp::FOX==m_config.special_op_id() and ui->tabWidget->currentIndex()==1 and foxcom_.nslots==1) {
4783               t=m_fm1.trimmed();
4784           }
4785           if(m_mode=="FT4") t="Tx: "+ m_currentMessage;
4786           tx_status_label.setText(t.trimmed());
4787         }
4788       }
4789     } else if(m_monitoring) {
4790         // Z
4791         progressBar.setStyleSheet(green);
4792       if (!m_tx_watchdog) {
4793         tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #00aa00}");
4794         auto t = tr ("Receiving");
4795         if(m_mode=="MSK144") {
4796           int npct=int(100.0*m_fCPUmskrtd/0.298667);
4797           if(npct>90) tx_status_label.setStyleSheet("QLabel{color: #000000; background-color: #ff0000}");
4798           t += QString {"   %1%"}.arg (npct, 2);
4799         }
4800         tx_status_label.setText (t);
4801       }
4802       transmitDisplay(false);
4803     } else if (!m_diskData && !m_tx_watchdog) {
4804       tx_status_label.setStyleSheet("");
4805       tx_status_label.setText("");
4806     }
4807 
4808     QDateTime t = QDateTime::currentDateTimeUtc();
4809     // Z
4810     QString utc = t.time().toString() + " ";
4811 
4812     ui->labUTC->setText(utc);
4813     if(m_bBestSPArmed and (m_dateTimeBestSP.secsTo(t) >= 120)) on_pbBestSP_clicked(); //BestSP timeout
4814     if(!m_monitoring and !m_diskData) ui->signal_meter_widget->setValue(0,0);
4815     m_sec0=nsec;
4816     displayDialFrequency ();
4817   }
4818   m_iptt0=g_iptt;
4819   m_btxok0=m_btxok;
4820 }               //End of guiUpdate
4821 
useNextCall()4822 void MainWindow::useNextCall()
4823 {
4824   ui->dxCallEntry->setText(m_nextCall);
4825   m_nextCall="";
4826   if(m_nextGrid.contains(grid_regexp)) {
4827     ui->dxGridEntry->setText(m_nextGrid);
4828     m_ntx=2;
4829     ui->txrb2->setChecked(true);
4830   } else {
4831     m_ntx=3;
4832     ui->txrb3->setChecked(true);
4833   }
4834   genStdMsgs(m_nextRpt);
4835 }
4836 
startTx2()4837 void MainWindow::startTx2()
4838 {
4839   if (!m_modulator->isActive ()) { // TODO - not thread safe
4840     double fSpread=0.0;
4841     double snr=99.0;
4842     QString t=ui->tx5->currentText();
4843     if(t.mid(0,1)=="#") fSpread=t.mid(1,5).toDouble();
4844     m_modulator->setSpread(fSpread); // TODO - not thread safe
4845     t=ui->tx6->text();
4846     if(t.mid(0,1)=="#") snr=t.mid(1,5).toDouble();
4847     if(snr>0.0 or snr < -50.0) snr=99.0;
4848     if((m_ntx==6 or m_ntx==7) and m_config.force_call_1st()) {
4849       ui->cbAutoSeq->setChecked(true);
4850       ui->cbFirst->setChecked(true);
4851     }
4852     transmit (snr);
4853     ui->signal_meter_widget->setValue(0,0);
4854     if(m_mode=="Echo" and !m_tune) m_bTransmittedEcho=true;
4855 
4856     if((m_mode=="WSPR" or m_mode=="FST4W") and !m_tune) {
4857       if (m_config.TX_messages ()) {
4858         t = " Transmitting " + m_mode + " ----------------------- " +
4859           m_config.bands ()->find (m_freqNominal);
4860         t=beacon_start_time (m_TRperiod / 2) + ' ' + t.rightJustified (66, '-');
4861         ui->decodedTextBrowser->appendText(t);
4862       }
4863       write_all("Tx",m_currentMessage);
4864     }
4865   }
4866 }
4867 
stopTx()4868 void MainWindow::stopTx()
4869 {
4870   Q_EMIT endTransmitMessage ();
4871   m_btxok = false;
4872   m_transmitting = false;
4873   g_iptt=0;
4874   if (!m_tx_watchdog) {
4875     tx_status_label.setStyleSheet("");
4876     tx_status_label.setText("");
4877   }
4878   ptt0Timer.start(200);                       //end-of-transmission sequencer delay
4879   monitor (true);
4880   statusUpdate ();
4881 }
4882 
stopTx2()4883 void MainWindow::stopTx2()
4884 {
4885   m_config.transceiver_ptt (false); //Lower PTT
4886   if (m_mode == "JT9" && m_bFast9
4887       && ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isEnabled () && ui->cbAutoSeq->isChecked ()
4888       && m_ntx == 5 && m_nTx73 >= 5) {
4889     on_stopTxButton_clicked ();
4890     m_nTx73 = 0;
4891   }
4892   if(((m_mode=="WSPR" or m_mode=="FST4W") and m_ntr==-1) and !m_tuneup) {
4893     m_wideGraph->setWSPRtransmitted();
4894     WSPR_scheduling ();
4895     m_ntr=0;
4896   }
4897   last_tx_label.setText(tr ("Last Tx: %1").arg (m_currentMessage.trimmed()));
4898 }
4899 
ba2msg(QByteArray ba,char message[])4900 void MainWindow::ba2msg(QByteArray ba, char message[])             //ba2msg()
4901 {
4902   int iz=ba.length();
4903   for(int i=0; i<37; i++) {
4904     if(i<iz) {
4905       if(int(ba[i])>=97 and int(ba[i])<=122) ba[i]=int(ba[i])-32;
4906       message[i]=ba[i];
4907     } else {
4908       message[i]=32;
4909     }
4910   }
4911   message[37]=0;
4912 }
4913 
on_txFirstCheckBox_stateChanged(int nstate)4914 void MainWindow::on_txFirstCheckBox_stateChanged(int nstate)        //TxFirst
4915 {
4916   m_txFirst = (nstate==2);
4917 }
4918 
set_dateTimeQSO(int m_ntx)4919 void MainWindow::set_dateTimeQSO(int m_ntx)
4920 {
4921     // m_ntx = -1 resets to default time
4922     // Our QSO start time can be fairly well determined from Tx 2 and Tx 3 -- the grid reports
4923     // If we CQ'd and sending sigrpt then 2 minutes ago n=2
4924     // If we're on msg 3 then 3 minutes ago n=3 -- might have sat on msg1 for a while
4925     // If we've already set our time on just return.
4926     // This should mean that Tx2 or Tx3 has been repeated so don't update the start time
4927     // We reset it in several places
4928     if (m_ntx == -1) { // we use a default date to detect change
4929       m_dateTimeQSOOn = QDateTime {};
4930     }
4931     else if (m_dateTimeQSOOn.isValid ()) {
4932         return;
4933     }
4934     else { // we also take of m_TRperiod/2 to allow for late clicks
4935       auto now = QDateTime::currentDateTimeUtc();
4936       m_dateTimeQSOOn = now.addSecs (-(m_ntx - 2) * int(m_TRperiod) -
4937                                      int(fmod(double(now.time().second()),m_TRperiod)));
4938     }
4939 }
4940 
set_ntx(int n)4941 void MainWindow::set_ntx(int n)                                   //set_ntx()
4942 {
4943   m_ntx=n;
4944 }
4945 
on_txrb1_toggled(bool status)4946 void MainWindow::on_txrb1_toggled (bool status)
4947 {
4948   if (status) {
4949     log("Toggled: TXRB1");
4950     if (ui->tx1->isEnabled ()) {
4951       m_ntx = 1;
4952       set_dateTimeQSO (-1); // we reset here as tx2/tx3 is used for start times
4953     }
4954     else {
4955       QTimer::singleShot (0, ui->txrb2, SLOT (click ()));
4956     }
4957   }
4958 }
4959 
elide_tx1_not_allowed() const4960 bool MainWindow::elide_tx1_not_allowed () const
4961 {
4962   auto const& my_callsign = m_config.my_callsign ();
4963   return
4964     (m_mode=="FT8" && SpecOp::HOUND == m_config.special_op_id())
4965     || ((m_mode.startsWith ("FT") || "MSK144" == m_mode || "Q65" == m_mode || "FST4" == m_mode)
4966         && Radio::is_77bit_nonstandard_callsign (my_callsign))
4967     || (my_callsign != m_baseCall && !shortList (my_callsign));
4968 }
4969 
on_txrb1_doubleClicked()4970 void MainWindow::on_txrb1_doubleClicked ()
4971 {
4972   ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ());
4973   if (!ui->tx1->isEnabled ()) {
4974     // leave time for clicks to complete before setting txrb2
4975     QTimer::singleShot (500, ui->txrb2, SLOT (click ()));
4976   }
4977 }
4978 
on_txrb2_toggled(bool status)4979 void MainWindow::on_txrb2_toggled (bool status)
4980 {
4981   // Tx 2 means we already have CQ'd so good reference
4982   if (status) {
4983     log("Toggled: TXRB2");
4984     m_ntx=2;
4985     set_dateTimeQSO (m_ntx);
4986   }
4987 }
4988 
on_txrb3_toggled(bool status)4989 void MainWindow::on_txrb3_toggled(bool status)
4990 {
4991   // Tx 3 means we should have already have done Tx 1 so good reference
4992   if (status) {
4993     log("Toggled: TXRB3");
4994     m_ntx=3;
4995     set_dateTimeQSO(m_ntx);
4996   }
4997 }
4998 
on_txrb4_toggled(bool status)4999 void MainWindow::on_txrb4_toggled (bool status)
5000 {
5001   if (status) {
5002     log("Toggled: TXRB4");
5003     m_ntx=4;
5004   }
5005 }
5006 
on_txrb4_doubleClicked()5007 void MainWindow::on_txrb4_doubleClicked ()
5008 {
5009   // RR73 only allowed if not a type 2 compound callsign
5010   auto const& my_callsign = m_config.my_callsign ();
5011   auto is_compound = my_callsign != m_baseCall;
5012   m_send_RR73 = !((is_compound && !shortList (my_callsign)) || m_send_RR73);
5013   if(m_mode=="FT4") m_send_RR73=true;
5014   genStdMsgs (m_rpt);
5015 }
5016 
on_txrb5_toggled(bool status)5017 void MainWindow::on_txrb5_toggled (bool status)
5018 {
5019   if (status) {
5020     log("Toggled: TXRB5");
5021     m_ntx = 5;
5022   }
5023 }
5024 
on_txrb5_doubleClicked()5025 void MainWindow::on_txrb5_doubleClicked ()
5026 {
5027   genStdMsgs (m_rpt, true);
5028 }
5029 
on_txrb6_toggled(bool status)5030 void MainWindow::on_txrb6_toggled(bool status)
5031 {
5032   if (status) {
5033     // Z
5034     log("Toggled: TXRB6");
5035     if(ui->cbAutoCQ->isChecked()) auto_tx_mode(true);
5036 
5037     m_ntx=6;
5038     if (ui->txrb6->text().contains (QRegularExpression {"^(CQ|QRZ) "})) set_dateTimeQSO(-1);
5039   }
5040 }
5041 
on_txb1_clicked()5042 void MainWindow::on_txb1_clicked()
5043 {
5044   if (ui->tx1->isEnabled ()) {
5045     m_ntx=1;
5046     m_QSOProgress = REPLYING;
5047     ui->txrb1->setChecked(true);
5048     if(m_transmitting) m_restart=true;
5049   }
5050   else {
5051     on_txb2_clicked ();
5052   }
5053 }
5054 
on_txb1_doubleClicked()5055 void MainWindow::on_txb1_doubleClicked()
5056 {
5057   ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ());
5058 }
5059 
on_txb2_clicked()5060 void MainWindow::on_txb2_clicked()
5061 {
5062     m_ntx=2;
5063     m_QSOProgress = REPORT;
5064     ui->txrb2->setChecked(true);
5065     if(m_transmitting) m_restart=true;
5066 }
5067 
on_txb3_clicked()5068 void MainWindow::on_txb3_clicked()
5069 {
5070     m_ntx=3;
5071     m_QSOProgress = ROGER_REPORT;
5072     ui->txrb3->setChecked(true);
5073     if(m_transmitting) m_restart=true;
5074 }
5075 
on_txb4_clicked()5076 void MainWindow::on_txb4_clicked()
5077 {
5078     m_ntx=4;
5079     m_QSOProgress = ROGERS;
5080     ui->txrb4->setChecked(true);
5081     if(m_transmitting) m_restart=true;
5082 }
5083 
on_txb4_doubleClicked()5084 void MainWindow::on_txb4_doubleClicked()
5085 {
5086   // RR73 only allowed if not a type 2 compound callsign
5087   auto const& my_callsign = m_config.my_callsign ();
5088   auto is_compound = my_callsign != m_baseCall;
5089   m_send_RR73 = !((is_compound && !shortList (my_callsign)) || m_send_RR73);
5090   if(m_mode=="FT4") m_send_RR73=true;
5091   genStdMsgs (m_rpt);
5092 }
5093 
on_txb5_clicked()5094 void MainWindow::on_txb5_clicked()
5095 {
5096     m_ntx=5;
5097     m_QSOProgress = SIGNOFF;
5098     ui->txrb5->setChecked(true);
5099     if(m_transmitting) m_restart=true;
5100 }
5101 
on_txb5_doubleClicked()5102 void MainWindow::on_txb5_doubleClicked()
5103 {
5104   genStdMsgs (m_rpt, true);
5105 }
5106 
on_txb6_clicked()5107 void MainWindow::on_txb6_clicked()
5108 {
5109     m_ntx=6;
5110     m_QSOProgress = CALLING;
5111     set_dateTimeQSO(-1);
5112     ui->txrb6->setChecked(true);
5113     if(m_transmitting) m_restart=true;
5114 }
5115 
doubleClickOnCall2(Qt::KeyboardModifiers modifiers)5116 void MainWindow::doubleClickOnCall2(Qt::KeyboardModifiers modifiers)
5117 {
5118 //Confusing: come here after double-click on left text window, not right window.
5119   set_dateTimeQSO(-1); // reset our QSO start time
5120   m_decodedText2=true;
5121   doubleClickOnCall(modifiers);
5122   m_decodedText2=false;
5123 }
5124 
doubleClickOnCall(Qt::KeyboardModifiers modifiers)5125 void MainWindow::doubleClickOnCall(Qt::KeyboardModifiers modifiers)
5126 {
5127   // Z
5128   tx_watchdog(false);
5129 
5130   QTextCursor cursor;
5131   if(m_mode=="FST4W") {
5132     MessageBox::information_message (this,
5133         "Double-click not available for FST4W mode");
5134     return;
5135   }
5136   if(m_decodedText2) {
5137     cursor=ui->decodedTextBrowser->textCursor();
5138   } else {
5139     cursor=ui->decodedTextBrowser2->textCursor();
5140   }
5141 
5142   if(modifiers==(Qt::ShiftModifier + Qt::ControlModifier + Qt::AltModifier)) {
5143     //### What was the purpose of this ???  ###
5144     cursor.setPosition(0);
5145   } else {
5146     cursor.setPosition(cursor.selectionStart());
5147   }
5148 
5149   if(SpecOp::FOX==m_config.special_op_id() and m_decodedText2) {
5150     if(m_houndQueue.count()<10 and m_nSortedHounds>0) {
5151       QString t=cursor.block().text();
5152       selectHound(t);
5153     }
5154     return;
5155   }
5156   DecodedText message {cursor.block().text().trimmed().left(61).remove("TU; ")};
5157   m_bDoubleClicked = true;
5158   processMessage (message, modifiers);
5159 }
5160 
processMessage(DecodedText const & message,Qt::KeyboardModifiers modifiers)5161 void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifiers modifiers)
5162 {
5163   // decode keyboard modifiers we are interested in
5164   auto shift = modifiers.testFlag (Qt::ShiftModifier);
5165   auto ctrl = modifiers.testFlag (Qt::ControlModifier);
5166   // auto alt = modifiers.testFlag (Qt::AltModifier);
5167   auto auto_seq = ui->cbAutoSeq->isChecked ();
5168   // basic mode sanity checks
5169   auto const& parts = message.clean_string ().split (' ', SkipEmptyParts);
5170   if (parts.size () < 5) return;
5171   auto const& mode = parts.at (4).left (1);
5172   if (("JT65" == m_mode && mode != "#")
5173       || ("JT9" == m_mode && mode != "@")
5174       || ("MSK144" == m_mode && !("&" == mode || "^" == mode))
5175       || ("Q65" == m_mode && mode.left (1) != ":")) {
5176     return;      //Currently we do auto-sequencing only in FT4, FT8, MSK144, FST4, and Q65
5177   }
5178 
5179   //Skip the rest if no decoded text extracted
5180   int frequency = message.frequencyOffset();
5181   if (message.isTX()) {
5182     if (!m_config.enable_VHF_features()) {
5183       if(!shift) ui->RxFreqSpinBox->setValue(frequency); //Set Rx freq
5184       if((ctrl or shift) and !ui->cbHoldTxFreq->isChecked ()) {
5185         ui->TxFreqSpinBox->setValue(frequency); //Set Tx freq
5186       }
5187     }
5188     return;
5189   }
5190 
5191   // check for CQ with listening frequency
5192   if (parts.size () >= 7
5193       && (m_bFastMode || m_mode=="FT8")
5194       && "CQ" == parts[5]
5195       && m_config.is_transceiver_online ()) {
5196     bool ok;
5197     auto kHz = parts[6].toUInt (&ok);
5198     if (ok && kHz >= 10 && 3 == parts[6].size ()) {
5199       // QSY Freq for answering CQ nnn
5200       setRig (m_freqNominal / 1000000 * 1000000 + 1000 * kHz);
5201       ui->decodedTextBrowser2->displayQSY (QString {"QSY %1"}.arg (m_freqNominal / 1e6, 7, 'f', 3));
5202     }
5203   }
5204 
5205   int nmod = fmod(double(message.timeInSeconds()),2.0*m_TRperiod);
5206   m_txFirst=(nmod!=0);
5207   if( SpecOp::HOUND == m_config.special_op_id() ) m_txFirst=false;          //Hound must not transmit first
5208   if( SpecOp::FOX == m_config.special_op_id() ) m_txFirst=true;             //Fox must always transmit first
5209   ui->txFirstCheckBox->setChecked(m_txFirst);
5210 
5211   auto const& message_words = message.messageWords ();
5212   if (message_words.size () < 2) return;
5213 
5214   QString hiscall;
5215   QString hisgrid;
5216   message.deCallAndGrid(/*out*/hiscall,hisgrid);
5217 
5218   if(message.clean_string ().contains(hiscall+"/R")) {
5219     hiscall+="/R";
5220     ui->dxCallEntry->setText(hiscall);
5221   }
5222   if(message.clean_string ().contains(hiscall+"/P")) {
5223     hiscall+="/P";
5224     ui->dxCallEntry->setText(hiscall);
5225   }
5226 
5227   QStringList w=message.clean_string ().mid(22).remove("<").remove(">").split(" ",SkipEmptyParts);
5228 
5229   // Z
5230   dxLookup(hiscall, hisgrid);
5231   if (ui->cb_autoModeSwitch->isChecked()) resetAutoSwitch();
5232   int nw=w.size();
5233   if(nw>=4) {
5234     if(message_words.size()<3) return;
5235     int n=w.at(nw-2).toInt();
5236     if(n>=520001 and n<=592047) {
5237       hiscall=w.at(1);
5238       hisgrid=w.at(nw-1);
5239     }
5240   }
5241 
5242   bool is_73 = message_words.filter (QRegularExpression {"^(73|RR73)$"}).size ();
5243   if (!is_73 and !message.isStandardMessage() and !message.clean_string ().contains("<")) {
5244     qDebug () << "Not processing message - hiscall:" << hiscall << "hisgrid:" << hisgrid
5245               << message.clean_string () << message.isStandardMessage();
5246     return;
5247   }
5248 
5249   if ((message.isJT9 () and m_mode != "JT9" and m_mode != "JT4") or
5250              (message.isJT65 () and m_mode != "JT65" and m_mode != "JT4")) {
5251     // We are not allowing mode change, so don't process decode
5252     return;
5253   }
5254 
5255   // ignore calls by other hounds
5256   if (SpecOp::HOUND == m_config.special_op_id()
5257       && message.messageWords ().indexOf (QRegularExpression {R"(R\+-[0-9]+)"}) >= 0)
5258     {
5259       return;
5260     }
5261 
5262   QString firstcall = message.call();
5263   if(firstcall.length()==5 and firstcall.mid(0,3)=="CQ ") firstcall="CQ";
5264   if(!m_bFastMode and (!m_config.enable_VHF_features() or m_mode=="FT8")) {
5265     // Don't change Tx freq if in a fast mode, or VHF features enabled; also not if a
5266     // station is calling me, unless CTRL or SHIFT is held down.
5267     if ((Radio::is_callsign (firstcall)
5268          && firstcall != m_config.my_callsign () && firstcall != m_baseCall
5269          && firstcall != "DE")
5270         || "CQ" == firstcall || "QRZ" == firstcall || ctrl || shift) {
5271       if (((SpecOp::HOUND != m_config.special_op_id()) || m_mode != "FT8")
5272           && (!ui->cbHoldTxFreq->isChecked () || shift || ctrl)) {
5273         ui->TxFreqSpinBox->setValue(frequency);
5274       }
5275       if(m_mode != "JT4" && m_mode != "JT65" && !m_mode.startsWith ("JT9") &&
5276          m_mode != "Q65" && m_mode!="FT8" && m_mode!="FT4" && m_mode!="FST4") {
5277         return;
5278       }
5279     }
5280   }
5281 
5282   // prior DX call (possible QSO partner)
5283   auto qso_partner_base_call = Radio::base_callsign (ui->dxCallEntry->text ());
5284   auto base_call = Radio::base_callsign (hiscall);
5285 
5286 // Determine appropriate response to received message
5287   auto dtext = " " + message.clean_string () + " ";
5288   dtext=dtext.remove("<").remove(">");
5289   if(dtext.contains (" " + m_baseCall + " ")
5290      || dtext.contains ("<" + m_baseCall + "> ")
5291 //###???     || dtext.contains ("<" + m_baseCall + " " + hiscall + "> ")
5292      || dtext.contains ("/" + m_baseCall + " ")
5293      || dtext.contains (" " + m_baseCall + "/")
5294      || (firstcall == "DE")) {
5295 
5296     QString w2;
5297     int nw=w.size();
5298     if(nw>=3) w2=w.at(2);
5299     int nrpt=w2.toInt();
5300     QString w34;
5301     if(nw>=4) {
5302 //      w34=w.at(nw-2);
5303       nrpt=w.at(nw-2).toInt();
5304       w34=w.at(nw-1);
5305     }
5306     bool bRTTY = (nrpt>=529 and nrpt<=599);
5307     bool bEU_VHF_w2=(nrpt>=520001 and nrpt<=594000);
5308     if(bEU_VHF_w2 and SpecOp::EU_VHF!=m_config.special_op_id()) {
5309       auto const& msg = tr("Should you switch to EU VHF Contest mode?\n\n"
5310                                "To do so, check 'Special operating activity' and\n"
5311                                "'EU VHF Contest' on the Settings | Advanced tab.");
5312       MessageBox::information_message (this, msg);
5313     }
5314 
5315     QStringList t=message.clean_string ().split(' ', SkipEmptyParts);
5316     int n=t.size();
5317     QString t0=t.at(n-2);
5318     QString t1=t0.right(1);
5319     bool bFieldDay_msg = (t1>="A" and t1<="F" and t0.size()<=3 and n>=9);
5320     int m=t0.remove(t1).toInt();
5321     if(m < 1) bFieldDay_msg=false;
5322     if(bFieldDay_msg) {
5323       m_xRcvd=t.at(n-2) + " " + t.at(n-1);
5324       t0=t.at(n-3);
5325     }
5326     if(bFieldDay_msg and SpecOp::FIELD_DAY!=m_config.special_op_id()) {
5327       // ### Should be in ARRL Field Day mode ??? ###
5328       MessageBox::information_message (this, tr ("Should you switch to ARRL Field Day mode?"));
5329     }
5330 
5331     if(bRTTY and SpecOp::RTTY != m_config.special_op_id()) {
5332       // ### Should be in RTTY contest mode ??? ###
5333       MessageBox::information_message (this, tr ("Should you switch to RTTY contest mode?"));
5334     }
5335 
5336     if(SpecOp::EU_VHF==m_config.special_op_id() and message_words.at(1).contains(m_baseCall) and
5337        (!message_words.at(2).contains(qso_partner_base_call)) and (!m_bDoubleClicked)) {
5338       return;
5339     }
5340 
5341     bool bContestOK=(m_mode=="FT4" or m_mode=="FT8" or m_mode=="Q65" or m_mode=="MSK144");
5342     if(message_words.size () > 3   // enough fields for a normal message
5343        && (message_words.at(1).contains(m_baseCall) || "DE" == message_words.at(1))
5344        && (message_words.at(2).contains(qso_partner_base_call) or m_bDoubleClicked
5345            or bEU_VHF_w2 or (m_QSOProgress==CALLING))) {
5346       if(message_words.at(3).contains(grid_regexp) and SpecOp::EU_VHF!=m_config.special_op_id()) {
5347         if((SpecOp::NA_VHF==m_config.special_op_id() or SpecOp::WW_DIGI==m_config.special_op_id()) and bContestOK){
5348           setTxMsg(3);
5349           m_QSOProgress=ROGER_REPORT;
5350         } else {
5351           if(m_mode=="JT65" and message_words.size()>4 and message_words.at(4)=="OOO") {
5352             setTxMsg(3);
5353             m_QSOProgress=ROGER_REPORT;
5354           } else {
5355             setTxMsg(2);
5356             m_QSOProgress=REPORT;
5357           }
5358         }
5359       } else if(w34.contains(grid_regexp) and SpecOp::EU_VHF==m_config.special_op_id()) {
5360 
5361         if(nrpt==0) {
5362           setTxMsg(2);
5363           m_QSOProgress=REPORT;
5364         } else {
5365           if(w2=="R") {
5366             setTxMsg(4);
5367             m_QSOProgress=ROGERS;
5368           } else {
5369             setTxMsg(3);
5370             m_QSOProgress=ROGER_REPORT;
5371           }
5372         }
5373       } else if(SpecOp::RTTY == m_config.special_op_id() and bRTTY) {
5374         if(w2=="R") {
5375           setTxMsg(4);
5376           m_QSOProgress=ROGERS;
5377         } else {
5378           setTxMsg(3);
5379           m_QSOProgress=ROGER_REPORT;
5380         }
5381         m_xRcvd=t[n-2] + " " + t[n-1];
5382       } else if(SpecOp::FIELD_DAY==m_config.special_op_id() and bFieldDay_msg) {
5383         if(t0=="R") {
5384           setTxMsg(4);
5385           m_QSOProgress=ROGERS;
5386         } else {
5387           setTxMsg(3);
5388           m_QSOProgress=ROGER_REPORT;
5389         }
5390       } else {  // no grid on end of msg
5391         auto const& word_3 = message_words.at (3);
5392         auto word_3_as_number = word_3.toInt ();
5393         if (("RRR" == word_3
5394              || (word_3_as_number == 73 && ROGERS == m_QSOProgress)
5395              || "RR73" == word_3
5396              || ("R" == word_3 && m_QSOProgress != REPORT))) {
5397           if(m_mode=="FT4" and "RR73" == word_3) m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc();
5398           m_bTUmsg=false;
5399           m_nextCall="";   //### Temporary: disable use of "TU;" message
5400           if(SpecOp::RTTY == m_config.special_op_id() and m_nextCall!="") {
5401             // We're in RTTY contest and have "nextCall" queued up: send a "TU; ..." message
5402             logQSOTimer.start(0);
5403             ui->tx3->setText(ui->tx3->text().remove("TU; "));
5404             useNextCall();
5405             QString t="TU; " + ui->tx3->text();
5406             ui->tx3->setText(t);
5407             m_bTUmsg=true;
5408           } else {
5409             if (m_QSOProgress > CALLING && m_QSOProgress < SIGNOFF
5410                 && SpecOp::NONE < m_config.special_op_id () && SpecOp::FOX > m_config.special_op_id ()
5411                 && ("RR73" == word_3 || 73 == word_3_as_number))
5412               {
5413                 logQSOTimer.start(0);
5414                 m_ntx=6;
5415                 ui->txrb6->setChecked(true);
5416               }
5417             else if (word_3.contains (QRegularExpression {"^R(?!R73|RR)"})
5418                      && m_QSOProgress != ROGER_REPORT)
5419               {
5420                 m_ntx=4;
5421                 ui->txrb4->setChecked(true);
5422               }
5423             else if ((m_QSOProgress > CALLING && m_QSOProgress < ROGERS)
5424                      || word_3.contains (QRegularExpression {"^RR(?:R|73)$"}))
5425               {
5426                 m_ntx=5;
5427                 ui->txrb5->setChecked(true);
5428               }
5429             else if (ROGERS == m_QSOProgress)
5430               {
5431                 logQSOTimer.start(0);
5432                 m_ntx=6;
5433                 ui->txrb6->setChecked(true);
5434               }
5435             else
5436               {
5437                 // just work them (again)
5438                 if (ui->tx1->isEnabled ()) {
5439                   m_ntx = 1;
5440                   m_QSOProgress = REPLYING;
5441                   ui->txrb1->setChecked (true);
5442                 } else {
5443                   m_ntx=2;
5444                   m_QSOProgress = REPORT;
5445                   ui->txrb2->setChecked (true);
5446                 }
5447               }
5448           }
5449           if (m_QSOProgress >= ROGER_REPORT)
5450             {
5451               m_QSOProgress = SIGNOFF;
5452             }
5453         } else if((m_QSOProgress >= REPORT
5454                    || (m_QSOProgress >= REPLYING &&
5455                    (m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4" || "Q65" == m_mode)))
5456                   && word_3.startsWith ('R')) {
5457           m_ntx=4;
5458           m_QSOProgress = ROGERS;
5459           if(SpecOp::RTTY == m_config.special_op_id()) {
5460             int n=t.size();
5461             int nRpt=t[n-2].toInt();
5462             if(nRpt>=529 and nRpt<=599) m_xRcvd=t[n-2] + " " + t[n-1];
5463           }
5464           ui->txrb4->setChecked(true);
5465         } else if (m_QSOProgress >= CALLING)
5466           {
5467             if ((word_3_as_number >= -50 && word_3_as_number <= 49)
5468                 || (word_3_as_number >= 529 && word_3_as_number <= 599))
5469               {
5470                 if(SpecOp::EU_VHF==m_config.special_op_id() or
5471                    SpecOp::FIELD_DAY==m_config.special_op_id() or
5472                    SpecOp::RTTY==m_config.special_op_id())
5473                   {
5474                     setTxMsg(2);
5475                     m_QSOProgress=REPORT;
5476                   }
5477                 else
5478                   {
5479                     if (word_3.startsWith ("R-") || word_3.startsWith ("R+"))
5480                       {
5481                         setTxMsg(4);
5482                         m_QSOProgress=ROGERS;
5483                       }
5484                     else
5485                       {
5486                         setTxMsg (3);
5487                         m_QSOProgress = ROGER_REPORT;
5488                       }
5489                   }
5490               }
5491           }
5492         else
5493           {                // nothing for us
5494             return;
5495           }
5496       }
5497     }
5498     else if (m_QSOProgress >= ROGERS
5499              && message_words.size () > 2 && message_words.at (1).contains (m_baseCall)
5500              && message_words.at (2) == "73") {
5501       // 73 back to compound call holder
5502       m_ntx=5;
5503       ui->txrb5->setChecked(true);
5504       m_QSOProgress = SIGNOFF;
5505     }
5506     else if (!(m_bAutoReply && (m_QSOProgress > CALLING))) {
5507       if ((message_words.size () > 4 && message_words.at (1).contains (m_baseCall)
5508            && message_words.at (4) == "OOO")) {
5509         // EME short code report or MSK144/FT8 contest mode reply, send back Tx3
5510         m_ntx=3;
5511         m_QSOProgress = ROGER_REPORT;
5512         ui->txrb3->setChecked (true);
5513       } else if (!is_73) {    // don't respond to sign off messages
5514         m_ntx=2;
5515         m_QSOProgress = REPORT;
5516         ui->txrb2->setChecked(true);
5517         if (m_bDoubleClickAfterCQnnn and m_transmitting) {
5518           on_stopTxButton_clicked();
5519           TxAgainTimer.start(1500);
5520         }
5521         m_bDoubleClickAfterCQnnn=false;
5522       }
5523       else {
5524         return;               // nothing we need to respond to
5525       }
5526     }
5527     else {                  // nothing for us
5528       return;
5529     }
5530   }
5531   else if (firstcall == "DE" && message_words.size () > 3 && message_words.at (3) == "73") {
5532     if (m_QSOProgress >= ROGERS && base_call == qso_partner_base_call && m_currentMessageType) {
5533       // 73 back to compound call holder
5534       m_ntx=5;
5535       ui->txrb5->setChecked(true);
5536       m_QSOProgress = SIGNOFF;
5537     } else {
5538       // treat like a CQ/QRZ
5539       if (ui->tx1->isEnabled ()) {
5540         m_ntx = 1;
5541         m_QSOProgress = REPLYING;
5542         ui->txrb1->setChecked (true);
5543       } else {
5544         m_ntx=2;
5545         m_QSOProgress = REPORT;
5546         ui->txrb2->setChecked (true);
5547       }
5548     }
5549   }
5550   else if (is_73 && !message.isStandardMessage ()) {
5551     m_ntx=5;
5552     ui->txrb5->setChecked(true);
5553     m_QSOProgress = SIGNOFF;
5554   } else {
5555     // just work them
5556     if (ui->tx1->isEnabled ()) {
5557       m_ntx = 1;
5558       m_QSOProgress = REPLYING;
5559       ui->txrb1->setChecked (true);
5560     } else {
5561       m_ntx=2;
5562       m_QSOProgress = REPORT;
5563       ui->txrb2->setChecked (true);
5564     }
5565   }
5566   // if we get here then we are reacting to the message
5567   if (m_bAutoReply) m_bCallingCQ = CALLING == m_QSOProgress;
5568   if (ui->RxFreqSpinBox->isEnabled () and m_mode != "MSK144" and !shift) {
5569     ui->RxFreqSpinBox->setValue (frequency);    //Set Rx freq
5570   }
5571 
5572   QString s1 = m_QSOText.trimmed ();
5573   QString s2 = message.clean_string ().trimmed();
5574   if (s1!=s2 and !message.isTX()) {
5575     if (!s2.contains(m_baseCall) or m_mode=="MSK144") {  // Taken care of elsewhere if for_us and slow mode
5576       ui->decodedTextBrowser2->displayDecodedText (message, m_config.my_callsign (), m_mode, m_config.DXCC (),
5577       m_logBook, m_currentBand, m_config.ppfx ());
5578     }
5579     m_QSOText = s2;
5580   }
5581 
5582   if (Radio::is_callsign (hiscall)
5583       && (base_call != qso_partner_base_call || base_call != hiscall)) {
5584     if (qso_partner_base_call != base_call) {
5585       // clear the DX grid if the base call of his call is different
5586       // from the current DX call
5587       ui->dxGridEntry->clear ();
5588     }
5589     // his base call different or his call more qualified
5590     // i.e. compound version of same base call
5591     ui->dxCallEntry->setText (hiscall);
5592   }
5593   if (hisgrid.contains (grid_regexp)) {
5594     if(ui->dxGridEntry->text().mid(0,4) != hisgrid) ui->dxGridEntry->setText(hisgrid);
5595   }
5596   lookup();
5597   m_hisGrid = ui->dxGridEntry->text();
5598 
5599   if (!m_bSentReport || base_call != qso_partner_base_call) // Don't change report within a QSO
5600     {
5601       auto n = message.report ().toInt ();
5602       if(m_mode=="MSK144" and m_bShMsgs) {
5603         if(n<=-2) n=-3;
5604         if(n>=-1 and n<=1) n=0;
5605         if(n>=2 and n<=4) n=3;
5606         if(n>=5 and n<=7) n=6;
5607         if(n>=8 and n<=11) n=10;
5608         if(n>=12 and n<=14) n=13;
5609         if(n>=15) n=16;
5610       }
5611       ui->rptSpinBox->setValue (n);
5612     }
5613 // Don't genStdMsgs if we're already sending 73, or a "TU; " msg is queued.
5614   m_bTUmsg=false;   //### Temporary: disable use of "TU;" messages
5615   if (!m_nTx73 and !m_bTUmsg) {
5616     genStdMsgs (QString::number (ui->rptSpinBox->value ()));
5617   }
5618   if(m_transmitting) m_restart=true;
5619   if (auto_seq && !m_bDoubleClicked && m_mode!="FT4") {
5620     return;
5621   }
5622   if(m_config.quick_call() && m_bDoubleClicked) auto_tx_mode(true);
5623   m_bDoubleClicked=false;
5624 }
5625 
setTxMsg(int n)5626 void MainWindow::setTxMsg(int n)
5627 {
5628   m_ntx=n;
5629   if(n==1) ui->txrb1->setChecked(true);
5630   if(n==2) ui->txrb2->setChecked(true);
5631   if(n==3) ui->txrb3->setChecked(true);
5632   if(n==4) ui->txrb4->setChecked(true);
5633   if(n==5) ui->txrb5->setChecked(true);
5634   if(n==6) ui->txrb6->setChecked(true);
5635 }
5636 
genCQMsg()5637 void MainWindow::genCQMsg ()
5638 {
5639   auto const& my_callsign = m_config.my_callsign ();
5640   auto is_compound = my_callsign != m_baseCall;
5641   auto is_type_two = !is77BitMode () && is_compound && stdCall (m_baseCall) && !shortList (my_callsign);
5642   if(my_callsign.size () && m_config.my_grid().size ()) {
5643     auto const& grid = m_config.my_grid ();
5644     if (ui->cbCQTx->isEnabled () && ui->cbCQTx->isVisible () && ui->cbCQTx->isChecked ()) {
5645       if(stdCall (my_callsign)
5646          || is_type_two) {
5647         msgtype (QString {"CQ %1 %2 %3"}
5648                .arg (m_freqNominal / 1000 - m_freqNominal / 1000000 * 1000, 3, 10, QChar {'0'})
5649                .arg (my_callsign)
5650                .arg (grid.left (4)),
5651                ui->tx6);
5652       } else {
5653         msgtype (QString {"CQ %1 %2"}
5654                .arg (m_freqNominal / 1000 - m_freqNominal / 1000000 * 1000, 3, 10, QChar {'0'})
5655                .arg (my_callsign),
5656                ui->tx6);
5657       }
5658     } else {
5659       if (stdCall (my_callsign)
5660           || is_type_two) {
5661         msgtype (QString {"%1 %2 %3"}.arg(m_CQtype).arg(my_callsign)
5662                  .arg(grid.left(4)),ui->tx6);
5663       } else {
5664         msgtype (QString {"%1 %2"}.arg(m_CQtype).arg(my_callsign),ui->tx6);
5665       }
5666     }
5667     if ((m_mode=="JT4" or m_mode=="Q65") and  ui->cbShMsgs->isChecked()) {
5668       if (ui->cbTx6->isChecked ()) {
5669         msgtype ("@1250  (SEND MSGS)", ui->tx6);
5670       } else {
5671         msgtype ("@1000  (TUNE)", ui->tx6);
5672       }
5673     }
5674 
5675     QString t=ui->tx6->text();
5676     QStringList tlist=t.split(" ");
5677     if((m_mode=="FT4" or m_mode=="FT8" or m_mode=="MSK144" || "Q65" == m_mode) and
5678        SpecOp::NONE != m_config.special_op_id() and
5679        ( tlist.at(1)==my_callsign or
5680          tlist.at(2)==my_callsign ) and
5681        stdCall(my_callsign)) {
5682       if(SpecOp::NA_VHF == m_config.special_op_id())    m_cqStr="TEST";
5683       if(SpecOp::EU_VHF == m_config.special_op_id())    m_cqStr="TEST";
5684       if(SpecOp::FIELD_DAY == m_config.special_op_id()) m_cqStr="FD";
5685       if(SpecOp::RTTY == m_config.special_op_id())      m_cqStr="RU";
5686       if(SpecOp::WW_DIGI == m_config.special_op_id())   m_cqStr="WW";
5687       if( tlist.at(1)==my_callsign ) {
5688          t="CQ " + m_cqStr + " " + tlist.at(1) + " " + tlist.at(2);
5689       } else {
5690          t="CQ " + m_cqStr + " " + tlist.at(2) + " " + tlist.at(3);
5691       }
5692       ui->tx6->setText(t);
5693     }
5694   } else {
5695     ui->tx6->clear ();
5696   }
5697 }
5698 
abortQSO()5699 void MainWindow::abortQSO()
5700 {
5701   bool b=m_auto;
5702   clearDX();
5703   if(b) auto_tx_mode(false);
5704   ui->txrb6->setChecked(true);
5705 }
5706 
stdCall(QString const & w)5707 bool MainWindow::stdCall(QString const& w)
5708 {
5709   static QRegularExpression standard_call_re {
5710     R"(
5711         ^\s*                                      # optional leading spaces
5712         ( [A-Z]{0,2} | [A-Z][0-9] | [0-9][A-Z] )  # part 1
5713         ( [0-9][A-Z]{0,3} )                       # part 2
5714         (/R | /P)?                                # optional suffix
5715         \s*$                                      # optional trailing spaces
5716     )", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption};
5717   return standard_call_re.match (w).hasMatch ();
5718 }
5719 
is77BitMode() const5720 bool MainWindow::is77BitMode () const
5721 {
5722   return "FT8" == m_mode || "FT4" == m_mode || "MSK144" == m_mode
5723     || "FST4" == m_mode || "Q65" == m_mode;
5724 }
5725 
genStdMsgs(QString rpt,bool unconditional)5726 void MainWindow::genStdMsgs(QString rpt, bool unconditional)
5727 {
5728   genCQMsg ();
5729   auto const& hisCall=ui->dxCallEntry->text();
5730   if(!hisCall.size ()) {
5731     labAz.clear ();
5732     ui->tx1->clear ();
5733     ui->tx2->clear ();
5734     ui->tx3->clear ();
5735     ui->tx4->clear ();
5736     if(unconditional) ui->tx5->lineEdit ()->clear ();   //Test if it needs sending again
5737     m_gen_message_is_cq = false;
5738     return;
5739   }
5740   auto const& my_callsign = m_config.my_callsign ();
5741   auto is_compound = my_callsign != m_baseCall;
5742   auto is_type_one = !is77BitMode () && is_compound && shortList (my_callsign);
5743   auto const& my_grid = m_config.my_grid ().left (4);
5744   auto const& hisBase = Radio::base_callsign (hisCall);
5745   auto eme_short_codes = m_config.enable_VHF_features () && ui->cbShMsgs->isChecked ()
5746       && m_mode == "JT65";
5747 
5748   bool bMyCall=stdCall(my_callsign);
5749   bool bHisCall=stdCall(hisCall);
5750 
5751   QString t0=hisBase + " " + m_baseCall + " ";
5752   QString t0s=hisCall + " " + my_callsign + " ";
5753   QString t0a,t0b;
5754 
5755   if (is77BitMode () && bHisCall && bMyCall) t0=hisCall + " " + my_callsign + " ";
5756   t0a="<"+hisCall + "> " + my_callsign + " ";
5757   t0b=hisCall + " <" + my_callsign + "> ";
5758 
5759   QString t00=t0;
5760   QString t {t0 + my_grid};
5761   if(!bMyCall) t=t0a;
5762   msgtype(t, ui->tx1);
5763   if (eme_short_codes) {
5764     t=t+" OOO";
5765     msgtype(t, ui->tx2);
5766     msgtype("RO", ui->tx3);
5767     msgtype("RRR", ui->tx4);
5768     msgtype("73", ui->tx5->lineEdit());
5769   } else {
5770     int n=rpt.toInt();
5771     rpt = rpt.asprintf("%+2.2d",n);
5772 
5773     if (is77BitMode ()) {
5774       QString t2,t3;
5775       QString sent=rpt;
5776       QString rs,rst;
5777       int nn=(n+36)/6;
5778       if(nn<2) nn=2;
5779       if(nn>9) nn=9;
5780       rst = rst.asprintf("5%1d9 ",nn);
5781       rs=rst.mid(0,2);
5782       t=t0;
5783       if(!bMyCall) {
5784         t=t0b;
5785         msgtype(t0a, ui->tx1);
5786       }
5787       if(!bHisCall) {
5788         t=t0a;
5789         msgtype(t0a + my_grid, ui->tx1);
5790       }
5791       if(SpecOp::NA_VHF==m_config.special_op_id()) sent=my_grid;
5792       if(SpecOp::WW_DIGI==m_config.special_op_id()) sent=my_grid;
5793       if(SpecOp::FIELD_DAY==m_config.special_op_id()) sent=m_config.Field_Day_Exchange();
5794       if(SpecOp::RTTY==m_config.special_op_id()) {
5795         sent=rst + m_config.RTTY_Exchange();
5796         QString t1=m_config.RTTY_Exchange();
5797         if(t1=="DX" or t1=="#") {
5798           t1 = t1.asprintf("%4.4d",ui->sbSerialNumber->value());
5799           sent=rst + t1;
5800         }
5801       }
5802       if(SpecOp::EU_VHF==m_config.special_op_id()) {
5803         QString a;
5804         t="<" + t0s.split(" ").at(0) + "> <" + t0s.split(" ").at(1) + "> ";
5805         a = a.asprintf("%4.4d ",ui->sbSerialNumber->value());
5806         sent=rs + a + m_config.my_grid();
5807       }
5808       msgtype(t + sent, ui->tx2);
5809       if(sent==rpt) msgtype(t + "R" + sent, ui->tx3);
5810       if(sent!=rpt) msgtype(t + "R " + sent, ui->tx3);
5811       if(m_mode=="FT4" and SpecOp::RTTY==m_config.special_op_id()) {
5812         QDateTime now=QDateTime::currentDateTimeUtc();
5813         int sinceTx3 = m_dateTimeSentTx3.secsTo(now);
5814         int sinceRR73 = m_dateTimeRcvdRR73.secsTo(now);
5815         if(m_bDoubleClicked and (sinceTx3 < 15) and (sinceRR73 < 3)) {
5816           t="TU; " + ui->tx3->text();
5817           ui->tx3->setText(t);
5818         }
5819       }
5820     }
5821 
5822     if(m_mode=="MSK144" and m_bShMsgs) {
5823       int i=t0s.length()-1;
5824       t0="<" + t0s.mid(0,i) + "> ";
5825       if(SpecOp::NA_VHF != m_config.special_op_id()) {
5826         if(n<=-2) n=-3;
5827         if(n>=-1 and n<=1) n=0;
5828         if(n>=2 and n<=4) n=3;
5829         if(n>=5 and n<=7) n=6;
5830         if(n>=8 and n<=11) n=10;
5831         if(n>=12 and n<=14) n=13;
5832         if(n>=15) n=16;
5833         rpt = rpt.asprintf("%+2.2d",n);
5834       }
5835     }
5836 
5837     if (!is77BitMode ()) {
5838       t=(is_type_one ? t0 : t00) + rpt;
5839       msgtype(t, ui->tx2);
5840       t=t0 + "R" + rpt;
5841       msgtype(t, ui->tx3);
5842     }
5843 
5844     if(m_mode=="MSK144" and m_bShMsgs) {
5845       if(m_config.special_op_id()==SpecOp::NONE) {
5846         t=t0 + "R" + rpt;
5847         msgtype(t, ui->tx3);
5848       }
5849       m_send_RR73=false;
5850     }
5851 
5852     t=t0 + (m_send_RR73 ? "RR73" : "RRR");
5853     if((m_mode=="MSK144" and !m_bShMsgs) or m_mode=="FT8" or m_mode=="FT4" || m_mode == "FST4") {
5854       if(!bHisCall and bMyCall) t=hisCall + " <" + my_callsign + "> " + (m_send_RR73 ? "RR73" : "RRR");
5855       if(bHisCall and !bMyCall) t="<" + hisCall + "> " + my_callsign + " " + (m_send_RR73 ? "RR73" : "RRR");
5856     }
5857     if ((m_mode=="JT4" || m_mode=="Q65") && m_bShMsgs) t="@1500  (RRR)";
5858     msgtype(t, ui->tx4);
5859 
5860     t=t0 + "73";
5861     if((m_mode=="MSK144" and !m_bShMsgs) or m_mode=="FT8" or m_mode=="FT4" || m_mode == "FST4") {
5862       if(!bHisCall and bMyCall) t=hisCall + " <" + my_callsign + "> 73";
5863       if(bHisCall and !bMyCall) t="<" + hisCall + "> " + my_callsign + " 73";
5864     }
5865     if (m_mode=="JT4" || m_mode=="Q65") {
5866       if (m_bShMsgs) t="@1750  (73)";
5867       msgtype(t, ui->tx5->lineEdit());
5868     } else if ("MSK144" == m_mode && m_bShMsgs) {
5869       msgtype(t, ui->tx5->lineEdit());
5870     } else if(unconditional || hisBase != m_lastCallsign || !m_lastCallsign.size ()) {
5871       // only update tx5 when forced or  callsign changes
5872       msgtype(t, ui->tx5->lineEdit());
5873       m_lastCallsign = hisBase;
5874     }
5875   }
5876 
5877   if (is77BitMode ()) return;
5878 
5879   if (is_compound) {
5880     if (is_type_one) {
5881       t=hisBase + " " + my_callsign;
5882       msgtype(t, ui->tx1);
5883     } else {
5884       t = "DE " + my_callsign + " ";
5885       switch (m_config.type_2_msg_gen ())
5886         {
5887         case Configuration::type_2_msg_1_full:
5888           msgtype(t + my_grid, ui->tx1);
5889           if (!eme_short_codes) {
5890             if(is77BitMode () && SpecOp::NA_VHF == m_config.special_op_id()) {
5891               msgtype(t + "R " + my_grid, ui->tx3); // #### Unreachable code
5892             } else {
5893               msgtype(t + "R" + rpt, ui->tx3);
5894             }
5895             if ((m_mode != "JT4" && m_mode != "Q65") || !m_bShMsgs) {
5896               msgtype(t + "73", ui->tx5->lineEdit ());
5897             }
5898           }
5899           break;
5900 
5901         case Configuration::type_2_msg_3_full:
5902           if (is77BitMode () && SpecOp::NA_VHF == m_config.special_op_id()) {
5903             msgtype(t + "R " + my_grid, ui->tx3);
5904             msgtype(t + "RRR", ui->tx4);
5905           } else {
5906             msgtype(t00 + my_grid, ui->tx1);
5907             msgtype(t + "R" + rpt, ui->tx3);
5908           }
5909           if (!eme_short_codes && ((m_mode != "JT4" && m_mode != "Q65") || !m_bShMsgs)) {
5910             msgtype(t + "73", ui->tx5->lineEdit ());
5911           }
5912           break;
5913 
5914         case Configuration::type_2_msg_5_only:
5915           msgtype(t00 + my_grid, ui->tx1);
5916           if (!eme_short_codes) {
5917             if (is77BitMode () && SpecOp::NA_VHF == m_config.special_op_id()) {
5918               msgtype(t + "R " + my_grid, ui->tx3); // #### Unreachable code
5919               msgtype(t + "RRR", ui->tx4);
5920             } else {
5921               msgtype(t0 + "R" + rpt, ui->tx3);
5922             }
5923           }
5924           // don't use short codes here as in a sked with a type 2
5925           // prefix we would never send out prefix/suffix
5926           msgtype(t + "73", ui->tx5->lineEdit ());
5927           break;
5928         }
5929     }
5930     if (hisCall != hisBase
5931         && m_config.type_2_msg_gen () != Configuration::type_2_msg_5_only
5932         && !eme_short_codes) {
5933       // cfm we have his full call copied as we could not do this earlier
5934       t = hisCall + " 73";
5935       msgtype(t, ui->tx5->lineEdit ());
5936     }
5937   } else {
5938     if (hisCall != hisBase and SpecOp::HOUND != m_config.special_op_id()) {
5939       if (shortList(hisCall)) {
5940         // cfm we know his full call with a type 1 tx1 message
5941         t = hisCall + " " + my_callsign;
5942         msgtype(t, ui->tx1);
5943       }
5944       else if (!eme_short_codes
5945                && ("MSK144" != m_mode || !m_bShMsgs)) {
5946         t=hisCall + " 73";
5947         msgtype(t, ui->tx5->lineEdit ());
5948       }
5949     }
5950   }
5951   m_rpt=rpt;
5952   if(SpecOp::HOUND == m_config.special_op_id() and is_compound) ui->tx1->setText("DE " + my_callsign);
5953 }
5954 
TxAgain()5955 void MainWindow::TxAgain()
5956 {
5957   auto_tx_mode(true);
5958 }
5959 
clearDX()5960 void MainWindow::clearDX ()
5961 {
5962   log("clearDX");
5963   set_dateTimeQSO (-1);
5964   // Z
5965   //if (m_QSOProgress != CALLING) {
5966   //    auto_tx_mode (false);
5967   //}
5968   ui->dxCallEntry->clear ();
5969   ui->dxGridEntry->clear ();
5970   m_lastCallsign.clear ();
5971   m_rptSent.clear ();
5972   m_rptRcvd.clear ();
5973   m_qsoStart.clear ();
5974   m_qsoStop.clear ();
5975   m_inQSOwith.clear();
5976   genStdMsgs (QString {});
5977   if (m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id()) {
5978     m_ntx=1;
5979     ui->txrb1->setChecked(true);
5980   } else {
5981     m_ntx=6;
5982     ui->txrb6->setChecked(true);
5983   }
5984   m_QSOProgress = CALLING;
5985 }
5986 
lookup()5987 void MainWindow::lookup()
5988 {
5989   QString hisCall {ui->dxCallEntry->text()};
5990   QString hisgrid0 {ui->dxGridEntry->text()};
5991   if (!hisCall.size ()) return;
5992   QFile f {m_config.writeable_data_dir ().absoluteFilePath ("CALL3.TXT")};
5993   if (f.open (QIODevice::ReadOnly | QIODevice::Text))
5994     {
5995       char c[132];
5996       qint64 n=0;
5997       for(int i=0; i<999999; i++) {
5998         n=f.readLine(c,sizeof(c));
5999         if(n <= 0) {
6000           if(!hisgrid0.contains(grid_regexp)) {
6001             ui->dxGridEntry->clear();
6002           }
6003           break;
6004         }
6005         QString t=QString(c);
6006         int i1=t.indexOf(",");
6007         if(t.left(i1)==hisCall) {
6008           QString hisgrid=t.mid(i1+1,6);
6009           i1=hisgrid.indexOf(",");
6010           if(i1>0) {
6011             hisgrid=hisgrid.mid(0,4);
6012           } else {
6013             hisgrid=hisgrid.mid(0,6).toUpper();
6014           }
6015           if(hisgrid.left(4)==hisgrid0.left(4) or (hisgrid0.size()==0)) {
6016             ui->dxGridEntry->setText(hisgrid);
6017           }
6018           break;
6019         }
6020       }
6021       f.close();
6022     }
6023 }
6024 
on_lookupButton_clicked()6025 void MainWindow::on_lookupButton_clicked()                    //Lookup button
6026 {
6027   lookup();
6028 }
6029 
on_addButton_clicked()6030 void MainWindow::on_addButton_clicked()                       //Add button
6031 {
6032   if(!ui->dxGridEntry->text ().size ()) {
6033     MessageBox::warning_message (this, tr ("Add to CALL3.TXT")
6034                                  , tr ("Please enter a valid grid locator"));
6035     return;
6036   }
6037   m_call3Modified=false;
6038   QString hisCall=ui->dxCallEntry->text();
6039   QString hisgrid=ui->dxGridEntry->text();
6040   QString newEntry=hisCall + "," + hisgrid;
6041 
6042   //  int ret = MessageBox::query_message(this, tr ("Add to CALL3.TXT"),
6043   //       tr ("Is %1 known to be active on EME?").arg (newEntry));
6044   //  if(ret==MessageBox::Yes) {
6045   //    newEntry += ",EME,,";
6046   //  } else {
6047   newEntry += ",,,";
6048   //  }
6049 
6050   QFile f1 {m_config.writeable_data_dir ().absoluteFilePath ("CALL3.TXT")};
6051   if(!f1.open(QIODevice::ReadWrite | QIODevice::Text)) {
6052     MessageBox::warning_message (this, tr ("Add to CALL3.TXT")
6053                                  , tr ("Cannot open \"%1\" for read/write: %2")
6054                                  .arg (f1.fileName ()).arg (f1.errorString ()));
6055     return;
6056   }
6057   if(f1.size()==0) {
6058     QTextStream out(&f1);
6059     out << "ZZZZZZ"
6060 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
6061         << Qt::endl
6062 #else
6063         << endl
6064 #endif
6065       ;
6066     f1.seek (0);
6067   }
6068   QFile f2 {m_config.writeable_data_dir ().absoluteFilePath ("CALL3.TMP")};
6069   if(!f2.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) {
6070     MessageBox::warning_message (this, tr ("Add to CALL3.TXT")
6071                                  , tr ("Cannot open \"%1\" for writing: %2")
6072                                  .arg (f2.fileName ()).arg (f2.errorString ()));
6073     return;
6074   }
6075   {
6076     QTextStream in(&f1);          //Read from CALL3.TXT
6077     QTextStream out(&f2);         //Copy into CALL3.TMP
6078     QString hc=hisCall;
6079     QString hc1="";
6080     QString hc2="000000";
6081     QString s;
6082     do {
6083       s=in.readLine();
6084       hc1=hc2;
6085       if(s.mid(0,2)=="//") {
6086         out << s + QChar::LineFeed; //Copy all comment lines
6087       } else {
6088         int i1=s.indexOf(",");
6089         hc2=s.mid(0,i1);
6090         if(hc>hc1 && hc<hc2) {
6091           out << newEntry + QChar::LineFeed;
6092           out << s + QChar::LineFeed;
6093           m_call3Modified=true;
6094         } else if(hc==hc2) {
6095           QString t {tr ("%1\nis already in CALL3.TXT"
6096                          ", do you wish to replace it?").arg (s)};
6097           int ret = MessageBox::query_message (this, tr ("Add to CALL3.TXT"), t);
6098           if(ret==MessageBox::Yes) {
6099             out << newEntry + QChar::LineFeed;
6100             m_call3Modified=true;
6101           }
6102         } else {
6103           if(s!="") out << s + QChar::LineFeed;
6104         }
6105       }
6106     } while(!s.isNull());
6107     if(hc>hc1 && !m_call3Modified) out << newEntry + QChar::LineFeed;
6108   }
6109 
6110   if(m_call3Modified) {
6111     auto const& old_path = m_config.writeable_data_dir ().absoluteFilePath ("CALL3.OLD");
6112     QFile f0 {old_path};
6113     if (f0.exists ()) f0.remove ();
6114     f1.copy (old_path);                       // copying as we want to
6115                                               // preserve symlinks
6116     f1.open (QFile::WriteOnly | QFile::Text); // truncates
6117     f2.seek (0);
6118     f1.write (f2.readAll ());                 // copy contents
6119     f2.remove ();
6120   }
6121 }
6122 
msgtype(QString t,QLineEdit * tx)6123 void MainWindow::msgtype(QString t, QLineEdit* tx)               //msgtype()
6124 {
6125 // Set background colors of the Tx message boxes, depending on message type
6126   char message[38];
6127   char msgsent[38];
6128   QByteArray s=t.toUpper().toLocal8Bit();
6129   ba2msg(s,message);
6130   int ichk=1,itype=0;
6131   gen65_(message,&ichk,msgsent,const_cast<int*>(itone0),&itype,22,22);
6132   msgsent[22]=0;
6133   bool text=false;
6134   bool shortMsg=false;
6135   if(itype==6) text=true;
6136 
6137 //### Check this stuff ###
6138   if(itype==7 and m_config.enable_VHF_features() and m_mode=="JT65") shortMsg=true;
6139   if(m_mode=="MSK144" and t.mid(0,1)=="<") text=false;
6140   if((m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4" || "Q65" == m_mode) and
6141      SpecOp::NA_VHF==m_config.special_op_id()) {
6142     int i0=t.trimmed().length()-7;
6143     if(t.mid(i0,3)==" R ") text=false;
6144   }
6145   text=false;
6146 //### ... to here ...
6147 
6148 
6149   QPalette p(tx->palette());
6150   if(text) {
6151     p.setColor(QPalette::Base,"#ffccff");       //pink
6152   } else {
6153     if(shortMsg) {
6154       p.setColor(QPalette::Base,"#66ffff");     //light blue
6155     } else {
6156       p.setColor(QPalette::Base,Qt::transparent);
6157       if ("MSK144" == m_mode && t.count ('<') == 1) {
6158         p.setColor(QPalette::Base,"#00ffff");   //another light blue
6159       }
6160     }
6161   }
6162   tx->setPalette(p);
6163 
6164   auto pos  = tx->cursorPosition ();
6165   tx->setText(t.toUpper());
6166   tx->setCursorPosition (pos);
6167 }
6168 
on_tx1_editingFinished()6169 void MainWindow::on_tx1_editingFinished()                       //tx1 edited
6170 {
6171   QString t=ui->tx1->text();
6172   msgtype(t, ui->tx1);
6173 }
6174 
on_tx2_editingFinished()6175 void MainWindow::on_tx2_editingFinished()                       //tx2 edited
6176 {
6177   QString t=ui->tx2->text();
6178   msgtype(t, ui->tx2);
6179 }
6180 
on_tx3_editingFinished()6181 void MainWindow::on_tx3_editingFinished()                       //tx3 edited
6182 {
6183   QString t=ui->tx3->text();
6184   msgtype(t, ui->tx3);
6185 }
6186 
on_tx4_editingFinished()6187 void MainWindow::on_tx4_editingFinished()                       //tx4 edited
6188 {
6189   QString t=ui->tx4->text();
6190   msgtype(t, ui->tx4);
6191 }
6192 
on_tx5_currentTextChanged(QString const & text)6193 void MainWindow::on_tx5_currentTextChanged (QString const& text) //tx5 edited
6194 {
6195   msgtype(text, ui->tx5->lineEdit ());
6196 }
6197 
on_tx6_editingFinished()6198 void MainWindow::on_tx6_editingFinished()                       //tx6 edited
6199 {
6200   QString t=ui->tx6->text().toUpper();
6201   if(t.indexOf(" ")>0) {
6202     QString t1=t.split(" ").at(1);
6203     QRegExp AZ4("^[A-Z]{1,4}$");
6204     QRegExp NN3("^[0-9]{1,3}$");
6205     m_CQtype="CQ";
6206     if(t1.size()<=4 and t1.contains(AZ4)) m_CQtype="CQ " + t1;
6207     if(t1.size()<=3 and t1.contains(NN3)) m_CQtype="CQ " + t1;
6208   }
6209   msgtype(t, ui->tx6);
6210 }
6211 
on_RoundRobin_currentTextChanged(QString text)6212 void MainWindow::on_RoundRobin_currentTextChanged(QString text)
6213 {
6214   ui->sbTxPercent->setEnabled (text == tr ("Random"));
6215 }
6216 
6217 
on_dxCallEntry_textChanged(QString const & call)6218 void MainWindow::on_dxCallEntry_textChanged (QString const& call)
6219 {
6220   m_hisCall = call;
6221   ui->dxGridEntry->clear();
6222   statusChanged();
6223   statusUpdate ();
6224 }
6225 
on_dxCallEntry_returnPressed()6226 void MainWindow::on_dxCallEntry_returnPressed ()
6227 {
6228   on_lookupButton_clicked();
6229 }
6230 
on_dxGridEntry_textChanged(QString const & grid)6231 void MainWindow::on_dxGridEntry_textChanged (QString const& grid)
6232 {
6233   if (ui->dxGridEntry->hasAcceptableInput ()) {
6234     if (grid != m_hisGrid) {
6235       m_hisGrid = grid;
6236       statusUpdate ();
6237     }
6238     qint64 nsec = (QDateTime::currentMSecsSinceEpoch()/1000) % 86400;
6239     double utch=nsec/3600.0;
6240     int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter;
6241     azdist_(const_cast <char *> ((m_config.my_grid () + "      ").left (6).toLatin1().constData()),
6242             const_cast <char *> ((m_hisGrid + "      ").left (6).toLatin1().constData()),&utch,
6243             &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6);
6244     QString t;
6245     int nd=nDkm;
6246     if(m_config.miles()) nd=nDmiles;
6247     if(m_mode=="MSK144") {
6248       if(nHotABetter==0)t = t.asprintf("Az: %d   B: %d   El: %d   %d",nAz,nHotAz,nEl,nd);
6249       if(nHotABetter!=0)t = t.asprintf("Az: %d   A: %d   El: %d   %d",nAz,nHotAz,nEl,nd);
6250     } else {
6251       t = t.asprintf("Az: %d        %d",nAz,nd);
6252     }
6253     if(m_config.miles()) t += " mi";
6254     if(!m_config.miles()) t += " km";
6255     labAz.setText (t);
6256   } else {
6257     if (m_hisGrid.size ()) {
6258       m_hisGrid.clear ();
6259       labAz.clear ();
6260       statusUpdate ();
6261     }
6262   }
6263 }
6264 
on_genStdMsgsPushButton_clicked()6265 void MainWindow::on_genStdMsgsPushButton_clicked()         //genStdMsgs button
6266 {
6267   genStdMsgs(m_rpt);
6268 }
6269 
on_logQSOButton_clicked()6270 void MainWindow::on_logQSOButton_clicked()                 //Log QSO button
6271 {
6272     // Z
6273     log("on_logQSOButton_clicked!");
6274     if (!m_hisCall.size () && (ui->cbAutoCQ->isChecked() || ui->cbAutoCall->isChecked())) {
6275         log("on_logQSOButton_clicked: m_hisCall is empty. Exiting.");
6276         clearDX();
6277         m_inQSOwith="";
6278         return;
6279     }
6280 
6281   if (SpecOp::FOX != m_config.special_op_id ()
6282       && ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isEnabled () && ui->cbAutoSeq->isChecked ())
6283     {
6284       // ensure that auto Tx is disabled even if disable Tx
6285       // on 73 is not checked, unless in Fox mode where it is allowed
6286       // to be a robot.
6287       auto_tx_mode (false);
6288     }
6289 
6290   if (!m_hisCall.size ()) {
6291     MessageBox::warning_message (this, tr ("Warning:  DX Call field is empty."));
6292   }
6293   // m_dateTimeQSOOn should really already be set but we'll ensure it gets set to something just in case
6294   if (!m_dateTimeQSOOn.isValid ()) {
6295     m_dateTimeQSOOn = QDateTime::currentDateTimeUtc();
6296   }
6297   auto dateTimeQSOOff = QDateTime::currentDateTimeUtc();
6298   if (dateTimeQSOOff < m_dateTimeQSOOn) dateTimeQSOOff = m_dateTimeQSOOn;
6299   QString grid=m_hisGrid;
6300   if(grid=="....") grid="";
6301   // Z
6302   if (ui->ci_grid->text().length() > grid.length())  grid = ui->ci_grid->text();
6303 
6304   switch( m_config.special_op_id() )
6305     {
6306       case SpecOp::NA_VHF:
6307         m_xSent=m_config.my_grid().left(4);
6308         m_xRcvd=m_hisGrid;
6309         break;
6310       case SpecOp::EU_VHF:
6311         m_rptSent=m_xSent.split(" ").at(0).left(2);
6312         m_rptRcvd=m_xRcvd.split(" ").at(0).left(2);
6313         if(m_xRcvd.split(" ").size()>=2) m_hisGrid=m_xRcvd.split(" ").at(1);
6314         grid=m_hisGrid;
6315         ui->dxGridEntry->setText(grid);
6316         break;
6317       case SpecOp::FIELD_DAY:
6318         m_rptSent=m_xSent.split(" ").at(0);
6319         m_rptRcvd=m_xRcvd.split(" ").at(0);
6320         break;
6321       case SpecOp::RTTY:
6322         m_rptSent=m_xSent.split(" ").at(0);
6323         m_rptRcvd=m_xRcvd.split(" ").at(0);
6324         break;
6325       case SpecOp::WW_DIGI:
6326         m_xSent=m_config.my_grid().left(4);
6327         m_xRcvd=m_hisGrid;
6328         break;
6329       default: break;
6330     }
6331 
6332   // Z
6333   if (m_lastCall != m_hisCall) {
6334       if (m_rptSent.isEmpty()) {
6335           m_rptSent = QString::number(ui->rptSpinBox->value());
6336           int n=m_rptSent.toInt();
6337           m_rptSent = m_rptSent.asprintf("%+2.2d",n);
6338       }
6339       m_logDlg->initLogQSO (m_hisCall, grid, m_mode, m_rptSent , m_rptRcvd,
6340                             m_dateTimeQSOOn, dateTimeQSOOff, m_freqNominal +
6341                            ui->TxFreqSpinBox->value(), m_noSuffix, m_xSent, m_xRcvd);
6342 
6343          if (m_config.rxTotxFreq()) on_pbT2R_clicked();
6344          m_lastCall = m_hisCall;
6345          if (ui->cbAutoCQ->isChecked() || ui->cbAutoCall->isChecked()) {
6346              log("QSO Logged: " + m_hisCall);
6347              m_logDlg->accept();
6348              if (ui->cbAutoCall->isChecked()) auto_tx_mode (false);
6349              clearDX();
6350              resetAutoSwitch();
6351          }
6352 
6353     } else {
6354       clearDX();
6355       resetAutoSwitch();
6356   }
6357 
6358   m_inQSOwith="";
6359 }
6360 
acceptQSO(QDateTime const & QSO_date_off,QString const & call,QString const & grid,Frequency dial_freq,QString const & mode,QString const & rpt_sent,QString const & rpt_received,QString const & tx_power,QString const & comments,QString const & name,QDateTime const & QSO_date_on,QString const & operator_call,QString const & my_call,QString const & my_grid,QString const & exchange_sent,QString const & exchange_rcvd,QString const & propmode,QByteArray const & ADIF)6361 void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, QString const& grid
6362                             , Frequency dial_freq, QString const& mode
6363                             , QString const& rpt_sent, QString const& rpt_received
6364                             , QString const& tx_power, QString const& comments
6365                             , QString const& name, QDateTime const& QSO_date_on, QString const& operator_call
6366                             , QString const& my_call, QString const& my_grid
6367                             , QString const& exchange_sent, QString const& exchange_rcvd
6368                             , QString const& propmode, QByteArray const& ADIF)
6369 {
6370   QString date = QSO_date_on.toString("yyyyMMdd");
6371   if (!m_logBook.add (call, grid, m_config.bands()->find(dial_freq), mode, ADIF))
6372     {
6373       MessageBox::warning_message (this, tr ("Log file error"),
6374                                    tr ("Cannot open \"%1\"").arg (m_logBook.path ()));
6375     }
6376 
6377   m_messageClient->qso_logged (QSO_date_off, call, grid, dial_freq, mode, rpt_sent, rpt_received
6378                                , tx_power, comments, name, QSO_date_on, operator_call, my_call, my_grid
6379                                , exchange_sent, exchange_rcvd, propmode);
6380   m_messageClient->logged_ADIF (ADIF);
6381 
6382   // Z
6383   updateQsoCounter(true);
6384 
6385   // Log to N1MM Logger
6386   if (m_config.broadcast_to_n1mm () && m_config.valid_n1mm_info ())
6387     {
6388       QUdpSocket sock;
6389       if (-1 == sock.writeDatagram (ADIF + " <eor>"
6390                                     , QHostAddress {m_config.n1mm_server_name ()}
6391                                     , m_config.n1mm_server_port ()))
6392         {
6393           MessageBox::warning_message (this, tr ("Error sending log to N1MM"),
6394                                        tr ("Write returned \"%1\"").arg (sock.errorString ()));
6395         }
6396     }
6397 
6398   if(m_config.clear_DX () and SpecOp::HOUND != m_config.special_op_id()) clearDX ();
6399   m_dateTimeQSOOn = QDateTime {};
6400   auto special_op = m_config.special_op_id ();
6401   if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) {
6402     ui->sbSerialNumber->setValue(ui->sbSerialNumber->value() + 1);
6403   }
6404 
6405   m_xSent.clear ();
6406   m_xRcvd.clear ();
6407 }
6408 
nWidgets(QString t)6409 qint64 MainWindow::nWidgets(QString t)
6410 {
6411   Q_ASSERT(t.length()==N_WIDGETS);
6412   qint64 n=0;
6413   for(int i=0; i<N_WIDGETS; i++) {
6414     n=n + n + t.mid(i,1).toInt();
6415   }
6416   return n;
6417 }
6418 
displayWidgets(qint64 n)6419 void MainWindow::displayWidgets(qint64 n)
6420 {
6421   /* See text file "displayWidgets.txt" for widget numbers */
6422   qint64 j=qint64(1)<<(N_WIDGETS-1);
6423   bool b;
6424   for(int i=0; i<N_WIDGETS; i++) {
6425     b=(n&j) != 0;
6426     if(i==0) ui->txFirstCheckBox->setVisible(b);
6427     if(i==1) ui->TxFreqSpinBox->setVisible(b);
6428     if(i==2) ui->RxFreqSpinBox->setVisible(b);
6429     if(i==3) ui->sbFtol->setVisible(b);
6430     if(i==4) ui->rptSpinBox->setVisible(b);
6431     if(i==5) ui->sbTR->setVisible(b);
6432     if(i==6) {
6433       ui->sbCQTxFreq->setVisible (b);
6434       ui->cbCQTx->setVisible (b);
6435       auto is_compound = m_config.my_callsign () != m_baseCall;
6436       ui->cbCQTx->setEnabled (b && (!is_compound || shortList (m_config.my_callsign ())));
6437     }
6438     if(i==7) ui->cbShMsgs->setVisible(b);
6439     if(i==8) ui->cbFast9->setVisible(b);
6440     if(i==9) ui->cbAutoSeq->setVisible(b);
6441     if(i==10) ui->cbTx6->setVisible(b);
6442     // if(i==11) ui->pbTxMode->setVisible(b);
6443     if(i==12) ui->pbR2T->setVisible(b);
6444     if(i==13) ui->pbT2R->setVisible(b);
6445     if(i==14) ui->cbHoldTxFreq->setVisible(b);
6446     if(i==15) ui->sbSubmode->setVisible(b);
6447     if(i==16) ui->syncSpinBox->setVisible(b);
6448     if(i==17) ui->WSPR_controls_widget->setVisible(b);
6449     if(i==18) ui->ClrAvgButton->setVisible(b);
6450     if(i==19) ui->actionQuickDecode->setEnabled(b);
6451     if(i==19) ui->actionMediumDecode->setEnabled(b);
6452     if(i==19) ui->actionDeepestDecode->setEnabled(b);
6453     if(i==20) ui->actionInclude_averaging->setVisible (b);
6454     if(i==21) ui->actionInclude_correlation->setVisible (b);
6455     if(i==22) {
6456       if(!b && m_echoGraph->isVisible())  m_echoGraph->hide();
6457     }
6458     if(i==23) ui->cbSWL->setVisible(b);
6459     if(i==24) ui->actionEnable_AP_FT8->setVisible (b);
6460     if(i==25) ui->actionEnable_AP_JT65->setVisible (b);
6461     if(i==26) ui->actionEnable_AP_DXcall->setVisible (b);
6462     if(i==27) ui->cbFirst->setVisible(b);
6463     // if(i==28) ui->labNextCall->setVisible(b);
6464     if(i==29) ui->measure_check_box->setVisible(b);
6465     if(i==30) ui->labDXped->setVisible(b);
6466     if(i==31) ui->cbRxAll->setVisible(b);
6467     if(i==32) ui->cbCQonly->setVisible(b);
6468     if(i==33) ui->sbTR_FST4W->setVisible(b);
6469     if (34 == i)                // adjust the stacked widget
6470       {
6471         // Z
6472         // ui->opt_controls_stack->setCurrentIndex (b ? 1 : 0);
6473         ui->sbF_Low->setVisible(b);
6474       }
6475     if(i==35) ui->sbF_High->setVisible(b);
6476     if(i==36) ui->actionAuto_Clear_Avg->setVisible (b);
6477     if(i==37) ui->sbMaxDrift->setVisible(b);
6478     j=j>>1;
6479   }
6480   ui->pbBestSP->setVisible(m_mode=="FT4");
6481   b=false;
6482   if(m_mode=="FT4" or m_mode=="FT8" || "Q65" == m_mode) {
6483   b=SpecOp::EU_VHF==m_config.special_op_id() or
6484     ( SpecOp::RTTY==m_config.special_op_id() and
6485       (m_config.RTTY_Exchange()=="DX" or m_config.RTTY_Exchange()=="#") );
6486   }
6487   if(m_mode=="MSK144") b=SpecOp::EU_VHF==m_config.special_op_id();
6488   ui->sbSerialNumber->setVisible(b);
6489   m_lastCallsign.clear ();     // ensures Tx5 is updated for new modes
6490   b=m_mode.startsWith("FST4");
6491   ui->sbNB->setVisible(b);
6492   genStdMsgs (m_rpt, true);
6493 }
6494 
on_actionFST4_triggered()6495 void MainWindow::on_actionFST4_triggered()
6496 {
6497   m_mode="FST4";
6498   m_mode="FST4";
6499   ui->actionFST4->setChecked(true);
6500   m_bFast9=false;
6501   m_bFastMode=false;
6502   m_fastGraph->hide();
6503   m_wideGraph->show();
6504   m_nsps=6912;                   //For symspec only
6505   m_FFTSize = m_nsps / 2;
6506   Q_EMIT FFTSize(m_FFTSize);
6507   ui->lh_decodes_title_label->setText(tr ("Band Activity"));
6508   ui->rh_decodes_title_label->setText(tr ("Rx Frequency"));
6509   WSPR_config(false);
6510   if(m_config.single_decode()) {
6511 //                           01234567890123456789012345678901234567
6512     displayWidgets(nWidgets("11111100010011100001000000010000000000"));
6513     m_wideGraph->setSingleDecode(true);
6514   } else {
6515     displayWidgets(nWidgets("11101100010011100001000000010000001100"));
6516     m_wideGraph->setSingleDecode(false);
6517     ui->sbFtol->setValue(20);
6518   }
6519   setup_status_bar(false);
6520   ui->cbAutoSeq->setChecked(true);
6521   m_wideGraph->setMode(m_mode);
6522   m_wideGraph->setPeriod(m_TRperiod,6912);
6523   m_wideGraph->setRxFreq(ui->RxFreqSpinBox->value());
6524   m_wideGraph->setTol(ui->sbFtol->value());
6525   m_wideGraph->setTxFreq(ui->TxFreqSpinBox->value());
6526   m_wideGraph->setFST4_FreqRange(ui->sbF_Low->value(),ui->sbF_High->value());
6527   chk_FST4_freq_range();
6528   switch_mode (Modes::FST4);
6529   m_wideGraph->setMode(m_mode);
6530   ui->sbTR->values ({15, 30, 60, 120, 300, 900, 1800});
6531   on_sbTR_valueChanged (ui->sbTR->value());
6532   statusChanged();
6533   m_bOK_to_chk=true;
6534   chk_FST4_freq_range();
6535 }
6536 
on_actionFST4W_triggered()6537 void MainWindow::on_actionFST4W_triggered()
6538 {
6539   m_mode="FST4W";
6540   ui->actionFST4W->setChecked(true);
6541   m_bFast9=false;
6542   m_bFastMode=false;
6543   m_fastGraph->hide();
6544   m_wideGraph->show();
6545   m_nsps=6912;                   //For symspec only
6546   m_FFTSize = m_nsps / 2;
6547   Q_EMIT FFTSize(m_FFTSize);
6548   WSPR_config(true);
6549 //                         01234567890123456789012345678901234567
6550   displayWidgets(nWidgets("00000000000000000101000000000000010000"));
6551   setup_status_bar(false);
6552   ui->band_hopping_group_box->setChecked(false);
6553   ui->band_hopping_group_box->setVisible(false);
6554   on_sbTR_FST4W_valueChanged (ui->sbTR_FST4W->value ());
6555   ui->WSPRfreqSpinBox->setMinimum(100);
6556   ui->WSPRfreqSpinBox->setMaximum(5000);
6557   m_wideGraph->setMode(m_mode);
6558   m_wideGraph->setPeriod(m_TRperiod,6912);
6559   m_wideGraph->setTxFreq(ui->WSPRfreqSpinBox->value());
6560   m_wideGraph->setRxFreq(ui->sbFST4W_RxFreq->value());
6561   m_wideGraph->setTol(ui->sbFST4W_FTol->value());
6562   ui->sbFtol->setValue(100);
6563   switch_mode (Modes::FST4W);
6564   statusChanged();
6565 }
6566 
on_actionFT4_triggered()6567 void MainWindow::on_actionFT4_triggered()
6568 {
6569   m_mode="FT4";
6570   m_TRperiod=7.5;
6571   bool bVHF=m_config.enable_VHF_features();
6572   m_bFast9=false;
6573   m_bFastMode=false;
6574   WSPR_config(false);
6575   switch_mode (Modes::FT4);
6576   m_nsps=6912;
6577   m_FFTSize = m_nsps/2;
6578   Q_EMIT FFTSize (m_FFTSize);
6579   m_hsymStop=21;
6580   setup_status_bar (bVHF);
6581   m_toneSpacing=12000.0/576.0;
6582   ui->actionFT4->setChecked(true);
6583   m_wideGraph->setMode(m_mode);
6584   m_send_RR73=true;
6585   VHF_features_enabled(bVHF);
6586   m_fastGraph->hide();
6587   m_wideGraph->show();
6588   ui->rh_decodes_headings_label->setText("  UTC   dB   DT Freq    " + tr ("Message"));
6589   m_wideGraph->setPeriod(m_TRperiod,m_nsps);
6590   m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
6591   m_detector->setTRPeriod(m_TRperiod);  // TODO - not thread safe
6592   ui->rh_decodes_title_label->setText(tr ("Rx Frequency"));
6593   ui->lh_decodes_title_label->setText(tr ("Band Activity"));
6594   ui->lh_decodes_headings_label->setText( "  UTC   dB   DT Freq    " + tr ("Message"));
6595 //                         01234567890123456789012345678901234567
6596   displayWidgets(nWidgets("11101000010011100001000000011000100000"));
6597   ui->txrb2->setEnabled(true);
6598   ui->txrb4->setEnabled(true);
6599   ui->txrb5->setEnabled(true);
6600   ui->txrb6->setEnabled(true);
6601   ui->txb2->setEnabled(true);
6602   ui->txb4->setEnabled(true);
6603   ui->txb5->setEnabled(true);
6604   ui->txb6->setEnabled(true);
6605   ui->txFirstCheckBox->setEnabled(true);
6606   chkFT4();
6607   statusChanged();
6608 }
6609 
on_actionFT8_triggered()6610 void MainWindow::on_actionFT8_triggered()
6611 {
6612   m_mode="FT8";
6613   bool bVHF=m_config.enable_VHF_features();
6614   m_bFast9=false;
6615   m_bFastMode=false;
6616   WSPR_config(false);
6617   switch_mode (Modes::FT8);
6618   m_nsps=6912;
6619   m_FFTSize = m_nsps / 2;
6620   Q_EMIT FFTSize (m_FFTSize);
6621   m_hsymStop=50;
6622   setup_status_bar (bVHF);
6623   m_toneSpacing=0.0;                   //???
6624   ui->actionFT8->setChecked(true);     //???
6625   m_wideGraph->setMode(m_mode);
6626   VHF_features_enabled(bVHF);
6627   ui->cbAutoSeq->setChecked(true);
6628   m_TRperiod=15.0;
6629   m_fastGraph->hide();
6630   m_wideGraph->show();
6631   ui->rh_decodes_headings_label->setText("  UTC   dB   DT Freq    " + tr ("Message"));
6632   m_wideGraph->setPeriod(m_TRperiod,m_nsps);
6633   m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
6634   m_detector->setTRPeriod(m_TRperiod);  // TODO - not thread safe
6635   ui->rh_decodes_title_label->setText(tr ("Rx Frequency"));
6636   if(SpecOp::FOX==m_config.special_op_id()) {
6637     ui->lh_decodes_title_label->setText(tr ("Stations calling DXpedition %1").arg (m_config.my_callsign()));
6638     ui->lh_decodes_headings_label->setText( "Call         Grid   dB  Freq   Dist Age Continent");
6639   } else {
6640     ui->lh_decodes_title_label->setText(tr ("Band Activity"));
6641     ui->lh_decodes_headings_label->setText( "  UTC   dB   DT Freq    " + tr ("Message"));
6642   }
6643 //                         01234567890123456789012345678901234567
6644   displayWidgets(nWidgets("11101000010011100001000010011000100000"));
6645   ui->txrb2->setEnabled(true);
6646   ui->txrb4->setEnabled(true);
6647   ui->txrb5->setEnabled(true);
6648   ui->txrb6->setEnabled(true);
6649   ui->txb2->setEnabled(true);
6650   ui->txb4->setEnabled(true);
6651   ui->txb5->setEnabled(true);
6652   ui->txb6->setEnabled(true);
6653   ui->txFirstCheckBox->setEnabled(true);
6654   ui->cbAutoSeq->setEnabled(true);
6655   if(SpecOp::FOX==m_config.special_op_id()) {
6656     ui->txFirstCheckBox->setChecked(true);
6657     ui->txFirstCheckBox->setEnabled(false);
6658     ui->cbHoldTxFreq->setChecked(true);
6659     ui->cbAutoSeq->setEnabled(false);
6660     ui->tabWidget->setCurrentIndex(1);
6661     ui->TxFreqSpinBox->setValue(300);
6662   //                         01234567890123456789012345678901234567
6663     displayWidgets(nWidgets("11101000010011100001000000000010000000"));
6664     ui->labDXped->setText(tr ("Fox"));
6665     on_fox_log_action_triggered();
6666   }
6667   if(SpecOp::HOUND == m_config.special_op_id()) {
6668     ui->txFirstCheckBox->setChecked(false);
6669     ui->txFirstCheckBox->setEnabled(false);
6670     ui->cbAutoSeq->setEnabled(false);
6671     ui->tabWidget->setCurrentIndex(0);
6672     ui->cbHoldTxFreq->setChecked(true);
6673     //                       01234567890123456789012345678901234567
6674     displayWidgets(nWidgets("11101000010011000001000000000011000000"));
6675     ui->labDXped->setText(tr ("Hound"));
6676     ui->txrb1->setChecked(true);
6677     ui->txrb2->setEnabled(false);
6678     ui->txrb4->setEnabled(false);
6679     ui->txrb5->setEnabled(false);
6680     ui->txrb6->setEnabled(false);
6681     ui->txb2->setEnabled(false);
6682     ui->txb4->setEnabled(false);
6683     ui->txb5->setEnabled(false);
6684     ui->txb6->setEnabled(false);
6685   }
6686 
6687   if (SpecOp::NONE < m_config.special_op_id () && SpecOp::FOX > m_config.special_op_id ()) {
6688     QString t0="";
6689     if(SpecOp::NA_VHF==m_config.special_op_id()) t0+="NA VHF";
6690     if(SpecOp::EU_VHF==m_config.special_op_id()) t0+="EU VHF";
6691     if(SpecOp::FIELD_DAY==m_config.special_op_id()) t0+="Field Day";
6692     if(SpecOp::RTTY==m_config.special_op_id()) t0+="RTTY";
6693     if(SpecOp::WW_DIGI==m_config.special_op_id()) t0+="WW_DIGI";
6694     if(t0=="") {
6695       ui->labDXped->setVisible(false);
6696     } else {
6697       ui->labDXped->setVisible(true);
6698       ui->labDXped->setText(t0);
6699     }
6700     on_contest_log_action_triggered();
6701   }
6702 
6703   if((SpecOp::FOX==m_config.special_op_id() or SpecOp::HOUND==m_config.special_op_id()) and !m_config.split_mode() and !m_bWarnedSplit) {
6704     QString errorMsg;
6705     MessageBox::critical_message (this,
6706        "Operation in FT8 DXpedition mode normally requires\n"
6707        " *Split* rig control (either *Rig* or *Fake It* on\n"
6708        "the *Settings | Radio* tab.)", errorMsg);
6709     m_bWarnedSplit=true;
6710   }
6711   statusChanged();
6712 }
6713 
on_actionJT4_triggered()6714 void MainWindow::on_actionJT4_triggered()
6715 {
6716   m_mode="JT4";
6717   bool bVHF=m_config.enable_VHF_features();
6718   WSPR_config(false);
6719   switch_mode (Modes::JT4);
6720   m_TRperiod=60.0;
6721   m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
6722   m_detector->setTRPeriod(m_TRperiod);  // TODO - not thread safe
6723   m_nsps=6912;                   //For symspec only
6724   m_FFTSize = m_nsps / 2;
6725   Q_EMIT FFTSize (m_FFTSize);
6726   m_hsymStop=176;
6727   if(m_config.decode_at_52s()) m_hsymStop=184;
6728   m_toneSpacing=0.0;
6729   ui->actionJT4->setChecked(true);
6730   VHF_features_enabled(true);
6731   m_wideGraph->setPeriod(m_TRperiod,m_nsps);
6732   m_wideGraph->setMode(m_mode);
6733   m_bFastMode=false;
6734   m_bFast9=false;
6735   setup_status_bar (bVHF);
6736   ui->sbSubmode->setMaximum(6);
6737   ui->lh_decodes_title_label->setText(tr ("Single-Period Decodes"));
6738   ui->rh_decodes_title_label->setText(tr ("Average Decodes"));
6739   ui->lh_decodes_headings_label->setText("UTC   dB   DT Freq    " + tr ("Message"));
6740   ui->rh_decodes_headings_label->setText("UTC   dB   DT Freq    " + tr ("Message"));
6741   if(bVHF) {
6742     ui->sbSubmode->setValue(m_nSubMode);
6743   } else {
6744     ui->sbSubmode->setValue(0);
6745   }
6746   if(bVHF) {
6747     //                       01234567890123456789012345678901234567
6748     displayWidgets(nWidgets("11111001001011011011110000000000000000"));
6749   } else {
6750     displayWidgets(nWidgets("11101000000011000011000000000000000000"));
6751   }
6752   fast_config(false);
6753   statusChanged();
6754 }
6755 
on_actionJT9_triggered()6756 void MainWindow::on_actionJT9_triggered()
6757 {
6758   m_mode="JT9";
6759   bool bVHF=m_config.enable_VHF_features();
6760   m_bFast9=ui->cbFast9->isChecked();
6761   m_bFastMode=m_bFast9;
6762   WSPR_config(false);
6763   switch_mode (Modes::JT9);
6764   m_nsps=6912;
6765   m_FFTSize = m_nsps / 2;
6766   Q_EMIT FFTSize (m_FFTSize);
6767   m_hsymStop=173;
6768   if(m_config.decode_at_52s()) m_hsymStop=179;
6769   setup_status_bar (bVHF);
6770   m_toneSpacing=0.0;
6771   ui->actionJT9->setChecked(true);
6772   m_wideGraph->setMode(m_mode);
6773   VHF_features_enabled(bVHF);
6774   if(m_nSubMode>=4 and bVHF) {
6775     ui->cbFast9->setEnabled(true);
6776   } else {
6777     ui->cbFast9->setEnabled(false);
6778     ui->cbFast9->setChecked(false);
6779   }
6780   ui->sbSubmode->setMaximum(7);
6781   if(m_bFast9) {
6782     ui->sbTR->values ({5, 10, 15, 30});
6783     on_sbTR_valueChanged (ui->sbTR->value());
6784     m_wideGraph->hide();
6785     m_fastGraph->showNormal();
6786     ui->TxFreqSpinBox->setValue(700);
6787     ui->RxFreqSpinBox->setValue(700);
6788     ui->lh_decodes_headings_label->setText("  UTC   dB    T Freq    " + tr ("Message"));
6789     ui->rh_decodes_headings_label->setText("  UTC   dB    T Freq    " + tr ("Message"));
6790   } else {
6791     ui->cbAutoSeq->setChecked(false);
6792     if (m_mode != "FST4")
6793       {
6794         m_TRperiod=60.0;
6795         ui->lh_decodes_headings_label->setText("UTC   dB   DT Freq    " + tr ("Message"));
6796         ui->rh_decodes_headings_label->setText("UTC   dB   DT Freq    " + tr ("Message"));
6797       }
6798   }
6799   m_wideGraph->setPeriod(m_TRperiod,m_nsps);
6800   m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
6801   m_detector->setTRPeriod(m_TRperiod);  // TODO - not thread safe
6802   ui->lh_decodes_title_label->setText(tr ("Band Activity"));
6803   ui->rh_decodes_title_label->setText(tr ("Rx Frequency"));
6804   if(bVHF) {
6805     //                       01234567890123456789012345678901234567
6806     displayWidgets(nWidgets("11111010100011111001000000000000000000"));
6807   } else {
6808     displayWidgets(nWidgets("11101000000011100001000000000000100000"));
6809   }
6810   fast_config(m_bFastMode);
6811   ui->cbAutoSeq->setVisible(m_bFast9);
6812   statusChanged();
6813 }
6814 
on_actionJT65_triggered()6815 void MainWindow::on_actionJT65_triggered()
6816 {
6817   on_actionJT9_triggered();
6818   m_mode="JT65";
6819   bool bVHF=m_config.enable_VHF_features();
6820   WSPR_config(false);
6821   switch_mode (Modes::JT65);
6822   m_TRperiod=60.0;
6823   m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
6824   m_detector->setTRPeriod(m_TRperiod);   // TODO - not thread safe
6825   m_nsps=6912;                   //For symspec only
6826   m_FFTSize = m_nsps / 2;
6827   Q_EMIT FFTSize (m_FFTSize);
6828   m_hsymStop=174;
6829   if(m_config.decode_at_52s()) m_hsymStop=183;
6830   m_toneSpacing=0.0;
6831   ui->actionJT65->setChecked(true);
6832   VHF_features_enabled(bVHF);
6833   m_wideGraph->setPeriod(m_TRperiod,m_nsps);
6834   m_wideGraph->setMode(m_mode);
6835   m_wideGraph->setRxFreq(ui->RxFreqSpinBox->value());
6836   m_wideGraph->setTol(ui->sbFtol->value());
6837   m_wideGraph->setTxFreq(ui->TxFreqSpinBox->value());
6838   setup_status_bar (bVHF);
6839   m_bFastMode=false;
6840   m_bFast9=false;
6841   ui->sbSubmode->setMaximum(2);
6842   if(bVHF) {
6843     ui->sbSubmode->setValue(m_nSubMode);
6844     ui->lh_decodes_title_label->setText(tr ("Single-Period Decodes"));
6845     ui->rh_decodes_title_label->setText(tr ("Average Decodes"));
6846   } else {
6847     ui->sbSubmode->setValue(0);
6848     ui->lh_decodes_title_label->setText(tr ("Band Activity"));
6849     ui->rh_decodes_title_label->setText(tr ("Rx Frequency"));
6850   }
6851   if(bVHF) {
6852     //                       01234567890123456789012345678901234567
6853     displayWidgets(nWidgets("11111001000011011010110001000000000000"));
6854   } else {
6855     displayWidgets(nWidgets("11101000000011100001000000000000100000"));
6856   }
6857   fast_config(false);
6858   if(ui->cbShMsgs->isChecked()) {
6859     ui->cbAutoSeq->setChecked(false);
6860     ui->cbAutoSeq->setVisible(false);
6861   }
6862   statusChanged();
6863 }
6864 
on_actionQ65_triggered()6865 void MainWindow::on_actionQ65_triggered()
6866 {
6867   m_mode="Q65";
6868   ui->actionQ65->setChecked(true);
6869   switch_mode(Modes::Q65);
6870   fast_config(false);
6871   WSPR_config(false);
6872   setup_status_bar(true);
6873   ui->actionQuickDecode->setChecked(true);
6874   m_nsps=6912;                   //For symspec only
6875   m_FFTSize = m_nsps / 2;
6876   Q_EMIT FFTSize(m_FFTSize);
6877   m_hsymStop=49;
6878   ui->sbTR->values ({15, 30, 60, 120, 300});
6879   on_sbTR_valueChanged (ui->sbTR->value());
6880   ui->sbSubmode->setValue(m_nSubMode);
6881   QString fname {QDir::toNativeSeparators(m_config.temp_dir().absoluteFilePath ("red.dat"))};
6882   m_wideGraph->setRedFile(fname);
6883   m_wideGraph->setMode(m_mode);
6884   m_wideGraph->setPeriod(m_TRperiod,6912);
6885   m_wideGraph->setTol(ui->sbFtol->value());
6886   m_wideGraph->setRxFreq(ui->RxFreqSpinBox->value());
6887   m_wideGraph->setTxFreq(ui->TxFreqSpinBox->value());
6888   switch_mode (Modes::Q65);
6889 //                         01234567890123456789012345678901234567
6890   displayWidgets(nWidgets("11111101011011010011100000010000000011"));
6891   ui->labDXped->setText("");
6892   ui->lh_decodes_title_label->setText(tr ("Single-Period Decodes"));
6893   ui->rh_decodes_title_label->setText(tr ("Average Decodes"));
6894   ui->lh_decodes_headings_label->setText("UTC   dB   DT Freq    " + tr ("Message"));
6895   ui->rh_decodes_headings_label->setText("UTC   dB   DT Freq    " + tr ("Message"));
6896   statusChanged();
6897 
6898   if (SpecOp::NONE < m_config.special_op_id () && SpecOp::FOX > m_config.special_op_id ()) {
6899     QString t0="";
6900     if(SpecOp::NA_VHF==m_config.special_op_id()) t0="NA VHF";
6901     if(SpecOp::EU_VHF==m_config.special_op_id()) t0="EU VHF";
6902     if(SpecOp::FIELD_DAY==m_config.special_op_id()) t0="Field Day";
6903     if(SpecOp::RTTY==m_config.special_op_id()) t0+="RTTY";
6904     if(SpecOp::WW_DIGI==m_config.special_op_id()) t0+="WW_DIGI";
6905     if(t0=="") {
6906       ui->labDXped->setVisible(false);
6907     } else {
6908       ui->labDXped->setVisible(true);
6909       ui->labDXped->setText(t0);
6910     }
6911     on_contest_log_action_triggered();
6912   }
6913 
6914 }
6915 
on_actionMSK144_triggered()6916 void MainWindow::on_actionMSK144_triggered()
6917 {
6918   if(SpecOp::EU_VHF < m_config.special_op_id()) {
6919 // We are rejecting the requested mode change, so re-check the old mode
6920     if("FT8"==m_mode) ui->actionFT8->setChecked(true);
6921     if("JT4"==m_mode) ui->actionJT4->setChecked(true);
6922     if("JT9"==m_mode) ui->actionJT9->setChecked(true);
6923     if("JT65"==m_mode) ui->actionJT65->setChecked(true);
6924     if("Q65"==m_mode) ui->actionQ65->setChecked(true);
6925     if("WSPR"==m_mode) ui->actionWSPR->setChecked(true);
6926     if("Echo"==m_mode) ui->actionEcho->setChecked(true);
6927     if("FreqCal"==m_mode) ui->actionFreqCal->setChecked(true);
6928     if("FST4"==m_mode) ui->actionFST4->setChecked(true);
6929     if("FST4W"==m_mode) ui->actionFST4W->setChecked(true);
6930 // Make sure that MSK144 is not checked.
6931     ui->actionMSK144->setChecked(false);
6932     MessageBox::warning_message (this, tr ("Improper mode"),
6933        "MSK144 not available if Fox, Hound, Field Day, RTTY, or WW Digi contest is selected.");
6934     return;
6935   }
6936   m_mode="MSK144";
6937   ui->actionMSK144->setChecked(true);
6938   switch_mode (Modes::MSK144);
6939   m_nsps=6;
6940   m_FFTSize = 7 * 512;
6941   Q_EMIT FFTSize (m_FFTSize);
6942   setup_status_bar (true);
6943   m_toneSpacing=0.0;
6944   WSPR_config(false);
6945   VHF_features_enabled(true);
6946   m_bFastMode=true;
6947   m_bFast9=false;
6948   ui->sbTR->values ({5, 10, 15, 30});
6949   on_sbTR_valueChanged (ui->sbTR->value());
6950   m_wideGraph->hide();
6951   m_fastGraph->showNormal();
6952   ui->TxFreqSpinBox->setValue(1500);
6953   ui->RxFreqSpinBox->setValue(1500);
6954   ui->RxFreqSpinBox->setMinimum(1400);
6955   ui->RxFreqSpinBox->setMaximum(1600);
6956   ui->RxFreqSpinBox->setSingleStep(10);
6957   ui->lh_decodes_headings_label->setText("  UTC   dB    T Freq    " + tr ("Message"));
6958   ui->rh_decodes_headings_label->setText("  UTC   dB    T Freq    " + tr ("Message"));
6959   m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
6960   m_detector->setTRPeriod(m_TRperiod);  // TODO - not thread safe
6961   m_fastGraph->setTRPeriod(m_TRperiod);
6962   ui->lh_decodes_title_label->setText(tr ("Band Activity"));
6963   ui->rh_decodes_title_label->setText(tr ("Tx Messages"));
6964   ui->actionMSK144->setChecked(true);
6965   ui->rptSpinBox->setMinimum(-8);
6966   ui->rptSpinBox->setMaximum(24);
6967   ui->rptSpinBox->setValue(0);
6968   ui->rptSpinBox->setSingleStep(1);
6969   ui->sbFtol->values ({20, 50, 100, 200});
6970   //                       01234567890123456789012345678901234567
6971   displayWidgets(nWidgets("10111111010000000001000100001000000000"));
6972   fast_config(m_bFastMode);
6973   statusChanged();
6974 
6975   QString t0="";
6976   if(SpecOp::NA_VHF==m_config.special_op_id()) t0+="NA VHF";
6977   if(SpecOp::EU_VHF==m_config.special_op_id()) t0+="EU VHF";
6978   if(t0=="") {
6979     ui->labDXped->setVisible(false);
6980   } else {
6981     ui->labDXped->setVisible(true);
6982     ui->labDXped->setText(t0);
6983     on_contest_log_action_triggered();
6984   }
6985 }
6986 
on_actionWSPR_triggered()6987 void MainWindow::on_actionWSPR_triggered()
6988 {
6989   m_mode="WSPR";
6990   WSPR_config(true);
6991   switch_mode (Modes::WSPR);
6992   m_TRperiod=120.0;
6993   m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
6994   m_detector->setTRPeriod(m_TRperiod);  // TODO - not thread safe
6995   m_nsps=6912;                   //For symspec only
6996   m_FFTSize = m_nsps / 2;
6997   Q_EMIT FFTSize (m_FFTSize);
6998   m_hsymStop=396;
6999   m_toneSpacing=12000.0/8192.0;
7000   setup_status_bar (false);
7001   ui->actionWSPR->setChecked(true);
7002   VHF_features_enabled(false);
7003   ui->WSPRfreqSpinBox->setMinimum(1400);
7004   ui->WSPRfreqSpinBox->setMaximum(1600);
7005   m_wideGraph->setPeriod(m_TRperiod,m_nsps);
7006   m_wideGraph->setMode(m_mode);
7007   m_bFastMode=false;
7008   m_bFast9=false;
7009   ui->TxFreqSpinBox->setValue(ui->WSPRfreqSpinBox->value());
7010   //                       01234567890123456789012345678901234567
7011   displayWidgets(nWidgets("00000000000000000101000000000000000000"));
7012   fast_config(false);
7013   statusChanged();
7014 }
7015 
on_actionEcho_triggered()7016 void MainWindow::on_actionEcho_triggered()
7017 {
7018   on_actionJT4_triggered();
7019   m_mode="Echo";
7020   ui->actionEcho->setChecked(true);
7021   m_TRperiod=3.0;
7022   m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
7023   m_detector->setTRPeriod(m_TRperiod);  // TODO - not thread safe
7024   m_nsps=6912;                        //For symspec only
7025   m_FFTSize = m_nsps / 2;
7026   Q_EMIT FFTSize (m_FFTSize);
7027   m_hsymStop=9;
7028   m_toneSpacing=1.0;
7029   switch_mode(Modes::Echo);
7030   setup_status_bar (true);
7031   m_wideGraph->setMode(m_mode);
7032   ui->TxFreqSpinBox->setValue(1500);
7033   ui->TxFreqSpinBox->setEnabled (false);
7034   if(!m_echoGraph->isVisible()) m_echoGraph->show();
7035   if (!ui->actionAstronomical_data->isChecked ()) {
7036     ui->actionAstronomical_data->setChecked (true);
7037   }
7038   m_bFastMode=false;
7039   m_bFast9=false;
7040   WSPR_config(true);
7041   ui->lh_decodes_headings_label->setText("   UTC      N   Level    Sig      DF    Width   Q");
7042   //                       01234567890123456789012345678901234567
7043   displayWidgets(nWidgets("00000000000000000000001000000000000000"));
7044   fast_config(false);
7045   statusChanged();
7046 }
7047 
on_actionFreqCal_triggered()7048 void MainWindow::on_actionFreqCal_triggered()
7049 {
7050   on_actionJT9_triggered();
7051   m_mode="FreqCal";
7052   ui->actionFreqCal->setChecked(true);
7053   switch_mode(Modes::FreqCal);
7054   m_wideGraph->setMode(m_mode);
7055   ui->sbTR->values ({5, 10, 15, 30});
7056   on_sbTR_valueChanged (ui->sbTR->value());
7057   m_modulator->setTRPeriod(m_TRperiod); // TODO - not thread safe
7058   m_detector->setTRPeriod(m_TRperiod);  // TODO - not thread safe
7059   m_nsps=6912;                        //For symspec only
7060   m_FFTSize = m_nsps / 2;
7061   Q_EMIT FFTSize (m_FFTSize);
7062   m_hsymStop=((int(m_TRperiod/0.288))/8)*8;
7063   m_frequency_list_fcal_iter = m_config.frequencies ()->begin ();
7064   ui->RxFreqSpinBox->setValue(1500);
7065   setup_status_bar (true);
7066 //                               18:15:47      0  1  1500  1550.349     0.100    3.5   10.2
7067   ui->lh_decodes_headings_label->setText("  UTC      Freq CAL Offset  fMeas       DF     Level   S/N");
7068   ui->measure_check_box->setChecked (false);
7069   //                       01234567890123456789012345678901234567
7070   displayWidgets(nWidgets("00110100000000000000000000000100000000"));
7071   statusChanged();
7072 }
7073 
switch_mode(Mode mode)7074 void MainWindow::switch_mode (Mode mode)
7075 {
7076   // Z
7077   tx_watchdog(false);
7078   m_fastGraph->setMode(m_mode);
7079   m_config.frequencies ()->filter (m_config.region (), mode);
7080   auto const& row = m_config.frequencies ()->best_working_frequency (m_freqNominal);
7081   ui->bandComboBox->setCurrentIndex (row);
7082   if (row >= 0) {
7083     on_bandComboBox_activated (row);
7084   }
7085   ui->rptSpinBox->setSingleStep(1);
7086   ui->rptSpinBox->setMinimum(-50);
7087   ui->rptSpinBox->setMaximum(49);
7088   ui->sbFtol->values ({1, 2, 5, 10, 20, 50, 100, 200, 300, 400, 500, 1000});
7089   ui->sbFST4W_FTol->values({1, 2, 5, 10, 20, 50, 100});
7090   if(m_mode=="MSK144") {
7091     ui->RxFreqSpinBox->setMinimum(1400);
7092     ui->RxFreqSpinBox->setMaximum(1600);
7093     ui->RxFreqSpinBox->setSingleStep(25);
7094   } else {
7095     ui->RxFreqSpinBox->setMinimum(200);
7096     ui->RxFreqSpinBox->setMaximum(5000);
7097     ui->RxFreqSpinBox->setSingleStep(1);
7098   }
7099   bool b=m_mode=="FreqCal";
7100   ui->tabWidget->setVisible(!b);
7101   if(b) {
7102     // Z
7103     //ui->DX_controls_widget->setVisible(false);
7104     //ui->rh_decodes_widget->setVisible (false);
7105     ui->lh_decodes_title_label->setVisible(false);
7106   }
7107 }
7108 
WSPR_config(bool b)7109 void MainWindow::WSPR_config(bool b)
7110 {
7111   // Z
7112   // ui->rh_decodes_widget->setVisible(!b);
7113   ui->controls_stack_widget->setCurrentIndex (b && m_mode != "Echo" ? 1 : 0);
7114   ui->QSO_controls_widget->setVisible (!b);
7115   // Z
7116   // ui->DX_controls_widget->setVisible (!b);
7117   ui->WSPR_controls_widget->setVisible (b);
7118   ui->lh_decodes_title_label->setVisible(!b and ui->cbMenus->isChecked());
7119   ui->logQSOButton->setVisible(!b);
7120   ui->DecodeButton->setEnabled(!b);
7121   bool bFST4W=(m_mode=="FST4W");
7122   ui->sbTxPercent->setEnabled(!bFST4W or (tr("Random") == ui->RoundRobin->currentText()));
7123   ui->band_hopping_group_box->setVisible(true);
7124   ui->RoundRobin->setVisible(bFST4W);
7125   ui->sbFST4W_RxFreq->setVisible(bFST4W);
7126   ui->sbFST4W_FTol->setVisible(bFST4W);
7127   ui->RoundRobin->lineEdit()->setAlignment(Qt::AlignCenter);
7128   if(b and m_mode!="Echo" and m_mode!="FST4W") {
7129     QString t="UTC    dB   DT     Freq     Drift  Call          Grid    dBm    ";
7130     if(m_config.miles()) t += " mi";
7131     if(!m_config.miles()) t += " km";
7132     ui->lh_decodes_headings_label->setText(t);
7133     if (m_config.is_transceiver_online ()) {
7134       m_config.transceiver_tx_frequency (0); // turn off split
7135     }
7136     m_bSimplex = true;
7137   } else
7138     {
7139       m_bSimplex = false;
7140     }
7141   enable_DXCC_entity (m_config.DXCC ());  // sets text window proportions and (re)inits the logbook
7142 }
7143 
fast_config(bool b)7144 void MainWindow::fast_config(bool b)
7145 {
7146   m_bFastMode=b;
7147   ui->TxFreqSpinBox->setEnabled(!b);
7148   ui->sbTR->setVisible(b);
7149   if(b and (m_bFast9 or m_mode=="MSK144")) {
7150     m_wideGraph->hide();
7151     m_fastGraph->showNormal();
7152   } else {
7153     m_wideGraph->showNormal();
7154     m_fastGraph->hide();
7155   }
7156 }
7157 
on_TxFreqSpinBox_valueChanged(int n)7158 void MainWindow::on_TxFreqSpinBox_valueChanged(int n)
7159 {
7160   m_wideGraph->setTxFreq(n);
7161 //  if (ui->cbHoldTxFreq->isChecked ()) ui->RxFreqSpinBox->setValue(n);
7162   if(m_mode!="MSK144") {
7163     Q_EMIT transmitFrequency (n - m_XIT);
7164   }
7165 
7166   if(m_mode=="Q65") {
7167     if(((m_nSubMode==4 && m_TRperiod==60.0) || (m_nSubMode==3 && m_TRperiod==30.0) ||
7168        (m_nSubMode==2 && m_TRperiod==15.0)) && ui->TxFreqSpinBox->value()!=700) {
7169       ui->TxFreqSpinBox->setStyleSheet("QSpinBox{background-color:red}");
7170     } else {
7171       ui->TxFreqSpinBox->setStyleSheet("");
7172     }
7173   }
7174 
7175   statusUpdate ();
7176 }
7177 
on_RxFreqSpinBox_valueChanged(int n)7178 void MainWindow::on_RxFreqSpinBox_valueChanged(int n)
7179 {
7180   m_wideGraph->setRxFreq(n);
7181   if (m_mode == "FreqCal") {
7182     setRig ();
7183   }
7184   statusUpdate ();
7185 }
7186 
on_sbF_Low_valueChanged(int n)7187 void MainWindow::on_sbF_Low_valueChanged(int n)
7188 {
7189   m_wideGraph->setFST4_FreqRange(n,ui->sbF_High->value());
7190   chk_FST4_freq_range();
7191 }
7192 
on_sbF_High_valueChanged(int n)7193 void MainWindow::on_sbF_High_valueChanged(int n)
7194 {
7195   m_wideGraph->setFST4_FreqRange(ui->sbF_Low->value(),n);
7196   chk_FST4_freq_range();
7197 }
7198 
chk_FST4_freq_range()7199 void MainWindow::chk_FST4_freq_range()
7200 {
7201   if(!m_bOK_to_chk) return;
7202   if(ui->sbF_Low->value() < m_wideGraph->nStartFreq()) ui->sbF_Low->setValue(m_wideGraph->nStartFreq());
7203   if(ui->sbF_High->value() > m_wideGraph->Fmax()) {
7204     int n=m_wideGraph->Fmax()/100;
7205     ui->sbF_High->setValue(100*n);
7206   }
7207   int maxDiff=2000;
7208   if(m_TRperiod==120) maxDiff=1000;
7209   if(m_TRperiod==300) maxDiff=400;
7210   if(m_TRperiod>=900) maxDiff=200;
7211   int diff=ui->sbF_High->value() - ui->sbF_Low->value();
7212 
7213   if(diff<100 or diff>maxDiff) {
7214     ui->sbF_Low->setStyleSheet("QSpinBox { color: white; background-color: red; }");
7215     ui->sbF_High->setStyleSheet("QSpinBox { color: white; background-color: red; }");
7216   } else {
7217     ui->sbF_Low->setStyleSheet("");
7218     ui->sbF_High->setStyleSheet("");
7219   }
7220 }
7221 
on_actionQuickDecode_toggled(bool checked)7222 void MainWindow::on_actionQuickDecode_toggled (bool checked)
7223 {
7224   m_ndepth ^= (-checked ^ m_ndepth) & 0x00000001;
7225 }
7226 
on_actionMediumDecode_toggled(bool checked)7227 void MainWindow::on_actionMediumDecode_toggled (bool checked)
7228 {
7229   m_ndepth ^= (-checked ^ m_ndepth) & 0x00000002;
7230 }
7231 
on_actionDeepestDecode_toggled(bool checked)7232 void MainWindow::on_actionDeepestDecode_toggled (bool checked)
7233 {
7234   m_ndepth ^= (-checked ^ m_ndepth) & 0x00000003;
7235 }
7236 
on_actionInclude_averaging_toggled(bool checked)7237 void MainWindow::on_actionInclude_averaging_toggled (bool checked)
7238 {
7239   m_ndepth ^= (-checked ^ m_ndepth) & 0x00000010;
7240 }
7241 
on_actionInclude_correlation_toggled(bool checked)7242 void MainWindow::on_actionInclude_correlation_toggled (bool checked)
7243 {
7244   m_ndepth ^= (-checked ^ m_ndepth) & 0x00000020;
7245 }
7246 
on_actionEnable_AP_DXcall_toggled(bool checked)7247 void MainWindow::on_actionEnable_AP_DXcall_toggled (bool checked)
7248 {
7249   m_ndepth ^= (-checked ^ m_ndepth) & 0x00000040;
7250 }
7251 
on_actionAuto_Clear_Avg_toggled(bool checked)7252 void MainWindow::on_actionAuto_Clear_Avg_toggled (bool checked)
7253 {
7254   m_ndepth ^= (-checked ^ m_ndepth) & 0x00000080;
7255 }
7256 
on_actionErase_ALL_TXT_triggered()7257 void MainWindow::on_actionErase_ALL_TXT_triggered()          //Erase ALL.TXT
7258 {
7259   int ret = MessageBox::query_message (this, tr ("Confirm Erase"),
7260                                          tr ("Are you sure you want to erase file ALL.TXT?"));
7261   if(ret==MessageBox::Yes) {
7262     QFile f {m_config.writeable_data_dir ().absoluteFilePath ("ALL.TXT")};
7263     f.remove();
7264     m_RxLog=1;
7265   }
7266 }
7267 
on_reset_cabrillo_log_action_triggered()7268 void MainWindow::on_reset_cabrillo_log_action_triggered ()
7269 {
7270   if (MessageBox::Yes == MessageBox::query_message (this, tr ("Confirm Reset"),
7271                                                     tr ("Are you sure you want to erase your contest log?"),
7272                                                     tr ("Doing this will remove all QSO records for the current contest. "
7273                                                         "They will be kept in the ADIF log file but will not be available "
7274                                                         "for export in your Cabrillo log.")))
7275     {
7276       if(m_config.RTTY_Exchange()!="SCC") ui->sbSerialNumber->setValue(1);
7277       m_logBook.contest_log ()->reset ();
7278     }
7279 }
7280 
on_actionExport_Cabrillo_log_triggered()7281 void MainWindow::on_actionExport_Cabrillo_log_triggered()
7282 {
7283   if (QDialog::Accepted == ExportCabrillo {m_settings, &m_config, m_logBook.contest_log ()}.exec())
7284     {
7285       MessageBox::information_message (this, tr ("Cabrillo Log saved"));
7286     }
7287 }
7288 
7289 
on_actionErase_wsjtx_log_adi_triggered()7290 void MainWindow::on_actionErase_wsjtx_log_adi_triggered()
7291 {
7292   int ret = MessageBox::query_message (this, tr ("Confirm Erase"),
7293                                        tr ("Are you sure you want to erase file wsjtx_log.adi?"));
7294   if(ret==MessageBox::Yes) {
7295     QFile f {m_config.writeable_data_dir ().absoluteFilePath ("wsjtx_log.adi")};
7296     f.remove();
7297   }
7298 }
7299 
on_actionErase_WSPR_hashtable_triggered()7300 void MainWindow::on_actionErase_WSPR_hashtable_triggered()
7301 {
7302   int ret = MessageBox::query_message(this, tr ("Confirm Erase"),
7303             tr ("Are you sure you want to erase the WSPR hashtable?"));
7304   if(ret==MessageBox::Yes) {
7305     QFile f {m_config.writeable_data_dir().absoluteFilePath("hashtable.txt")};
7306     f.remove();
7307   }
7308 }
7309 
7310 
on_actionOpen_log_directory_triggered()7311 void MainWindow::on_actionOpen_log_directory_triggered ()
7312 {
7313   QDesktopServices::openUrl (QUrl::fromLocalFile (m_config.writeable_data_dir ().absolutePath ()));
7314 }
7315 
on_bandComboBox_currentIndexChanged(int index)7316 void MainWindow::on_bandComboBox_currentIndexChanged (int index)
7317 {
7318   auto const& frequencies = m_config.frequencies ();
7319   auto const& source_index = frequencies->mapToSource (frequencies->index (index, FrequencyList_v2::frequency_column));
7320   Frequency frequency {m_freqNominal};
7321   if (source_index.isValid ())
7322     {
7323       frequency = frequencies->frequency_list ()[source_index.row ()].frequency_;
7324     }
7325 
7326   // Lookup band
7327   auto const& band  = m_config.bands ()->find (frequency);
7328   ui->bandComboBox->setCurrentText (band.size () ? band : m_config.bands ()->oob ());
7329   displayDialFrequency ();
7330 }
7331 
on_bandComboBox_editTextChanged(QString const & text)7332 void MainWindow::on_bandComboBox_editTextChanged (QString const& text)
7333 {
7334     // Z
7335     return;
7336 
7337   if (text.size () && m_config.bands ()->oob () != text)
7338     {
7339       ui->bandComboBox->lineEdit ()->setStyleSheet ({});
7340     }
7341   else
7342     {
7343       ui->bandComboBox->lineEdit ()->setStyleSheet ("QLineEdit {color: yellow; background-color : red;}");
7344     }
7345 }
7346 
on_bandComboBox_activated(int index)7347 void MainWindow::on_bandComboBox_activated (int index)
7348 {
7349   auto const& frequencies = m_config.frequencies ();
7350   auto const& source_index = frequencies->mapToSource (frequencies->index (index, FrequencyList_v2::frequency_column));
7351   Frequency frequency {m_freqNominal};
7352   if (source_index.isValid ())
7353     {
7354       frequency = frequencies->frequency_list ()[source_index.row ()].frequency_;
7355     }
7356   m_bandEdited = true;
7357   band_changed (frequency);
7358   m_wideGraph->setRxBand (m_config.bands ()->find (frequency));
7359   // Z
7360   if (m_config.clearRX()) clearRXWindows();
7361 }
7362 
band_changed(Frequency f)7363 void MainWindow::band_changed (Frequency f)
7364 {
7365   // Set the attenuation value if options are checked
7366   if (m_config.pwrBandTxMemory() && !m_tune) {
7367     auto const&curBand = ui->bandComboBox->currentText();
7368     if (m_pwrBandTxMemory.contains(curBand)) {
7369       ui->outAttenuation->setValue(m_pwrBandTxMemory[curBand].toInt());
7370     }
7371     else {
7372       m_pwrBandTxMemory[curBand] = ui->outAttenuation->value();
7373     }
7374   }
7375 
7376   if (m_bandEdited) {
7377     if (m_mode!="WSPR") { // band hopping preserves auto Tx
7378       if (f + m_wideGraph->nStartFreq () > m_freqNominal + ui->TxFreqSpinBox->value ()
7379           || f + m_wideGraph->nStartFreq () + m_wideGraph->fSpan () <=
7380           m_freqNominal + ui->TxFreqSpinBox->value ()) {
7381 //        qDebug () << "start f:" << m_wideGraph->nStartFreq () << "span:" << m_wideGraph->fSpan () << "DF:" << ui->TxFreqSpinBox->value ();
7382         // disable auto Tx if "blind" QSY outside of waterfall
7383         ui->stopTxButton->click (); // halt any transmission
7384         auto_tx_mode (false);       // disable auto Tx
7385 //        m_send_RR73 = false;        // force user to reassess on new band
7386       }
7387     }
7388     m_lastBand.clear ();
7389     m_bandEdited = false;
7390     if (m_config.spot_to_psk_reporter ())
7391       {
7392         // Upload any queued spots before changing band
7393         m_psk_Reporter.sendReport();
7394       }
7395     if (!m_transmitting) monitor (true);
7396     if ("FreqCal" == m_mode)
7397       {
7398         m_frequency_list_fcal_iter = m_config.frequencies ()->find (f);
7399       }
7400     setRig (f);
7401     setXIT (ui->TxFreqSpinBox->value ());
7402   }
7403 }
7404 
enable_DXCC_entity(bool on)7405 void MainWindow::enable_DXCC_entity (bool on)
7406 {
7407   if (on and m_mode!="WSPR" and m_mode!="FST4W" and m_mode!="Echo") {
7408     //m_logBook.init();                        // re-read the log and cty.dat files
7409 //    ui->gridLayout->setColumnStretch(0,55);  // adjust proportions of text displays
7410 //    ui->gridLayout->setColumnStretch(1,45);
7411   } else {
7412 //    ui->gridLayout->setColumnStretch(0,0);
7413 //    ui->gridLayout->setColumnStretch(1,0);
7414   }
7415   updateGeometry ();
7416 }
7417 
on_rptSpinBox_valueChanged(int n)7418 void MainWindow::on_rptSpinBox_valueChanged(int n)
7419 {
7420   int step=ui->rptSpinBox->singleStep();
7421   if(n%step !=0) {
7422     n++;
7423     ui->rptSpinBox->setValue(n);
7424   }
7425   m_rpt=QString::number(n);
7426   int ntx0=m_ntx;
7427   genStdMsgs(m_rpt);
7428   m_ntx=ntx0;
7429   if(m_ntx==1) ui->txrb1->setChecked(true);
7430   if(m_ntx==2) ui->txrb2->setChecked(true);
7431   if(m_ntx==3) ui->txrb3->setChecked(true);
7432   if(m_ntx==4) ui->txrb4->setChecked(true);
7433   if(m_ntx==5) ui->txrb5->setChecked(true);
7434   if(m_ntx==6) ui->txrb6->setChecked(true);
7435   statusChanged();
7436 }
7437 
on_tuneButton_clicked(bool checked)7438 void MainWindow::on_tuneButton_clicked (bool checked)
7439 {
7440   if (checked) tx_watchdog(false);
7441   static bool lastChecked = false;
7442   if (lastChecked == checked) return;
7443   lastChecked = checked;
7444   if (checked && m_tune==false) { // we're starting tuning so remember Tx and change pwr to Tune value
7445     if (m_config.pwrBandTuneMemory ()) {
7446       auto const& curBand = ui->bandComboBox->currentText();
7447       m_pwrBandTxMemory[curBand] = ui->outAttenuation->value(); // remember our Tx pwr
7448       m_PwrBandSetOK = false;
7449       if (m_pwrBandTuneMemory.contains(curBand)) {
7450         ui->outAttenuation->setValue(m_pwrBandTuneMemory[curBand].toInt()); // set to Tune pwr
7451       }
7452       m_PwrBandSetOK = true;
7453     }
7454   }
7455   if (m_tune) {
7456     tuneButtonTimer.start(250);
7457   } else {
7458     m_sentFirst73=false;
7459     itone[0]=0;
7460     on_monitorButton_clicked (true);
7461     m_tune=true;
7462   }
7463   Q_EMIT tune (checked);
7464 }
7465 
end_tuning()7466 void MainWindow::end_tuning ()
7467 {
7468   on_stopTxButton_clicked ();
7469   // we're turning off so remember our Tune pwr setting and reset to Tx pwr
7470   if (m_config.pwrBandTuneMemory() || m_config.pwrBandTxMemory()) {
7471     auto const& curBand = ui->bandComboBox->currentText();
7472     m_pwrBandTuneMemory[curBand] = ui->outAttenuation->value(); // remember our Tune pwr
7473     m_PwrBandSetOK = false;
7474     ui->outAttenuation->setValue(m_pwrBandTxMemory[curBand].toInt()); // set to Tx pwr
7475     m_PwrBandSetOK = true;
7476   }
7477 }
7478 
stop_tuning()7479 void MainWindow::stop_tuning ()
7480 {
7481   on_tuneButton_clicked(false);
7482   ui->tuneButton->setChecked (false);
7483   m_bTxTime=false;
7484   m_tune=false;
7485 }
7486 
stopTuneATU()7487 void MainWindow::stopTuneATU()
7488 {
7489   on_tuneButton_clicked(false);
7490   m_bTxTime=false;
7491 }
7492 
on_stopTxButton_clicked()7493 void MainWindow::on_stopTxButton_clicked()                    //Stop Tx
7494 {
7495   log("stopTXButton");
7496   if (m_tune) stop_tuning ();
7497   if (m_auto and !m_tuneup) auto_tx_mode (false);
7498   ui->autoButton->setChecked (false);
7499   // Z
7500   on_autoButton_clicked (false);
7501   m_btxok=false;
7502   m_bCallingCQ = false;
7503   m_bAutoReply = false;         // ready for next
7504   ui->cbFirst->setStyleSheet ("");
7505   m_QSOProgress = CALLING;
7506   if (m_config.rxTotxFreq()) on_pbT2R_clicked();
7507 }
7508 
rigOpen()7509 void MainWindow::rigOpen ()
7510 {
7511   update_dynamic_property (ui->readFreq, "state", "warning");
7512   ui->readFreq->setText ("");
7513   ui->readFreq->setEnabled (true);
7514   m_config.transceiver_online ();
7515   m_config.sync_transceiver (true, true);
7516 }
7517 
on_pbR2T_clicked()7518 void MainWindow::on_pbR2T_clicked()
7519 {
7520   ui->TxFreqSpinBox->setValue(ui->RxFreqSpinBox->value ());
7521 }
7522 
on_pbT2R_clicked()7523 void MainWindow::on_pbT2R_clicked()
7524 {
7525   if (ui->RxFreqSpinBox->isEnabled ())
7526     {
7527       ui->RxFreqSpinBox->setValue (ui->TxFreqSpinBox->value ());
7528     }
7529 }
7530 
7531 
on_readFreq_clicked()7532 void MainWindow::on_readFreq_clicked()
7533 {
7534   if (m_transmitting) return;
7535 
7536   if (m_config.transceiver_online ())
7537     {
7538       m_config.sync_transceiver (true, true);
7539     }
7540 }
7541 
setXIT(int n,Frequency base)7542 void MainWindow::setXIT(int n, Frequency base)
7543 {
7544   if (m_transmitting && !m_config.tx_QSY_allowed ()) return;
7545   // If "CQ nnn ..." feature is active, set the proper Tx frequency
7546   if(m_config.split_mode () && ui->cbCQTx->isEnabled () && ui->cbCQTx->isVisible () &&
7547      ui->cbCQTx->isChecked())
7548     {
7549       if (6 == m_ntx || (7 == m_ntx && m_gen_message_is_cq))
7550         {
7551           // All conditions are met, use calling frequency
7552           base = m_freqNominal / 1000000 * 1000000 + 1000 * ui->sbCQTxFreq->value () + m_XIT;
7553         }
7554     }
7555   if (!base) base = m_freqNominal;
7556   m_XIT = 0;
7557   if (!m_bSimplex) {
7558     // m_bSimplex is false, so we can use split mode if requested
7559     if (m_config.split_mode () && (!m_config.enable_VHF_features () ||
7560         m_mode=="FT4" || m_mode == "FT8" || m_mode=="FST4")) {
7561       // Don't use XIT for VHF & up
7562       m_XIT=(n/500)*500 - 1500;
7563     }
7564 
7565     if ((m_monitoring || m_transmitting)
7566         && m_config.is_transceiver_online ()
7567         && m_config.split_mode ())
7568       {
7569         // All conditions are met, reset the transceiver Tx dial
7570         // frequency
7571         m_freqTxNominal = base + m_XIT;
7572         if (m_astroWidget) m_astroWidget->nominal_frequency (m_freqNominal, m_freqTxNominal);
7573         m_config.transceiver_tx_frequency (m_freqTxNominal + m_astroCorrection.tx);
7574       }
7575   }
7576   //Now set the audio Tx freq
7577   Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value () - m_XIT);
7578 }
7579 
setFreq4(int rxFreq,int txFreq)7580 void MainWindow::setFreq4(int rxFreq, int txFreq)
7581 {
7582   if (ui->RxFreqSpinBox->isEnabled ()) ui->RxFreqSpinBox->setValue(rxFreq);
7583   if(m_mode=="WSPR" or m_mode=="FST4W") {
7584     ui->WSPRfreqSpinBox->setValue(txFreq);
7585   } else {
7586     if (ui->TxFreqSpinBox->isEnabled ()) {
7587       ui->TxFreqSpinBox->setValue(txFreq);
7588       if ("FT8" == m_mode || "FT4" == m_mode || m_mode=="FST4")
7589         {
7590           // we need to regenerate the current transmit waveform for
7591           // GFSK modulated modes
7592           if (m_transmitting) m_restart = true;
7593         }
7594     }
7595     else if (m_config.enable_VHF_features ()
7596              && (Qt::ControlModifier & QApplication::keyboardModifiers ())) {
7597       // for VHF & up we adjust Tx dial frequency to equalize Tx to Rx
7598       // when user CTRL+clicks on waterfall
7599       auto temp = ui->TxFreqSpinBox->value ();
7600       ui->RxFreqSpinBox->setValue (temp);
7601       setRig (m_freqNominal + txFreq - temp);
7602       setXIT (ui->TxFreqSpinBox->value ());
7603     }
7604   }
7605 }
7606 
handle_transceiver_update(Transceiver::TransceiverState const & s)7607 void MainWindow::handle_transceiver_update (Transceiver::TransceiverState const& s)
7608 {
7609   Transceiver::TransceiverState old_state {m_rigState};
7610   //transmitDisplay (s.ptt ());
7611   if (s.ptt () // && !m_rigState.ptt ()
7612       ) { // safe to start audio
7613                                         // (caveat - DX Lab Suite Commander)
7614     if (m_tx_when_ready && g_iptt) {    // waiting to Tx and still needed
7615       int ms_delay=1000*m_config.txDelay();
7616       if(m_mode=="FT4") ms_delay=20;
7617       ptt1Timer.start(ms_delay); //Start-of-transmission sequencer delay
7618       m_tx_when_ready = false;
7619     }
7620   }
7621   m_rigState = s;
7622   auto old_freqNominal = m_freqNominal;
7623   if (!old_freqNominal)
7624     {
7625       // always take initial rig frequency to avoid start up problems
7626       // with bogus Tx frequencies
7627       m_freqNominal = s.frequency ();
7628     }
7629   if (old_state.online () == false && s.online () == true)
7630     {
7631       // initializing
7632       on_monitorButton_clicked (!m_config.monitor_off_at_startup ());
7633     }
7634   if (s.frequency () != old_state.frequency () || s.split () != m_splitMode)
7635     {
7636       m_splitMode = s.split ();
7637       if (!s.ptt ())
7638         {
7639           m_freqNominal = s.frequency () - m_astroCorrection.rx;
7640           if (old_freqNominal != m_freqNominal)
7641             {
7642               m_freqTxNominal = m_freqNominal;
7643               genCQMsg ();
7644             }
7645 
7646           if (m_monitoring)
7647             {
7648               m_lastMonitoredFrequency = m_freqNominal;
7649             }
7650           if (m_lastDialFreq != m_freqNominal &&
7651               (m_mode != "MSK144"
7652                || !(ui->cbCQTx->isEnabled () && ui->cbCQTx->isVisible () && ui->cbCQTx->isChecked()))) {
7653             m_lastDialFreq = m_freqNominal;
7654             m_secBandChanged=QDateTime::currentMSecsSinceEpoch()/1000;
7655             pskSetLocal ();
7656             statusChanged();
7657             m_wideGraph->setDialFreq(m_freqNominal / 1.e6);
7658           }
7659       } else {
7660         m_freqTxNominal = s.split () ? s.tx_frequency () - m_astroCorrection.tx : s.frequency ();
7661       }
7662       if (m_astroWidget) m_astroWidget->nominal_frequency (m_freqNominal, m_freqTxNominal);
7663   }
7664   // ensure frequency display is correct
7665   if (m_astroWidget && old_state.ptt () != s.ptt ()) setRig ();
7666 
7667   displayDialFrequency ();
7668   update_dynamic_property (ui->readFreq, "state", "ok");
7669   ui->readFreq->setEnabled (false);
7670   ui->readFreq->setText (s.split () ? "S" : "");
7671 }
7672 
handle_transceiver_failure(QString const & reason)7673 void MainWindow::handle_transceiver_failure (QString const& reason)
7674 {
7675   update_dynamic_property (ui->readFreq, "state", "error");
7676   ui->readFreq->setEnabled (true);
7677   log("on_stopTxButton_clicked: handle_transceiver_failure");
7678   on_stopTxButton_clicked ();
7679   rigFailure (reason);
7680 }
7681 
rigFailure(QString const & reason)7682 void MainWindow::rigFailure (QString const& reason)
7683 {
7684   if (m_first_error)
7685     {
7686       // one automatic retry
7687       QTimer::singleShot (0, this, SLOT (rigOpen ()));
7688       m_first_error = false;
7689     }
7690   else
7691     {
7692       if (m_splash && m_splash->isVisible ()) m_splash->hide ();
7693       m_rigErrorMessageBox.setDetailedText (reason + "\n\nTimestamp: "
7694 #if QT_VERSION >= QT_VERSION_CHECK (5, 8, 0)
7695                                             + QDateTime::currentDateTimeUtc ().toString (Qt::ISODateWithMs)
7696 #else
7697                                             + QDateTime::currentDateTimeUtc ().toString ("yyyy-MM-ddTHH:mm:ss.zzzZ")
7698 #endif
7699                                             );
7700 
7701       // don't call slot functions directly to avoid recursion
7702       m_rigErrorMessageBox.exec ();
7703       auto const clicked_button = m_rigErrorMessageBox.clickedButton ();
7704       if (clicked_button == m_configurations_button)
7705         {
7706           ui->menuConfig->exec (QCursor::pos ());
7707         }
7708       else
7709         {
7710           switch (m_rigErrorMessageBox.standardButton (clicked_button))
7711             {
7712             case MessageBox::Ok:
7713               m_config.select_tab (1);
7714               QTimer::singleShot (0, this, SLOT (on_actionSettings_triggered ()));
7715               break;
7716 
7717             case MessageBox::Retry:
7718               QTimer::singleShot (0, this, SLOT (rigOpen ()));
7719               break;
7720 
7721             case MessageBox::Cancel:
7722               QTimer::singleShot (0, this, SLOT (close ()));
7723               break;
7724 
7725             default: break;     // squashing compile warnings
7726             }
7727         }
7728       m_first_error = true;     // reset
7729     }
7730 }
7731 
transmit(double snr)7732 void MainWindow::transmit (double snr)
7733 {
7734   double toneSpacing=0.0;
7735   if (m_mode == "JT65") {
7736     if(m_nSubMode==0) toneSpacing=11025.0/4096.0;
7737     if(m_nSubMode==1) toneSpacing=2*11025.0/4096.0;
7738     if(m_nSubMode==2) toneSpacing=4*11025.0/4096.0;
7739     Q_EMIT sendMessage (m_mode, NUM_JT65_SYMBOLS,
7740            4096.0*12000.0/11025.0, ui->TxFreqSpinBox->value () - m_XIT,
7741            toneSpacing, m_soundOutput, m_config.audio_output_channel (),
7742            true, false, snr, m_TRperiod);
7743   }
7744 
7745   if (m_mode == "FT8") {
7746 //    toneSpacing=12000.0/1920.0;
7747     toneSpacing=-3;
7748     if(m_config.x2ToneSpacing()) toneSpacing=2*12000.0/1920.0;
7749     if(m_config.x4ToneSpacing()) toneSpacing=4*12000.0/1920.0;
7750     if(SpecOp::FOX==m_config.special_op_id() and !m_tune) toneSpacing=-1;
7751     Q_EMIT sendMessage (m_mode, NUM_FT8_SYMBOLS,
7752            1920.0, ui->TxFreqSpinBox->value () - m_XIT,
7753            toneSpacing, m_soundOutput, m_config.audio_output_channel (),
7754            true, false, snr, m_TRperiod);
7755   }
7756 
7757   if (m_mode == "FT4") {
7758     m_dateTimeSentTx3=QDateTime::currentDateTimeUtc();
7759     toneSpacing=-2.0;                     //Transmit a pre-computed, filtered waveform.
7760     Q_EMIT sendMessage (m_mode, NUM_FT4_SYMBOLS,
7761            576.0, ui->TxFreqSpinBox->value() - m_XIT,
7762            toneSpacing, m_soundOutput, m_config.audio_output_channel(),
7763            true, false, snr, m_TRperiod);
7764   }
7765 
7766   if (m_mode == "FST4" or m_mode == "FST4W") {
7767     m_dateTimeSentTx3=QDateTime::currentDateTimeUtc();
7768     toneSpacing=-2.0;                     //Transmit a pre-computed, filtered waveform.
7769     int nsps=720;
7770     if(m_TRperiod==30) nsps=1680;
7771     if(m_TRperiod==60) nsps=3888;
7772     if(m_TRperiod==120) nsps=8200;
7773     if(m_TRperiod==300) nsps=21504;
7774     if(m_TRperiod==900) nsps=66560;
7775     if(m_TRperiod==1800) nsps=134400;
7776     int hmod=1;
7777     if(m_config.x2ToneSpacing()) hmod=2;
7778     if(m_config.x4ToneSpacing()) hmod=4;
7779     double dfreq=hmod*12000.0/nsps;
7780     double f0=ui->WSPRfreqSpinBox->value() - m_XIT;
7781     if(m_mode=="FST4") f0=ui->TxFreqSpinBox->value() - m_XIT;
7782     if(!m_tune) f0 += 1.5*dfreq;
7783     Q_EMIT sendMessage (m_mode, NUM_FST4_SYMBOLS,double(nsps),f0,toneSpacing,
7784                         m_soundOutput,m_config.audio_output_channel(),
7785                         true, false, snr, m_TRperiod);
7786   }
7787 
7788   if (m_mode == "Q65") {
7789     int nsps=1800;
7790     if(m_TRperiod==30) nsps=3600;
7791     if(m_TRperiod==60) nsps=7200;
7792     if(m_TRperiod==120) nsps=16000;
7793     if(m_TRperiod==300) nsps=41472;
7794     int mode65=pow(2.0,double(m_nSubMode));
7795     toneSpacing=mode65*12000.0/nsps;
7796 //    toneSpacing=-4.0;
7797     Q_EMIT sendMessage (m_mode, NUM_Q65_SYMBOLS,
7798            double(nsps), ui->TxFreqSpinBox->value () - m_XIT,
7799            toneSpacing, m_soundOutput, m_config.audio_output_channel (),
7800            true, false, snr, m_TRperiod);
7801   }
7802 
7803   if (m_mode == "JT9") {
7804     int nsub=pow(2,m_nSubMode);
7805     int nsps[]={480,240,120,60};
7806     double sps=m_nsps;
7807     m_toneSpacing=nsub*12000.0/6912.0;
7808     if(m_config.x2ToneSpacing()) m_toneSpacing=2.0*m_toneSpacing;
7809     if(m_config.x4ToneSpacing()) m_toneSpacing=4.0*m_toneSpacing;
7810     bool fastmode=false;
7811     if(m_bFast9 and (m_nSubMode>=4)) {
7812       fastmode=true;
7813       sps=nsps[m_nSubMode-4];
7814       m_toneSpacing=12000.0/sps;
7815     }
7816     Q_EMIT sendMessage (m_mode, NUM_JT9_SYMBOLS, sps,
7817                         ui->TxFreqSpinBox->value() - m_XIT, m_toneSpacing,
7818                         m_soundOutput, m_config.audio_output_channel (),
7819                         true, fastmode, snr, m_TRperiod);
7820   }
7821 
7822   if (m_mode == "MSK144") {
7823     m_nsps=6;
7824     double f0=1000.0;
7825     if(!m_bFastMode) {
7826       m_nsps=192;
7827       f0=ui->TxFreqSpinBox->value () - m_XIT - 0.5*m_toneSpacing;
7828     }
7829     m_toneSpacing=6000.0/m_nsps;
7830     m_FFTSize = 7 * 512;
7831     Q_EMIT FFTSize (m_FFTSize);
7832     int nsym;
7833     nsym=NUM_MSK144_SYMBOLS;
7834     if(itone[40] < 0) nsym=40;
7835     Q_EMIT sendMessage (m_mode, nsym, double(m_nsps), f0, m_toneSpacing,
7836                         m_soundOutput, m_config.audio_output_channel (),
7837                         true, true, snr, m_TRperiod);
7838   }
7839 
7840   if (m_mode == "JT4") {
7841     if(m_nSubMode==0) toneSpacing=4.375;
7842     if(m_nSubMode==1) toneSpacing=2*4.375;
7843     if(m_nSubMode==2) toneSpacing=4*4.375;
7844     if(m_nSubMode==3) toneSpacing=9*4.375;
7845     if(m_nSubMode==4) toneSpacing=18*4.375;
7846     if(m_nSubMode==5) toneSpacing=36*4.375;
7847     if(m_nSubMode==6) toneSpacing=72*4.375;
7848     Q_EMIT sendMessage (m_mode, NUM_JT4_SYMBOLS,
7849            2520.0*12000.0/11025.0, ui->TxFreqSpinBox->value () - m_XIT,
7850            toneSpacing, m_soundOutput, m_config.audio_output_channel (),
7851            true, false, snr, m_TRperiod);
7852   }
7853   if (m_mode=="WSPR") {
7854     int nToneSpacing=1;
7855     if(m_config.x2ToneSpacing()) nToneSpacing=2;
7856     if(m_config.x4ToneSpacing()) nToneSpacing=4;
7857     Q_EMIT sendMessage (m_mode, NUM_WSPR_SYMBOLS, 8192.0,
7858                         ui->TxFreqSpinBox->value() - 1.5 * 12000 / 8192,
7859                         m_toneSpacing*nToneSpacing, m_soundOutput,
7860                         m_config.audio_output_channel(),true, false, snr,
7861                         m_TRperiod);
7862   }
7863 
7864   if(m_mode=="Echo") {
7865     //??? should use "fastMode = true" here ???
7866     Q_EMIT sendMessage (m_mode, 27, 1024.0, 1500.0, 0.0, m_soundOutput,
7867                         m_config.audio_output_channel(),
7868                         false, false, snr, m_TRperiod);
7869   }
7870 
7871 // In auto-sequencing mode, stop after 5 transmissions of "73" message.
7872   if (m_bFastMode || m_bFast9) {
7873     if (ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isEnabled () && ui->cbAutoSeq->isChecked ()) {
7874       if(m_ntx==5) {
7875         m_nTx73 += 1;
7876       } else {
7877         m_nTx73=0;
7878       }
7879     }
7880   }
7881 }
7882 
on_outAttenuation_valueChanged(int a)7883 void MainWindow::on_outAttenuation_valueChanged (int a)
7884 {
7885   QString tt_str;
7886   qreal dBAttn {a / 10.};       // slider interpreted as dB / 100
7887   if (m_tune && m_config.pwrBandTuneMemory()) {
7888     tt_str = tr ("Tune digital gain ");
7889   } else {
7890     tt_str = tr ("Transmit digital gain ");
7891   }
7892   tt_str += (a ? QString::number (-dBAttn, 'f', 1) : "0") + "dB";
7893   if (!m_block_pwr_tooltip) {
7894     QToolTip::showText (QCursor::pos (), tt_str, ui->outAttenuation);
7895   }
7896   QString curBand = ui->bandComboBox->currentText();
7897   if (m_PwrBandSetOK && !m_tune && m_config.pwrBandTxMemory ()) {
7898     m_pwrBandTxMemory[curBand] = a; // remember our Tx pwr
7899   }
7900   if (m_PwrBandSetOK && m_tune && m_config.pwrBandTuneMemory()) {
7901     m_pwrBandTuneMemory[curBand] = a; // remember our Tune pwr
7902   }
7903   Q_EMIT outAttenuationChanged (dBAttn);
7904 }
7905 
on_actionShort_list_of_add_on_prefixes_and_suffixes_triggered()7906 void MainWindow::on_actionShort_list_of_add_on_prefixes_and_suffixes_triggered()
7907 {
7908   if (!m_prefixes) {
7909     m_prefixes.reset (new HelpTextWindow {tr ("Prefixes")
7910                                             , R"(Type 1 Prefixes:
7911 
7912  1A    1S    3A    3B6   3B8   3B9   3C    3C0   3D2   3D2C  3D2R  3DA   3V    3W    3X
7913  3Y    3YB   3YP   4J    4L    4S    4U1I  4U1U  4W    4X    5A    5B    5H    5N    5R
7914  5T    5U    5V    5W    5X    5Z    6W    6Y    7O    7P    7Q    7X    8P    8Q    8R
7915  9A    9G    9H    9J    9K    9L    9M2   9M6   9N    9Q    9U    9V    9X    9Y    A2
7916  A3    A4    A5    A6    A7    A9    AP    BS7   BV    BV9   BY    C2    C3    C5    C6
7917  C9    CE    CE0X  CE0Y  CE0Z  CE9   CM    CN    CP    CT    CT3   CU    CX    CY0   CY9
7918  D2    D4    D6    DL    DU    E3    E4    EA    EA6   EA8   EA9   EI    EK    EL    EP
7919  ER    ES    ET    EU    EX    EY    EZ    F     FG    FH    FJ    FK    FKC   FM    FO
7920  FOA   FOC   FOM   FP    FR    FRG   FRJ   FRT   FT5W  FT5X  FT5Z  FW    FY    M     MD
7921  MI    MJ    MM    MU    MW    H4    H40   HA    HB    HB0   HC    HC8   HH    HI    HK
7922  HK0A  HK0M  HL    HM    HP    HR    HS    HV    HZ    I     IS    IS0   J2    J3    J5
7923  J6    J7    J8    JA    JDM   JDO   JT    JW    JX    JY    K     KG4   KH0   KH1   KH2
7924  KH3   KH4   KH5   KH5K  KH6   KH7   KH8   KH9   KL    KP1   KP2   KP4   KP5   LA    LU
7925  LX    LY    LZ    OA    OD    OE    OH    OH0   OJ0   OK    OM    ON    OX    OY    OZ
7926  P2    P4    PA    PJ2   PJ7   PY    PY0F  PT0S  PY0T  PZ    R1F   R1M   S0    S2    S5
7927  S7    S9    SM    SP    ST    SU    SV    SVA   SV5   SV9   T2    T30   T31   T32   T33
7928  T5    T7    T8    T9    TA    TF    TG    TI    TI9   TJ    TK    TL    TN    TR    TT
7929  TU    TY    TZ    UA    UA2   UA9   UK    UN    UR    V2    V3    V4    V5    V6    V7
7930  V8    VE    VK    VK0H  VK0M  VK9C  VK9L  VK9M  VK9N  VK9W  VK9X  VP2E  VP2M  VP2V  VP5
7931  VP6   VP6D  VP8   VP8G  VP8H  VP8O  VP8S  VP9   VQ9   VR    VU    VU4   VU7   XE    XF4
7932  XT    XU    XW    XX9   XZ    YA    YB    YI    YJ    YK    YL    YN    YO    YS    YU
7933  YV    YV0   Z2    Z3    ZA    ZB    ZC4   ZD7   ZD8   ZD9   ZF    ZK1N  ZK1S  ZK2   ZK3
7934  ZL    ZL7   ZL8   ZL9   ZP    ZS    ZS8   KC4   E5
7935 
7936 Type 1 Suffixes:    /0 /1 /2 /3 /4 /5 /6 /7 /8 /9 /A /P)", {"Courier", 10}});
7937   }
7938   m_prefixes->showNormal();
7939   m_prefixes->raise ();
7940 }
7941 
shortList(QString callsign) const7942 bool MainWindow::shortList(QString callsign) const
7943 {
7944   int n=callsign.length();
7945   int i1=callsign.indexOf("/");
7946   Q_ASSERT(i1>0 and i1<n);
7947   QString t1=callsign.mid(0,i1);
7948   QString t2=callsign.mid(i1+1,n-i1-1);
7949   bool b=(m_pfx.contains(t1) or m_sfx.contains(t2));
7950   return b;
7951 }
7952 
pskSetLocal()7953 void MainWindow::pskSetLocal ()
7954 {
7955   if (!m_config.spot_to_psk_reporter ()) return;
7956 
7957   // find the station row, if any, that matches the band we are on
7958   auto stations = m_config.stations ();
7959   auto matches = stations->match (stations->index (0, StationList::band_column)
7960                                   , Qt::DisplayRole
7961                                   , ui->bandComboBox->currentText ()
7962                                   , 1
7963                                   , Qt::MatchExactly);
7964   QString antenna_description;
7965   if (!matches.isEmpty ()) {
7966     antenna_description = stations->index (matches.first ().row ()
7967                                            , StationList::description_column).data ().toString ();
7968   }
7969   // qDebug() << "To PSKreporter: local station details";
7970   m_psk_Reporter.setLocalStation(m_config.my_callsign (), m_config.my_grid (), antenna_description);
7971 }
7972 
transmitDisplay(bool transmitting)7973 void MainWindow::transmitDisplay (bool transmitting)
7974 {
7975   if (transmitting == m_transmitting) {
7976     if (transmitting) {
7977       ui->signal_meter_widget->setValue(0,0);
7978       if (m_monitoring) monitor (false);
7979       m_btxok=true;
7980     }
7981 
7982     auto QSY_allowed = !transmitting or m_config.tx_QSY_allowed () or
7983       !m_config.split_mode ();
7984     if (ui->cbHoldTxFreq->isChecked ()) {
7985       ui->TxFreqSpinBox->setEnabled (QSY_allowed);
7986       ui->pbT2R->setEnabled (QSY_allowed);
7987     }
7988 
7989     if (m_mode!="WSPR" and m_mode!="FST4W") {
7990       if(m_config.enable_VHF_features ()) {
7991         ui->TxFreqSpinBox->setEnabled (true);
7992       } else {
7993         ui->TxFreqSpinBox->setEnabled (QSY_allowed and !m_bFastMode);
7994         ui->pbR2T->setEnabled (QSY_allowed);
7995         ui->cbHoldTxFreq->setEnabled (QSY_allowed);
7996       }
7997     }
7998 
7999     // the following are always disallowed in transmit
8000     ui->menuMode->setEnabled (!transmitting);
8001   }
8002 }
8003 
on_sbFtol_valueChanged(int value)8004 void MainWindow::on_sbFtol_valueChanged(int value)
8005 {
8006   m_wideGraph->setTol (value);
8007   statusUpdate ();
8008 }
8009 
VHF_features_enabled(bool b)8010 void::MainWindow::VHF_features_enabled(bool b)
8011 {
8012   if(m_mode!="JT4" and m_mode!="JT65" and m_mode!="Q65") b=false;
8013   if(b and m_mode!="Q65" and (ui->actionInclude_averaging->isChecked() or
8014              ui->actionInclude_correlation->isChecked())) {
8015     ui->actionDeepestDecode->setChecked (true);
8016   }
8017   ui->actionInclude_averaging->setVisible (b);
8018   ui->actionInclude_correlation->setVisible (b && m_mode!="Q65");
8019   ui->actionMessage_averaging->setEnabled(b && (m_mode=="JT4" or m_mode=="JT65"));
8020   ui->actionEnable_AP_JT65->setVisible (b && m_mode=="JT65");
8021 
8022   if(!b && m_msgAvgWidget and (SpecOp::FOX != m_config.special_op_id()) and !m_config.autoLog()) {
8023     if(m_msgAvgWidget->isVisible() and m_mode!="JT4" and m_mode!="JT9" and m_mode!="JT65") {
8024       m_msgAvgWidget->close();
8025     }
8026   }
8027 }
8028 
on_sbTR_valueChanged(int value)8029 void MainWindow::on_sbTR_valueChanged(int value)
8030 {
8031   //  if(!m_bFastMode and n>m_nSubMode) m_MinW=m_nSubMode;
8032   if(m_bFastMode or m_mode=="FreqCal" or m_mode=="FST4" or m_mode=="FST4W" or m_mode=="Q65") {
8033     m_TRperiod = value;
8034     if (m_mode == "FST4" || m_mode == "FST4W" || m_mode=="Q65")
8035       {
8036         if (m_TRperiod < 60)
8037           {
8038             ui->lh_decodes_headings_label->setText("  UTC   dB   DT Freq    " + tr ("Message"));
8039             if (m_mode != "FST4W")
8040               {
8041                 ui->rh_decodes_headings_label->setText("  UTC   dB   DT Freq    " + tr ("Message"));
8042               }
8043           }
8044         else
8045           {
8046             ui->lh_decodes_headings_label->setText("UTC   dB   DT Freq    " + tr ("Message"));
8047             if (m_mode != "FST4W")
8048               {
8049                 ui->rh_decodes_headings_label->setText("UTC   dB   DT Freq    " + tr ("Message"));
8050               }
8051           }
8052 
8053        if ("Q65" == m_mode)
8054          {
8055          switch (value)
8056              {
8057               case 15: ui->sbSubmode->setMaximum (2); break;
8058               case 30: ui->sbSubmode->setMaximum (3); break;
8059               default: ui->sbSubmode->setMaximum (4); break;
8060               }
8061           }
8062        }
8063     m_fastGraph->setTRPeriod (value);
8064     m_modulator->setTRPeriod (value); // TODO - not thread safe
8065     m_detector->setTRPeriod (value);  // TODO - not thread safe
8066     m_wideGraph->setPeriod (value, m_nsps);
8067     progressBar.setMaximum (value);
8068   }
8069   if(m_mode=="FST4") chk_FST4_freq_range();
8070 //  if(m_transmitting) on_stopTxButton_clicked();      //### Is this needed or desirable? ###
8071   on_sbSubmode_valueChanged(ui->sbSubmode->value());
8072   statusUpdate ();
8073 }
8074 
on_sbTR_FST4W_valueChanged(int value)8075 void MainWindow::on_sbTR_FST4W_valueChanged(int value)
8076 {
8077   on_sbTR_valueChanged(value);
8078 }
8079 
current_submode() const8080 QChar MainWindow::current_submode () const
8081 {
8082   QChar submode {0};
8083   if (m_mode.contains (QRegularExpression {R"(^(JT65|JT9|JT4|Q65)$)"})
8084       && (m_config.enable_VHF_features () || "JT4" == m_mode))
8085     {
8086       submode = m_nSubMode + 65;
8087     }
8088   return submode;
8089 }
8090 
on_sbSubmode_valueChanged(int n)8091 void MainWindow::on_sbSubmode_valueChanged(int n)
8092 {
8093   m_nSubMode=n;
8094   m_wideGraph->setSubMode(m_nSubMode);
8095   auto submode = current_submode ();
8096   if (submode != QChar::Null) {
8097     QString t{m_mode + " " + submode};
8098     if(m_mode=="Q65") t=m_mode + "-" + QString::number(m_TRperiod) + submode;
8099     mode_label.setText (t);
8100   } else {
8101     mode_label.setText (m_mode);
8102   }
8103   if(m_mode=="Q65") {
8104     if(((m_nSubMode==4 && m_TRperiod==60.0) || (m_nSubMode==3 && m_TRperiod==30.0) ||
8105        (m_nSubMode==2 && m_TRperiod==15.0)) && ui->TxFreqSpinBox->value()!=700) {
8106       ui->TxFreqSpinBox->setStyleSheet("QSpinBox{background-color:red}");
8107     } else {
8108       ui->TxFreqSpinBox->setStyleSheet("");
8109     }
8110   }
8111   if(m_mode=="JT9") {
8112     if(m_nSubMode<4) {
8113       ui->cbFast9->setChecked(false);
8114       on_cbFast9_clicked(false);
8115       ui->cbFast9->setEnabled(false);
8116       ui->sbTR->setVisible(false);
8117       m_TRperiod=60.0;
8118     } else {
8119       ui->cbFast9->setEnabled(true);
8120     }
8121     ui->sbTR->setVisible(m_bFast9);
8122     if(m_bFast9) ui->TxFreqSpinBox->setValue(700);
8123   }
8124   if(m_transmitting and m_bFast9 and m_nSubMode>=4) transmit (99.0);
8125   statusUpdate ();
8126 }
8127 
on_cbFast9_clicked(bool b)8128 void MainWindow::on_cbFast9_clicked(bool b)
8129 {
8130   if(m_mode=="JT9") {
8131     m_bFast9=b;
8132 //    ui->cbAutoSeq->setVisible(b);
8133     on_actionJT9_triggered();
8134   }
8135 
8136   if(b) {
8137     m_TRperiod = ui->sbTR->value ();
8138   } else {
8139     m_TRperiod=60.0;
8140   }
8141   progressBar.setMaximum(int(m_TRperiod));
8142   m_wideGraph->setPeriod(m_TRperiod,m_nsps);
8143   fast_config(b);
8144   statusChanged ();
8145 }
8146 
8147 
on_cbShMsgs_toggled(bool b)8148 void MainWindow::on_cbShMsgs_toggled(bool b)
8149 {
8150   ui->cbTx6->setEnabled(b);
8151   m_bShMsgs=b;
8152   if(b) ui->cbSWL->setChecked(false);
8153   if(m_bShMsgs and (m_mode=="MSK144")) ui->rptSpinBox->setValue(1);
8154   int it0=itone[0];
8155   int ntx=m_ntx;
8156   m_lastCallsign.clear ();      // ensure Tx5 gets updated
8157   genStdMsgs(m_rpt);
8158   itone[0]=it0;
8159   if(ntx==1) ui->txrb1->setChecked(true);
8160   if(ntx==2) ui->txrb2->setChecked(true);
8161   if(ntx==3) ui->txrb3->setChecked(true);
8162   if(ntx==4) ui->txrb4->setChecked(true);
8163   if(ntx==5) ui->txrb5->setChecked(true);
8164   if(ntx==6) ui->txrb6->setChecked(true);
8165 }
8166 
on_cbSWL_toggled(bool b)8167 void MainWindow::on_cbSWL_toggled(bool b)
8168 {
8169   if(b) ui->cbShMsgs->setChecked(false);
8170 }
8171 
on_cbTx6_toggled(bool)8172 void MainWindow::on_cbTx6_toggled(bool)
8173 {
8174   genCQMsg ();
8175 }
8176 
8177 // Takes a decoded CQ line and sets it up for reply
replyToCQ(QTime time,qint32 snr,float delta_time,quint32 delta_frequency,QString const & mode,QString const & message_text,bool,quint8 modifiers)8178 void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 delta_frequency
8179                             , QString const& mode, QString const& message_text
8180                             , bool /*low_confidence*/, quint8 modifiers)
8181 {
8182   QString format_string {"%1 %2 %3 %4 %5 %6"};
8183   auto const& time_string = time.toString ("~" == mode || "&" == mode
8184                                            || "+" == mode ? "hhmmss" : "hhmm");
8185   auto text = message_text;
8186   auto ap_pos = text.lastIndexOf (QRegularExpression {R"((?:\?\s)?a[0-9]$)"});
8187   if (ap_pos >= 0)
8188     {
8189       // beware of decodes ending on shorter version of wanted call so
8190       // add a space
8191       text = text.left (ap_pos).trimmed () + ' ';
8192     }
8193   auto message_line = format_string
8194     .arg (time_string)
8195     .arg (snr, 3)
8196     .arg (delta_time, 4, 'f', 1)
8197     .arg (delta_frequency, 4)
8198     .arg (mode, -2)
8199     .arg (text);
8200   QTextCursor start {ui->decodedTextBrowser->document ()};
8201   start.movePosition (QTextCursor::End);
8202   auto cursor = ui->decodedTextBrowser->document ()->find (message_line, start, QTextDocument::FindBackward);
8203   if (cursor.isNull ())
8204     {
8205       // try again with with -0.0 delta time
8206       cursor = ui->decodedTextBrowser->document ()->find (format_string
8207                                                           .arg (time_string)
8208                                                           .arg (snr, 3)
8209                                                           .arg ('-' + QString::number (delta_time, 'f', 1), 4)
8210                                                           .arg (delta_frequency, 4)
8211                                                           .arg (mode, -2)
8212                                                           .arg (text), start, QTextDocument::FindBackward);
8213     }
8214   if (!cursor.isNull ())
8215     {
8216       if (m_config.udpWindowToFront ())
8217         {
8218           show ();
8219           raise ();
8220           activateWindow ();
8221         }
8222       if (m_config.udpWindowRestore () && isMinimized ())
8223         {
8224           showNormal ();
8225           raise ();
8226         }
8227         // Z
8228         m_bDoubleClicked = true;
8229 
8230       DecodedText message {message_line};
8231       Qt::KeyboardModifiers kbmod {modifiers << 24};
8232       processMessage (message, kbmod);
8233       tx_watchdog (false);
8234       QApplication::alert (this);
8235     }
8236   else
8237     {
8238       qDebug () << "process reply message ignored, decode not found:" << message_line;
8239     }
8240 }
8241 
locationChange(QString const & location)8242 void MainWindow::locationChange (QString const& location)
8243 {
8244   QString grid {location.trimmed ()};
8245   int len;
8246 
8247   // string 6 chars or fewer, interpret as a grid, or use with a 'GRID:' prefix
8248   if (grid.size () > 6) {
8249     if (grid.toUpper ().startsWith ("GRID:")) {
8250       grid = grid.mid (5).trimmed ();
8251     }
8252     else {
8253       // TODO - support any other formats, e.g. latlong? Or have that conversion done external to wsjtx
8254       return;
8255     }
8256   }
8257   if (MaidenheadLocatorValidator::Acceptable == MaidenheadLocatorValidator ().validate (grid, len)) {
8258     qDebug() << "locationChange: Grid supplied is " << grid;
8259     if (m_config.my_grid () != grid) {
8260       m_config.set_location (grid);
8261       genStdMsgs (m_rpt, false);
8262       statusUpdate ();
8263     }
8264   } else {
8265     qDebug() << "locationChange: Invalid grid " << grid;
8266   }
8267 }
8268 
replayDecodes()8269 void MainWindow::replayDecodes ()
8270 {
8271   // we accept this request even if the setting to accept UDP requests
8272   // is not checked
8273 
8274   // attempt to parse the decoded text
8275   for (QTextBlock block = ui->decodedTextBrowser->document ()->firstBlock (); block.isValid (); block = block.next ())
8276     {
8277       auto message = block.text ();
8278       message = message.left (message.indexOf (QChar::Nbsp)); // discard
8279                                                               // any
8280                                                               // appended info
8281       if (message.size() >= 4 && message.left (4) != "----")
8282         {
8283           auto const& parts = message.split (' ', SkipEmptyParts);
8284           if (parts.size () >= 5 && parts[3].contains ('.')) {
8285             postWSPRDecode (false, parts);
8286           } else {
8287             postDecode (false, message);
8288           }
8289       }
8290   }
8291   statusChanged ();
8292 }
8293 
postDecode(bool is_new,QString const & message)8294 void MainWindow::postDecode (bool is_new, QString const& message)
8295 {
8296   auto const& decode = message.trimmed ();
8297   auto const& parts = decode.left (22).split (' ', SkipEmptyParts);
8298   if (parts.size () >= 5)
8299     {
8300       auto has_seconds = parts[0].size () > 4;
8301       m_messageClient->decode (is_new
8302                                , QTime::fromString (parts[0], has_seconds ? "hhmmss" : "hhmm")
8303                                , parts[1].toInt ()
8304                                , parts[2].toFloat (), parts[3].toUInt (), parts[4]
8305                                , decode.mid (has_seconds ? 24 : 22)
8306                                , QChar {'?'} == decode.mid (has_seconds ? 24 + 21 : 22 + 21, 1)
8307                                , m_diskData);
8308     }
8309 }
8310 
postWSPRDecode(bool is_new,QStringList parts)8311 void MainWindow::postWSPRDecode (bool is_new, QStringList parts)
8312 {
8313   if (parts.size () < 8)
8314     {
8315       parts.insert (6, "");
8316     }
8317   m_messageClient->WSPR_decode (is_new, QTime::fromString (parts[0], "hhmm"), parts[1].toInt ()
8318                                 , parts[2].toFloat (), Radio::frequency (parts[3].toFloat (), 6)
8319                                 , parts[4].toInt (), parts[5], parts[6], parts[7].toInt ()
8320                                 , m_diskData);
8321 }
8322 
networkError(QString const & e)8323 void MainWindow::networkError (QString const& e)
8324 {
8325   if (m_splash && m_splash->isVisible ()) m_splash->hide ();
8326   if (MessageBox::Retry == MessageBox::warning_message (this, tr ("Network Error")
8327                                                         , tr ("Error: %1\nUDP server %2:%3")
8328                                                         .arg (e)
8329                                                         .arg (m_config.udp_server_name ())
8330                                                         .arg (m_config.udp_server_port ())
8331                                                         , QString {}
8332                                                         , MessageBox::Cancel | MessageBox::Retry
8333                                                         , MessageBox::Cancel))
8334     {
8335       // retry server lookup
8336       m_messageClient->set_server (m_config.udp_server_name (), m_config.udp_interface_names ());
8337     }
8338 }
8339 
on_syncSpinBox_valueChanged(int n)8340 void MainWindow::on_syncSpinBox_valueChanged(int n)
8341 {
8342   m_minSync=n;
8343 }
8344 
p1ReadFromStdout()8345 void MainWindow::p1ReadFromStdout()                        //p1readFromStdout
8346 {
8347   QString t1;
8348   while(p1.canReadLine()) {
8349     QString t(p1.readLine());
8350     if(ui->cbNoOwnCall->isChecked()) {
8351       if(t.contains(" " + m_config.my_callsign() + " ")) continue;
8352       if(t.contains(" <" + m_config.my_callsign() + "> ")) continue;
8353     }
8354     if(t.indexOf("<DecodeFinished>") >= 0) {
8355       m_bDecoded = m_nWSPRdecodes > 0;
8356       if(!m_diskData) {
8357         WSPR_history(m_dialFreqRxWSPR, m_nWSPRdecodes);
8358         if(m_nWSPRdecodes==0 and ui->band_hopping_group_box->isChecked()) {
8359           t = " " + tr ("Receiving") + " " + m_mode + " ----------------------- " +
8360               m_config.bands ()->find (m_dialFreqRxWSPR);
8361           t=beacon_start_time (-m_TRperiod / 2) + ' ' + t.rightJustified (66, '-');
8362           ui->decodedTextBrowser->appendText(t);
8363         }
8364         killFileTimer.start (45*1000); //Kill in 45s (for slow modes)
8365       }
8366       m_nWSPRdecodes=0;
8367       ui->DecodeButton->setChecked (false);
8368       if (m_uploadWSPRSpots
8369           && m_config.is_transceiver_online ()) { // need working rig control
8370 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
8371         uploadTimer.start(QRandomGenerator::global ()->bounded (0, 20000)); // Upload delay
8372 #else
8373         uploadTimer.start(20000 * qrand()/((double)RAND_MAX + 1.0)); // Upload delay
8374 #endif
8375       } else {
8376         QFile f {QDir::toNativeSeparators (m_config.writeable_data_dir ().absoluteFilePath ("wspr_spots.txt"))};
8377         if (f.exists ()) f.remove ();
8378       }
8379       m_RxLog=0;
8380       m_startAnother=m_loopall;
8381       m_decoderBusy = false;
8382       statusUpdate ();
8383     } else {
8384 
8385       int n=t.length();
8386       t=t.mid(0,n-2) + "                                                  ";
8387       t.remove(QRegExp("\\s+$"));
8388       QStringList rxFields = t.split(QRegExp("\\s+"));
8389       QString rxLine;
8390       QString grid="";
8391       if ( rxFields.count() == 8 ) {
8392           rxLine = QString("%1 %2 %3 %4 %5   %6  %7  %8")
8393                   .arg(rxFields.at(0), 4)
8394                   .arg(rxFields.at(1), 4)
8395                   .arg(rxFields.at(2), 5)
8396                   .arg(rxFields.at(3), 11)
8397                   .arg(rxFields.at(4), 4)
8398                   .arg(rxFields.at(5).leftJustified (12))
8399                   .arg(rxFields.at(6), -6)
8400                   .arg(rxFields.at(7), 3);
8401           postWSPRDecode (true, rxFields);
8402           grid = rxFields.at(6);
8403       } else if ( rxFields.count() == 7 ) { // Type 2 message
8404           rxLine = QString("%1 %2 %3 %4 %5   %6  %7  %8")
8405                   .arg(rxFields.at(0), 4)
8406                   .arg(rxFields.at(1), 4)
8407                   .arg(rxFields.at(2), 5)
8408                   .arg(rxFields.at(3), 11)
8409                   .arg(rxFields.at(4), 4)
8410                   .arg(rxFields.at(5).leftJustified (12))
8411                   .arg("", -6)
8412                   .arg(rxFields.at(6), 3);
8413           postWSPRDecode (true, rxFields);
8414       } else {
8415           rxLine = t;
8416       }
8417       if(grid!="") {
8418         double utch=0.0;
8419         int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter;
8420         azdist_(const_cast <char *> ((m_config.my_grid () + "      ").left (6).toLatin1 ().constData ()),
8421                 const_cast <char *> ((grid + "      ").left (6).toLatin1 ().constData ()),&utch,
8422                 &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6);
8423         QString t1;
8424         if(m_config.miles()) {
8425           t1 = t1.asprintf("%7d",nDmiles);
8426         } else {
8427           t1 = t1.asprintf("%7d",nDkm);
8428         }
8429         rxLine += t1;
8430       }
8431 
8432       if (rxLine.left (4) != m_tBlankLine) {
8433         ui->decodedTextBrowser->new_period ();
8434         if (m_config.insert_blank ()) {
8435           QString band;
8436           Frequency f=1000000.0*rxFields.at(3).toDouble()+0.5;
8437           band = ' ' + m_config.bands ()->find (f);
8438           ui->decodedTextBrowser->appendText(band.rightJustified (71, '-'));
8439         }
8440         m_tBlankLine = rxLine.left(4);
8441       }
8442       m_nWSPRdecodes += 1;
8443       ui->decodedTextBrowser->appendText(rxLine);
8444     }
8445   }
8446 }
8447 
beacon_start_time(int n)8448 QString MainWindow::beacon_start_time (int n)
8449 {
8450   auto bt = qt_truncate_date_time_to (QDateTime::currentDateTimeUtc ().addSecs (n), m_TRperiod * 1.e3);
8451   if (m_TRperiod < 60.)
8452     {
8453       return bt.toString ("HHmmss");
8454     }
8455   else
8456     {
8457       return bt.toString ("HHmm");
8458     }
8459 }
8460 
WSPR_history(Frequency dialFreq,int ndecodes)8461 void MainWindow::WSPR_history(Frequency dialFreq, int ndecodes)
8462 {
8463   QDateTime t=QDateTime::currentDateTimeUtc().addSecs(-60);
8464   QString t1=t.toString("yyMMdd");
8465   QString t2=beacon_start_time (-m_TRperiod / 2);
8466   QString t3;
8467   t3 = t3.asprintf("%13.6f",0.000001*dialFreq);
8468   if(ndecodes<0) {
8469     t1=t1 + " " + t2 + t3 + "  T";
8470   } else {
8471     QString t4;
8472     t4 = t4.asprintf("%4d",ndecodes);
8473     t1=t1 + " " + t2 + t3 + "  R" + t4;
8474   }
8475   QFile f {m_config.writeable_data_dir ().absoluteFilePath ("WSPR_history.txt")};
8476   if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) {
8477     QTextStream out(&f);
8478     out << t1
8479 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
8480         << Qt::endl
8481 #else
8482         << endl
8483 #endif
8484       ;
8485     f.close();
8486   } else {
8487     MessageBox::warning_message (this, tr ("File Error")
8488                                  , tr ("Cannot open \"%1\" for append: %2")
8489                                  .arg (f.fileName ()).arg (f.errorString ()));
8490   }
8491 }
8492 
uploadWSPRSpots(bool direct_post,QString const & decode_text)8493 void MainWindow::uploadWSPRSpots (bool direct_post, QString const& decode_text)
8494 {
8495   // do not spot if disabled, replays, or if rig control not working
8496   if(!m_uploadWSPRSpots || m_diskData || !m_config.is_transceiver_online ()) return;
8497   if(m_uploading && !decode_text.size ()) {
8498     qDebug() << "Previous upload has not completed, spots were lost";
8499     wsprNet->abortOutstandingRequests ();
8500     m_uploading = false;
8501   }
8502   QString rfreq = QString("%1").arg((m_dialFreqRxWSPR + m_wideGraph->rxFreq ()) / 1e6, 0, 'f', 6);
8503   QString tfreq = QString("%1").arg((m_dialFreqRxWSPR +
8504                         ui->TxFreqSpinBox->value()) / 1e6, 0, 'f', 6);
8505   auto pct = QString::number (ui->autoButton->isChecked () ? ui->sbTxPercent->value () : 0);
8506   if (direct_post)
8507     {
8508       // queues one FST4W spot
8509       wsprNet->post (m_config.my_callsign (), m_config.my_grid (), rfreq, tfreq,
8510                      m_mode, m_TRperiod, pct,
8511                      QString::number (m_dBm), version (), decode_text);
8512     }
8513   else
8514     {
8515       // queues spots for each decode in wspr_spots.txt
8516       wsprNet->upload (m_config.my_callsign (), m_config.my_grid (), rfreq, tfreq,
8517                        m_mode, m_TRperiod, pct,
8518                        QString::number (m_dBm), version (),
8519                        m_config.writeable_data_dir ().absoluteFilePath ("wspr_spots.txt"));
8520     }
8521   // trigger upload of any queued spots
8522   if (!decode_text.size ())
8523     {
8524       m_uploading = true;
8525     }
8526 }
8527 
uploadResponse(QString const & response)8528 void MainWindow::uploadResponse(QString const& response)
8529 {
8530   if (response == "done") {
8531     m_uploading=false;
8532   } else {
8533     if (response.startsWith ("Upload Failed")) {
8534       m_uploading=false;
8535     }
8536     qDebug () << "WSPRnet.org status:" << response;
8537   }
8538 }
8539 
on_TxPowerComboBox_currentIndexChanged(int index)8540 void MainWindow::on_TxPowerComboBox_currentIndexChanged(int index)
8541 {
8542   m_dBm = ui->TxPowerComboBox->itemData (index).toInt ();
8543 }
8544 
on_cbUploadWSPR_Spots_toggled(bool b)8545 void MainWindow::on_cbUploadWSPR_Spots_toggled(bool b)
8546 {
8547   m_uploadWSPRSpots=b;
8548 }
8549 
on_WSPRfreqSpinBox_valueChanged(int n)8550 void MainWindow::on_WSPRfreqSpinBox_valueChanged(int n)
8551 {
8552   ui->TxFreqSpinBox->setValue(n);
8553 }
8554 
on_sbFST4W_RxFreq_valueChanged(int n)8555 void MainWindow::on_sbFST4W_RxFreq_valueChanged(int n)
8556 {
8557   m_wideGraph->setRxFreq(n);
8558   statusUpdate ();
8559 }
8560 
on_sbFST4W_FTol_valueChanged(int n)8561 void MainWindow::on_sbFST4W_FTol_valueChanged(int n)
8562 {
8563   ui->sbFST4W_RxFreq->setSingleStep(n);
8564   m_wideGraph->setTol(n);
8565   statusUpdate ();
8566 }
8567 
on_pbTxNext_clicked(bool b)8568 void MainWindow::on_pbTxNext_clicked(bool b)
8569 {
8570   if (b && !ui->autoButton->isChecked ())
8571     {
8572       ui->autoButton->click (); // make sure Tx is possible
8573     }
8574 }
8575 
WSPR_scheduling()8576 void MainWindow::WSPR_scheduling ()
8577 {
8578   if (ui->pbTxNext->isEnabled () && ui->pbTxNext->isChecked ())
8579     {
8580       // Tx Next button overrides all scheduling
8581       m_WSPR_tx_next = true;
8582       return;
8583     }
8584   QString t=ui->RoundRobin->currentText();
8585   if(m_mode=="FST4W" and t != tr ("Random")) {
8586     bool ok;
8587     int i=t.left (1).toInt (&ok) - 1;
8588     if (!ok) return;
8589     int n=t.right (1).toInt (&ok);
8590     if (!ok || 0 == n) return;
8591 
8592     qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000;
8593     int nsec=ms/1000;
8594     int ntr=m_TRperiod;
8595     int j=((nsec+ntr-1) % (n*ntr))/ntr;
8596     m_WSPR_tx_next = i == j;
8597     return;
8598   }
8599   m_WSPR_tx_next = false;
8600   if (!ui->sbTxPercent->isEnabled ())
8601     {
8602       return;                   // don't schedule if %age disabled
8603     }
8604   if (m_config.is_transceiver_online () // need working rig control for hopping
8605       && !m_config.is_dummy_rig ()
8606       && ui->band_hopping_group_box->isChecked ()) {
8607     auto hop_data = m_WSPR_band_hopping.next_hop (m_auto);
8608     qDebug () << "hop data: period:" << hop_data.period_name_
8609               << "frequencies index:" << hop_data.frequencies_index_
8610               << "tune:" << hop_data.tune_required_
8611               << "tx:" << hop_data.tx_next_;
8612     m_WSPR_tx_next = hop_data.tx_next_;
8613     if (hop_data.frequencies_index_ >= 0) { // new band
8614       ui->bandComboBox->setCurrentIndex (hop_data.frequencies_index_);
8615       on_bandComboBox_activated (hop_data.frequencies_index_);
8616       // Execute user's hardware controller
8617       auto band = m_config.bands ()->find (m_freqNominal).remove ('m');
8618 #if defined(Q_OS_WIN)
8619       // On  windows   we  use  CMD.EXE   to  find  and   execute  the
8620       // user_hardware executable. This means  that the first matching
8621       // file extension  on the PATHEXT environment  variable found on
8622       // the PATH  environment variable  path list. This  give maximum
8623       // flexibility  for  users  to   write  user_hardware  in  their
8624       // language of choice,  and place the file anywhere  on the PATH
8625       // environment  variable.  Equivalent  to  typing  user_hardware
8626       // without any path or extension at the CMD.EXE prompt.
8627       p3.start("CMD", QStringList {"/C", "user_hardware", band});
8628 #else
8629       // On non-Windows systems we expect the user_hardware executable
8630       // to be anywhere in the paths specified in the PATH environment
8631       // variable  path list,  and  executable.  Equivalent to  typing
8632       // user_hardware without any path at the shell prompt.
8633       p3.start("/bin/sh", QStringList {"-c", "user_hardware " + band});
8634 #endif
8635 
8636       // Produce a short tuneup signal
8637       m_tuneup = false;
8638       if (hop_data.tune_required_) {
8639         m_tuneup = true;
8640         on_tuneButton_clicked (true);
8641         tuneATU_Timer.start (2500);
8642       }
8643     }
8644 
8645     // Display grayline status
8646     band_hopping_label.setText (hop_data.period_name_);
8647   }
8648   else {
8649     m_WSPR_tx_next = m_WSPR_band_hopping.next_is_tx(m_mode=="FST4W");
8650   }
8651 }
8652 
astroUpdate()8653 void MainWindow::astroUpdate ()
8654 {
8655   if (m_astroWidget)
8656     {
8657       // no Doppler correction while CTRL pressed allows manual tuning
8658       if (Qt::ControlModifier & QApplication::queryKeyboardModifiers ()) return;
8659 
8660       auto correction = m_astroWidget->astroUpdate(QDateTime::currentDateTimeUtc (),
8661                                                    m_config.my_grid(), m_hisGrid,
8662                                                    m_freqNominal,
8663                                                    "Echo" == m_mode, m_transmitting,
8664                                                    !m_config.tx_QSY_allowed (), m_TRperiod);
8665       // no Doppler correction in Tx if rig can't do it
8666       if (m_transmitting && !m_config.tx_QSY_allowed ()) return;
8667       if (!m_astroWidget->doppler_tracking ()) return;
8668       if ((m_monitoring || m_transmitting)
8669           // no Doppler correction below 6m
8670           && m_freqNominal >= 50000000
8671           && m_config.split_mode ())
8672         {
8673           // adjust for rig resolution
8674           if (m_config.transceiver_resolution () > 2)
8675             {
8676               correction.rx = (correction.rx + 50) / 100 * 100;
8677               correction.tx = (correction.tx + 50) / 100 * 100;
8678             }
8679           else if (m_config.transceiver_resolution () > 1)
8680             {
8681               correction.rx = (correction.rx + 10) / 20 * 20;
8682               correction.tx = (correction.tx + 10) / 20 * 20;
8683             }
8684           else if (m_config.transceiver_resolution () > 0)
8685             {
8686               correction.rx = (correction.rx + 5) / 10 * 10;
8687               correction.tx = (correction.tx + 5) / 10 * 10;
8688             }
8689           else if (m_config.transceiver_resolution () < -2)
8690             {
8691               correction.rx = correction.rx / 100 * 100;
8692               correction.tx = correction.tx / 100 * 100;
8693             }
8694           else if (m_config.transceiver_resolution () < -1)
8695             {
8696               correction.rx = correction.rx / 20 * 20;
8697               correction.tx = correction.tx / 20 * 20;
8698             }
8699           else if (m_config.transceiver_resolution () < 0)
8700             {
8701               correction.rx = correction.rx / 10 * 10;
8702               correction.tx = correction.tx / 10 * 10;
8703             }
8704           m_astroCorrection = correction;
8705           if (m_reverse_Doppler)
8706             {
8707               m_astroCorrection.reverse ();
8708             }
8709         }
8710       else
8711         {
8712           m_astroCorrection = {};
8713         }
8714 
8715       setRig ();
8716     }
8717 }
8718 
setRig(Frequency f)8719 void MainWindow::setRig (Frequency f)
8720 {
8721   if (f)
8722     {
8723       m_freqNominal = f;
8724       genCQMsg ();
8725       m_freqTxNominal = m_freqNominal;
8726       if (m_astroWidget) m_astroWidget->nominal_frequency (m_freqNominal, m_freqTxNominal);
8727     }
8728   if (m_mode == "FreqCal"
8729       && m_frequency_list_fcal_iter != m_config.frequencies ()->end ()) {
8730     m_freqNominal = m_frequency_list_fcal_iter->frequency_ - ui->RxFreqSpinBox->value ();
8731   }
8732   if(m_transmitting && !m_config.tx_QSY_allowed ()) return;
8733   if ((m_monitoring || m_transmitting) && m_config.transceiver_online ())
8734     {
8735       if (m_transmitting && m_config.split_mode ())
8736         {
8737           m_config.transceiver_tx_frequency (m_freqTxNominal + m_astroCorrection.tx);
8738         }
8739       else
8740         {
8741           m_config.transceiver_frequency (m_freqNominal + m_astroCorrection.rx);
8742         }
8743     }
8744 }
8745 
fastPick(int x0,int x1,int y)8746 void MainWindow::fastPick(int x0, int x1, int y)
8747 {
8748   float pixPerSecond=12000.0/512.0;
8749   if(m_TRperiod<30.0) pixPerSecond=12000.0/256.0;
8750   if(m_mode!="MSK144") return;
8751   if(!m_decoderBusy) {
8752     dec_data.params.newdat=0;
8753     dec_data.params.nagain=1;
8754     m_nPick=1;
8755     if(y > 120) m_nPick=2;
8756     m_t0Pick=x0/pixPerSecond;
8757     m_t1Pick=x1/pixPerSecond;
8758     m_dataAvailable=true;
8759     decode();
8760   }
8761 }
8762 
on_actionMeasure_reference_spectrum_triggered()8763 void MainWindow::on_actionMeasure_reference_spectrum_triggered()
8764 {
8765   if(!m_monitoring) on_monitorButton_clicked (true);
8766   m_bRefSpec=true;
8767 }
8768 
on_actionMeasure_phase_response_triggered()8769 void MainWindow::on_actionMeasure_phase_response_triggered()
8770 {
8771   if(m_bTrain) {
8772     m_bTrain=false;
8773     MessageBox::information_message (this, tr ("Phase Training Disabled"));
8774   } else {
8775     m_bTrain=true;
8776     MessageBox::information_message (this, tr ("Phase Training Enabled"));
8777   }
8778 }
8779 
on_actionErase_reference_spectrum_triggered()8780 void MainWindow::on_actionErase_reference_spectrum_triggered()
8781 {
8782   m_bClearRefSpec=true;
8783 }
8784 
freqCalStep()8785 void MainWindow::freqCalStep()
8786 {
8787   if (m_frequency_list_fcal_iter == m_config.frequencies ()->end ()
8788       || ++m_frequency_list_fcal_iter == m_config.frequencies ()->end ()) {
8789     m_frequency_list_fcal_iter = m_config.frequencies ()->begin ();
8790   }
8791 
8792   // allow for empty list
8793   if (m_frequency_list_fcal_iter != m_config.frequencies ()->end ()) {
8794     setRig (m_frequency_list_fcal_iter->frequency_ - ui->RxFreqSpinBox->value ());
8795   }
8796 }
8797 
on_sbCQTxFreq_valueChanged(int)8798 void MainWindow::on_sbCQTxFreq_valueChanged(int)
8799 {
8800   setXIT (ui->TxFreqSpinBox->value ());
8801 }
8802 
on_cbCQTx_toggled(bool b)8803 void MainWindow::on_cbCQTx_toggled(bool b)
8804 {
8805   ui->sbCQTxFreq->setEnabled(b);
8806   genCQMsg();
8807   if(b) {
8808     ui->txrb6->setChecked(true);
8809     m_ntx=6;
8810     m_QSOProgress = CALLING;
8811   }
8812   setRig ();
8813   setXIT (ui->TxFreqSpinBox->value ());
8814 }
8815 
statusUpdate() const8816 void MainWindow::statusUpdate () const
8817 {
8818   if (!ui || m_block_udp_status_updates) return;
8819   auto submode = current_submode ();
8820   auto ftol = ui->sbFtol->value ();
8821   if ("FST4W" == m_mode)
8822     {
8823       ftol = ui->sbFST4W_FTol->value ();
8824     }
8825   else if (!(ui->sbFtol->isVisible () && ui->sbFtol->isEnabled ()))
8826     {
8827       ftol = quint32_max;
8828     }
8829   auto tr_period = ui->sbTR->value ();
8830   auto rx_frequency = ui->RxFreqSpinBox->value ();
8831   if ("FST4W" == m_mode)
8832     {
8833       tr_period = ui->sbTR_FST4W->value ();
8834       rx_frequency = ui->sbFST4W_RxFreq->value ();
8835     }
8836   else if (!(ui->sbTR->isVisible () && ui->sbTR->isEnabled ()))
8837     {
8838       tr_period = quint32_max;
8839     }
8840   m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
8841                                   QString::number (ui->rptSpinBox->value ()),
8842                                   m_mode, ui->autoButton->isChecked (),
8843                                   m_transmitting, m_decoderBusy,
8844                                   rx_frequency, ui->TxFreqSpinBox->value (),
8845                                   m_config.my_callsign (), m_config.my_grid (),
8846                                   m_hisGrid, m_tx_watchdog,
8847                                   submode != QChar::Null ? QString {submode} : QString {}, m_bFastMode,
8848                                   static_cast<quint8> (m_config.special_op_id ()),
8849                                   ftol, tr_period, m_multi_settings->configuration_name (),
8850                                   m_currentMessage);
8851 }
8852 
childEvent(QChildEvent * e)8853 void MainWindow::childEvent (QChildEvent * e)
8854 {
8855   if (e->child ()->isWidgetType ())
8856     {
8857       switch (e->type ())
8858         {
8859         case QEvent::ChildAdded: add_child_to_event_filter (e->child ()); break;
8860         case QEvent::ChildRemoved: remove_child_from_event_filter (e->child ()); break;
8861         default: break;
8862         }
8863     }
8864   QMainWindow::childEvent (e);
8865 }
8866 
8867 // add widget and any child widgets to our event filter so that we can
8868 // take action on key press ad mouse press events anywhere in the main window
add_child_to_event_filter(QObject * target)8869 void MainWindow::add_child_to_event_filter (QObject * target)
8870 {
8871   if (target && target->isWidgetType ())
8872     {
8873       target->installEventFilter (this);
8874     }
8875   auto const& children = target->children ();
8876   for (auto iter = children.begin (); iter != children.end (); ++iter)
8877     {
8878       add_child_to_event_filter (*iter);
8879     }
8880 }
8881 
8882 // recursively remove widget and any child widgets from our event filter
remove_child_from_event_filter(QObject * target)8883 void MainWindow::remove_child_from_event_filter (QObject * target)
8884 {
8885   auto const& children = target->children ();
8886   for (auto iter = children.begin (); iter != children.end (); ++iter)
8887     {
8888       remove_child_from_event_filter (*iter);
8889     }
8890   if (target && target->isWidgetType ())
8891     {
8892       target->removeEventFilter (this);
8893     }
8894 }
8895 
tx_watchdog(bool triggered)8896 void MainWindow::tx_watchdog (bool triggered)
8897 {
8898   auto prior = m_tx_watchdog;
8899   m_tx_watchdog = triggered;
8900   if (triggered)
8901     {
8902       log("TXWatchdog: TRUE");
8903       m_bTxTime=false;
8904       // Z
8905 
8906       if (ui->cbAutoCall->isChecked() && ui->cb_IgnoreAfterWD->isChecked())
8907           ui->pte_IgnoredStations->appendPlainText(m_hisCall);
8908 
8909       if (ui->cbAutoCQ->isChecked()) {
8910                   ui->txrb6->setChecked (true);
8911                   m_idleMinutes = 0;
8912                   update_watchdog_label ();
8913                 } else {
8914                   if (m_auto) auto_tx_mode (false);
8915                   if (m_tune) stop_tuning ();
8916                   tx_status_label.setStyleSheet ("QLabel{background-color: #ff0000}");
8917                   tx_status_label.setText (tr ("Runaway Tx watchdog"));
8918                   if (m_config.rxTotxFreq()) on_pbT2R_clicked();
8919                   QApplication::alert (this);
8920                 }
8921 
8922       clearDX();
8923 
8924     }
8925   else
8926     {
8927       log("TXWatchdog: FALSE");
8928       m_idleMinutes = 0;
8929       update_watchdog_label ();
8930     }
8931   if (prior != triggered) statusUpdate ();
8932 }
8933 
update_watchdog_label()8934 void MainWindow::update_watchdog_label ()
8935 {
8936   if (watchdog () && m_mode!="WSPR" && m_mode!="FST4W")
8937     {
8938       watchdog_label.setText (tr ("WD:%1m").arg (watchdog () - m_idleMinutes));
8939       watchdog_label.setVisible (true);
8940     }
8941   else
8942     {
8943       watchdog_label.setText (QString {});
8944       watchdog_label.setVisible (false);
8945     }
8946 }
8947 
on_cbMenus_toggled(bool b)8948 void MainWindow::on_cbMenus_toggled(bool b)
8949 {
8950   select_geometry (!b ? 2 : ui->actionSWL_Mode->isChecked () ? 1 : 0);
8951 }
8952 
on_cbCQonly_toggled(bool)8953 void MainWindow::on_cbCQonly_toggled(bool)
8954 {  //Fix this -- no decode here?
8955   to_jt9(m_ihsym,1,-1);                //Send m_ihsym to jt9[.exe] and start decoding
8956   decodeBusy(true);
8957 }
8958 
on_cbFirst_toggled(bool b)8959 void MainWindow::on_cbFirst_toggled(bool b)
8960 {
8961   if (b) {
8962     if (m_auto && CALLING == m_QSOProgress) {
8963       ui->cbFirst->setStyleSheet ("QCheckBox{color:red}");
8964     }
8965   } else {
8966     ui->cbFirst->setStyleSheet ("");
8967   }
8968 }
8969 
on_cbAutoSeq_toggled(bool b)8970 void MainWindow::on_cbAutoSeq_toggled(bool b)
8971 {
8972   if(!b) ui->cbFirst->setChecked(false);
8973   ui->cbFirst->setVisible((m_mode=="FT8" or m_mode=="FT4" or m_mode=="FST4"
8974                            or m_mode=="Q65") and b);
8975 }
8976 
on_measure_check_box_stateChanged(int state)8977 void MainWindow::on_measure_check_box_stateChanged (int state)
8978 {
8979   m_config.enable_calibration (Qt::Checked != state);
8980 }
8981 
write_transmit_entry(QString const & file_name)8982 void MainWindow::write_transmit_entry (QString const& file_name)
8983 {
8984   QFile f {m_config.writeable_data_dir ().absoluteFilePath (file_name)};
8985   if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append))
8986     {
8987       QTextStream out(&f);
8988       auto time = QDateTime::currentDateTimeUtc ();
8989       time = time.addSecs (-fmod(double(time.time().second()),m_TRperiod));
8990       out << time.toString("yyMMdd_hhmmss")
8991           << "  Transmitting " << qSetRealNumberPrecision (12) << (m_freqNominal / 1.e6)
8992           << " MHz  " << m_mode
8993           << ":  " << m_currentMessage
8994 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
8995           << Qt::endl
8996 #else
8997           << endl
8998 #endif
8999         ;
9000       f.close();
9001     }
9002   else
9003     {
9004       auto const& message = tr ("Cannot open \"%1\" for append: %2")
9005         .arg (f.fileName ()).arg (f.errorString ());
9006       QTimer::singleShot (0, [=] {                   // don't block guiUpdate
9007           MessageBox::warning_message (this, tr ("Log File Error"), message);
9008         });
9009     }
9010 }
9011 
9012 // -------------------------- Code for FT8 DXpedition Mode ---------------------------
9013 
hound_reply()9014 void MainWindow::hound_reply ()
9015 {
9016   if (!m_tune) {
9017     //Select TX3, set TxFreq to FoxFreq, and Force Auto ON.
9018     ui->txrb3->setChecked (true);
9019     m_nSentFoxRrpt = 1;
9020     ui->rptSpinBox->setValue(m_rptSent.toInt());
9021     if (!m_auto) auto_tx_mode(true);
9022     ui->TxFreqSpinBox->setValue (m_nFoxFreq);
9023   }
9024 }
9025 
on_sbNlist_valueChanged(int n)9026 void MainWindow::on_sbNlist_valueChanged(int n)
9027 {
9028   m_Nlist=n;
9029 }
9030 
on_sbNslots_valueChanged(int n)9031 void MainWindow::on_sbNslots_valueChanged(int n)
9032 {
9033   m_Nslots=n;
9034   if(m_config.special_op_id()!=SpecOp::FOX) return;
9035   QString t;
9036   t = t.asprintf(" NSlots %d",m_Nslots);
9037   writeFoxQSO(t);
9038 }
9039 
on_sbMax_dB_valueChanged(int n)9040 void MainWindow::on_sbMax_dB_valueChanged(int n)
9041 {
9042   m_max_dB=n;
9043   if(m_config.special_op_id()!=SpecOp::FOX) return;
9044   QString t;
9045   t = t.asprintf(" Max_dB %d",m_max_dB);
9046   writeFoxQSO(t);
9047 }
9048 
on_pbFoxReset_clicked()9049 void MainWindow::on_pbFoxReset_clicked()
9050 {
9051   if(m_config.special_op_id()!=SpecOp::FOX) return;
9052   auto button = MessageBox::query_message (this, tr ("Confirm Reset"),
9053       tr ("Are you sure you want to clear the QSO queues?"));
9054   if(button == MessageBox::Yes) {
9055     QFile f(m_config.temp_dir().absoluteFilePath("houndcallers.txt"));
9056     f.remove();
9057     ui->decodedTextBrowser->setText("");
9058     ui->textBrowser4->setText("");
9059     m_houndQueue.clear();
9060     m_foxQSO.clear();
9061     m_foxQSOinProgress.clear();
9062     writeFoxQSO(" Reset");
9063   }
9064 }
9065 
on_comboBoxHoundSort_activated(int index)9066 void MainWindow::on_comboBoxHoundSort_activated(int index)
9067 {
9068   if(index!=-99) houndCallers();            //Silence compiler warning
9069 }
9070 
9071 //------------------------------------------------------------------------------
sortHoundCalls(QString t,int isort,int max_dB)9072 QString MainWindow::sortHoundCalls(QString t, int isort, int max_dB)
9073 {
9074 /* Called from "houndCallers()" to sort the list of calling stations by
9075  * specified criteria.
9076  *
9077  * QString "t" contains a list of Hound callers read from file "houndcallers.txt".
9078  *    isort=0: random    (shuffled order)
9079  *          1: Call
9080  *          2: Grid
9081  *          3: SNR       (reverse order)
9082  *          4: Distance  (reverse order)
9083 */
9084 
9085   QMap<QString,QString> map;
9086   QStringList lines,lines2;
9087   QString msg,houndCall,t1;
9088   QString ABC{"ABCDEFGHIJKLMNOPQRSTUVWXYZ _"};
9089   QList<int> list;
9090   int i,j,k,n,nlines;
9091   bool bReverse=(isort >= 3);
9092 
9093   isort=qAbs(isort);
9094 // Save only the most recent transmission from each caller.
9095   lines = t.split("\n");
9096   nlines=lines.length()-1;
9097   for(i=0; i<nlines; i++) {
9098     msg=lines.at(i);                        //key = callsign
9099     if(msg.mid(13,1)==" ") msg=msg.mid(0,13) + "...." + msg.mid(17);
9100     houndCall=msg.split(" ").at(0);         //value = "call grid snr freq dist age"
9101     map[houndCall]=msg;
9102   }
9103 
9104   j=0;
9105   t="";
9106   for(auto a: map.keys()) {
9107     t1=map[a].split(" ",SkipEmptyParts).at(2);
9108     int nsnr=t1.toInt();                         // get snr
9109     if(nsnr <= max_dB) {                         // keep only if snr in specified range
9110       if(isort==1) t += map[a] + "\n";
9111       if(isort==3 or isort==4) {
9112         i=2;                                           // sort Hound calls by snr
9113         if(isort==4) i=4;                              // sort Hound calls by distance
9114         t1=map[a].split(" ",SkipEmptyParts).at(i);
9115         n=1000*(t1.toInt()+100) + j;                   // pack (snr or dist) and index j into n
9116         list.insert(j,n);                              // add n to list at [j]
9117       }
9118 
9119       if(isort==2) {                                   // sort Hound calls by grid
9120         t1=map[a].split(" ",SkipEmptyParts).at(1);
9121         if(t1=="....") t1="ZZ99";
9122         int i1=ABC.indexOf(t1.mid(0,1));
9123         int i2=ABC.indexOf(t1.mid(1,1));
9124         n=100*(26*i1+i2)+t1.mid(2,2).toInt();
9125         n=1000*n + j;                                 // pack ngrid and index j into n
9126         list.insert(j,n);                             // add n to list at [j]
9127       }
9128 
9129       lines2.insert(j,map[a]);                        // add map[a] to lines2 at [j]
9130       j++;
9131     }
9132   }
9133 
9134   if(isort>1) {
9135     if(bReverse) {
9136       std::sort (list.begin (), list.end (), std::greater<int> ());
9137     } else {
9138       std::sort (list.begin (), list.end ());
9139     }
9140   }
9141 
9142   if(isort>1) {
9143     for(i=0; i<j; i++) {
9144       k=list[i]%1000;
9145       n=list[i]/1000 - 100;
9146       t += lines2.at(k) + "\n";
9147     }
9148   }
9149 
9150   int nn=lines2.length();
9151   if(isort==0) {                                      // shuffle Hound calls to random order
9152     int a[nn];
9153     for(i=0; i<nn; i++) {
9154       a[i]=i;
9155     }
9156     for(i=nn-1; i>-1; i--) {
9157 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
9158       j = (i + 1) * QRandomGenerator::global ()->generateDouble ();
9159 #else
9160       j=(i+1)*double(qrand())/RAND_MAX;
9161 #endif
9162       std::swap (a[j], a[i]);
9163       t += lines2.at(a[i]) + "\n";
9164     }
9165   }
9166 
9167   int i0=t.indexOf("\n") + 1;
9168   m_nSortedHounds=0;
9169   if(i0 > 0) {
9170     m_nSortedHounds=qMin(t.length(),m_Nlist*i0)/i0; // Number of sorted & displayed Hounds
9171   }
9172   m_houndCallers=t.mid(0,m_Nlist*i0);
9173 
9174   return m_houndCallers;
9175 }
9176 
9177 //------------------------------------------------------------------------------
selectHound(QString line)9178 void MainWindow::selectHound(QString line)
9179 {
9180 /* Called from doubleClickOnCall() in DXpedition Fox mode.
9181  * QString "line" is a user-selected line from left text window.
9182  * The line may be selected by double-clicking; alternatively, hitting
9183  * <Enter> is equivalent to double-clicking on the top-most line.
9184 */
9185 
9186   if(line.length()==0) return;
9187   QString houndCall=line.split(" ",SkipEmptyParts).at(0);
9188 
9189 // Don't add a call already enqueued or in QSO
9190   if(ui->textBrowser4->toPlainText().indexOf(houndCall) >= 0) return;
9191 
9192   QString houndGrid=line.split(" ",SkipEmptyParts).at(1);  // Hound caller's grid
9193   QString rpt=line.split(" ",SkipEmptyParts).at(2);        // Hound SNR
9194 
9195   m_houndCallers=m_houndCallers.remove(line+"\n");      // Remove t from sorted Hound list
9196   m_nSortedHounds--;
9197   ui->decodedTextBrowser->setText(m_houndCallers);   // Populate left window with Hound callers
9198   QString t1=houndCall + "          ";
9199   QString t2=rpt;
9200   if(rpt.mid(0,1) != "-" and rpt.mid(0,1) != "+") t2="+" + rpt;
9201   if(t2.length()==2) t2=t2.mid(0,1) + "0" + t2.mid(1,1);
9202   t1=t1.mid(0,12) + t2;
9203   ui->textBrowser4->displayFoxToBeCalled(t1); // Add hound call and rpt to tb4
9204   t1=t1 + " " + houndGrid;                    // Append the grid
9205   m_houndQueue.enqueue(t1);     // Put this hound into the queue
9206   writeFoxQSO(" Sel:  " + t1);
9207   QTextCursor cursor = ui->textBrowser4->textCursor();
9208   cursor.setPosition(0);                                 // Scroll to top of list
9209   ui->textBrowser4->setTextCursor(cursor);
9210 }
9211 
9212 //------------------------------------------------------------------------------
houndCallers()9213 void MainWindow::houndCallers()
9214 {
9215 /* Called from decodeDone(), in DXpedition Fox mode.  Reads decodes from file
9216  * "houndcallers.txt", ignoring any that are not addressed to MyCall, are already
9217  * in the stack, or with whom a QSO has been started.  Others are considered to
9218  * be Hounds eager for a QSO.  We add caller information (Call, Grid, SNR, Freq,
9219  * Distance, Age, and Continent) to a list, sort the list by specified criteria,
9220  * and display the top N_Hounds entries in the left text window.
9221 */
9222   QFile f(m_config.temp_dir().absoluteFilePath("houndcallers.txt"));
9223   if(f.open(QIODevice::ReadOnly | QIODevice::Text)) {
9224     QTextStream s(&f);
9225     QString t="";
9226     QString line,houndCall,paddedHoundCall;
9227     m_nHoundsCalling=0;
9228     int nTotal=0;  //Total number of decoded Hounds calling Fox in 4 most recent Rx sequences
9229 
9230 // Read and process the file of Hound callers.
9231     while(!s.atEnd()) {
9232       line=s.readLine();
9233       nTotal++;
9234       int i0=line.indexOf(" ");
9235       houndCall=line.mid(0,i0);
9236       paddedHoundCall=houndCall + " ";
9237       //Don't list a hound already in the queue
9238       if(!ui->textBrowser4->toPlainText().contains(paddedHoundCall)) {
9239         if(m_loggedByFox[houndCall].contains(m_lastBand))   continue;   //already logged on this band
9240         if(m_foxQSO.contains(houndCall)) continue;   //still in the QSO map
9241         auto const& entity = m_logBook.countries ()->lookup (houndCall);
9242         auto const& continent = AD1CCty::continent (entity.continent);
9243 
9244 //If we are using a directed CQ, ignore Hound calls that do not comply.
9245         QString CQtext=ui->comboBoxCQ->currentText();
9246         if(CQtext.length()==5 and (continent!=CQtext.mid(3,2))) continue;
9247         int nCallArea=-1;
9248         if(CQtext.length()==4) {
9249           for(int i=houndCall.length()-1; i>0; i--) {
9250             if(houndCall.mid(i,1).toInt() > 0) nCallArea=houndCall.mid(i,1).toInt();
9251             if(houndCall.mid(i,1)=="0") nCallArea=0;
9252             if(nCallArea>=0) break;
9253           }
9254           if(nCallArea!=CQtext.mid(3,1).toInt()) continue;
9255         }
9256 //This houndCall passes all tests, add it to the list.
9257         t = t + line + "  " + continent + "\n";
9258         /*/ Z FOX
9259         if (ui->cb_tryme->isChecked()) {
9260             log("Adding to queue.");
9261             selectHound(line);
9262             log("Done.");
9263         }*/
9264         m_nHoundsCalling++;                // Number of accepted Hounds to be sorted
9265       }
9266     }
9267     if(m_foxLogWindow) m_foxLogWindow->callers (nTotal);
9268 
9269 // Sort and display accumulated list of Hound callers
9270     if(t.length()>30) {
9271       m_isort=ui->comboBoxHoundSort->currentIndex();
9272       QString t1=sortHoundCalls(t,m_isort,m_max_dB);
9273       ui->decodedTextBrowser->setText(t1);
9274     }
9275     f.close();
9276   }
9277 }
9278 
foxRxSequencer(QString msg,QString houndCall,QString rptRcvd)9279 void MainWindow::foxRxSequencer(QString msg, QString houndCall, QString rptRcvd)
9280 {
9281 /* Called from "readFromStdOut()" to process decoded messages of the form
9282  * "myCall houndCall R+rpt".
9283  *
9284  * If houndCall matches a callsign in one of our active QSO slots, we
9285  * prepare to send "houndCall RR73" to that caller.
9286 */
9287   if(m_foxQSO.contains(houndCall)) {
9288     m_foxQSO[houndCall].rcvd=rptRcvd.mid(1);  //Save report Rcvd, for the log
9289     m_foxQSO[houndCall].tFoxRrpt=m_tFoxTx;    //Save time R+rpt was received
9290     writeFoxQSO(" Rx:   " + msg.trimmed());
9291   } else {
9292     for(QString hc: m_foxQSO.keys()) {        //Check for a matching compound call
9293       if(hc.contains("/"+houndCall) or hc.contains(houndCall+"/")) {
9294         m_foxQSO[hc].rcvd=rptRcvd.mid(1);  //Save report Rcvd, for the log
9295         m_foxQSO[hc].tFoxRrpt=m_tFoxTx;    //Save time R+rpt was received
9296         writeFoxQSO(" Rx:   " + msg.trimmed());
9297       }
9298     }
9299   }
9300 }
9301 
foxTxSequencer()9302 void MainWindow::foxTxSequencer()
9303 {
9304 /* Called from guiUpdate at the point where an FT8 Fox-mode transmission
9305  * is to be started.
9306  *
9307  * Determine what the Tx message(s) will be for each active slot, call
9308  * foxgen() to generate and accumulate the corresponding waveform.
9309 */
9310 
9311   qint64 now=QDateTime::currentMSecsSinceEpoch()/1000;
9312   QStringList list1;                        //Up to NSlots Hound calls to be sent RR73
9313   QStringList list2;                        //Up to NSlots Hound calls to be sent a report
9314   QString fm;                               //Fox message to be transmitted
9315   QString hc,hc1,hc2;                       //Hound calls
9316   QString t,rpt;
9317   qint32  islot=0;
9318   qint32  n1,n2,n3;
9319 
9320   m_tFoxTx++;                               //Increment Fox Tx cycle counter
9321 
9322   //Is it time for a stand-alone CQ?
9323   if(m_tFoxTxSinceCQ >= m_foxCQtime and ui->cbMoreCQs->isChecked()) {
9324     fm=ui->comboBoxCQ->currentText() + " " + m_config.my_callsign();
9325     if(!fm.contains("/")) {
9326       //If Fox is not a compound callsign, add grid to the CQ message.
9327       fm += " " + m_config.my_grid().mid(0,4);
9328       m_fullFoxCallTime=now;
9329     }
9330     m_tFoxTx0=m_tFoxTx;                     //Remember when we sent a CQ
9331     islot++;
9332     foxGenWaveform(islot-1,fm);
9333     goto Transmit;
9334   }
9335 //Compile list1: up to NSLots Hound calls to be sent RR73
9336   for(QString hc: m_foxQSO.keys()) {           //Check all Hound calls: First priority
9337     if(m_foxQSO[hc].tFoxRrpt<0) continue;
9338     if(m_foxQSO[hc].tFoxRrpt - m_foxQSO[hc].tFoxTxRR73 > 3) {
9339       //Has been a long time since we sent RR73
9340       list1 << hc;                          //Add to list1
9341       m_foxQSO[hc].tFoxTxRR73 = m_tFoxTx;   //Time RR73 is sent
9342       m_foxQSO[hc].nRR73++;                 //Increment RR73 counter
9343       if(list1.size()==m_Nslots) goto list1Done;
9344     }
9345   }
9346 
9347   for(QString hc: m_foxQSO.keys()) {           //Check all Hound calls: Second priority
9348     if(m_foxQSO[hc].tFoxRrpt<0) continue;
9349     if(m_foxQSO[hc].tFoxTxRR73 < 0) {
9350       //Have not yet sent RR73
9351       list1 << hc;                          //Add to list1
9352       m_foxQSO[hc].tFoxTxRR73 = m_tFoxTx;   //Time RR73 is sent
9353       m_foxQSO[hc].nRR73++;                 //Increment RR73 counter
9354       if(list1.size()==m_Nslots) goto list1Done;
9355     }
9356   }
9357 
9358   for(QString hc: m_foxQSO.keys()) {           //Check all Hound calls: Third priority
9359     if(m_foxQSO[hc].tFoxRrpt<0) continue;
9360     if(m_foxQSO[hc].tFoxTxRR73 <= m_foxQSO[hc].tFoxRrpt) {
9361       //We received R+rpt more recently than we sent RR73
9362       list1 << hc;                          //Add to list1
9363       m_foxQSO[hc].tFoxTxRR73 = m_tFoxTx;   //Time RR73 is sent
9364       m_foxQSO[hc].nRR73++;                 //Increment RR73 counter
9365       if(list1.size()==m_Nslots) goto list1Done;
9366     }
9367   }
9368 
9369 list1Done:
9370 //Compile list2: Up to Nslots Hound calls to be sent a report.
9371   for(int i=0; i<m_foxQSOinProgress.count(); i++) {
9372     //First do those for QSOs in progress
9373     hc=m_foxQSOinProgress.at(i);
9374     if((m_foxQSO[hc].tFoxRrpt < 0) and (m_foxQSO[hc].ncall < m_maxStrikes)) {
9375       //Sent him a report and have not received R+rpt: call him again
9376       list2 << hc;                          //Add to list2
9377       if(list2.size()==m_Nslots) goto list2Done;
9378     }
9379   }
9380 
9381   while(!m_houndQueue.isEmpty()) {
9382     //Start QSO with a new Hound
9383     t=m_houndQueue.dequeue();             //Fetch new hound from queue
9384     int i0=t.indexOf(" ");
9385     hc=t.mid(0,i0);                       //hound call
9386     list2 << hc;                          //Add new Hound to list2
9387     m_foxQSOinProgress.enqueue(hc);       //Put him in the QSO queue
9388     m_foxQSO[hc].grid=t.mid(16,4);        //Hound grid
9389     rpt=t.mid(12,3);                      //report to send Hound
9390     m_foxQSO[hc].sent=rpt;                //Report to send him
9391     m_foxQSO[hc].ncall=0;                 //Start a new Hound
9392     m_foxQSO[hc].nRR73 = 0;               //Have not sent RR73
9393     m_foxQSO[hc].rcvd = -99;              //Have not received R+rpt
9394     m_foxQSO[hc].tFoxRrpt = -1;           //Have not received R+rpt
9395     m_foxQSO[hc].tFoxTxRR73 = -1;         //Have not sent RR73
9396     rm_tb4(hc);                           //Remove this Hound from tb4
9397     if(list2.size()==m_Nslots) goto list2Done;
9398     if(m_foxQSO.count()>=2*m_Nslots) goto list2Done;
9399   }
9400 
9401 list2Done:
9402   n1=list1.size();
9403   n2=list2.size();
9404   n3=qMax(n1,n2);
9405   if(n3>m_Nslots) n3=m_Nslots;
9406   for(int i=0; i<n3; i++) {
9407     hc1="";
9408     fm="";
9409     if(i<n1 and i<n2) {
9410       hc1=list1.at(i);
9411       hc2=list2.at(i);
9412       m_foxQSO[hc2].ncall++;
9413       fm = Radio::base_callsign(hc1) + " RR73; " + Radio::base_callsign(hc2) +
9414           " <" + m_config.my_callsign() + "> " + m_foxQSO[hc2].sent;
9415     }
9416     if(i<n1 and i>=n2) {
9417       hc1=list1.at(i);
9418       fm = Radio::base_callsign(hc1) + " " + m_baseCall + " RR73";                 //Standard FT8 message
9419     }
9420 
9421     if(hc1!="") {
9422       // Log this QSO!
9423       auto QSO_time = QDateTime::currentDateTimeUtc ();
9424       m_hisCall=hc1;
9425       m_hisGrid=m_foxQSO[hc1].grid;
9426       m_rptSent=m_foxQSO[hc1].sent;
9427       m_rptRcvd=m_foxQSO[hc1].rcvd;
9428       if (!m_foxLogWindow) on_fox_log_action_triggered ();
9429       if (m_logBook.fox_log ()->add_QSO (QSO_time, m_hisCall, m_hisGrid, m_rptSent, m_rptRcvd, m_lastBand))
9430         {
9431           writeFoxQSO (QString {" Log:  %1 %2 %3 %4 %5"}.arg (m_hisCall).arg (m_hisGrid)
9432                        .arg (m_rptSent).arg (m_rptRcvd).arg (m_lastBand));
9433           on_logQSOButton_clicked ();
9434           m_foxRateQueue.enqueue (now); //Add present time in seconds
9435                                         //to Rate queue.
9436         }
9437       m_loggedByFox[hc1] += (m_lastBand + " ");
9438     }
9439 
9440     if(i<n2 and fm=="") {
9441       hc2=list2.at(i);
9442       m_foxQSO[hc2].ncall++;
9443       fm = Radio::base_callsign(hc2) + " " + m_baseCall + " " + m_foxQSO[hc2].sent; //Standard FT8 message
9444     }
9445     islot++;
9446     foxGenWaveform(islot-1,fm);                             //Generate tx waveform
9447   }
9448 
9449   if(islot < m_Nslots) {
9450     //At least one slot is still open
9451     if(islot==0 or ((m_tFoxTx-m_tFoxTx0>=4) and ui->cbMoreCQs->isChecked())) {
9452       //Roughly every 4th Tx sequence, put a CQ message in an otherwise empty slot
9453       fm=ui->comboBoxCQ->currentText() + " " + m_config.my_callsign();
9454       if(!fm.contains("/")) {
9455         fm += " " + m_config.my_grid().mid(0,4);
9456         m_tFoxTx0=m_tFoxTx;                                //Remember when we send a CQ
9457         m_fullFoxCallTime=now;
9458       }
9459       islot++;
9460       foxGenWaveform(islot-1,fm);
9461     }
9462   }
9463 
9464 Transmit:
9465   foxcom_.nslots=islot;
9466   foxcom_.nfreq=ui->TxFreqSpinBox->value();
9467   if(m_config.split_mode()) foxcom_.nfreq = foxcom_.nfreq - m_XIT;  //Fox Tx freq
9468   QString foxCall=m_config.my_callsign() + "         ";
9469   ::memcpy(foxcom_.mycall, foxCall.toLatin1(),sizeof foxcom_.mycall);   //Copy Fox callsign into foxcom_
9470   foxgen_();
9471   m_tFoxTxSinceCQ++;
9472 
9473   for(QString hc: m_foxQSO.keys()) {               //Check for strikeout or timeout
9474     if(m_foxQSO[hc].ncall>=m_maxStrikes) m_foxQSO[hc].ncall++;
9475     bool b1=((m_tFoxTx - m_foxQSO[hc].tFoxRrpt) > 2*m_maxFoxWait) and
9476         (m_foxQSO[hc].tFoxRrpt > 0);
9477     bool b2=((m_tFoxTx - m_foxQSO[hc].tFoxTxRR73) > m_maxFoxWait) and
9478         (m_foxQSO[hc].tFoxTxRR73>0);
9479     bool b3=(m_foxQSO[hc].ncall >= m_maxStrikes+m_maxFoxWait);
9480     bool b4=(m_foxQSO[hc].nRR73 >= m_maxStrikes);
9481     if(b1 or b2 or b3 or b4) {
9482       m_foxQSO.remove(hc);
9483       m_foxQSOinProgress.removeOne(hc);
9484     }
9485   }
9486 
9487   while(!m_foxRateQueue.isEmpty()) {
9488     qint64 age = now - m_foxRateQueue.head();
9489     if(age < 3600) break;
9490     m_foxRateQueue.dequeue();
9491   }
9492   if (m_foxLogWindow)
9493     {
9494       m_foxLogWindow->rate (m_foxRateQueue.size ());
9495       m_foxLogWindow->queued (m_foxQSOinProgress.count ());
9496     }
9497 }
9498 
rm_tb4(QString houndCall)9499 void MainWindow::rm_tb4(QString houndCall)
9500 {
9501   if(houndCall=="") return;
9502   QString t="";
9503   QString tb4=ui->textBrowser4->toPlainText();
9504   QStringList list=tb4.split("\n");
9505   int n=list.size();
9506   int j=0;
9507   for (int i=0; i<n; i++) {
9508     if(j>0) t += "\n";
9509     QString line=list.at(i);
9510     if(!line.contains(houndCall + " ")) {
9511       j++;
9512       t += line;
9513     }
9514   }
9515   t.replace("\n\n","\n");
9516   ui->textBrowser4->setText(t);
9517 }
9518 
doubleClickOnFoxQueue(Qt::KeyboardModifiers modifiers)9519 void MainWindow::doubleClickOnFoxQueue(Qt::KeyboardModifiers modifiers)
9520 {
9521   if(modifiers==9999) return;                               //Silence compiler warning
9522   QTextCursor cursor=ui->textBrowser4->textCursor();
9523   cursor.setPosition(cursor.selectionStart());
9524   QString houndCall=cursor.block().text().mid(0,12).trimmed();
9525   rm_tb4(houndCall);
9526   writeFoxQSO(" Del:  " + houndCall);
9527   QQueue<QString> tmpQueue;
9528   while(!m_houndQueue.isEmpty()) {
9529     QString t=m_houndQueue.dequeue();
9530     QString hc=t.mid(0,12).trimmed();
9531     if(hc != houndCall) tmpQueue.enqueue(t);
9532   }
9533   m_houndQueue.swap(tmpQueue);
9534 }
9535 
foxGenWaveform(int i,QString fm)9536 void MainWindow::foxGenWaveform(int i,QString fm)
9537 {
9538 //Generate and accumulate the Tx waveform
9539   fm += "                                        ";
9540   fm=fm.mid(0,40);
9541   if(fm.mid(0,3)=="CQ ") m_tFoxTxSinceCQ=-1;
9542 
9543   QString txModeArg;
9544   txModeArg = txModeArg.asprintf("FT8fox %d",i+1);
9545   ui->decodedTextBrowser2->displayTransmittedText(fm.trimmed(), txModeArg,
9546         ui->TxFreqSpinBox->value()+60*i,m_bFastMode,m_TRperiod);
9547   foxcom_.i3bit[i]=0;
9548   if(fm.indexOf("<")>0) foxcom_.i3bit[i]=1;
9549   strncpy(&foxcom_.cmsg[i][0],fm.toLatin1(),40);   //Copy this message into cmsg[i]
9550   if(i==0) m_fm1=fm;
9551   QString t;
9552   t = t.asprintf(" Tx%d:  ",i+1);
9553   writeFoxQSO(t + fm.trimmed());
9554 }
9555 
writeFoxQSO(QString const & msg)9556 void MainWindow::writeFoxQSO(QString const& msg)
9557 {
9558   QString t;
9559   t = t.asprintf("%3d%3d%3d",m_houndQueue.count(),m_foxQSOinProgress.count(),m_foxQSO.count());
9560   QFile f {m_config.writeable_data_dir ().absoluteFilePath ("FoxQSO.txt")};
9561   if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) {
9562     QTextStream out(&f);
9563     out << QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd hh:mm:ss") << "  "
9564 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
9565         << Qt::fixed
9566 #else
9567         << fixed
9568 #endif
9569         << qSetRealNumberPrecision (3) << (m_freqNominal/1.e6)
9570         << t << msg
9571 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
9572         << Qt::endl
9573 #else
9574         << endl
9575 #endif
9576       ;
9577     f.close();
9578   } else {
9579     MessageBox::warning_message (this, tr("File Open Error"),
9580       tr("Cannot open \"%1\" for append: %2").arg(f.fileName()).arg(f.errorString()));
9581   }
9582 }
9583 
9584 /*################################################################################### */
foxTest()9585 void MainWindow::foxTest()
9586 {
9587   QFile f("steps.txt");
9588   if(!f.open(QIODevice::ReadOnly | QIODevice::Text)) return;
9589 
9590   QFile fdiag("diag.txt");
9591   if(!fdiag.open(QIODevice::WriteOnly | QIODevice::Text)) return;
9592 
9593   QTextStream s(&f);
9594   QTextStream sdiag(&fdiag);
9595   QString line;
9596   QString t;
9597   QString msg;
9598   QString hc1;
9599   QString rptRcvd;
9600   qint32 n=0;
9601 
9602   while(!s.atEnd()) {
9603     line=s.readLine();
9604     if(line.length()==0) continue;
9605     if(line.mid(0,4).toInt()==0) line="                                     " + line;
9606     if(line.contains("NSlots")) {
9607       n=line.mid(44,1).toInt();
9608       ui->sbNslots->setValue(n);
9609     }
9610     if(line.contains("Sel:")) {
9611       t=line.mid(43,6) + "       " + line.mid(54,4) + "   " + line.mid(50,3);
9612       selectHound(t);
9613     }
9614 
9615     if(line.contains("Del:")) {
9616       int i0=line.indexOf("Del:");
9617       hc1=line.mid(i0+6);
9618       int i1=hc1.indexOf(" ");
9619       hc1=hc1.mid(0,i1);
9620       rm_tb4(hc1);
9621       writeFoxQSO(" Del:  " + hc1);
9622       QQueue<QString> tmpQueue;
9623       while(!m_houndQueue.isEmpty()) {
9624         t=m_houndQueue.dequeue();
9625         QString hc=t.mid(0,6).trimmed();
9626         if(hc != hc1) tmpQueue.enqueue(t);
9627       }
9628       m_houndQueue.swap(tmpQueue);
9629     }
9630     if(line.contains("Rx:"))  {
9631       msg=line.mid(43);
9632       t=msg.mid(24);
9633       int i0=t.indexOf(" ");
9634       hc1=t.mid(i0+1);
9635       int i1=hc1.indexOf(" ");
9636       hc1=hc1.mid(0,i1);
9637       int i2=qMax(msg.indexOf("R+"),msg.indexOf("R-"));
9638       if(i2>20) {
9639         rptRcvd=msg.mid(i2,4);
9640         foxRxSequencer(msg,hc1,rptRcvd);
9641       }
9642     }
9643     if(line.contains("Tx1:")) {
9644       foxTxSequencer();
9645     } else {
9646       t = t.asprintf("%3d %3d %3d %3d %5d   ",m_houndQueue.count(),
9647                 m_foxQSOinProgress.count(),m_foxQSO.count(),
9648                 m_loggedByFox.count(),m_tFoxTx);
9649       sdiag << t << line.mid(37).trimmed() << "\n";
9650     }
9651   }
9652 }
9653 
write_all(QString txRx,QString message)9654 void MainWindow::write_all(QString txRx, QString message)
9655 {
9656   // Z
9657   if(m_config.disableWriteALL()) return;
9658 
9659   QString line;
9660   QString t;
9661   QString msg;
9662   QString mode_string;
9663 
9664   if (message.size () > 5 && message[4]==' ') {
9665      msg=message.mid(4,-1);
9666   } else {
9667      msg=message.mid(6,-1);
9668   }
9669 
9670   if (message.size () > 19 && message[19]=='#') {
9671      mode_string="JT65  ";
9672   } else if (message.size () > 19 && message[19]=='@') {
9673      mode_string="JT9   ";
9674   } else if(m_mode=="Q65") {
9675     mode_string=mode_label.text();
9676   } else {
9677      mode_string=m_mode.leftJustified(6,' ');
9678   }
9679 
9680   msg=msg.mid(0,15) + msg.mid(18,-1);
9681 
9682   t = t.asprintf("%5d",ui->TxFreqSpinBox->value());
9683   if (txRx=="Tx") msg="   0  0.0" + t + " " + message;
9684   auto time = QDateTime::currentDateTimeUtc ();
9685   if( txRx=="Rx" && !m_bFastMode ) time=m_dateTimeSeqStart;
9686 
9687   t = t.asprintf("%10.3f ",m_freqNominal/1.e6);
9688   if (m_diskData) {
9689     if (m_fileDateTime.size()==11) {
9690       line=m_fileDateTime + "  " + t + txRx + " " + mode_string + msg;
9691     } else {
9692       line=m_fileDateTime + t + txRx + " " + mode_string + msg;
9693     }
9694   } else {
9695     line=time.toString("yyMMdd_hhmmss") + t + txRx + " " + mode_string + msg;
9696   }
9697 
9698   QString file_name="ALL.TXT";
9699   if (m_mode=="WSPR") file_name="ALL_WSPR.TXT";
9700   QFile f{m_config.writeable_data_dir().absoluteFilePath(file_name)};
9701   if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) {
9702     QTextStream out(&f);
9703     out << line.trimmed()
9704 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
9705         << Qt::endl
9706 #else
9707         << endl
9708 #endif
9709       ;
9710     f.close();
9711   } else {
9712     auto const& message2 = tr ("Cannot open \"%1\" for append: %2")
9713         .arg (f.fileName ()).arg (f.errorString ());
9714     QTimer::singleShot (0, [=] {                   // don't block guiUpdate
9715       MessageBox::warning_message(this, tr ("Log File Error"), message2); });
9716   }
9717 }
9718 
chkFT4()9719 void MainWindow::chkFT4()
9720 {
9721   if(m_mode!="FT4") return;
9722   ui->cbAutoSeq->setEnabled(true);
9723   ui->cbFirst->setVisible(true);
9724   ui->cbFirst->setEnabled(true);
9725   ui->labDXped->setVisible(m_config.special_op_id()!=SpecOp::NONE);
9726   ui->cbFirst->setVisible(ui->cbAutoSeq->isChecked());
9727 
9728   if (SpecOp::NONE < m_config.special_op_id () && SpecOp::FOX > m_config.special_op_id ()) {
9729     QString t0="";
9730     if(SpecOp::NA_VHF==m_config.special_op_id()) t0+="NA VHF";
9731     if(SpecOp::EU_VHF==m_config.special_op_id()) t0+="EU VHF";
9732     if(SpecOp::FIELD_DAY==m_config.special_op_id()) t0+="Field Day";
9733     if(SpecOp::RTTY==m_config.special_op_id()) t0+="RTTY";
9734     if(SpecOp::WW_DIGI==m_config.special_op_id()) t0+="WW_DIGI";
9735     if(t0=="") {
9736       ui->labDXped->setVisible(false);
9737     } else {
9738       ui->labDXped->setVisible(true);
9739       ui->labDXped->setText(t0);
9740     }
9741     on_contest_log_action_triggered();
9742   }
9743   if (SpecOp::HOUND == m_config.special_op_id() or SpecOp::FOX == m_config.special_op_id()) {
9744     ui->labDXped->setVisible(false);
9745   }
9746 
9747 }
9748 
on_pbBestSP_clicked()9749 void MainWindow::on_pbBestSP_clicked()
9750 {
9751   m_bBestSPArmed = !m_bBestSPArmed;
9752   if(m_bBestSPArmed and !m_transmitting) ui->pbBestSP->setStyleSheet ("QPushButton{color:red}");
9753   if(!m_bBestSPArmed) ui->pbBestSP->setStyleSheet ("");
9754   if(m_bBestSPArmed) m_dateTimeBestSP=QDateTime::currentDateTimeUtc();
9755 }
9756 
set_mode(QString const & mode)9757 void MainWindow::set_mode (QString const& mode)
9758 {
9759     if ("FT4" == mode) on_actionFT4_triggered ();
9760     else if ("FST4" == mode) on_actionFST4_triggered ();
9761     else if ("FST4W" == mode) on_actionFST4W_triggered ();
9762     else if ("FT8" == mode) on_actionFT8_triggered ();
9763     else if ("JT4" == mode) on_actionJT4_triggered ();
9764     else if ("JT9" == mode) on_actionJT9_triggered ();
9765     else if ("JT65" == mode) on_actionJT65_triggered ();
9766     else if ("Q65" == mode) on_actionQ65_triggered ();
9767     else if ("FreqCal" == mode) on_actionFreqCal_triggered ();
9768     else if ("MSK144" == mode) on_actionMSK144_triggered ();
9769     else if ("WSPR" == mode) on_actionWSPR_triggered ();
9770     else if ("Echo" == mode) on_actionEcho_triggered ();
9771 }
9772 
remote_configure(QString const & mode,quint32 frequency_tolerance,QString const & submode,bool fast_mode,quint32 tr_period,quint32 rx_df,QString const & dx_call,QString const & dx_grid,bool generate_messages)9773 void MainWindow::remote_configure (QString const& mode, quint32 frequency_tolerance
9774                                    , QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
9775                                    , QString const& dx_call, QString const& dx_grid, bool generate_messages)
9776 {
9777   if (mode.size ())
9778     {
9779       set_mode (mode);
9780     }
9781   auto is_FST4W = "FST4W" == m_mode;
9782   if (frequency_tolerance != quint32_max && (ui->sbFtol->isVisible () || is_FST4W))
9783     {
9784       m_block_udp_status_updates = true;
9785       if (is_FST4W)
9786         {
9787           ui->sbFST4W_FTol->setValue (frequency_tolerance);
9788         }
9789       else
9790         {
9791           ui->sbFtol->setValue (frequency_tolerance);
9792         }
9793       m_block_udp_status_updates = false;
9794     }
9795   if (submode.size () && ui->sbSubmode->isVisible ())
9796     {
9797       ui->sbSubmode->setValue (submode.toUpper ().at (0).toLatin1 () - 'A');
9798     }
9799   if (ui->cbFast9->isVisible () && ui->cbFast9->isChecked () != fast_mode)
9800     {
9801       ui->cbFast9->click ();
9802     }
9803   if (tr_period != quint32_max && ui->sbTR->isVisible ())
9804     {
9805       if (is_FST4W)
9806         {
9807           ui->sbTR_FST4W->setValue (tr_period);
9808           ui->sbTR_FST4W->interpretText ();
9809         }
9810       else
9811         {
9812           ui->sbTR->setValue (tr_period);
9813           ui->sbTR->interpretText ();
9814         }
9815     }
9816   if (rx_df != quint32_max && ui->RxFreqSpinBox->isVisible ())
9817     {
9818       m_block_udp_status_updates = true;
9819       if (is_FST4W)
9820         {
9821           ui->sbFST4W_RxFreq->setValue (rx_df);
9822           ui->sbFST4W_RxFreq->interpretText ();
9823         }
9824       else
9825         {
9826           ui->RxFreqSpinBox->setValue (rx_df);
9827           ui->RxFreqSpinBox->interpretText ();
9828         }
9829       m_block_udp_status_updates = false;
9830     }
9831   if (dx_call.size () && ui->dxCallEntry->isVisible ())
9832     {
9833       ui->dxCallEntry->setText (dx_call);
9834     }
9835   if (dx_grid.size () && ui->dxGridEntry->isVisible ())
9836     {
9837       ui->dxGridEntry->setText (dx_grid);
9838     }
9839   if (generate_messages && ui->genStdMsgsPushButton->isVisible ())
9840     {
9841       ui->genStdMsgsPushButton->click ();
9842     }
9843   if (m_config.udpWindowToFront ())
9844     {
9845       show ();
9846       raise ();
9847       activateWindow ();
9848     }
9849   if (m_config.udpWindowRestore () && isMinimized ())
9850     {
9851       showNormal ();
9852       raise ();
9853     }
9854   tx_watchdog (false);
9855   QApplication::alert (this);
9856 }
9857 
WSPR_message()9858 QString MainWindow::WSPR_message()
9859 {
9860   QString sdBm,msg0,msg1,msg2;
9861   sdBm = sdBm.asprintf(" %d",m_dBm);
9862   m_tx=1-m_tx;
9863   int i2=m_config.my_callsign().indexOf("/");
9864   if(i2>0
9865      || (6 == m_config.my_grid ().size ()
9866          && !ui->WSPR_prefer_type_1_check_box->isChecked ())) {
9867     if(i2<0) {                                                 // "Type 2" WSPR message
9868       msg1=m_config.my_callsign() + " " + m_config.my_grid().mid(0,4) + sdBm;
9869     } else {
9870       msg1=m_config.my_callsign() + sdBm;
9871     }
9872     msg0="<" + m_config.my_callsign() + "> " + m_config.my_grid();
9873     if(m_mode=="WSPR") msg0 += sdBm;
9874     if(m_tx==0) msg2=msg0;
9875     if(m_tx==1) msg2=msg1;
9876   } else {
9877     msg2=m_config.my_callsign() + " " + m_config.my_grid().mid(0,4) + sdBm; // Normal WSPR message
9878   }
9879   return msg2;
9880 }
9881 // Z
9882 
on_cbAutoCall_toggled(bool b)9883 void MainWindow::on_cbAutoCall_toggled(bool b)
9884 {
9885 
9886     if (b) {
9887         ui->cbCQonly->setChecked(true);
9888         ui->cbCQonly->setEnabled(false);
9889         ui->cbAutoCQ->setChecked(false);
9890         ui->cbAutoCQ->setEnabled(false);
9891         ui->cb_callB4onBand->setChecked(true);
9892         ui->cb_callB4onBand->setEnabled(false);
9893         ui->cb_filtering->setChecked(true);
9894         ui->cb_filtering->setEnabled(false);
9895         resetAutoSwitch();
9896         if (!m_autoModeSwitch) clearDX();
9897     } else {
9898         ui->cbCQonly->setEnabled(true);
9899         ui->cbAutoCQ->setEnabled(true);
9900         ui->cb_callB4onBand->setEnabled(true);
9901         ui->cb_filtering->setEnabled(true);
9902     }
9903 
9904     auto_tx_mode(false);
9905 }
9906 
on_cbAutoCQ_toggled(bool b)9907 void MainWindow::on_cbAutoCQ_toggled(bool b)
9908 {
9909     if (b) {
9910         ui->cbAutoCall->setChecked(false);
9911         ui->cbAutoCall->setEnabled(false);
9912         ui->cbFirst->setChecked(true);
9913         ui->cbAutoSeq->setChecked(true);
9914         ui->txrb6->setChecked(true);
9915         resetAutoSwitch();
9916         if (!m_autoModeSwitch) clearDX();
9917     } else {
9918         ui->cbAutoCall->setEnabled(true);
9919     }
9920 
9921     auto_tx_mode(b);
9922 }
9923 
on_btn_addToIgnore_clicked()9924 void MainWindow::on_btn_addToIgnore_clicked( ) {
9925     ui->pte_IgnoredStations->appendPlainText(m_hisCall);
9926 }
9927 
on_btn_clearIgnore_clicked()9928 void MainWindow::on_btn_clearIgnore_clicked( ) {
9929     ui->pte_IgnoredStations->clear();
9930     ui->pte_IgnoredStations->appendPlainText(m_config.permIgnoreList());
9931     m_ignoreListReset = QDateTime::currentDateTime();
9932 }
9933 
9934 
callsignFiltered(DecodedText dt)9935 bool MainWindow::callsignFiltered(DecodedText dt)
9936 {
9937 
9938     QString dxCall;
9939     QString dxGrid;
9940     QString CQTarget;
9941     bool callB4;
9942     bool callB4onBand;
9943     bool countryB4;
9944     bool countryB4onBand;
9945     bool gridB4;
9946     bool gridB4onBand;
9947     bool continentB4;
9948     bool continentB4onBand;
9949     bool CQZoneB4;
9950     bool CQZoneB4onBand;
9951     bool ITUZoneB4;
9952     bool ITUZoneB4onBand;
9953     bool matched = false;
9954 
9955     log("callsignFiltered: ENTRY");
9956 
9957     auto const& message_words = dt.messageWords ();
9958     if (message_words.size() == 0) return false;
9959     log("message:" + dt.string());
9960     dt.deCallAndGrid (/*out*/ dxCall, dxGrid);
9961     log("dxCall: " + dxCall);
9962     int nmod = fmod(double(dt.timeInSeconds()),2.0*m_TRperiod);
9963     auto m_txFirst=(nmod!=0);
9964 
9965     if (!dxCall.contains(QRegExp("[0-9]")) || dxCall.length() < 3) {
9966         log("callsignFiltered: Invalid callsign. Skipping.");
9967         return true;
9968     }
9969 
9970     if (dxCall.endsWith("/R")) {
9971         log("callsignFiltered: False decode (ends with /R). Skipping.");
9972         return true;
9973     }
9974 
9975     bool is_73 = (message_words.size() == 4 && (message_words[3] == "73" || message_words[3] == "RR73"));
9976     bool is_CQ = message_words.filter (QRegularExpression {"^(CQ|CQ\\s\\w+)$"}).size();
9977 
9978     // Auto call next
9979     if (ui->cb_autoCallNext->isChecked() && dxCall == ui->dxCallEntry->text() ) {
9980           if (is_CQ || is_73) {
9981 
9982               if (m_TxFirstLock && (ui->txFirstCheckBox->isChecked() != m_txFirst)) {
9983                 log("callsignFiltered: TX First Lock. Pounce cancelled.");
9984                 return false;
9985               } else {
9986                   m_priorityCall = dxCall;
9987                   m_prioFreq = dt.frequencyOffset();
9988                   m_nextRpt = dt.report();
9989                   m_prioTxFirst=(nmod!=0);
9990                   m_prioGrid  = dxGrid;
9991                   log("callsignFiltered: Pounce mode");
9992                   return false;
9993               }
9994           }
9995      }
9996 
9997 
9998     if (!ui->cb_filtering->isChecked()) return false;
9999 
10000     // LOTW only filter
10001     if ( ui->cb_f_LOTW->isChecked() && !m_config.lotw_users ().user (dxCall)) {
10002         log("callsignFiltered: User not in LOTW");
10003         return true;
10004     }
10005 
10006     // Ignored stations filter
10007     QStringList ignoredStations = ui->pte_IgnoredStations->toPlainText().split(QRegExp("[\r\n]"),SkipEmptyParts);
10008     if (ignoredStations.contains(dxCall)) {
10009         log("callsignFiltered: Station is in the ignore list");
10010         return true;
10011     }
10012 
10013 
10014     // Minimum signal strngth filter
10015     QString dbM = dt.report();
10016     if (ui->sbMindB->value() > -30 && dbM.toInt() < ui->sbMindB->value()) {
10017         log("callsignFiltered: Station signal strength under threshold: " + dbM);
10018         return true;
10019     }
10020 
10021     // Continent filter
10022     auto const& looked_up = m_logBook.countries ()->lookup (dxCall);
10023     QString continent = AD1CCty::continent (looked_up.continent);
10024     log("callsignFiltered: Continent filtering...");
10025     if (continent == "EU" && !ui->cb_c_EU->isChecked()) return true;
10026     else if (continent == "AF" && !ui->cb_c_AF->isChecked()) return true;
10027     else if (continent == "AN" && !ui->cb_c_AN->isChecked()) return true;
10028     else if (continent == "AS" && !ui->cb_c_AS->isChecked()) return true;
10029     else if (continent == "NA" && !ui->cb_c_NA->isChecked()) return true;
10030     else if (continent == "SA" && !ui->cb_c_SA->isChecked()) return true;
10031     else if (continent == "OC" && !ui->cb_c_OC->isChecked()) return true;
10032 
10033     log("callsignFiltered: CQ Target filtering...");
10034 
10035     if (!message_words[1].startsWith("CQ") && ui->cb_ignoreCQTarget->currentIndex() == 3) return true;
10036 
10037     if( message_words.size() > 2 && ( ui->cb_ignoreCQTarget->currentIndex() > 0 || ui->cb_filter_CQDX_Continent->currentIndex() > 0) && message_words[1].startsWith("CQ")) {
10038         QString w0 = message_words[0];
10039         QString w1 = message_words[1];
10040         QString w2 = message_words[2];
10041 
10042         if (w2.length()<=4 && !w2.contains(QRegExp("[0-9]"))) {
10043             CQTarget = w2;
10044         } else {
10045             CQTarget = w1.mid(w1.indexOf(" ")+1, -1);
10046         }
10047 
10048         log("++ CQ Target: " + CQTarget);
10049         if ((CQTarget == "CQ" || CQTarget == "DX") && ui->cb_ignoreCQTarget->currentIndex() == 3) return true;
10050 
10051             if (CQTarget != "CQ")
10052             {
10053                 QStringList  ignoreCQXXList = ui->le_ignoreCQXX->text().split(",",SkipEmptyParts);
10054                 if (CQTarget != "DX" && CQTarget.length() && ignoreCQXXList.size()>0) {
10055                     // CQ Target filter
10056                     if ( ui->cb_ignoreCQTarget->currentIndex() == 2 && ignoreCQXXList.contains(CQTarget)) return true;
10057                     if ( (ui->cb_ignoreCQTarget->currentIndex() == 1 || ui->cb_ignoreCQTarget->currentIndex() == 3 ) && !ignoreCQXXList.contains(CQTarget)) return true;
10058                 }
10059                 // CQ DX filter
10060                 if (CQTarget == "DX" && ui->cb_filter_CQDX_Continent->currentIndex() > 0 ) {
10061                     log("callsignFiltered: CQ DX filtering...");
10062                     int idx = ui->cb_filter_CQDX_Continent->currentIndex();
10063                     switch (idx) {
10064                         case 1:
10065                             if (continent == "AF") return true;
10066                             break;
10067 
10068                         case 2:
10069                             if (continent == "NA") return true;
10070                             break;
10071 
10072                         case 3:
10073                             if (continent == "SA") return true;
10074                             break;
10075 
10076                         case 4:
10077                             if (continent == "OC") return true;
10078                             break;
10079 
10080                         case 5:
10081                             if (continent == "EU") return true;
10082                             break;
10083 
10084                         case 6:
10085                             if (continent == "AS") return true;
10086                             break;
10087 
10088                         case 7:
10089                             if (continent == "AN") return true;
10090                             break;
10091 
10092                         default:
10093                             break;
10094                     }
10095 
10096                 }
10097         }
10098     }
10099 
10100     // New / New on band filter
10101     if (ui->cb_callB4->isChecked() || ui->cb_callB4onBand->isChecked() || ui->cb_countryB4->isChecked() || ui->cb_countryB4onBand->isChecked() ||
10102             ui->cb_gridB4->isChecked() || ui->cb_gridB4onBand->isChecked() || ui->cb_continentB4->isChecked() || ui->cb_continentB4onBand->isChecked() ||
10103             ui->cb_CQZoneB4->isChecked() || ui->cb_CQZoneB4onBand->isChecked() || ui->cb_ITUZoneB4->isChecked() || ui->cb_ITUZoneB4onBand->isChecked()) {
10104 
10105         log("callsignFiltered: New/New on band filtering...");
10106 
10107         m_logBook.match (dxCall, m_mode, dxGrid, looked_up, callB4, countryB4, gridB4, continentB4, CQZoneB4, ITUZoneB4);
10108         m_logBook.match (dxCall, m_mode, dxGrid, looked_up, callB4onBand, countryB4onBand, gridB4onBand,
10109                        continentB4onBand, CQZoneB4onBand, ITUZoneB4onBand, m_currentBand);
10110         matched = true;
10111 
10112         if (ui->cb_callB4->isChecked() && callB4) return true;
10113         if (ui->cb_callB4onBand->isChecked() && callB4onBand) return true;
10114         if (ui->cb_countryB4->isChecked() && countryB4) return true;
10115         if (ui->cb_countryB4onBand->isChecked() && countryB4onBand) return true;
10116         if (ui->cb_gridB4->isChecked() && gridB4) return true;
10117         if (ui->cb_gridB4onBand->isChecked() && gridB4onBand) return true;
10118         if (ui->cb_continentB4->isChecked() && continentB4) return true;
10119         if (ui->cb_continentB4onBand->isChecked() && continentB4onBand) return true;
10120         if (ui->cb_CQZoneB4->isChecked() && CQZoneB4) return true;
10121         if (ui->cb_CQZoneB4onBand->isChecked() && CQZoneB4onBand) return true;
10122         if (ui->cb_ITUZoneB4->isChecked() && ITUZoneB4) return true;
10123         if (ui->cb_ITUZoneB4onBand->isChecked() && ITUZoneB4onBand) return true;
10124 
10125     }
10126 
10127     // Prefix filter
10128     if (ui->cb_prefixFilter->currentIndex() > 0) {
10129         log("callsignFiltered: Prefix filtering...");
10130         QString filterText = ui->pte_prefixFilter->toPlainText();
10131         QStringList filterLines = filterText.split(QRegExp("[\r\n]"),SkipEmptyParts);
10132         int filterIndex = filterLines.indexOf(QRegExp("^" + m_currentBand +":.*"));
10133         QStringList filterPrefixes;
10134         if (filterIndex > -1) {
10135             QString filterLine = filterLines[filterIndex];
10136             filterLine = filterLine.mid(filterLine.indexOf(":")+1, -1);
10137             if (filterLine.trimmed().length() > 0)
10138                 filterPrefixes = filterLine.split(",");
10139 
10140             if (filterPrefixes.size()>0) {
10141                 // Exclude
10142                 if (ui->cb_prefixFilter->currentIndex() == 2)
10143                     for ( const auto& i : filterPrefixes  )
10144                     {
10145                             if (i.trimmed().startsWith("+")) {
10146                                     if(looked_up.entity_name.toUpper().indexOf(i.trimmed().toUpper().remove(0,1)) >= 0) return true;
10147                             } else {
10148                                     if (dxCall.startsWith(i.trimmed())) return true;
10149                             }
10150                     }
10151                 // Include
10152                 if (ui->cb_prefixFilter->currentIndex() == 1) {
10153                     bool filtered = true;
10154                     for ( const auto& i : filterPrefixes  )
10155                     {
10156                             if (i.trimmed().startsWith("+")) {
10157                                     if(looked_up.entity_name.toUpper().indexOf(i.trimmed().toUpper().remove(0,1)) >= 0)
10158                                         {
10159                                             filtered = false;
10160                                             break;
10161                                         }
10162                             } else {
10163                                     if (dxCall.startsWith(i.trimmed()))
10164                                     {
10165                                         filtered = false;
10166                                         break;
10167                                     }
10168                             }
10169                     }
10170 
10171                     if (filtered) return true;
10172                 }
10173 
10174             }
10175         }
10176     }
10177 
10178     //State filter
10179     if (ui->cb_stateFilter->currentIndex() > 0) {
10180         QString country = looked_up.entity_name;
10181         if  (country == "United States")  {
10182             QString state = stateLookup(dxCall);
10183             log("callsignFiltered: US State filtering: " + state);
10184 
10185             QString filterText = ui->pte_stateFilter->toPlainText();
10186             QStringList filterLines = filterText.split(QRegExp("[\r\n]"),SkipEmptyParts);
10187             int filterIndex = filterLines.indexOf(QRegExp("^" + m_currentBand +":.*"));
10188             QStringList filterPrefixes;
10189             if (filterIndex > -1) {
10190                 QString filterLine = filterLines[filterIndex];
10191                 filterLine = filterLine.mid(filterLine.indexOf(":")+1, -1);
10192                 if (filterLine.trimmed().length() > 0)
10193                     filterPrefixes = filterLine.split(",");
10194 
10195                 if (filterPrefixes.size()>0) {
10196                     // Exclude
10197                     if (ui->cb_stateFilter->currentIndex() == 2)
10198                         for ( const auto& i : filterPrefixes  )
10199                         {
10200                             if (state == i.trimmed()) return true;
10201                         }
10202                     // Include
10203                     if (ui->cb_stateFilter->currentIndex() == 1) {
10204                         bool filtered = true;
10205                         for ( const auto& i : filterPrefixes  )
10206                         {
10207                                 if (state == i.trimmed())
10208                                 {
10209                                     filtered = false;
10210                                     break;
10211                                 }
10212                         }
10213 
10214                         if (filtered) return true;
10215                     }
10216                 }
10217             }
10218 
10219         }
10220 
10221     }
10222 
10223 
10224     // Alerts
10225           if ((is_CQ || (ui->cbCQonlyIncl73->isChecked() && is_73)) &&
10226            (ui->cb_callB4_alert->isChecked() || ui->cb_callB4onBand_alert->isChecked() || ui->cb_countryB4_alert->isChecked() || ui->cb_countryB4onBand_alert->isChecked() ||
10227                   ui->cb_gridB4_alert->isChecked() || ui->cb_gridB4onBand_alert->isChecked() || ui->cb_continentB4_alert->isChecked() || ui->cb_continentB4onBand_alert->isChecked() ||
10228                   ui->cb_CQZoneB4_alert->isChecked() || ui->cb_CQZoneB4onBand_alert->isChecked() || ui->cb_ITUZoneB4_alert->isChecked() || ui->cb_ITUZoneB4onBand_alert->isChecked() ||
10229                   ui->le_CustomAlerts->text().length() > 0)) {
10230 
10231 
10232               if (!matched) {
10233                   m_logBook.match (dxCall, m_mode, dxGrid, looked_up, callB4, countryB4, gridB4, continentB4, CQZoneB4, ITUZoneB4);
10234                   m_logBook.match (dxCall, m_mode, dxGrid, looked_up, callB4onBand, countryB4onBand, gridB4onBand,
10235                              continentB4onBand, CQZoneB4onBand, ITUZoneB4onBand, m_currentBand);
10236               }
10237 
10238               // Custom alerts
10239               bool customAlert = false;
10240               QStringList customAlerts = ui->le_CustomAlerts->text().split(",",SkipEmptyParts);
10241 
10242               for ( const auto& i : customAlerts  ) {
10243                   if (dxCall.contains(i.toUpper())) {
10244                       customAlert = true;
10245                       break;
10246                   }
10247               }
10248 
10249 
10250               if (customAlert ||
10251                (ui->cb_callB4_alert->isChecked() && !callB4) ||
10252                (ui->cb_callB4onBand_alert->isChecked() && !callB4onBand)  ||
10253                (ui->cb_countryB4_alert->isChecked() && !countryB4)  ||
10254                (ui->cb_countryB4onBand_alert->isChecked() && !countryB4onBand)  ||
10255                (ui->cb_gridB4_alert->isChecked() && !gridB4)  ||
10256                (ui->cb_gridB4onBand_alert->isChecked() && !gridB4onBand)  ||
10257                (ui->cb_continentB4_alert->isChecked() && !continentB4)  ||
10258                (ui->cb_continentB4onBand_alert->isChecked() && !continentB4onBand)  ||
10259                (ui->cb_CQZoneB4_alert->isChecked() && !CQZoneB4)  ||
10260                (ui->cb_CQZoneB4onBand_alert->isChecked() && !CQZoneB4onBand)  ||
10261                (ui->cb_ITUZoneB4_alert->isChecked() && !ITUZoneB4)  ||
10262                (ui->cb_ITUZoneB4onBand_alert->isChecked() && !ITUZoneB4onBand)) {
10263                   if (!m_beeped) {
10264                       QApplication::beep();
10265                       ui->pte_alerts->appendPlainText( "[" + m_currentBand +"] " + QDateTime::currentDateTimeUtc().toString());
10266 
10267                   }
10268                   ui->pte_alerts->appendPlainText(dxCall.leftJustified(12, ' ') + looked_up.entity_name);
10269                   m_beeped = true;
10270               }
10271           }
10272 
10273     log("Call not filtered: " + dxCall);
10274 
10275     if ( !is_CQ && !(ui->cbCQonlyIncl73->isChecked() && is_73) ) {
10276         log("Not CQ/73. Exiting.");
10277         return false;
10278     }
10279 
10280 
10281     if (m_TxFirstLock && (ui->txFirstCheckBox->isChecked() != m_txFirst)) {
10282         log("callsignFiltered: TX First Lock. Exiting.");
10283         return false;
10284     }
10285 
10286     if (ui->cb_autoCallNext->isChecked()) {
10287         log("callsignFiltered: Pounce mode. Exiting.");
10288         return false;
10289     }
10290 
10291     bool prio = false;
10292     if (ui->cb_autoCallPriority->currentIndex() == 2) {
10293         if (dxGrid.length() == 4 && dxGrid != "RR73" && !dxGrid.startsWith("R-") && !dxGrid.startsWith("R+")) {
10294             double utch=0.0;
10295             int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter;
10296             azdist_(const_cast <char *> ((m_config.my_grid () + "      ").left (6).toLatin1 ().constData ()),
10297                     const_cast <char *> (dxGrid.left (6).toLatin1 ().constData ()),&utch,
10298                     &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6);
10299             if (nDkm >  m_maxDistance) {
10300                 prio = true;
10301                 m_maxDistance = nDkm;
10302             }
10303 
10304         } else {
10305             prio = true;
10306             m_maxDistance = 1;
10307         }
10308 
10309     } else if (ui->cb_autoCallPriority->currentIndex() == 1) {
10310         bool convOK;
10311         int intdbM = dbM.toInt(&convOK);
10312         if (convOK && intdbM >  m_maxSignal) {
10313                prio = true;
10314                m_maxSignal = intdbM;
10315 
10316        }
10317 
10318     } else if (ui->cb_autoCallPriority->currentIndex() == 0) {
10319         prio = true;
10320     }
10321 
10322 
10323     if (prio) {
10324         log("++ New Priority Call: " + dxCall);
10325         m_priorityCall = dxCall;
10326         m_prioFreq = dt.frequencyOffset();
10327         m_nextRpt = dt.report();
10328         int nmod = fmod(double(dt.timeInSeconds()),2.0*m_TRperiod);
10329         m_prioTxFirst=(nmod!=0);
10330         m_prioGrid  = dxGrid;
10331     }
10332 
10333 
10334 
10335     log("callsignFiltered: EXIT");
10336     return false;
10337 }
10338 
on_actionIgnore_station_triggered()10339 void MainWindow::on_actionIgnore_station_triggered() {
10340     QTextCursor cursor;
10341     QString dxCall;
10342     QString dxGrid;
10343 
10344     if (ui->decodedTextBrowser->hasFocus()) {
10345         cursor = ui->decodedTextBrowser->textCursor();
10346 
10347     } else {
10348         cursor = ui->decodedTextBrowser2->textCursor();
10349     }
10350 
10351     DecodedText message {cursor.selectedText().trimmed().remove("TU; ")};
10352     message.deCallAndGrid (/*out*/ dxCall, dxGrid);
10353 
10354     ui->pte_IgnoredStations->appendPlainText(dxCall);
10355     cursor.movePosition(QTextCursor::End); // move/modify/etc.
10356 
10357     if (ui->decodedTextBrowser->hasFocus()) {
10358         ui->decodedTextBrowser->setTextCursor(cursor);
10359     } else {
10360         ui->decodedTextBrowser2->setTextCursor(cursor);
10361     }
10362 
10363 }
10364 
on_actionCall_next_triggered()10365 void MainWindow::on_actionCall_next_triggered() {
10366     QTextCursor cursor;
10367     QString dxCall;
10368     QString dxGrid;
10369 
10370 
10371     if (ui->decodedTextBrowser->hasFocus()) {
10372         cursor = ui->decodedTextBrowser->textCursor();
10373 
10374     } else {
10375         cursor = ui->decodedTextBrowser2->textCursor();
10376     }
10377 
10378     DecodedText message {cursor.selectedText().trimmed().remove("TU; ")};
10379 
10380     cursor.movePosition(QTextCursor::End);
10381 
10382     if (ui->decodedTextBrowser->hasFocus()) {
10383         ui->decodedTextBrowser->setTextCursor(cursor);
10384     } else {
10385         ui->decodedTextBrowser2->setTextCursor(cursor);
10386     }
10387 
10388     message.deCallAndGrid (/*out*/ dxCall, dxGrid);
10389 
10390     m_nextCall = dxCall;
10391 
10392     m_nextRpt = message.report();
10393     ui->rptSpinBox->setValue(m_nextRpt.toInt());
10394 
10395     int nmod = fmod(double(message.timeInSeconds()),2.0*m_TRperiod);
10396     auto m_txFirst=(nmod!=0);
10397     if (!m_TxFirstLock)
10398         ui->txFirstCheckBox->setChecked(m_txFirst);
10399 
10400     int frequency = message.frequencyOffset();
10401     ui->RxFreqSpinBox->setValue(frequency);
10402 
10403     on_dxGridEntry_textChanged(dxGrid);
10404 
10405     useNextCall();
10406     on_txb1_clicked();
10407     ui->cb_autoCallNext->setChecked(true);
10408 }
10409 
on_actionClear_triggered()10410 void MainWindow::on_actionClear_triggered() {
10411     if (ui->decodedTextBrowser->hasFocus()) {
10412         ui->decodedTextBrowser->erase();
10413     } else {
10414         ui->decodedTextBrowser2->erase();
10415     }
10416 }
10417 
on_cb_autoCallNext_toggled(bool b)10418 void MainWindow::on_cb_autoCallNext_toggled(bool b) {
10419     if (b) {
10420        ui->dxCallEntry->setStyleSheet("background-color: #00aa00;");
10421        ui->cbAutoCall->setChecked(false);
10422        ui->cbAutoCQ->setChecked(false);
10423        genStdMsgs("");
10424        on_txb1_clicked();
10425        if (ui->cb_autoModeSwitch->isChecked()) resetAutoSwitch();
10426     } else {
10427        ui->dxCallEntry->setStyleSheet("");
10428     }
10429 }
10430 
on_cbMini_toggled(bool b)10431 void MainWindow::on_cbMini_toggled(bool b) {
10432         ui->signal_meter_widget->setVisible(!b);
10433         ui->QSO_controls_widget->setVisible(!b);
10434         ui->outAttenuation->setVisible(!b);
10435         ui->label_16->setVisible(!b);
10436         ui->logQSOButton->setVisible(!b);
10437         ui->monitorButton->setVisible(!b);
10438         ui->DecodeButton->setVisible(!b);
10439         ui->genStdMsgsPushButton->setVisible(!b);
10440         ui->lookupButton->setVisible(!b);
10441         ui->addButton->setVisible(!b);
10442 }
10443 
10444 
on_cbCQonlyIncl73_toggled(bool b)10445 void MainWindow::on_cbCQonlyIncl73_toggled(bool b) {
10446     if (b) {
10447         ui->cbHoldTxFreq->setChecked(true);
10448     }
10449 }
10450 
10451 
10452 
on_actionSet_Rx_Freq_triggered()10453 void MainWindow::on_actionSet_Rx_Freq_triggered() {
10454     QTextCursor cursor;
10455 
10456     if (ui->decodedTextBrowser->hasFocus()) {
10457         cursor = ui->decodedTextBrowser->textCursor();
10458 
10459     } else {
10460         cursor = ui->decodedTextBrowser2->textCursor();
10461     }
10462 
10463     DecodedText message {cursor.selectedText().trimmed().remove("TU; ")};
10464 
10465     cursor.movePosition(QTextCursor::End);
10466 
10467     if (ui->decodedTextBrowser->hasFocus()) {
10468         ui->decodedTextBrowser->setTextCursor(cursor);
10469     } else {
10470         ui->decodedTextBrowser2->setTextCursor(cursor);
10471     }
10472 
10473     int frequency = message.frequencyOffset();
10474     ui->RxFreqSpinBox->setValue(frequency);
10475 
10476  }
10477 
on_actionQRZ_Lookup_triggered()10478 void MainWindow::on_actionQRZ_Lookup_triggered() {
10479     QTextCursor cursor;
10480     QString dxCall;
10481     QString dxGrid;
10482 
10483     if (ui->decodedTextBrowser->hasFocus()) {
10484         cursor = ui->decodedTextBrowser->textCursor();
10485 
10486     } else {
10487         cursor = ui->decodedTextBrowser2->textCursor();
10488     }
10489 
10490     DecodedText message {cursor.selectedText().trimmed().remove("TU; ")};
10491     message.deCallAndGrid (/*out*/ dxCall, dxGrid);
10492 
10493     QDesktopServices::openUrl(QUrl("https://www.qrz.com/db/"+dxCall, QUrl::TolerantMode));
10494     cursor.movePosition(QTextCursor::End); // move/modify/etc.
10495 
10496     if (ui->decodedTextBrowser->hasFocus()) {
10497         ui->decodedTextBrowser->setTextCursor(cursor);
10498     } else {
10499         ui->decodedTextBrowser2->setTextCursor(cursor);
10500     }
10501 
10502 }
10503 
moveEvent(QMoveEvent *)10504 void MainWindow::moveEvent(QMoveEvent *)
10505 {
10506     if (!ui->cbDockWF->isChecked()) return;
10507 
10508     if ((m_lastX == -1 && m_lastY == -1)) {
10509         m_lastX = this->pos().x();
10510         m_lastY = this->pos().y();
10511         return;
10512     }
10513 
10514     int x = m_lastX - this->pos().x();
10515     int y = m_lastY - this->pos().y();
10516     m_lastX = this->pos().x();
10517     m_lastY = this->pos().y();
10518 
10519     if  (m_wideGraph && m_wideGraph->isVisible()) {
10520         int gx = m_wideGraph->pos().x();
10521         int gy = m_wideGraph->pos().y();
10522         m_wideGraph->move(gx-x,gy-y);
10523     }
10524 
10525     if (m_unfilteredView &&  m_unfilteredView->isVisible()) {
10526         int rx = m_unfilteredView->pos().x();
10527         int ry = m_unfilteredView->pos().y();
10528         m_unfilteredView->move(rx-x,ry-y);
10529     }
10530 
10531     if (m_pskReporterView &&  m_pskReporterView->isVisible()) {
10532         int rx = m_pskReporterView->pos().x();
10533         int ry = m_pskReporterView->pos().y();
10534         m_pskReporterView->move(rx-x,ry-y);
10535     }
10536 }
10537 
hideEvent(QHideEvent *)10538 void MainWindow::hideEvent(QHideEvent *)
10539 {
10540     if (!ui->cbDockWF->isChecked()) return;
10541     m_wideGraph->hide();
10542 }
10543 
showEvent(QShowEvent *)10544 void MainWindow::showEvent(QShowEvent *)
10545 {
10546     if (!ui->cbDockWF->isChecked()) return;
10547     m_wideGraph->show();
10548 }
10549 
on_actionCopy_triggered()10550 void MainWindow::on_actionCopy_triggered() {
10551     if (ui->decodedTextBrowser->hasFocus()) {
10552         ui->decodedTextBrowser->copy();
10553     } else {
10554         ui->decodedTextBrowser2->copy();
10555     }
10556  }
10557 
on_txrb6_doubleClicked()10558 void MainWindow::on_txrb6_doubleClicked ()
10559 {
10560     ui->dxCallEntry->clear();
10561     ui->dxGridEntry->clear();
10562     genStdMsgs ("0", true);
10563 }
10564 
leftClickHandler(Qt::KeyboardModifiers modifiers)10565 void MainWindow::leftClickHandler(Qt::KeyboardModifiers modifiers) {
10566   if(modifiers==(Qt::ControlModifier)) {
10567     dxLookup(NULL, NULL);
10568   } else {
10569       if (ui->decodedTextBrowser->hasFocus()) {
10570           ui->decodedTextBrowser->copy();
10571       } else {
10572           ui->decodedTextBrowser2->copy();
10573       }
10574   }
10575 }
10576 
dxLookup(QString dxCall,QString dxGrid)10577 void MainWindow::dxLookup(QString dxCall, QString dxGrid) {
10578 
10579     if (!ui->w_callInfo->isVisible()) return;
10580 
10581     QTextCursor cursor;
10582     bool callB4;
10583     bool countryB4;
10584     bool gridB4;
10585     bool continentB4;
10586     bool CQZoneB4;
10587     bool ITUZoneB4;
10588 
10589     clearCallInfo();
10590 
10591 
10592     if (dxCall.isNull()) {
10593         if (ui->decodedTextBrowser->hasFocus()) {
10594             cursor = ui->decodedTextBrowser->textCursor();
10595         } else {
10596             cursor = ui->decodedTextBrowser2->textCursor();
10597         }
10598 
10599         DecodedText message {cursor.selectedText().trimmed().remove("TU; ")};
10600         message.deCallAndGrid (/*out*/ dxCall, dxGrid);
10601         cursor.movePosition(QTextCursor::End); // move/modify/etc.
10602 
10603         if (ui->decodedTextBrowser->hasFocus()) {
10604             ui->decodedTextBrowser->setTextCursor(cursor);
10605         } else {
10606             ui->decodedTextBrowser2->setTextCursor(cursor);
10607         }
10608     }
10609 
10610     ui->ci_dxcall->setText(dxCall);
10611     qrzLookup(dxCall);
10612     auto const& looked_up = m_logBook.countries ()->lookup (dxCall);
10613     QString continent = AD1CCty::continent (looked_up.continent);
10614     continent.replace("AF", "Africa");
10615     continent.replace("AN", "Antarctica");
10616     continent.replace("AS", "Asia");
10617     continent.replace("EU", "Europe");
10618     continent.replace("NA", "N. America");
10619     continent.replace("OC", "Oceania");
10620     continent.replace("SA", "S. America");
10621     continent.replace("UN", "N/A");
10622 
10623     ui->ci_continent->setText(continent);
10624     ui->ci_continent->setCursorPosition(0);
10625 
10626     ui->ci_dxcc->setText(looked_up.entity_name);
10627     ui->ci_dxcc->setCursorPosition(0);
10628 
10629     ui->ci_cqzone->setText(QString::number(looked_up.CQ_zone));
10630     ui->ci_ituzone->setText(QString::number(looked_up.ITU_zone));
10631 
10632     m_logBook.match (dxCall, m_mode, dxGrid, looked_up, callB4, countryB4, gridB4, continentB4, CQZoneB4, ITUZoneB4);
10633 
10634     QFont bold = ui->ci_continent->font();
10635     bold.setWeight(QFont::Bold);
10636 
10637     if (!continentB4) ui->ci_continent->setFont(bold);
10638     if (!countryB4) ui->ci_dxcc->setFont(bold);
10639     if (!CQZoneB4) ui->ci_cqzone->setFont(bold);
10640     if (!ITUZoneB4) ui->ci_ituzone->setFont(bold);
10641     if (!callB4) ui->ci_dxcall->setFont(bold);
10642 
10643     if (dxGrid.length() != 4 || dxGrid == "RR73" || dxGrid.startsWith("R-") || dxGrid.startsWith("R+")) return;
10644 
10645     ui->ci_grid->setText(dxGrid);
10646 
10647     ci_gridLookup();
10648 }
10649 
on_actionCall_info_triggered()10650 void MainWindow::on_actionCall_info_triggered() {
10651     ui->w_callInfo->setVisible(ui->actionCall_info->isChecked());
10652 }
10653 
qrzVisible(bool b)10654 void MainWindow::qrzVisible(bool b) {
10655     ui->l_q_name->setVisible(b);
10656     ui->l_q_email->setVisible(b);
10657     ui->l_q_state->setVisible(b);
10658     ui->l_q_zipcode->setVisible(b);
10659     ui->q_name->setVisible(b);
10660     ui->q_email->setVisible(b);
10661     ui->q_state->setVisible(b);
10662     ui->q_zipcode->setVisible(b);
10663     ui->q_addr1->setVisible(b);
10664     ui->q_addr2->setVisible(b);
10665     ui->l_q_addr1->setVisible(b);
10666     ui->l_q_addr2->setVisible(b);
10667     ui->q_button->setVisible(b);
10668 }
10669 
qrzInit()10670 void MainWindow::qrzInit() {
10671     log("ENTRY: qrzInit");
10672     QUrlQuery query;
10673     query.addQueryItem("username", m_config.qrzComUn());
10674     query.addQueryItem("password", m_config.qrzComPw());
10675     QUrl url("http://xmldata.qrz.com/xml/");
10676     url.setQuery(query);
10677     QNetworkRequest networkRequest(url);
10678     connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(qrzSetSessionKey(QNetworkReply*)));
10679     networkManager->get(networkRequest);
10680     log("EXIT: qrzInit");
10681 }
10682 
qrzSetSessionKey(QNetworkReply * r)10683 void MainWindow::qrzSetSessionKey(QNetworkReply *r) {
10684     log("ENTRY: qrzSetSessionKey");
10685     disconnect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(qrzSetSessionKey(QNetworkReply*)));
10686     qrzSessionKey = "";
10687     QString error;
10688     if(r->error() == QNetworkReply::NoError) {
10689         QByteArray data = r->read(2048);
10690         QXmlStreamReader reader(data);
10691         while(!reader.atEnd() && !reader.hasError()) {
10692             if(reader.readNext() == QXmlStreamReader::StartElement) {
10693                 if (reader.name() == "Key") {
10694                     qrzSessionKey = reader.readElementText();
10695                 }
10696 
10697                 if(reader.name() == "Error") {
10698                     error = reader.readElementText();
10699                 }
10700             }
10701         }
10702 
10703     } else {
10704         error = r->errorString();
10705     }
10706 
10707     if (!error.isNull()) {
10708         qrzVisible(false);
10709         MessageBox::critical_message (this, tr ("Error connecting to QRZ.COM"), error);
10710     } else {
10711         qrzVisible(true);
10712         if (!qrzPendingLookupCall.isEmpty()) {
10713             qrzLookup(qrzPendingLookupCall);
10714         }
10715     }
10716 
10717     r->deleteLater();
10718     log("EXIT: qrzSetSessionKey");
10719 }
10720 
qrzLookup(QString dxCall)10721 void MainWindow::qrzLookup(QString dxCall) {
10722     if (qrzSessionKey.isNull() || qrzSessionKey.isEmpty() ) return;
10723 
10724     // TODO: if (dxCall.contains("/")
10725     qrzPendingLookupCall = dxCall;
10726 
10727     QUrlQuery query;
10728     query.addQueryItem("s", qrzSessionKey);
10729     query.addQueryItem("callsign", dxCall);
10730     QUrl url("http://xmldata.qrz.com/xml/");
10731     url.setQuery(query);
10732     QNetworkRequest networkRequest(url);
10733     connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(qrzResponseHandler(QNetworkReply*)));
10734     networkManager->get(networkRequest);
10735 }
10736 
qrzResponseHandler(QNetworkReply * r)10737 void MainWindow::qrzResponseHandler(QNetworkReply * r) {
10738     disconnect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(qrzResponseHandler(QNetworkReply*)));
10739     QString error = "";
10740     if(r->error() == QNetworkReply::NoError) {
10741         QByteArray data = r->read(2048);
10742         QXmlStreamReader reader(data);
10743         while(!reader.atEnd() && !reader.hasError()) {
10744             if(reader.readNext() == QXmlStreamReader::StartElement) {
10745                 if ( reader.name() == "fname") {
10746                     ui->q_name->setText(reader.readElementText());
10747                     continue;
10748                 }
10749 
10750                 if ( reader.name() == "name") {
10751                     ui->q_name->insert(" " + reader.readElementText());
10752                     ui->q_name->setCursorPosition(0);
10753                     continue;
10754                 }
10755 
10756                 if ( reader.name() == "addr1") {
10757                     ui->q_addr1->setText(reader.readElementText());
10758                     ui->q_addr1->setCursorPosition(0);
10759                     continue;
10760                 }
10761 
10762                 if ( reader.name() == "addr2") {
10763                     ui->q_addr2->setText(reader.readElementText());
10764                     ui->q_addr2->setCursorPosition(0);
10765                     continue;
10766                 }
10767 
10768                 if ( reader.name() == "grid") {
10769                     ui->ci_grid->setText(reader.readElementText());
10770                     ui->ci_grid->setCursorPosition(0);
10771                     continue;
10772                 }
10773 
10774                 if ( reader.name() == "email") {
10775                     ui->q_email->setText(reader.readElementText());
10776                     ui->q_email->setCursorPosition(0);
10777                     continue;
10778                 }
10779 
10780                 if ( reader.name() == "state") {
10781                     ui->q_state->setText(reader.readElementText());
10782                     ui->q_state->setCursorPosition(0);
10783                     continue;
10784                 }
10785 
10786                 if ( reader.name() == "zip") {
10787                     ui->q_zipcode->setText(reader.readElementText());
10788                     ui->q_zipcode->setCursorPosition(0);
10789                     continue;
10790                 }
10791 
10792                 if ( reader.name() == "Error") {
10793                     error=reader.readElementText();
10794                     continue;
10795                 }
10796 
10797             }
10798         }
10799 
10800         if (error == "Invalid session key" || error == "Session Timeout") {
10801             log("QRZ Error: " + error);
10802             qrzInit();
10803         } else {
10804             if (ui->ci_grid->text().length() > ui->dxGridEntry->text().length() && qrzPendingLookupCall == ui->dxCallEntry->text()) ui->dxGridEntry->setText(ui->ci_grid->text());
10805             qrzPendingLookupCall = "";
10806             ci_gridLookup();
10807         }
10808     }
10809     r->deleteLater();
10810 }
10811 
10812 
on_q_button_clicked()10813 void MainWindow::on_q_button_clicked() {
10814     clearCallInfo();
10815 }
10816 
log(QString s)10817 void MainWindow::log(QString s) {
10818     if (!ui->actionWSJT_Z_Debug->isChecked()) return ;
10819     QString zdebug = "ZDebug.log";
10820 
10821     if (m_config.dbgScreen() || m_config.dbgBoth()) ui->decodedTextBrowser2->appendText(s, "#ff5050");
10822 
10823     if (m_config.dbgFile() || m_config.dbgBoth()) {
10824         QFile f{m_config.writeable_data_dir().absoluteFilePath(zdebug)};
10825         if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) {
10826             QTextStream out(&f);
10827             out << s
10828 #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
10829                 << Qt::endl
10830 #else
10831                 << endl
10832 #endif
10833               ;
10834             f.close();
10835         }
10836     }
10837 
10838 }
10839 
clearCallInfo()10840 void MainWindow::clearCallInfo() {
10841 
10842     ui->ci_dxcall->clear();
10843     ui->ci_grid->clear();
10844     ui->ci_continent->clear();
10845     ui->ci_distance->clear();
10846     ui->ci_bearing->clear();
10847     ui->ci_cqzone->clear();
10848     ui->ci_ituzone->clear();
10849     ui->ci_dxcc->clear();
10850     ui->q_name->clear();
10851     ui->q_email->clear();
10852     ui->q_state->clear();
10853     ui->q_zipcode->clear();
10854     ui->q_addr1->clear();
10855     ui->q_addr2->clear();
10856 
10857     QFont bold = ui->ci_continent->font();
10858     bold.setWeight(QFont::Normal);
10859 
10860     ui->ci_continent->setFont(bold);
10861     ui->ci_dxcc->setFont(bold);
10862     ui->ci_cqzone->setFont(bold);
10863     ui->ci_ituzone->setFont(bold);
10864     ui->ci_dxcall->setFont(bold);
10865 }
10866 
on_cb_filtering_toggled(bool b)10867 void MainWindow::on_cb_filtering_toggled(bool b) {
10868     if (b) {
10869        ui->cb_filtering->setStyleSheet("background-color: #00aa00;");
10870     } else {
10871        ui->cb_filtering->setStyleSheet("");
10872     }
10873 }
10874 
on_cb_specialMode_currentIndexChanged(int index)10875 void MainWindow::on_cb_specialMode_currentIndexChanged (int index)
10876 {
10877     switch (index) {
10878     case 0:
10879         m_config.setSpecial_None();
10880         ui->cb_specialMode->setStyleSheet("");
10881         break;
10882     case 1:
10883         m_config.setSpecial_Fox();
10884         ui->cb_specialMode->setStyleSheet("background-color: #ff0000;");
10885         break;
10886     case 2:
10887         m_config.setSpecial_Hound();
10888         ui->cb_specialMode->setStyleSheet("background-color: #ff0000;");
10889         break;
10890     default:
10891         break;
10892     }
10893     on_actionFT8_triggered();
10894 }
10895 
on_ci_pb_lookup_clicked()10896 void MainWindow::on_ci_pb_lookup_clicked() {
10897     dxLookup(ui->ci_dxcall->text(), "");
10898 }
10899 
ci_gridLookup()10900 void MainWindow::ci_gridLookup() {
10901     QString dxGrid = ui->ci_grid->text();
10902     if (dxGrid.length() < 4) return;
10903 
10904     qint64 nsec = (QDateTime::currentMSecsSinceEpoch()/1000) % 86400;
10905     double utch=nsec/3600.0;
10906     int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter;
10907     azdist_(const_cast <char *> ((m_config.my_grid () + "      ").left (6).toLatin1().constData()),
10908             const_cast <char *> ((dxGrid + "      ").left (6).toLatin1().constData()),&utch,
10909             &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6);
10910     QString t;
10911     int nd=nDkm;
10912     if(m_config.miles()) nd=nDmiles;
10913     t = QString::number(nd);
10914     if(m_config.miles()) t += " mi";
10915     if(!m_config.miles()) t += " km";
10916 
10917 
10918     ui->ci_distance->setText(t);
10919     ui->ci_bearing->setText(QString::number(nAz));
10920 }
10921 
on_cb_autoModeSwitch_toggled(bool b)10922 void MainWindow::on_cb_autoModeSwitch_toggled(bool b) {
10923     if (b) {
10924         resetAutoSwitch();
10925         ui->cbHoldTxFreq->setChecked(true);
10926     } else {
10927         ui->le_autoCallLeft->setText("");
10928         ui->le_autoCQLeft->setText("");
10929     }
10930 }
10931 
switchBands()10932 void MainWindow::switchBands() {
10933     if (!ui->cb_bandHopper->isChecked() || (!ui->cbAutoCall->isChecked() && !ui->cbAutoCQ->isChecked())) return;
10934     log("switchBands: ENTRY");
10935 
10936     QString bhText = ui->pte_bandHopper->toPlainText();
10937     QStringList bhList = bhText.split(QRegExp("[\r\n]"),SkipEmptyParts);
10938     int now = QDateTime::currentDateTimeUtc().toString("hh").toInt();
10939     int targeth = -1;
10940     QString target= "";
10941     for ( const auto& i : bhList  ) {
10942        int h = i.left(2).toInt();
10943        if (h <= now && h > targeth) {
10944            targeth = h;
10945            target = i;
10946        }
10947     }
10948 
10949     if (targeth == -1) {
10950         for ( const auto& i : bhList  ) {
10951            int h = i.left(2).toInt();
10952            if (h > targeth) {
10953                targeth = h;
10954                target = i;
10955            }
10956         }
10957     }
10958 
10959     QStringList bands = target.remove(0,target.indexOf(":")+1).split(",");
10960     QString newBand = "";
10961 
10962    int idx = bands.indexOf(m_currentBand);
10963    if (idx >= 0 && idx < bands.size()-1) {
10964         newBand = bands.at(idx+1);
10965    } else {
10966         newBand = bands.at(0);
10967    }
10968 
10969     if (newBand == "" || newBand == m_currentBand) return;
10970 
10971     ui->bandComboBox->setCurrentText (newBand);
10972     m_wideGraph->setRxBand (newBand);
10973     m_lastBand = newBand;
10974     Mode mode;
10975     if (m_mode == "FT8") mode = Mode::FT8;
10976     else if (m_mode == "FT4") mode = Mode::FT4;
10977     else return;
10978 
10979     m_config.frequencies ()->filter (m_config.region (), mode);
10980     auto const& row = m_config.frequencies ()->best_working_frequency (newBand);
10981     if (row >= 0) {
10982         ui->stopTxButton->click ();
10983         ui->bandComboBox->setCurrentIndex (row);
10984         on_bandComboBox_activated (row);
10985         m_priorityCall = QString();
10986         clearDX();
10987         on_btn_clearIgnore_clicked();
10988         if (m_config.autoTune()) {
10989             ui->tuneButton->setChecked (true);
10990             on_tuneButton_clicked (true);
10991             tuneButtonTimer.start(4000);
10992         }
10993         if (ui->cbAutoCQ->isChecked()) auto_tx_mode(true);
10994 
10995         log("switchBands: Changed band to: " + newBand);
10996     }
10997 
10998     log("switchBands: EXIT");
10999 
11000 }
11001 
ZMessage()11002 void MainWindow::ZMessage ()
11003 {
11004 
11005     QString message = "Please visit our <a href='https://groups.io/g/WSJT-Z/topics'>groups.io</a> forum if you need help with Z! <br /><br />"
11006                         "Latest versions can be downloaded from <a href='https://sourceforge.net/projects/wsjt-z/'>sourceforge</a> <br /><br />"
11007                         "<a href='https://sourceforge.net/projects/wsjt-z/files/Documentation/WSJT-Z%20User%20Manual.pdf/download'>WSJT-Z Documentation</a> ";
11008 
11009     MessageBox::information_message(this, message);
11010 }
11011 
on_actionAbout_WSJT_Z_triggered()11012 void MainWindow::on_actionAbout_WSJT_Z_triggered ()
11013 {
11014     ZMessage ();
11015 }
11016 
ZProcess()11017 void MainWindow::ZProcess ()
11018 {
11019     log("ZProcess: ENTRY");
11020     if (m_transmitting)
11021     {
11022         m_priorityCall = QString();
11023         log("ZProcess: EXIT (Transmitting)");
11024         return;
11025     }
11026 
11027     if (m_config.ignoreListReset() != 0 && m_ignoreListReset.secsTo(QDateTime::currentDateTime()) / 60 >= m_config.ignoreListReset()) {
11028         log("ZProcess: Clearing ignore list");
11029         on_btn_clearIgnore_clicked();
11030     }
11031 
11032     if (ui->cbAutoCall->isChecked() && ui->txrb6->isChecked()) {
11033         clearDX();
11034     }
11035 
11036     log("ZProcess: autoButton: " + QString::number(ui->autoButton->isChecked()));
11037     log("ZProcess: cbAutoCall: " + QString::number(ui->cbAutoCall->isChecked()));
11038     log("ZProcess: cbnAutoCQ: " + QString::number(ui->cbAutoCQ->isChecked()));
11039     log("ZProcess: m_priorityCall: " + m_priorityCall);
11040     log("ZProcess: dxCallEntry: " + ui->dxCallEntry->text());
11041     log("ZProcess: m_hiscall: " + m_hisCall);
11042     log("ZProcess: m_lastcall: " + m_lastCall);
11043 
11044     if (!ui->autoButton->isChecked() && (ui->cbAutoCall->isChecked() || ui->cb_autoCallNext->isChecked())
11045             && !m_priorityCall.isNull() && !m_priorityCall.isEmpty()
11046             && m_lastCall != m_priorityCall && (ui->dxCallEntry->text().isEmpty() || ui->dxCallEntry->text() == m_priorityCall)) {
11047         tx_watchdog(false);
11048         log("Next call: " + m_priorityCall);
11049         m_nextCall = m_priorityCall;
11050         m_nextGrid = m_prioGrid;
11051         dxLookup(m_nextCall, m_prioGrid);
11052         ui->rptSpinBox->setValue(m_nextRpt.toInt());
11053         ui->txFirstCheckBox->setChecked(m_prioTxFirst);
11054         ui->RxFreqSpinBox->setValue(m_prioFreq);
11055         if (!ui->cbHoldTxFreq->isChecked()) ui->TxFreqSpinBox->setValue(m_prioFreq);
11056         useNextCall();
11057         on_txb1_clicked();
11058         auto_tx_mode(true);
11059         if (m_config.highlightDX()) ui->decodedTextBrowser->highlight_callsign(m_priorityCall, QColor(255,0,0), QColor(255,255,255), true);
11060 
11061     }
11062 
11063 
11064     if (m_QSOProgress == CALLING) {
11065         log("ZProcess: m_QSOProgress = CALLING");
11066 
11067         if (ui->cbAutoCall->isChecked() || ui->cbAutoCQ->isChecked()) {
11068 
11069                 if (ui->cbAutoCall->isChecked()) {
11070                     int l = ui->le_autoCallLeft->text().toInt();
11071                     if (l > 1){
11072                         ui->le_autoCallLeft->setText(QString::number(l-1));
11073                     } else {
11074                         resetAutoSwitch();
11075                         if (ui->cb_autoModeSwitch->isChecked()) {
11076                             m_autoModeSwitch = true;
11077                             ui->cbAutoCall->setChecked(false);
11078                             ui->cbAutoCQ->setChecked(true);
11079                             m_autoModeSwitch = false;
11080                             if  (!m_TxFirstLock) {
11081                                     QDateTime now {QDateTime::currentDateTimeUtc()};
11082                                     int n=fmod(double(now.time().second()),m_TRperiod);
11083                                     int periodTotal = now.time().second() - n + m_TRperiod;
11084                                     bool txf = !(fmod(periodTotal/m_TRperiod, 2) == 0);
11085                                     ui->txFirstCheckBox->setChecked(txf);
11086                             }
11087                             log("ZProcess: Switched to AutoCQ");
11088                         } else {
11089                             switchBands();
11090                         }
11091                     }
11092                 } else if (ui->cbAutoCQ->isChecked()) {
11093                     int l = ui->le_autoCQLeft->text().toInt();
11094                     if (l > 1){
11095                         ui->le_autoCQLeft->setText(QString::number(l-1));
11096                     } else {
11097                           resetAutoSwitch();
11098                           switchBands();
11099                           if (ui->cb_autoModeSwitch->isChecked()) {
11100                               m_autoModeSwitch = true;
11101                               ui->cbAutoCQ->setChecked(false);
11102                               ui->cbAutoCall->setChecked(true);
11103                               m_autoModeSwitch = false;
11104                               log("ZProcess: Switched to AutoCall");
11105                           }
11106                     }
11107                 }
11108 
11109         }
11110     }
11111 
11112     m_maxDistance = 0 ;
11113     m_maxSignal = -30;
11114     m_priorityCall = QString();
11115     m_beeped = false;
11116     log("ZProcess: EXIT");
11117 }
11118 
on_pb_WDReset_clicked()11119 void MainWindow::on_pb_WDReset_clicked() {
11120     tx_watchdog(false);
11121 }
11122 
resetAutoSwitch()11123 void MainWindow::resetAutoSwitch() {
11124         ui->le_autoCallLeft->setText(QString::number(ui->sb_autoCallCount->value()));
11125         ui->le_autoCQLeft->setText(QString::number(ui->sb_autoCQCount->value()));
11126         m_priorityCall = QString();
11127 }
11128 
watchdog()11129 int MainWindow::watchdog() {
11130     if (m_config.wd_Timer()) {
11131         if (m_mode == "FT8") return m_config.wd_FT8();
11132         if (m_mode == "FT4") return m_config.wd_FT4();
11133     }
11134     return m_config.watchdog();
11135 }
11136 
on_actionDark_mode_triggered()11137 void MainWindow::on_actionDark_mode_triggered() {
11138     if (ui->actionDark_mode->isChecked()) {
11139         log("Stylesheet: DARK");
11140         QFile file(":/qdarkstyle/style.qss");
11141         file.open(QFile::ReadOnly);
11142         QString styleSheet = QLatin1String(file.readAll());
11143         qApp->setStyleSheet(styleSheet);
11144         labAz.setStyleSheet ("QLabel{background-color: #005555; padding-left: 10px; padding-right: 10px}");
11145         qso_count.setStyleSheet ("QLabel{background-color: #005555; padding-left: 10px; padding-right: 10px}");
11146         initialize_fonts();
11147     } else {
11148         log("Stylesheet: LIGHT");
11149         qApp->setStyleSheet("");
11150         labAz.setStyleSheet ("QLabel{background-color: #00bbbb; padding-left: 10px; padding-right: 10px}");
11151         qso_count.setStyleSheet ("QLabel{background-color: #00bbbb; padding-left: 10px; padding-right: 10px}");
11152         initialize_fonts();
11153     }
11154 }
11155 
stateLookup(QString callsign)11156 QString MainWindow::stateLookup(QString callsign) {
11157     auto const& looked_up = m_logBook.countries ()->lookup (callsign);
11158     QString country = looked_up.entity_name;
11159     if  (country != "United States")  return "";
11160 
11161     auto const file_name = "USState.db";
11162     QString path;
11163     QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::DataLocation)};
11164     path = m_config.data_dir ().absoluteFilePath (file_name);
11165 
11166     QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
11167     db.setDatabaseName(path);
11168 
11169     if (!db.open())
11170        {
11171           qDebug() << "Error: connection with database failed";
11172           log("DB: connection failed");
11173        }
11174        else
11175        {
11176           qDebug() << "Database: connection ok";
11177        }
11178 
11179     QString state =  "";
11180     QSqlQuery q(db);
11181     q.prepare("SELECT state FROM USState WHERE call = (:call)");
11182     q.bindValue(":call", callsign);
11183     if (q.exec())
11184     {
11185        if (q.next())
11186        {
11187           state = q.value(0).toString();
11188        }
11189     }
11190 
11191     db.close();
11192     return state;
11193 }
11194 
on_actionUnfiltered_View_triggered()11195 void MainWindow::on_actionUnfiltered_View_triggered() {
11196     if (m_unfilteredView && m_unfilteredView->isVisible()) {
11197         m_unfilteredView->hide();
11198     } else {
11199         m_unfilteredView.reset (new UnfilteredView {});
11200         connect (this, &MainWindow::finished, m_unfilteredView.data (), &UnfilteredView::close);
11201         m_unfilteredView->restoreGeometry(m_unfilteredViewGeometry);
11202         m_unfilteredView->showNormal ();
11203         m_unfilteredView->setFont(m_config.decoded_text_font ());
11204         m_unfilteredView->raise ();
11205         m_unfilteredView->activateWindow ();
11206     }
11207 }
11208 
leftJustifyAppendage(QString message,QString appendage)11209 QString MainWindow::leftJustifyAppendage (QString message, QString appendage)
11210 {
11211   if (appendage.size ())
11212     {
11213       int space_count {48 - message.size ()};
11214       if (space_count > 0) {
11215         message += QString {space_count, QChar {' '}};
11216       }
11217       message += appendage;
11218     }
11219   return message;
11220 }
11221 
clearRXWindows()11222 void MainWindow::clearRXWindows() {
11223     ui->decodedTextBrowser->erase();
11224     ui->decodedTextBrowser2->erase();
11225     if (m_unfilteredView) m_unfilteredView->erase();
11226 
11227 }
11228 
on_actionPSKReporter_triggered()11229 void MainWindow::on_actionPSKReporter_triggered() {
11230     if (m_pskReporterView && m_pskReporterView->isVisible()) {
11231         m_pskReporterView->hide();
11232     } else {
11233         m_pskReporterView.reset (new PSKReporterWidget {nullptr, &m_config, &m_logBook});
11234         connect (this, &MainWindow::finished, m_pskReporterView.data (), &UnfilteredView::close);
11235         m_pskReporterView->restoreGeometry(m_pskReporterViewGeometry);
11236         m_pskReporterView->showNormal ();
11237         m_pskReporterView->setFont(m_config.decoded_text_font ());
11238         m_pskReporterView->raise ();
11239         m_pskReporterView->activateWindow ();
11240     }
11241 }
11242 
updateQsoCounter(bool increment)11243 void MainWindow::updateQsoCounter(bool increment) {
11244     if (increment) {
11245         qso_total++;
11246         qso_new++;
11247     }
11248     QString qc = "%1 New / %2 Total";
11249     qso_count.setText(qc.arg(QString::number(qso_new), QString::number(qso_total)));
11250 }
11251 
on_txFirstCheckBox_toggled()11252 void MainWindow::on_txFirstCheckBox_toggled() {
11253     qint64 ms=QDateTime::currentMSecsSinceEpoch();
11254 
11255     if((ms-m_msTxFirst)<500) {
11256         if  (m_TxFirstLock) {
11257             ui->txFirstCheckBox->setStyleSheet("");
11258             m_TxFirstLock =  false;
11259         } else  {
11260             m_TxFirstLock =  true;
11261             ui->txFirstCheckBox->setStyleSheet("background-color: #ff0000; color: #ffffff;");
11262         }
11263     }
11264 
11265     m_msTxFirst = ms;
11266 
11267 }
11268 
11269