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