1 /*
2 *  aui_controls.cpp
3 *  PHD Guiding
4 *
5 *  Created by Bruce Waddington
6 *  Copyright (c) 2016 Bruce Waddington and Andy Galasso
7 *  All rights reserved.
8 *
9 *  This source code is distributed under the following "BSD" license
10 *  Redistribution and use in source and binary forms, with or without
11 *  modification, are permitted provided that the following conditions are met:
12 *    Redistributions of source code must retain the above copyright notice,
13 *     this list of conditions and the following disclaimer.
14 *    Redistributions in binary form must reproduce the above copyright notice,
15 *     this list of conditions and the following disclaimer in the
16 *     documentation and/or other materials provided with the distribution.
17 *    Neither the name of Bret McKee, Dad Dog Development,
18 *     Craig Stark, Stark Labs nor the names of its
19 *     contributors may be used to endorse or promote products derived from
20 *     this software without specific prior written permission.
21 *
22 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 *  POSSIBILITY OF SUCH DAMAGE.
33 *
34 */
35 
36 #include "phd.h"
37 #include "aui_controls.h"
38 
39 #include <algorithm>
40 #include <unordered_set>
41 
42 //#define ICON_DEV
43 #ifndef ICON_DEV
44 #include "icons/sb_led_green.png.h"
45 #include "icons/sb_led_yellow.png.h"
46 #include "icons/sb_led_red.png.h"
47 #include "icons/sb_arrow_left_16.png.h"
48 #include "icons/sb_arrow_right_16.png.h"
49 #include "icons/sb_arrow_up_16.png.h"
50 #include "icons/sb_arrow_down_16.png.h"
51 #endif
52 
53 wxBEGIN_EVENT_TABLE(PHDStatusBar, wxStatusBar)
54   EVT_SIZE(PHDStatusBar::OnSize)
55 wxEND_EVENT_TABLE()
56 
57 // Types of fields in the statusbar
58 enum SBFieldTypes
59 {
60     Field_StatusMsg,
61     Field_Sat,
62     Field_SNR,
63     Field_RAInfo,
64     Field_DecInfo,
65     Field_Darks,
66     Field_Calib,
67     Field_Gear,
68     Field_Max
69 };
70 
71 class PHDStatusBar;
72 
73 // Self-drawn panel for hosting controls in the wxStatusBar
74 class SBPanel : public wxPanel
75 {
76     std::vector<int> m_fieldOffsets;
77     wxString m_overlayText;
78     std::unordered_set<wxWindow *> m_hidden;   // controls that are hidden by the overlay text
79 
80     enum { OVERLAY_HPADDING = 10 };
81 
OverlayWidth() const82     int OverlayWidth() const {
83         wxSize sz = GetTextExtent(m_overlayText);
84         return OVERLAY_HPADDING + sz.GetWidth() + OVERLAY_HPADDING;
85     }
86 
87 #ifdef __APPLE__
88     // OSX needs a timer to clear the overlay text since no events are
89     // delivered when menu items are de-selected :(
90     wxTimer m_timer;
91     void OnTimer(wxTimerEvent&);
92 #endif
93 
94 public:
95     int emWidth;
96 
97     SBPanel(wxStatusBar *parent, const wxSize& panelSize);
98     void ShowControl(wxWindow *ctrl, bool show);
99     void SetOverlayText(const wxString& text);
100     void OnPaint(wxPaintEvent& evt);
101     void BuildFieldOffsets(const std::vector<int>& fldWidths);
102     wxPoint FieldLoc(int fieldId);
103     int GetMinPanelWidth();
104 
105     wxDECLARE_EVENT_TABLE();
106 };
107 
108 wxBEGIN_EVENT_TABLE(SBPanel, wxPanel)
109   EVT_PAINT(SBPanel::OnPaint)
110 #ifdef __APPLE__
111   EVT_TIMER(wxID_ANY, SBPanel::OnTimer)
112 #endif
113 wxEND_EVENT_TABLE()
114 
115 // Classes for color-coded state indicators
116 class SBStateIndicatorItem;
117 
118 class SBStateIndicators
119 {
120     std::vector <SBStateIndicatorItem *> m_stateItems;
121     SBPanel *m_parentPanel;
122 
123 public:
124     wxIcon icoGreenLed;
125     wxIcon icoYellowLed;
126     wxIcon icoRedLed;
127 
128     SBStateIndicators(SBPanel *panel, std::vector<int>& fldWidths);
129     ~SBStateIndicators();
130     void PositionControls();
131     void UpdateState();
132 };
133 
134 class SBStateIndicatorItem
135 {
136 public:
137     SBFieldTypes m_type;
138     int txtWidth;
139     int fieldId;
140     int lastState;
141     SBPanel *m_parentPanel;
142     SBStateIndicators *container;
143     wxStaticText *ctrl;
144     wxStaticBitmap *pic;
145     wxString otherInfo;
146 
147 public:
148     SBStateIndicatorItem(SBPanel *panel, SBStateIndicators *container,
149         int indField, const wxString& indLabel, SBFieldTypes indType, std::vector<int>& fldWidths);
150     void PositionControl();
151     void UpdateState();
152     wxString GearToolTip(int quadState);
153     wxString DarksToolTip(int quadState);
154 };
155 
156 class SBGuideIndicators
157 {
158     wxStaticBitmap *bitmapRA;
159     wxStaticBitmap *bitmapDec;
160     wxStaticText *txtRaAmounts;
161     wxStaticText *txtDecAmounts;
162     wxBitmap arrowLeft;
163     wxBitmap arrowRight;
164     wxBitmap arrowUp;
165     wxBitmap arrowDown;
166     SBPanel *m_parentPanel;
167 
168 public:
169     SBGuideIndicators(SBPanel *panel, std::vector<int>& fldWidths);
170     void PositionControls();
171     void UpdateState(int raDirection, int decDirection, double raPx, int raPulse, double decPx, int decPulse);
ClearState()172     void ClearState() { UpdateState(LEFT, UP, 0, 0, 0, 0); }
173 };
174 
175 class SBStarIndicators
176 {
177     wxStaticText *txtSNRLabel;
178     wxStaticText *txtSNRValue;
179     wxStaticText *txtStarInfo;
180     int snrLabelWidth;
181     SBPanel *m_parentPanel;
182 
183 public:
184     SBStarIndicators(SBPanel *panel, std::vector<int>& fldWidths);
185     void PositionControls();
186     void UpdateState(double MassPct, double SNR, bool Saturated);
187 
188 };
189 
190 // How this works:
191 // PHDStatusBar is a child of wxStatusBar and is composed of various control groups - properties of the guide star, info about
192 // current guide commands, and state information about the current app session.  Each group is managed by its own class, and that class
193 // is responsible for building, positioning, and updating its controls. The various controls are positioned (via the OnSize event) on top of the SBPanel that
194 // is the single underlying field in the base-class statusbar.  The SBPanel class handles its own Paint event in order to render
195 // the borders and field separators the way we want.
196 
197 // ----------------------------------------------------------------------------
198 // SBPanel - parent control is the parent for all the status bar items
199 //
SBPanel(wxStatusBar * parent,const wxSize & panelSize)200 SBPanel::SBPanel(wxStatusBar *parent, const wxSize& panelSize)
201         : wxPanel(parent, wxID_ANY, wxDefaultPosition, panelSize)
202 {
203     m_fieldOffsets.reserve(12);
204     int txtHeight;
205     parent->GetTextExtent("M", &emWidth, &txtHeight);       // Horizontal spacer used by various controls
206     SetBackgroundStyle(wxBG_STYLE_PAINT);
207 
208 #ifdef __APPLE__
209     m_timer.SetOwner(this);
210 #endif
211 
212 #ifndef __APPLE__
213     SetDoubleBuffered(true);
214 #endif
215 }
216 
217 // wxWidgets does not support controls that overlap
218 //   our workaround is to hide the controls that are overlapped by the overlay text
219 //   m_hidden contains the set of controls that are hidden, but would otherwise be visible if the overlay text were not present
220 //
ShowControl(wxWindow * ctrl,bool show)221 void SBPanel::ShowControl(wxWindow *ctrl, bool show)
222 {
223     if (m_overlayText.empty() || ctrl->GetPosition().x >= OverlayWidth())
224     {
225         ctrl->Show(show);
226         return;
227     }
228 
229     // ctrl is overlapped by overlap text
230     //   show=true
231     //      ctrl already in hidden - do nothing
232     //      ctrl not in hidden - show(false), add to hidden
233     //   show=false
234     //      ctrl in hidden - remove it
235     //      ctrl not in hidden - show(false)
236     if (show)
237     {
238         if (m_hidden.find(ctrl) == m_hidden.end())
239         {
240             ctrl->Show(false);
241             m_hidden.insert(ctrl);
242         }
243     }
244     else
245     {
246         ctrl->Show(false);
247         auto it = m_hidden.find(ctrl);
248         if (it != m_hidden.end())
249         {
250             m_hidden.erase(it);
251         }
252     }
253 }
254 
SetOverlayText(const wxString & s)255 void SBPanel::SetOverlayText(const wxString& s)
256 {
257     m_overlayText = s;
258 
259     if (s.empty())
260     {
261         // un-hide overlapped controls
262         std::for_each(m_hidden.begin(), m_hidden.end(), [](wxWindow *p) { p->Show(true); });
263         m_hidden.clear();
264 #ifdef __APPLE__
265         m_timer.Stop();
266 #endif
267     }
268     else
269     {
270         int const width = OverlayWidth();
271 
272         // hide overlapped controls and un-hide hidden controls that are no longer overlapped
273         const wxWindowList& children = GetChildren();
274         for (auto it = children.begin(); it != children.end(); ++it)
275         {
276             wxWindow *w = *it;
277             if (w->IsShown())
278             {
279                 if (w->GetPosition().x < width)
280                 {
281                     w->Show(false);
282                     m_hidden.insert(w);
283                 }
284             }
285             else
286             {
287                 if (w->GetPosition().x >= width)
288                 {
289                     auto it = m_hidden.find(w);
290                     if (it != m_hidden.end())
291                     {
292                         m_hidden.erase(it);
293                         w->Show(true);
294                     }
295                 }
296             }
297         }
298 #ifdef __APPLE__
299         m_timer.StartOnce(5000);
300 #endif
301     }
302 
303     Refresh();
304 }
305 
306 #ifdef __APPLE__
OnTimer(wxTimerEvent & evt)307 void SBPanel::OnTimer(wxTimerEvent& evt)
308 {
309     SetOverlayText(wxEmptyString);
310 }
311 #endif // __APPLE__
312 
OnPaint(wxPaintEvent & evt)313 void SBPanel::OnPaint(wxPaintEvent& evt)
314 {
315     wxAutoBufferedPaintDC dc(this);
316     dc.SetBackground(*wxBLACK_BRUSH);
317     dc.Clear();
318 
319     wxPen pen(*wxWHITE, 1);
320     wxSize panelSize = GetClientSize();
321     int x;
322 
323     dc.SetPen(pen);
324     // Draw vertical white lines slightly in front of each field
325     for (auto it = m_fieldOffsets.begin() + 1; it != m_fieldOffsets.end(); it++)
326     {
327         x = panelSize.x - *it - 4;
328         dc.DrawLine(wxPoint(x, 0), wxPoint(x, panelSize.y));
329     }
330     // Put a border on the top of the panel
331     dc.DrawLine(wxPoint(0, 0), wxPoint(panelSize.x, 0));
332     dc.SetPen(wxNullPen);
333 
334     if (!m_overlayText.empty())
335     {
336         dc.SetBrush(wxColor(0xe5, 0xdc, 0x62));
337         dc.DrawRectangle(0, 0, OverlayWidth(), GetSize().GetHeight());
338         dc.SetTextForeground(*wxBLACK);
339         dc.DrawText(m_overlayText, OVERLAY_HPADDING, 1);
340     }
341 }
342 
343 // We want a vector with the integer offset of each field relative to the righthand end of the panel
BuildFieldOffsets(const std::vector<int> & fldWidths)344 void SBPanel::BuildFieldOffsets(const std::vector<int>& fldWidths)
345 {
346     int cum = 0;
347     // Add up field widths starting at right end of panel
348     for (auto it = fldWidths.rbegin(); it != fldWidths.rend(); it++)
349     {
350         cum += *it;
351         m_fieldOffsets.push_back(cum);
352     }
353     // Reverse it because the fields are indexed from left to right
354     std::reverse(m_fieldOffsets.begin(), m_fieldOffsets.end());
355 }
356 
GetMinPanelWidth()357 int SBPanel::GetMinPanelWidth()
358 {
359     return m_fieldOffsets.at(0);
360 }
361 
FieldLoc(int fieldId)362 wxPoint SBPanel::FieldLoc(int fieldId)
363 {
364     wxSize panelSize = GetClientSize();
365     int x = panelSize.x - m_fieldOffsets.at(fieldId);
366     return wxPoint(x, 3);
367 }
368 
369 //-----------------------------------------------------------------------------
370 // SBStarIndicators - properties of the guide star
371 //
SBStarIndicators(SBPanel * panel,std::vector<int> & fldWidths)372 SBStarIndicators::SBStarIndicators(SBPanel *panel, std::vector<int>& fldWidths)
373 {
374     int snrValueWidth;
375     int satWidth;
376 
377     int txtHeight;
378     panel->GetTextExtent(_("SNR"), &snrLabelWidth, &txtHeight);
379     panel->GetTextExtent("999.9", &snrValueWidth, &txtHeight);
380     panel->GetTextExtent(_("SAT"), &satWidth, &txtHeight);
381     fldWidths.push_back(satWidth + 1 * panel->emWidth);
382     fldWidths.push_back(snrLabelWidth + snrValueWidth + 2 * panel->emWidth);
383 
384     // Use default positions for control creation - positioning is handled explicitly in PositionControls()
385     txtStarInfo = new wxStaticText(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(satWidth, -1));
386     txtStarInfo->SetBackgroundColour(*wxBLACK);
387     txtStarInfo->SetForegroundColour(*wxWHITE);
388     // Label and value fields separated to allow different foreground colors for each
389     txtSNRLabel = new wxStaticText(panel, wxID_ANY, _("SNR"), wxDefaultPosition, wxDefaultSize);
390     txtSNRValue = new wxStaticText(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(snrValueWidth, 3), wxALIGN_RIGHT);
391     txtSNRLabel->SetBackgroundColour(*wxBLACK);
392     txtSNRLabel->SetForegroundColour(*wxWHITE);
393     txtSNRLabel->Show(false);
394     txtSNRValue->SetBackgroundColour(*wxBLACK);
395     txtSNRValue->SetForegroundColour(*wxGREEN);
396     txtSNRValue->SetToolTip(_("Signal-to-noise ratio of guide star\nGreen means SNR >= 10\nYellow means  4 <= SNR < 10\nRed means SNR < 4"));
397 
398     m_parentPanel = panel;
399 }
400 
PositionControls()401 void SBStarIndicators::PositionControls()
402 {
403     int fieldNum = (int)Field_Sat;
404     wxPoint snrPos;
405     wxPoint satPos;
406 
407     satPos = m_parentPanel->FieldLoc(fieldNum++);
408     txtStarInfo->SetPosition(wxPoint(satPos.x + 1, satPos.y));
409     snrPos = m_parentPanel->FieldLoc(fieldNum++);
410     txtSNRLabel->SetPosition(wxPoint(snrPos.x + 3, snrPos.y));
411     txtSNRValue->SetPosition(wxPoint(snrPos.x + 3 + snrLabelWidth + 6, snrPos.y));
412 }
413 
UpdateState(double MassPct,double SNR,bool Saturated)414 void SBStarIndicators::UpdateState(double MassPct, double SNR, bool Saturated)
415 {
416     if (SNR >= 0)
417     {
418         if (SNR >= 10.0)
419             txtSNRValue->SetForegroundColour(*wxGREEN);
420         else
421         {
422             if (SNR >= 4.0)
423                 txtSNRValue->SetForegroundColour(*wxYELLOW);
424             else
425                 txtSNRValue->SetForegroundColour(*wxRED);
426         }
427         m_parentPanel->ShowControl(txtSNRLabel, true);
428         txtSNRValue->SetLabelText(wxString::Format("%3.1f", SNR));
429         m_parentPanel->ShowControl(txtStarInfo, true);
430         m_parentPanel->ShowControl(txtSNRValue, true);
431         if (pFrame->pGuider->GetMultiStarMode())
432         {
433             wxString txtCount = pFrame->pGuider->GetStarCount();
434             txtStarInfo->SetLabelText(txtCount);
435         }
436         else
437         {
438             if (Saturated)
439                 txtStarInfo->SetLabelText("SAT");
440             else
441                 txtStarInfo->SetLabelText(wxEmptyString);
442         }
443     }
444     else
445     {
446         m_parentPanel->ShowControl(txtSNRLabel, false);
447         m_parentPanel->ShowControl(txtSNRValue, false);
448         m_parentPanel->ShowControl(txtStarInfo, false);
449     }
450 }
451 
452 //-----------------------------------------------------------------------------
453 // SBGuideIndicators - info about the most recent guide commands
454 //
SBGuideIndicators(SBPanel * panel,std::vector<int> & fldWidths)455 SBGuideIndicators::SBGuideIndicators(SBPanel *panel, std::vector<int>& fldWidths)
456 {
457 #ifdef ICON_DEV
458     wxIcon arrow = wxIcon("SB_arrow_left_16.png", wxBITMAP_TYPE_PNG, 16, 16);
459     arrowLeft.CopyFromIcon(arrow);
460     arrow = wxIcon("SB_arrow_right_16.png", wxBITMAP_TYPE_PNG, 16, 16);
461     arrowRight.CopyFromIcon(arrow);
462     arrow = wxIcon("SB_arrow_up_16.png", wxBITMAP_TYPE_PNG, 16, 16);
463     arrowUp.CopyFromIcon(arrow);
464     arrow = wxIcon("SB_arrow_down_16.png", wxBITMAP_TYPE_PNG, 16, 16);
465     arrowDown.CopyFromIcon(arrow);
466 #else
467     arrowLeft = (wxBITMAP_PNG_FROM_DATA(sb_arrow_left_16));
468     arrowRight = wxBitmap(wxBITMAP_PNG_FROM_DATA(sb_arrow_right_16));
469     arrowUp = wxBitmap(wxBITMAP_PNG_FROM_DATA(sb_arrow_up_16));
470     arrowDown = wxBitmap(wxBITMAP_PNG_FROM_DATA(sb_arrow_down_16));
471 #endif
472     wxColor fgColor(200, 200, 200);           // reduced brightness
473     int guideAmtWidth;
474     int txtHeight;
475     panel->GetTextExtent("5555 ms, 555 px", &guideAmtWidth, &txtHeight);
476 
477     // Use default positions for control creation - positioning is handled explicitly in PositionControls()
478     bitmapRA = new wxStaticBitmap(panel, wxID_ANY, arrowLeft);
479     wxSize bitmapSize = bitmapRA->GetSize();
480     bitmapRA->Show(false);
481     txtRaAmounts = new wxStaticText(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(guideAmtWidth, bitmapSize.y), wxALIGN_CENTER);
482     txtRaAmounts->SetBackgroundColour(*wxBLACK);
483     txtRaAmounts->SetForegroundColour(fgColor);
484     txtDecAmounts = new wxStaticText(panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(guideAmtWidth, bitmapSize.y), wxALIGN_RIGHT);
485     txtDecAmounts->SetBackgroundColour(*wxBLACK);
486     txtDecAmounts->SetForegroundColour(fgColor);
487     bitmapDec = new wxStaticBitmap(panel, wxID_ANY, arrowUp);
488     bitmapDec->Show(false);
489     m_parentPanel = panel;
490     // Since we don't want separators between the arrows and the text info, we lump the two together and treat them as one field for the purpose
491     // of positioning
492     fldWidths.push_back(bitmapSize.x + guideAmtWidth + 2 * panel->emWidth);          // RA info
493     fldWidths.push_back(bitmapSize.x + guideAmtWidth + 2 * panel->emWidth);          // Dec info
494 }
495 
PositionControls()496 void SBGuideIndicators::PositionControls()
497 {
498     int fieldNum = (int)Field_RAInfo;
499     int txtWidth;
500     int txtHeight;
501     wxPoint loc;
502 
503     loc = m_parentPanel->FieldLoc(fieldNum);
504     bitmapRA->SetPosition(wxPoint(loc.x, loc.y - 1));
505     wxPoint raPosition = m_parentPanel->FieldLoc(fieldNum);
506     raPosition.x += 20;
507     txtRaAmounts->SetPosition(raPosition);
508 
509     fieldNum++;
510     wxString txtSizer =  wxString::Format(_("%d ms, %0.1f px"), 120, 4.38);
511     m_parentPanel->GetTextExtent(txtSizer, &txtWidth, &txtHeight);
512     wxPoint decPosition = m_parentPanel->FieldLoc(fieldNum);
513     txtDecAmounts->SetPosition(decPosition);
514 
515     decPosition.x += txtWidth + 8;
516     decPosition.y -= 1;
517     bitmapDec->SetPosition(decPosition);
518 }
519 
UpdateState(int raDirection,int decDirection,double raPx,int raPulse,double decPx,int decPulse)520 void SBGuideIndicators::UpdateState(int raDirection, int decDirection, double raPx, int raPulse, double decPx, int decPulse)
521 {
522     wxString raInfo;
523     wxString decInfo;
524 
525     if (raPulse > 0)
526     {
527         if (raDirection == RIGHT)
528             bitmapRA->SetBitmap(arrowRight);
529         else
530             bitmapRA->SetBitmap(arrowLeft);
531 
532         m_parentPanel->ShowControl(bitmapRA, true);
533         raInfo = wxString::Format(_("%d ms, %0.1f px"), raPulse, raPx);
534     }
535     else
536     {
537         m_parentPanel->ShowControl(bitmapRA, false);
538     }
539 
540     if (decPulse > 0)
541     {
542         if (decDirection == UP)
543             bitmapDec->SetBitmap(arrowUp);
544         else
545             bitmapDec->SetBitmap(arrowDown);
546         m_parentPanel->ShowControl(bitmapDec, true);
547         decInfo = wxString::Format(_("%d ms, %0.1f px"), decPulse, decPx);
548     }
549     else
550     {
551         m_parentPanel->ShowControl(bitmapDec, false);
552     }
553 
554     txtRaAmounts->SetLabelText(raInfo);
555     txtDecAmounts->SetLabelText(decInfo);
556 }
557 
558 //------------------------------------------------------------------------------------------
559 // ---SBStateIndicatorItem - individual state indicators
560 //
SBStateIndicatorItem(SBPanel * panel,SBStateIndicators * host,int indField,const wxString & indLabel,SBFieldTypes indType,std::vector<int> & fldWidths)561 SBStateIndicatorItem::SBStateIndicatorItem(SBPanel *panel, SBStateIndicators *host, int indField, const wxString& indLabel, SBFieldTypes indType, std::vector<int>& fldWidths)
562 {
563     m_type = indType;
564     lastState = -2;
565     m_parentPanel = panel;
566     container = host;
567     fieldId = indField;
568     otherInfo = wxEmptyString;
569     int txtHeight;
570     m_parentPanel->GetTextExtent(indLabel, &txtWidth, &txtHeight);
571     // Use default positions for control creation - positioning is handled explicitly in PositionControls()
572     if (indType != Field_Gear)
573     {
574         ctrl = new wxStaticText(m_parentPanel, wxID_ANY, indLabel, wxDefaultPosition, wxSize(txtWidth + m_parentPanel->emWidth, -1), wxALIGN_CENTER);
575         fldWidths.push_back(txtWidth + 2 * m_parentPanel->emWidth);
576     }
577     else
578     {
579         pic = new wxStaticBitmap(m_parentPanel, wxID_ANY, container->icoGreenLed, wxDefaultPosition, wxSize(16, 16));
580         fldWidths.push_back(20 + 1 * m_parentPanel->emWidth);
581     }
582 }
583 
PositionControl()584 void SBStateIndicatorItem::PositionControl()
585 {
586     wxPoint loc;
587     if (m_type == Field_Gear)
588     {
589         loc = m_parentPanel->FieldLoc(fieldId);
590         pic->SetPosition(wxPoint(loc.x + 7, loc.y));
591     }
592     else
593         ctrl->SetPosition(m_parentPanel->FieldLoc(fieldId));
594 }
595 
CalibrationQuadState(wxString * tip)596 static int CalibrationQuadState(wxString *tip)
597 {
598     // For calib quad state: -1 => no cal, 0 => cal but no pointing compensation, 1 => golden
599 
600     bool calibrated = (pMount || pSecondaryMount) &&
601         (!pMount || pMount->IsCalibrated()) && (!pSecondaryMount || pSecondaryMount->IsCalibrated());
602 
603     if (!calibrated)
604     {
605         *tip = _("Not calibrated");
606         return -1;
607     }
608 
609     const Scope *scope = TheScope();
610     if (!scope && TheAO())
611     {
612         *tip = _("Calibrated"); // just an AO, we don't care about dec compensation
613         return 1;
614     }
615 
616     if (!pPointingSource || !pPointingSource->IsConnected() || !pPointingSource->CanReportPosition())
617     {
618         *tip = _("Calibrated, scope pointing information not available");
619         return 0;
620     }
621 
622     if (!scope->DecCompensationEnabled())
623     {
624         *tip = _("Calibrated, declination compensation disabled");
625         return 0;
626     }
627 
628     if (scope->MountCal().declination == UNKNOWN_DECLINATION)
629     {
630         *tip = _("Calibrated, but a new calibration is required to activate declination compensation");
631         return 0;
632     }
633 
634     *tip = _("Calibrated, scope pointing info in use");
635     return 1;
636 }
637 
Join(const wxString & delim,const std::vector<wxString> & vec)638 static wxString Join(const wxString& delim, const std::vector<wxString>& vec)
639 {
640     if (vec.size() == 0)
641         return wxEmptyString;
642 
643     size_t l = 0;
644     std::for_each(vec.begin(), vec.end(), [&l](const wxString& s) { l += s.length(); });
645     wxString buf;
646     buf.reserve(l + delim.length() * (vec.size() - 1));
647     std::for_each(vec.begin(), vec.end(),
648                   [&buf, &delim](const wxString& s) {
649                       if (!buf.empty())
650                           buf += delim;
651                       buf += s;
652                   });
653     return buf;
654 }
655 
UpdateState()656 void SBStateIndicatorItem::UpdateState()
657 {
658     int quadState = -1;
659     wxString cal_tooltip;
660 
661     switch (m_type)
662     {
663     case Field_Gear: {
664         std::vector<wxString> MIAs;
665         bool cameraOk = true;
666         bool problems = false;
667         bool partials = false;
668 
669         if (pCamera && pCamera->Connected)
670         {
671             partials = true;
672         }
673         else
674         {
675             MIAs.push_back(_("Camera"));
676             cameraOk = false;
677             problems = true;
678         }
679 
680         if ((pMount && pMount->IsConnected()) || (pSecondaryMount && pSecondaryMount->IsConnected()))
681             partials = true;
682         else
683         {
684             MIAs.push_back(_("Mount"));
685             problems = true;
686         }
687 
688         if (pPointingSource && pPointingSource->IsConnected())
689             partials = true;
690         else
691         {
692             MIAs.push_back(_("Aux Mount"));
693             problems = true;
694         }
695 
696         if (pMount && pMount->IsStepGuider())
697         {
698             if (pMount->IsConnected())
699                 partials = true;
700             else
701             {
702                 MIAs.push_back(_("AO"));
703                 problems = true;
704             }
705         }
706 
707         if (pRotator)
708         {
709             if (pRotator->IsConnected())
710                 partials = true;
711             else
712             {
713                 MIAs.push_back(_("Rotator"));
714                 problems = true;
715             }
716         }
717 
718         if (partials)
719         {
720             if (!problems)
721             {
722                 pic->SetIcon(wxIcon(container->icoGreenLed));
723                 quadState = 1;
724                 otherInfo.clear();
725             }
726             else
727             {
728                 if (cameraOk)
729                     pic->SetIcon(container->icoYellowLed);
730                 else
731                     pic->SetIcon(container->icoRedLed);     // What good are we without a camera
732                 quadState = 0;
733                 otherInfo = Join(_(", "), MIAs);
734                 pic->SetToolTip(GearToolTip(quadState));
735             }
736         }
737         else
738         {
739             pic->SetIcon(container->icoRedLed);
740             quadState = -1;
741         }
742 
743         break;
744     }
745 
746     case Field_Darks:
747         if (pFrame)
748         {
749             if (pFrame->m_useDarksMenuItem->IsChecked() || pFrame->m_useDefectMapMenuItem->IsChecked())
750             {
751                 quadState = 1;
752                 wxString lastLabel = ctrl->GetLabelText();
753                 wxString currLabel = (pFrame->m_useDefectMapMenuItem->IsChecked() ? _("BPM") : _("Dark"));
754                 if (lastLabel != currLabel)
755                 {
756                     ctrl->SetLabelText(currLabel);
757                     ctrl->SetToolTip(DarksToolTip(quadState));
758                 }
759             }
760         }
761 
762         break;
763 
764     case Field_Calib: {
765         quadState = CalibrationQuadState(&cal_tooltip);
766         lastState = -2; // force tool-tip update even if state did not change
767         break;
768       }
769 
770     default:
771         break;
772     }
773 
774     // Don't flog the status icons unless something has changed
775     if (lastState != quadState)
776     {
777         if (m_type != Field_Gear)
778         {
779             switch (quadState)
780             {
781             case -2:
782                 ctrl->SetForegroundColour(*wxLIGHT_GREY);
783                 break;
784             case -1:
785                 ctrl->SetForegroundColour(*wxRED);
786                 break;
787             case 0:
788                 ctrl->SetForegroundColour(*wxYELLOW);
789                 break;
790             case 1:
791                 ctrl->SetForegroundColour(*wxGREEN);
792                 break;
793             }
794             ctrl->Refresh();
795 
796             if (quadState != -2)
797             {
798                 if (m_type == Field_Darks)
799                     ctrl->SetToolTip(DarksToolTip(quadState));
800                 else if (m_type == Field_Calib)
801                     ctrl->SetToolTip(cal_tooltip);
802             }
803         }
804         else if (quadState != -2)
805         {
806             // m_type == Field_Gear
807             pic->SetToolTip(GearToolTip(quadState));
808         }
809 
810         lastState = quadState;
811     }
812 }
813 
GearToolTip(int quadState)814 wxString SBStateIndicatorItem::GearToolTip(int quadState)
815 {
816     if (quadState == 1)
817         return _("All devices connected");
818     else if (quadState == -1)
819         return _("No devices connected");
820     else
821         return wxString::Format(_("Devices not connected: %s"), otherInfo);
822 }
823 
DarksToolTip(int quadState)824 wxString SBStateIndicatorItem::DarksToolTip(int quadState)
825 {
826     if (ctrl->GetLabelText() == _("Dark"))
827         return quadState == 1 ? _("Dark library in use") : _("Dark library not in use");
828     else
829         return quadState == 1 ? _("Bad-pixel map in use") : _("Bad-pixel map not in use");
830 }
831 
832 //--------------------------------------------------------------------------------------
833 // ---SBStateIndicators - the group of all app/session state controls, a mix of static text and bitmap controls
834 //
SBStateIndicators(SBPanel * panel,std::vector<int> & fldWidths)835 SBStateIndicators::SBStateIndicators(SBPanel *panel, std::vector<int>& fldWidths)
836 {
837     m_parentPanel = panel;
838     wxString labels[] = { _("Dark"), _("Cal"), wxEmptyString };
839 
840 #ifdef ICON_DEV
841     icoGreenLed = wxIcon("SB_led_green.ico", wxBITMAP_TYPE_ICO, 16, 16);
842     icoYellowLed = wxIcon("SB_led_yellow.ico", wxBITMAP_TYPE_ICO, 16, 16);
843     icoRedLed = wxIcon("SB_led_red.ico", wxBITMAP_TYPE_ICO, 16, 16);
844 #else
845     wxBitmap led(wxBITMAP_PNG_FROM_DATA(sb_led_green));
846     icoGreenLed.CopyFromBitmap(led);
847     led = wxBitmap(wxBITMAP_PNG_FROM_DATA(sb_led_yellow));
848     icoYellowLed.CopyFromBitmap(led);
849     led = wxBitmap(wxBITMAP_PNG_FROM_DATA(sb_led_red));
850     icoRedLed.CopyFromBitmap(led);
851 #endif
852     for (int inx = (int)Field_Darks; inx <= (int)Field_Gear; inx++)
853     {
854         SBStateIndicatorItem *item = new SBStateIndicatorItem(m_parentPanel, this, inx, labels[inx - Field_Darks], (SBFieldTypes)(inx), fldWidths);
855         m_stateItems.push_back(item);
856         item->UpdateState();
857     }
858 }
859 
~SBStateIndicators()860 SBStateIndicators::~SBStateIndicators()
861 {
862     for (unsigned int inx = 0; inx < m_stateItems.size(); inx++)
863         delete m_stateItems.at(inx);
864 }
865 
PositionControls()866 void SBStateIndicators::PositionControls()
867 {
868     for (auto it = m_stateItems.begin(); it != m_stateItems.end(); it++)
869     {
870         (*it)->PositionControl();
871     }
872 }
873 
UpdateState()874 void SBStateIndicators::UpdateState()
875 {
876     for (auto it = m_stateItems.begin(); it != m_stateItems.end(); it++)
877     {
878         (*it)->UpdateState();
879     }
880 }
881 
882 
883 enum {
884     SB_HEIGHT = 16
885 };
886 
887 // -----------  PHDStatusBar Class
888 //
PHDStatusBar(wxWindow * parent,long style)889 PHDStatusBar::PHDStatusBar(wxWindow *parent, long style)
890     : wxStatusBar(parent, wxID_ANY, wxSTB_SHOW_TIPS | wxSTB_ELLIPSIZE_END | wxFULL_REPAINT_ON_RESIZE, "PHDStatusBar")
891 {
892     std::vector<int> fieldWidths;
893 
894     // Set up the only field the wxStatusBar base class will know about
895     int widths[] = {-1};
896 
897     SetFieldsCount(1);
898     SetStatusWidths(1, widths);
899     this->SetBackgroundColour(*wxBLACK);
900 
901     m_ctrlPanel = new SBPanel(this, wxSize(500, SB_HEIGHT));
902     m_ctrlPanel->SetPosition(wxPoint(1, 2));
903 
904     // Build the leftmost text status field, the only field managed at this level
905     m_Msg1 = new wxStaticText(m_ctrlPanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(150, -1));
906     int txtWidth, txtHeight;
907     GetTextExtent(_("Selected star at (999.9, 999.9)"), &txtWidth, &txtHeight);         // only care about the width
908     m_Msg1->SetBackgroundColour(*wxBLACK);
909     m_Msg1->SetForegroundColour(*wxWHITE);
910     fieldWidths.push_back(txtWidth);                    // Doesn't matter but we need to occupy the position in fieldWidths
911 
912     // Build the star status fields
913     m_StarIndicators = new SBStarIndicators(m_ctrlPanel, fieldWidths);
914 
915     // Build the guide indicators
916     m_GuideIndicators = new SBGuideIndicators(m_ctrlPanel, fieldWidths);
917 
918     // Build the state indicator controls
919     m_StateIndicators = new SBStateIndicators(m_ctrlPanel, fieldWidths);
920 
921     m_ctrlPanel->BuildFieldOffsets(fieldWidths);
922 }
923 
924 // Helper function - not safe to call SetMinHeight in the constructor
CreateInstance(wxWindow * parent,long style)925 PHDStatusBar *PHDStatusBar::CreateInstance(wxWindow *parent, long style)
926 {
927     PHDStatusBar *sb = new PHDStatusBar(parent, style);
928     sb->SetMinHeight(SB_HEIGHT);
929     return sb;
930 }
931 
932 // Destructor
~PHDStatusBar()933 PHDStatusBar::~PHDStatusBar()
934 {
935     this->DestroyChildren();        // any wxWidgets objects will be deleted
936     delete m_StateIndicators;
937     delete m_GuideIndicators;
938     delete m_StarIndicators;
939 }
940 
OverlayMsg(const wxString & text)941 void PHDStatusBar::OverlayMsg(const wxString& text)
942 {
943     m_ctrlPanel->SetOverlayText(text);
944 }
945 
ClearOverlayMsg()946 void PHDStatusBar::ClearOverlayMsg()
947 {
948     m_ctrlPanel->SetOverlayText(wxEmptyString);
949 }
950 
OnSize(wxSizeEvent & event)951 void PHDStatusBar::OnSize(wxSizeEvent& event)
952 {
953     wxRect fldRect;
954     GetFieldRect(0, fldRect);
955     int fldWidth = fldRect.GetWidth();
956     m_ctrlPanel->SetSize(fldWidth - 1, fldRect.GetHeight());
957     m_Msg1->SetPosition(wxPoint(2, 3));
958     m_StarIndicators->PositionControls();
959     m_GuideIndicators->PositionControls();
960     m_StateIndicators->PositionControls();
961 
962     event.Skip();
963 }
964 
965 // Let client force updates to various statusbar components
UpdateStates()966 void PHDStatusBar::UpdateStates()
967 {
968     m_StateIndicators->UpdateState();
969 }
970 
UpdateStarInfo(double SNR,bool Saturated)971 void PHDStatusBar::UpdateStarInfo(double SNR, bool Saturated)
972 {
973     m_StarIndicators->UpdateState(0, SNR, Saturated);
974 }
975 
UpdateGuiderInfo(const GuideStepInfo & info)976 void PHDStatusBar::UpdateGuiderInfo(const GuideStepInfo& info)
977 {
978     m_GuideIndicators->UpdateState(info.directionRA, info.directionDec, fabs(info.mountOffset.X),
979         info.durationRA, fabs(info.mountOffset.Y), info.durationDec);
980 }
981 
ClearGuiderInfo()982 void PHDStatusBar::ClearGuiderInfo()
983 {
984     m_GuideIndicators->ClearState();
985 }
986 
GetMinSBWidth()987 int PHDStatusBar::GetMinSBWidth()
988 {
989     return m_ctrlPanel->GetMinPanelWidth();
990 }
991 
StatusMsg(const wxString & text)992 void PHDStatusBar::StatusMsg(const wxString& text)
993 {
994     m_Msg1->SetLabelText(text);
995     m_Msg1->Update();
996 }
997 
998 // Trivial class to handle the background color on the toolbar control
DrawBackground(wxDC & dc,wxWindow * parent,const wxRect & rect)999 void PHDToolBarArt::DrawBackground(wxDC& dc, wxWindow *parent, const wxRect& rect)
1000 {
1001     dc.SetBrush(wxColour(100, 100, 100));
1002     dc.DrawRectangle(rect);
1003 }
1004