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