1 /*
2 * graph.cpp
3 * PHD Guiding
4 *
5 * Created by Craig Stark.
6 * Copyright (c) 2008-2010 Craig Stark.
7 * All rights reserved.
8 *
9 * Seriously modified by Bret McKee
10 * Copyright (c) 2013 Bret McKee
11 * All rights reserved.
12 *
13 * This source code is distributed under the following "BSD" license
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are met:
16 * Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 * Redistributions in binary form must reproduce the above copyright notice,
19 * this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * Neither the name of Craig Stark, Stark Labs,
22 * Bret McKee, Dad Dog Development, Ltd, nor the names of its
23 * contributors may be used to endorse or promote products derived from
24 * this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
30 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 *
38 */
39
40 #include "phd.h"
41 #include <wx/dcbuffer.h>
42 #include <wx/utils.h>
43 #include <wx/colordlg.h>
44
wxBEGIN_EVENT_TABLE(GraphLogWindow,wxWindow)45 wxBEGIN_EVENT_TABLE(GraphLogWindow, wxWindow)
46 EVT_PAINT(GraphLogWindow::OnPaint)
47 EVT_BUTTON(BUTTON_GRAPH_SETTINGS,GraphLogWindow::OnButtonSettings)
48 EVT_MENU_RANGE(GRAPH_RADEC, GRAPH_DXDY, GraphLogWindow::OnRADecDxDy)
49 EVT_MENU_RANGE(GRAPH_ARCSECS, GRAPH_PIXELS, GraphLogWindow::OnArcsecsPixels)
50 EVT_MENU(GRAPH_SCALE_CORR, GraphLogWindow::OnCorrectionScale)
51 EVT_MENU(GRAPH_RADX_COLOR, GraphLogWindow::OnRADxColor)
52 EVT_MENU(GRAPH_DECDY_COLOR, GraphLogWindow::OnDecDyColor)
53 EVT_MENU(GRAPH_STAR_MASS, GraphLogWindow::OnMenuStarMass)
54 EVT_MENU(GRAPH_STAR_SNR, GraphLogWindow::OnMenuStarSNR)
55 EVT_BUTTON(BUTTON_GRAPH_LENGTH,GraphLogWindow::OnButtonLength)
56 EVT_MENU_RANGE(MENU_LENGTH_BEGIN, MENU_LENGTH_END, GraphLogWindow::OnMenuLength)
57 EVT_BUTTON(BUTTON_GRAPH_HEIGHT,GraphLogWindow::OnButtonHeight)
58 EVT_MENU_RANGE(MENU_HEIGHT_BEGIN, MENU_HEIGHT_END, GraphLogWindow::OnMenuHeight)
59 EVT_BUTTON(BUTTON_GRAPH_CLEAR,GraphLogWindow::OnButtonClear)
60 EVT_CHECKBOX(CHECKBOX_GRAPH_TRENDLINES,GraphLogWindow::OnCheckboxTrendlines)
61 EVT_CHECKBOX(CHECKBOX_GRAPH_CORRECTIONS,GraphLogWindow::OnCheckboxCorrections)
62 wxEND_EVENT_TABLE()
63
64 #ifdef __WXOSX__
65 # define OSX_SMALL_FONT(lbl) do { (lbl)->SetFont(*wxSMALL_FONT); } while (0)
66 #else
67 # define OSX_SMALL_FONT(lbl)
68 #endif
69
70 GraphLogWindow::GraphLogWindow(wxWindow *parent) :
71 wxWindow(parent,wxID_ANY,wxDefaultPosition,wxDefaultSize, wxFULL_REPAINT_ON_RESIZE,_T("Graph"))
72 {
73 SetBackgroundStyle(wxBG_STYLE_PAINT);
74
75 wxBoxSizer *pMainSizer = new wxBoxSizer(wxHORIZONTAL);
76 wxBoxSizer *pButtonSizer = new wxBoxSizer(wxVERTICAL);
77 wxBoxSizer *pClientSizer = new wxBoxSizer(wxVERTICAL);
78
79 m_pClient = new GraphLogClientWindow(this);
80
81 m_pControlSizer = new wxFlexGridSizer(3, 1, 0, 0);
82 m_ControlNbRows = 3;
83
84 if (pMount)
85 {
86 m_pXControlPane = pMount->GetXGuideAlgorithmControlPane(this);
87 m_pYControlPane = pMount->GetYGuideAlgorithmControlPane(this);
88 m_pScopePane = pMount->GetGraphControlPane(this, _("Scope:"));
89 }
90 else
91 {
92 m_pXControlPane = nullptr;
93 m_pYControlPane = nullptr;
94 m_pScopePane = nullptr;
95 }
96
97 if (m_pXControlPane)
98 {
99 m_pControlSizer->Add(m_pXControlPane, wxSizerFlags().Border(wxTOP, 5));
100 }
101
102 if (m_pYControlPane)
103 {
104 m_pControlSizer->Add(m_pYControlPane, wxSizerFlags().Border(wxTOP, 5));
105 }
106
107 if (m_pScopePane)
108 {
109 m_pControlSizer->Add(m_pScopePane, wxSizerFlags().Border(wxTOP, 5));
110 }
111
112 m_visible = false;
113
114 SetBackgroundColour(*wxBLACK);
115
116 m_pLengthButton = new OptionsButton(this,BUTTON_GRAPH_LENGTH, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
117 m_pLengthButton->SetToolTip(_("Select the number of frames of history to display on the X-axis"));
118 m_pLengthButton->SetLabel(wxString::Format(_T("x: %3d"), m_pClient->m_length));
119 pButtonSizer->Add(m_pLengthButton, wxSizerFlags().Border(wxTOP, 5).Expand());
120
121 m_pHeightButton = new OptionsButton(this,BUTTON_GRAPH_HEIGHT, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
122 m_heightButtonLabelVal = 0;
123 UpdateHeightButtonLabel();
124 pButtonSizer->Add(m_pHeightButton, wxSizerFlags().Expand());
125
126 m_pSettingsButton = new OptionsButton(this, BUTTON_GRAPH_SETTINGS, _("Settings"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
127 m_pSettingsButton->SetToolTip(_("Graph settings"));
128 pButtonSizer->Add(m_pSettingsButton, wxSizerFlags().Expand());
129
130 wxButton *clearButton = new wxButton(this, BUTTON_GRAPH_CLEAR, _("Clear"));
131 clearButton->SetToolTip(_("Clear graph data. You can also partially clear the graph by holding down the Ctrl key and clicking on the graph where you want the data to start."));
132 clearButton->SetBackgroundStyle(wxBG_STYLE_TRANSPARENT);
133 pButtonSizer->Add(clearButton, wxSizerFlags().Expand());
134
135 m_pCheckboxTrendlines = new wxCheckBox(this,CHECKBOX_GRAPH_TRENDLINES,_("Trendlines"));
136 #if defined(__WXOSX__)
137 // workaround inability to set checkbox foreground color
138 m_pCheckboxTrendlines->SetBackgroundColour(wxColor(200, 200, 200));
139 #else
140 m_pCheckboxTrendlines->SetForegroundColour(*wxLIGHT_GREY);
141 #endif
142 m_pCheckboxTrendlines->SetToolTip(_("Plot trend lines"));
143 pButtonSizer->Add(m_pCheckboxTrendlines, wxSizerFlags().Expand().Border(wxTOP, 1));
144
145 m_pCheckboxCorrections = new wxCheckBox(this,CHECKBOX_GRAPH_CORRECTIONS,_("Corrections"));
146 #if defined(__WXOSX__)
147 // workaround inability to set checkbox foreground color
148 m_pCheckboxCorrections->SetBackgroundColour(wxColor(200, 200, 200));
149 #else
150 m_pCheckboxCorrections->SetForegroundColour(*wxLIGHT_GREY);
151 #endif
152 m_pCheckboxCorrections->SetToolTip(_("Display mount corrections"));
153 m_pCheckboxCorrections->SetValue(m_pClient->m_showCorrections);
154 pButtonSizer->Add(m_pCheckboxCorrections, wxSizerFlags().Expand());
155
156 wxBoxSizer *pLabelSizer = new wxBoxSizer(wxHORIZONTAL);
157
158 m_pLabel1 = new wxStaticText(this, wxID_ANY, _("RA"));
159 m_pLabel1->SetForegroundColour(m_pClient->m_raOrDxColor);
160 m_pLabel1->SetBackgroundColour(*wxBLACK);
161 pLabelSizer->Add(m_pLabel1, wxSizerFlags().Left());
162
163 m_pLabel2 = new wxStaticText(this, wxID_ANY, _("Dec"));
164 m_pLabel2->SetForegroundColour(m_pClient->m_decOrDyColor);
165 m_pLabel2->SetBackgroundColour(*wxBLACK);
166
167 UpdateRADecDxDyLabels();
168
169 pLabelSizer->AddStretchSpacer();
170 pLabelSizer->Add(m_pLabel2, wxSizerFlags().Right());
171
172 pButtonSizer->Add(pLabelSizer, wxSizerFlags().Expand());
173
174 wxStaticText *lbl;
175
176 lbl = new wxStaticText(this, wxID_ANY, _("RMS Error [px]:"), wxDefaultPosition, wxDefaultSize);
177 OSX_SMALL_FONT(lbl);
178 lbl->SetForegroundColour(*wxLIGHT_GREY);
179 lbl->SetBackgroundColour(*wxBLACK);
180 pButtonSizer->Add(lbl);
181
182 wxSize size1 = GetTextExtent(_T("XXXX"));
183
184 wxBoxSizer *szRaRMS = new wxBoxSizer(wxHORIZONTAL);
185 lbl = new wxStaticText(this, wxID_ANY, _("RA"), wxDefaultPosition, size1, wxALIGN_RIGHT);
186 OSX_SMALL_FONT(lbl);
187 lbl->SetForegroundColour(*wxLIGHT_GREY);
188 lbl->SetBackgroundColour(*wxBLACK);
189 m_pClient->m_pRaRMS = new wxStaticText(this, wxID_ANY, _T("0.00"), wxDefaultPosition, wxSize(80,-1));
190 OSX_SMALL_FONT(m_pClient->m_pRaRMS);
191 m_pClient->m_pRaRMS->SetForegroundColour(*wxLIGHT_GREY);
192 m_pClient->m_pRaRMS->SetBackgroundColour(*wxBLACK);
193 szRaRMS->Add(lbl, wxSizerFlags().Border(wxRIGHT, 5));
194 szRaRMS->Add(m_pClient->m_pRaRMS);
195 pButtonSizer->Add(szRaRMS);
196
197 wxBoxSizer *szDecRMS = new wxBoxSizer(wxHORIZONTAL);
198 lbl = new wxStaticText(this, wxID_ANY, _("Dec"), wxDefaultPosition, size1, wxALIGN_RIGHT);
199 OSX_SMALL_FONT(lbl);
200 lbl->SetForegroundColour(*wxLIGHT_GREY);
201 lbl->SetBackgroundColour(*wxBLACK);
202 m_pClient->m_pDecRMS = new wxStaticText(this, wxID_ANY, _T("0.00"), wxDefaultPosition, wxSize(80,-1));
203 OSX_SMALL_FONT(m_pClient->m_pDecRMS);
204 m_pClient->m_pDecRMS->SetForegroundColour(*wxLIGHT_GREY);
205 m_pClient->m_pDecRMS->SetBackgroundColour(*wxBLACK);
206 szDecRMS->Add(lbl, wxSizerFlags().Border(wxRIGHT, 5));
207 szDecRMS->Add(m_pClient->m_pDecRMS);
208 pButtonSizer->Add(szDecRMS);
209
210 wxBoxSizer *szTotRMS = new wxBoxSizer(wxHORIZONTAL);
211 lbl = new wxStaticText(this, wxID_ANY, _("Tot"), wxDefaultPosition, size1, wxALIGN_RIGHT);
212 OSX_SMALL_FONT(lbl);
213 lbl->SetForegroundColour(*wxLIGHT_GREY);
214 lbl->SetBackgroundColour(*wxBLACK);
215 m_pClient->m_pTotRMS = new wxStaticText(this, wxID_ANY, _T("0.00"), wxDefaultPosition, wxSize(80,-1));
216 OSX_SMALL_FONT(m_pClient->m_pTotRMS);
217 m_pClient->m_pTotRMS->SetForegroundColour(*wxLIGHT_GREY);
218 m_pClient->m_pTotRMS->SetBackgroundColour(*wxBLACK);
219 szTotRMS->Add(lbl, wxSizerFlags().Border(wxRIGHT, 5));
220 szTotRMS->Add(m_pClient->m_pTotRMS);
221 pButtonSizer->Add(szTotRMS);
222
223 m_pClient->m_pOscIndex = new wxStaticText(this, wxID_ANY, _("RA Osc: 0.00"));
224 OSX_SMALL_FONT(m_pClient->m_pOscIndex);
225 m_pClient->m_pOscIndex->SetForegroundColour(*wxLIGHT_GREY);
226 m_pClient->m_pOscIndex->SetBackgroundColour(*wxBLACK);
227 pButtonSizer->Add(m_pClient->m_pOscIndex);
228
229 pClientSizer->Add(m_pClient, wxSizerFlags().Expand().Proportion(1));
230 pClientSizer->Add(m_pControlSizer, wxSizerFlags().Expand().Border(wxRIGHT | wxLEFT | wxBOTTOM, 10));
231 pMainSizer->Add(pButtonSizer, wxSizerFlags().Left().DoubleHorzBorder().Expand());
232 pMainSizer->Add(pClientSizer, wxSizerFlags().Expand().Proportion(1));
233
234 SetSizer(pMainSizer);
235 pMainSizer->SetSizeHints(this);
236 m_pClient->m_timeBase = ::wxGetUTCTimeMillis().GetValue();
237 }
238
~GraphLogWindow()239 GraphLogWindow::~GraphLogWindow()
240 {
241 delete m_pClient;
242 }
243
GetRaOrDxColor()244 const wxColor& GraphLogWindow::GetRaOrDxColor()
245 {
246 return m_pClient->m_raOrDxColor;
247 }
248
GetDecOrDyColor()249 const wxColor& GraphLogWindow::GetDecOrDyColor()
250 {
251 return m_pClient->m_decOrDyColor;
252 }
253
StringWidth(const wxString & string)254 int GraphLogWindow::StringWidth(const wxString& string)
255 {
256 int width, height;
257
258 GetParent()->GetTextExtent(string, &width, &height);
259
260 return width;
261 }
262
OnButtonSettings(wxCommandEvent & WXUNUSED (evt))263 void GraphLogWindow::OnButtonSettings(wxCommandEvent& WXUNUSED(evt))
264 {
265 wxMenu *menu = new wxMenu();
266 wxMenuItem *item1, *item2;
267
268 // setup RA/Dec / dx/dy items
269 item1 = menu->Append(wxID_ANY, _("Plot mode"));
270 item1->Enable(false);
271 item1 = menu->AppendRadioItem(GRAPH_RADEC, _("RA / Dec"));
272 item2 = menu->AppendRadioItem(GRAPH_DXDY, _("dx / dy"));
273 if (m_pClient->m_mode == GraphLogClientWindow::MODE_RADEC)
274 item1->Check();
275 else
276 item2->Check();
277 menu->AppendSeparator();
278
279 // setup Arcses / pixels items
280 item1 = menu->Append(wxID_ANY, _("Y-axis units"));
281 item1->Enable(false);
282 item1 = menu->AppendRadioItem(GRAPH_ARCSECS, _("Arc-seconds"));
283 bool enable_arcsecs = pFrame->GetCameraPixelScale() != 1.0;
284 if (!enable_arcsecs)
285 item1->Enable(false);
286 item2 = menu->AppendRadioItem(GRAPH_PIXELS, _("Pixels"));
287 if (m_pClient->m_heightUnits == UNIT_ARCSEC && enable_arcsecs)
288 item1->Check();
289 else
290 item2->Check();
291 menu->AppendSeparator();
292
293 // setup Correction styles
294 item1 = menu->AppendCheckItem(GRAPH_SCALE_CORR, _("Corrections to Scale"));
295 item1->Check(m_pClient->m_correctionsToScale);
296 menu->AppendSeparator();
297
298 item1 = menu->AppendCheckItem(GRAPH_STAR_MASS, _("Star Mass"));
299 item1->Check(m_pClient->m_showStarMass);
300 item1 = menu->AppendCheckItem(GRAPH_STAR_SNR, _("Star SNR"));
301 item1->Check(m_pClient->m_showStarSNR);
302 menu->AppendSeparator();
303
304 // setup color selection items
305 if (m_pClient->m_mode == GraphLogClientWindow::MODE_RADEC)
306 {
307 menu->Append(GRAPH_RADX_COLOR, _("RA Color..."));
308 menu->Append(GRAPH_DECDY_COLOR, _("Dec Color..."));
309 }
310 else
311 {
312 menu->Append(GRAPH_RADX_COLOR, _("dx Color..."));
313 menu->Append(GRAPH_DECDY_COLOR, _("dy Color..."));
314 }
315
316 PopupMenu(menu, m_pSettingsButton->GetPosition().x,
317 m_pSettingsButton->GetPosition().y + m_pSettingsButton->GetSize().GetHeight());
318
319 delete menu;
320 }
321
UpdateRADecDxDyLabels()322 void GraphLogWindow::UpdateRADecDxDyLabels()
323 {
324 switch (m_pClient->m_mode)
325 {
326 case GraphLogClientWindow::MODE_RADEC:
327 m_pLabel1->SetLabel(_("RA"));
328 m_pLabel2->SetLabel(_("Dec"));
329 break;
330 case GraphLogClientWindow::MODE_DXDY:
331 m_pLabel1->SetLabel(_("dx"));
332 m_pLabel2->SetLabel(_("dy"));
333 break;
334 }
335 }
336
SetMode(GraphLogClientWindow::GRAPH_MODE newMode)337 GraphLogClientWindow::GRAPH_MODE GraphLogWindow::SetMode(GraphLogClientWindow::GRAPH_MODE newMode)
338 {
339 GraphLogClientWindow::GRAPH_MODE prev = m_pClient->m_mode;
340 if (m_pClient->m_mode != newMode)
341 {
342 m_pClient->m_mode = newMode;
343 pConfig->Global.SetInt("/graph/ScopeOrCameraUnits", (int) m_pClient->m_mode);
344 UpdateRADecDxDyLabels();
345 Refresh();
346 }
347 return prev;
348 }
349
OnRADecDxDy(wxCommandEvent & evt)350 void GraphLogWindow::OnRADecDxDy(wxCommandEvent& evt)
351 {
352 switch (evt.GetId())
353 {
354 case GRAPH_DXDY:
355 SetMode(GraphLogClientWindow::MODE_DXDY);
356 break;
357 case GRAPH_RADEC:
358 SetMode(GraphLogClientWindow::MODE_RADEC);
359 break;
360 }
361 }
362
OnArcsecsPixels(wxCommandEvent & evt)363 void GraphLogWindow::OnArcsecsPixels(wxCommandEvent& evt)
364 {
365 switch (evt.GetId())
366 {
367 case GRAPH_ARCSECS:
368 m_pClient->m_heightUnits = UNIT_ARCSEC;
369 break;
370 case GRAPH_PIXELS:
371 m_pClient->m_heightUnits = UNIT_PIXELS;
372 break;
373 }
374 pConfig->Global.SetInt("/graph/HeightUnits", (int)m_pClient->m_heightUnits);
375 Refresh();
376 }
377
OnCorrectionScale(wxCommandEvent & evt)378 void GraphLogWindow::OnCorrectionScale(wxCommandEvent& evt)
379 {
380 m_pClient->m_correctionsToScale = evt.IsChecked();
381 pConfig->Global.SetBoolean("/graph/correctionsToScale", m_pClient->m_correctionsToScale);
382 Refresh();
383 }
384
OnMenuStarMass(wxCommandEvent & evt)385 void GraphLogWindow::OnMenuStarMass(wxCommandEvent& evt)
386 {
387 m_pClient->m_showStarMass = evt.IsChecked();
388 pConfig->Global.SetBoolean("/graph/showStarMass", m_pClient->m_showStarMass);
389 Refresh();
390 }
391
OnMenuStarSNR(wxCommandEvent & evt)392 void GraphLogWindow::OnMenuStarSNR(wxCommandEvent& evt)
393 {
394 m_pClient->m_showStarSNR = evt.IsChecked();
395 pConfig->Global.SetBoolean("/graph/showStarSNR", m_pClient->m_showStarSNR);
396 Refresh();
397 }
398
OnRADxColor(wxCommandEvent & evt)399 void GraphLogWindow::OnRADxColor(wxCommandEvent& evt)
400 {
401 wxColourData cdata;
402 cdata.SetColour(m_pClient->m_raOrDxColor);
403 wxColourDialog cdialog(this, &cdata);
404 cdialog.SetTitle(_("RA or dx Color"));
405 if (cdialog.ShowModal() == wxID_OK)
406 {
407 cdata = cdialog.GetColourData();
408 m_pClient->m_raOrDxColor = cdata.GetColour();
409 pConfig->Global.SetString("/graph/RAColor", m_pClient->m_raOrDxColor.GetAsString(wxC2S_HTML_SYNTAX));
410 m_pLabel1->SetForegroundColour(m_pClient->m_raOrDxColor);
411 Refresh();
412 }
413 }
414
OnDecDyColor(wxCommandEvent & evt)415 void GraphLogWindow::OnDecDyColor(wxCommandEvent& evt)
416 {
417 wxColourData cdata;
418 cdata.SetColour(m_pClient->m_decOrDyColor);
419 wxColourDialog cdialog(this, &cdata);
420 cdialog.SetTitle(_("Dec or dy Color"));
421 if (cdialog.ShowModal() == wxID_OK)
422 {
423 cdata = cdialog.GetColourData();
424 m_pClient->m_decOrDyColor = cdata.GetColour();
425 pConfig->Global.SetString("/graph/DecColor", m_pClient->m_decOrDyColor.GetAsString(wxC2S_HTML_SYNTAX));
426 m_pLabel2->SetForegroundColour(m_pClient->m_decOrDyColor);
427 Refresh();
428 }
429 }
430
GetLengthMenu()431 wxMenu *GraphLogWindow::GetLengthMenu()
432 {
433 wxMenu *menu = new wxMenu();
434 unsigned int val = m_pClient->m_minLength;
435 for (int id = MENU_LENGTH_BEGIN; id <= MENU_LENGTH_END; id++)
436 {
437 wxMenuItem *item = menu->AppendRadioItem(id, wxString::Format("%d", val));
438 if (val == m_pClient->m_length)
439 item->Check(true);
440 val *= 2;
441 if (val > m_pClient->m_history.capacity())
442 break;
443 }
444 return menu;
445 }
446
OnButtonLength(wxCommandEvent & WXUNUSED (evt))447 void GraphLogWindow::OnButtonLength(wxCommandEvent& WXUNUSED(evt))
448 {
449 wxMenu *menu = GetLengthMenu();
450
451 PopupMenu(menu, m_pLengthButton->GetPosition().x,
452 m_pLengthButton->GetPosition().y + m_pLengthButton->GetSize().GetHeight());
453
454 delete menu;
455 }
456
OnMenuLength(wxCommandEvent & evt)457 void GraphLogWindow::OnMenuLength(wxCommandEvent& evt)
458 {
459 unsigned int val = m_pClient->m_minLength;
460 for (int id = MENU_LENGTH_BEGIN; id < evt.GetId(); id++)
461 val *= 2;
462
463 SetLength(val);
464 Refresh();
465 }
466
GetLength() const467 int GraphLogWindow::GetLength() const
468 {
469 return m_pClient->m_length;
470 }
471
GetHistoryItemCount() const472 unsigned int GraphLogWindow::GetHistoryItemCount() const
473 {
474 return m_pClient->GetItemCount();
475 }
476
SetLength(int length)477 void GraphLogWindow::SetLength(int length)
478 {
479 if (length > (int) m_pClient->m_history.capacity())
480 length = m_pClient->m_history.capacity();
481 if (length < (int) m_pClient->m_minLength)
482 length = m_pClient->m_minLength;
483 m_pClient->m_length = length;
484 m_pClient->m_noDitherDec.ChangeWindowSize(length);
485 m_pClient->m_noDitherRA.ChangeWindowSize(length);
486 Debug.Write(wxString::Format("GraphStats window size = %d\n", length));
487 m_pClient->RecalculateTrendLines();
488 m_pLengthButton->SetLabel(wxString::Format(_T("x:%3d"), length));
489 pConfig->Global.SetInt("/graph/length", length);
490 }
491
OnButtonHeight(wxCommandEvent & WXUNUSED (evt))492 void GraphLogWindow::OnButtonHeight(wxCommandEvent& WXUNUSED(evt))
493 {
494 wxMenu *menu = new wxMenu();
495
496 unsigned int val = m_pClient->m_minHeight;
497 for (int id = MENU_HEIGHT_BEGIN; id <= MENU_HEIGHT_END; id++)
498 {
499 wxMenuItem *item = menu->AppendRadioItem(id, wxString::Format("%d", val));
500 if (val == m_pClient->m_height)
501 item->Check(true);
502 val *= 2;
503 if (val > m_pClient->m_maxHeight)
504 break;
505 }
506
507 PopupMenu(menu, m_pHeightButton->GetPosition().x,
508 m_pHeightButton->GetPosition().y + m_pHeightButton->GetSize().GetHeight());
509
510 delete menu;
511 }
512
OnMenuHeight(wxCommandEvent & evt)513 void GraphLogWindow::OnMenuHeight(wxCommandEvent& evt)
514 {
515 unsigned int val = m_pClient->m_minHeight;
516 for (int id = MENU_HEIGHT_BEGIN; id < evt.GetId(); id++)
517 val *= 2;
518
519 SetHeight(val);
520 Refresh();
521 }
522
GetHeight() const523 int GraphLogWindow::GetHeight() const
524 {
525 return m_pClient->m_height;
526 }
527
SetHeight(int height)528 void GraphLogWindow::SetHeight(int height)
529 {
530 m_pClient->m_height = height;
531 UpdateHeightButtonLabel();
532 pConfig->Global.SetInt("/graph/height", height);
533 }
534
SetState(bool is_active)535 void GraphLogWindow::SetState(bool is_active)
536 {
537 m_visible = is_active;
538 if (is_active)
539 {
540 UpdateControls();
541 }
542 Show(is_active);
543 }
544
EnableTrendLines(bool enable)545 void GraphLogWindow::EnableTrendLines(bool enable)
546 {
547 m_pCheckboxTrendlines->SetValue(enable);
548 wxCommandEvent dummy;
549 OnCheckboxTrendlines(dummy);
550 }
551
AppendData(const GuideStepInfo & step)552 void GraphLogWindow::AppendData(const GuideStepInfo& step)
553 {
554 if (m_pXControlPane)
555 m_pXControlPane->UpdateControls();
556 if (m_pYControlPane)
557 m_pYControlPane->UpdateControls();
558 if (m_pScopePane)
559 m_pScopePane->UpdateControls();
560
561 m_pClient->AppendData(step);
562
563 if (m_visible)
564 {
565 Refresh();
566 }
567 }
568
AppendData(const FrameDroppedInfo & info)569 void GraphLogWindow::AppendData(const FrameDroppedInfo& info)
570 {
571 m_pClient->AppendData(info);
572 }
573
AppendData(const DitherInfo & info)574 void GraphLogWindow::AppendData(const DitherInfo& info)
575 {
576 m_pClient->AppendData(info);
577 }
578
UpdateControls()579 void GraphLogWindow::UpdateControls()
580 {
581 Freeze();
582
583 if (m_pXControlPane)
584 {
585 m_pControlSizer->Detach(m_pXControlPane);
586 m_pXControlPane->Destroy();
587 }
588
589 if (m_pYControlPane)
590 {
591 m_pControlSizer->Detach(m_pYControlPane);
592 m_pYControlPane->Destroy();
593 }
594
595 if (m_pScopePane)
596 {
597 m_pControlSizer->Detach(m_pScopePane);
598 m_pScopePane->Destroy();
599 }
600
601 if (pMount && pMount->IsConnected())
602 {
603 m_pXControlPane = pMount->GetXGuideAlgorithmControlPane(this);
604 m_pYControlPane = pMount->GetYGuideAlgorithmControlPane(this);
605 m_pScopePane = pMount->GetGraphControlPane(this, _("Scope:"));
606 }
607 else
608 {
609 m_pXControlPane = nullptr;
610 m_pYControlPane = nullptr;
611 m_pScopePane = nullptr;
612 }
613
614 if (m_pXControlPane)
615 {
616 m_pControlSizer->Add(m_pXControlPane, wxSizerFlags().Border(wxTOP, 5));
617 }
618
619 if (m_pYControlPane)
620 {
621 m_pControlSizer->Add(m_pYControlPane, wxSizerFlags().Border(wxTOP, 5));
622 }
623
624 if (m_pScopePane)
625 {
626 m_pControlSizer->Add(m_pScopePane, wxSizerFlags().Border(wxTOP, 5));
627 }
628
629 Layout();
630 Thaw();
631 Refresh();
632 }
633
EnableDecControls(bool enable)634 void GraphLogWindow::EnableDecControls(bool enable)
635 {
636 if (m_pYControlPane)
637 {
638 m_pYControlPane->EnableDecControls(enable);
639 m_pYControlPane->Refresh();
640 }
641 }
642
ResetData()643 void GraphLogWindow::ResetData()
644 {
645 m_pClient->ResetData();
646 Refresh();
647 }
648
OnButtonClear(wxCommandEvent & WXUNUSED (evt))649 void GraphLogWindow::OnButtonClear(wxCommandEvent& WXUNUSED(evt))
650 {
651 ResetData();
652 }
653
OnCheckboxTrendlines(wxCommandEvent & WXUNUSED (evt))654 void GraphLogWindow::OnCheckboxTrendlines(wxCommandEvent& WXUNUSED(evt))
655 {
656 m_pClient->m_showTrendlines = m_pCheckboxTrendlines->IsChecked();
657 if (!m_pClient->m_showTrendlines)
658 {
659 // clear the polar alignment circle
660 pFrame->pGuider->SetPolarAlignCircle(PHD_Point(), 0.0);
661 }
662 Refresh();
663 }
664
OnCheckboxCorrections(wxCommandEvent & WXUNUSED (evt))665 void GraphLogWindow::OnCheckboxCorrections(wxCommandEvent& WXUNUSED(evt))
666 {
667 m_pClient->m_showCorrections = m_pCheckboxCorrections->IsChecked();
668 pConfig->Global.SetBoolean("/graph/showCorrections", m_pClient->m_showCorrections);
669 Refresh();
670 }
671
OnPaint(wxPaintEvent & WXUNUSED (evt))672 void GraphLogWindow::OnPaint(wxPaintEvent& WXUNUSED(evt))
673 {
674 wxAutoBufferedPaintDC dc(this);
675
676 dc.SetBackground(*wxBLACK_BRUSH);
677 //dc.SetBackground(wxColour(10,0,0));
678 dc.Clear();
679
680 UpdateHeightButtonLabel();
681
682 int XControlPaneWidth = 0;
683 int YControlPaneWidth = 0;
684 int ScopePaneWidth = 0;
685
686 if (m_pXControlPane)
687 {
688 XControlPaneWidth = m_pXControlPane->GetSize().GetWidth();
689 }
690
691 if (m_pYControlPane)
692 {
693 YControlPaneWidth = m_pYControlPane->GetSize().GetWidth();
694 }
695
696 if (m_pScopePane)
697 {
698 ScopePaneWidth = m_pScopePane->GetSize().GetWidth();
699 }
700
701 int ControlSizerWidth = m_pControlSizer->GetSize().GetWidth();
702 if (ControlSizerWidth != 0)
703 {
704 int nb_row;
705 if (ControlSizerWidth > XControlPaneWidth + YControlPaneWidth + ScopePaneWidth)
706 {
707 nb_row = 1;
708 }
709 else if (ControlSizerWidth > XControlPaneWidth + YControlPaneWidth)
710 {
711 nb_row = 2;
712 }
713 else
714 {
715 nb_row = 3;
716 }
717 if (m_ControlNbRows != nb_row)
718 {
719 m_ControlNbRows = nb_row;
720 m_pControlSizer->SetRows(nb_row);
721 m_pControlSizer->SetCols(4 - nb_row);
722 Layout();
723 }
724 }
725 }
726
UpdateHeightButtonLabel()727 void GraphLogWindow::UpdateHeightButtonLabel()
728 {
729 int val = m_pClient->m_height;
730
731 if (pFrame && pFrame->GetCameraPixelScale() != 1.0 && m_pClient->m_heightUnits == UNIT_ARCSEC)
732 val = -val; // <0 indicates arc-sec
733
734 if (m_heightButtonLabelVal != val)
735 {
736 if (val > 0)
737 {
738 m_pHeightButton->SetLabel(wxString::Format(_T("y: +/-%d px"), m_pClient->m_height));
739 m_pHeightButton->SetToolTip(_("Select the Y-axis scale, pixels per Y division"));
740 }
741 else
742 {
743 m_pHeightButton->SetLabel(wxString::Format(_T("y: +/-%d''"), m_pClient->m_height));
744 m_pHeightButton->SetToolTip(_("Select the Y-axis scale, arc-seconds per Y division"));
745 }
746 m_heightButtonLabelVal = val;
747 }
748 }
749
wxBEGIN_EVENT_TABLE(GraphLogClientWindow,wxWindow)750 wxBEGIN_EVENT_TABLE(GraphLogClientWindow, wxWindow)
751 EVT_PAINT(GraphLogClientWindow::OnPaint)
752 EVT_LEFT_DOWN(GraphLogClientWindow::OnLeftBtnDown)
753 wxEND_EVENT_TABLE()
754
755 GraphLogClientWindow::GraphLogClientWindow(wxWindow *parent) :
756 wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(401,200), wxFULL_REPAINT_ON_RESIZE),
757 m_line1(0),
758 m_line2(0)
759 {
760 SetBackgroundStyle(wxBG_STYLE_PAINT);
761
762 ResetData();
763 m_mode = (GRAPH_MODE) pConfig->Global.GetInt("/graph/ScopeOrCameraUnits", (int) MODE_RADEC);
764
765 if (!m_raOrDxColor.Set(pConfig->Global.GetString("/graph/RAColor", wxEmptyString)))
766 {
767 m_raOrDxColor = wxColour(100,100,255);
768 pConfig->Global.SetString("/graph/RAColor", m_raOrDxColor.GetAsString(wxC2S_HTML_SYNTAX));
769 }
770 if (!m_decOrDyColor.Set(pConfig->Global.GetString("/graph/DecColor", wxEmptyString)))
771 {
772 m_decOrDyColor = wxColour(255,0,0);
773 pConfig->Global.SetString("/graph/DecColor", m_decOrDyColor.GetAsString(wxC2S_HTML_SYNTAX));
774 }
775
776 int minLength = pConfig->Global.GetInt("/graph/minLength", GraphLogWindow::DefaultMinLength);
777 SetMinLength(minLength);
778
779 int maxLength = pConfig->Global.GetInt("/graph/maxLength", GraphLogWindow::DefaultMaxLength);
780 SetMaxLength(maxLength);
781
782 int minHeight = pConfig->Global.GetInt("/graph/minHeight", GraphLogWindow::DefaultMinHeight);
783 SetMinHeight(minHeight);
784
785 int maxHeight = pConfig->Global.GetInt("/graph/maxHeight", GraphLogWindow::DefaultMaxHeight);
786 SetMaxHeight(maxHeight);
787
788 m_length = pConfig->Global.GetInt("/graph/length", m_minLength * 2);
789 m_noDitherDec.ChangeWindowSize(m_length);
790 m_noDitherRA.ChangeWindowSize(m_length);
791 Debug.Write(wxString::Format("GraphStats window size = %d\n", (int)m_length));
792 m_height = pConfig->Global.GetInt("/graph/height", m_minHeight * 2 * 2); // match PHD1 4-pixel scale for new users
793 m_heightUnits = (GRAPH_UNITS) pConfig->Global.GetInt("graph/HeightUnits", (int) UNIT_ARCSEC); // preferred units, will still display pixels if camera pixel scale not available
794
795 m_showTrendlines = false;
796 m_showCorrections = pConfig->Global.GetBoolean("/graph/showCorrections", true);
797 m_showStarMass = pConfig->Global.GetBoolean("/graph/showStarMass", false);
798 m_showStarSNR = pConfig->Global.GetBoolean("/graph/showStarSNR", false);
799 m_correctionsToScale = pConfig->Global.GetBoolean("/graph/correctionsToScale", false);
800 }
801
~GraphLogClientWindow()802 GraphLogClientWindow::~GraphLogClientWindow()
803 {
804 delete [] m_line1;
805 delete [] m_line2;
806 }
807
reset_trend_accums(TrendLineAccum accums[4])808 static void reset_trend_accums(TrendLineAccum accums[4])
809 {
810 for (int i = 0; i < 4; i++)
811 {
812 accums[i].sum_xy = 0.0;
813 accums[i].sum_y = 0.0;
814 accums[i].sum_y2 = 0.0;
815 }
816 }
817
ResetData()818 void GraphLogClientWindow::ResetData()
819 {
820 m_history.clear();
821 reset_trend_accums(m_trendLineAccum);
822 m_noDitherDec.ClearAll();
823 m_noDitherRA.ClearAll();
824 m_raSameSides = 0;
825 m_ditherStarted = false;
826 UpdateStats(0, 0);
827 m_stats.ra_peak = m_stats.dec_peak = 0.0;
828 m_stats.star_lost_cnt = 0;
829 m_stats.ra_limit_cnt = m_stats.dec_limit_cnt = 0;
830 if (pFrame && pFrame->pStatsWin)
831 pFrame->pStatsWin->UpdateStats();
832 }
833
SetMinLength(unsigned int minLength)834 bool GraphLogClientWindow::SetMinLength(unsigned int minLength)
835 {
836 bool bError = false;
837
838 try
839 {
840 if (minLength < 1)
841 {
842 throw ERROR_INFO("minLength < 1");
843 }
844 m_minLength = minLength;
845 }
846 catch (const wxString& Msg)
847 {
848 POSSIBLY_UNUSED(Msg);
849 bError = true;
850 m_minLength = GraphLogWindow::DefaultMinLength;
851 }
852
853 pConfig->Global.SetInt("/graph/minLength", m_minLength);
854
855 return bError;
856 }
857
SetMaxLength(unsigned int maxLength)858 bool GraphLogClientWindow::SetMaxLength(unsigned int maxLength)
859 {
860 bool bError = false;
861
862 try
863 {
864 if (maxLength < m_minLength)
865 {
866 throw ERROR_INFO("maxLength < m_minLength");
867 }
868 }
869 catch (const wxString& Msg)
870 {
871 POSSIBLY_UNUSED(Msg);
872 bError = true;
873 maxLength = m_minLength;
874 }
875
876 m_history.resize(maxLength);
877
878 delete [] m_line1;
879 m_line1 = new wxPoint[maxLength];
880
881 delete [] m_line2;
882 m_line2 = new wxPoint[maxLength];
883
884 pConfig->Global.SetInt("/graph/maxLength", m_history.capacity());
885
886 return bError;
887 }
888
SetMinHeight(unsigned int minHeight)889 bool GraphLogClientWindow::SetMinHeight(unsigned int minHeight)
890 {
891 bool bError = false;
892
893 try
894 {
895 if (minHeight < 1)
896 {
897 throw ERROR_INFO("minHeight < 1");
898 }
899 m_minHeight = minHeight;
900 }
901 catch (const wxString& Msg)
902 {
903 POSSIBLY_UNUSED(Msg);
904 bError = true;
905 m_minHeight = GraphLogWindow::DefaultMinHeight;
906 }
907
908 pConfig->Global.SetInt("/graph/minHeight", m_minHeight);
909
910 return bError;
911 }
912
SetMaxHeight(unsigned int maxHeight)913 bool GraphLogClientWindow::SetMaxHeight(unsigned int maxHeight)
914 {
915 bool bError = false;
916
917 try
918 {
919 if (maxHeight <= m_minHeight)
920 {
921 throw ERROR_INFO("maxHeight < m_minHeight");
922 }
923 m_maxHeight = maxHeight;
924 }
925 catch (const wxString& Msg)
926 {
927 POSSIBLY_UNUSED(Msg);
928 bError = true;
929 m_minHeight = GraphLogWindow::DefaultMinHeight;
930 m_maxHeight = GraphLogWindow::DefaultMaxHeight;
931 }
932
933 pConfig->Global.SetInt("/graph/maxHeight", m_maxHeight);
934
935 return bError;
936 }
937
938 // update_trend - update running accumulators for trend line calculations
939 //
update_trend(int nr,int max_nr,double newval,const double & oldval,TrendLineAccum * accum)940 static void update_trend(int nr, int max_nr, double newval, const double& oldval, TrendLineAccum *accum)
941 {
942 // note: not safe to dereference oldval when nr == 0
943
944 if (nr < max_nr)
945 {
946 // number of items is increasing, increment sums
947 accum->sum_y += newval;
948 accum->sum_xy += nr * newval;
949 accum->sum_y2 += newval * newval;
950 }
951 else
952 {
953 // number of items has reached limit. Update counters to reflect
954 // removal of oldest value (oldval) and addition of new value.
955 accum->sum_xy += (max_nr - 1) * newval + oldval - accum->sum_y;
956 accum->sum_y += newval - oldval;
957 accum->sum_y2 += newval * newval - oldval * oldval;
958 }
959 }
960
rms(unsigned int nr,const TrendLineAccum * accum)961 static double rms(unsigned int nr, const TrendLineAccum *accum)
962 {
963 if (nr == 0)
964 return 0.0;
965 double const n = (double)nr;
966 double const s1 = accum->sum_y;
967 double const s2 = accum->sum_y2;
968 return sqrt(n * s2 - s1 * s1) / n;
969 }
970
UpdateStats(unsigned int nr,const S_HISTORY * cur)971 void GraphLogClientWindow::UpdateStats(unsigned int nr, const S_HISTORY *cur)
972 {
973 m_stats.nr = nr;
974 m_stats.rms_ra = m_noDitherRA.GetPopulationSigma();
975 m_stats.rms_dec = m_noDitherDec.GetPopulationSigma();
976 m_stats.rms_tot = hypot(m_stats.rms_ra, m_stats.rms_dec);
977 m_stats.ra_peak = std::max(fabs(m_noDitherRA.GetMaxDisplacement()), fabs(m_noDitherRA.GetMinDisplacement()));
978 m_stats.dec_peak = std::max(fabs(m_noDitherDec.GetMaxDisplacement()), fabs(m_noDitherDec.GetMinDisplacement()));
979
980 if (nr >= 2)
981 {
982 m_stats.osc_index = 1.0 - (double) m_raSameSides / (double)(nr - 1);
983 m_stats.osc_alert = m_stats.osc_index > 0.6 || m_stats.osc_index < 0.15;
984 }
985 else
986 {
987 m_stats.osc_index = 0.0;
988 m_stats.osc_alert = false;
989 }
990
991 if (cur)
992 m_stats.cur = *cur;
993 else
994 {
995 static S_HISTORY s_zero;
996 m_stats.cur = s_zero;
997 }
998 }
999
peak_ra(const circular_buffer<S_HISTORY> & history,unsigned int nr)1000 static double peak_ra(const circular_buffer<S_HISTORY> &history, unsigned int nr)
1001 {
1002 double peak = 0.0;
1003 const int end = history.size();
1004 const int begin = end - nr;
1005 for (int i = begin; i < end; i++) {
1006 double val = fabs(history[i].ra);
1007 if (val > peak)
1008 peak = val;
1009 }
1010 return peak;
1011 }
1012
peak_dec(const circular_buffer<S_HISTORY> & history,unsigned int nr)1013 static double peak_dec(const circular_buffer<S_HISTORY> &history, unsigned int nr)
1014 {
1015 double peak = 0.0;
1016 const int end = history.size();
1017 const int begin = end - nr;
1018 for (int i = begin; i < end; i++) {
1019 double val = fabs(history[i].dec);
1020 if (val > peak)
1021 peak = val;
1022 }
1023 return peak;
1024 }
1025
AppendData(const GuideStepInfo & step)1026 void GraphLogClientWindow::AppendData(const GuideStepInfo& step)
1027 {
1028 unsigned int trend_items = GetItemCount();
1029 const int oldest_idx = m_history.size() - trend_items;
1030
1031 S_HISTORY oldest;
1032 if (m_history.size() > 0)
1033 oldest = m_history[oldest_idx];
1034 update_trend(trend_items, m_length, step.cameraOffset.X, oldest.dx, &m_trendLineAccum[0]);
1035 update_trend(trend_items, m_length, step.cameraOffset.Y, oldest.dy, &m_trendLineAccum[1]);
1036 update_trend(trend_items, m_length, step.mountOffset.X, oldest.ra, &m_trendLineAccum[2]);
1037 update_trend(trend_items, m_length, step.mountOffset.Y, oldest.dec, &m_trendLineAccum[3]);
1038
1039 // update counter for osc index
1040 if (trend_items >= 1)
1041 {
1042 if (step.mountOffset.X * m_history[m_history.size() - 1].ra > 0.0)
1043 ++m_raSameSides;
1044 if (trend_items >= m_length)
1045 {
1046 if (m_history[oldest_idx].ra * m_history[oldest_idx + 1].ra > 0.0)
1047 --m_raSameSides;
1048 }
1049 }
1050
1051 m_stats.ra_limit_cnt += (step.raLimited ? 1 : 0) - ((trend_items >= m_length && oldest.raLimited) ? 1 : 0);
1052 m_stats.dec_limit_cnt += (step.decLimited ? 1 : 0) - ((trend_items >= m_length && oldest.decLimited) ? 1 : 0);
1053
1054 S_HISTORY cur(step);
1055 m_history.push_front(cur);
1056
1057 if (m_ditherStarted)
1058 m_ditherStarted = false;
1059 else if (!PhdController::IsSettling())
1060 {
1061 long dt = ::wxGetUTCTimeMillis().GetValue() - m_timeBase;
1062 m_noDitherDec.AddGuideInfo(dt, cur.dec, cur.decDur);
1063 m_noDitherRA.AddGuideInfo(dt, cur.ra, cur.raDur);
1064 }
1065
1066 // remove any dither history entries older than the first guide step history entry
1067 wxLongLong_t t0 = m_history[0].timestamp;
1068 while (m_dithers.size() > 0)
1069 {
1070 const DitherInfo& info = m_dithers.front();
1071 if (info.timestamp < t0)
1072 m_dithers.pop_front();
1073 else
1074 break;
1075 }
1076
1077 unsigned int new_nr = GetItemCount();
1078 UpdateStats(new_nr, &cur);
1079
1080 pFrame->pStatsWin->UpdateStats();
1081 }
1082
AppendData(const FrameDroppedInfo & info)1083 void GraphLogClientWindow::AppendData(const FrameDroppedInfo& info)
1084 {
1085 ++m_stats.star_lost_cnt;
1086 pFrame->pStatsWin->UpdateStats();
1087 }
1088
AppendData(const DitherInfo & info)1089 void GraphLogClientWindow::AppendData(const DitherInfo& info)
1090 {
1091 m_dithers.push_back(info);
1092 m_ditherStarted = true;
1093 }
1094
RecalculateTrendLines()1095 void GraphLogClientWindow::RecalculateTrendLines()
1096 {
1097 reset_trend_accums(m_trendLineAccum);
1098 unsigned int trend_items = GetItemCount();
1099 const int begin = m_history.size() - trend_items;
1100 for (unsigned int x = 0, i = begin; x < trend_items; i++, x++) {
1101 const S_HISTORY& h = m_history[i];
1102 update_trend(x, trend_items, h.dx, 0.0, &m_trendLineAccum[0]);
1103 update_trend(x, trend_items, h.dy, 0.0, &m_trendLineAccum[1]);
1104 update_trend(x, trend_items, h.ra, 0.0, &m_trendLineAccum[2]);
1105 update_trend(x, trend_items, h.dec, 0.0, &m_trendLineAccum[3]);
1106 }
1107 // recalculate ra same side counter
1108 m_raSameSides = 0;
1109 if (trend_items >= 2)
1110 {
1111 const int end = m_history.size() - 1;
1112 double cur = m_history[begin].ra;
1113 for (int i = begin; i < end; i++)
1114 {
1115 double next = m_history[i + 1].ra;
1116 if (cur * next > 0.0)
1117 ++m_raSameSides;
1118 cur = next;
1119 }
1120 }
1121
1122 m_stats.ra_peak = peak_ra(m_history, trend_items);
1123 m_stats.dec_peak = peak_dec(m_history, trend_items);
1124
1125 {
1126 unsigned int raLimitedCnt = 0;
1127 unsigned int decLimitedCnt = 0;
1128 const int end = m_history.size();
1129 for (int i = begin; i < end; i++)
1130 {
1131 const S_HISTORY& h = m_history[i];
1132 if (h.raLimited)
1133 ++raLimitedCnt;
1134 if (h.decLimited)
1135 ++decLimitedCnt;
1136 }
1137 m_stats.ra_limit_cnt = raLimitedCnt;
1138 m_stats.dec_limit_cnt = decLimitedCnt;
1139 }
1140
1141 const S_HISTORY *latest = 0;
1142 if (m_history.size() > 0)
1143 latest = &m_history[m_history.size() - 1];
1144 UpdateStats(trend_items, latest);
1145
1146 pFrame->pStatsWin->UpdateStats();
1147 }
1148
1149 // trendline - calculate the the trendline slope and intercept. We can do this
1150 // in O(1) without iterating over the history data since we have kept running
1151 // sums sum(y), sum(xy), and since sum(x) and sum(x^2) can be computed directly
1152 // in a single expression (without iterating) for x from 0..n-1
1153 //
trendline(const TrendLineAccum & accum,int nn)1154 static std::pair<double, double> trendline(const TrendLineAccum& accum, int nn)
1155 {
1156 assert(nn > 1);
1157 double n = (double) nn;
1158 // sum_x is: sum(x) for x from 0 .. n-1
1159 double sum_x = 0.5 * n * (n - 1.0);
1160 // denom is: (n sum(x^2) - sum(x)^2) for x from 0 .. n-1
1161 double denom = n * n * (n - 1.0) * ((2.0 * n - 1.0) / 6.0 - 0.25 * (n - 1));
1162
1163 double a = (n * accum.sum_xy - sum_x * accum.sum_y) / denom;
1164 double b = (accum.sum_y - a * sum_x) / n;
1165
1166 return std::make_pair(a, b);
1167 }
1168
1169 // helper class to scale and translate points
1170 struct ScaleAndTranslate
1171 {
1172 int m_xorig, m_yorig;
1173 double m_xmag, m_ymag;
ScaleAndTranslateScaleAndTranslate1174 ScaleAndTranslate(int xorig, int yorig, double xmag, double ymag) : m_xorig(xorig), m_yorig(yorig), m_xmag(xmag), m_ymag(ymag) { }
ptScaleAndTranslate1175 wxPoint pt(double x, double y) const {
1176 return wxPoint(m_xorig + (int)(x * m_xmag), m_yorig + (int)(y * m_ymag));
1177 }
1178 };
1179
rms_label(double rms,double sampling)1180 static wxString rms_label(double rms, double sampling)
1181 {
1182 if (sampling != 1.0)
1183 return wxString::Format("%4.2f (%.2f'')", rms, rms * sampling);
1184 else
1185 return wxString::Format("%4.2f", rms);
1186 }
1187
GetMaxDuration(const circular_buffer<S_HISTORY> & history,int start_item)1188 static int GetMaxDuration(const circular_buffer<S_HISTORY>& history, int start_item)
1189 {
1190 int maxdur = 1; // always return at least 1 to protect against divide-by-zero
1191 for (unsigned int i = start_item; i < history.size(); i++)
1192 {
1193 const S_HISTORY& h = history[i];
1194 int d = abs(h.raDur);
1195 if (d > maxdur)
1196 maxdur = d;
1197 d = abs(h.decDur);
1198 if (d > maxdur)
1199 maxdur = d;
1200 }
1201 return maxdur;
1202 }
1203
GetMaxStarMass(const circular_buffer<S_HISTORY> & history,int start_item)1204 static double GetMaxStarMass(const circular_buffer<S_HISTORY>& history, int start_item)
1205 {
1206 double maxMass = 0.0;
1207 for (unsigned int i = start_item; i < history.size(); i++)
1208 {
1209 const S_HISTORY& h = history[i];
1210 if (h.starMass > maxMass)
1211 maxMass = h.starMass;
1212 }
1213 return maxMass;
1214 }
1215
GetMaxStarSNR(const circular_buffer<S_HISTORY> & history,int start_item)1216 static double GetMaxStarSNR(const circular_buffer<S_HISTORY>& history, int start_item)
1217 {
1218 double maxSNR = 0.0;
1219 for (unsigned int i = start_item; i < history.size(); i++)
1220 {
1221 const S_HISTORY& h = history[i];
1222 if (h.starSNR > maxSNR)
1223 maxSNR = h.starSNR;
1224 }
1225 return maxSNR;
1226 }
1227
1228 enum { GRAPH_BORDER = 5 };
1229
OnPaint(wxPaintEvent & WXUNUSED (evt))1230 void GraphLogClientWindow::OnPaint(wxPaintEvent& WXUNUSED(evt))
1231 {
1232 wxAutoBufferedPaintDC dc(this);
1233
1234 wxSize size(GetClientSize());
1235 wxSize center(size.x / 2, size.y / 2);
1236
1237 const int leftEdge = 0;
1238 const int rightEdge = size.x - GRAPH_BORDER;
1239
1240 const int topEdge = GRAPH_BORDER;
1241 const int bottomEdge = size.y - GRAPH_BORDER;
1242
1243 const int xorig = 0;
1244 const int yorig = size.y / 2;
1245
1246 const int xDivisions = m_length / m_xSamplesPerDivision - 1;
1247 const int xPixelsPerDivision = size.x / 2 / (xDivisions + 1);
1248 const int yPixelsPerDivision = size.y / 2 / (m_yDivisions + 1);
1249
1250 const double sampling = pFrame ? pFrame->GetCameraPixelScale() : 1.0;
1251 GRAPH_UNITS units = m_heightUnits;
1252 if (sampling == 1.0)
1253 {
1254 // force units to pixels if pixel scale not available
1255 units = UNIT_PIXELS;
1256 }
1257
1258 dc.SetBackground(*wxBLACK_BRUSH);
1259 dc.Clear();
1260
1261 wxPen GreyDashPen(wxColour(200,200,200),1, wxPENSTYLE_DOT);
1262
1263 // Draw axes
1264 dc.SetPen(*wxGREY_PEN);
1265 dc.DrawLine(center.x,topEdge,center.x,bottomEdge);
1266 dc.DrawLine(leftEdge,center.y,rightEdge,center.y);
1267
1268 // draw a box around the client area
1269 dc.DrawLine(leftEdge, topEdge, rightEdge, topEdge);
1270 dc.DrawLine(rightEdge, topEdge, rightEdge, bottomEdge);
1271 dc.DrawLine(rightEdge, bottomEdge, leftEdge, bottomEdge);
1272 dc.DrawLine(leftEdge, bottomEdge, leftEdge, topEdge);
1273
1274 // Draw horiz rule (scale is 1 pixel error per 25 pixels) + scale labels
1275 dc.SetPen(GreyDashPen);
1276 dc.SetTextForeground(*wxLIGHT_GREY);
1277 const wxFont& SmallFont =
1278 #if defined(__WXOSX__)
1279 *wxSMALL_FONT;
1280 #else
1281 *wxSWISS_FONT;
1282 #endif
1283 dc.SetFont(SmallFont);
1284
1285 for (int i = 1; i <= m_yDivisions; i++)
1286 {
1287 double div_y = center.y - i * yPixelsPerDivision;
1288 dc.DrawLine(leftEdge,div_y, rightEdge, div_y);
1289 dc.DrawText(wxString::Format("%g%s", i * (double)m_height / (m_yDivisions + 1), units == UNIT_ARCSEC ? "''" : (i == 3 ? " px" : "")), leftEdge + 3, div_y - 13);
1290
1291 div_y = center.y + i * yPixelsPerDivision;
1292 dc.DrawLine(leftEdge, div_y, rightEdge, div_y);
1293 dc.DrawText(wxString::Format("%g%s", -i * (double)m_height / (m_yDivisions + 1), units == UNIT_ARCSEC ? "''" : ""), leftEdge + 3, div_y - 13);
1294 }
1295
1296 for (int i = 1; i <= xDivisions; i++)
1297 {
1298 dc.DrawLine(center.x - i * xPixelsPerDivision, topEdge, center.x - i * xPixelsPerDivision, bottomEdge);
1299 dc.DrawLine(center.x + i * xPixelsPerDivision, topEdge, center.x + i * xPixelsPerDivision, bottomEdge);
1300 }
1301
1302 const double xmag = size.x / (double) m_length;
1303 const double ymag = yPixelsPerDivision * (double)(m_yDivisions + 1) / (double)m_height * (units == UNIT_ARCSEC ? sampling : 1.0);
1304
1305 ScaleAndTranslate sctr(xorig, yorig, xmag, ymag);
1306
1307 if (m_showCorrections && !(pMount && pMount->IsStepGuider()))
1308 {
1309 wxString lblN(_("GuideNorth"));
1310 wxString lblE(_("GuideEast"));
1311 static wxSize szN, szE;
1312 dc.SetFont(wxFont(wxFontInfo(8)));
1313 if (szN.x == 0)
1314 {
1315 szN = dc.GetTextExtent(lblN);
1316 szE = dc.GetTextExtent(lblE);
1317 }
1318 dc.SetTextForeground(m_decOrDyColor.ChangeLightness(75));
1319 dc.DrawText(lblN, rightEdge - szN.GetWidth() - 4, topEdge + 2);
1320 dc.SetTextForeground(m_raOrDxColor.ChangeLightness(75));
1321 dc.DrawText(lblE, rightEdge - szE.GetWidth() - 4, bottomEdge - szE.GetHeight() - 2);
1322 dc.SetTextForeground(*wxLIGHT_GREY);
1323 dc.SetFont(SmallFont);
1324 }
1325
1326 // Draw data
1327 if (m_history.size() > 0)
1328 {
1329 unsigned int plot_length = GetItemCount();
1330 unsigned int start_item = m_history.size() - plot_length;
1331
1332 if (m_showCorrections)
1333 {
1334 double ymagc;
1335 if (m_correctionsToScale)
1336 {
1337 ymagc = ymag;
1338 }
1339 else
1340 {
1341 int maxDur = GetMaxDuration(m_history, start_item);
1342 ymagc = (size.y - 10) * 0.5 / (double) maxDur;
1343 }
1344 ScaleAndTranslate sctr(xorig, yorig, xmag, ymagc);
1345
1346 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1347 dc.SetPen(wxPen(m_raOrDxColor.ChangeLightness(60)));
1348
1349 double const xRate = pMount ? pMount->xRate() : 1.0;
1350
1351 for (unsigned int i = start_item, j = 0; i < m_history.size(); i++, j++)
1352 {
1353 const S_HISTORY& h = m_history[i];
1354
1355 if (h.raDur != 0)
1356 {
1357 // West corrections => Up on graph
1358 double raDur = h.raDir == WEST ? -h.raDur : h.raDur;
1359 if (m_correctionsToScale)
1360 raDur *= xRate;
1361 wxPoint pt(sctr.pt(j, raDur));
1362 if (raDur < 0)
1363 dc.DrawRectangle(pt, wxSize(4, yorig - pt.y));
1364 else
1365 dc.DrawRectangle(wxPoint(pt.x, yorig), wxSize(4, pt.y - yorig));
1366 }
1367 }
1368
1369 dc.SetPen(wxPen(m_decOrDyColor.ChangeLightness(60)));
1370
1371 double const yRate = pMount ? pMount->yRate() : 1.0;
1372
1373 for (unsigned int i = start_item, j = 0; i < m_history.size(); i++, j++)
1374 {
1375 const S_HISTORY& h = m_history[i];
1376
1377 if (h.decDur != 0)
1378 {
1379 // North Corrections => Up on graph
1380 double decDur = h.decDir == SOUTH ? h.decDur : -h.decDur;
1381 if (m_correctionsToScale)
1382 decDur *= yRate;
1383 wxPoint pt(sctr.pt(j, decDur));
1384 pt.x += 5;
1385 if (decDur < 0)
1386 dc.DrawRectangle(pt,wxSize(4, yorig - pt.y));
1387 else
1388 dc.DrawRectangle(wxPoint(pt.x, yorig), wxSize(4, pt.y - yorig));
1389 }
1390 }
1391 }
1392
1393 if (m_showStarMass)
1394 {
1395 double maxMass = GetMaxStarMass(m_history, start_item);
1396
1397 const double ymag = (size.y - 10) * 0.5 / maxMass;
1398 ScaleAndTranslate sctr(xorig, yorig, xmag, -ymag);
1399
1400 for (unsigned int i = start_item, j = 0; i < m_history.size(); i++, j++)
1401 {
1402 const S_HISTORY& h = m_history[i];
1403 m_line1[j] = sctr.pt(j, h.starMass);
1404 }
1405
1406 dc.SetPen(*wxYELLOW_PEN);
1407 dc.DrawLines(plot_length, m_line1);
1408 }
1409
1410 if (m_showStarSNR)
1411 {
1412 double maxSNR = GetMaxStarSNR(m_history, start_item);
1413
1414 const double ymag = (size.y - 10) * 0.5 / maxSNR;
1415 ScaleAndTranslate sctr(xorig, yorig, xmag, -ymag);
1416
1417 for (unsigned int i = start_item, j = 0; i < m_history.size(); i++, j++)
1418 {
1419 const S_HISTORY& h = m_history[i];
1420 m_line1[j] = sctr.pt(j, h.starSNR);
1421 }
1422
1423 dc.SetPen(*wxWHITE_PEN);
1424 dc.DrawLines(plot_length, m_line1);
1425 }
1426
1427 std::deque<DitherInfo>::const_iterator it = m_dithers.begin();
1428 { // advance to the first dither that will show on the plot
1429 const S_HISTORY& h = m_history[start_item];
1430 while (it != m_dithers.end() && it->timestamp < h.timestamp)
1431 ++it;
1432 }
1433
1434 for (unsigned int i = start_item, j = 0; i < m_history.size(); i++, j++)
1435 {
1436 const S_HISTORY& h = m_history[i];
1437
1438 if (it != m_dithers.end() && it->timestamp < h.timestamp)
1439 {
1440 wxPoint pt(sctr.pt((double) j - 0.5, 0.0));
1441 pt.y = topEdge + 6;
1442 dc.DrawText(_("Dither"), pt);
1443 ++it;
1444 }
1445
1446 switch (m_mode)
1447 {
1448 case MODE_RADEC:
1449 m_line1[j] = sctr.pt(j, h.ra);
1450 m_line2[j] = sctr.pt(j, -h.dec); // North corrections Up, North offsets down
1451 break;
1452 case MODE_DXDY:
1453 m_line1[j] = sctr.pt(j, h.dx);
1454 m_line2[j] = sctr.pt(j, h.dy);
1455 break;
1456 }
1457 }
1458
1459 wxPen raOrDxPen(m_raOrDxColor, 2);
1460 dc.SetPen(raOrDxPen);
1461 dc.DrawLines(plot_length, m_line1);
1462
1463 wxPen decOrDyPen(m_decOrDyColor, 2);
1464 dc.SetPen(decOrDyPen);
1465 dc.DrawLines(plot_length, m_line2);
1466
1467 // draw trend lines
1468 double polarAlignCircleRadius = 0.0;
1469 if (m_showTrendlines && plot_length >= 5)
1470 {
1471 std::pair<double, double> trendRaOrDx;
1472 std::pair<double, double> trendDecOrDy;
1473 switch (m_mode)
1474 {
1475 case MODE_RADEC:
1476 trendRaOrDx = trendline(m_trendLineAccum[2], plot_length);
1477 trendDecOrDy = trendline(m_trendLineAccum[3], plot_length);
1478 // North offsets plotted downward
1479 trendDecOrDy = std::make_pair(-trendDecOrDy.first, -trendDecOrDy.second);
1480 break;
1481 case MODE_DXDY:
1482 trendRaOrDx = trendline(m_trendLineAccum[0], plot_length);
1483 trendDecOrDy = trendline(m_trendLineAccum[1], plot_length);
1484 break;
1485 }
1486
1487 wxPoint lineRaOrDx[2];
1488 lineRaOrDx[0] = sctr.pt(0.0, trendRaOrDx.second);
1489 lineRaOrDx[1] = sctr.pt(m_length, trendRaOrDx.first * m_length + trendRaOrDx.second);
1490
1491 wxPoint lineDecOrDy[2];
1492 lineDecOrDy[0] = sctr.pt(0.0, trendDecOrDy.second);
1493 lineDecOrDy[1] = sctr.pt(m_length, trendDecOrDy.first * m_length + trendDecOrDy.second);
1494
1495 raOrDxPen.SetStyle(wxPENSTYLE_LONG_DASH);
1496 dc.SetPen(raOrDxPen);
1497 dc.DrawLines(2, lineRaOrDx, 0, 0);
1498
1499 decOrDyPen.SetStyle(wxPENSTYLE_LONG_DASH);
1500 dc.SetPen(decOrDyPen);
1501 dc.DrawLines(2, lineDecOrDy, 0, 0);
1502
1503 // show polar alignment error
1504 if (m_mode == MODE_RADEC && sampling != 1.0 && pMount && pMount->IsDecDrifting())
1505 {
1506 double declination = pPointingSource->GetDeclination();
1507 if (declination == UNKNOWN_DECLINATION) // assume declination 0
1508 declination = 0.0;
1509
1510 if (fabs(declination) <= Scope::DEC_COMP_LIMIT)
1511 {
1512 const S_HISTORY& h0 = m_history[start_item];
1513 const S_HISTORY& h1 = m_history[m_history.size() - 1];
1514 double dt = (double)(h1.timestamp - h0.timestamp) / (1000.0 * 60.0); // time span in minutes
1515 double ddec = (double) (plot_length - 1) * trendDecOrDy.first; // dec drift (pixels)
1516 ddec *= sampling; // convert pixels to arc-seconds
1517 // From Frank Barrett, "Determining Polar Axis Alignment Accuracy"
1518 // http://celestialwonders.com/articles/polaralignment/PolarAlignmentAccuracy.pdf
1519 double err_arcmin = (3.82 * ddec) / (dt * cos(declination));
1520 polarAlignCircleRadius = fabs(err_arcmin * 60.0 / sampling);
1521 double correction = pFrame->pGuider->GetPolarAlignCircleCorrection();
1522 double err_px = polarAlignCircleRadius * correction;
1523 dc.DrawText(wxString::Format("Polar alignment error: %.2f' (%s%.f px)", err_arcmin,
1524 correction < 1.0 ? "" : "< ", err_px),
1525 leftEdge + 30, bottomEdge - 18);
1526 }
1527 }
1528 }
1529 // when drifting, center the polar align circle on the star; when adjusting, center the polar
1530 // align circle at the lock position
1531 const PHD_Point& center = pFrame->pGuider->IsCalibratingOrGuiding() ?
1532 pFrame->pGuider->CurrentPosition() : pFrame->pGuider->LockPosition();
1533 pFrame->pGuider->SetPolarAlignCircle(center, polarAlignCircleRadius);
1534
1535 m_pRaRMS->SetLabel(rms_label(m_stats.rms_ra, sampling));
1536 m_pDecRMS->SetLabel(rms_label(m_stats.rms_dec, sampling));
1537 m_pTotRMS->SetLabel(rms_label(m_stats.rms_tot, sampling));
1538
1539 if (m_stats.osc_alert)
1540 {
1541 m_pOscIndex->SetForegroundColour(wxColour(185,20,0));
1542 }
1543 else
1544 {
1545 m_pOscIndex->SetForegroundColour(*wxLIGHT_GREY);
1546 }
1547
1548 m_pOscIndex->SetLabel(wxString::Format("RA Osc: %4.2f", m_stats.osc_index));
1549 }
1550 }
1551
OnLeftBtnDown(wxMouseEvent & evt)1552 void GraphLogClientWindow::OnLeftBtnDown(wxMouseEvent& evt)
1553 {
1554 if (wxGetKeyState(WXK_CONTROL))
1555 {
1556 wxSize size(GetClientSize());
1557 const int leftEdge = 0;
1558 const int rightEdge = size.x - GRAPH_BORDER;
1559 const int topEdge = GRAPH_BORDER;
1560 const int bottomEdge = size.y - GRAPH_BORDER;
1561 const int xorig = 0;
1562
1563 if (evt.GetX() >= leftEdge && evt.GetX() <= rightEdge && evt.GetY() >= topEdge && evt.GetY() <= bottomEdge)
1564 {
1565 const double xmag = size.x / (double) m_length;
1566
1567 unsigned int plot_length = GetItemCount();
1568 unsigned int start_item = m_history.size() - plot_length;
1569
1570 unsigned int i = start_item + (unsigned int) floor((double)(evt.GetX() - xorig) / xmag + 0.5);
1571 if (i < m_history.size())
1572 {
1573 wxLongLong_t deltaT = m_history[m_history.size() - 1].timestamp - m_history[i].timestamp;
1574
1575 m_history.pop_back(i);
1576
1577 // Some items removed from m_history may not be resident in the "noDither" collections
1578 int numDeletes = wxMin(m_noDitherDec.GetCount(), i);
1579 long newStart = m_noDitherDec.GetLastEntry().DeltaTime - deltaT; // mSec for new starting point in noDither collections
1580
1581 while (numDeletes > 0)
1582 {
1583 if (m_noDitherDec.GetEntry(0).DeltaTime <= newStart)
1584 {
1585 m_noDitherDec.RemoveOldestEntry();
1586 numDeletes--;
1587 }
1588 else
1589 break;
1590 if (m_noDitherDec.GetCount() == 0)
1591 break;
1592 }
1593 numDeletes = wxMin(m_noDitherRA.GetCount(), i);
1594 while (numDeletes > 0)
1595 {
1596 if (m_noDitherRA.GetEntry(0).DeltaTime <= newStart)
1597 {
1598 m_noDitherRA.RemoveOldestEntry();
1599 numDeletes--;
1600 }
1601 else
1602 break;
1603 if (m_noDitherRA.GetCount() == 0)
1604 break;
1605 }
1606 RecalculateTrendLines();
1607 Refresh();
1608 }
1609 }
1610 }
1611
1612 evt.Skip();
1613 }
1614
GraphControlPane(wxWindow * pParent,const wxString & label)1615 GraphControlPane::GraphControlPane(wxWindow *pParent, const wxString& label)
1616 : wxWindow(pParent, wxID_ANY)
1617 {
1618 m_pControlSizer = new wxBoxSizer(wxHORIZONTAL);
1619
1620 SetBackgroundColour(*wxBLACK);
1621
1622 int width = StringWidth(label);
1623 wxStaticText *pLabel = new wxStaticText(this,wxID_ANY,label, wxDefaultPosition, wxSize(width + 5, -1));
1624 wxFont f = pLabel->GetFont();
1625 f.SetWeight(wxFONTWEIGHT_BOLD);
1626 pLabel->SetFont(f);
1627 pLabel->SetForegroundColour(*wxWHITE);
1628 pLabel->SetBackgroundColour(*wxBLACK);
1629
1630 m_pControlSizer->Add(pLabel, wxSizerFlags().Right().Align(wxALIGN_CENTER_VERTICAL));
1631 SetSizer(m_pControlSizer);
1632 }
1633
~GraphControlPane()1634 GraphControlPane::~GraphControlPane()
1635 {
1636 }
1637
UpdateControls()1638 void GraphControlPane::UpdateControls()
1639 {
1640 }
1641
EnableDecControls(bool enable)1642 void GraphControlPane::EnableDecControls(bool enable)
1643 {
1644
1645 }
1646
StringWidth(const wxString & string)1647 int GraphControlPane::StringWidth(const wxString& string)
1648 {
1649 int width, height;
1650
1651 GetParent()->GetTextExtent(string, &width, &height);
1652
1653 return width;
1654 }
1655
DoAdd(wxControl * pCtrl,const wxString & lbl)1656 void GraphControlPane::DoAdd(wxControl *pCtrl, const wxString& lbl)
1657 {
1658 if (!lbl.empty())
1659 {
1660 wxStaticText *pLabel = new wxStaticText(this, wxID_ANY, lbl);
1661 pLabel->SetForegroundColour(*wxWHITE);
1662 pLabel->SetBackgroundColour(*wxBLACK);
1663 m_pControlSizer->Add(pLabel, wxSizerFlags().Right().Align(wxALIGN_CENTER_VERTICAL));
1664 m_pControlSizer->AddSpacer(5);
1665 }
1666 m_pControlSizer->Add(pCtrl, wxSizerFlags().Left().Align(wxALIGN_CENTER_VERTICAL));
1667 m_pControlSizer->AddSpacer(10);
1668 }
1669