1 /*  profile_wizard.cpp
2  *  PHD Guiding
3  *
4  *  Created by Bruce Waddington in collaboration with Andy Galasso
5  *  Copyright (c) 2014 Bruce Waddington
6  *  All rights reserved.
7  *
8  *  This source code is distributed under the following "BSD" license
9  *  Redistribution and use in source and binary forms, with or without
10  *  modification, are permitted provided that the following conditions are met:
11  *    Redistributions of source code must retain the above copyright notice,
12  *     this list of conditions and the following disclaimer.
13  *    Redistributions in binary form must reproduce the above copyright notice,
14  *     this list of conditions and the following disclaimer in the
15  *     documentation and/or other materials provided with the distribution.
16  *    Neither the name of Bret McKee, Dad Dog Development,
17  *     Craig Stark, Stark Labs nor the names of its
18  *     contributors may be used to endorse or promote products derived from
19  *     this software without specific prior written permission.
20  *
21  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  *  POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34 
35 #include "phd.h"
36 #include "profile_wizard.h"
37 #include "calstep_dialog.h"
38 
39 #include <memory>
40 #include <wx/gbsizer.h>
41 #include <wx/hyperlink.h>
42 
43 class ProfileWizard : public wxDialog
44 {
45 public:
46     enum DialogState
47     {
48         STATE_GREETINGS = 0,
49         STATE_CAMERA,
50         STATE_MOUNT,
51         STATE_AUXMOUNT,
52         STATE_AO,
53         STATE_WRAPUP,
54         STATE_DONE, NUM_PAGES = STATE_DONE
55     };
56 
57     enum CtrlIds
58     {
59         ID_COMBO = 10001,
60         ID_PIXELSIZE,
61         ID_DETECT_GUIDESPEED,
62         ID_FOCALLENGTH,
63         ID_BINNING,
64         ID_GUIDESPEED,
65         ID_PREV,
66         ID_HELP,
67         ID_NEXT
68     };
69 
70 private:
71     AutoTempProfile m_profile;
72 
73     // wx UI controls
74     wxBoxSizer *m_pvSizer;
75     wxStaticBitmap *m_bitmap;
76     wxStaticText *m_pInstructions;
77     wxStaticText *m_pGearLabel;
78     wxChoice *m_pGearChoice;
79     wxSpinCtrlDouble *m_pPixelSize;
80     wxStaticBitmap *m_scaleIcon;
81     wxStaticText *m_pixelScale;
82     wxChoice *m_pBinningLevel;
83     wxSpinCtrlDouble *m_pFocalLength;
84     wxSpinCtrlDouble *m_pGuideSpeed;
85     wxCheckBox *m_pDecEncoder;
86     wxButton *m_pPrevBtn;
87     wxButton *m_pNextBtn;
88     wxStaticBoxSizer *m_pHelpGroup;
89     wxStaticText *m_pHelpText;
90     wxFlexGridSizer *m_pGearGrid;
91     wxGridBagSizer *m_pUserProperties;
92     wxFlexGridSizer *m_pMountProperties;
93     wxFlexGridSizer *m_pWrapUp;
94     wxTextCtrl *m_pProfileName;
95     wxCheckBox *m_pLaunchDarks;
96     wxCheckBox *m_pAutoRestore;
97     wxStatusBar *m_pStatusBar;
98     wxHyperlinkCtrl* m_EqLink;
99 
100     wxString m_SelectedCamera;
101     wxString m_SelectedMount;
102     bool m_PositionAware;
103     wxString m_SelectedAuxMount;
104     wxString m_SelectedAO;
105     int m_FocalLength;
106     double m_GuideSpeed;
107     double m_PixelSize;
108     wxString m_ProfileName;
109     wxBitmap *m_bitmaps[NUM_PAGES];
110 
111     void OnNext(wxCommandEvent& evt);
112     void OnPrev(wxCommandEvent& evt);
113     void OnGearChoice(wxCommandEvent& evt);
114     void OnPixelSizeChange(wxSpinDoubleEvent& evt);
115     void OnFocalLengthChange(wxSpinDoubleEvent& evt);
116     void OnFocalLengthText(wxCommandEvent& evt);
117     void OnBinningChange(wxCommandEvent& evt);
118     void UpdatePixelScale();
119     void OnGuideSpeedChange(wxSpinDoubleEvent& evt);
120     void OnContextHelp(wxCommandEvent& evt);
121     void ShowStatus(const wxString& msg, bool appending = false);
122     void UpdateState(const int change);
123     bool SemanticCheck(DialogState state, int change);
124     void ShowHelp(DialogState state);
125     void WrapUp();
126     void InitCameraProps(bool tryConnect);
127     void InitMountProps(Scope *theScope);
128     DialogState m_State;
129     bool m_useCamera;
130     bool m_useMount;
131     bool m_useAuxMount;
132     bool m_autoRestore;
133 
134 public:
135 
136     bool m_launchDarks;
137 
138     ProfileWizard(wxWindow *parent, bool showGreeting);
139     ~ProfileWizard(void);
140 
141     wxDECLARE_EVENT_TABLE();
142 };
143 
144 wxBEGIN_EVENT_TABLE(ProfileWizard, wxDialog)
145 EVT_BUTTON(ID_NEXT, ProfileWizard::OnNext)
146 EVT_BUTTON(ID_PREV, ProfileWizard::OnPrev)
147 EVT_CHOICE(ID_COMBO, ProfileWizard::OnGearChoice)
148 EVT_SPINCTRLDOUBLE(ID_PIXELSIZE, ProfileWizard::OnPixelSizeChange)
149 EVT_SPINCTRLDOUBLE(ID_FOCALLENGTH, ProfileWizard::OnFocalLengthChange)
150 EVT_TEXT(ID_FOCALLENGTH, ProfileWizard::OnFocalLengthText)
151 EVT_CHOICE(ID_BINNING, ProfileWizard::OnBinningChange)
152 EVT_SPINCTRLDOUBLE(ID_GUIDESPEED, ProfileWizard::OnGuideSpeedChange)
153 EVT_BUTTON(ID_HELP, ProfileWizard::OnContextHelp)
154 wxEND_EVENT_TABLE()
155 
156 static const int DialogWidth = 425;
157 static const int TextWrapPoint = 400;
158 // Help text heights - "tall" is for greetings page, "normal" is for gear selection panels
159 static const int TallHelpHeight = 150;
160 static const int NormalHelpHeight = 85;
161 static wxString TitlePrefix;
162 
Label(wxWindow * parent,const wxString & txt)163 static wxStaticText *Label(wxWindow *parent, const wxString& txt)
164 {
165     return new wxStaticText(parent, wxID_ANY, wxString::Format(_("%s:"), txt));
166 }
167 
168 // Utility function to add the <label, input> pairs to a flexgrid
AddTableEntryPair(wxWindow * parent,wxSizer * pTable,const wxString & label,wxWindow * pControl)169 static void AddTableEntryPair(wxWindow *parent, wxSizer *pTable, const wxString& label, wxWindow *pControl)
170 {
171     pTable->Add(Label(parent, label), 0, wxALL, 5);
172     pTable->Add(pControl, 0, wxALL, 5);
173 }
174 
AddTableEntryPair(wxWindow * parent,wxSizer * pTable,const wxString & label,wxSizer * group)175 static void AddTableEntryPair(wxWindow *parent, wxSizer *pTable, const wxString& label, wxSizer *group)
176 {
177     pTable->Add(Label(parent, label), 0, wxALL, 5);
178     pTable->Add(group, 0, wxALL, 5);
179 }
180 
AddCellPair(wxWindow * parent,wxGridBagSizer * gbs,int row,const wxString & label,wxWindow * ctrl)181 static void AddCellPair(wxWindow *parent, wxGridBagSizer *gbs, int row, const wxString& label, wxWindow *ctrl)
182 {
183     gbs->Add(Label(parent, label), wxGBPosition(row, 1), wxDefaultSpan, wxALL, 5);
184     gbs->Add(ctrl, wxGBPosition(row, 2), wxDefaultSpan, wxALL, 5);
185 }
186 
ProfileWizard(wxWindow * parent,bool showGreeting)187 ProfileWizard::ProfileWizard(wxWindow *parent, bool showGreeting) :
188     wxDialog(parent, wxID_ANY, _("New Profile Wizard"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX),
189     m_useCamera(false), m_useMount(false), m_useAuxMount(false), m_autoRestore(false), m_launchDarks(true)
190 {
191     TitlePrefix = _("New Profile Wizard - ");
192 
193     // Create overall vertical sizer
194     m_pvSizer = new wxBoxSizer(wxVERTICAL);
195 
196 #   include "icons/phd2_48.png.h"
197     wxBitmap phd2(wxBITMAP_PNG_FROM_DATA(phd2_48));
198     m_bitmaps[STATE_GREETINGS] = new wxBitmap(phd2);
199     m_bitmaps[STATE_WRAPUP] = new wxBitmap(phd2);
200 #   include "icons/cam2.xpm"
201     m_bitmaps[STATE_CAMERA] = new wxBitmap(cam_icon);
202 #   include "icons/scope1.xpm"
203     m_bitmaps[STATE_MOUNT] = new wxBitmap(scope_icon);
204     m_bitmaps[STATE_AUXMOUNT] = new wxBitmap(scope_icon);
205 #   include "icons/ao.xpm"
206     m_bitmaps[STATE_AO] = new wxBitmap(ao_xpm);
207 
208     // Build the superset of UI controls, minus state-specific labels and data
209     // User instructions at top
210     wxBoxSizer *instrSizer = new wxBoxSizer(wxHORIZONTAL);
211     m_bitmap = new wxStaticBitmap(this, wxID_ANY, *m_bitmaps[STATE_GREETINGS], wxDefaultPosition, wxSize(55, 55));
212     instrSizer->Add(m_bitmap, 0, wxALIGN_CENTER_VERTICAL | wxFIXED_MINSIZE, 5);
213 
214     m_pInstructions = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(DialogWidth, 75), wxALIGN_LEFT | wxST_NO_AUTORESIZE);
215     wxFont font = m_pInstructions->GetFont();
216     font.SetWeight(wxFONTWEIGHT_BOLD);
217     m_pInstructions->SetFont(font);
218     instrSizer->Add(m_pInstructions, wxSizerFlags().Border(wxALL, 10));
219     m_pvSizer->Add(instrSizer);
220 
221     // Verbose help block
222     m_pHelpGroup = new wxStaticBoxSizer(wxVERTICAL, this, _("More Info"));
223     m_pHelpText = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(DialogWidth, -1));
224     // Vertical sizing of help text will be handled in state machine
225     m_pHelpGroup->Add(m_pHelpText, wxSizerFlags().Border(wxLEFT, 10).Border(wxBOTTOM, 10));
226     m_pvSizer->Add(m_pHelpGroup, wxSizerFlags().Border(wxALL, 5));
227 
228     // Status bar for error messages
229     m_pStatusBar = new wxStatusBar(this, -1);
230     m_pStatusBar->SetFieldsCount(1);
231 
232     // Gear label and combo box
233     m_pGearGrid = new wxFlexGridSizer(1, 2, 5, 15);
234     m_pGearLabel = new wxStaticText(this, wxID_ANY, "Temp:", wxDefaultPosition, wxDefaultSize);
235     m_pGearChoice = new wxChoice(this, ID_COMBO, wxDefaultPosition, wxDefaultSize,
236                               GuideCamera::GuideCameraList(), 0, wxDefaultValidator, _("Gear"));
237     m_pGearGrid->Add(m_pGearLabel, 1, wxALL, 5);
238     m_pGearGrid->Add(m_pGearChoice, 1, wxLEFT, 10);
239     m_pvSizer->Add(m_pGearGrid, wxSizerFlags().Center().Border(wxALL, 5));
240 
241     m_pUserProperties = new wxGridBagSizer(5, 5);
242 
243     // Pixel-size
244     m_pPixelSize = pFrame->MakeSpinCtrlDouble(this, ID_PIXELSIZE, wxEmptyString, wxDefaultPosition,
245         wxSize(StringWidth(this, _T("888.88")), -1), wxSP_ARROW_KEYS, 0.0, 20.0, 0.0, 0.1);
246     m_pPixelSize->SetDigits(2);
247     m_PixelSize = m_pPixelSize->GetValue();
248     m_pPixelSize->SetToolTip(_("Get this value from your camera documentation or from an online source.  You can use the up/down control "
249         "or type in a value directly. If the pixels aren't square, just enter the larger of the X/Y dimensions."));
250     AddCellPair(this, m_pUserProperties, 0, wxString::Format(_("Guide camera un-binned pixel size (%s)"), MICRONS_SYMBOL), m_pPixelSize);
251 
252     // Binning
253     wxArrayString opts;
254     GuideCamera::GetBinningOpts(4, &opts);
255     m_pBinningLevel = new wxChoice(this, ID_BINNING, wxDefaultPosition, wxDefaultSize, opts);
256     m_pBinningLevel->SetToolTip(_("If your camera supports binning (many do not), you can choose a binning value > 1.  "
257         "With long focal length guide scopes and OAGs, binning can allow use of fainter guide "
258         "stars.  For more common setups, it's better to leave binning at 1."));
259     m_pBinningLevel->SetSelection(0);
260     wxBoxSizer *sz = new wxBoxSizer(wxHORIZONTAL);
261     sz->Add(Label(this, _("Binning level")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
262     sz->Add(m_pBinningLevel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
263     m_pUserProperties->Add(sz, wxGBPosition(1, 1), wxDefaultSpan, 0, 0);
264 
265     // Focal length
266     m_pFocalLength = pFrame->MakeSpinCtrlDouble(this, ID_FOCALLENGTH, wxEmptyString, wxDefaultPosition,
267         wxSize(StringWidth(this, _T("88888")), -1), wxSP_ARROW_KEYS,
268         0, AdvancedDialog::MAX_FOCAL_LENGTH, 0.0, 50.0);
269     m_pFocalLength->SetValue(0);
270     m_pFocalLength->SetDigits(0);
271     m_pFocalLength->SetToolTip(_("This is the focal length of the guide scope - or the imaging scope if you are using an off-axis-guider or "
272         "adaptive optics device (Focal length = aperture x f-ratio).  Typical finder scopes have a focal length of about 165mm."));
273     m_FocalLength = (int)m_pFocalLength->GetValue();
274     AddCellPair(this, m_pUserProperties, 2, _("Guide scope focal length (mm)"), m_pFocalLength);
275 
276     // pixel scale
277 #   include "icons/transparent24.png.h"
278     wxBitmap transparent(wxBITMAP_PNG_FROM_DATA(transparent24));
279     m_scaleIcon = new wxStaticBitmap(this, wxID_ANY, transparent);
280     m_pUserProperties->Add(m_scaleIcon, wxGBPosition(3, 0));
281 
282     m_pixelScale = new wxStaticText(this, wxID_ANY, wxString::Format(_("Pixel scale: %8.2f\"/px"), 99.99));
283     m_pixelScale->SetToolTip(_("The pixel scale of your guide configuration, arc-seconds per pixel"));
284     m_pUserProperties->Add(m_pixelScale, wxGBPosition(3, 1), wxDefaultSpan, wxALL, 5);
285 
286     UpdatePixelScale();
287 
288     // controls for the mount pane
289     wxBoxSizer *mtSizer = new wxBoxSizer(wxHORIZONTAL);
290     m_pMountProperties = new wxFlexGridSizer(1, 2, 5, 15);
291     m_pGuideSpeed = new wxSpinCtrlDouble(this, ID_GUIDESPEED, wxEmptyString, wxDefaultPosition,
292         wxDefaultSize, wxSP_ARROW_KEYS, 0.2, 1.0, 0.5, 0.1);
293     m_GuideSpeed = Scope::DEFAULT_MOUNT_GUIDE_SPEED;
294     m_pGuideSpeed->SetValue(m_GuideSpeed);
295     m_pGuideSpeed->SetDigits(2);
296     m_pGuideSpeed->SetToolTip(wxString::Format(_("The mount guide speed you will use for calibration and guiding, expressed as a multiple of the sidereal rate. If you "
297         "don't know, leave the setting at the default value (%0.1fX), which should produce a successful calibration in most cases"), Scope::DEFAULT_MOUNT_GUIDE_SPEED));
298     mtSizer->Add(m_pGuideSpeed, 1);
299     AddTableEntryPair(this, m_pMountProperties, _("Mount guide speed (n.n x sidereal)"), mtSizer);
300 
301     m_pDecEncoder = new wxCheckBox(this, wxID_ANY, _("Declination axis has high-precision encoder (a few high-end mounts)"));
302     m_pDecEncoder->SetToolTip(_("Mount has high-precision encoders on both axes with little or no Dec backlash (e.g. 10Micron, Astro-Physics AE, iOptron EC2 or other high-end mounts"));
303     m_pDecEncoder->SetValue(false);
304 
305     m_pMountProperties->Add(m_pDecEncoder);
306 
307     m_pvSizer->Add(m_pUserProperties, wxSizerFlags().Center().Border(wxALL, 5));
308     m_pvSizer->Add(m_pMountProperties, wxSizerFlags().Center().Border(wxALL, 5));
309 
310     // Wrapup panel
311     m_pWrapUp = new wxFlexGridSizer(2, 2, 5, 15);
312     m_pProfileName = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(250,-1));
313     m_pLaunchDarks = new wxCheckBox(this, wxID_ANY, _("Build dark library"));
314     m_pLaunchDarks->SetValue(m_launchDarks);
315     m_pLaunchDarks->SetToolTip(_("Check this to automatically start the process of building a dark library for this profile."));
316     m_pAutoRestore = new wxCheckBox(this, wxID_ANY, _("Auto restore calibration"));
317     m_pAutoRestore->SetValue(m_autoRestore);
318     m_pAutoRestore->SetToolTip(_("Check this to automatically re-use the last calibration when the profile is loaded. "
319         "For this to work, the rotational orientation of the guide camera and all other optical properties of the guiding setup must remain the same between imaging sessions."));
320     AddTableEntryPair(this, m_pWrapUp, _("Profile Name"), m_pProfileName);
321     m_pWrapUp->Add(m_pLaunchDarks, wxSizerFlags().Border(wxTOP, 5).Border(wxLEFT, 10));
322     m_pWrapUp->Add(m_pAutoRestore, wxSizerFlags().Align(wxALIGN_RIGHT));
323     m_pvSizer->Add(m_pWrapUp, wxSizerFlags().Border(wxALL, 10).Expand().Center());
324 
325     // Row of buttons for prev, help, next
326     wxBoxSizer *pButtonSizer = new wxBoxSizer(wxHORIZONTAL);
327     m_pPrevBtn = new wxButton(this, ID_PREV, _("< Back"));
328     m_pPrevBtn->SetToolTip(_("Back up to the previous screen"));
329 
330     wxButton* helpBtn = new wxButton(this, ID_HELP, _("Help"));
331 
332     m_pNextBtn = new wxButton(this, ID_NEXT, _("Next >"));
333     m_pNextBtn->SetToolTip(_("Move forward to next screen"));
334 
335     pButtonSizer->AddStretchSpacer();
336     pButtonSizer->Add(
337         m_pPrevBtn,
338         wxSizerFlags(0).Align(0).Border(wxALL, 5));
339     pButtonSizer->Add(
340         helpBtn,
341         wxSizerFlags(0).Align(0).Border(wxALL, 5));
342     pButtonSizer->Add(
343         m_pNextBtn,
344         wxSizerFlags(0).Align(0).Border(wxALL, 5));
345     m_pvSizer->Add(pButtonSizer, wxSizerFlags().Expand().Border(wxALL, 10));
346 
347     m_pvSizer->Add(m_pStatusBar, 0, wxGROW);
348 
349     SetAutoLayout(true);
350     SetSizerAndFit(m_pvSizer);
351     CentreOnScreen();
352 
353     // Special cases - neither AuxMount nor AO requires an explicit user choice
354     m_SelectedAuxMount = _("None");
355     m_SelectedAO = _("None");
356     if (showGreeting)
357         m_State = STATE_GREETINGS;
358     else
359         m_State = STATE_CAMERA;
360     UpdateState(0);
361 }
362 
~ProfileWizard(void)363 ProfileWizard::~ProfileWizard(void)
364 {
365     delete m_bitmaps[STATE_GREETINGS];
366     delete m_bitmaps[STATE_CAMERA];
367     delete m_bitmaps[STATE_MOUNT];
368     delete m_bitmaps[STATE_AUXMOUNT];
369     delete m_bitmaps[STATE_AO];
370     delete m_bitmaps[STATE_WRAPUP];
371 }
372 
373 // Build verbose help strings based on dialog state
ShowHelp(DialogState state)374 void ProfileWizard::ShowHelp(DialogState state)
375 {
376     wxString hText;
377 
378     switch (m_State)
379     {
380     case STATE_GREETINGS:
381         hText = _("This short sequence of steps will help you identify the equipment you want to use for guiding and will associate it with a profile name of your choice. "
382             "This profile will then be available any time you run PHD2.  At a minimum, you will need to choose both the guide camera and the mount interface that PHD2 will use for guiding.  "
383             "You will also enter some information about the optical characteristics of your setup. "
384             "PHD2 will use this to create a good 'starter set' of guiding and calibration "
385             "parameters. If you are a new user, please review the 'Basic Use' section of the 'Help' guide after the wizard dialog has finished.");
386         break;
387     case STATE_CAMERA:
388         hText = _("Select your guide camera from the list.  All cameras supported by PHD2 and all installed ASCOM cameras are shown. If your camera is not shown, "
389             "it is either not supported by PHD2 or its camera driver is not installed. "
390             " PHD2 needs to know the camera pixel size and guide scope focal length in order to compute reasonable guiding parameters. "
391             " When you choose a camera, you'll be given the option to connect to it immediately to get the pixel-size automatically. "
392             " You can also choose a binning-level if your camera supports binning." );
393         break;
394     case STATE_MOUNT:
395         hText = wxString::Format(_("Select your mount interface from the list.  This determines how PHD2 will send guide commands to the mount. For most modern "
396             "mounts, the ASCOM interface is a good choice if you are running MS Windows.  The other interfaces are available for "
397             "cases where ASCOM isn't available or isn't well supported by mount firmware.  If you know the mount guide speed, you can specify it "
398             " so PHD2 can calibrate more efficiently.  If you don't know the mount guide speed, you can just use the default value of %0.1fx.  When you choose a "
399             " mount, you'll usually be given the option to connect to it immediately so PHD2 can read the guide speed for you."), Scope::DEFAULT_MOUNT_GUIDE_SPEED );
400         break;
401     case STATE_AUXMOUNT:
402         if (m_SelectedCamera == _("Simulator"))
403         {
404             hText = _("The 'simulator' camera/mount interface doesn't provide pointing information, so PHD2 will not be able to automatically adjust "
405                 "guiding for side-of-pier and declination. You can enable these features by choosing an 'Aux Mount' connection that does provide pointing "
406                 "information.");
407         }
408         else
409         {
410             hText = _("The mount interface you chose in the previous step doesn't provide pointing information, so PHD2 will not be able to automatically adjust "
411                 "guiding for side-of-pier and declination. You can enable these features by choosing an 'Aux Mount' connection that does provide pointing "
412                 "information.  The Aux Mount interface will be used only for that purpose and not for sending guide commands.");
413         }
414         break;
415     case STATE_AO:
416         hText = _("If you have an adaptive optics (AO) device, you can select it here.  The AO device will be used for high speed, small guiding corrections, "
417             "while the mount interface you chose earlier will be used for larger ('bump') corrections. Calibration of both interfaces will be handled automatically.");
418         break;
419     case STATE_WRAPUP:
420         hText = _("Your profile is complete and ready to save.  Give it a name and, optionally, build a dark-frame library for it. This is strongly "
421             "recommended for best results. If your setup is stable from one night to the next, you can choose to automatically "
422             "re-use the last calibration when you load this profile. If you are new to PHD2 or encounter problems, please use the 'Help' function for assistance.");
423     case STATE_DONE:
424         break;
425     }
426 
427     // Need to do it this way to handle 125% font scaling in Windows accessibility
428     m_pHelpText = new wxStaticText(this, wxID_ANY, hText, wxDefaultPosition, wxSize(DialogWidth, -1));
429     m_pHelpText->Wrap(TextWrapPoint);
430     m_pHelpGroup->Clear(true);
431     m_pHelpGroup->Add(m_pHelpText, wxSizerFlags().Border(wxLEFT, 10).Border(wxBOTTOM, 10).Expand());
432     m_pHelpGroup->Layout();
433     SetSizerAndFit(m_pvSizer);
434 }
435 
ShowStatus(const wxString & msg,bool appending)436 void ProfileWizard::ShowStatus(const wxString& msg, bool appending)
437 {
438     if (appending)
439         m_pStatusBar->SetStatusText(m_pStatusBar->GetStatusText() + " " + msg);
440     else
441         m_pStatusBar->SetStatusText(msg);
442 }
443 enum ConfigSuggestionResults
444 {
445     eProceed,
446     eBack,
447     eDontAsk
448 };
449 enum ConfigWarningTypes
450 {
451     eNoPointingInfo,
452     eEQModMount
453     // Room for future warnings if needed
454 };
455 // Dialog for warning user about poor config choices
456 struct ConfigSuggestionDlg : public wxDialog
457 {
458     ConfigSuggestionDlg(ConfigWarningTypes Type, wxHyperlinkCtrl* m_EqLink);
459     ConfigSuggestionResults UserChoice;
460     void OnBack(wxCommandEvent& evt);
461     void OnProceed(wxCommandEvent& evt);
462     void OnDontAsk(wxCommandEvent& evt);
463     void OnURLClicked(wxHyperlinkEvent& event);
464 };
465 
ConfigSuggestionDlg(ConfigWarningTypes Type,wxHyperlinkCtrl * m_EqLink)466 ConfigSuggestionDlg::ConfigSuggestionDlg(ConfigWarningTypes Type, wxHyperlinkCtrl* m_EqLink) : wxDialog(pFrame, wxID_ANY, _("Configuration Suggestion"))
467 {
468     wxBoxSizer* vSizer = new wxBoxSizer(wxVERTICAL);
469     wxStaticText* explanation = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
470     wxStaticText* wikiLoc;
471     wxString msg;
472     if (Type == eNoPointingInfo)
473         msg = _("This configuration doesn't provide PHD2 with any information about the scope's pointing position.  This means you will need to recalibrate\n"
474         "whenever the scope is slewed, and some PHD2 features will be disabled.  You should choose an ASCOM or INDI mount connection\n"
475         "for either 'mount' or 'aux-mount' unless there are no drivers available for your mount.\n"
476         "Please review the Help guide on 'Equipment Connections' for more details.");
477     else if (Type == eEQModMount)
478     {
479         msg = wxString::Format(_("Please make sure the EQMOD ASCOM settings are configured for PHD2 according to this document: \n"), "");
480         wikiLoc = new wxStaticText (this, wxID_ANY, "https://github.com/OpenPHDGuiding/phd2/wiki/EQASCOM-Settings");
481         m_EqLink = new wxHyperlinkCtrl(this, wxID_ANY, _("Open EQMOD document..."), "https://github.com/OpenPHDGuiding/phd2/wiki/EQASCOM-Settings");
482         m_EqLink->Connect(wxEVT_COMMAND_HYPERLINK, wxHyperlinkEventHandler(ConfigSuggestionDlg::OnURLClicked), nullptr, this);
483     }
484 
485     explanation->SetLabelText(msg);
486 
487     wxButton* backBtn = new wxButton(this, wxID_ANY, _("Go Back"));
488     backBtn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ConfigSuggestionDlg::OnBack), NULL, this);
489     wxButton* proceedBtn = new wxButton(this, wxID_ANY, _("Proceed"));
490     proceedBtn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ConfigSuggestionDlg::OnProceed), NULL, this);
491     wxButton* dontAskBtn = new wxButton(this, wxID_ANY, _("Don't Ask"));
492     dontAskBtn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ConfigSuggestionDlg::OnDontAsk), NULL, this);
493 
494     wxBoxSizer* btnSizer = new wxBoxSizer(wxHORIZONTAL);
495     btnSizer->Add(backBtn, wxSizerFlags(0).Border(wxALL, 8));
496     btnSizer->Add(proceedBtn, wxSizerFlags(0).Border(wxALL, 8));
497     if (Type != eEQModMount)
498         btnSizer->Add(dontAskBtn, wxSizerFlags(0).Border(wxALL, 8));
499 
500     vSizer->Add(explanation, wxSizerFlags(0).Border(wxALL, 8).Center());
501     if (Type == eEQModMount)
502     {
503         vSizer->AddSpacer(10);
504         vSizer->Add(wikiLoc, wxSizerFlags(0).Center());
505         vSizer->AddSpacer(10);
506         vSizer->Add(m_EqLink, wxSizerFlags(0).Center());
507         vSizer->AddSpacer(20);
508         dontAskBtn->Enable(false);
509     }
510     vSizer->Add(btnSizer, wxSizerFlags(0).Border(wxALL, 8).Center());
511 
512     SetAutoLayout(true);
513     SetSizerAndFit(vSizer);
514 }
515 
OnURLClicked(wxHyperlinkEvent & event)516 void ConfigSuggestionDlg::OnURLClicked(wxHyperlinkEvent& event)
517 {
518     event.Skip();
519 }
OnProceed(wxCommandEvent & evt)520 void ConfigSuggestionDlg::OnProceed(wxCommandEvent& evt)
521 {
522     UserChoice = eProceed;
523     EndDialog(wxOK);
524 }
OnBack(wxCommandEvent & evt)525 void ConfigSuggestionDlg::OnBack(wxCommandEvent& evt)
526 {
527     UserChoice = eBack;
528     EndDialog(wxCANCEL);
529 }
OnDontAsk(wxCommandEvent & evt)530 void ConfigSuggestionDlg::OnDontAsk(wxCommandEvent& evt)
531 {
532     UserChoice = eDontAsk;
533     EndDialog(wxOK);
534 }
535 
ProfWizWarningKey(ConfigWarningTypes Type)536 static wxString ProfWizWarningKey(ConfigWarningTypes Type)
537 {
538     wxString which;
539     if (Type == eNoPointingInfo)
540         which = wxString("NoPointingInfo");
541     return wxString::Format("/Confirm/%d/ProfileWizWarning_%s", pConfig->GetCurrentProfileId(), which);
542 }
543 
WarningAllowed(ConfigWarningTypes Type)544 static bool WarningAllowed(ConfigWarningTypes Type)
545 {
546     bool rslt = pConfig->Global.GetBoolean(ProfWizWarningKey(Type), true);
547     return rslt;
548 }
BlockWarning(ConfigWarningTypes Type)549 static void BlockWarning(ConfigWarningTypes Type)
550 {
551     pConfig->Global.SetBoolean(ProfWizWarningKey(Type), false);
552 }
553 
554 // Do semantic checks for 'next' commands
SemanticCheck(DialogState state,int change)555 bool ProfileWizard::SemanticCheck(DialogState state, int change)
556 {
557     bool bOk = true;            // Only 'next' commands could have problems
558     if (change > 0)
559     {
560         switch (state)
561         {
562         case STATE_GREETINGS:
563             break;
564         case STATE_CAMERA:
565             bOk = (m_SelectedCamera.length() > 0 && m_PixelSize > 0 && m_FocalLength > 0 && m_SelectedCamera != _("None"));
566             if (!bOk)
567                 ShowStatus(_("Please specify camera type, guider focal length, and guide camera pixel size"));
568             break;
569         case STATE_MOUNT:
570             bOk = (m_SelectedMount.Length() > 0 && m_SelectedMount != _("None"));
571             if (bOk)
572             {
573                 // Check for absence of pointing info
574                 if ((m_SelectedMount.Upper().Contains("EQMOD")))  //  && !m_PositionAware && WarningAllowed(eNoPointingInfo))
575                 {
576                     ConfigSuggestionDlg userAlert(eEQModMount, m_EqLink);
577                     int userRspns = userAlert.ShowModal();
578                     if (userRspns == wxOK)
579                     {
580                         // Could be either 'proceed' or 'dontAsk'
581                         if (userAlert.UserChoice == eDontAsk)
582                         {
583                             BlockWarning(eNoPointingInfo);
584                         }
585                         bOk = true;
586                     }
587                     else
588                         bOk = false;
589                     this->SetFocus();
590                 }
591             }
592             else
593                 ShowStatus(_("Please select a mount type to handle guider commands"));
594             break;
595         case STATE_AUXMOUNT:
596             {
597             // Check for absence of pointing info
598             if (m_SelectedAuxMount == _("None") && !m_PositionAware && WarningAllowed(eNoPointingInfo))
599                 {
600                     ConfigSuggestionDlg userAlert(eNoPointingInfo, m_EqLink);
601                     int userRspns = userAlert.ShowModal();
602                     if (userRspns == wxOK)
603                     {
604                         // Could be either 'proceed' or 'dontAsk'
605                         if (userAlert.UserChoice == eDontAsk)
606                         {
607                             BlockWarning(eNoPointingInfo);
608                         }
609                         bOk = true;
610                     }
611                     else
612                         bOk = false;
613                 }
614             }
615             break;
616         case STATE_AO:
617             break;
618         case STATE_WRAPUP:
619             m_ProfileName = m_pProfileName->GetValue();
620             bOk = m_ProfileName.length() > 0;
621             if (!bOk)
622                 ShowStatus(_("Please specify a name for the profile."));
623             if (pConfig->GetProfileId(m_ProfileName) > 0)
624             {
625                 bOk = false;
626                 ShowStatus(_("There is already a profile with that name. Please choose a different name."));
627             }
628             break;
629         case STATE_DONE:
630             break;
631         }
632     }
633 
634     return bOk;
635 }
636 
RangeCheck(int thisval)637 static int RangeCheck(int thisval)
638 {
639     return wxMin(wxMax(thisval, 0), (int) ProfileWizard::STATE_DONE);
640 }
641 
642 // State machine manager.  Layout and content of dialog panel will be changed here based on state.
UpdateState(const int change)643 void ProfileWizard::UpdateState(const int change)
644 {
645     ShowStatus(wxEmptyString);
646     if (SemanticCheck(m_State, change))
647     {
648         m_State = (DialogState) RangeCheck(((int)m_State + change));
649 
650         if (m_State >= 0 && m_State < NUM_PAGES)
651         {
652             const wxBitmap& bmp = *m_bitmaps[m_State];
653             m_bitmap->SetSize(bmp.GetSize());
654             m_bitmap->SetBitmap(bmp);
655         }
656 
657         switch (m_State)
658         {
659         case STATE_GREETINGS:
660             SetTitle(TitlePrefix + _("Introduction"));
661             m_pPrevBtn->Enable(false);
662             m_pGearLabel->Show(false);
663             m_pGearChoice->Show(false);
664             m_pUserProperties->Show(false);
665             m_pMountProperties->Show(false);
666             m_pWrapUp->Show(false);
667             m_pInstructions->SetLabel(_("Welcome to the PHD2 'first light' wizard"));
668             m_pHelpText->SetSizeHints(wxSize(-1, TallHelpHeight));
669             SetSizerAndFit(m_pvSizer);
670             break;
671         case STATE_CAMERA:
672             SetTitle(TitlePrefix + _("Choose a Guide Camera"));
673             m_pPrevBtn->Enable(true);
674             m_pGearLabel->SetLabel(_("Guide Camera:"));
675             m_pGearChoice->Clear();
676             m_pGearChoice->Append(GuideCamera::GuideCameraList());
677             if (m_SelectedCamera.length() > 0)
678                 m_pGearChoice->SetStringSelection(m_SelectedCamera);
679             m_pGearLabel->Show(true);
680             m_pGearChoice->Show(true);
681             m_pUserProperties->Show(true);
682             m_pMountProperties->Show(false);
683             m_pWrapUp->Show(false);
684             m_pHelpText->SetSizeHints(wxSize(-1, NormalHelpHeight));
685             SetSizerAndFit(m_pvSizer);
686             m_pInstructions->SetLabel(_("Select your guide camera and specify the optical properties of your guiding setup"));
687             m_pInstructions->Wrap(TextWrapPoint);
688             break;
689         case STATE_MOUNT:
690             if (m_SelectedCamera == _("Simulator"))
691             {
692                 m_pMountProperties->Show(false);
693                 m_pUserProperties->Show(false);
694                 m_SelectedMount = _("On-camera");
695                 m_PositionAware = false;
696                 UpdateState(change);
697             }
698             else
699             {
700                 SetTitle(TitlePrefix + _("Choose a Mount Connection"));
701                 m_pPrevBtn->Enable(true);
702                 m_pGearLabel->SetLabel(_("Mount:"));
703                 m_pGearChoice->Clear();
704                 m_pGearChoice->Append(Scope::MountList());
705                 if (m_SelectedMount.length() > 0)
706                     m_pGearChoice->SetStringSelection(m_SelectedMount);
707                 m_pUserProperties->Show(false);
708                 m_pMountProperties->Show(true);
709                 m_pInstructions->SetLabel(_("Select your mount connection - this will determine how guide signals are transmitted"));
710             }
711             break;
712         case STATE_AUXMOUNT:
713             m_pMountProperties->Show(false);
714             if (m_PositionAware)                        // Skip this state if the selected mount is already position aware
715             {
716                 UpdateState(change);
717             }
718             else
719             {
720                 SetTitle(TitlePrefix + _("Choose an Auxiliary Mount Connection (optional)"));
721                 m_pGearLabel->SetLabel(_("Aux Mount:"));
722                 m_pGearChoice->Clear();
723                 m_pGearChoice->Append(Scope::AuxMountList());
724                 m_pGearChoice->SetStringSelection(m_SelectedAuxMount);      // SelectedAuxMount is never null
725                 m_pInstructions->SetLabel(_("Since your primary mount connection does not report pointing position, you may want to choose an 'Aux Mount' connection"));
726             }
727             break;
728         case STATE_AO:
729             SetTitle(TitlePrefix + _("Choose an Adaptive Optics Device (optional)"));
730             m_pGearLabel->SetLabel(_("AO:"));
731             m_pGearChoice->Clear();
732             m_pGearChoice->Append(StepGuider::AOList());
733             m_pGearChoice->SetStringSelection(m_SelectedAO);            // SelectedAO is never null
734             m_pInstructions->SetLabel(_("Specify your adaptive optics device if desired"));
735             if (change == -1)                   // User is backing up in wizard dialog
736             {
737                 // Assert UI state for gear selection
738                 m_pGearGrid->Show(true);
739                 m_pNextBtn->SetLabel(_("Next >"));
740                 m_pNextBtn->SetToolTip(_("Move forward to next screen"));
741                 m_pWrapUp->Show(false);
742             }
743             break;
744         case STATE_WRAPUP:
745             SetTitle(TitlePrefix + _("Finish Creating Your New Profile"));
746             m_pGearGrid->Show(false);
747             m_pWrapUp->Show(true);
748             m_pNextBtn->SetLabel(_("Finish"));
749             m_pNextBtn->SetToolTip(_("Finish creating the equipment profile"));
750             m_pLaunchDarks->SetValue(m_useCamera || m_pLaunchDarks);
751             m_pInstructions->SetLabel(_("Enter a name for your profile and optionally launch the process to build a dark library"));
752             m_pAutoRestore->Show((m_PositionAware || m_SelectedAuxMount != _("None")));
753             m_pAutoRestore->SetValue(m_autoRestore);
754             SetSizerAndFit(m_pvSizer);
755             break;
756         case STATE_DONE:
757             WrapUp();
758             break;
759         }
760     }
761 
762     ShowHelp(m_State);
763 }
764 
GetCalibrationStepSize(int focalLength,double pixelSize,double guideSpeed,int binning,int distance)765 static int GetCalibrationStepSize(int focalLength, double pixelSize, double guideSpeed, int binning, int distance)
766 {
767     int calibrationStep;
768     double const declination = 0.0;
769     CalstepDialog::GetCalibrationStepSize(focalLength, pixelSize, binning, guideSpeed,
770         CalstepDialog::DEFAULT_STEPS, declination, distance, 0, &calibrationStep);
771     return calibrationStep;
772 }
773 
774 // Set up some reasonable starting guiding parameters
SetGuideAlgoParams(double pixelSize,int focalLength,int binning,bool highResEncoders)775 static void SetGuideAlgoParams(double pixelSize, int focalLength, int binning, bool highResEncoders)
776 {
777     double minMove = GuideAlgorithm::SmartDefaultMinMove(focalLength, pixelSize, binning);
778 
779     // Typically Min moves for hysteresis guiding in RA and resist switch in Dec, but Dec Lowpass2 for mounts with high-end encoders
780     pConfig->Profile.SetDouble("/scope/GuideAlgorithm/X/Hysteresis/minMove", minMove);
781     if (!highResEncoders)
782         pConfig->Profile.SetDouble("/scope/GuideAlgorithm/Y/ResistSwitch/minMove", minMove);
783     else
784         pConfig->Profile.SetDouble("/scope/GuideAlgorithm/Y/Lowpass2/minMove", minMove);
785 }
786 
787 struct AutoConnectCamera
788 {
789     GuideCamera *m_camera;
790 
AutoConnectCameraAutoConnectCamera791     AutoConnectCamera(wxWindow *parent, const wxString& selection)
792     {
793         m_camera = GuideCamera::Factory(selection);
794         pFrame->ClearAlert();
795 
796         if (m_camera)
797         {
798             wxBusyCursor busy;
799             m_camera->Connect(GuideCamera::DEFAULT_CAMERA_ID);
800             pFrame->ClearAlert();
801         }
802 
803         if (!m_camera || !m_camera->Connected)
804         {
805             wxMessageBox(_("PHD2 could not connect to the camera so you may want to deal with that later. "
806                 "In the meantime, you can just enter the pixel-size manually along with the "
807                 "focal length and binning levels."));
808 
809             delete m_camera;
810             m_camera = nullptr;
811         }
812 
813         parent->SetFocus();    // In case driver messages might have caused us to lose it
814     }
815 
~AutoConnectCameraAutoConnectCamera816     ~AutoConnectCamera()
817     {
818         if (m_camera)
819         {
820             if (m_camera->Connected)
821                 m_camera->Disconnect();
822             delete m_camera;
823         }
824     }
825 
operator GuideCamera*AutoConnectCamera826     operator GuideCamera*() const  { return m_camera; }
operator ->AutoConnectCamera827     GuideCamera *operator->() const { return m_camera; }
828 };
829 
SetBinningLevel(wxWindow * parent,const wxString & selection,int val)830 static void SetBinningLevel(wxWindow *parent, const wxString& selection, int val)
831 {
832     AutoConnectCamera cam(parent, selection);
833     if (cam && cam->MaxBinning > 1)
834         cam->SetBinning(val);
835 }
836 
837 // Wrapup logic - build the new profile, maybe launch the darks dialog
WrapUp()838 void ProfileWizard::WrapUp()
839 {
840     m_launchDarks = m_pLaunchDarks->GetValue();
841     m_autoRestore = m_pAutoRestore->GetValue();
842 
843     int binning = m_pBinningLevel->GetSelection() + 1;
844     if (m_useCamera)
845         SetBinningLevel(this, m_SelectedCamera, binning);
846 
847     int calibrationDistance = CalstepDialog::GetCalibrationDistance(m_FocalLength, m_PixelSize, binning);
848     int calibrationStepSize = GetCalibrationStepSize(m_FocalLength, m_PixelSize, m_GuideSpeed, binning, calibrationDistance);
849 
850     Debug.Write(wxString::Format("Profile Wiz: Name=%s, Camera=%s, Mount=%s, High-res encoders=%s, AuxMount=%s, "
851         "AO=%s, PixelSize=%0.1f, FocalLength=%d, CalStep=%d, CalDist=%d, LaunchDarks=%d\n",
852         m_ProfileName, m_SelectedCamera, m_SelectedMount, m_pDecEncoder->GetValue() ? "True" : "False", m_SelectedAuxMount, m_SelectedAO,
853         m_PixelSize, m_FocalLength, calibrationStepSize, calibrationDistance, m_launchDarks));
854 
855     // create the new profile
856     if (!m_profile.Commit(m_ProfileName))
857     {
858         ShowStatus(wxString::Format(_("Could not create profile %s"), m_ProfileName));
859         return;
860     }
861 
862     // populate the profile. The caller will load the profile.
863     pConfig->Profile.SetString("/camera/LastMenuchoice", m_SelectedCamera);
864     pConfig->Profile.SetString("/scope/LastMenuChoice", m_SelectedMount);
865     pConfig->Profile.SetString("/scope/LastAuxMenuChoice", m_SelectedAuxMount);
866     pConfig->Profile.SetString("/stepguider/LastMenuChoice", m_SelectedAO);
867     pConfig->Profile.SetInt("/frame/focalLength", m_FocalLength);
868     pConfig->Profile.SetDouble("/camera/pixelsize", m_PixelSize);
869     pConfig->Profile.SetInt("/camera/binning", binning);
870     pConfig->Profile.SetInt("/scope/CalibrationDuration", calibrationStepSize);
871     pConfig->Profile.SetInt("/scope/CalibrationDistance", calibrationDistance);
872     bool highResEncoders = m_pDecEncoder->GetValue();
873     pConfig->Profile.SetBoolean("/scope/HiResEncoders", highResEncoders);
874     if (highResEncoders)
875         pConfig->Profile.SetInt("/scope/YGuideAlgorithm", GUIDE_ALGORITHM_LOWPASS2);
876     pConfig->Profile.SetDouble("/CalStepCalc/GuideSpeed", m_GuideSpeed);
877     pConfig->Profile.SetBoolean("/AutoLoadCalibration", m_autoRestore);
878 
879     GuideLog.EnableLogging(true);       // Especially for newbies
880 
881     // Construct a good baseline set of guiding parameters based on image scale
882     SetGuideAlgoParams(m_PixelSize, m_FocalLength, binning, m_pDecEncoder->GetValue());
883 
884     EndModal(wxOK);
885 }
886 
887 class ConnectDialog : public wxDialog
888 {
889     wxStaticText *m_Instructions;
890     ProfileWizard* m_Parent;
891 
892 public:
893     ConnectDialog(ProfileWizard *parent, ProfileWizard::DialogState currState);
894 
895     void OnYesButton(wxCommandEvent& evt);
896     void OnNoButton(wxCommandEvent& evt);
897     void OnCancelButton(wxCommandEvent& evt);
898 };
899 
900 // Event handlers below
OnGearChoice(wxCommandEvent & evt)901 void ProfileWizard::OnGearChoice(wxCommandEvent& evt)
902 {
903     switch (m_State)
904     {
905     case STATE_CAMERA: {
906         wxString prevSelection = m_SelectedCamera;
907         m_SelectedCamera = m_pGearChoice->GetStringSelection();
908         bool camNone = (m_SelectedCamera == _("None"));
909         if (m_SelectedCamera != prevSelection && !camNone)
910         {
911             ConnectDialog cnDlg(this, STATE_CAMERA);
912             int answer = cnDlg.ShowModal();
913             if (answer == wxYES)
914             {
915                 m_useCamera = true;
916             }
917             else if (answer == wxNO)
918             {
919                 m_useCamera = false;
920             }
921             else if (answer == wxCANCEL)
922             {
923                 m_SelectedCamera = _("None");
924                 UpdateState(0);
925                 return;
926             }
927         }
928         if (m_SelectedCamera != prevSelection)
929             InitCameraProps(m_useCamera && !camNone);
930         break;
931     }
932 
933     case STATE_MOUNT: {
934         wxString prevSelection = m_SelectedMount;
935         m_SelectedMount = m_pGearChoice->GetStringSelection();
936         std::unique_ptr<Scope> scope(Scope::Factory(m_SelectedMount));
937         m_PositionAware = scope && scope->CanReportPosition();
938         if (m_PositionAware)
939         {
940             if (prevSelection != m_SelectedMount)
941             {
942                 ConnectDialog cnDlg(this, STATE_MOUNT);
943                 int answer = cnDlg.ShowModal();
944                 if (answer == wxYES)
945                 {
946                     m_useMount = true;
947                 }
948                 else if (answer == wxNO)
949                 {
950                     m_useMount = false;
951                 }
952                 else if (answer == wxCANCEL)
953                 {
954                     m_SelectedMount = _("None");
955                     UpdateState(0);
956                     return;
957                 }
958             }
959             m_SelectedAuxMount = _("None");
960             if (prevSelection != m_SelectedMount)
961             {
962                 if (m_useMount)
963                     InitMountProps(scope.get());
964                 else
965                     InitMountProps(nullptr);
966             }
967         }
968         else
969         {
970             if (prevSelection != m_SelectedMount)
971                 InitMountProps(nullptr);
972         }
973         break;
974     }
975 
976     case STATE_AUXMOUNT: {
977         ShowStatus(wxEmptyString);
978         wxString prevSelection = m_SelectedAuxMount;
979         m_SelectedAuxMount = m_pGearChoice->GetStringSelection();
980         std::unique_ptr<Scope> scope(Scope::Factory(m_SelectedAuxMount));
981         // Handle setting of guide speed behind the scenes using aux-mount
982         if (prevSelection != m_SelectedAuxMount)
983         {
984             if (m_SelectedAuxMount != _("None") && !m_SelectedAuxMount.Contains(_("Ask")))
985             {
986                 ConnectDialog cnDlg(this, STATE_AUXMOUNT);
987                 int answer = cnDlg.ShowModal();
988                 if (answer == wxYES)
989                 {
990                     m_useAuxMount = true;
991                 }
992                 else if (answer == wxNO)
993                 {
994                     m_useAuxMount = false;
995                 }
996                 else if (answer == wxCANCEL)
997                 {
998                     m_SelectedAuxMount = _("None");
999                     UpdateState(0);
1000                     return;
1001                 }
1002             }
1003             else
1004                 m_useAuxMount = false;
1005         }
1006 
1007         if (prevSelection != m_SelectedAuxMount)
1008         {
1009             if (m_useAuxMount)
1010             {
1011                 double oldGuideSpeed = m_pGuideSpeed->GetValue();
1012                 InitMountProps(scope.get());
1013                 if (oldGuideSpeed != m_pGuideSpeed->GetValue())
1014                     ShowStatus(wxString::Format(_("Guide speed setting adjusted from %0.1f to %0.1fx"), oldGuideSpeed, m_pGuideSpeed->GetValue()));
1015             }
1016             else
1017                 InitMountProps(nullptr);
1018         }
1019         break;
1020     }
1021 
1022     case STATE_AO:
1023         m_SelectedAO = m_pGearChoice->GetStringSelection();
1024         break;
1025     case STATE_GREETINGS:
1026     case STATE_WRAPUP:
1027     case STATE_DONE:
1028         break;
1029     }
1030 }
1031 
GetPixelSize(GuideCamera * cam)1032 static double GetPixelSize(GuideCamera *cam)
1033 {
1034     double rslt;
1035     if (cam->GetDevicePixelSize(&rslt))
1036     {
1037         wxMessageBox(_("This camera driver doesn't report the pixel size, so you'll need to enter the value manually"));
1038         rslt = 0.;
1039     }
1040     return rslt;
1041 }
1042 
InitCameraProps(bool tryConnect)1043 void ProfileWizard::InitCameraProps(bool tryConnect)
1044 {
1045     if (tryConnect)
1046     {
1047         // Pixel size
1048         double pxSz = 0.;
1049         AutoConnectCamera cam(this, m_SelectedCamera);
1050         if (cam)
1051             pxSz = GetPixelSize(cam);
1052         m_pPixelSize->SetValue(pxSz);           // Might be zero if driver doesn't report it
1053         m_pPixelSize->Enable(pxSz == 0);
1054         wxSpinDoubleEvent dummy;
1055         OnPixelSizeChange(dummy);
1056         // Binning
1057         wxArrayString opts;
1058         if (cam)
1059             cam->GetBinningOpts(&opts);
1060         else
1061             GuideCamera::GetBinningOpts(4, &opts);
1062         m_pBinningLevel->Set(opts);
1063         m_pBinningLevel->SetSelection(0);
1064     }
1065     else
1066     {
1067         wxArrayString opts;
1068         GuideCamera::GetBinningOpts(4, &opts);
1069         m_pBinningLevel->Set(opts);
1070         m_pBinningLevel->SetSelection(0);
1071         m_pPixelSize->SetValue(0.);
1072         m_pPixelSize->Enable(true);
1073         wxSpinDoubleEvent dummy;
1074         OnPixelSizeChange(dummy);
1075     }
1076 }
1077 
InitMountProps(Scope * theScope)1078 void ProfileWizard::InitMountProps(Scope* theScope)
1079 {
1080     double raSpeed;
1081     double decSpeed;
1082     double speedVal;
1083     const double siderealSecondPerSec = 0.9973;
1084     bool err;
1085 
1086     if (theScope)
1087     {
1088         ShowStatus(_("Connecting to mount..."));
1089         err = theScope->Connect();
1090         ShowStatus(wxEmptyString);
1091         if (err)
1092         {
1093             wxMessageBox(
1094                 wxString::Format(_("PHD2 could not connect to the mount, so you'll probably want to deal with that later.  "
1095                 "In the meantime, if you know the mount guide speed setting, you can enter it manually. "
1096                 " Otherwise, you can just leave it at the default value of %0.1fx"), Scope::DEFAULT_MOUNT_GUIDE_SPEED));
1097             speedVal = Scope::DEFAULT_MOUNT_GUIDE_SPEED;
1098         }
1099         else
1100         {
1101             // GetGuideRates handles exceptions thrown from driver, just returns a bool error
1102             if (!theScope->GetGuideRates(&raSpeed, &decSpeed))
1103                 speedVal = wxMax(raSpeed, decSpeed) * 3600.0 / (15.0 * siderealSecondPerSec);  // deg/sec -> sidereal multiple
1104             else
1105             {
1106                 wxMessageBox(
1107                     wxString::Format(_("Apparently, this mount driver doesn't report guide speeds.  If you know the mount guide speed setting, you can enter it manually. "
1108                     "Otherwise, you can just leave it at the default value of %0.1fx"), Scope::DEFAULT_MOUNT_GUIDE_SPEED));
1109                 speedVal = Scope::DEFAULT_MOUNT_GUIDE_SPEED;
1110             }
1111         }
1112     }
1113     else
1114         speedVal = Scope::DEFAULT_MOUNT_GUIDE_SPEED;
1115     m_pGuideSpeed->SetValue(speedVal);
1116     wxSpinDoubleEvent dummy;
1117     OnGuideSpeedChange(dummy);
1118     this->SetFocus();
1119 }
1120 
OnPixelSizeChange(wxSpinDoubleEvent & evt)1121 void ProfileWizard::OnPixelSizeChange(wxSpinDoubleEvent& evt)
1122 {
1123     m_PixelSize = m_pPixelSize->GetValue();
1124     UpdatePixelScale();
1125 }
1126 
OnFocalLengthChange(wxSpinDoubleEvent & evt)1127 void ProfileWizard::OnFocalLengthChange(wxSpinDoubleEvent& evt)
1128 {
1129     m_FocalLength = (int) m_pFocalLength->GetValue();
1130     m_pFocalLength->SetValue(m_FocalLength);                        // Rounding
1131     UpdatePixelScale();
1132 }
1133 
OnFocalLengthText(wxCommandEvent & evt)1134 void ProfileWizard::OnFocalLengthText(wxCommandEvent& evt)
1135 {
1136     unsigned long val;
1137     if (evt.GetString().ToULong(&val) &&
1138         val >= AdvancedDialog::MIN_FOCAL_LENGTH &&
1139         val <= AdvancedDialog::MAX_FOCAL_LENGTH)
1140     {
1141         m_FocalLength = val;
1142         UpdatePixelScale();
1143     }
1144 }
1145 
OnBinningChange(wxCommandEvent & evt)1146 void ProfileWizard::OnBinningChange(wxCommandEvent& evt)
1147 {
1148     UpdatePixelScale();
1149 }
1150 
round2(double x)1151 inline static double round2(double x)
1152 {
1153     // round x to 2 decimal places
1154     return floor(x * 100. + 0.5) / 100.;
1155 }
1156 
UpdatePixelScale()1157 void ProfileWizard::UpdatePixelScale()
1158 {
1159     int binning = m_pBinningLevel->GetSelection() + 1;
1160 
1161     double scale = 0.0;
1162     if (m_FocalLength > 0)
1163     {
1164         scale = MyFrame::GetPixelScale(m_PixelSize, m_FocalLength, binning);
1165         m_pixelScale->SetLabel(wxString::Format(_("Pixel scale: %8.2f\"/px"), scale));
1166     }
1167     else
1168         m_pixelScale->SetLabel(wxEmptyString);
1169 
1170     static const double MIN_SCALE = 0.50;
1171     if (scale != 0.0 && round2(scale) < MIN_SCALE)
1172     {
1173         if (!m_scaleIcon->GetClientData())
1174         {
1175             m_scaleIcon->SetClientData((void *)-1); // so we only do this once
1176 #   include "icons/alert24.png.h"
1177             wxBitmap alert(wxBITMAP_PNG_FROM_DATA(alert24));
1178             m_scaleIcon->SetBitmap(alert);
1179             m_scaleIcon->SetToolTip(_("Guide star identification works best when the pixel scale is above 0.5\"/px. "
1180                 "Select binning level 2 to increase the pixel scale."));
1181             m_scaleIcon->Hide();
1182         }
1183         if (!m_scaleIcon->IsShown())
1184         {
1185             m_scaleIcon->ShowWithEffect(wxSHOW_EFFECT_BLEND, 2000);
1186             ShowStatus(_("Low pixel scale"));
1187         }
1188     }
1189     else
1190     {
1191         if (m_scaleIcon->IsShown())
1192         {
1193             m_scaleIcon->Hide();
1194             ShowStatus(wxEmptyString);
1195         }
1196     }
1197 }
1198 
OnGuideSpeedChange(wxSpinDoubleEvent & evt)1199 void ProfileWizard::OnGuideSpeedChange(wxSpinDoubleEvent& evt)
1200 {
1201     m_GuideSpeed = m_pGuideSpeed->GetValue();
1202 }
1203 
OnNext(wxCommandEvent & evt)1204 void ProfileWizard::OnNext(wxCommandEvent& evt)
1205 {
1206     UpdateState(1);
1207 }
1208 
OnContextHelp(wxCommandEvent & evt)1209 void ProfileWizard::OnContextHelp(wxCommandEvent& evt)
1210 {
1211     pFrame->help->Display("Basic_use.htm#New_profile_wizard");
1212 }
1213 
OnPrev(wxCommandEvent & evt)1214 void ProfileWizard::OnPrev(wxCommandEvent& evt)
1215 {
1216     if (m_State == ProfileWizard::STATE_WRAPUP)             // Special handling for basic controls with no event-handlers
1217     {
1218         m_autoRestore = m_pAutoRestore->GetValue();
1219         m_launchDarks = m_pLaunchDarks->GetValue();
1220     }
1221     UpdateState(-1);
1222 }
1223 
1224 
1225 // Supporting dialog classes
ConnectDialog(ProfileWizard * parent,ProfileWizard::DialogState currState)1226 ConnectDialog::ConnectDialog(ProfileWizard* parent, ProfileWizard::DialogState currState) :
1227 wxDialog(parent, wxID_ANY, _("Ask About Connection"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
1228 {
1229     static const int DialogWidth = 425;
1230     static const int TextWrapPoint = 400;
1231 
1232     m_Parent = parent;
1233     wxBoxSizer* vSizer = new wxBoxSizer(wxVERTICAL);
1234     // Expanded explanations
1235     m_Instructions = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(DialogWidth, 95), wxALIGN_LEFT | wxST_NO_AUTORESIZE);
1236     switch (currState)
1237     {
1238     case ProfileWizard::STATE_CAMERA:
1239         m_Instructions->SetLabelText(
1240             _("Is the camera already connected to the PC?   If so, PHD2 can usually determine the camera pixel-size automatically. "
1241             " If the camera isn't connected or its driver doesn't report the pixel-size, you can enter the value yourself using information in the camera manual or online. ")
1242             );
1243         this->SetTitle(_("Camera Already Connected?"));
1244         break;
1245     case ProfileWizard::STATE_MOUNT:
1246         m_Instructions->SetLabelText(
1247             wxString::Format(_("Is the mount already connected and set up to communicate with PHD2?  If so, PHD2 can determine the mount guide speed automatically. "
1248             " If not, you can enter the guide-speed manually.  If you don't know what it is, just leave the setting at the default value of %0.1fx."), Scope::DEFAULT_MOUNT_GUIDE_SPEED)
1249             );
1250         this->SetTitle(_("Mount Already Connected?"));
1251         break;
1252     case ProfileWizard::STATE_AUXMOUNT:
1253         m_Instructions->SetLabelText(
1254             wxString::Format(_("Is the aux-mount already connected and set up to communicate with PHD2?  If so, PHD2 can determine the mount guide speed automatically. "
1255             " If not, you can enter it manually.  If you don't know what it is, just leave the setting at the default value of %0.1fx. "
1256             " If the guide speed on the previous page doesn't match what is read from the mount, the mount value will be used."), Scope::DEFAULT_MOUNT_GUIDE_SPEED)
1257             );
1258         this->SetTitle(_("Aux-mount Already Connected?"));
1259         break;
1260     default:
1261         break;
1262     }
1263     m_Instructions->Wrap(TextWrapPoint);
1264 
1265     vSizer->Add(m_Instructions, wxSizerFlags().Border(wxALL, 10));
1266 
1267     // Buttons for yes, no, cancel
1268 
1269     wxBoxSizer *pButtonSizer = new wxBoxSizer(wxHORIZONTAL);
1270     wxButton* yesBtn = new wxButton(this, wxID_ANY, _("Yes"));
1271     yesBtn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ConnectDialog::OnYesButton), NULL, this);
1272     wxButton* noBtn = new wxButton(this, wxID_ANY, _("No"));
1273     noBtn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ConnectDialog::OnNoButton), NULL, this);
1274     wxButton* cancelBtn = new wxButton(this, wxID_ANY, _("Cancel"));
1275     cancelBtn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ConnectDialog::OnCancelButton), NULL, this);
1276 
1277     pButtonSizer->AddStretchSpacer();
1278     pButtonSizer->Add(
1279         yesBtn,
1280         wxSizerFlags(0).Align(0).Border(wxALL, 5));
1281     pButtonSizer->Add(
1282         noBtn,
1283         wxSizerFlags(0).Align(0).Border(wxALL, 5));
1284     pButtonSizer->Add(
1285         cancelBtn,
1286         wxSizerFlags(0).Align(0).Border(wxALL, 5));
1287     vSizer->Add(pButtonSizer, wxSizerFlags().Expand().Border(wxALL, 10));
1288 
1289     SetAutoLayout(true);
1290     SetSizerAndFit(vSizer);
1291 }
1292 
OnYesButton(wxCommandEvent & evt)1293 void ConnectDialog::OnYesButton(wxCommandEvent& evt)
1294 {
1295     EndModal(wxYES);
1296 }
OnNoButton(wxCommandEvent & evt)1297 void ConnectDialog::OnNoButton(wxCommandEvent& evt)
1298 {
1299     EndModal(wxNO);
1300 }
OnCancelButton(wxCommandEvent & evt)1301 void ConnectDialog::OnCancelButton(wxCommandEvent& evt)
1302 {
1303     EndModal(wxCANCEL);
1304 }
1305 
ShowModal(wxWindow * parent,bool showGreeting,bool * darks_requested)1306 bool EquipmentProfileWizard::ShowModal(wxWindow *parent, bool showGreeting, bool *darks_requested)
1307 {
1308     ProfileWizard wiz(parent, showGreeting);
1309     if (wiz.ShowModal() != wxOK)
1310         return false;
1311     *darks_requested = wiz.m_launchDarks;
1312     return true;
1313 }
1314