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