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