Updated FSF address in all files. Fixes ticket:51
[debian/gnuradio] / ezdop / src / host / hunter / src / hunter.cc
1 /*
2  Copyright 2006 Johnathan Corgan.
3  
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License version 2
6  as published by the Free Software Foundation.
7  
8  This software is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with GNU Radio; see the file COPYING.  If not, write to
15  the Free Software Foundation, Inc., 51 Franklin Street,
16  Boston, MA 02110-1301, USA.
17 */
18
19 // Application includes
20 #include "hunter.h"
21 #include "hunter.xpm"
22 #include "doppler.h"
23 #include "tactical.h"
24 #include "calibrate.h"
25 #include "settings.h"
26 #include "gps.h"
27 #include "serial.h"
28 #include "search.h"
29 #include "util.h"
30 #include "spherical.h"
31 #include "sample.h"
32 #include "samplelog.h"
33 #include "known.h"
34 #include "histogram.h"
35
36 // wxWidgets includes
37 #include <wx/xrc/xmlres.h>
38 #include <wx/wx.h>          // Too many to do individually
39 #include <wx/file.h>        // hmmm, fails compile on mingw32 without it
40
41 // Event table for HunterFrame
42 BEGIN_EVENT_TABLE(HunterFrame, wxFrame)
43     // Application level events
44         EVT_CLOSE(HunterFrame::OnClose)
45     
46     // File menu events
47     EVT_MENU(XRCID("file_new_menuitem"), HunterFrame::OnFileNew)
48     EVT_MENU(XRCID("file_open_menuitem"), HunterFrame::OnFileOpen)
49     EVT_MENU(XRCID("file_close_menuitem"), HunterFrame::OnFileClose)
50     EVT_MENU(XRCID("file_save_menuitem"), HunterFrame::OnFileSave)
51         EVT_MENU(XRCID("file_exit_menuitem"), HunterFrame::OnExit)
52
53     // Doppler menu events
54     EVT_MENU(XRCID("doppler_toggle_menuitem"), HunterFrame::OnDopplerToggle)
55     EVT_MENU(XRCID("doppler_autostart_menuitem"), HunterFrame::OnDopplerAutostart)
56     EVT_MENU(XRCID("doppler_reset_menuitem"), HunterFrame::OnDopplerReset)
57
58     // Calibration menu events
59     EVT_MENU(XRCID("calibration_savetofile_menuitem"), HunterFrame::OnCalibrationSaveToFile)
60     EVT_MENU(XRCID("calibration_loadfromfile_menuitem"), HunterFrame::OnCalibrationLoadFromFile)
61     EVT_MENU(XRCID("calibration_loadtransmitter_menuitem"), HunterFrame::OnCalibrationLoadTransmitter)
62     EVT_MENU(XRCID("calibration_savetransmitter_menuitem"), HunterFrame::OnCalibrationSaveTransmitter)
63
64     // About menu events
65         EVT_MENU(XRCID("about_menuitem"), HunterFrame::OnAbout)
66
67     // Status panel events
68     EVT_RADIOBOX(XRCID("statistics_source_radiobox"), HunterFrame::OnHistogramSourceChg)
69     EVT_RADIOBOX(XRCID("statistics_coords_radiobox"), HunterFrame::OnHistogramCoordsChg)
70
71     // Doppler tab events
72         EVT_BUTTON(XRCID("doppler_toggle_button"), HunterFrame::OnDopplerToggle)
73         EVT_CHECKBOX(XRCID("doppler_autostart_checkbox"), HunterFrame::OnDopplerAutostart)
74         EVT_BUTTON(XRCID("doppler_reset_button"), HunterFrame::OnDopplerReset)
75         EVT_COMMAND_SCROLL(XRCID("doppler_filter_slider"), HunterFrame::OnDopplerFilterChg)
76         EVT_RADIOBOX(XRCID("doppler_rotation_radiobox"), HunterFrame::OnDopplerRotationChg)
77         EVT_DOPPLER_UPDATE(HunterFrame::OnDopplerUpdate)
78
79     // GPS tab events
80     EVT_BUTTON(XRCID("gps_toggle_button"), HunterFrame::OnGPSToggle)
81         EVT_CHECKBOX(XRCID("gps_autostart_checkbox"), HunterFrame::OnGPSAutostart)
82     EVT_COMBOBOX(XRCID("gps_device_combobox"), HunterFrame::OnGPSDeviceSelect)
83     EVT_TEXT_ENTER(XRCID("gps_device_combobox"), HunterFrame::OnGPSDeviceSelect)
84         EVT_GPS_UPDATE(HunterFrame::OnGPSUpdate)
85
86     // Calibration tab events
87         EVT_BUTTON(XRCID("calibration_equalize_button"), HunterFrame::OnCalibrationEqualize)
88     EVT_BUTTON(XRCID("calibration_zero_button"), HunterFrame::OnCalibrationZero)
89     EVT_SPIN_UP(XRCID("calibration_adjust_spinner"), HunterFrame::OnCalibrationAdjustRight)
90     EVT_SPIN_DOWN(XRCID("calibration_adjust_spinner"), HunterFrame::OnCalibrationAdjustLeft)
91         EVT_CHECKBOX(XRCID("calibration_all_checkbox"), HunterFrame::OnCalibrationAffectAllRates)
92     EVT_BUTTON(XRCID("known_transmitter_update_button"), HunterFrame::OnKnownTransmitterUpdate)
93     EVT_CHECKBOX(XRCID("known_transmitter_checkbox"), HunterFrame::OnUseKnownTransmitter)
94
95     // Search tab events
96     EVT_BUTTON(XRCID("search_newsave_button"), HunterFrame::OnSearchNewSave)
97     EVT_BUTTON(XRCID("search_openclose_button"), HunterFrame::OnSearchOpenClose)
98     EVT_BUTTON(XRCID("search_toggle_button"), HunterFrame::OnSearchToggle)
99     EVT_BUTTON(XRCID("search_once_button"), HunterFrame::OnSearchOnce)
100     EVT_SEARCH_UPDATE(HunterFrame::OnSearchUpdate)
101     
102     // Display tab events
103         EVT_RADIOBOX(XRCID("display_orientation_radiobox"), HunterFrame::OnDisplayOrientation)
104     EVT_CHECKBOX(XRCID("display_doppler_checkbox"), HunterFrame::OnDisplayDoppler)
105     EVT_CHECKBOX(XRCID("display_known_checkbox"), HunterFrame::OnDisplayKnown)
106     EVT_CHECKBOX(XRCID("display_estimated_checkbox"), HunterFrame::OnDisplayEstimated)
107
108 END_EVENT_TABLE()
109
110 HunterFrame::HunterFrame() :
111 wxFrame(),
112 m_search(this)
113 {
114     m_settings = NULL;
115     m_doppler = NULL;
116     m_gps = NULL;
117     m_log = NULL;
118     m_tactical_panel = NULL;
119     m_error_histogram_panel = NULL;
120         
121     m_doppler_started = false;
122     m_gps_started = false;
123     m_capture = false;
124     m_one_shot = false;
125     m_histogram_source = 0;
126             
127     InitializeSettings();
128         InitializeWindows();
129         InitializeDoppler();
130     InitializeGPS();
131     InitializeCalibration();
132     InitializeSearch();
133     InitializeDisplay();
134 }
135         
136 void HunterFrame::InitializeSettings()
137 {
138     m_settings = new HunterSettings();
139     wxSetWorkingDirectory(m_settings->GetWorkingDirectory());
140 }
141
142 void HunterFrame::InitializeWindows()
143 {
144         // Build main window controls
145         bool loaded = wxXmlResource::Get()->LoadFrame(this, NULL, wxT("main_frame"));
146         wxASSERT(loaded);
147
148         // Hack until XRC understands <gravity> for wxSplitterWindow!!
149         XRCCTRL(*this, "horiz_splitter", wxSplitterWindow)->SetSashGravity(1.0);
150
151     // Increase font size for better visibility in certain text displays
152     wxFont font = XRCCTRL(*this, "doppler_relative_bearing_text", wxStaticText)->GetFont();
153     float points = font.GetPointSize()*2.0;
154     font.SetPointSize((int)points);
155     XRCCTRL(*this, "doppler_relative_bearing_text", wxStaticText)->SetFont(font);
156     XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetFont(font);
157     XRCCTRL(*this, "transmitter_estimated_bearing_text", wxStaticText)->SetFont(font);
158     XRCCTRL(*this, "transmitter_estimated_range_text", wxStaticText)->SetFont(font);
159     XRCCTRL(*this, "transmitter_actual_bearing_text", wxStaticText)->SetFont(font);
160     XRCCTRL(*this, "transmitter_actual_range_text", wxStaticText)->SetFont(font);
161     points *= 0.75;
162     font.SetPointSize((int)points);
163     XRCCTRL(*this, "gps_latitude_text", wxStaticText)->SetFont(font);
164     XRCCTRL(*this, "gps_longitude_text", wxStaticText)->SetFont(font);
165     XRCCTRL(*this, "gps_heading_text", wxStaticText)->SetFont(font);
166     XRCCTRL(*this, "gps_speed_text", wxStaticText)->SetFont(font);
167     XRCCTRL(*this, "search_latitude_text", wxStaticText)->SetFont(font);
168     XRCCTRL(*this, "search_longitude_text", wxStaticText)->SetFont(font);
169     XRCCTRL(*this, "search_count_text", wxStaticText)->SetFont(font);
170     XRCCTRL(*this, "search_status_text", wxStaticText)->SetFont(font);
171     XRCCTRL(*this, "search_mode_text", wxStaticText)->SetFont(font);
172     XRCCTRL(*this, "search_mean_text", wxStaticText)->SetFont(font);
173     XRCCTRL(*this, "search_score_text", wxStaticText)->SetFont(font);
174     XRCCTRL(*this, "search_disterror_text", wxStaticText)->SetFont(font);
175     XRCCTRL(*this, "error_count_text", wxStaticText)->SetFont(font);
176     XRCCTRL(*this, "error_mode_text", wxStaticText)->SetFont(font);
177     XRCCTRL(*this, "error_mean_text", wxStaticText)->SetFont(font);
178     XRCCTRL(*this, "error_conc_text", wxStaticText)->SetFont(font);
179
180     // Create the menu 
181         SetMenuBar(wxXmlResource::Get()->LoadMenuBar(wxT("main_menu")));
182         CreateStatusBar(1);
183         SetIcon(wxIcon(hunter_xpm));
184
185     // Restore saved window size and position
186     int x = m_settings->GetWindowXPos();
187     int y = m_settings->GetWindowYPos();
188     wxSize size = m_settings->GetWindowSize();
189     int hsplitpos = size.GetHeight()-170;   // HACK!
190     SetSize(x, y, size.GetWidth(), size.GetHeight());
191         XRCCTRL(*this, "horiz_splitter", wxSplitterWindow)->SetSashPosition(hsplitpos);
192
193     // Error histogram is a custom control outside XRC system
194     m_error_histogram_panel = new HistogramPanel(this);
195         wxXmlResource::Get()->AttachUnknownControl(wxT("error_histogram_panel"), 
196                                                        m_error_histogram_panel, this);
197 }
198
199 void HunterFrame::InitializeDoppler()
200 {
201         m_doppler = new EZDoppler(this);
202         m_doppler->Initialize();
203         m_doppler_started = false;
204     SetDopplerParams();
205 }
206
207 void HunterFrame::InitializeGPS()
208 {
209     m_gps = new GPS(this);
210         m_gps_started = false;
211
212     // Populate device selection combobox
213     XRCCTRL(*this, "gps_device_combobox", wxComboBox)->Clear();
214         wxArrayString ports = EnumerateSerialPorts();
215     for (int i = 0; i < ports.GetCount(); i++)
216         XRCCTRL(*this, "gps_device_combobox", wxComboBox)->Append(ports[i]);
217     XRCCTRL(*this, "gps_device_combobox", wxComboBox)->SetValue(m_settings->GetGPSDeviceName());
218     
219     // GPS autostart
220     if (m_settings->GetGPSAutostart()) {
221        XRCCTRL(*this, "gps_autostart_checkbox", wxCheckBox)->SetValue(true);
222        StartGPS();            
223     }
224 }
225
226 void HunterFrame::InitializeCalibration()
227 {
228     XRCCTRL(*this, "calibration_all_checkbox", wxCheckBox)->SetValue(m_settings->GetCalibrationAffectAllRates());
229
230     if (m_settings->GetUseKnownTransmitter()) {
231         m_known.Location(Spherical(m_settings->GetKnownTransmitterLatitude(),
232                                    m_settings->GetKnownTransmitterLongitude()));
233         XRCCTRL(*this, "known_transmitter_checkbox", wxCheckBox)->SetValue(true);
234     }
235
236     UpdateKnownLocation();
237 }
238
239 void HunterFrame::InitializeDisplay()
240 {
241     // Tactical display is a custom control outside XRC system
242         m_tactical_panel = new TacticalPanel(this);
243         wxXmlResource::Get()->AttachUnknownControl(wxT("tactical_panel"), 
244                                                        m_tactical_panel, this);
245
246     if (m_settings->GetDisplayOrientation()) {
247         XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->SetSelection(1);
248         m_tactical_panel->SetOrientation(NorthUp);
249     }
250
251     if (m_settings->GetDisplayDoppler()) {
252         XRCCTRL(*this, "display_doppler_checkbox", wxCheckBox)->SetValue(true);
253         m_tactical_panel->SetDisplayDoppler(true);
254     }
255
256     if (m_settings->GetDisplayKnown()) {
257         XRCCTRL(*this, "display_known_checkbox", wxCheckBox)->SetValue(true);
258         m_tactical_panel->SetDisplayKnown(true);
259     }
260
261     if (m_settings->GetDisplayEstimated()) {
262         XRCCTRL(*this, "display_estimated_checkbox", wxCheckBox)->SetValue(true);
263         m_tactical_panel->SetDisplayEstimated(true);
264     }
265 }
266
267 void HunterFrame::InitializeSearch()
268 {
269     EnableSearchItems(false);
270 }
271
272 void HunterFrame::SetDopplerParams()
273 {
274     // NOTE: This is not in InitializeDoppler() as it needs to be called 
275     // separately when resetting Doppler
276
277         // Adjust windows based on device status
278         if (!m_doppler->IsOnline()) { 
279             // Disable all GUI elements associated with Doppler
280         
281         // Doppler control tab
282             XRCCTRL(*this, "doppler_control_panel", wxPanel)->Disable();
283
284         // Doppler menu items
285         this->GetMenuBar()->FindItem(XRCID("doppler_toggle_menuitem"))->Enable(false);
286         this->GetMenuBar()->FindItem(XRCID("doppler_autostart_menuitem"))->Enable(false);
287         this->GetMenuBar()->FindItem(XRCID("doppler_reset_menuitem"))->Enable(false);
288
289         // Calibration control tab
290         XRCCTRL(*this, "calibration_equalize_button", wxButton)->Enable(false);
291         XRCCTRL(*this, "calibration_zero_button", wxButton)->Enable(false);
292         XRCCTRL(*this, "calibration_adjust_spinner", wxSpinButton)->Enable(false);
293
294         return;
295         }
296
297     // Doppler filter level
298     int filter = m_settings->GetDopplerFilter();
299     XRCCTRL(*this, "doppler_filter_slider", wxSlider)->SetValue(filter);
300     wxScrollEvent dummy;
301     OnDopplerFilterChg(dummy);
302
303     // Doppler rotation rate
304     int rate = m_settings->GetDopplerRotation();
305     XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->SetSelection(rate);
306     wxCommandEvent dummy2;
307     OnDopplerRotationChg(dummy2);
308
309     // Doppler calibration values
310     for (int i=0; i < 7; i++) {  // i==6 gets zero offset
311         float offset = m_settings->GetDopplerCalibration(i);
312         m_doppler->SetCalibration(i, offset);
313     }
314
315     // Doppler autostart
316     if (m_settings->GetDopplerAutostart()) {
317         this->GetMenuBar()->FindItem(XRCID("doppler_autostart_menuitem"))->Check(true);
318         XRCCTRL(*this, "doppler_autostart_checkbox", wxCheckBox)->SetValue(true);
319         StartDoppler();            
320     }
321 }
322
323 void HunterFrame::StartDoppler()
324 {
325         m_doppler->Start();
326         m_doppler_started = true;
327         XRCCTRL(*this, "doppler_toggle_button", wxButton)->SetLabel(_T("Stop"));
328     XRCCTRL(*this, "calibration_equalize_button", wxButton)->Enable();
329     XRCCTRL(*this, "calibration_zero_button", wxButton)->Enable();
330     XRCCTRL(*this, "calibration_adjust_spinner", wxSpinButton)->Enable();
331     this->GetMenuBar()->FindItem(XRCID("doppler_toggle_menuitem"))->SetText(_T("&Stop"));
332     this->GetMenuBar()->FindItem(XRCID("doppler_reset_menuitem"))->Enable(true);
333 }
334
335 void HunterFrame::StopDoppler()
336 {
337         m_doppler->Stop();
338         m_doppler_started = false;
339     UpdateDopplerStatus(false);
340         XRCCTRL(*this, "doppler_toggle_button", wxButton)->SetLabel(_T("Start"));
341     this->GetMenuBar()->FindItem(XRCID("doppler_toggle_menuitem"))->SetText(_("&Start"));
342     this->GetMenuBar()->FindItem(XRCID("doppler_reset_menuitem"))->Enable(false);
343     XRCCTRL(*this, "calibration_equalize_button", wxButton)->Disable();
344     XRCCTRL(*this, "calibration_zero_button", wxButton)->Disable();
345     XRCCTRL(*this, "calibration_adjust_spinner", wxSpinButton)->Disable();
346 }
347
348 void HunterFrame::StartGPS()
349 {
350     wxString port = XRCCTRL(*this, "gps_device_combobox", wxComboBox)->GetValue();
351     if (!m_gps->Start(port)) {
352         wxMessageDialog(this, wxT("Failed to start GPS!"), wxT("GPS Error"),
353                         wxOK|wxICON_ERROR).ShowModal();
354         return;
355     }    
356     
357     m_gps_started = true;
358     XRCCTRL(*this, "gps_toggle_button", wxButton)->SetLabel(_T("Stop"));
359     XRCCTRL(*this, "gps_device_combobox", wxComboBox)->Disable();
360     XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->Enable();
361     if (XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->GetSelection() == 1) {
362         m_tactical_panel->SetOrientation(NorthUp);
363     }
364
365     if (m_search.HasSolution())
366         UpdateSearchDirection(true);
367
368     UpdateKnownDirection();
369     UpdateKnownStatistics();
370 }
371
372 void HunterFrame::StopGPS()
373 {
374     m_gps->Stop();
375     m_gps_started = false;
376     m_tactical_panel->SetOrientation(TrackUp);
377     m_tactical_panel->SetActualBearing(-1.0);
378     m_tactical_panel->SetEstimatedBearing(-1.0);
379         XRCCTRL(*this, "gps_toggle_button", wxButton)->SetLabel(_T("Start"));
380     XRCCTRL(*this, "gps_device_combobox", wxComboBox)->Enable();
381     XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->Disable();
382
383     UpdateGPSStatus(false);
384     UpdateSearchDirection(false);
385     UpdateKnownDirection();
386     UpdateKnownStatistics();
387         
388     // Note: can't replace with call to UpdateDopplerStatus as we only want to clear one field
389     XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetLabel(_T(""));
390 }
391
392 void HunterFrame::EnableSearchItems(bool enable)
393 {
394         XRCCTRL(*this, "search_toggle_button", wxButton)->Enable(enable);
395         XRCCTRL(*this, "search_once_button", wxButton)->Enable(enable);
396
397     // These fields will get populated when samples come in
398     UpdateSearchStatus(false);
399     UpdateSearchLocation(false);
400     UpdateSearchDirection(false);    
401
402     this->GetMenuBar()->FindItem(XRCID("file_save_menuitem"))->Enable(enable);
403     this->GetMenuBar()->FindItem(XRCID("file_close_menuitem"))->Enable(enable);
404
405     if (!enable) {
406         XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Stop"));
407         XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Automatic"));
408     }
409 }
410
411 void HunterFrame::OnClose(wxCloseEvent &event)
412 {
413     wxCommandEvent dummy;
414         
415     // Cleanup for this scope should be done here
416     // TODO: factor out into methods corresponding to start up initialization
417
418     // Save window size and position
419     int x, y;
420     GetPosition(&x, &y);
421     m_settings->SetWindowXPos(x);
422     m_settings->SetWindowYPos(y);
423     m_settings->SetWindowSize(GetSize());
424
425     wxASSERT(m_doppler != NULL);
426     if (m_doppler_started)
427         StopDoppler();
428     m_doppler->Finalize();
429     if (m_gps_started)
430         StopGPS();
431
432     if (m_log)
433         OnFileClose(dummy);
434
435         
436     m_settings->SetWorkingDirectory(wxGetCwd());
437
438     delete m_doppler;
439     delete m_gps;
440     delete m_settings;
441     if (m_log)
442         delete m_log;        
443
444     // Last thing to do
445         Destroy();
446 }
447
448 void HunterFrame::OnExit(wxCommandEvent &event)
449 {
450         // Cleanup for this scope should be done in ::OnClose, not here.  This
451         // method is not called when exiting from the system menu, but rather it
452         // goes straight to ::OnClose
453
454         // Sends close event
455         this->Close();
456 }
457
458 void HunterFrame::OnAbout(wxCommandEvent &event)
459 {
460         wxMessageBox(wxT("Copyright(C) 2005 Johnathan Corgan"), 
461                      wxT("About AE6HO Radio Location System"), 
462                      wxOK | wxICON_INFORMATION, this);
463 }
464
465 void HunterFrame::OnDopplerToggle(wxCommandEvent &event)
466 {
467         if (m_doppler_started)
468             StopDoppler();
469         else
470             StartDoppler();
471 }
472
473 void HunterFrame::OnDopplerAutostart(wxCommandEvent &event)
474 {
475     bool autostart = event.IsChecked();
476     this->GetMenuBar()->FindItem(XRCID("doppler_autostart_menuitem"))->Check(autostart);
477     XRCCTRL(*this, "doppler_autostart_checkbox", wxCheckBox)->SetValue(autostart);
478     m_settings->SetDopplerAutostart(autostart);
479 }
480
481 void HunterFrame::OnDopplerReset(wxCommandEvent &event)
482 {
483         StopDoppler();
484         m_doppler->Reset();
485     SetDopplerParams();  // restarts Doppler if autostart is configured
486 }
487
488 void HunterFrame::OnDopplerFilterChg(wxScrollEvent &event)
489 {
490         int n = XRCCTRL(*this, "doppler_filter_slider", wxSlider)->GetValue();
491         m_doppler->SetFilter(n);
492     m_settings->SetDopplerFilter(n);
493 }
494
495 void HunterFrame::OnDopplerRotationChg(wxCommandEvent &event)
496 {
497         int n = XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->GetSelection();
498         m_doppler->SelectRotationRate(n);
499     m_settings->SetDopplerRotation(n);
500 }
501
502 void HunterFrame::OnDopplerUpdate(EZDopplerUpdate &event)
503 {
504     if (!m_doppler_started) // Spurious event after doppler stopped
505         return;
506         
507     // Update current state variables
508     m_sample.Volume(event.m_volume);
509     m_sample.InPhase(event.m_in_phase);
510     m_sample.Quadrature(event.m_quadrature);
511     m_sample.Strength(sqrt(event.m_in_phase*event.m_in_phase + 
512                            event.m_quadrature*event.m_quadrature));
513     m_sample.Phase(atan2(event.m_quadrature, event.m_in_phase));
514
515     UpdateDopplerStatus(true);
516 }
517
518 void HunterFrame::UpdateDopplerStatus(bool display)
519 {
520     wxString str;
521     if (!display) {
522         m_tactical_panel->SetDopplerBearing(-1.0);
523         XRCCTRL(*this, "doppler_level_gauge", wxGauge)->SetValue(0);
524         XRCCTRL(*this, "doppler_audio_gauge", wxGauge)->SetValue(0);
525         XRCCTRL(*this, "doppler_relative_bearing_text", wxStaticText)->SetLabel(_T(""));
526         XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetLabel(_T(""));
527         return;
528     }
529
530     float display_relative_bearing = degree_normalize(to_degrees(m_sample.Phase()));
531     if (m_tactical_panel)
532         m_tactical_panel->SetDopplerBearing(display_relative_bearing);
533
534     int display_magnitude = (int)limit((m_sample.Strength()*200.0), 0.0, 100.0);
535     XRCCTRL(*this, "doppler_level_gauge", wxGauge)->SetValue(display_magnitude);
536
537     int display_amplitude = (int)limit((m_sample.Volume()*100.0), 0.0, 100.0);
538     XRCCTRL(*this, "doppler_audio_gauge", wxGauge)->SetValue(display_amplitude);
539
540     str.Printf(_T("%03i"), degree_normalize((int)(display_relative_bearing+0.5))); // So zero is from -0.5 to 0.5
541     XRCCTRL(*this, "doppler_relative_bearing_text", wxStaticText)->SetLabel(str);
542
543     if (m_gps_started) {
544         str.Printf(_T("%03i"), degree_normalize((int)(m_sample.Heading()+display_relative_bearing))); 
545         XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetLabel(str);
546     }
547 }
548
549 void HunterFrame::CalcKnownStatistics()
550 {
551     if (m_log)
552         m_known.Calc(m_log->Samples());
553
554     UpdateKnownStatistics();
555 }   
556
557 void HunterFrame::UpdateKnownStatistics()
558 {
559     if (m_error_histogram_panel && m_histogram_source == 1)
560         m_error_histogram_panel->SetData(m_known.Histogram());
561     
562     if (!m_known.HasStats()) {
563         XRCCTRL(*this, "error_count_text", wxStaticText)->SetLabel(_T(""));
564         XRCCTRL(*this, "error_mode_text", wxStaticText)->SetLabel(_T(""));
565         XRCCTRL(*this, "error_mean_text", wxStaticText)->SetLabel(_T(""));
566         XRCCTRL(*this, "error_conc_text", wxStaticText)->SetLabel(_T(""));
567         return;
568     }
569     
570     wxString str;
571
572     str.Printf(_T("%i"), m_known.Count());
573     XRCCTRL(*this, "error_count_text", wxStaticText)->SetLabel(str);
574
575     str.Printf(_T("%i"), m_known.Mode());
576     XRCCTRL(*this, "error_mode_text", wxStaticText)->SetLabel(str);
577
578     str.Printf(_T("%5.1f"), m_known.Mean());
579     XRCCTRL(*this, "error_mean_text", wxStaticText)->SetLabel(str);
580
581     str.Printf(_T("%1.3f"), m_known.Concentration());
582     XRCCTRL(*this, "error_conc_text", wxStaticText)->SetLabel(str);
583 }
584
585 void HunterFrame::OnCalibrationEqualize(wxCommandEvent &event)
586 {
587     CalibrationDialog *dlg = new CalibrationDialog(this);
588     dlg->ShowModal();
589 }
590
591 void HunterFrame::DoCalibrationStep(int which)
592 {
593     static int delay;
594     
595     if (which == 0) {       // Set up doppler 
596         delay = XRCCTRL(*this, "doppler_filter_slider", wxSlider)->GetValue(); // Empirically determined
597         if (delay == 0)
598             delay = 1;
599     }
600
601     // Set rotation rate for this step
602     wxCommandEvent dummy;
603     XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->SetSelection(which);
604     OnDopplerRotationChg(dummy);
605
606     // Wait until stable value
607     wxStartTimer();
608     while(wxGetElapsedTime(false) < 1000*delay)
609         wxYield(); // eeewwww!
610     
611     // Now stable reading can be calibrated
612     m_doppler->Calibrate(-M_PI);      // Sets to zero
613
614     float offset=m_doppler->GetCalibration(which);
615     m_settings->SetDopplerCalibration(which, offset);
616 }
617
618 void HunterFrame::OnCalibrationZero(wxCommandEvent &event)
619 {
620     m_doppler->SetOffset();
621     float offset=m_doppler->GetCalibration(6);
622     m_settings->SetDopplerCalibration(6, offset);
623 }
624
625 void HunterFrame::OnCalibrationAdjustLeft(wxSpinEvent &event)
626 {
627     if (m_settings->GetCalibrationAffectAllRates())
628         m_doppler->NudgeAll(to_radians(-0.5));
629     else
630         m_doppler->Nudge(to_radians(-0.5));
631
632     float offset=m_doppler->GetCalibration(6);
633     m_settings->SetDopplerCalibration(6, offset);
634 }
635
636 void HunterFrame::OnCalibrationAdjustRight(wxSpinEvent &event)
637 {
638     if (m_settings->GetCalibrationAffectAllRates())
639         m_doppler->NudgeAll(to_radians(0.5));
640     else
641         m_doppler->Nudge(to_radians(0.5));
642
643     float offset=m_doppler->GetCalibration(6);
644     m_settings->SetDopplerCalibration(6, offset);
645 }
646
647 void HunterFrame::OnCalibrationAffectAllRates(wxCommandEvent &event)
648 {
649     bool affect = event.IsChecked();
650     XRCCTRL(*this, "calibration_all_checkbox", wxCheckBox)->SetValue(affect);
651     m_settings->SetCalibrationAffectAllRates(affect);
652 }
653
654 void HunterFrame::OnGPSToggle(wxCommandEvent &event)
655 {
656         if (m_gps_started)
657             StopGPS();
658         else
659             StartGPS();
660 }
661
662 void HunterFrame::OnGPSAutostart(wxCommandEvent &event)
663 {
664     bool start = XRCCTRL(*this, "gps_autostart_checkbox", wxCheckBox)->GetValue();
665     m_settings->SetGPSAutostart(start);
666 }
667
668 void HunterFrame::OnGPSDeviceSelect(wxCommandEvent &event)
669 {
670         wxString device = XRCCTRL(*this, "gps_device_combobox", wxComboBox)->GetValue();
671     m_settings->SetGPSDeviceName(device);
672 }
673
674 void HunterFrame::OnGPSUpdate(GPSUpdate &update)
675 {
676     // Update state variables
677     GPRMC *gprmc = update.m_gprmc;
678     m_sample.Valid(update.m_gprmc->m_valid);
679     m_sample.Time(update.m_gprmc->m_stamp);
680     m_sample.Location(update.m_gprmc->m_fix);
681     m_sample.Heading(update.m_gprmc->m_heading);
682     m_sample.Speed(update.m_gprmc->m_speed*1.15077945); // Conversion from knots to mph
683     m_sample.Rate(XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->GetSelection());
684     m_sample.Filtering(XRCCTRL(*this, "doppler_filter_slider", wxSlider)->GetValue());
685     
686     UpdateGPSValidity(update.m_gprmc->m_valid);                 // Colors red for invalid, black for valid
687     UpdateGPSStatus(true);                                      // gps lat, lon, heading, speed
688     UpdateKnownDirection();                                     // actual bearing and range
689     CalcKnownStatistics();
690
691     if (m_log && m_capture && m_doppler_started &&
692         m_sample.Speed() >= 5.0 && m_sample.Valid()) {
693         m_log->Add(m_sample);
694         CalcSolution();
695         if (m_one_shot == true)
696             StopCapture();
697     }
698
699     if (m_search.HasSolution()) {
700         UpdateSearchStatus(true);
701         UpdateSearchDirection(true);
702     }
703         
704     delete update.m_gprmc;
705 }
706
707 void HunterFrame::CalcSolution()
708 {
709     GetStatusBar()->SetStatusText(wxT("Searching..."));    
710     m_search.Solve(m_log);
711 }
712
713 void HunterFrame::OnSearchUpdate(SearchUpdate &update)
714 {
715     if (update.m_done)
716         GetStatusBar()->SetStatusText(wxT(""));    
717
718     if (m_search.HasSolution()) {
719         UpdateSearchStatus(true);
720         UpdateSearchLocation(true);
721         if (m_gps_started)
722            UpdateSearchDirection(true);
723     }
724 }
725
726 void HunterFrame::UpdateSearchStatus(bool display)
727 {
728     wxString str;
729
730     if (m_error_histogram_panel && m_histogram_source == 0)
731         m_error_histogram_panel->SetData(m_search.Histogram());
732         
733     if (!display) {
734         XRCCTRL(*this, "search_count_text", wxStaticText)->SetLabel(_T(""));    
735         XRCCTRL(*this, "search_status_text", wxStaticText)->SetLabel(_T(""));    
736         XRCCTRL(*this, "search_mode_text", wxStaticText)->SetLabel(_T(""));    
737         XRCCTRL(*this, "search_mean_text", wxStaticText)->SetLabel(_T(""));    
738         XRCCTRL(*this, "search_score_text", wxStaticText)->SetLabel(_T(""));    
739         return;
740     }
741
742     str.Printf(_T("%i"), m_log->Count());
743     XRCCTRL(*this, "search_count_text", wxStaticText)->SetLabel(str);
744
745     str.Printf(_T("%s"), m_search.Busy() ? _T("BUSY") : _T(""));
746     XRCCTRL(*this, "search_status_text", wxStaticText)->SetLabel(str);
747
748     str.Printf(_T("%i"), m_search.Mode());
749     XRCCTRL(*this, "search_mode_text", wxStaticText)->SetLabel(str);
750
751     str.Printf(_T("%6.1f"), m_search.Mean());
752     XRCCTRL(*this, "search_mean_text", wxStaticText)->SetLabel(str);
753
754     str.Printf(_T("%i"), (int)(m_search.Concentration()*100.0));
755     XRCCTRL(*this, "search_score_text", wxStaticText)->SetLabel(str);
756 }
757
758 void HunterFrame::UpdateGPSStatus(bool display)
759 {            
760     wxString str;
761
762     if (!display) {
763         XRCCTRL(*this, "gps_latitude_text", wxStaticText)->SetLabel(_T(""));    
764         XRCCTRL(*this, "gps_longitude_text", wxStaticText)->SetLabel(_T(""));    
765         XRCCTRL(*this, "gps_heading_text", wxStaticText)->SetLabel(_T(""));    
766         XRCCTRL(*this, "gps_speed_text", wxStaticText)->SetLabel(_T(""));    
767         return;
768     }
769
770     // Calculate latitude to display
771     str.Printf(_T("%1.5lf"), fabs(m_sample.Latitude()));
772     str.Append(m_sample.Latitude() < 0.0 ? _T("S") : _T("N"));
773     XRCCTRL(*this, "gps_latitude_text", wxStaticText)->SetLabel(str);    
774
775     // Calculate longitude to display
776     str.Printf(_T("%1.5lf"), fabs(m_sample.Longitude()));
777     str.Append(m_sample.Longitude() < 0.0 ? _T("W") : _T("E"));
778     XRCCTRL(*this, "gps_longitude_text", wxStaticText)->SetLabel(str);    
779
780     // Calculate heading to display
781     m_tactical_panel->SetHeading(m_sample.Heading());
782     str.Printf(_T("%03i"), (unsigned int)(m_sample.Heading()));
783     XRCCTRL(*this, "gps_heading_text", wxStaticText)->SetLabel(str);    
784
785     // Calculate speed to display
786     str.Printf(_T("%i"), (unsigned int)(m_sample.Speed()));
787     XRCCTRL(*this, "gps_speed_text", wxStaticText)->SetLabel(str);    
788 }    
789
790 void HunterFrame::UpdateSearchDirection(bool display)
791 {
792     wxString str;
793
794     if (!display) {
795         XRCCTRL(*this, "transmitter_estimated_bearing_text", wxStaticText)->SetLabel(_T(""));
796         XRCCTRL(*this, "transmitter_estimated_range_text", wxStaticText)->SetLabel(_T(""));
797         if (m_tactical_panel)
798             m_tactical_panel->SetEstimatedBearing(-1.0);
799         return;
800     }
801
802     float estimated_bearing = 
803         bearing(m_sample.Location(), m_search.GetEstimatedLocation());
804                 
805     float estimated_range = 
806         range(m_sample.Location(), m_search.GetEstimatedLocation());
807     
808     m_tactical_panel->SetEstimatedBearing(estimated_bearing);
809     str.Printf(_T("%03i"), degree_normalize((int)estimated_bearing));
810     XRCCTRL(*this, "transmitter_estimated_bearing_text", wxStaticText)->SetLabel(str);
811     if (estimated_range > 99.9)
812         str.Printf(_T("%1.0f"), estimated_range);
813     else if (estimated_range > 9.99)
814         str.Printf(_T("%1.1f"), estimated_range);
815     else
816         str.Printf(_T("%1.2f"), estimated_range);
817     XRCCTRL(*this, "transmitter_estimated_range_text", wxStaticText)->SetLabel(str);
818 }
819
820 void HunterFrame::UpdateSearchLocation(bool display)
821 {
822     wxString str;
823     
824     if (!display) {
825         XRCCTRL(*this, "search_latitude_text", wxStaticText)->SetLabel(_T(""));    
826         XRCCTRL(*this, "search_longitude_text", wxStaticText)->SetLabel(_T(""));    
827         XRCCTRL(*this, "search_disterror_text", wxStaticText)->SetLabel(_T(""));    
828         return;
829     }
830
831     Spherical estimated_location = m_search.GetEstimatedLocation();
832
833     // Calculate latitude to display
834     str.Printf(_T("%1.5f"), fabs(estimated_location.Latitude()));
835     str.Append(estimated_location.Latitude() < 0.0 ? _T("S") : _T("N"));
836     XRCCTRL(*this, "search_latitude_text", wxStaticText)->SetLabel(str);    
837
838     // Calculate longitude to display
839     str.Printf(_T("%1.5f"), fabs(estimated_location.Longitude()));
840     str.Append(estimated_location.Longitude() < 0.0 ? _T("W") : _T("E"));
841     XRCCTRL(*this, "search_longitude_text", wxStaticText)->SetLabel(str);    
842
843     if (m_known.IsSet()) {
844         float distance_error = range(m_search.GetEstimatedLocation(), m_known.Location());
845         if (distance_error > 99.9)
846             str.Printf(_T("%1.0f"), distance_error);
847         else if (distance_error > 9.99)
848             str.Printf(_T("%1.1f"), distance_error);
849         else
850             str.Printf(_T("%1.2f"), distance_error);
851         XRCCTRL(*this, "search_disterror_text", wxStaticText)->SetLabel(str);
852     }
853 }
854
855 void HunterFrame::UpdateKnownDirection()
856 {
857     wxString str;
858
859     if (!m_known.IsSet() || !m_gps_started) {
860         XRCCTRL(*this, "transmitter_actual_bearing_text", wxStaticText)->SetLabel(_T(""));
861         XRCCTRL(*this, "transmitter_actual_range_text", wxStaticText)->SetLabel(_T(""));
862         if (m_tactical_panel)
863             m_tactical_panel->SetActualBearing(-1.0);
864         return;
865     }    
866
867     float actual_bearing = bearing(m_sample.Location(), m_known.Location());
868     float actual_range   = range(m_sample.Location(), m_known.Location());
869                                        
870     m_tactical_panel->SetActualBearing(actual_bearing);
871
872     str.Printf(_T("%03i"), degree_normalize((int)actual_bearing));
873     XRCCTRL(*this, "transmitter_actual_bearing_text", wxStaticText)->SetLabel(str);
874     if (actual_range > 99.9)
875         str.Printf(_T("%1.0f"), actual_range);
876     else if (actual_range > 9.99)
877         str.Printf(_T("%1.1f"), actual_range);
878     else
879         str.Printf(_T("%1.2f"), actual_range);
880     XRCCTRL(*this, "transmitter_actual_range_text", wxStaticText)->SetLabel(str);
881 }
882
883 void HunterFrame::UpdateKnownLocation()
884 {
885     wxString str;
886     
887     if (!m_known.IsSet()) {
888         XRCCTRL(*this, "known_transmitter_latitude_edit", wxTextCtrl)->SetValue(_T(""));
889         XRCCTRL(*this, "known_transmitter_longitude_edit", wxTextCtrl)->SetValue(_T(""));
890         XRCCTRL(*this, "search_disterror_text", wxStaticText)->SetLabel(_T(""));
891         return;
892     }    
893
894     str.Printf(_T("%1.5f"), m_known.Latitude());        
895     XRCCTRL(*this, "known_transmitter_latitude_edit", wxTextCtrl)->SetValue(str);
896     str.Printf(_T("%1.5f"), m_known.Longitude());
897     XRCCTRL(*this, "known_transmitter_longitude_edit", wxTextCtrl)->SetValue(str);
898     UpdateSearchLocation(true); // to update the known error
899 }
900
901 void HunterFrame::OnKnownTransmitterUpdate(wxCommandEvent &event)
902 {
903     // Note: this is an event handler for a calibration tab button
904     wxString lat_text = XRCCTRL(*this, "known_transmitter_latitude_edit", wxTextCtrl)->GetValue();
905     wxString lon_text = XRCCTRL(*this, "known_transmitter_longitude_edit", wxTextCtrl)->GetValue();
906
907     double lat = 0.0, lon = 0.0;
908
909     // TODO: use Spherical constructor/exceptions
910     if (!lat_text.ToDouble(&lat) || lat > 90.0 || lat < -90.0) {
911         wxMessageDialog(this, wxT("Invalid latitude entered."), wxT("Data Error"),
912                         wxOK|wxICON_ERROR).ShowModal();
913         return;
914     }        
915
916     if (!lon_text.ToDouble(&lon) || lon >180.0 || lon < -180.0) {
917         wxMessageDialog(this, wxT("Invalid longitude entered."), wxT("Data Error"),
918                         wxOK|wxICON_ERROR).ShowModal();
919         return;
920     }        
921
922     m_known.Location(Spherical(lat, lon));
923     CalcKnownStatistics();
924     
925     m_settings->SetKnownTransmitterLongitude(lon);
926     m_settings->SetKnownTransmitterLatitude(lat);
927
928     UpdateKnownLocation();
929     if (m_gps_started)
930         UpdateKnownDirection();
931 }
932
933 void HunterFrame::OnUseKnownTransmitter(wxCommandEvent &event)
934 {
935     if (event.IsChecked())
936         m_known.Location(Spherical(m_settings->GetKnownTransmitterLatitude(),
937                                    m_settings->GetKnownTransmitterLongitude()));
938     else
939         m_known.Clear();
940
941     m_settings->SetUseKnownTransmitter(m_known.IsSet());
942     CalcKnownStatistics();
943     UpdateKnownLocation();
944     if (m_gps_started)
945         UpdateKnownDirection();    
946 }
947
948 void HunterFrame::UpdateGPSValidity(bool valid)
949 {
950     m_sample.Valid(valid);
951
952     wxColour color = *wxBLACK;
953     if (!valid)
954         color = *wxRED;
955
956     XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetForegroundColour(color);
957     XRCCTRL(*this, "transmitter_actual_bearing_text", wxStaticText)->SetForegroundColour(color);
958     XRCCTRL(*this, "transmitter_actual_range_text", wxStaticText)->SetForegroundColour(color);
959     XRCCTRL(*this, "gps_latitude_text", wxStaticText)->SetForegroundColour(color);
960     XRCCTRL(*this, "gps_longitude_text", wxStaticText)->SetForegroundColour(color);
961     XRCCTRL(*this, "gps_heading_text", wxStaticText)->SetForegroundColour(color);
962     XRCCTRL(*this, "gps_speed_text", wxStaticText)->SetForegroundColour(color);
963 }
964
965 void HunterFrame::OnFileNew(wxCommandEvent &event)
966 {
967     if (m_log) {
968         wxLogDebug(_T("Not Implemented: current log is discarded without saving..."));
969         StopCapture();
970         delete m_log;
971         m_search.Reset();
972         m_log = NULL;
973     }
974
975     MakeNewLogAndSearch();
976     this->SetTitle(wxT("AE6HO Radio Location System - Unsaved Search")); // refactor into EnableSearchItems()
977     XRCCTRL(*this, "search_newsave_button", wxButton)->SetLabel(_T("Save"));
978     XRCCTRL(*this, "search_openclose_button", wxButton)->SetLabel(_T("Close"));
979     this->GetMenuBar()->FindItem(XRCID("file_new_menuitem"))->Enable(false);
980     this->GetMenuBar()->FindItem(XRCID("file_open_menuitem"))->Enable(false);
981     EnableSearchItems(true);
982 }
983
984 void HunterFrame::MakeNewLogAndSearch()
985 {
986     m_log = new SampleLog();
987     m_search.Reset();
988 }
989
990 void HunterFrame::OnFileOpen(wxCommandEvent &event)
991 {
992     wxString filename;
993
994     if (m_log)
995         OnFileClose(event);
996
997     wxFileDialog dlg(this, wxT("Open Log File"), _T("."), _T(""), _T("*.dat"),
998                      wxOPEN|wxFILE_MUST_EXIST|wxCHANGE_DIR);
999     if (dlg.ShowModal() == wxID_OK) {
1000         this->GetMenuBar()->FindItem(XRCID("file_new_menuitem"))->Enable(false);
1001         this->GetMenuBar()->FindItem(XRCID("file_open_menuitem"))->Enable(false);
1002         filename = dlg.GetPath();
1003         MakeNewLogAndSearch();
1004         m_log->Load(filename);        
1005         this->SetTitle(wxT("AE6HO Radio Location System - ")+filename);
1006         XRCCTRL(*this, "search_newsave_button", wxButton)->SetLabel(_T("Save"));
1007         XRCCTRL(*this, "search_openclose_button", wxButton)->SetLabel(_T("Close"));
1008         EnableSearchItems(true);
1009         CalcKnownStatistics();
1010         CalcSolution();
1011     }
1012 }
1013
1014 void HunterFrame::OnFileSave(wxCommandEvent &event)
1015 {
1016     wxASSERT(m_log);
1017     
1018     wxString filename;
1019     if (!m_log->HasFile()) {
1020         wxFileDialog dlg(this, wxT("Save Search Data"), _T("."), _T(""), _T("*.dat"), 
1021                          wxSAVE|wxOVERWRITE_PROMPT|wxCHANGE_DIR);
1022         if (dlg.ShowModal() == wxID_OK) {
1023             filename = dlg.GetPath();
1024             m_log->Save(filename);
1025             this->SetTitle(wxT("AE6HO Radio Location System - ")+filename);
1026         }
1027     }
1028     else
1029         m_log->Save();  // Adds additional samples since last save
1030 }
1031
1032 void HunterFrame::OnFileClose(wxCommandEvent &event)
1033 {
1034     // FIXME: ask user if they want to save changed data instead of going straight to save dialog
1035     if (m_log->IsDirty())
1036         OnFileSave(event);
1037
1038     StopCapture();
1039     delete m_log;
1040     m_search.Reset();
1041     m_log = NULL;
1042     
1043     this->SetTitle(wxT("AE6HO Radio Location System")); // refactor into EnableSearchItems()
1044     XRCCTRL(*this, "search_newsave_button", wxButton)->SetLabel(_T("New"));
1045     XRCCTRL(*this, "search_openclose_button", wxButton)->SetLabel(_T("Open"));
1046     this->GetMenuBar()->FindItem(XRCID("file_new_menuitem"))->Enable(true);
1047     this->GetMenuBar()->FindItem(XRCID("file_open_menuitem"))->Enable(true);
1048     EnableSearchItems(false);
1049     m_known.ClearStats();
1050     UpdateKnownStatistics();
1051 }
1052
1053 void HunterFrame::OnCalibrationSaveToFile(wxCommandEvent &event)
1054 {
1055     wxString filename;
1056     wxFileDialog dlg(this, wxT("Save Calibration Data"), _T("."), _T(""), _T("*.cal"), 
1057                      wxSAVE|wxOVERWRITE_PROMPT|wxCHANGE_DIR);
1058     if (dlg.ShowModal() == wxID_OK) {
1059         filename = dlg.GetPath();
1060         wxFile file(filename.c_str(), wxFile::write);
1061         for (int i = 0; i < 7; i++) {
1062             wxString str;
1063             float offset = m_settings->GetDopplerCalibration(i);
1064             str.Printf(_T("%f\n"), offset);
1065             file.Write(str.c_str(), str.length());
1066         }
1067     }
1068 }
1069
1070 void HunterFrame::OnCalibrationLoadFromFile(wxCommandEvent &event)
1071 {
1072     wxString filename;
1073     wxFileDialog dlg(this, wxT("Load Calibration Data"), _T("."), _T(""), _T("*.cal"),
1074                      wxOPEN|wxFILE_MUST_EXIST|wxCHANGE_DIR);
1075     if (dlg.ShowModal() == wxID_OK) {
1076         filename = dlg.GetPath();
1077         wxFile file(filename.c_str(), wxFile::read);
1078         for (int i = 0; i < 7; i++) {
1079             char ch;
1080             size_t count;
1081             wxString line;
1082             count = file.Read(&ch, 1);
1083             while (count == 1 && ch != '\n') {
1084                 line.Append(ch);
1085                 count = file.Read(&ch, 1);
1086             }
1087             float offset = atof((char *)line.c_str());
1088             m_settings->SetDopplerCalibration(i, offset);
1089             m_doppler->SetCalibration(i, offset);
1090         }
1091     }
1092 }
1093
1094 void HunterFrame::OnCalibrationSaveTransmitter(wxCommandEvent &event)
1095 {
1096     wxString filename;
1097     if (!m_known.IsSet())
1098         return; // FIXME: disable menu item when no known transmitter so we can't get here
1099
1100     wxFileDialog dlg(this, wxT("Save Transmitter Coordinates"), _T("."), _T(""), _T("*.loc"), 
1101                      wxSAVE|wxOVERWRITE_PROMPT|wxCHANGE_DIR);
1102     if (dlg.ShowModal() == wxID_OK) {
1103         filename = dlg.GetPath();
1104         wxFile file(filename.c_str(), wxFile::write);
1105         wxString str;
1106         // TODO: use Spherical method to output strings
1107         str.Printf(_T("%f\n%f\n"), m_known.Latitude(),
1108                                m_known.Longitude());
1109         file.Write(str.c_str(), str.length());
1110     }
1111 }
1112
1113 void HunterFrame::OnCalibrationLoadTransmitter(wxCommandEvent &event)
1114 {
1115     wxString filename;
1116     wxFileDialog dlg(this, wxT("Load Transmitter Location"), _T("."), _T(""), _T("*.loc"),
1117                      wxOPEN|wxFILE_MUST_EXIST|wxCHANGE_DIR);
1118     if (dlg.ShowModal() == wxID_OK) {
1119         filename = dlg.GetPath();
1120         wxFile file(filename.c_str(), wxFile::read);
1121         wxString latitude, longitude;
1122
1123         for (int i = 0; i < 2; i++) {
1124             char ch;
1125             size_t count;
1126             wxString line;
1127
1128             count = file.Read(&ch, 1);
1129             while (count == 1 && ch != '\n') {
1130                 line.Append(ch);
1131                 count = file.Read(&ch, 1);
1132             }
1133             if (i == 0) {
1134                 latitude = line;
1135             }
1136             else if (i == 1) {
1137                 longitude = line;
1138             }
1139         }
1140
1141         m_known.Location(Spherical(latitude, longitude));
1142         CalcKnownStatistics();
1143
1144         XRCCTRL(*this, "known_transmitter_checkbox", wxCheckBox)->SetValue(true);
1145         m_settings->SetUseKnownTransmitter(true);
1146         m_settings->SetKnownTransmitterLatitude(m_known.Latitude());
1147         m_settings->SetKnownTransmitterLongitude(m_known.Longitude());
1148
1149         UpdateKnownLocation();
1150         if (m_gps_started)
1151             UpdateKnownDirection();
1152     }
1153 }
1154
1155 void HunterFrame::OnSearchNewSave(wxCommandEvent &event)
1156 {
1157     if (!m_log)
1158         OnFileNew(event);
1159     else
1160         OnFileSave(event);
1161 }
1162
1163 void HunterFrame::OnSearchOpenClose(wxCommandEvent &event)
1164 {
1165     if (!m_log)
1166         OnFileOpen(event);
1167     else
1168         OnFileClose(event);
1169 }
1170
1171 void HunterFrame::OnSearchToggle(wxCommandEvent &event)
1172 {
1173         if (!m_capture)
1174         StartCaptureAutomatic();
1175         else
1176         StopCapture();
1177 }
1178
1179 void HunterFrame::StartCaptureAutomatic()
1180 {
1181     m_capture = true;
1182     m_one_shot = false;
1183     XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Stop"));
1184     XRCCTRL(*this, "search_once_button", wxButton)->Enable(false);
1185 }
1186
1187 void HunterFrame::StartCaptureOnce()
1188 {
1189     m_capture = true;
1190     m_one_shot = true;
1191     XRCCTRL(*this, "search_once_button", wxButton)->SetLabel(_T("Cancel"));
1192     XRCCTRL(*this, "search_toggle_button", wxButton)->Enable(false);
1193 }
1194
1195 void HunterFrame::StopCapture()
1196 {
1197     m_capture = false;
1198     m_one_shot = false;
1199     XRCCTRL(*this, "search_toggle_button", wxButton)->Enable(true);
1200     XRCCTRL(*this, "search_once_button", wxButton)->Enable(true);
1201     XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Automatic"));
1202     XRCCTRL(*this, "search_once_button", wxButton)->SetLabel(_T("One Shot"));
1203 }
1204
1205 void HunterFrame::OnSearchOnce(wxCommandEvent &event)
1206 {
1207     if (!m_capture)
1208         StartCaptureOnce();
1209     else
1210         StopCapture();
1211 }
1212
1213 void HunterFrame::OnDisplayOrientation(wxCommandEvent &event)
1214 {
1215     int selection = XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->GetSelection();
1216     if (selection == 0)
1217         m_tactical_panel->SetOrientation(TrackUp);
1218     else
1219         m_tactical_panel->SetOrientation(NorthUp);
1220
1221     m_settings->SetDisplayOrientation(selection);
1222 }
1223
1224 void HunterFrame::OnDisplayDoppler(wxCommandEvent &event)
1225 {
1226     bool checked = event.IsChecked();
1227     m_tactical_panel->SetDisplayDoppler(checked);
1228     m_settings->SetDisplayDoppler(checked);
1229 }
1230
1231 void HunterFrame::OnDisplayKnown(wxCommandEvent &event)
1232 {
1233     bool checked = event.IsChecked();
1234     m_tactical_panel->SetDisplayKnown(checked);
1235     m_settings->SetDisplayKnown(checked);
1236 }
1237
1238 void HunterFrame::OnDisplayEstimated(wxCommandEvent &event)
1239 {
1240     bool checked = event.IsChecked();
1241     m_tactical_panel->SetDisplayEstimated(checked);
1242     m_settings->SetDisplayEstimated(checked);
1243 }
1244
1245 void HunterFrame::OnHistogramSourceChg(wxCommandEvent &event)
1246 {
1247         m_histogram_source = XRCCTRL(*this, "statistics_source_radiobox", wxRadioBox)->GetSelection();
1248     if (m_histogram_source == 0)
1249         m_error_histogram_panel->SetData(m_search.Histogram());
1250     else if (m_histogram_source == 1)
1251         m_error_histogram_panel->SetData(m_known.Histogram());
1252     // TODO: remember this in m_settings
1253     Refresh(); // Needed?
1254 }
1255
1256 void HunterFrame::OnHistogramCoordsChg(wxCommandEvent &event)
1257 {
1258         int n = XRCCTRL(*this, "statistics_coords_radiobox", wxRadioBox)->GetSelection();
1259     if (n == 0)
1260         m_error_histogram_panel->SetMode(HistogramPanel::Rectangular);
1261     else if (n == 1)
1262         m_error_histogram_panel->SetMode(HistogramPanel::Polar);
1263     // TODO: remember this in m_settings
1264 }