1 /***************************************************************************
2                           qsodatadialog.cpp  -  description
3                              -------------------
4     begin                : Sat Dec 7 2002
5     copyright            : (C) 2002 by ARRL
6     author               : Jon Bloom
7     email                : jbloom@arrl.org
8     revision             : $Id: qsodatadialog.cpp,v 1.7 2013/03/01 12:59:37 k1mu Exp $
9  ***************************************************************************/
10 
11 #include "qsodatadialog.h"
12 #include <iostream>
13 #include <fstream>
14 #include <string>
15 #include <vector>
16 #include <algorithm>
17 
18 #ifdef HAVE_CONFIG_H
19 #include "sysconfig.h"
20 #endif
21 
22 #include "tqslvalidator.h"
23 #include "wx/valgen.h"
24 #include "wx/spinctrl.h"
25 #include "wx/statline.h"
26 #include "tqsllib.h"
27 #include "tqslexcept.h"
28 #include "tqsltrace.h"
29 #include "wxutil.h"
30 
31 using std::vector;
32 using std::ofstream;
33 using std::ios;
34 using std::cerr;
35 using std::endl;
36 
37 #define TQSL_ID_LOW 6000
38 
39 #ifdef __WIN32__
40 	#define TEXT_HEIGHT 24
41 	#define LABEL_HEIGHT 18
42 	#define TEXT_WIDTH 8
43 	#define TEXT_POINTS 10
44 	#define VSEP 3
45 	#define GEOM1 4
46 #elif defined(__APPLE__)
47 	#define TEXT_HEIGHT 24
48 	#define LABEL_HEIGHT 18
49 	#define TEXT_WIDTH 8
50 	#define TEXT_POINTS 10
51 	#define VSEP 3
52 	#define GEOM1 4
53 #else
54 	#define TEXT_HEIGHT 18
55 	#define LABEL_HEIGHT TEXT_HEIGHT
56 	#define TEXT_WIDTH 8
57 	#define TEXT_POINTS 12
58 	#define VSEP 4
59 	#define GEOM1 6
60 #endif
61 
62 #undef TEXT_HEIGHT
63 #define TEXT_HEIGHT -1
64 
65 #define SKIP_HEIGHT (TEXT_HEIGHT+VSEP)
66 
67 #define QD_CALL	TQSL_ID_LOW
68 #define QD_DATE	TQSL_ID_LOW+1
69 #define QD_TIME	TQSL_ID_LOW+2
70 #define QD_MODE	TQSL_ID_LOW+3
71 #define QD_BAND	TQSL_ID_LOW+4
72 #define QD_FREQ	TQSL_ID_LOW+5
73 #define QD_OK TQSL_ID_LOW+6
74 #define QD_CANCEL TQSL_ID_LOW+7
75 #define QD_RECNO TQSL_ID_LOW+8
76 #define QD_RECDOWN TQSL_ID_LOW+9
77 #define QD_RECUP TQSL_ID_LOW+10
78 #define QD_RECBOTTOM TQSL_ID_LOW+11
79 #define QD_RECTOP TQSL_ID_LOW+12
80 #define QD_RECNEW TQSL_ID_LOW+13
81 #define QD_RECDELETE TQSL_ID_LOW+14
82 #define QD_RECNOLABEL TQSL_ID_LOW+15
83 #define QD_HELP TQSL_ID_LOW+16
84 #define QD_PROPMODE TQSL_ID_LOW+17
85 #define QD_SATELLITE TQSL_ID_LOW+18
86 #define QD_RXBAND TQSL_ID_LOW+19
87 #define QD_RXFREQ TQSL_ID_LOW+20
88 
89 
set_font(wxWindow * w,wxFont & font)90 static void set_font(wxWindow *w, wxFont& font) {
91 #ifndef __WIN32__
92 	w->SetFont(font);
93 #endif
94 }
95 
96 // Images for buttons.
97 
98 #include "left.xpm"
99 #include "right.xpm"
100 #include "bottom.xpm"
101 #include "top.xpm"
102 
103 class choice {
104  public:
choice(const wxString & _value,const wxString & _display=wxT (""),int _low=0,int _high=0)105 	explicit choice(const wxString& _value, const wxString& _display = wxT(""), int _low = 0, int _high = 0) {
106 		value = _value;
107 		display = (_display == wxT("")) ? value : _display;
108 		low = _low;
109 		high = _high;
110 	}
111 	wxString value, display;
112 	int low, high;
operator ==(const choice & other)113 	bool operator ==(const choice& other) { return other.value == value; }
operator ==(const wxString & other)114 	bool operator ==(const wxString& other) { return other == value; }
115 };
116 
117 class valid_list : public vector<choice> {
118  public:
valid_list()119 	valid_list() {}
120 	valid_list(const char **values, int nvalues);
121 	wxString *GetChoices() const;
122 };
123 
valid_list(const char ** values,int nvalues)124 valid_list::valid_list(const char **values, int nvalues) {
125 	while(nvalues--)
126 		push_back(choice(wxString::FromUTF8(*(values++))));
127 }
128 
129 wxString *
GetChoices() const130 valid_list::GetChoices() const {
131 	wxString *ary = new wxString[size()];
132 	wxString *sit = ary;
133 	const_iterator it;
134 	for (it = begin(); it != end(); it++)
135 		*sit++ = (*it).display;
136 	return ary;
137 }
138 static bool
sat_cmp(const choice & p1,const choice & p2)139 sat_cmp(const choice& p1, const choice& p2) {
140         return p1.value < p2.value;
141 }
142 
143 static valid_list valid_modes;
144 static valid_list valid_bands;
145 static valid_list valid_rxbands;
146 static valid_list valid_propmodes;
147 static valid_list valid_satellites;
148 
149 static int
init_valid_lists()150 init_valid_lists() {
151 	tqslTrace("init_valid_lists", NULL);
152 	if (valid_bands.size() > 0)
153 		return 0;
154 	if (tqsl_init())
155 		return 1;
156 	int count;
157 	if (tqsl_getNumMode(&count))
158 		return 1;
159 	const char *cp, *cp1;
160 	for (int i = 0; i < count; i++) {
161 		if (tqsl_getMode(i, &cp, 0))
162 			return 1;
163 		valid_modes.push_back(choice(wxString::FromUTF8(cp)));
164 	}
165 	valid_rxbands.push_back(choice(wxT(""), _("NONE")));
166 	if (tqsl_getNumBand(&count))
167 		return 1;
168 	for (int i = 0; i < count; i++) {
169 		int low, high, scale;
170 		if (tqsl_getBand(i, &cp, &cp1, &low, &high))
171 			return 1;
172 		wxString low_s = wxString::Format(wxT("%d"), low);
173 		wxString high_s = wxString::Format(wxT("%d"), high);
174 		const char *hz;
175 		if (!strcmp(cp1, "HF")) {
176 			hz = "kHz";
177 			scale = 1;		// Config file freqs are in KHz
178 		} else {
179 			hz = "mHz";
180 			scale = 1000;		// Freqs are in MHz for VHF/UHF.
181 		}
182 		if (low >= 1000) {
183 			low_s = wxString::Format(wxT("%g"), low / 1000.0);
184 			high_s = wxString::Format(wxT("%g"), high / 1000.0);
185 			if (!strcmp(cp1, "HF")) {
186 				hz = "MHz";
187 			} else {
188 				hz = "GHz";
189 			}
190 			if (high == 0)
191 				high_s = _("UP");
192 		}
193 		wxString display = wxString::Format(wxT("%hs (%s-%s %hs)"), cp,
194 			low_s.c_str(), high_s.c_str(), hz);
195 		valid_bands.push_back(choice(wxString::FromUTF8(cp), display, low*scale, high*scale));
196 		valid_rxbands.push_back(choice(wxString::FromUTF8(cp), display, low*scale, high*scale));
197 	}
198 	valid_propmodes.push_back(choice(wxT(""), _("NONE")));
199 	if (tqsl_getNumPropagationMode(&count))
200 		return 1;
201 	for (int i = 0; i < count; i++) {
202 		if (tqsl_getPropagationMode(i, &cp, &cp1))
203 			return 1;
204 		valid_propmodes.push_back(choice(wxString::FromUTF8(cp), wxString::FromUTF8(cp1)));
205 	}
206 	valid_satellites.push_back(choice(wxT(""), _("NONE")));
207 	if (tqsl_getNumSatellite(&count))
208 		return 1;
209 	for (int i = 0; i < count; i++) {
210 		if (tqsl_getSatellite(i, &cp, &cp1, 0, 0))
211 			return 1;
212 		valid_satellites.push_back(choice(wxString::FromUTF8(cp), wxString::Format(wxT("[%hs] %hs"), cp, cp1)));
213 	}
214 	sort(valid_satellites.begin(), valid_satellites.end(), sat_cmp);
215 	return 0;
216 }
217 
218 #define LABEL_WIDTH (22*TEXT_WIDTH)
219 
BEGIN_EVENT_TABLE(QSODataDialog,wxDialog)220 BEGIN_EVENT_TABLE(QSODataDialog, wxDialog)
221 	EVT_COMBOBOX(-1, QSODataDialog::OnFieldChanged)
222 	EVT_TEXT(-1, QSODataDialog::OnFieldChanged)
223 	EVT_BUTTON(QD_OK, QSODataDialog::OnOk)
224 	EVT_BUTTON(QD_CANCEL, QSODataDialog::OnCancel)
225 	EVT_BUTTON(QD_HELP, QSODataDialog::OnHelp)
226 	EVT_BUTTON(QD_RECDOWN, QSODataDialog::OnRecDown)
227 	EVT_BUTTON(QD_RECUP, QSODataDialog::OnRecUp)
228 	EVT_BUTTON(QD_RECBOTTOM, QSODataDialog::OnRecBottom)
229 	EVT_BUTTON(QD_RECTOP, QSODataDialog::OnRecTop)
230 	EVT_BUTTON(QD_RECNEW, QSODataDialog::OnRecNew)
231 	EVT_BUTTON(QD_RECDELETE, QSODataDialog::OnRecDelete)
232 END_EVENT_TABLE()
233 
234 QSODataDialog::QSODataDialog(wxWindow *parent, wxString& filename, wxHtmlHelpController *help, QSORecordList *reclist, wxWindowID id, const wxString& title)
235 	: wxDialog(parent, id, title), _reclist(reclist), _isend(false), _filename(filename), _help(help) {
236 	tqslTrace("QSODataDialog::QSODataDialog", "parent=0x%lx, reclist=0x%lx, id=0x%lx, %s", reinterpret_cast<void *>(parent), reinterpret_cast<void *>(reclist), reinterpret_cast<void *>(id), S(title));
237 	wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
238 	wxFont font = GetFont();
239 //	font.SetPointSize(TEXT_POINTS);
240 	set_font(this, font);
241 
242 #define QD_MARGIN 3
243 
244 	if (init_valid_lists()) {
245 		char err[256];
246 		strncpy(err, getLocalizedErrorString().ToUTF8(), sizeof err);
247 		throw TQSLException(err);
248 	}
249 	// Call sign
250 	wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
251 	sizer->Add(new wxStaticText(this, -1, _("Call Sign:"), wxDefaultPosition,
252 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
253 	_call_ctrl = new wxTextCtrl(this, QD_CALL, wxT(""), wxDefaultPosition, wxSize(14*TEXT_WIDTH, TEXT_HEIGHT),
254 		0, wxTextValidator(wxFILTER_NONE, &rec._call));
255 	sizer->Add(_call_ctrl, 0, wxALL, QD_MARGIN);
256 	topsizer->Add(sizer, 0);
257 	// Date
258 	sizer = new wxBoxSizer(wxHORIZONTAL);
259 	sizer->Add(new wxStaticText(this, -1, _("UTC Date (YYYY-MM-DD):"), wxDefaultPosition,
260 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
261 	_date_ctrl = new wxTextCtrl(this, QD_DATE, wxT(""), wxDefaultPosition, wxSize(14*TEXT_WIDTH, TEXT_HEIGHT),
262 		0, TQSLDateValidator(&rec._date));
263 	sizer->Add(_date_ctrl, 0, wxALL, QD_MARGIN);
264 	topsizer->Add(sizer, 0);
265 	// Time
266 	sizer = new wxBoxSizer(wxHORIZONTAL);
267 	sizer->Add(new wxStaticText(this, -1, _("UTC Time (HHMM):"), wxDefaultPosition,
268 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
269 	_time_ctrl = new wxTextCtrl(this, QD_TIME, wxT(""), wxDefaultPosition, wxSize(14*TEXT_WIDTH, TEXT_HEIGHT),
270 		0, TQSLTimeValidator(&rec._time));
271 	sizer->Add(_time_ctrl, 0, wxALL, QD_MARGIN);
272 	topsizer->Add(sizer, 0);
273 	// Mode
274 	sizer = new wxBoxSizer(wxHORIZONTAL);
275 	wxString *choices = valid_modes.GetChoices();
276 	sizer->Add(new wxStaticText(this, -1, _("Mode:"), wxDefaultPosition,
277 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
278 	sizer->Add(new wxChoice(this, QD_MODE, wxDefaultPosition, wxDefaultSize,
279 		valid_modes.size(), choices, 0, wxGenericValidator(&_mode)), 0, wxALL, QD_MARGIN);
280 	delete[] choices;
281 	topsizer->Add(sizer, 0);
282 	// Band
283 	sizer = new wxBoxSizer(wxHORIZONTAL);
284 	choices = valid_bands.GetChoices();
285 	sizer->Add(new wxStaticText(this, -1, _("Band:"), wxDefaultPosition,
286 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
287 	_band_ctrl = new wxChoice(this, QD_BAND, wxDefaultPosition, wxDefaultSize,
288 		valid_bands.size(), choices, 0, wxGenericValidator(&_band));
289 	sizer->Add(_band_ctrl, 0, wxALL, QD_MARGIN);
290 	delete[] choices;
291 	topsizer->Add(sizer, 0);
292 	// RX Band
293 	sizer = new wxBoxSizer(wxHORIZONTAL);
294 	choices = valid_rxbands.GetChoices();
295 	sizer->Add(new wxStaticText(this, -1, _("RX Band:"), wxDefaultPosition,
296 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
297 	_rxband_ctrl = new wxChoice(this, QD_RXBAND, wxDefaultPosition, wxDefaultSize,
298 		valid_rxbands.size(), choices, 0, wxGenericValidator(&_rxband));
299 	sizer->Add(_rxband_ctrl, 0, wxALL, QD_MARGIN);
300 	delete[] choices;
301 	topsizer->Add(sizer, 0);
302 	// Frequency
303 	sizer = new wxBoxSizer(wxHORIZONTAL);
304 	sizer->Add(new wxStaticText(this, -1, _("Frequency (MHz):"), wxDefaultPosition,
305 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
306 	sizer->Add(new wxTextCtrl(this, QD_FREQ, wxT(""), wxDefaultPosition, wxSize(14*TEXT_WIDTH, TEXT_HEIGHT),
307 		0, wxTextValidator(wxFILTER_NONE, &rec._freq)), 0, wxALL, QD_MARGIN);
308 	topsizer->Add(sizer, 0);
309 	// RX Frequency
310 	sizer = new wxBoxSizer(wxHORIZONTAL);
311 	sizer->Add(new wxStaticText(this, -1, _("RX Frequency (MHz):"), wxDefaultPosition,
312 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
313 	sizer->Add(new wxTextCtrl(this, QD_RXFREQ, wxT(""), wxDefaultPosition, wxSize(14*TEXT_WIDTH, TEXT_HEIGHT),
314 		0, wxTextValidator(wxFILTER_NONE, &rec._rxfreq)), 0, wxALL, QD_MARGIN);
315 	topsizer->Add(sizer, 0);
316 	// Propagation Mode
317 	sizer = new wxBoxSizer(wxHORIZONTAL);
318 	choices = valid_propmodes.GetChoices();
319 	sizer->Add(new wxStaticText(this, -1, _("Propagation Mode:"), wxDefaultPosition,
320 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
321 	sizer->Add(new wxChoice(this, QD_PROPMODE, wxDefaultPosition, wxDefaultSize,
322 		valid_propmodes.size(), choices, 0, wxGenericValidator(&_propmode)), 0, wxALL, QD_MARGIN);
323 	delete[] choices;
324 	topsizer->Add(sizer, 0);
325 	// Satellite
326 	sizer = new wxBoxSizer(wxHORIZONTAL);
327 	choices = valid_satellites.GetChoices();
328 	sizer->Add(new wxStaticText(this, -1, _("Satellite:"), wxDefaultPosition,
329 		wxSize(LABEL_WIDTH, TEXT_HEIGHT), wxALIGN_RIGHT), 0, wxALL, QD_MARGIN);
330 	sizer->Add(new wxChoice(this, QD_SATELLITE, wxDefaultPosition, wxDefaultSize,
331 		valid_satellites.size(), choices, 0, wxGenericValidator(&_satellite)), 0, wxALL, QD_MARGIN);
332 	delete[] choices;
333 	topsizer->Add(sizer, 0);
334 
335 	if (_reclist != 0) {
336 		_newrec = -1;
337 		if (_reclist->empty()) {
338 			_reclist->push_back(QSORecord());
339 			_newrec = 1;
340 		}
341 		topsizer->Add(new wxStaticLine(this, -1), 0, wxEXPAND|wxLEFT|wxRIGHT, 10);
342 		_recno_label_ctrl = new wxStaticText(this, QD_RECNOLABEL, wxT(""), wxDefaultPosition,
343 			wxSize(20*TEXT_WIDTH, TEXT_HEIGHT), wxST_NO_AUTORESIZE|wxALIGN_CENTER);
344 		topsizer->Add(_recno_label_ctrl, 0, wxALIGN_CENTER|wxALL, 5);
345 		_recno = 1;
346 		sizer = new wxBoxSizer(wxHORIZONTAL);
347 		// Use a really tiny label font on the buttons, as the labels are there
348 		// for accessibility only.
349 		wxFont f(1, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
350 		_recbottom_ctrl = new wxBitmapButton(this, QD_RECBOTTOM, wxBitmap(bottom_xpm));
351 		_recbottom_ctrl->SetLabel(_("Go to the first QSO in this log"));
352 		_recbottom_ctrl->SetFont(f);
353 		sizer->Add(_recbottom_ctrl, 0, wxTOP|wxBOTTOM, 5);
354 		_recdown_ctrl = new wxBitmapButton(this, QD_RECDOWN, wxBitmap(left_xpm));
355 		_recdown_ctrl->SetLabel(_("Go to the previous QSO in this log"));
356 		_recdown_ctrl->SetFont(f);
357 		sizer->Add(_recdown_ctrl, 0, wxTOP|wxBOTTOM, 5);
358 		_recno_ctrl = new wxTextCtrl(this, QD_RECNO, wxT(""), wxDefaultPosition,
359 			wxSize(4*TEXT_WIDTH, TEXT_HEIGHT));
360 		_recno_ctrl->Enable(FALSE);
361 		sizer->Add(_recno_ctrl, 0, wxALL, 5);
362 		_recup_ctrl = new wxBitmapButton(this, QD_RECUP, wxBitmap(right_xpm));
363 		_recup_ctrl->SetLabel(_("Go to the next QSO in this log"));
364 		_recup_ctrl->SetFont(f);
365 		sizer->Add(_recup_ctrl, 0, wxTOP|wxBOTTOM, 5);
366 		_rectop_ctrl = new wxBitmapButton(this, QD_RECTOP, wxBitmap(top_xpm));
367 		_rectop_ctrl->SetLabel(_("Go to the last QSO in this log"));
368 		_rectop_ctrl->SetFont(f);
369 		sizer->Add(_rectop_ctrl, 0, wxTOP|wxBOTTOM, 5);
370 		if (_reclist->size() > 0)
371 			rec = *(_reclist->begin());
372 		topsizer->Add(sizer, 0, wxALIGN_CENTER);
373 		sizer = new wxBoxSizer(wxHORIZONTAL);
374 		_recadd_ctrl = new wxButton(this, QD_RECNEW, _("Add QSO"));
375 		sizer->Add(_recadd_ctrl, 0, wxALL, 5);
376 		sizer->Add(new wxButton(this, QD_RECDELETE, _("Delete")), 0, wxALL, 5);
377 		topsizer->Add(sizer, 0, wxALIGN_CENTER);
378 	}
379 
380 	topsizer->Add(new wxStaticLine(this, -1), 0, wxEXPAND|wxLEFT|wxRIGHT, 10);
381 	sizer = new wxBoxSizer(wxHORIZONTAL);
382 	sizer->Add(new wxButton(this, QD_HELP, _("Help")), 0, wxALL, 10);
383 	sizer->Add(new wxButton(this, QD_CANCEL, _("Cancel")), 0, wxALL, 10);
384 	sizer->Add(new wxButton(this, QD_OK, _("OK")), 0, wxALL, 10);
385 	topsizer->Add(sizer, 0, wxALIGN_CENTER);
386 
387 	UpdateControls();
388 
389 	SetAutoLayout(TRUE);
390 	SetSizer(topsizer);
391 	topsizer->Fit(this);
392 	topsizer->SetSizeHints(this);
393 
394 	SetFocus();
395 	CentreOnParent();
396 	if (_reclist) rec = (*_reclist)[0];
397 	_recno = 1;
398 	TransferDataToWindow();
399 }
400 
~QSODataDialog()401 QSODataDialog::~QSODataDialog() {
402 }
403 
404 void
OnFieldChanged(wxCommandEvent & event)405 QSODataDialog::OnFieldChanged(wxCommandEvent& event) {
406 	// If there's no record currently being added, enable
407 	// the "add" button
408 
409 	if (_newrec < 0) {
410 		_recadd_ctrl->Enable(true);
411 		return;
412 	}
413 	_recadd_ctrl->Enable(false);
414 	// If we're not on the record pending add, get out
415 	if (_newrec != _recno)
416 		return;
417 	// If there's an error in the data, can't Add.
418 	if (!wxDialog::TransferDataFromWindow())
419 		return;
420 	// Update the band selections to match the frequencies
421 	double freq, rxfreq;
422 	bool freqOK = false, rxfreqOK = false;
423 
424 	char *oldloc = setlocale(LC_ALL, "C");
425 	if (!rec._freq.IsEmpty()) {
426 		freqOK = rec._freq.Trim(TRUE).Trim(FALSE).ToDouble(&freq);
427 	}
428 	if (!rec._rxfreq.IsEmpty()) {
429 		rxfreqOK = rec._rxfreq.Trim(TRUE).Trim(FALSE).ToDouble(&rxfreq);
430 	}
431 	setlocale(LC_ALL, oldloc);
432 	if (freqOK) {
433 		freq = freq * 1000.0;		// Freq is is MHz but the limits are in KHz
434 		for (size_t i = 0; i < valid_bands.size(); i++) {
435 			if (freq >= valid_bands[i].low && freq <= valid_bands[i].high) {
436 				_band = i;
437 				_band_ctrl->SetSelection(_band);
438 				break;
439 			}
440 		}
441 	}
442 	if (rxfreqOK) {
443 		rxfreq = rxfreq * 1000.0;		// Freq is is MHz but the limits are in KHz
444 		for (size_t i = 0; i < valid_bands.size(); i++) {
445 			if (rxfreq >= valid_bands[i].low && rxfreq <= valid_bands[i].high) {
446 				_rxband = i+1;	// Add one for 'NONE'.
447 				_rxband_ctrl->SetSelection(_rxband);
448 				break;
449 			}
450 		}
451 	}
452 
453 	if (_call_ctrl->GetValue() == wxT("") || _call_ctrl->GetValue() == wxT("NONE"))	// No callsign
454 		return;
455 	if (_date_ctrl->GetValue() == wxT(""))	// No date
456 		return;
457 	if (_time_ctrl->GetValue() == wxT(""))	// No time
458 		return;
459 	// All is OK, allow save.
460 	_recadd_ctrl->Enable(true);
461 }
462 
463 bool
TransferDataFromWindow()464 QSODataDialog::TransferDataFromWindow() {
465 	tqslTrace("QSODataDialog::TransferDataFromWindow", NULL);
466 	rec._call.Trim(FALSE).Trim(TRUE);
467 	if (!wxDialog::TransferDataFromWindow())
468 		return false;
469 	if (_mode < 0 || _mode >= static_cast<int>(valid_modes.size()))
470 		return false;
471 	rec._mode = valid_modes[_mode].value;
472 	if (_band < 0 || _band >= static_cast<int>(valid_bands.size()))
473 		return false;
474 	rec._band = valid_bands[_band].value;
475 	if (_rxband < 0) {
476 		rec._rxband = wxT("");
477 	} else {
478 		rec._rxband = valid_rxbands[_rxband].value;
479 	}
480 	rec._freq.Trim(FALSE).Trim(TRUE);
481 	rec._rxfreq.Trim(FALSE).Trim(TRUE);
482 	if (_propmode < 0) {
483 		rec._propmode = wxT("");
484 	} else {
485 		rec._propmode = valid_propmodes[_propmode].value;
486 	}
487 	if (_satellite < 0) {
488 		rec._satellite = wxT("");
489 	} else {
490 		rec._satellite = valid_satellites[_satellite].value;
491 	}
492 
493 	double freq;
494 	// Set locale to "C" so the . is forced as a decimal point
495 	char *oldloc = setlocale(LC_ALL, "C");
496 
497 		if (!rec._freq.IsEmpty()) {
498 			if (!rec._freq.ToDouble(&freq)) {
499 				wxMessageBox(_("QSO Frequency is invalid"), _("QSO Data Error"),
500 					wxOK | wxICON_EXCLAMATION, this);
501 				setlocale(LC_ALL, oldloc);
502 				return false;
503 			}
504 			freq = freq * 1000.0;		// Freq is is MHz but the limits are in KHz
505 			if (freq < valid_bands[_band].low || (valid_bands[_band].high > 0 && freq > valid_bands[_band].high)) {
506 				wxMessageBox(_("QSO Frequency is out of range for the selected band"), _("QSO Data Error"),
507 					wxOK | wxICON_EXCLAMATION, this);
508 				setlocale(LC_ALL, oldloc);
509 				return false;
510 			}
511 			_band_ctrl->SetSelection(_band);
512 		}
513 		if (!rec._rxfreq.IsEmpty()) {
514 			if (!rec._rxfreq.ToDouble(&freq)) {
515 				wxMessageBox(_("QSO RX Frequency is invalid"), _("QSO Data Error"),
516 					wxOK | wxICON_EXCLAMATION, this);
517 				setlocale(LC_ALL, oldloc);
518 				return false;
519 			}
520 			freq = freq * 1000.0;		// Freq is is MHz but the limits are in KHz
521 			if (freq < valid_rxbands[_rxband].low || (valid_rxbands[_rxband].high > 0 && freq > valid_rxbands[_rxband].high)) {
522 				wxMessageBox(_("QSO RX Frequency is out of range for the selected band"), _("QSO Data Error"),
523 					wxOK | wxICON_EXCLAMATION, this);
524 				setlocale(LC_ALL, oldloc);
525 				return false;
526 			}
527 			_rxband_ctrl->SetSelection(_rxband);
528 		}
529 		// No other numeric conversions, revert to original locale
530 		setlocale(LC_ALL, oldloc);
531 		if (rec._freq.IsEmpty() && rec._band.IsEmpty()) {
532 			wxMessageBox(_("You must select a band or enter a frequency"), _("QSO Data Error"),
533 				wxOK | wxICON_EXCLAMATION, this);
534 			return false;
535 		}
536 		if (!_isend && rec._call == wxT("")) {
537 			wxMessageBox(_("Call Sign cannot be empty"), _("QSO Data Error"),
538 				wxOK | wxICON_EXCLAMATION, this);
539 			return false;
540 		}
541 		if (rec._propmode == wxT("SAT") && rec._satellite == wxT("")) {
542 			wxMessageBox(_("'Satellite' propagation mode selected, so a Satellite must be chosen"), _("QSO Data Error"),
543 				wxOK | wxICON_EXCLAMATION, this);
544 			return false;
545 		}
546 		if (rec._propmode != wxT("SAT") && rec._satellite != wxT("")) {
547 			wxMessageBox(_("Satellite choice requires that Propagation Mode be 'Satellite'"), _("QSO Data Error"),
548 				wxOK | wxICON_EXCLAMATION, this);
549 			return false;
550 		}
551 	if (_reclist != 0)
552 		(*_reclist)[_recno-1] = rec;
553 	return true;
554 }
555 
556 bool
TransferDataToWindow()557 QSODataDialog::TransferDataToWindow() {
558 	tqslTrace("QSODataDialog::TransferDataToWindow", NULL);
559 	valid_list::iterator it;
560 	wxString mode = rec._mode.Upper();
561 	if ((it = find(valid_modes.begin(), valid_modes.end(), mode))  != valid_modes.end()) {
562 		_mode = distance(valid_modes.begin(), it);
563 	} else {
564 		wxLogWarning(_("QSO Data: Invalid Mode ignored - %s"), mode.c_str());
565 		_mode = 0;
566 	}
567 	if ((it = find(valid_bands.begin(), valid_bands.end(), rec._band.Upper())) != valid_bands.end()) {
568 		_band = distance(valid_bands.begin(), it);
569 		_band_ctrl->SetSelection(_band);
570 	}
571 	if ((it = find(valid_rxbands.begin(), valid_rxbands.end(), rec._rxband.Upper())) != valid_rxbands.end()) {
572 		_rxband = distance(valid_rxbands.begin(), it);
573 		_rxband_ctrl->SetSelection(_rxband);
574 	}
575 	if ((it = find(valid_propmodes.begin(), valid_propmodes.end(), rec._propmode.Upper())) != valid_propmodes.end())
576 		_propmode = distance(valid_propmodes.begin(), it);
577 	if ((it = find(valid_satellites.begin(), valid_satellites.end(), rec._satellite.Upper())) != valid_satellites.end())
578 		_satellite = distance(valid_satellites.begin(), it);
579 	return wxDialog::TransferDataToWindow();
580 }
581 
582 bool
WriteQSOFile(QSORecordList & recs,const char * fname)583 QSODataDialog::WriteQSOFile(QSORecordList& recs, const char *fname) {
584 	tqslTrace("QSODataDialog::writeQSOFile", "fname=%s", fname);
585 	if (recs.empty()) {
586 		wxLogWarning(_("No QSO records"));
587 		return true;
588 	}
589 	wxString s_fname;
590 	if (fname)
591 		s_fname = wxString::FromUTF8(fname);
592 	wxString path, basename, type;
593 	wxFileName::SplitPath(s_fname, &path, &basename, &type);
594 	if (type != wxT(""))
595 		basename += wxT(".") + type;
596 	else
597 		basename += wxT(".adi");
598 	if (path == wxT(""))
599 		path = wxConfig::Get()->Read(wxT("QSODataPath"), wxT(""));
600 	s_fname = wxFileSelector(_("Save File"), path, basename, wxT("adi"),
601 #if !defined(__APPLE__) && !defined(_WIN32)
602 		_("ADIF files (*.adi;*.adif;*.ADI;*.ADIF)|*.adi;*.adif;*.ADI;*.ADIF|All files (*.*)|*.*"),
603 #else
604 		_("ADIF files (*.adi;*.adif)|*.adi;*.adif|All files (*.*)|*.*"),
605 #endif
606 		wxFD_SAVE|wxFD_OVERWRITE_PROMPT, this);
607 	if (s_fname == wxT("")) { // Cancel
608 		return false;
609 	}
610 	wxConfig::Get()->Write(wxT("QSODataPath"), wxPathOnly(s_fname));
611 
612 #ifdef _WIN32
613 	wchar_t* lfn = utf8_to_wchar(s_fname.ToUTF8());
614 	ofstream out(lfn, ios::out|ios::trunc|ios::binary);
615 	free_wchar(lfn);
616 #else
617 	ofstream out(s_fname.ToUTF8(), ios::out|ios::trunc|ios::binary);
618 #endif
619 	if (!out.is_open())
620 		return false;
621 	unsigned char buf[256];
622 	int rec_cnt = 0;
623 	QSORecordList::iterator it;
624 	for (it = recs.begin(); it != recs.end(); it++) {
625 		wxString dtstr;
626 		if (it->_call == wxT("NONE"))		// Skipped back on added record
627 			continue;
628 		rec_cnt++;
629 		tqsl_adifMakeField("CALL", 0, (const unsigned char*)(const char *)it->_call.ToUTF8(), -1, buf, sizeof buf);
630 		out << buf << endl;
631 		tqsl_adifMakeField("BAND", 0, (const unsigned char*)(const char *)it->_band.ToUTF8(), -1, buf, sizeof buf);
632 		out << "   " << buf << endl;
633 		tqsl_adifMakeField("MODE", 0, (const unsigned char*)(const char *)it->_mode.ToUTF8(), -1, buf, sizeof buf);
634 		out << "   " << buf << endl;
635 		dtstr.Printf(wxT("%04d%02d%02d"), it->_date.year, it->_date.month, it->_date.day);
636 		tqsl_adifMakeField("QSO_DATE", 0, (const unsigned char*)(const char *)dtstr.ToUTF8(), -1, buf, sizeof buf);
637 		out << "   " << buf << endl;
638 		dtstr.Printf(wxT("%02d%02d%02d"), it->_time.hour, it->_time.minute, it->_time.second);
639 		tqsl_adifMakeField("TIME_ON", 0, (const unsigned char*)(const char *)dtstr.ToUTF8(), -1, buf, sizeof buf);
640 		out << "   " << buf << endl;
641 		if (it->_freq != wxT("")) {
642 			tqsl_adifMakeField("FREQ", 0, (const unsigned char*)(const char *)it->_freq.ToUTF8(), -1, buf, sizeof buf);
643 			out << "   " << buf << endl;
644 		}
645 		if (it->_rxband != wxT("")) {
646 			tqsl_adifMakeField("BAND_RX", 0, (const unsigned char*)(const char *)it->_rxband.ToUTF8(), -1, buf, sizeof buf);
647 			out << "   " << buf << endl;
648 		}
649 		if (it->_rxfreq != wxT("")) {
650 			tqsl_adifMakeField("FREQ_RX", 0, (const unsigned char*)(const char *)it->_rxfreq.ToUTF8(), -1, buf, sizeof buf);
651 			out << "   " << buf << endl;
652 		}
653 		if (it->_propmode != wxT("")) {
654 			tqsl_adifMakeField("PROP_MODE", 0, (const unsigned char*)(const char *)it->_propmode.ToUTF8(), -1, buf, sizeof buf);
655 			out << "   " << buf << endl;
656 		}
657 		if (it->_satellite != wxT("")) {
658 			tqsl_adifMakeField("SAT_NAME", 0, (const unsigned char*)(const char *)it->_satellite.ToUTF8(), -1, buf, sizeof buf);
659 			out << "   " << buf << endl;
660 		}
661 		out << "<EOR>" << endl;
662 	}
663 	out.close();
664 	wxLogMessage(_("Wrote %d QSO records to %s"), rec_cnt, s_fname.c_str());
665 	return true;
666 }
667 
668 void
OnOk(wxCommandEvent &)669 QSODataDialog::OnOk(wxCommandEvent&) {
670 	tqslTrace("QSODataDialog::OnOk", NULL);
671 	if (!Validate())
672 		return;
673 	_isend = true;
674 	TransferDataFromWindow();
675 	_isend = false;
676 	if (rec._call == wxT("") && _recno == static_cast<int>(_reclist->size())) {
677 		_reclist->erase(_reclist->begin() + _recno - 1);
678 		if (WriteQSOFile(*_reclist, _filename.ToUTF8()))
679 			EndModal(wxID_OK);
680 	} else if (Validate() && TransferDataFromWindow()) {
681 		if (WriteQSOFile(*_reclist, _filename.ToUTF8()))
682 			EndModal(wxID_OK);
683 	}
684 }
685 
686 void
OnCancel(wxCommandEvent &)687 QSODataDialog::OnCancel(wxCommandEvent&) {
688 	tqslTrace("QSODataDialog::OnCancel", NULL);
689 	EndModal(wxID_CANCEL);
690 }
691 
692 void
OnHelp(wxCommandEvent &)693 QSODataDialog::OnHelp(wxCommandEvent&) {
694 	tqslTrace("QSODataDialog::OnHelp", NULL);
695 	if (_help)
696 		_help->Display(wxT("qsodata.htm"));
697 }
698 
699 void
SetRecno(int new_recno)700 QSODataDialog::SetRecno(int new_recno) {
701 	tqslTrace("QSODataDialog::SetRecno", "new_recno=%d", new_recno);
702 	if (_reclist == NULL || new_recno < 1)
703 		return;
704 	if (Validate() && TransferDataFromWindow()) {
705 //   		(*_reclist)[_recno-1] = rec;
706 		if (_reclist && new_recno > static_cast<int>(_reclist->size())) {
707 			_newrec = _reclist->size() + 1;
708 			QSORecord newrec;
709 			// Copy QSO fields from current record
710 			if (_recno > 0) {
711 				newrec = (*_reclist)[_recno-1];
712 				newrec._call = wxT("");
713 			}
714 			_reclist->push_back(newrec);
715 		}
716 		_recno = new_recno;
717 		if (_reclist) rec = (*_reclist)[_recno-1];
718 		TransferDataToWindow();
719 		UpdateControls();
720 		_call_ctrl->SetFocus();
721 	}
722 }
723 
724 void
OnRecDown(wxCommandEvent &)725 QSODataDialog::OnRecDown(wxCommandEvent&) {
726 	tqslTrace("QSODataDialog::OnRecDown", NULL);
727 	if (_reclist == 0)
728 		return;
729 	if (_recno == _newrec) { 		// Backing up from a record being added
730 		if (rec._call == wxT("")) { 	// And the call is empty
731 			rec._call = wxT("NONE");
732 			_call_ctrl->SetValue(wxT("NONE"));
733 		}
734 	}
735 	if (_recno > 1)
736 		SetRecno(_recno - 1);
737 }
738 
739 void
OnRecUp(wxCommandEvent &)740 QSODataDialog::OnRecUp(wxCommandEvent&) {
741 	tqslTrace("QSODataDialog::OnRecUp", NULL);
742 	SetRecno(_recno + 1);
743 }
744 
745 void
OnRecBottom(wxCommandEvent &)746 QSODataDialog::OnRecBottom(wxCommandEvent&) {
747 	tqslTrace("QSODataDialog::OnRecBottom", NULL);
748 	if (_reclist == 0)
749 		return;
750 	if (_recno == _newrec) { 		// Backing up from a record being added
751 		if (rec._call == wxT("")) { 	// And the call is empty
752 			rec._call = wxT("NONE");
753 			_call_ctrl->SetValue(wxT("NONE"));
754 			_reclist->erase(_reclist->begin() + _recno - 1);
755 		}
756 	}
757 	SetRecno(1);
758 }
759 
760 void
OnRecTop(wxCommandEvent &)761 QSODataDialog::OnRecTop(wxCommandEvent&) {
762 	tqslTrace("QSODataDialog::OnRecTop", NULL);
763 	if (_reclist == 0)
764 		return;
765 	if (_recno == _newrec) { 		// Backing up from a record being added
766 		if (rec._call == wxT("")) { 	// And the call is empty
767 			_reclist->erase(_reclist->begin() + _recno - 1);
768 			if (_reclist->empty())
769 				_reclist->push_back(QSORecord());
770 			if (_recno > static_cast<int>(_reclist->size()))
771 				_recno = _reclist->size();
772 			rec = (*_reclist)[_recno-1];
773 			TransferDataToWindow();
774 		}
775 		_recadd_ctrl->Enable(true);
776 	}
777 	SetRecno(_reclist->size());
778 }
779 
780 void
OnRecNew(wxCommandEvent &)781 QSODataDialog::OnRecNew(wxCommandEvent&) {
782 	tqslTrace("QSODataDialog::OnRecNew", NULL);
783 	if (_reclist == 0)
784 		return;
785 	SetRecno(_reclist->size()+1);
786 }
787 
788 void
OnRecDelete(wxCommandEvent &)789 QSODataDialog::OnRecDelete(wxCommandEvent&) {
790 	tqslTrace("QSODataDialog::OnRecDelete", NULL);
791 	if (_reclist == 0)
792 		return;
793 	_reclist->erase(_reclist->begin() + _recno - 1);
794 	if (_reclist->empty())
795 		_reclist->push_back(QSORecord());
796 	if (_recno > static_cast<int>(_reclist->size()))
797 		_recno = _reclist->size();
798 	rec = (*_reclist)[_recno-1];
799 	TransferDataToWindow();
800 	UpdateControls();
801 }
802 
803 void
UpdateControls()804 QSODataDialog::UpdateControls() {
805 	tqslTrace("QSODataDialog::UpdateControls", NULL);
806 	if (_reclist == 0)
807 		return;
808 	_recdown_ctrl->Enable(_recno > 1);
809 	_recbottom_ctrl->Enable(_recno > 1);
810 	_recup_ctrl->Enable(_recno < static_cast<int>(_reclist->size()));
811 	_rectop_ctrl->Enable(_recno < static_cast<int>(_reclist->size()));
812 	_recno_ctrl->SetValue(wxString::Format(wxT("%d"), _recno));
813 	if (_reclist->size() == 1) {
814 		_recno_label_ctrl->SetLabel(_("One QSO Record"));
815 	} else {
816 		_recno_label_ctrl->SetLabel(wxString::Format(_("%d QSO Records"), static_cast<int>(_reclist->size())));
817 	}
818 	_recadd_ctrl->Enable(_newrec < 0);
819 }
820