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