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