2 Copyright 2006 Johnathan Corgan.
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.
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.
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.
19 // Application includes
24 #include "calibrate.h"
30 #include "spherical.h"
32 #include "samplelog.h"
34 #include "histogram.h"
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
41 // Event table for HunterFrame
42 BEGIN_EVENT_TABLE(HunterFrame, wxFrame)
43 // Application level events
44 EVT_CLOSE(HunterFrame::OnClose)
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)
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)
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)
65 EVT_MENU(XRCID("about_menuitem"), HunterFrame::OnAbout)
67 // Status panel events
68 EVT_RADIOBOX(XRCID("statistics_source_radiobox"), HunterFrame::OnHistogramSourceChg)
69 EVT_RADIOBOX(XRCID("statistics_coords_radiobox"), HunterFrame::OnHistogramCoordsChg)
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)
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)
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)
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)
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)
110 HunterFrame::HunterFrame() :
118 m_tactical_panel = NULL;
119 m_error_histogram_panel = NULL;
121 m_doppler_started = false;
122 m_gps_started = false;
125 m_histogram_source = 0;
127 InitializeSettings();
131 InitializeCalibration();
136 void HunterFrame::InitializeSettings()
138 m_settings = new HunterSettings();
139 wxSetWorkingDirectory(m_settings->GetWorkingDirectory());
142 void HunterFrame::InitializeWindows()
144 // Build main window controls
145 bool loaded = wxXmlResource::Get()->LoadFrame(this, NULL, wxT("main_frame"));
148 // Hack until XRC understands <gravity> for wxSplitterWindow!!
149 XRCCTRL(*this, "horiz_splitter", wxSplitterWindow)->SetSashGravity(1.0);
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);
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);
181 SetMenuBar(wxXmlResource::Get()->LoadMenuBar(wxT("main_menu")));
183 SetIcon(wxIcon(hunter_xpm));
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);
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);
199 void HunterFrame::InitializeDoppler()
201 m_doppler = new EZDoppler(this);
202 m_doppler->Initialize();
203 m_doppler_started = false;
207 void HunterFrame::InitializeGPS()
209 m_gps = new GPS(this);
210 m_gps_started = false;
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());
220 if (m_settings->GetGPSAutostart()) {
221 XRCCTRL(*this, "gps_autostart_checkbox", wxCheckBox)->SetValue(true);
226 void HunterFrame::InitializeCalibration()
228 XRCCTRL(*this, "calibration_all_checkbox", wxCheckBox)->SetValue(m_settings->GetCalibrationAffectAllRates());
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);
236 UpdateKnownLocation();
239 void HunterFrame::InitializeDisplay()
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);
246 if (m_settings->GetDisplayOrientation()) {
247 XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->SetSelection(1);
248 m_tactical_panel->SetOrientation(NorthUp);
251 if (m_settings->GetDisplayDoppler()) {
252 XRCCTRL(*this, "display_doppler_checkbox", wxCheckBox)->SetValue(true);
253 m_tactical_panel->SetDisplayDoppler(true);
256 if (m_settings->GetDisplayKnown()) {
257 XRCCTRL(*this, "display_known_checkbox", wxCheckBox)->SetValue(true);
258 m_tactical_panel->SetDisplayKnown(true);
261 if (m_settings->GetDisplayEstimated()) {
262 XRCCTRL(*this, "display_estimated_checkbox", wxCheckBox)->SetValue(true);
263 m_tactical_panel->SetDisplayEstimated(true);
267 void HunterFrame::InitializeSearch()
269 EnableSearchItems(false);
272 void HunterFrame::SetDopplerParams()
274 // NOTE: This is not in InitializeDoppler() as it needs to be called
275 // separately when resetting Doppler
277 // Adjust windows based on device status
278 if (!m_doppler->IsOnline()) {
279 // Disable all GUI elements associated with Doppler
281 // Doppler control tab
282 XRCCTRL(*this, "doppler_control_panel", wxPanel)->Disable();
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);
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);
297 // Doppler filter level
298 int filter = m_settings->GetDopplerFilter();
299 XRCCTRL(*this, "doppler_filter_slider", wxSlider)->SetValue(filter);
301 OnDopplerFilterChg(dummy);
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);
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);
316 if (m_settings->GetDopplerAutostart()) {
317 this->GetMenuBar()->FindItem(XRCID("doppler_autostart_menuitem"))->Check(true);
318 XRCCTRL(*this, "doppler_autostart_checkbox", wxCheckBox)->SetValue(true);
323 void HunterFrame::StartDoppler()
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);
335 void HunterFrame::StopDoppler()
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();
348 void HunterFrame::StartGPS()
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();
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);
365 if (m_search.HasSolution())
366 UpdateSearchDirection(true);
368 UpdateKnownDirection();
369 UpdateKnownStatistics();
372 void HunterFrame::StopGPS()
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();
383 UpdateGPSStatus(false);
384 UpdateSearchDirection(false);
385 UpdateKnownDirection();
386 UpdateKnownStatistics();
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(""));
392 void HunterFrame::EnableSearchItems(bool enable)
394 XRCCTRL(*this, "search_toggle_button", wxButton)->Enable(enable);
395 XRCCTRL(*this, "search_once_button", wxButton)->Enable(enable);
397 // These fields will get populated when samples come in
398 UpdateSearchStatus(false);
399 UpdateSearchLocation(false);
400 UpdateSearchDirection(false);
402 this->GetMenuBar()->FindItem(XRCID("file_save_menuitem"))->Enable(enable);
403 this->GetMenuBar()->FindItem(XRCID("file_close_menuitem"))->Enable(enable);
406 XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Stop"));
407 XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Automatic"));
411 void HunterFrame::OnClose(wxCloseEvent &event)
413 wxCommandEvent dummy;
415 // Cleanup for this scope should be done here
416 // TODO: factor out into methods corresponding to start up initialization
418 // Save window size and position
421 m_settings->SetWindowXPos(x);
422 m_settings->SetWindowYPos(y);
423 m_settings->SetWindowSize(GetSize());
425 wxASSERT(m_doppler != NULL);
426 if (m_doppler_started)
428 m_doppler->Finalize();
436 m_settings->SetWorkingDirectory(wxGetCwd());
448 void HunterFrame::OnExit(wxCommandEvent &event)
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
458 void HunterFrame::OnAbout(wxCommandEvent &event)
460 wxMessageBox(wxT("Copyright(C) 2005 Johnathan Corgan"),
461 wxT("About AE6HO Radio Location System"),
462 wxOK | wxICON_INFORMATION, this);
465 void HunterFrame::OnDopplerToggle(wxCommandEvent &event)
467 if (m_doppler_started)
473 void HunterFrame::OnDopplerAutostart(wxCommandEvent &event)
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);
481 void HunterFrame::OnDopplerReset(wxCommandEvent &event)
485 SetDopplerParams(); // restarts Doppler if autostart is configured
488 void HunterFrame::OnDopplerFilterChg(wxScrollEvent &event)
490 int n = XRCCTRL(*this, "doppler_filter_slider", wxSlider)->GetValue();
491 m_doppler->SetFilter(n);
492 m_settings->SetDopplerFilter(n);
495 void HunterFrame::OnDopplerRotationChg(wxCommandEvent &event)
497 int n = XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->GetSelection();
498 m_doppler->SelectRotationRate(n);
499 m_settings->SetDopplerRotation(n);
502 void HunterFrame::OnDopplerUpdate(EZDopplerUpdate &event)
504 if (!m_doppler_started) // Spurious event after doppler stopped
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));
515 UpdateDopplerStatus(true);
518 void HunterFrame::UpdateDopplerStatus(bool 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(""));
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);
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);
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);
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);
544 str.Printf(_T("%03i"), degree_normalize((int)(m_sample.Heading()+display_relative_bearing)));
545 XRCCTRL(*this, "doppler_absolute_bearing_text", wxStaticText)->SetLabel(str);
549 void HunterFrame::CalcKnownStatistics()
552 m_known.Calc(m_log->Samples());
554 UpdateKnownStatistics();
557 void HunterFrame::UpdateKnownStatistics()
559 if (m_error_histogram_panel && m_histogram_source == 1)
560 m_error_histogram_panel->SetData(m_known.Histogram());
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(""));
572 str.Printf(_T("%i"), m_known.Count());
573 XRCCTRL(*this, "error_count_text", wxStaticText)->SetLabel(str);
575 str.Printf(_T("%i"), m_known.Mode());
576 XRCCTRL(*this, "error_mode_text", wxStaticText)->SetLabel(str);
578 str.Printf(_T("%5.1f"), m_known.Mean());
579 XRCCTRL(*this, "error_mean_text", wxStaticText)->SetLabel(str);
581 str.Printf(_T("%1.3f"), m_known.Concentration());
582 XRCCTRL(*this, "error_conc_text", wxStaticText)->SetLabel(str);
585 void HunterFrame::OnCalibrationEqualize(wxCommandEvent &event)
587 CalibrationDialog *dlg = new CalibrationDialog(this);
591 void HunterFrame::DoCalibrationStep(int which)
595 if (which == 0) { // Set up doppler
596 delay = XRCCTRL(*this, "doppler_filter_slider", wxSlider)->GetValue(); // Empirically determined
601 // Set rotation rate for this step
602 wxCommandEvent dummy;
603 XRCCTRL(*this, "doppler_rotation_radiobox", wxRadioBox)->SetSelection(which);
604 OnDopplerRotationChg(dummy);
606 // Wait until stable value
608 while(wxGetElapsedTime(false) < 1000*delay)
609 wxYield(); // eeewwww!
611 // Now stable reading can be calibrated
612 m_doppler->Calibrate(-M_PI); // Sets to zero
614 float offset=m_doppler->GetCalibration(which);
615 m_settings->SetDopplerCalibration(which, offset);
618 void HunterFrame::OnCalibrationZero(wxCommandEvent &event)
620 m_doppler->SetOffset();
621 float offset=m_doppler->GetCalibration(6);
622 m_settings->SetDopplerCalibration(6, offset);
625 void HunterFrame::OnCalibrationAdjustLeft(wxSpinEvent &event)
627 if (m_settings->GetCalibrationAffectAllRates())
628 m_doppler->NudgeAll(to_radians(-0.5));
630 m_doppler->Nudge(to_radians(-0.5));
632 float offset=m_doppler->GetCalibration(6);
633 m_settings->SetDopplerCalibration(6, offset);
636 void HunterFrame::OnCalibrationAdjustRight(wxSpinEvent &event)
638 if (m_settings->GetCalibrationAffectAllRates())
639 m_doppler->NudgeAll(to_radians(0.5));
641 m_doppler->Nudge(to_radians(0.5));
643 float offset=m_doppler->GetCalibration(6);
644 m_settings->SetDopplerCalibration(6, offset);
647 void HunterFrame::OnCalibrationAffectAllRates(wxCommandEvent &event)
649 bool affect = event.IsChecked();
650 XRCCTRL(*this, "calibration_all_checkbox", wxCheckBox)->SetValue(affect);
651 m_settings->SetCalibrationAffectAllRates(affect);
654 void HunterFrame::OnGPSToggle(wxCommandEvent &event)
662 void HunterFrame::OnGPSAutostart(wxCommandEvent &event)
664 bool start = XRCCTRL(*this, "gps_autostart_checkbox", wxCheckBox)->GetValue();
665 m_settings->SetGPSAutostart(start);
668 void HunterFrame::OnGPSDeviceSelect(wxCommandEvent &event)
670 wxString device = XRCCTRL(*this, "gps_device_combobox", wxComboBox)->GetValue();
671 m_settings->SetGPSDeviceName(device);
674 void HunterFrame::OnGPSUpdate(GPSUpdate &update)
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());
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();
691 if (m_log && m_capture && m_doppler_started &&
692 m_sample.Speed() >= 5.0 && m_sample.Valid()) {
693 m_log->Add(m_sample);
695 if (m_one_shot == true)
699 if (m_search.HasSolution()) {
700 UpdateSearchStatus(true);
701 UpdateSearchDirection(true);
704 delete update.m_gprmc;
707 void HunterFrame::CalcSolution()
709 GetStatusBar()->SetStatusText(wxT("Searching..."));
710 m_search.Solve(m_log);
713 void HunterFrame::OnSearchUpdate(SearchUpdate &update)
716 GetStatusBar()->SetStatusText(wxT(""));
718 if (m_search.HasSolution()) {
719 UpdateSearchStatus(true);
720 UpdateSearchLocation(true);
722 UpdateSearchDirection(true);
726 void HunterFrame::UpdateSearchStatus(bool display)
730 if (m_error_histogram_panel && m_histogram_source == 0)
731 m_error_histogram_panel->SetData(m_search.Histogram());
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(""));
742 str.Printf(_T("%i"), m_log->Count());
743 XRCCTRL(*this, "search_count_text", wxStaticText)->SetLabel(str);
745 str.Printf(_T("%s"), m_search.Busy() ? _T("BUSY") : _T(""));
746 XRCCTRL(*this, "search_status_text", wxStaticText)->SetLabel(str);
748 str.Printf(_T("%i"), m_search.Mode());
749 XRCCTRL(*this, "search_mode_text", wxStaticText)->SetLabel(str);
751 str.Printf(_T("%6.1f"), m_search.Mean());
752 XRCCTRL(*this, "search_mean_text", wxStaticText)->SetLabel(str);
754 str.Printf(_T("%i"), (int)(m_search.Concentration()*100.0));
755 XRCCTRL(*this, "search_score_text", wxStaticText)->SetLabel(str);
758 void HunterFrame::UpdateGPSStatus(bool 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(""));
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);
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);
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);
785 // Calculate speed to display
786 str.Printf(_T("%i"), (unsigned int)(m_sample.Speed()));
787 XRCCTRL(*this, "gps_speed_text", wxStaticText)->SetLabel(str);
790 void HunterFrame::UpdateSearchDirection(bool 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);
802 float estimated_bearing =
803 bearing(m_sample.Location(), m_search.GetEstimatedLocation());
805 float estimated_range =
806 range(m_sample.Location(), m_search.GetEstimatedLocation());
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);
816 str.Printf(_T("%1.2f"), estimated_range);
817 XRCCTRL(*this, "transmitter_estimated_range_text", wxStaticText)->SetLabel(str);
820 void HunterFrame::UpdateSearchLocation(bool 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(""));
831 Spherical estimated_location = m_search.GetEstimatedLocation();
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);
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);
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);
850 str.Printf(_T("%1.2f"), distance_error);
851 XRCCTRL(*this, "search_disterror_text", wxStaticText)->SetLabel(str);
855 void HunterFrame::UpdateKnownDirection()
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);
867 float actual_bearing = bearing(m_sample.Location(), m_known.Location());
868 float actual_range = range(m_sample.Location(), m_known.Location());
870 m_tactical_panel->SetActualBearing(actual_bearing);
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);
879 str.Printf(_T("%1.2f"), actual_range);
880 XRCCTRL(*this, "transmitter_actual_range_text", wxStaticText)->SetLabel(str);
883 void HunterFrame::UpdateKnownLocation()
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(""));
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
901 void HunterFrame::OnKnownTransmitterUpdate(wxCommandEvent &event)
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();
907 double lat = 0.0, lon = 0.0;
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();
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();
922 m_known.Location(Spherical(lat, lon));
923 CalcKnownStatistics();
925 m_settings->SetKnownTransmitterLongitude(lon);
926 m_settings->SetKnownTransmitterLatitude(lat);
928 UpdateKnownLocation();
930 UpdateKnownDirection();
933 void HunterFrame::OnUseKnownTransmitter(wxCommandEvent &event)
935 if (event.IsChecked())
936 m_known.Location(Spherical(m_settings->GetKnownTransmitterLatitude(),
937 m_settings->GetKnownTransmitterLongitude()));
941 m_settings->SetUseKnownTransmitter(m_known.IsSet());
942 CalcKnownStatistics();
943 UpdateKnownLocation();
945 UpdateKnownDirection();
948 void HunterFrame::UpdateGPSValidity(bool valid)
950 m_sample.Valid(valid);
952 wxColour color = *wxBLACK;
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);
965 void HunterFrame::OnFileNew(wxCommandEvent &event)
968 wxLogDebug(_T("Not Implemented: current log is discarded without saving..."));
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);
984 void HunterFrame::MakeNewLogAndSearch()
986 m_log = new SampleLog();
990 void HunterFrame::OnFileOpen(wxCommandEvent &event)
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();
1014 void HunterFrame::OnFileSave(wxCommandEvent &event)
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);
1029 m_log->Save(); // Adds additional samples since last save
1032 void HunterFrame::OnFileClose(wxCommandEvent &event)
1034 // FIXME: ask user if they want to save changed data instead of going straight to save dialog
1035 if (m_log->IsDirty())
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();
1053 void HunterFrame::OnCalibrationSaveToFile(wxCommandEvent &event)
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++) {
1063 float offset = m_settings->GetDopplerCalibration(i);
1064 str.Printf(_T("%f\n"), offset);
1065 file.Write(str.c_str(), str.length());
1070 void HunterFrame::OnCalibrationLoadFromFile(wxCommandEvent &event)
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++) {
1082 count = file.Read(&ch, 1);
1083 while (count == 1 && ch != '\n') {
1085 count = file.Read(&ch, 1);
1087 float offset = atof((char *)line.c_str());
1088 m_settings->SetDopplerCalibration(i, offset);
1089 m_doppler->SetCalibration(i, offset);
1094 void HunterFrame::OnCalibrationSaveTransmitter(wxCommandEvent &event)
1097 if (!m_known.IsSet())
1098 return; // FIXME: disable menu item when no known transmitter so we can't get here
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);
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());
1113 void HunterFrame::OnCalibrationLoadTransmitter(wxCommandEvent &event)
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;
1123 for (int i = 0; i < 2; i++) {
1128 count = file.Read(&ch, 1);
1129 while (count == 1 && ch != '\n') {
1131 count = file.Read(&ch, 1);
1141 m_known.Location(Spherical(latitude, longitude));
1142 CalcKnownStatistics();
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());
1149 UpdateKnownLocation();
1151 UpdateKnownDirection();
1155 void HunterFrame::OnSearchNewSave(wxCommandEvent &event)
1163 void HunterFrame::OnSearchOpenClose(wxCommandEvent &event)
1171 void HunterFrame::OnSearchToggle(wxCommandEvent &event)
1174 StartCaptureAutomatic();
1179 void HunterFrame::StartCaptureAutomatic()
1183 XRCCTRL(*this, "search_toggle_button", wxButton)->SetLabel(_T("Stop"));
1184 XRCCTRL(*this, "search_once_button", wxButton)->Enable(false);
1187 void HunterFrame::StartCaptureOnce()
1191 XRCCTRL(*this, "search_once_button", wxButton)->SetLabel(_T("Cancel"));
1192 XRCCTRL(*this, "search_toggle_button", wxButton)->Enable(false);
1195 void HunterFrame::StopCapture()
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"));
1205 void HunterFrame::OnSearchOnce(wxCommandEvent &event)
1213 void HunterFrame::OnDisplayOrientation(wxCommandEvent &event)
1215 int selection = XRCCTRL(*this, "display_orientation_radiobox", wxRadioBox)->GetSelection();
1217 m_tactical_panel->SetOrientation(TrackUp);
1219 m_tactical_panel->SetOrientation(NorthUp);
1221 m_settings->SetDisplayOrientation(selection);
1224 void HunterFrame::OnDisplayDoppler(wxCommandEvent &event)
1226 bool checked = event.IsChecked();
1227 m_tactical_panel->SetDisplayDoppler(checked);
1228 m_settings->SetDisplayDoppler(checked);
1231 void HunterFrame::OnDisplayKnown(wxCommandEvent &event)
1233 bool checked = event.IsChecked();
1234 m_tactical_panel->SetDisplayKnown(checked);
1235 m_settings->SetDisplayKnown(checked);
1238 void HunterFrame::OnDisplayEstimated(wxCommandEvent &event)
1240 bool checked = event.IsChecked();
1241 m_tactical_panel->SetDisplayEstimated(checked);
1242 m_settings->SetDisplayEstimated(checked);
1245 void HunterFrame::OnHistogramSourceChg(wxCommandEvent &event)
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?
1256 void HunterFrame::OnHistogramCoordsChg(wxCommandEvent &event)
1258 int n = XRCCTRL(*this, "statistics_coords_radiobox", wxRadioBox)->GetSelection();
1260 m_error_histogram_panel->SetMode(HistogramPanel::Rectangular);
1262 m_error_histogram_panel->SetMode(HistogramPanel::Polar);
1263 // TODO: remember this in m_settings