1 // ----------------------------------------------------------------------------
2 // logsupport.cxx
3 //
4 // Copyright (C) 2006-2010
5 // Dave Freese, W1HKJ
6 // Copyright (C) 2008-2009
7 // Stelios Bounanos, M0GLD
8 //
9 // This file is part of fldigi.
10 //
11 // Fldigi is free software: you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation, either version 3 of the License, or
14 // (at your option) any later version.
15 //
16 // Fldigi is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with fldigi. If not, see <http://www.gnu.org/licenses/>.
23 // ----------------------------------------------------------------------------
24
25 #include <config.h>
26
27 #include <string>
28 #include <cstring>
29 #include <cstdlib>
30 #include <ctime>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <stdio.h>
34 #include <fstream>
35 #include <vector>
36
37 #include "main.h"
38 #include "trx.h"
39 #include "debug.h"
40 #include "macros.h"
41 #include "status.h"
42 #include "date.h"
43
44 #include "logger.h"
45 #include "n3fjp_logger.h"
46 #include "adif_io.h"
47 #include "textio.h"
48 #include "logbook.h"
49 #include "rigsupport.h"
50 #include "fd_logger.h"
51
52 #include "fl_digi.h"
53 #include "fileselect.h"
54 #include "configuration.h"
55 #include "main.h"
56 #include "locator.h"
57 #include "icons.h"
58 #include "gettext.h"
59 #include "qrunner.h"
60 #include "flmisc.h"
61
62 #include "network.h"
63
64 #include "timeops.h"
65
66 #include "strutil.h"
67
68 #include <FL/filename.H>
69 #include <FL/fl_ask.H>
70 #include <FL/Fl_Double_Window.H>
71 #include <FL/Fl_Text_Display.H>
72 #include <FL/Fl_Text_Editor.H>
73 #include <FL/Fl_Button.H>
74
75 using namespace std;
76
77 extern vector<int> lotw_recs_sent;
78
79 cQsoDb qsodb;
80 cAdifIO adifFile;
81 cTextFile txtFile;
82
83 string logbook_filename;
84
85 enum sorttype {NONE, SORTCALL, SORTDATE, SORTFREQ, SORTMODE};
86 sorttype lastsort = SORTDATE;
87 bool callfwd = true;
88 bool modefwd = true;
89 bool freqfwd = true;
90
91 int editNbr = 0;
92
93 void restore_sort();
94 void addBrowserRow(cQsoRec *, int);
95 void adjustBrowser(bool keep_pos = false);
96
97 // convert to and from "00:00:00" <=> "000000"
timeview(const char * s)98 const char *timeview(const char *s)
99 {
100 static char ds[9];
101 int len = strlen(s);
102 strcpy(ds, "00:00:00");
103 if (len < 4)
104 return ds;
105 ds[0] = s[0]; ds[1] = s[1];
106 ds[3] = s[2]; ds[4] = s[3];
107 if (len < 6)
108 return ds;
109 ds[6] = s[4];
110 ds[7] = s[5];
111 return ds;
112 }
113
timestring(const char * s)114 const char *timestring(const char *s)
115 {
116 static char ds[7];
117 int len = strlen(s);
118 if (len <= 4) return s;
119 ds[0] = s[0]; ds[1] = s[1];
120 ds[2] = s[3]; ds[3] = s[4];
121 if (len < 8) {
122 ds[4] = ds[5] = '0';
123 ds[6] = 0;
124 return ds;
125 }
126 ds[4] = s[6];
127 ds[5] = s[7];
128 ds[6] = 0;
129 return ds;
130 }
131
timeview4(const char * s)132 const char *timeview4(const char *s)
133 {
134 static char ds[6];
135 int len = strlen(s);
136 strcpy(ds, "00:00");
137 if (len < 5)
138 return ds;
139 ds[0] = s[0]; ds[1] = s[1];
140 ds[3] = s[2]; ds[4] = s[3];
141 return ds;
142 }
143
time4(const char * s)144 const char *time4(const char *s)
145 {
146 static char ds[5];
147 int len = strlen(s);
148 if (len <= 4)
149 return ds;
150 memset(ds, 0, 5);
151 strncpy(ds, s, 4);
152 return ds;
153 }
154
Export_CSV()155 void Export_CSV()
156 {
157 if (chkExportBrowser->nchecked() == 0) return;
158
159 cQsoRec *rec;
160
161 string title = _("Export to CSV file");
162 string filters = "CSV\t*.csv";
163 #ifdef __APPLE__
164 filters.append("\n");
165 #endif
166 const char* p = FSEL::saveas( title.c_str(), filters.c_str(), "export.csv");
167
168 if (!p) return;
169 if (!*p) return;
170
171 for (int i = 0; i < chkExportBrowser->FLTK_nitems(); i++) {
172 if (chkExportBrowser->checked(i + 1)) {
173 rec = qsodb.getRec(i);
174 rec->putField(EXPORT, "E");
175 qsodb.qsoUpdRec (i, rec);
176 }
177 }
178 string sp = p;
179 if (sp.find(".csv") == string::npos) sp.append(".csv");
180 txtFile.writeCSVFile(sp.c_str(), &qsodb);
181 }
182
Export_TXT()183 void Export_TXT()
184 {
185 if (chkExportBrowser->nchecked() == 0) return;
186
187 cQsoRec *rec;
188 string title = _("Export to fixed field text file");
189 string filters = "TEXT\t*.txt";
190 #ifdef __APPLE__
191 filters.append("\n");
192 #endif
193 const char* p = FSEL::saveas( title.c_str(), filters.c_str(), "export.txt");
194
195 if (!p) return;
196 if (!*p) return;
197
198 for (int i = 0; i < chkExportBrowser->FLTK_nitems(); i++) {
199 if (chkExportBrowser->checked(i + 1)) {
200 rec = qsodb.getRec(i);
201 rec->putField(EXPORT, "E");
202 qsodb.qsoUpdRec (i, rec);
203 }
204 }
205 string sp = p;
206 if (sp.find(".txt") == string::npos) sp.append(".txt");
207 txtFile.writeTXTFile(p, &qsodb);
208 }
209
Export_ADIF()210 void Export_ADIF()
211 {
212 if (chkExportBrowser->nchecked() == 0) return;
213
214 cQsoRec *rec;
215
216 string title = _("Export to ADIF file");
217 string filters;
218 string defname;
219 filters.assign("ADIF\t*.").append(ADIF_SUFFIX);
220 #ifdef __APPLE__
221 filters.append("\n");
222 #endif
223 defname.assign("export.").append(ADIF_SUFFIX);
224 const char* p = FSEL::saveas( title.c_str(), filters.c_str(), defname.c_str());
225
226 if (!p) return;
227 if (!*p) return;
228
229 for (int i = 0; i < chkExportBrowser->FLTK_nitems(); i++) {
230 if (chkExportBrowser->checked(i + 1)) {
231 rec = qsodb.getRec(i);
232 rec->putField(EXPORT, "E");
233 qsodb.qsoUpdRec (i, rec);
234 }
235 }
236 string sp = p;
237 string temp = ".";
238 temp.append(ADIF_SUFFIX);
239
240 if (sp.find(temp) == string::npos) sp.append(temp);
241 adifFile.writeFile (sp.c_str(), &qsodb);
242 }
243
244 // refresh_logbook_dialog ONLY called as an Fl::awake process
245 // insures that logbook dialog LOTWSDATE field is updated by
246 // FLTK UI thread.
247 // called by Export_LOTW() and saveRecord()
248
249 static string lotwsdate;
refresh_logbook_dialog(void *)250 void refresh_logbook_dialog(void *)
251 {
252 inpLOTWsentdate_log->value(lotwsdate.c_str());
253 inpLOTWsentdate_log->redraw();
254 }
255
Export_LOTW()256 void Export_LOTW()
257 {
258 if (chkExportBrowser->nchecked() == 0) return;
259
260 cQsoRec *rec;
261
262 if (str_lotw.empty())
263 str_lotw = "Fldigi LoTW upload file\n<ADIF_VER:5>2.2.7\n<EOH>\n";
264 string adifrec;
265
266 for (int i = 0; i < chkExportBrowser->FLTK_nitems(); i++) {
267 if (chkExportBrowser->checked(i + 1)) {
268 rec = qsodb.getRec(i);
269 rec->putField(EXPORT, "E");
270 rec->putField(LOTWSDATE, zdate());
271 qsodb.qsoUpdRec (i, rec);
272 lotw_recs_sent.push_back(i);
273 if (i == editNbr) {
274 lotwsdate = rec->getField(LOTWSDATE);
275 Fl::awake(refresh_logbook_dialog);
276 }
277 adifrec = lotw_rec(*rec);
278 if (adifrec.empty()) {
279 LOG_INFO("%s", "Invalid LOTW record");
280 } else
281 str_lotw.append(adifrec);
282 }
283 }
284 }
285
286 Fl_Double_Window *lotw_review_dialog = 0;
287 static Fl_Text_Buffer *buff = 0;
288 static Fl_Text_Editor *disp = 0;
289 static Fl_Button *lotw_close_review = 0;
290 static Fl_Button *lotw_save_review = 0;
291 static Fl_Button *lotw_clear_review = 0;
292
cb_lotw_close_review(Fl_Button *,void *)293 void cb_lotw_close_review(Fl_Button *, void *)
294 {
295 lotw_review_dialog->hide();
296 delete lotw_review_dialog;
297 lotw_review_dialog = 0;
298 lotw_close_review = 0;
299 buff = 0;
300 disp = 0;
301 }
302
cb_lotw_save_review(Fl_Button *,void *)303 void cb_lotw_save_review(Fl_Button *, void *)
304 {
305 str_lotw = buff->text();
306 }
307
cb_lotw_clear_review(Fl_Button *,void *)308 void cb_lotw_clear_review(Fl_Button *, void *)
309 {
310 buff->text("");
311 }
312
cb_review_lotw()313 void cb_review_lotw()
314 {
315 if (str_lotw.empty()) return;
316
317 if (!lotw_review_dialog) {
318 lotw_review_dialog = new Fl_Double_Window(50,50, 640, 400, _("LoTW Review"));
319 lotw_review_dialog->begin();
320
321 buff = new Fl_Text_Buffer();
322 disp = new Fl_Text_Editor(4, 4, 632, 364);
323 disp->textfont(FL_SCREEN);
324 disp->buffer(buff); // attach text buffer to display widget
325 lotw_close_review = new Fl_Button(576, 372, 60, 24, _("Close"));
326 lotw_close_review->callback((Fl_Callback *)cb_lotw_close_review);
327
328 lotw_clear_review = new Fl_Button(4, 372, 60, 24, _("Clear"));
329 lotw_clear_review->callback((Fl_Callback *)cb_lotw_clear_review);
330
331 lotw_save_review = new Fl_Button(lotw_review_dialog->w()/2-30, 372, 60, 24, _("Save"));
332 lotw_save_review->callback((Fl_Callback *)cb_lotw_save_review);
333
334 lotw_review_dialog->end();
335 }
336 buff->text(str_lotw.c_str());
337
338 lotw_review_dialog->show();
339 }
340
cb_send_lotw()341 void cb_send_lotw()
342 {
343 send_to_lotw(NULL);
344 }
345
346 static savetype export_to = ADIF;
347
Export_log()348 void Export_log()
349 {
350 if (export_to == LOTW) Export_LOTW();
351 else if (export_to == ADIF) Export_ADIF();
352 else if (export_to == CSV) Export_CSV();
353 else Export_TXT();
354 }
355
saveLogbook(bool force)356 void saveLogbook(bool force)
357 {
358 if (!force && !qsodb.isdirty()) return;
359 if (!force && progdefaults.NagMe)
360 if (!fl_choice2(_("Save changed Logbook?"), _("No"), _("Yes"), NULL))
361 return;
362
363 qsodb.isdirty(0);
364 restore_sort();
365
366 if (force)
367 adifFile.writeLog (logbook_filename.c_str(), &qsodb, true);
368 else
369 adifFile.writeLog (logbook_filename.c_str(), &qsodb);
370
371 }
372
373 static void dxcc_entity_cache_clear(void);
374 static void dxcc_entity_cache_add(cQsoRec* r);
375 static void dxcc_entity_cache_rm(cQsoRec* r);
376 static void dxcc_entity_cache_add(cQsoDb& db);
377
cb_mnuNewLogbook(Fl_Menu_ * m,void * d)378 void cb_mnuNewLogbook(Fl_Menu_* m, void* d){
379 saveLogbook(true);
380
381 string title = _("Create new logbook file");
382 string filter;
383 filter.assign("ADIF\t*.").append(ADIF_SUFFIX);
384 #ifdef __APPLE__
385 filter.append("\n");
386 #endif
387
388 logbook_filename = LogsDir;
389 logbook_filename.append("newlog.").append(ADIF_SUFFIX);
390
391 const char* p = FSEL::saveas( title.c_str(), filter.c_str(), logbook_filename.c_str());
392 if (!p) return;
393 if (!*p) return;
394
395 string temp = p;
396 string suffix = ".";
397 suffix.append(ADIF_SUFFIX);
398 if (temp.find(suffix) == string::npos) temp.append(suffix);
399
400 FILE *testopen = fl_fopen(temp.c_str(), "r");
401 if (testopen) {
402 string warn = logbook_filename;
403 int ans = fl_choice2(
404 _("%s exists, overwrite?"),
405 _("No"), _("Yes"), NULL,
406 temp.c_str());
407 if (!ans) return;
408 fclose(testopen);
409 }
410
411 progdefaults.logbookfilename = logbook_filename = temp;
412
413 dlgLogbook->label(fl_filename_name(logbook_filename.c_str()));
414 txtLogFile->value(logbook_filename.c_str());
415 txtLogFile->redraw();
416
417 progdefaults.changed = true;
418 qsodb.deleteRecs();
419 dxcc_entity_cache_clear();
420 wBrowser->clear();
421 clearRecord();
422 qsodb.isdirty(1);
423 saveLogbook();
424 }
425
adif_read_OK()426 void adif_read_OK()
427 {
428 if (qsodb.nbrRecs() == 0)
429 adifFile.writeFile(logbook_filename.c_str(), &qsodb);
430 dxcc_entity_cache_clear();
431 dxcc_entity_cache_add(qsodb);
432 qsodb.isdirty(0);
433 restore_sort();
434 activateButtons();
435 loadBrowser();
436 }
437
cb_mnuOpenLogbook(Fl_Menu_ * m,void * d)438 void cb_mnuOpenLogbook(Fl_Menu_* m, void* d)
439 {
440 string title = _("Open logbook file");
441 string filter;
442 filter.assign("ADIF file\t*.{adi,adif}");
443 #ifdef __APPLE__
444 filter.append("\n");
445 #endif
446
447 std::string deffilename = LogsDir;
448 deffilename.append(fl_filename_name(logbook_filename.c_str()));
449
450 const char* p = FSEL::select( title.c_str(), filter.c_str(), deffilename.c_str());
451 if (!p) return;
452 if (!*p) return;
453
454 saveLogbook(true);
455
456 qsodb.deleteRecs();
457
458 logbook_filename = p;
459 progdefaults.logbookfilename = logbook_filename;
460 progdefaults.changed = true;
461
462 adifFile.readFile (logbook_filename.c_str(), &qsodb);
463 dlgLogbook->label(fl_filename_name(logbook_filename.c_str()));
464
465 txtLogFile->value(logbook_filename.c_str());
466 txtLogFile->redraw();
467
468 qsodb.isdirty(0);
469
470 }
471
cb_mnuSaveLogbook(Fl_Menu_ * m,void * d)472 void cb_mnuSaveLogbook(Fl_Menu_*m, void* d) {
473 string title = _("Save logbook file");
474 string filter;
475 filter.assign("ADIF\t*.").append(ADIF_SUFFIX);
476 #ifdef __APPLE__
477 filter.append("\n");
478 #endif
479 std::string deffilename = LogsDir;
480 deffilename.append(fl_filename_name(logbook_filename.c_str()));
481
482 const char* p = FSEL::saveas( title.c_str(), filter.c_str(), deffilename.c_str());
483
484 if (!p) return;
485 if (!*p) return;
486
487 logbook_filename = p;
488 string temp = ".";
489 temp.append(ADIF_SUFFIX);
490 if (logbook_filename.find(temp) == string::npos)
491 logbook_filename.append(temp);
492
493 progdefaults.logbookfilename = logbook_filename;
494 progdefaults.changed = true;
495
496 dlgLogbook->label(fl_filename_name(logbook_filename.c_str()));
497 txtLogFile->value(logbook_filename.c_str());
498 txtLogFile->redraw();
499
500 // cQsoDb::reverse = false;
501 // qsodb.SortByDate(progdefaults.sort_date_time_off);
502
503 qsodb.isdirty(0);
504 restore_sort();
505 adifFile.writeLog (logbook_filename.c_str(), &qsodb);
506
507 }
508
509 //======================================================================
510 // separate thread for performing the database merger
511 //
512 // thread 'merge_thread' is instantiated for a database file merger
513 // either on failure or successful merger the thread signals the main
514 // UI thread to close the merge_thread and release all of it's resources
515 //
516 // merge_thread is not re-entrant. Only a single instance of the thread
517 // is allowed.
518 //
519 // the user will be notified if an attempt is made to start a new merger
520 // while one is already in progress.
521 //
522 //======================================================================
523
524 pthread_t* MERGE_thread = 0;
525 pthread_mutex_t MERGE_mutex = PTHREAD_MUTEX_INITIALIZER;
526
527 static string mrg_fname;
528 static string disptxt;
529 static bool abort_merger;
530 static int num_merge_recs;
531 static float read_secs;
532
merge_announce_1(void *)533 static void merge_announce_1(void *)
534 {
535 static char announce[500];
536 snprintf(announce, sizeof(announce),
537 "Merging records:\n File: %s\n", mrg_fname.c_str());
538 ReceiveText->addstr(announce);
539 ReceiveText->redraw();
540 Fl::flush();
541 }
542
merge_announce_2(void *)543 static void merge_announce_2(void *)
544 {
545 static char announce[500];
546 snprintf(announce, sizeof(announce),
547 " Read %d records in %4.1f seconds\n Merging ... please wait",
548 num_merge_recs, read_secs );
549 ReceiveText->addstr(announce);
550 ReceiveText->redraw();
551 Fl::flush();
552 }
553
close_MERGE_thread(void *)554 void close_MERGE_thread (void *)
555 {
556 ENSURE_THREAD(FLMAIN_TID);
557
558 if (!MERGE_thread) return;
559
560 pthread_mutex_lock(&MERGE_mutex);
561 abort_merger = true;
562 pthread_mutex_unlock(&MERGE_mutex);
563
564 pthread_join(*MERGE_thread, NULL);
565
566 delete MERGE_thread;
567
568 MERGE_thread = 0;
569 abort_merger = false;
570
571 qsodb.isdirty(1);
572 saveLogbook(true); // force the save independent of user settings
573 loadBrowser();
574
575 ReceiveText->addstr(disptxt.c_str());
576 ReceiveText->redraw();
577
578 }
579
580 // for testing only
581 /*
582 static char recfield[200];
583
584 static std::string adif_record(cQsoRec *rec)
585 {
586 static std::string record;
587 static std::string sFld;
588 record.clear();
589 for (int j = 0; fields[j].type != NUMFIELDS; j++) {
590 if (strcmp(fields[j].name,"MYXCHG") == 0) continue;
591 if (strcmp(fields[j].name,"XCHG1") == 0) continue;
592 sFld = rec->getField(fields[j].type);
593 if (!sFld.empty()) {
594 snprintf(recfield, sizeof(recfield),
595 "<%s:%d>",
596 fields[j].name,
597 sFld.length());
598 record.append(recfield).append(sFld);
599 }
600 }
601 record.append("<EOR>\n");
602 return record;
603 }
604
605 static void writeLog(std::string fname, cQsoDb *db)
606 {
607 FILE *adiFile = fl_fopen (fname.c_str(), "wb");
608
609 cQsoRec *rec;
610
611 std::string records;
612
613 records.clear();
614 for (int i = 0; i < db->nbrRecs(); i++) {
615 rec = db->getRec(i);
616 records.append(adif_record(rec));
617 }
618
619 fprintf (adiFile, "%s\n<EOH>\n", fl_filename_name(fname.c_str()));
620 fprintf (adiFile, "%s", records.c_str());
621
622 fclose (adiFile);
623
624 return;
625 }
626 */
627
merge_thread(void * args)628 static void *merge_thread(void *args)
629 {
630 SET_THREAD_ID(ADIF_MERGE_TID);
631
632 static char msg1[200];
633
634 sorttype orig_sort = lastsort;
635
636 int orig_reverse = cQsoDb::reverse;
637
638 cQsoDb::reverse = false;
639
640 cQsoDb *db = &qsodb;
641 cQsoDb *mrgdb = new cQsoDb;
642 cQsoDb *merge_dups = new cQsoDb;
643 cQsoDb *orig_dups = new cQsoDb;
644 cQsoDb *copy = new cQsoDb(db);
645
646 string mergedir;
647 string mrg_dups_name;
648 string orig_dups_name;
649 string lg_recs_name;
650 string fname;
651 size_t pname;
652
653 cQsoRec *lastrec = 0;
654 cQsoRec *rec_n;
655 cQsoRec *rec_m;
656
657 int N;
658 int M;
659 int n = 0;
660 int m = 0;
661 int cmp = 0;
662 int cmp2 = 0;
663
664 int merged = 0;
665 int merge_duplicates = 0;
666 int orig_duplicates = 0;
667
668 struct timespec t0, t1, t2;
669 float merger_time = 0;
670
671 Fl::awake(merge_announce_1);
672
673 #ifdef _POSIX_MONOTONIC_CLOCK
674 clock_gettime(CLOCK_MONOTONIC, &t0);
675 #else
676 clock_gettime(CLOCK_REALTIME, &t0);
677 #endif
678
679 LOG_INFO("MERGE: adifFile.do_readfile(%s)", mrg_fname.c_str());
680 adifFile.do_readfile (mrg_fname.c_str(), mrgdb);
681
682 #ifdef _POSIX_MONOTONIC_CLOCK
683 clock_gettime(CLOCK_MONOTONIC, &t1);
684 #else
685 clock_gettime(CLOCK_REALTIME, &t1);
686 #endif
687
688 N = copy->nbrRecs();
689 M = mrgdb->nbrRecs();
690
691 if (M == 0) {
692 disptxt.assign("\n================================================\n");
693 disptxt.append("Merge file contains no records\n");
694 disptxt.append("\n================================================");
695 LOG_INFO("%s", disptxt.c_str());
696 disptxt.append("\n");
697 goto exit_merge_thread;
698 }
699
700 read_secs = t1.tv_sec - t0.tv_sec + (t1.tv_nsec- t0.tv_nsec)/1e9;
701 num_merge_recs = M;
702
703 Fl::awake(merge_announce_2);
704
705 disptxt.assign("\n================================================\n");
706
707 copy->SortByCall();
708 mrgdb->SortByCall();
709
710 for (int i = 0; i < M; i++) {
711 mrgdb->getRec(i)->checkBand();
712 mrgdb->getRec(i)->checkDateTimes();
713 }
714
715 //writeLog("copy.adi", copy);
716 //writeLog("mrgdb.adi", mrgdb);
717
718 db->clearDatabase();
719
720 for (;;) {
721
722 pthread_mutex_lock(&MERGE_mutex);
723 if (abort_merger) goto abort;
724 pthread_mutex_unlock(&MERGE_mutex);
725
726 rec_n = copy->getRec(n);
727 rec_m = mrgdb->getRec(m);
728
729 if (N == 0) {
730 if (m == M) break;
731 if (lastrec == 0) {
732 db->qsoNewRec(lastrec = rec_m);
733 merged++;
734 } else {
735 cmp = comparebycall(lastrec, rec_m);
736 if (cmp != 0) {
737 db->qsoNewRec(lastrec = rec_m);
738 merged++;
739 } else {
740 merge_dups->qsoNewRec(rec_m);
741 merge_duplicates++;
742 }
743 }
744 m++;
745 continue;
746 }
747
748 if (n == N) {
749 if (m == M) break;
750 cmp = comparebycall(lastrec, rec_m);
751 if (cmp == 0) {
752 merge_dups->qsoNewRec(rec_m);
753 merge_duplicates++;
754 } else {
755 db->qsoNewRec(lastrec = rec_m);
756 merged++;
757 }
758 m++;
759 continue;
760 }
761 if (m == M) {
762 if (n == N) break;
763 cmp = comparebycall(lastrec, rec_n);
764 if (cmp == 0) {
765 orig_dups->qsoNewRec(rec_n);
766 orig_duplicates++;
767 } else {
768 db->qsoNewRec(lastrec = rec_n);
769 }
770 n++;
771 continue;
772 }
773
774 if (lastrec == 0) {
775 cmp = comparebycall(rec_n, rec_m);
776 if (cmp < 0) {
777 db->qsoNewRec(lastrec = rec_n);
778 n++;
779 continue;
780 }
781 if (cmp == 0) {
782 db->qsoNewRec(lastrec = rec_n);
783 n++;
784 merge_dups->qsoNewRec(rec_m);
785 m++;
786 merge_duplicates++;
787 continue;
788 } // cmp > 0
789 db->qsoNewRec(lastrec = rec_m);
790 m++;
791 } else { // lastrec exists
792 cmp = comparebycall(rec_n, rec_m);
793 if (cmp == 0) {
794 merge_dups->qsoNewRec(rec_m);
795 merge_duplicates++;
796 m++;
797 cmp2 = comparebycall(lastrec, rec_n);
798 if (cmp2 == 0) {
799 orig_dups->qsoNewRec(rec_n);
800 orig_duplicates++;
801 } else
802 db->qsoNewRec(lastrec = rec_n);
803 n++;
804 continue;
805 }
806 if (cmp < 0) {
807 cmp2 = comparebycall(lastrec, rec_n);
808 if (cmp2 == 0) {
809 orig_dups->qsoNewRec(rec_n);
810 orig_duplicates++;
811 } else
812 db->qsoNewRec(lastrec = rec_n);
813 n++;
814 continue;
815 } // cmp > 0
816 cmp2 = comparebycall(lastrec, rec_m);
817 if (cmp2 == 0) {
818 merge_dups->qsoNewRec(rec_m);
819 merge_duplicates++;
820 } else if (cmp2 < 0) {
821 db->qsoNewRec(lastrec = rec_m);
822 merged++;
823 }
824 m++;
825 }
826 }
827
828 mergedir = logbook_filename;
829
830 fname = fl_filename_name(mergedir.c_str());
831 pname = mergedir.find(fname);
832 mergedir.erase(pname);
833
834 if (db->nbrRecs())
835 db->SortByCall();
836
837 if (merged > 0) {
838 snprintf(msg1, sizeof(msg1), "Merged %d records\n", merged);
839 disptxt.append(msg1);
840 }
841
842 if (merge_duplicates) {
843 merge_dups->SortByCall();
844 mrg_dups_name = mergedir;
845 mrg_dups_name.append("merge_file_dups");
846 #ifdef __WIN32__
847 mrg_dups_name.append(".adi");
848 #else
849 mrg_dups_name.append(".adif");
850 #endif
851 adifFile.writeLog (mrg_dups_name.c_str(), merge_dups, true);
852
853 snprintf(msg1, sizeof(msg1), "Found %d duplicate records\n", merge_duplicates);
854 disptxt.append(msg1);
855 snprintf(msg1, sizeof(msg1), "Duplicate's saved in %s\n", mrg_dups_name.c_str());
856 disptxt.append(msg1);
857 }
858
859 if (orig_duplicates) {
860 orig_dups->SortByCall();
861 orig_dups_name = mergedir;
862 orig_dups_name.append("original_file_dups");
863 #ifdef __WIN32__
864 orig_dups_name.append(".adi");
865 #else
866 orig_dups_name.append(".adif");
867 #endif
868 adifFile.writeLog (orig_dups_name.c_str(), orig_dups, true);
869
870 snprintf(msg1,sizeof(msg1), "Original database had %d duplicates\n", orig_duplicates);
871 disptxt.append(msg1);
872 snprintf(msg1, sizeof(msg1), "Duplicate's saved in %s\n", orig_dups_name.c_str());
873 disptxt.append(msg1);
874 }
875
876 #ifdef _POSIX_MONOTONIC_CLOCK
877 clock_gettime(CLOCK_MONOTONIC, &t2);
878 #else
879 clock_gettime(CLOCK_REALTIME, &t2);
880 #endif
881
882 merger_time = t2.tv_sec - t0.tv_sec + (t2.tv_nsec- t2.tv_nsec)/1e9;
883
884 snprintf(msg1, sizeof(msg1), "Merger took %4.1f seconds\n", merger_time);
885 disptxt.append(msg1);
886
887 disptxt.append("================================================");
888 LOG_INFO("%s", disptxt.c_str());
889 disptxt.append("\n");
890
891 exit_merge_thread:
892
893 delete mrgdb;
894 delete orig_dups;
895 delete merge_dups;
896 delete copy;
897
898 lastsort = orig_sort;
899 cQsoDb::reverse = orig_reverse;
900
901 Fl::awake(close_MERGE_thread);
902
903 return NULL;
904
905 abort:
906
907 pthread_mutex_unlock(&MERGE_mutex);
908
909 disptxt.assign("Merger aborted");
910
911 delete mrgdb;
912 delete orig_dups;
913 delete merge_dups;
914 delete copy;
915
916 lastsort = orig_sort;
917 cQsoDb::reverse = orig_reverse;
918
919 Fl::awake(close_MERGE_thread);
920
921 return NULL;
922 }
923
924 static notify_dialog *alert_window = 0;
cb_mnuMergeADIF_log(Fl_Menu_ * m,void * d)925 void cb_mnuMergeADIF_log(Fl_Menu_* m, void* d) {
926
927 ENSURE_THREAD(FLMAIN_TID);
928
929 if (MERGE_thread) {
930 if (!alert_window) alert_window = new notify_dialog;
931 alert_window->notify(_("Database merger in progress"), 5.0);
932 REQ(show_notifier, alert_window);
933 return;
934 }
935
936 const char* p = FSEL::select(
937 _("Merge ADIF file"),
938 "ADIF\t*.{adi,adif}",
939 LogsDir.c_str());
940
941 fl_digi_main->redraw();
942 Fl::flush();
943
944 if (!p) return;
945 if (!*p) return;
946
947 mrg_fname = p;
948
949 abort_merger = false;
950
951 MERGE_thread = new pthread_t;
952 if (pthread_create(MERGE_thread, NULL, merge_thread, NULL) != 0) {
953 LOG_PERROR("pthread_create");
954 return;
955 }
956 MilliSleep(10);
957
958 }
959 //======================================================================
960
961 static string lotw_download_name = "";
962 static cQsoDb *lotw_db = 0;
963
964 extern Fl_Button *btn_view_unmatched;
965
verify_lotw(void *)966 void verify_lotw(void *)
967 {
968 lotw_db = new cQsoDb;
969 LOG_INFO("VERIFY_LOTW: adifFile.do_readfile(%s", lotw_download_name.c_str());
970 adifFile.do_readfile (lotw_download_name.c_str(), lotw_db);
971
972 stringstream ss_note;
973
974 if (lotw_db->nbrRecs() == 0) {
975 LOG_INFO("%s", _("No records in lotw download file"));
976 } else {
977
978 string report_fname = LoTWDir;
979 report_fname.append("unverified.txt");
980 ofstream report_file(report_fname.c_str());
981
982 int matchrec;
983 cQsoRec *qrec, *lrec;
984 int nverified = 0;
985 int unverified = 0;
986 string date;
987 string qdate;
988
989 for (int i = 0; i < lotw_db->nbrRecs(); i++) {
990 lrec = lotw_db->getRec(i);
991 date = lrec->getField(QSLRDATE);
992 matchrec = qsodb.matched( lrec );
993 if (matchrec != -1) {
994 qrec = qsodb.getRec(matchrec);
995 qdate = qrec->getField(LOTWRDATE);
996 if (date != qdate) {
997 qrec->putField(STATE, lrec->getField(STATE));
998 qrec->putField(GRIDSQUARE, lrec->getField(GRIDSQUARE));
999 qrec->putField(CQZ, lrec->getField(CQZ));
1000 qrec->putField(COUNTRY, lrec->getField(COUNTRY));
1001 qrec->putField(CNTY, lrec->getField(CNTY));
1002 qrec->putField(DXCC, lrec->getField(DXCC));
1003 qrec->putField(DXCC, lrec->getField(DXCC));
1004 qrec->putField(LOTWRDATE, lrec->getField(QSLRDATE));
1005 }
1006 nverified++;
1007 } else {
1008 unverified++;
1009 report_file << lrec->getField(CALL) << ", "
1010 << lrec->getField(QSO_DATE) << ", "
1011 << lrec->getField(TIME_ON) << ", "
1012 << lrec->getField(FREQ) << ", "
1013 << lrec->getField(BAND) << ", "
1014 << lrec->getField(ADIF_MODE) << "\n";
1015 }
1016 }
1017 report_file.close();
1018 if (!unverified) remove(report_fname.c_str());
1019
1020 ss_note << "LoTW download contains " << lotw_db->nbrRecs() << " records\n\n"
1021 << nverified << " verified\n";
1022 if (unverified) {
1023 ss_note << unverified << " unverified\n\n"
1024 << "Check file " << report_fname;
1025
1026 btn_view_unmatched->activate();
1027
1028 if (nverified) qsodb.isdirty(1);
1029 LOG_INFO("%d records verified", nverified);
1030 LOG_INFO("%d records unverified", unverified);
1031 }
1032 if (!alert_window) alert_window = new notify_dialog;
1033 alert_window->notify(ss_note.str().c_str(), 15.0);
1034 REQ(show_notifier, alert_window);
1035 }
1036 delete lotw_db;
1037 }
1038
1039 static Fl_Window *unmatched_viewer = (Fl_Window *)0;
1040 static Fl_Text_Display *viewer = (Fl_Text_Display *)0;
1041 static Fl_Text_Buffer *buffer = (Fl_Text_Buffer *)0;
1042 static Fl_Button *close_viewer = (Fl_Button *)0;
1043
cb_close_viewer(Fl_Button *,void *)1044 void cb_close_viewer(Fl_Button *, void *) {
1045 if (unmatched_viewer) {
1046 unmatched_viewer->hide();
1047 delete unmatched_viewer;
1048 unmatched_viewer = 0;
1049 }
1050 }
1051
cb_btn_view_unmatched(Fl_Button *,void *)1052 void cb_btn_view_unmatched(Fl_Button *, void *) {
1053 btn_view_unmatched->deactivate();
1054 if (!unmatched_viewer) {
1055 unmatched_viewer = new Fl_Window(100,100, 400, 500, _("Unmatched LoTW Records"));
1056 viewer = new Fl_Text_Display(5, 5, 390, 470, "");
1057 buffer = new Fl_Text_Buffer(8192);
1058 viewer->buffer(buffer);
1059 viewer->textfont(progdefaults.LOGBOOKtextfont);
1060 viewer->textsize(progdefaults.LOGBOOKtextsize);
1061 close_viewer = new Fl_Button(320, 477, 75, 20, _("Close"));
1062 close_viewer->callback((Fl_Callback *)cb_close_viewer);
1063 unmatched_viewer->end();
1064 unmatched_viewer->resizable(viewer);
1065 }
1066 string report_fname = LoTWDir;
1067 report_fname.append("unverified.txt");
1068 ifstream report_file(report_fname.c_str());
1069 if (report_file) {
1070 char linebuff[1025];
1071 viewer->buffer()->text("");
1072 while (!report_file.eof()) {
1073 report_file.getline(linebuff, 1024);
1074 strcat(linebuff, "\n");
1075 viewer->insert(linebuff);
1076 }
1077 report_file.close();
1078 unmatched_viewer->show();
1079 }
1080 }
1081
cb_btn_verify_lotw(Fl_Button *,void *)1082 void cb_btn_verify_lotw(Fl_Button *, void *) {
1083
1084 string deffname = LoTWDir;
1085 deffname.append("lotwreport.adi");
1086
1087 ifstream f(deffname.c_str());
1088
1089 if (!f) {
1090 std::string alert = _("\
1091 Could not find LoTW report file.\n\n\
1092 Download from ARRL's LoTW page after logging in at:\n\n\
1093 https://lotw.arrl.org/lotwuser/default\n\n\
1094 Store the report file to the fldigi LOTW folder,\n\n\
1095 naming the file 'lotwreport.adi'");
1096 if (!alert_window) alert_window = new notify_dialog;
1097 alert_window->notify(alert.c_str(), 20);
1098 REQ(show_notifier, alert_window);
1099 return;
1100 }
1101
1102 f.close();
1103 lotw_download_name = deffname;
1104 Fl::awake(verify_lotw);
1105 }
1106
cb_export_date_select()1107 void cb_export_date_select() {
1108 if (qsodb.nbrRecs() == 0) return;
1109 int start = atoi(inp_export_start_date->value());
1110 int stop = atoi(inp_export_stop_date->value());
1111
1112 chkExportBrowser->check_none();
1113
1114 if (!start || !stop) return;
1115 int chkdate;
1116
1117 if (!btn_export_by_date->value()) return;
1118
1119 cQsoRec *rec;
1120 for (int i = 0; i < qsodb.nbrRecs(); i++) {
1121 rec = qsodb.getRec (i);
1122 chkdate = atoi(rec->getField(progdefaults.sort_date_time_off ? QSO_DATE_OFF : QSO_DATE));
1123 if (chkdate >= start && chkdate <= stop)
1124 chkExportBrowser->checked(i+1, 1);
1125 }
1126
1127 chkExportBrowser->redraw();
1128 }
1129
szfreq(const char * freq)1130 inline const char *szfreq(const char *freq)
1131 {
1132 static char szf[11];
1133 float f = atof(freq);
1134 snprintf(szf, sizeof(szf), "%10.6f", f);
1135 return szf;
1136 }
1137
cb_Export_log()1138 void cb_Export_log() {
1139 if (qsodb.nbrRecs() == 0) return;
1140 cQsoRec *rec;
1141 char line[80];
1142 chkExportBrowser->clear();
1143 #ifdef __APPLE__
1144 chkExportBrowser->textfont(FL_SCREEN_BOLD);
1145 chkExportBrowser->textsize(12);
1146 #else
1147 chkExportBrowser->textfont(FL_COURIER);
1148 chkExportBrowser->textsize(12);
1149 #endif
1150 for( int i = 0; i < qsodb.nbrRecs(); i++ ) {
1151 rec = qsodb.getRec (i);
1152 snprintf(line,sizeof(line),"%8s %4s %-10s %-10s %-s %-s",
1153 rec->getField(QSO_DATE),
1154 rec->getField((export_to == LOTW ? TIME_ON : TIME_OFF) ),
1155 rec->getField(CALL),
1156 szfreq(rec->getField(FREQ)),
1157 adif2export(rec->getField(ADIF_MODE)).c_str(),
1158 adif2submode(rec->getField(ADIF_MODE)).c_str()
1159 );
1160 chkExportBrowser->add(line);
1161 }
1162 cb_export_date_select();
1163 wExport->show();
1164 }
1165
cb_mnuExportADIF_log(Fl_Menu_ * m,void * d)1166 void cb_mnuExportADIF_log(Fl_Menu_* m, void* d) {
1167 export_to = ADIF;
1168 cb_Export_log();
1169 }
1170
cb_mnuExportCSV_log(Fl_Menu_ * m,void * d)1171 void cb_mnuExportCSV_log(Fl_Menu_* m, void* d) {
1172 export_to = CSV;
1173 cb_Export_log();
1174 }
1175
cb_btnExportLoTW()1176 void cb_btnExportLoTW() {
1177 export_to = LOTW;
1178 cb_Export_log();
1179 }
1180
cb_mnuExportTEXT_log(Fl_Menu_ * m,void * d)1181 void cb_mnuExportTEXT_log(Fl_Menu_* m, void *d) {
1182 export_to = TEXT;
1183 cb_Export_log();
1184 }
1185
1186
cb_mnuShowLogbook(Fl_Menu_ * m,void * d)1187 void cb_mnuShowLogbook(Fl_Menu_* m, void* d)
1188 {
1189 dlgLogbook->resize(
1190 progStatus.logbook_x, progStatus.logbook_y,
1191 progStatus.logbook_w, progStatus.logbook_h);
1192
1193 dlgLogbook->show();
1194 }
1195
1196 enum State {VIEWREC, NEWREC};
1197 static State logState = VIEWREC;
1198
activateButtons()1199 void activateButtons()
1200 {
1201 if (logState == NEWREC) {
1202 bNewSave->label(_("Save"));
1203 bUpdateCancel->label(_("Cancel"));
1204 bUpdateCancel->activate();
1205 bDelete->deactivate ();
1206 bSearchNext->deactivate ();
1207 bSearchPrev->deactivate ();
1208 inpDate_log->take_focus();
1209 return;
1210 }
1211 bNewSave->label(_("New"));
1212 bUpdateCancel->label(_("Update"));
1213 if (qsodb.nbrRecs() > 0) {
1214 bDelete->activate();
1215 bUpdateCancel->activate();
1216 bSearchNext->activate ();
1217 bSearchPrev->activate ();
1218 wBrowser->take_focus();
1219 } else {
1220 bDelete->deactivate();
1221 bUpdateCancel->deactivate();
1222 bSearchNext->deactivate();
1223 bSearchPrev->deactivate();
1224 }
1225 }
1226
cb_btnNewSave(Fl_Button * b,void * d)1227 void cb_btnNewSave(Fl_Button* b, void* d) {
1228 if (logState == VIEWREC) {
1229 logState = NEWREC;
1230 clearRecord();
1231 activateButtons();
1232 } else {
1233 saveRecord();
1234 qsodb.SortByDate(progdefaults.sort_date_time_off);
1235 loadBrowser();
1236 logState = VIEWREC;
1237 activateButtons();
1238 }
1239 }
1240
cb_btnUpdateCancel(Fl_Button * b,void * d)1241 void cb_btnUpdateCancel(Fl_Button* b, void* d) {
1242 if (logState == NEWREC) {
1243 logState = VIEWREC;
1244 activateButtons ();
1245 } else {
1246 updateRecord();
1247 wBrowser->take_focus();
1248 }
1249 }
1250
cb_btnDelete(Fl_Button * b,void * d)1251 void cb_btnDelete(Fl_Button* b, void* d) {
1252 deleteRecord();
1253 wBrowser->take_focus();
1254 }
1255
restore_sort()1256 void restore_sort()
1257 {
1258 switch (lastsort) {
1259 case SORTCALL :
1260 cQsoDb::reverse = callfwd;
1261 qsodb.SortByCall();
1262 break;
1263 case SORTDATE :
1264 cQsoDb::reverse = progStatus.logbook_reverse;
1265 qsodb.SortByDate(progdefaults.sort_date_time_off);
1266 break;
1267 case SORTFREQ :
1268 cQsoDb::reverse = freqfwd;
1269 qsodb.SortByFreq();
1270 break;
1271 case SORTMODE :
1272 cQsoDb::reverse = modefwd;
1273 qsodb.SortByMode();
1274 break;
1275 default: break;
1276 }
1277 }
1278
cb_SortByCall(void)1279 void cb_SortByCall (void) {
1280 if (lastsort == SORTCALL)
1281 callfwd = !callfwd;
1282 else {
1283 callfwd = false;
1284 lastsort = SORTCALL;
1285 }
1286 cQsoDb::reverse = callfwd;
1287 qsodb.SortByCall();
1288 loadBrowser();
1289 }
1290
cb_SortByDate(void)1291 void cb_SortByDate (void) {
1292 if (lastsort == SORTDATE)
1293 progStatus.logbook_reverse = !progStatus.logbook_reverse;
1294 else {
1295 lastsort = SORTDATE;
1296 }
1297 cQsoDb::reverse = progStatus.logbook_reverse;
1298 qsodb.SortByDate(progdefaults.sort_date_time_off);
1299 loadBrowser();
1300 }
1301
reload_browser()1302 void reload_browser()
1303 {
1304 qsodb.SortByDate(progdefaults.sort_date_time_off);
1305 loadBrowser();
1306 }
1307
cb_SortByMode(void)1308 void cb_SortByMode (void) {
1309 if (lastsort == SORTMODE)
1310 modefwd = !modefwd;
1311 else {
1312 modefwd = false;
1313 lastsort = SORTMODE;
1314 }
1315 cQsoDb::reverse = modefwd;
1316 qsodb.SortByMode();
1317
1318 loadBrowser();
1319 }
1320
cb_SortByFreq(void)1321 void cb_SortByFreq (void) {
1322 if (lastsort == SORTFREQ)
1323 freqfwd = !freqfwd;
1324 else {
1325 freqfwd = false;
1326 lastsort = SORTFREQ;
1327 }
1328 cQsoDb::reverse = freqfwd;
1329 qsodb.SortByFreq();
1330
1331 loadBrowser();
1332 }
1333
show_dup(void * dup)1334 void show_dup(void *dup)
1335 {
1336 int cdup = 0;
1337 if (dup == (void *)1) cdup = 1;
1338 if (dup == (void *)2) cdup = 2;
1339
1340 Fl_Color call_clr = progdefaults.LOGGINGcolor;
1341 Fl_Color dup_clr = fl_rgb_color(
1342 progdefaults.dup_color.R,
1343 progdefaults.dup_color.G,
1344 progdefaults.dup_color.B);
1345 Fl_Color pdup_clr = fl_rgb_color(
1346 progdefaults.possible_dup_color.R,
1347 progdefaults.possible_dup_color.G,
1348 progdefaults.possible_dup_color.B);
1349 inpCall1->color(cdup == 1 ? dup_clr : cdup == 2 ? pdup_clr : call_clr);
1350 inpCall2->color(cdup == 1 ? dup_clr : cdup == 2 ? pdup_clr : call_clr);
1351 inpCall3->color(cdup == 1 ? dup_clr : cdup == 2 ? pdup_clr : call_clr);
1352 inpCall4->color(cdup == 1 ? dup_clr : cdup == 2 ? pdup_clr : call_clr);
1353
1354 inpCall1->redraw();
1355 inpCall2->redraw();
1356 inpCall3->redraw();
1357 inpCall4->redraw();
1358 }
1359
DupCheck()1360 void DupCheck()
1361 {
1362 size_t dup = 0;
1363
1364 if (n3fjp_connected) {
1365 show_dup(0);
1366 n3fjp_dupcheck();
1367 return;
1368 }
1369
1370 if ( FD_logged_on && strlen(inpCall->value()) > 2)
1371 dup = FD_dupcheck();
1372 else if ( progdefaults.xml_logbook)
1373 dup = xml_check_dup();
1374 else {
1375 // check for call only for possible dup returns 2 if possible
1376 dup = qsodb.duplicate(inpCall->value());
1377 if (dup && qsodb.duplicate(
1378 inpCall->value(),
1379 zdate(), ztime(), progdefaults.timespan, progdefaults.duptimespan,
1380 inpFreq->value(), progdefaults.dupband,
1381 inpState->value(), progdefaults.dupstate,
1382 mode_info[active_modem->get_mode()].adif_name, progdefaults.dupmode,
1383 inpXchgIn->value(), progdefaults.dupxchg1 ) )
1384 dup = 1;
1385 }
1386 show_dup((void*)dup);
1387 }
1388
SearchLog(const char * callsign)1389 cQsoRec* SearchLog(const char *callsign)
1390 {
1391 if (progdefaults.xml_logbook)
1392 return search_fllog(callsign);
1393
1394 size_t len = strlen(callsign);
1395 char* re = new char[len + 3];
1396 snprintf(re, len + 3, "^%s$", callsign);
1397
1398 int row = 0, col = 2;
1399 return wBrowser->search(row, col, !cQsoDb::reverse, re) ? qsodb.getRec(row) : 0;
1400 }
1401
SearchLastQSO(const char * callsign)1402 void SearchLastQSO(const char *callsign)
1403 {
1404 if (n3fjp_connected) {
1405 n3fjp_get_record(callsign);
1406 return;
1407 }
1408
1409 size_t len = strlen(callsign);
1410
1411 if (len < 3)
1412 return;
1413
1414 if (progdefaults.xml_logbook) {
1415 if(xml_get_record(callsign))
1416 return;
1417 }
1418
1419 Fl::focus(inpCall);
1420
1421 char* re = new char[len + 3];
1422 snprintf(re, len + 3, "^%s$", callsign);
1423
1424 int row = 0, col = 2;
1425 if (wBrowser->search(row, col, !cQsoDb::reverse, re)) {
1426 wBrowser->GotoRow(row);
1427 inpName->value(inpName_log->value());
1428 inpQTH->value(inpQth_log->value());
1429 inpLoc1->value(inpLoc_log->value());
1430 inpLoc1->position (0);
1431 inpState->value(inpState_log->value());
1432 inpState->position (0);
1433 inpVEprov->value(inpVE_Prov_log->value ());
1434 inpVEprov->position (0);
1435 inpCounty->value(inpCNTY_log->value ());
1436 inpCounty->position (0);
1437 cboCountry->value(inpCountry_log->value ());
1438 inpSearchString->value(callsign);
1439 inpNotes->value(inpNotes_log->value ());
1440 if (inpLoc->value()[0]) {
1441 double lon1, lat1, lon2, lat2;
1442 double azimuth, distance;
1443 char szAZ[4];
1444 if ( QRB::locator2longlat(&lon1, &lat1, progdefaults.myLocator.c_str()) == QRB::QRB_OK &&
1445 QRB::locator2longlat(&lon2, &lat2, inpLoc->value()) == QRB::QRB_OK &&
1446 QRB::qrb(lon1, lat1, lon2, lat2, &distance, &azimuth) == QRB::QRB_OK ) {
1447 snprintf(szAZ,sizeof(szAZ),"%0.f", azimuth);
1448 inpAZ->value(szAZ);
1449 inpAZ->position (0);
1450 } else
1451 inpAZ->value("");
1452 } else
1453 inpAZ->value("");
1454 } else {
1455 clear_log_fields();
1456 }
1457 delete [] re;
1458 }
1459
cb_search(Fl_Widget * w,void *)1460 void cb_search(Fl_Widget* w, void*)
1461 {
1462 const char* str = inpSearchString->value();
1463 if (!*str)
1464 return;
1465
1466 bool rev = w == bSearchPrev;
1467 int col = 2, row = wBrowser->value() + (rev ? -1 : 1);
1468 row = WCLAMP(row, 0, wBrowser->rows() - 1);
1469
1470 if (wBrowser->search(row, col, rev, str))
1471 wBrowser->GotoRow(row);
1472 wBrowser->take_focus();
1473 }
1474
log_search_handler(int)1475 int log_search_handler(int)
1476 {
1477 if (!(Fl::event_state() & FL_CTRL))
1478 return 0;
1479
1480 switch (Fl::event_key()) {
1481 case 's':
1482 bSearchNext->do_callback();
1483 break;
1484 case 'r':
1485 bSearchPrev->do_callback();
1486 break;
1487 default:
1488 return 0;
1489 }
1490 return 1;
1491 }
1492
cb_btnRetrieve(Fl_Button * b,void * d)1493 void cb_btnRetrieve(Fl_Button* b, void* d)
1494 {
1495 double drf = atof(inpFreq_log->value());
1496 if (!drf) return;
1497
1498 int rf1, rf, audio;
1499 rf1 = drf * 1e6;
1500 rf = rf1 / 10000;
1501 rf *= 10000;
1502 audio = rf1 - rf;
1503 // try to keep within normal xcvr bw, 500 - 3000 Hz
1504 while (audio > 3000) {
1505 audio -= 3000;
1506 rf += 3000;
1507 }
1508 if (audio < 500) {
1509 audio += 500;
1510 rf -= 500;
1511 }
1512 qsy(rf, audio);
1513
1514 std::string mode_name = inpMode_log->value();
1515 trx_mode m;
1516 for (m = 0; m < NUM_MODES; m++)
1517 if (mode_name == mode_info[m].adif_name)
1518 break;
1519 // do we have a valid modem?
1520 if (m < NUM_MODES && active_modem->get_mode() != mode_info[m].mode)
1521 init_modem(mode_info[m].mode);
1522
1523 const cQsoRec *qsoPtr = qsodb.getRec(editNbr);
1524 inpCall->value(qsoPtr->getField(CALL));
1525 inpName->value (qsoPtr->getField(NAME));
1526
1527 sDate_on = sDate_off = zdate();
1528 sTime_on = sTime_off = ztime();
1529
1530 inpTimeOn->value(inpTimeOff->value());
1531 inpTimeOn1->value(inpTimeOff->value());
1532 inpTimeOn2->value(inpTimeOff->value());
1533 inpTimeOn3->value(inpTimeOff->value());
1534 inpTimeOn4->value(inpTimeOff->value());
1535 inpTimeOn5->value(inpTimeOff->value());
1536
1537 inpState->value (qsoPtr->getField(STATE));
1538 inpState->position (0);
1539 inpCounty->value (qsoPtr->getField(CNTY));
1540 inpCounty->position (0);
1541 cboCountry->value (qsoPtr->getField(COUNTRY));
1542 inpXchgIn->value(qsoPtr->getField(XCHG1));
1543 inpQTH->value (qsoPtr->getField(QTH));
1544 inpLoc1->value (qsoPtr->getField(GRIDSQUARE));
1545 inpLoc1->position (0);
1546 inpNotes->value (qsoPtr->getField(NOTES));
1547
1548 wBrowser->take_focus();
1549 if (n3fjp_connected)
1550 n3fjp_get_record(inpCall->value());
1551
1552 }
1553
clearRecord()1554 void clearRecord() {
1555 Date tdy;
1556 inpCall_log->value ("");
1557 inpName_log->value ("");
1558 inpDate_log->value (tdy.szDate(2));
1559 inpDateOff_log->value (tdy.szDate(2));
1560 inpTimeOn_log->value ("");
1561 inpTimeOff_log->value ("");
1562 inpRstR_log->value ("");
1563 inpRstS_log->value ("");
1564 inpFreq_log->value ("");
1565 inpBand_log->value ("");
1566 inpMode_log->value ("");
1567 inpQth_log->value ("");
1568 inpState_log->value ("");
1569 inpVE_Prov_log->value ("");
1570 inpCountry_log->value ("");
1571 inpLoc_log->value ("");
1572
1573 inpQSLrcvddate_log->value ("");
1574 inpQSLsentdate_log->value ("");
1575
1576 inpEQSLrcvddate_log->value ("");
1577 inpEQSLsentdate_log->value ("");
1578
1579 inpLOTWrcvddate_log->value ("");
1580 inpLOTWsentdate_log->value ("");
1581
1582 inpSerNoOut_log->value ("");
1583 inpSerNoIn_log->value ("");
1584 inpXchgIn_log->value("");
1585 inpMyXchg_log->value(progdefaults.myXchg.c_str());
1586 inpNotes_log->value ("");
1587 inpIOTA_log->value("");
1588 inpDXCC_log->value("");
1589 inpQSL_VIA_log->value("");
1590 inpCONT_log->value("");
1591 inpCNTY_log->value("");
1592 inpCQZ_log->value("");
1593 inpITUZ_log->value("");
1594 inpTX_pwr_log->value("");
1595 inpSearchString->value ("");
1596
1597 inp_log_sta_call->value("");
1598 inp_log_op_call->value("");
1599 inp_log_sta_qth->value("");
1600 inp_log_sta_loc->value("");
1601
1602 inp_log_cwss_serno->value("");
1603 inp_log_cwss_prec->value("");
1604 inp_log_cwss_chk->value("");
1605 inp_log_cwss_sec->value("");
1606
1607 }
1608
saveRecord()1609 void saveRecord() {
1610 cQsoRec rec;
1611 rec.putField(CALL, inpCall_log->value());
1612 rec.putField(NAME, inpName_log->value());
1613 rec.putField(QSO_DATE, inpDate_log->value());
1614 rec.putField(QSO_DATE_OFF, inpDateOff_log->value());
1615
1616 string tm = timestring(inpTimeOn_log->value());
1617 rec.putField(TIME_ON, tm.c_str());
1618 inpTimeOn_log->value(timeview(tm.c_str()));
1619
1620 tm = timestring(inpTimeOff_log->value());
1621 rec.putField(TIME_OFF, tm.c_str());
1622 inpTimeOff_log->value(timeview(tm.c_str()));
1623
1624 rec.putField(FREQ, inpFreq_log->value());
1625 rec.putField(BAND, inpBand_log->value());
1626 rec.putField(ADIF_MODE, inpMode_log->value());
1627 rec.putField(QTH, inpQth_log->value());
1628 rec.putField(STATE, inpState_log->value());
1629 rec.putField(VE_PROV, inpVE_Prov_log->value());
1630 rec.putField(COUNTRY, inpCountry_log->value());
1631 rec.putField(GRIDSQUARE, inpLoc_log->value());
1632 rec.putField(NOTES, inpNotes_log->value());
1633
1634 rec.putField(QSLRDATE, inpQSLrcvddate_log->value());
1635 rec.putField(QSLSDATE, inpQSLsentdate_log->value());
1636
1637 rec.putField(EQSLRDATE, inpEQSLrcvddate_log->value());
1638 rec.putField(EQSLSDATE, inpEQSLsentdate_log->value());
1639
1640 rec.putField(LOTWRDATE, inpLOTWrcvddate_log->value());
1641
1642 if (progdefaults.submit_lotw) {
1643 lotwsdate = zdate();
1644 rec.putField(LOTWSDATE, lotwsdate.c_str());
1645 Fl::awake(refresh_logbook_dialog);
1646 } else
1647 rec.putField(LOTWSDATE, inpLOTWsentdate_log->value());
1648
1649 rec.putField(RST_RCVD, inpRstR_log->value ());
1650 rec.putField(RST_SENT, inpRstS_log->value ());
1651 rec.putField(SRX, inpSerNoIn_log->value());
1652 rec.putField(STX, inpSerNoOut_log->value());
1653
1654 if (inpSPCnum->value()[0]) {
1655 inpXchgIn_log->value(inpSPCnum->value());
1656 rec.putField(XCHG1, inpSPCnum->value());
1657 } else if (inpSQSO_category->value()[0]) {
1658 inpXchgIn_log->value(inpSQSO_category->value());
1659 rec.putField(XCHG1, inpSQSO_category->value());
1660 } else
1661 rec.putField(XCHG1, inpXchgIn_log->value());
1662
1663 rec.putField(CLASS, inpClass_log->value());
1664 rec.putField(ARRL_SECT, inpSection_log->value());
1665
1666 if (!qso_exchange.empty()) {
1667 rec.putField(MYXCHG, qso_exchange.c_str());
1668 qso_exchange.clear();
1669 qso_time.clear();
1670 } else if (!qso_time.empty()) {
1671 string myexch = inpMyXchg_log->value();
1672 myexch.append(" ").append(qso_time);
1673 rec.putField(MYXCHG, myexch.c_str());
1674 qso_time.clear();
1675 } else {
1676 rec.putField(MYXCHG, inpMyXchg_log->value());
1677 }
1678 rec.putField(CNTY, inpCNTY_log->value());
1679 rec.putField(IOTA, inpIOTA_log->value());
1680 rec.putField(DXCC, inpDXCC_log->value());
1681 rec.putField(DXCC, inpQSL_VIA_log->value());
1682 rec.putField(CONT, inpCONT_log->value());
1683 rec.putField(CQZ, inpCQZ_log->value());
1684 rec.putField(ITUZ, inpITUZ_log->value());
1685 rec.putField(TX_PWR, inpTX_pwr_log->value());
1686
1687 rec.putField(STA_CALL, inp_log_sta_call->value());
1688 rec.putField(OP_CALL, inp_log_op_call->value());
1689 rec.putField(MY_CITY, inp_log_sta_qth->value());
1690 rec.putField(MY_GRID, inp_log_sta_loc->value());
1691
1692 rec.putField(SS_SERNO, inp_log_cwss_serno->value());
1693 rec.putField(SS_PREC, inp_log_cwss_prec->value());
1694 rec.putField(SS_CHK, inp_log_cwss_chk->value());
1695 rec.putField(SS_SEC, inp_log_cwss_sec->value());
1696
1697 rec.putField(AGE, inp_age_log->value());
1698 rec.putField(TEN_TEN, inp_1010_log->value());
1699 rec.putField(CHECK, inp_check_log->value());
1700
1701 rec.putField(TROOPS, inp_log_troop_s->value());
1702 rec.putField(TROOPR, inp_log_troop_r->value());
1703 rec.putField(SCOUTS, inp_log_scout_s->value());
1704 rec.putField(SCOUTR, inp_log_scout_r->value());
1705
1706 qsodb.qsoNewRec (&rec);
1707
1708 lotw_recs_sent.push_back(qsodb.nbrRecs() - 1);
1709
1710 dxcc_entity_cache_add(&rec);
1711 submit_record(rec);
1712
1713 qsodb.isdirty(0);
1714
1715 reload_browser();
1716
1717 if (qsodb.nbrRecs() == 1)
1718 adifFile.writeLog (logbook_filename.c_str(), &qsodb);
1719 else
1720 adifFile.writeAdifRec(&rec, logbook_filename.c_str());
1721
1722 }
1723
1724
updateRecord()1725 void updateRecord() {
1726 cQsoRec rec;
1727 if (qsodb.nbrRecs() == 0) return;
1728 rec.putField(CALL, inpCall_log->value());
1729 rec.putField(NAME, inpName_log->value());
1730 rec.putField(QSO_DATE, inpDate_log->value());
1731 rec.putField(QSO_DATE_OFF, inpDateOff_log->value());
1732
1733 string tm = timestring(inpTimeOn_log->value());
1734 rec.putField(TIME_ON, tm.c_str());
1735 inpTimeOn_log->value(timeview(tm.c_str()));
1736
1737 tm = timestring(inpTimeOff_log->value());
1738 rec.putField(TIME_OFF, tm.c_str());
1739 inpTimeOff_log->value(timeview(tm.c_str()));
1740
1741 rec.putField(FREQ, inpFreq_log->value());
1742 rec.putField(BAND, inpBand_log->value());
1743 rec.putField(ADIF_MODE, inpMode_log->value());
1744 rec.putField(QTH, inpQth_log->value());
1745 rec.putField(STATE, inpState_log->value());
1746 rec.putField(VE_PROV, inpVE_Prov_log->value());
1747 rec.putField(COUNTRY, inpCountry_log->value());
1748 rec.putField(GRIDSQUARE, inpLoc_log->value());
1749 rec.putField(NOTES, inpNotes_log->value());
1750
1751 rec.putField(QSLRDATE, inpQSLrcvddate_log->value());
1752 rec.putField(QSLSDATE, inpQSLsentdate_log->value());
1753
1754 rec.putField(EQSLRDATE, inpEQSLrcvddate_log->value());
1755 rec.putField(EQSLSDATE, inpEQSLsentdate_log->value());
1756
1757 rec.putField(LOTWRDATE, inpLOTWrcvddate_log->value());
1758 rec.putField(LOTWSDATE, inpLOTWsentdate_log->value());
1759
1760 rec.putField(RST_RCVD, inpRstR_log->value ());
1761 rec.putField(RST_SENT, inpRstS_log->value ());
1762 rec.putField(SRX, inpSerNoIn_log->value());
1763 rec.putField(STX, inpSerNoOut_log->value());
1764 rec.putField(XCHG1, inpXchgIn_log->value());
1765 rec.putField(MYXCHG, inpMyXchg_log->value());
1766
1767 rec.putField(CLASS, inpClass_log->value());
1768 rec.putField(ARRL_SECT, inpSection_log->value());
1769
1770 rec.putField(CNTY, inpCNTY_log->value());
1771 rec.putField(IOTA, inpIOTA_log->value());
1772 rec.putField(DXCC, inpDXCC_log->value());
1773 rec.putField(QSL_VIA, inpQSL_VIA_log->value());
1774 rec.putField(CONT, inpCONT_log->value());
1775 rec.putField(CQZ, inpCQZ_log->value());
1776 rec.putField(ITUZ, inpITUZ_log->value());
1777 rec.putField(TX_PWR, inpTX_pwr_log->value());
1778
1779 rec.putField(STA_CALL, inp_log_sta_call->value());
1780 rec.putField(OP_CALL, inp_log_op_call->value());
1781 rec.putField(MY_CITY, inp_log_sta_qth->value());
1782 rec.putField(MY_GRID, inp_log_sta_loc->value());
1783
1784 rec.putField(SS_SERNO, inp_log_cwss_serno->value());
1785 rec.putField(SS_PREC, inp_log_cwss_prec->value());
1786 rec.putField(SS_CHK, inp_log_cwss_chk->value());
1787 rec.putField(SS_SEC, inp_log_cwss_sec->value());
1788
1789 rec.putField(AGE, inp_age_log->value());
1790 rec.putField(TEN_TEN, inp_1010_log->value());
1791 rec.putField(CHECK, inp_check_log->value());
1792
1793 rec.putField(TROOPS, inp_log_troop_s->value());
1794 rec.putField(TROOPR, inp_log_troop_r->value());
1795 rec.putField(SCOUTS, inp_log_scout_s->value());
1796 rec.putField(SCOUTR, inp_log_scout_r->value());
1797
1798 dxcc_entity_cache_rm(qsodb.getRec(editNbr));
1799 qsodb.qsoUpdRec (editNbr, &rec);
1800 dxcc_entity_cache_add(&rec);
1801
1802 qsodb.isdirty(0);
1803 restore_sort();
1804
1805 loadBrowser(true);
1806
1807 adifFile.writeLog (logbook_filename.c_str(), &qsodb);
1808
1809 }
1810
deleteRecord()1811 void deleteRecord () {
1812 if (qsodb.nbrRecs() == 0 || fl_choice2(_("Really delete record for \"%s\"?"),
1813 _("Yes"), _("No"), NULL, wBrowser->valueAt(-1, 2)))
1814 return;
1815
1816 dxcc_entity_cache_rm(qsodb.getRec(editNbr));
1817 qsodb.qsoDelRec(editNbr);
1818
1819 qsodb.isdirty(0);
1820 restore_sort();
1821
1822 loadBrowser(true);
1823
1824 adifFile.writeLog (logbook_filename.c_str(), &qsodb);
1825
1826 }
1827
EditRecord(int i)1828 void EditRecord( int i )
1829 {
1830 cQsoRec *editQSO = qsodb.getRec (i);
1831 if( !editQSO )
1832 return;
1833
1834 inpCall_log->value (editQSO->getField(CALL));
1835 inpName_log->value (editQSO->getField(NAME));
1836 inpDate_log->value (editQSO->getField(QSO_DATE));
1837 inpDateOff_log->value (editQSO->getField(QSO_DATE_OFF));
1838 inpTimeOn_log->value (timeview(editQSO->getField(TIME_ON)));
1839 inpTimeOff_log->value (timeview(editQSO->getField(TIME_OFF)));
1840 inpRstR_log->value (editQSO->getField(RST_RCVD));
1841 inpRstS_log->value (editQSO->getField(RST_SENT));
1842 inpFreq_log->value (editQSO->getField(FREQ));
1843 inpBand_log->value (editQSO->getField(BAND));
1844 inpMode_log->value (editQSO->getField(ADIF_MODE));
1845 inpState_log->value (editQSO->getField(STATE));
1846 inpVE_Prov_log->value (editQSO->getField(VE_PROV));
1847 inpCountry_log->value (editQSO->getField(COUNTRY));
1848 inpQth_log->value (editQSO->getField(QTH));
1849 inpLoc_log->value (editQSO->getField(GRIDSQUARE));
1850
1851 inpQSLrcvddate_log->value (editQSO->getField(QSLRDATE));
1852 inpQSLsentdate_log->value (editQSO->getField(QSLSDATE));
1853
1854 inpEQSLrcvddate_log->value (editQSO->getField(EQSLRDATE));
1855 inpEQSLsentdate_log->value (editQSO->getField(EQSLSDATE));
1856
1857 inpLOTWrcvddate_log->value (editQSO->getField(LOTWRDATE));
1858 inpLOTWsentdate_log->value (editQSO->getField(LOTWSDATE));
1859
1860 inpNotes_log->value (editQSO->getField(NOTES));
1861 inpSerNoIn_log->value(editQSO->getField(SRX));
1862 inpSerNoOut_log->value(editQSO->getField(STX));
1863 inpXchgIn_log->value(editQSO->getField(XCHG1));
1864
1865 inpClass_log->value(editQSO->getField(CLASS));
1866 inpSection_log->value(editQSO->getField(ARRL_SECT));
1867
1868 inpMyXchg_log->value(editQSO->getField(MYXCHG));
1869 inpCNTY_log->value(editQSO->getField(CNTY));
1870 inpIOTA_log->value(editQSO->getField(IOTA));
1871 inpDXCC_log->value(editQSO->getField(DXCC));
1872 inpQSL_VIA_log->value(editQSO->getField(QSL_VIA));
1873 inpCONT_log->value(editQSO->getField(CONT));
1874 inpCQZ_log->value(editQSO->getField(CQZ));
1875 inpITUZ_log->value(editQSO->getField(ITUZ));
1876 inpTX_pwr_log->value(editQSO->getField(TX_PWR));
1877
1878 inp_log_sta_call->value(editQSO->getField(STA_CALL));
1879 inp_log_op_call->value(editQSO->getField(OP_CALL));
1880 inp_log_sta_qth->value(editQSO->getField(MY_CITY));
1881 inp_log_sta_loc->value(editQSO->getField(MY_GRID));
1882
1883 inp_log_cwss_serno->value(editQSO->getField(SS_SERNO));
1884 inp_log_cwss_prec->value(editQSO->getField(SS_PREC));
1885 inp_log_cwss_chk->value(editQSO->getField(SS_CHK));
1886 inp_log_cwss_sec->value(editQSO->getField(SS_SEC));
1887
1888 inp_age_log->value(editQSO->getField(AGE));
1889 inp_1010_log->value(editQSO->getField(TEN_TEN));
1890 inp_check_log->value(editQSO->getField(CHECK));
1891
1892 inp_log_troop_s->value(editQSO->getField(TROOPS));
1893 inp_log_troop_r->value(editQSO->getField(TROOPR));
1894 inp_log_scout_s->value(editQSO->getField(SCOUTS));
1895 inp_log_scout_r->value(editQSO->getField(SCOUTR));
1896
1897 }
1898
1899 std::string sDate_on = "";
1900 std::string sTime_on = "";
1901 std::string sDate_off = "";
1902 std::string sTime_off = "";
1903
AddRecord()1904 void AddRecord ()
1905 {
1906 inpCall_log->value(inpCall->value());
1907 inpName_log->value (inpName->value());
1908
1909 if (progdefaults.force_date_time) {
1910 inpDate_log->value(sDate_off.c_str());
1911 inpTimeOn_log->value (timeview(sTime_off.c_str()));
1912 } else {
1913 inpDate_log->value(sDate_on.c_str());
1914 inpTimeOn_log->value (timeview(sTime_on.c_str()));
1915 }
1916 inpDateOff_log->value(sDate_off.c_str());
1917 inpTimeOff_log->value (timeview(sTime_off.c_str()));
1918
1919 inpRstR_log->value (inpRstIn->value());
1920 inpRstS_log->value (inpRstOut->value());
1921 {
1922 char Mhz[30];
1923 snprintf(Mhz, sizeof(Mhz), "%-f", atof(inpFreq->value()) / 1000.0);
1924 inpFreq_log->value(Mhz);
1925 inpBand_log->value(band_name(Mhz));
1926 }
1927 inpMode_log->value (logmode);
1928 inpState_log->value (ucasestr(inpState->value()).c_str());
1929 inpVE_Prov_log->value (ucasestr(inpVEprov->value()).c_str());
1930 inpCountry_log->value (cboCountry->value());
1931 inpCNTY_log->value (inpCounty->value());
1932
1933 inpSerNoIn_log->value(inpSerNo->value());
1934 inpSerNoOut_log->value(outSerNo->value());
1935 inpXchgIn_log->value(inpXchgIn->value());
1936 inpMyXchg_log->value(progdefaults.myXchg.c_str());
1937
1938 inpQth_log->value (inpQTH->value());
1939 inpLoc_log->value (inpLoc->value());
1940
1941 inpQSLrcvddate_log->value ("");
1942 inpQSLsentdate_log->value ("");
1943
1944 inpEQSLrcvddate_log->value ("");
1945 inpEQSLsentdate_log->value ("");
1946
1947 inpLOTWrcvddate_log->value ("");
1948 inpLOTWsentdate_log->value ("");
1949
1950 inpNotes_log->value (inpNotes->value());
1951
1952 inpTX_pwr_log->value (progdefaults.mytxpower.c_str());
1953
1954 inpIOTA_log->value("");
1955 inpDXCC_log->value("");
1956 inpQSL_VIA_log->value("");
1957 inpCONT_log->value("");
1958
1959 inpCQZ_log->value(inp_CQzone->value());
1960 inpITUZ_log->value("");
1961
1962 inpClass_log->value(ucasestr(inpClass->value()).c_str());
1963 inpSection_log->value(ucasestr(inpSection->value()).c_str());
1964
1965 inp_log_sta_call->value(progdefaults.myCall.c_str());
1966 inp_log_op_call->value(progdefaults.operCall.c_str());
1967 inp_log_sta_qth->value(progdefaults.myQth.c_str());
1968 inp_log_sta_loc->value(progdefaults.myLocator.c_str());
1969
1970 inp_log_cwss_serno->value(ucasestr(inp_SS_SerialNoR->value()).c_str());
1971 inp_log_cwss_prec->value(ucasestr(inp_SS_Precedence->value()).c_str());
1972 inp_log_cwss_chk->value(ucasestr(inp_SS_Check->value()).c_str());
1973 inp_log_cwss_sec->value(ucasestr(inp_SS_Section->value()).c_str());
1974
1975 inp_age_log->value(ucasestr(inp_KD_age->value()).c_str());
1976 inp_check_log->value(ucasestr(inp_ARR_check->value()).c_str());
1977 inp_1010_log->value(ucasestr(inp_1010_nr->value()).c_str());
1978
1979 inp_log_troop_s->value(progdefaults.my_JOTA_troop.c_str());
1980 inp_log_troop_r->value(inp_JOTA_troop->value());
1981 inp_log_scout_s->value(progdefaults.my_JOTA_scout.c_str());
1982 inp_log_scout_r->value(inp_JOTA_scout->value());
1983
1984 saveRecord();
1985
1986 logState = VIEWREC;
1987 activateButtons();
1988 }
1989
cb_browser(Fl_Widget * w,void * data)1990 void cb_browser (Fl_Widget *w, void *data )
1991 {
1992 Table *table = (Table *)w;
1993 editNbr = atoi(table->valueAt(-1,6));
1994 EditRecord (editNbr);
1995 }
1996
addBrowserRow(cQsoRec * rec,int nbr)1997 void addBrowserRow(cQsoRec *rec, int nbr)
1998 {
1999 char sNbr[6];
2000 snprintf(sNbr,sizeof(sNbr),"%d", nbr);
2001 wBrowser->addRow (7,
2002 rec->getField(progdefaults.sort_date_time_off ? QSO_DATE_OFF : QSO_DATE),
2003 timeview4(rec->getField(progdefaults.sort_date_time_off ? TIME_OFF : TIME_ON)),
2004 rec->getField(CALL),
2005 rec->getField(NAME),
2006 rec->getField(FREQ),
2007 rec->getField(ADIF_MODE),
2008 sNbr);
2009 }
2010
adjustBrowser(bool keep_pos)2011 void adjustBrowser(bool keep_pos)
2012 {
2013 int row = wBrowser->value(), pos = wBrowser->scrollPos();
2014 if (row >= qsodb.nbrRecs()) row = qsodb.nbrRecs() - 1;
2015 if (keep_pos && row >= 0) {
2016 wBrowser->value(row);
2017 wBrowser->scrollTo(pos);
2018 }
2019 else {
2020 if (cQsoDb::reverse == true)
2021 wBrowser->FirstRow ();
2022 else
2023 wBrowser->LastRow ();
2024 }
2025 char szRecs[6];
2026 snprintf(szRecs, sizeof(szRecs), "%5d", qsodb.nbrRecs());
2027 txtNbrRecs_log->value(szRecs);
2028 }
2029
loadBrowser(bool keep_pos)2030 void loadBrowser(bool keep_pos)
2031 {
2032 cQsoRec *rec;
2033 wBrowser->clear();
2034 if (qsodb.nbrRecs() == 0)
2035 return;
2036 for( int i = 0; i < qsodb.nbrRecs(); i++ ) {
2037 rec = qsodb.getRec (i);
2038 addBrowserRow(rec, i);
2039 }
2040 adjustBrowser(keep_pos);
2041 }
2042
2043 //=============================================================================
2044 // Cabrillo reporter
2045 //=============================================================================
2046
2047 const char *szContests[] =
2048 { "AP-SPRINT",
2049 "ARRL-10", "ARRL-160", "ARRL-DX-CW", "ARRL-DX-SSB", "ARRL-SS-CW",
2050 "ARRL-SS-SSB", "ARRL-UHF-AUG", "ARRL-VHF-JAN", "ARRL-VHF-JUN", "ARRL-VHF-SEP",
2051 "ARRL-RTTY",
2052 "BARTG-RTTY", "BARTG-SPRINT",
2053 "CQ-160-CW", "CQ-160-SSB", "CQ-WPX-CW", "CQ-WPX-RTTY", "CQ-WPX-SSB", "CQ-VHF",
2054 "CQ-WW-CW", "CQ-WW-RTTY", "CQ-WW-SSB",
2055 "DARC-WAEDC-CW", "DARC-WAEDC-RTTY", "DARC-WAEDC-SSB",
2056 "FCG-FQP", "IARU-HF", "JIDX-CW", "JIDX-SSB",
2057 "NAQP-CW", "NAQP-RTTY", "NAQP-SSB", "NA-SPRINT-CW", "NA-SPRINT-SSB", "NCCC-CQP",
2058 "NEQP", "OCEANIA-DX-CW", "OCEANIA-DX-SSB", "RDXC", "RSGB-IOTA",
2059 "SAC-CW", "SAC-SSB", "STEW-PERRY", "TARA-RTTY", 0 };
2060
2061 enum icontest {
2062 AP_SPRINT,
2063 ARRL_10, ARRL_160, ARRL_DX_CW, ARRL_DX_SSB, ARRL_SS_CW,
2064 ARRL_SS_SSB, ARRL_UHF_AUG, ARRL_VHF_JAN, ARRL_VHF_JUN, ARRL_VHF_SEP,
2065 ARRL_RTTY,
2066 BARTG_RTTY, BARTG_SPRINT,
2067 CQ_160_CW, CQ_160_SSB, CQ_WPX_CW, CQ_WPX_RTTY, CQ_WPX_SSB, CQ_VHF,
2068 CQ_WW_CW, CQ_WW_RTTY, CQ_WW_SSB,
2069 DARC_WAEDC_CW, DARC_WAEDC_RTTY, DARC_WAEDC_SSB,
2070 FCG_FQP, IARU_HF, JIDX_CW, JIDX_SSB,
2071 NAQP_CW, NAQP_RTTY, NAQP_SSB, NA_SPRINT_CW, NA_SPRINT_SSB, NCCC_CQP,
2072 NEQP, OCEANIA_DX_CW, OCEANIA_DX_SSB, RDXC, RSGB_IOTA,
2073 SAC_CW, SAC_SSB, STEW_PERRY, TARA_RTTY
2074 };
2075
2076 bool bInitCombo = true;
2077 icontest contestnbr;
2078
setContestType()2079 void setContestType()
2080 {
2081 contestnbr = (icontest)cboContest->index();
2082
2083 btnCabCall->value(true); btnCabFreq->value(true); btnCabMode->value(true);
2084 btnCabQSOdate->value(true); btnCabTimeOFF->value(true); btnCabRSTsent->value(true);
2085 btnCabRSTrcvd->value(true); btnCabSerialIN->value(true);btnCabSerialOUT->value(true);
2086 btnCabXchgIn->value(true); btnCabMyXchg->value(true);
2087 btnCabCounty->value(true);
2088 btnCabState->value(true);
2089
2090 switch (contestnbr) {
2091 case ARRL_SS_CW :
2092 case ARRL_SS_SSB :
2093 btnCabRSTrcvd->value(false);
2094 break;
2095 case BARTG_RTTY :
2096 case BARTG_SPRINT :
2097 break;
2098 case ARRL_UHF_AUG :
2099 case ARRL_VHF_JAN :
2100 case ARRL_VHF_JUN :
2101 case ARRL_VHF_SEP :
2102 case CQ_VHF :
2103 btnCabRSTrcvd->value(false);
2104 btnCabSerialIN->value(false);
2105 btnCabSerialOUT->value(false);
2106 break;
2107 case AP_SPRINT :
2108 case ARRL_10 :
2109 case ARRL_160 :
2110 case ARRL_DX_CW :
2111 case ARRL_DX_SSB :
2112 case CQ_160_CW :
2113 case CQ_160_SSB :
2114 case CQ_WPX_CW :
2115 case CQ_WPX_RTTY :
2116 case CQ_WPX_SSB :
2117 case RDXC :
2118 case OCEANIA_DX_CW :
2119 case OCEANIA_DX_SSB :
2120 break;
2121 case DARC_WAEDC_CW :
2122 case DARC_WAEDC_RTTY :
2123 case DARC_WAEDC_SSB :
2124 break;
2125 case NAQP_CW :
2126 case NAQP_RTTY :
2127 case NAQP_SSB :
2128 case NA_SPRINT_CW :
2129 case NA_SPRINT_SSB :
2130 break;
2131 case RSGB_IOTA :
2132 break;
2133 default :
2134 break;
2135 }
2136 }
2137
cb_Export_Cabrillo(Fl_Menu_ * m,void * d)2138 void cb_Export_Cabrillo(Fl_Menu_* m, void* d) {
2139 if (qsodb.nbrRecs() == 0) return;
2140 cQsoRec *rec;
2141 char line[80];
2142 int indx = 0;
2143
2144 if (bInitCombo) {
2145 bInitCombo = false;
2146 while (szContests[indx]) {
2147 cboContest->add(szContests[indx]);
2148 indx++;
2149 }
2150 }
2151 cboContest->index(0);
2152 chkCabBrowser->clear();
2153 #ifdef __APPLE__
2154 chkCabBrowser->textfont(FL_SCREEN_BOLD);
2155 chkCabBrowser->textsize(12);
2156 #else
2157 chkCabBrowser->textfont(FL_COURIER);
2158 chkCabBrowser->textsize(12);
2159 #endif
2160 for( int i = 0; i < qsodb.nbrRecs(); i++ ) {
2161 rec = qsodb.getRec (i);
2162 memset(line, 0, sizeof(line));
2163 snprintf(line,sizeof(line),"%8s %4s %-10s %-10s %-s",
2164 rec->getField(QSO_DATE),
2165 time4(rec->getField(TIME_OFF)),
2166 rec->getField(CALL),
2167 szfreq(rec->getField(FREQ)),
2168 adif2export(rec->getField(ADIF_MODE)).c_str() );
2169 chkCabBrowser->add(line);
2170 }
2171 wCabrillo->show();
2172 }
2173
cabrillo_append_qso(FILE * fp,cQsoRec * rec)2174 void cabrillo_append_qso (FILE *fp, cQsoRec *rec)
2175 {
2176 char freq[16] = "";
2177 string rst_in, rst_out, exch_in, exch_out, date, time, mode, mycall, call, exch, state, county;
2178 string qsoline = "QSO: ";
2179 int ifreq = 0;
2180 size_t len = 0;
2181 size_t p = 0;
2182
2183 exch_out.clear();
2184 exch_in.clear();
2185 exch.clear();
2186
2187 if (btnCabFreq->value()) {
2188 ifreq = (int)(1000.0 * atof(rec->getField(FREQ)));
2189 snprintf(freq, sizeof(freq), "%7d", ifreq);
2190 qsoline.append(freq); qsoline.append(" ");
2191 }
2192
2193 if (btnCabMode->value()) {
2194 mode = adif2export(rec->getField(ADIF_MODE));
2195 if (mode.compare("USB") == 0 || mode.compare("LSB") == 0 ||
2196 mode.compare("FM") == 0 ||
2197 mode.compare("SSB") == 0 || mode.compare("PH") == 0 ) mode = "PH";
2198 else if (mode.compare("RTTY") == 0) mode = "RY";
2199 if (mode.length() < 10) mode.append(10 - mode.length(), ' ');
2200 qsoline.append(mode).append(" ");
2201 }
2202
2203 if (btnCabQSOdate->value()) {
2204 date = rec->getField(progdefaults.sort_date_time_off ? QSO_DATE_OFF : QSO_DATE);
2205 date.insert(4,"-");
2206 date.insert(7,"-");
2207 qsoline.append(date).append(" ");
2208 }
2209
2210 if (btnCabTimeOFF->value()) {
2211 time = rec->getField(progdefaults.sort_date_time_off ? TIME_OFF : TIME_ON);
2212 qsoline.append(time4(time.c_str())).append(" ");
2213 }
2214
2215 mycall = progdefaults.myCall;
2216 if (mycall.length() > 13) mycall = mycall.substr(0,13);
2217 len = mycall.length();
2218 if (len < 13) mycall.append(13 - len, ' ');
2219 qsoline.append(mycall).append(" ");
2220
2221 if (btnCabRSTsent->value() || contestnbr == BARTG_RTTY) {
2222 rst_out = rec->getField(RST_SENT);
2223 if (rst_out.length() > 3) rst_out = rst_out.substr(0, 3);
2224 len = rst_out.length();
2225 if (len < 3) rst_out.append(3 - len, ' ');
2226 exch_out.append(rst_out).append(" ");
2227 }
2228
2229 if (btnCabSerialOUT->value() || contestnbr == BARTG_RTTY) {
2230 exch_out.append(rec->getField(STX)).append(" ");
2231 }
2232
2233 if (btnCabMyXchg->value()) {
2234 exch = rec->getField(MYXCHG);
2235 if (!exch.empty())
2236 exch_out.append(exch).append(" ");
2237 }
2238
2239 if (contestnbr == BARTG_RTTY) {
2240 string toff = rec->getField(TIME_OFF);
2241 if (toff.length() > 4) toff = toff.substr(0,4);
2242 toff = toff.append(" ");
2243 exch_out.append(toff);
2244 }
2245 //
2246 // ADD CONTESTNBR == FD
2247 //
2248 //
2249 if (exch_out.length() > 20) exch_out = exch_out.substr(0,20);
2250 len = exch_out.length();
2251 if (len < 20) exch_out.append(20 - len, ' ');
2252
2253 qsoline.append(exch_out);
2254
2255 if (btnCabCall->value()) {
2256 call = rec->getField(CALL);
2257 if (call.length() > 13) call = call.substr(0,13);
2258 len = call.length();
2259 if (len < 13) call.append(13 - len, ' ');
2260 qsoline.append(call); qsoline.append(" ");
2261 }
2262
2263 if (btnCabRSTrcvd->value()) {
2264 rst_in = rec->getField(RST_RCVD);
2265 if (rst_in.length() > 3) rst_in = rst_in.substr(0,3);
2266 len = rst_in.length();
2267 if (len < 3) rst_in.append(3 - len, ' ');
2268 qsoline.append(rst_in); qsoline.append(" ");
2269 }
2270
2271 if (btnCabSerialIN->value()) {
2272 exch_in = exch_in.append(rec->getField(SRX));
2273 if (exch_in.length())
2274 exch_in += ' ';
2275 }
2276
2277 if (btnCabXchgIn->value()) {
2278 exch = rec->getField(XCHG1);
2279 while ((p = exch.find(":")) != string::npos) exch.erase(p,1);
2280 while ((p = exch.find(" ")) != string::npos) exch.erase(p,1);
2281 if (exch[0] == ' ') exch.erase(0,1);
2282 exch_in.append(exch);
2283 }
2284
2285 if (exch_in.length() > 14) exch_in = exch_in.substr(0,14);
2286 len = exch_in.length();
2287 if (len < 14) exch_in.append(14 - len, ' ');
2288
2289 if (btnCabState->value()) {
2290 state = rec->getField(STATE);
2291 if (!state.empty())
2292 qsoline.append(state).append(" ");
2293 }
2294
2295 if (btnCabCounty->value()) {
2296 county = rec->getField(CNTY);
2297 if (!county.empty())
2298 qsoline.append(county).append(" ");
2299 }
2300
2301 qsoline.append(exch_in);
2302
2303 fprintf (fp, "%s\n", qsoline.c_str());
2304 return;
2305 }
2306
WriteCabrillo()2307 void WriteCabrillo()
2308 {
2309 if (chkCabBrowser->nchecked() == 0) return;
2310
2311 cQsoRec *rec;
2312
2313 string title = _("Create cabrillo report");
2314 string filters = "TEXT\t*.txt";
2315 #ifdef __APPLE__
2316 filters.append("\n");
2317 #endif
2318 string strContest = "";
2319
2320 const char* p = FSEL::saveas( title.c_str(), filters.c_str(), "contest.txt");
2321
2322 if (!p) return;
2323 if (!*p) return;
2324
2325 for (int i = 0; i < chkCabBrowser->FLTK_nitems(); i++) {
2326 if (chkCabBrowser->checked(i + 1)) {
2327 rec = qsodb.getRec(i);
2328 rec->putField(EXPORT, "E");
2329 qsodb.qsoUpdRec (i, rec);
2330 }
2331 }
2332
2333 string sp = p;
2334 if (sp.find(".txt") == string::npos) sp.append(".txt");
2335 FILE *cabFile = fl_fopen (p, "w");
2336 if (!cabFile)
2337 return;
2338
2339 strContest = cboContest->value();
2340 contestnbr = (icontest)cboContest->index();
2341
2342 fprintf (cabFile,
2343 "START-OF-LOG: 3.0\n\
2344 CREATED-BY: %s %s\n\
2345 \n\
2346 # The callsign used during the contest.\n\
2347 CALLSIGN: %s\n\
2348 \n\
2349 # ASSISTED or NON-ASSISTED\n\
2350 CATEGORY-ASSISTED: \n\
2351 \n\
2352 # Band: ALL, 160M, 80M, 40M, 20M, 15M, 10M, 6M, 2M, 222, 432, 902, 1.2G\n\
2353 CATEGORY-BAND: \n\
2354 \n\
2355 # Mode: SSB, CW, RTTY, MIXED \n\
2356 CATEGORY-MODE: \n\
2357 \n\
2358 # Operator: SINGLE-OP, MULTI-OP, CHECKLOG \n\
2359 CATEGORY-OPERATOR: \n\
2360 \n\
2361 # Power: HIGH, LOW, QRP \n\
2362 CATEGORY-POWER: \n\
2363 \n\
2364 # Station: FIXED, MOBILE, PORTABLE, ROVER, EXPEDITION, HQ, SCHOOL \n\
2365 CATEGORY-STATION: \n\
2366 \n\
2367 # Time: 6-HOURS, 12-HOURS, 24-HOURS \n\
2368 CATEGORY-TIME: \n\
2369 \n\
2370 # Transmitter: ONE, TWO, LIMITED, UNLIMITED, SWL \n\
2371 CATEGORY-TRANSMITTER: \n\
2372 \n\
2373 # Overlay: ROOKIE, TB-WIRES, NOVICE-TECH, OVER-50 \n\
2374 CATEGORY-OVERLAY: \n\
2375 \n\
2376 # Integer number\n\
2377 CLAIMED-SCORE: \n\
2378 \n\
2379 # Name of the radio club with which the score should be aggregated.\n\
2380 CLUB: \n\
2381 \n\
2382 # Contest: AP-SPRINT, ARRL-10, ARRL-160, ARRL-DX-CW, ARRL-DX-SSB, ARRL-SS-CW,\n\
2383 # ARRL-SS-SSB, ARRL-UHF-AUG, ARRL-VHF-JAN, ARRL-VHF-JUN, ARRL-VHF-SEP,\n\
2384 # ARRL-RTTY, BARTG-RTTY, CQ-160-CW, CQ-160-SSB, CQ-WPX-CW, CQ-WPX-RTTY,\n\
2385 # CQ-WPX-SSB, CQ-VHF, CQ-WW-CW, CQ-WW-RTTY, CQ-WW-SSB, DARC-WAEDC-CW,\n\
2386 # DARC-WAEDC-RTTY, DARC-WAEDC-SSB, FCG-FQP, IARU-HF, JIDX-CW, JIDX-SSB,\n\
2387 # NAQP-CW, NAQP-RTTY, NAQP-SSB, NA-SPRINT-CW, NA-SPRINT-SSB, NCCC-CQP,\n\
2388 # NEQP, OCEANIA-DX-CW, OCEANIA-DX-SSB, RDXC, RSGB-IOTA, SAC-CW, SAC-SSB,\n\
2389 # STEW-PERRY, TARA-RTTY \n\
2390 CONTEST: %s\n\
2391 \n\
2392 # Optional email address\n\
2393 EMAIL: \n\
2394 \n\
2395 LOCATION: \n\
2396 \n\
2397 # Operator name\n\
2398 NAME: \n\
2399 \n\
2400 # Maximum 4 address lines.\n\
2401 ADDRESS: \n\
2402 ADDRESS: \n\
2403 ADDRESS: \n\
2404 ADDRESS: \n\
2405 \n\
2406 # A space-delimited list of operator callsign(s). \n\
2407 OPERATORS: \n\
2408 \n\
2409 # Offtime yyyy-mm-dd nnnn yyyy-mm-dd nnnn \n\
2410 # OFFTIME: \n\
2411 \n\
2412 # Soapbox comments.\n\
2413 SOAPBOX: \n\
2414 SOAPBOX: \n\
2415 SOAPBOX: \n\n",
2416 PACKAGE_NAME, PACKAGE_VERSION,
2417 progdefaults.myCall.c_str(),
2418 strContest.c_str() );
2419
2420 qsodb.SortByDate(progdefaults.sort_date_time_off);
2421 for (int i = 0; i < qsodb.nbrRecs(); i++) {
2422 rec = qsodb.getRec(i);
2423 if (rec->getField(EXPORT)[0] == 'E') {
2424 cabrillo_append_qso(cabFile, rec);
2425 rec->putField(EXPORT,"");
2426 qsodb.qsoUpdRec(i, rec);
2427 }
2428 }
2429 fprintf(cabFile, "END-OF-LOG:\n");
2430 fclose (cabFile);
2431 return;
2432 }
2433
2434 #if HAVE_STD_HASH
2435 # include <unordered_map>
2436 typedef std::unordered_map<string, unsigned> dxcc_entity_cache_t;
2437 #elif HAVE_STD_TR1_HASH
2438 # include <tr1/unordered_map>
2439 typedef tr1::unordered_map<string, unsigned> dxcc_entity_cache_t;
2440 #else
2441 # error "No std::hash or std::tr1::hash support"
2442 #endif
2443
2444 static dxcc_entity_cache_t dxcc_entity_cache;
2445 static bool dxcc_entity_cache_enabled = false;
2446
2447 #include "dxcc.h"
2448
dxcc_entity_cache_clear(void)2449 static void dxcc_entity_cache_clear(void)
2450 {
2451 if (dxcc_entity_cache_enabled)
2452 dxcc_entity_cache.clear();
2453 }
2454
dxcc_entity_cache_add(cQsoRec * r)2455 static void dxcc_entity_cache_add(cQsoRec* r)
2456 {
2457 if (!dxcc_entity_cache_enabled | !r)
2458 return;
2459
2460 const dxcc* e = dxcc_lookup(r->getField(CALL));
2461 if (e)
2462 dxcc_entity_cache[e->country]++;
2463 }
dxcc_entity_cache_add(cQsoDb & db)2464 static void dxcc_entity_cache_add(cQsoDb& db)
2465 {
2466 if (!dxcc_entity_cache_enabled)
2467 return;
2468
2469 int n = db.nbrRecs();
2470 for (int i = 0; i < n; i++)
2471 dxcc_entity_cache_add(db.getRec(i));
2472 if (!dxcc_entity_cache.empty()) {
2473 unsigned int un = dxcc_entity_cache.size();
2474 LOG_INFO("Found %u countries in %d QSO records", un, n);
2475 }
2476 }
2477
dxcc_entity_cache_rm(cQsoRec * r)2478 static void dxcc_entity_cache_rm(cQsoRec* r)
2479 {
2480 if (!dxcc_entity_cache_enabled || !r)
2481 return;
2482
2483 const dxcc* e = dxcc_lookup(r->getField(CALL));
2484 if (!e)
2485 return;
2486 dxcc_entity_cache_t::iterator i = dxcc_entity_cache.find(e->country);
2487 if (i != dxcc_entity_cache.end()) {
2488 if (i->second)
2489 i->second--;
2490 else
2491 dxcc_entity_cache.erase(i);
2492 }
2493 }
2494
dxcc_entity_cache_enable(bool v)2495 void dxcc_entity_cache_enable(bool v)
2496 {
2497 if (dxcc_entity_cache_enabled == v)
2498 return;
2499
2500 dxcc_entity_cache_clear();
2501 if ((dxcc_entity_cache_enabled = v))
2502 dxcc_entity_cache_add(qsodb);
2503 }
2504
qsodb_dxcc_entity_find(const char * country)2505 bool qsodb_dxcc_entity_find(const char* country)
2506 {
2507 return dxcc_entity_cache.find(country) != dxcc_entity_cache.end();
2508 }
2509
2510 //======================================================================
2511 // eQSL verification support
2512 //======================================================================
2513
2514 static string eqsl_download_name = "";
2515 static cQsoDb *eqsl_db = 0;
2516
verify_eqsl(void *)2517 void verify_eqsl(void *)
2518 {
2519 eqsl_db = new cQsoDb;
2520 LOG_INFO("VERIFY_EQSL: adifFile.do_readfile(%s)", eqsl_download_name.c_str());
2521 adifFile.do_readfile (eqsl_download_name.c_str(), eqsl_db);
2522
2523 if (eqsl_db->nbrRecs() == 0) {
2524 LOG_INFO("No records in eqsl download file");
2525 return;
2526 }
2527 LOG_INFO("logbook %d records, verify with %d records", qsodb.nbrRecs(), eqsl_db->nbrRecs());
2528
2529 int matchrec;
2530 cQsoRec *qrec, *lrec;
2531 int nverified = 0;
2532
2533 for (int i = 0; i < eqsl_db->nbrRecs(); i++) {
2534 lrec = eqsl_db->getRec(i);
2535 matchrec = qsodb.matched( lrec );
2536 if (matchrec != -1) {
2537 qrec = qsodb.getRec(matchrec);
2538 if (qrec->getField(EQSLRDATE)[0] == 0) {
2539 nverified++;
2540 qrec->putField(EQSLRDATE, zdate());
2541 }
2542 } else {
2543 LOG_INFO("Could not match %s on %s", lrec->getField(CALL), lrec->getField(QSO_DATE));
2544 }
2545 }
2546 LOG_INFO("%d records updated", nverified);
2547 delete eqsl_db;
2548 }
2549
cb_btn_verify_eqsl(Fl_Button *,void *)2550 void cb_btn_verify_eqsl(Fl_Button *, void *) {
2551 ENSURE_THREAD(FLMAIN_TID);
2552
2553 const char* p = FSEL::select(_("LoTW download file"), "ADIF\t*.{adi,adif}", LoTWDir.c_str());
2554
2555 if (!p) return;
2556 if (!*p) return;
2557
2558 eqsl_download_name = p;
2559
2560 Fl::awake(verify_eqsl);
2561
2562 }
2563
2564