1 /*
2  *  myframe.cpp
3  *  PHD Guiding
4  *
5  *  Created by Craig Stark.
6  *  Copyright (c) 2006-2010 Craig Stark.
7  *  Refactored by Bret McKee
8  *  Copyright (c) 2012 Bret McKee
9  *  All rights reserved.
10  *
11  *  This source code is distributed under the following "BSD" license
12  *  Redistribution and use in source and binary forms, with or without
13  *  modification, are permitted provided that the following conditions are met:
14  *    Redistributions of source code must retain the above copyright notice,
15  *     this list of conditions and the following disclaimer.
16  *    Redistributions in binary form must reproduce the above copyright notice,
17  *     this list of conditions and the following disclaimer in the
18  *     documentation and/or other materials provided with the distribution.
19  *    Neither the name of Craig Stark, Stark Labs nor the names of its
20  *     contributors may be used to endorse or promote products derived from
21  *     this software without specific prior written permission.
22  *
23  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  *  POSSIBILITY OF SUCH DAMAGE.
34  *
35  */
36 
37 #include "phd.h"
38 
39 #include "aui_controls.h"
40 #include "comet_tool.h"
41 #include "config_indi.h"
42 #include "guiding_assistant.h"
43 #include "phdupdate.h"
44 #include "pierflip_tool.h"
45 #include "Refine_DefMap.h"
46 
47 #include <algorithm>
48 #include <memory>
49 
50 #include <wx/filesys.h>
51 #include <wx/fs_zip.h>
52 #include <wx/artprov.h>
53 #include <wx/dirdlg.h>
54 #include <wx/dnd.h>
55 #include <wx/textwrapper.h>
56 #include "wx/valnum.h"
57 
58 static const int DefaultNoiseReductionMethod = 0;
59 static const double DefaultDitherScaleFactor = 1.00;
60 static const bool DefaultDitherRaOnly = false;
61 static const DitherMode DefaultDitherMode = DITHER_RANDOM;
62 static const bool DefaultServerMode = true;
63 static const int DefaultTimelapse = 0;
64 static const int DefaultFocalLength = 0;
65 static const int DefaultExposureDuration = 1000;
66 static const int DefaultAutoExpMin = 1000;
67 static const int DefaultAutoExpMax = 5000;
68 static const double DefaultAutoExpSNR = 6.0;
69 
70 wxDEFINE_EVENT(REQUEST_EXPOSURE_EVENT, wxCommandEvent);
71 wxDEFINE_EVENT(REQUEST_MOUNT_MOVE_EVENT, wxCommandEvent);
72 wxDEFINE_EVENT(WXMESSAGEBOX_PROXY_EVENT, wxCommandEvent);
73 wxDEFINE_EVENT(STATUSBAR_ENQUEUE_EVENT, wxCommandEvent);
74 wxDEFINE_EVENT(STATUSBAR_TIMER_EVENT, wxTimerEvent);
75 wxDEFINE_EVENT(SET_STATUS_TEXT_EVENT, wxThreadEvent);
76 wxDEFINE_EVENT(ALERT_FROM_THREAD_EVENT, wxThreadEvent);
77 wxDEFINE_EVENT(RECONNECT_CAMERA_EVENT, wxThreadEvent);
78 wxDEFINE_EVENT(UPDATER_EVENT, wxThreadEvent);
79 
80 wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
81     EVT_MENU_HIGHLIGHT_ALL(MyFrame::OnMenuHighlight)
82     EVT_MENU_CLOSE(MyFrame::OnAnyMenuClose)
83     EVT_MENU(wxID_ANY, MyFrame::OnAnyMenu)
84     EVT_MENU(wxID_EXIT,  MyFrame::OnQuit)
85     EVT_MENU(wxID_ABOUT, MyFrame::OnAbout)
86     EVT_MENU(EEGG_RESTORECAL, MyFrame::OnEEGG)
87     EVT_MENU(EEGG_MANUALCAL, MyFrame::OnEEGG)
88     EVT_MENU(EEGG_CLEARCAL, MyFrame::OnEEGG)
89     EVT_MENU(EEGG_REVIEWCAL, MyFrame::OnEEGG)
90     EVT_MENU(EEGG_MANUALLOCK, MyFrame::OnEEGG)
91     EVT_MENU(EEGG_STICKY_LOCK, MyFrame::OnEEGG)
92     EVT_MENU(EEGG_FLIPCAL, MyFrame::OnEEGG)
93     EVT_MENU(MENU_DRIFTTOOL, MyFrame::OnDriftTool)
94     EVT_MENU(MENU_POLARDRIFTTOOL, MyFrame::OnPolarDriftTool)
95     EVT_MENU(MENU_STATICPATOOL, MyFrame::OnStaticPaTool)
96     EVT_MENU(MENU_COMETTOOL, MyFrame::OnCometTool)
97     EVT_MENU(MENU_GUIDING_ASSISTANT, MyFrame::OnGuidingAssistant)
98     EVT_MENU(MENU_HELP_UPGRADE, MyFrame::OnUpgrade)
99     EVT_MENU(MENU_HELP_ONLINE, MyFrame::OnHelpOnline)
100     EVT_MENU(MENU_HELP_LOG_FOLDER, MyFrame::OnHelpLogFolder)
101     EVT_MENU(MENU_HELP_UPLOAD_LOGS, MyFrame::OnHelpUploadLogs)
102     EVT_MENU(wxID_HELP_PROCEDURES, MyFrame::OnInstructions)
103     EVT_MENU(wxID_HELP_CONTENTS,MyFrame::OnHelp)
104     EVT_MENU(wxID_SAVE, MyFrame::OnSave)
105     EVT_MENU(MENU_TAKEDARKS,MyFrame::OnDark)
106     EVT_MENU(MENU_LOADDARK,MyFrame::OnLoadDark)
107     EVT_MENU(MENU_LOADDEFECTMAP,MyFrame::OnLoadDefectMap)
108     EVT_MENU(MENU_MANGUIDE, MyFrame::OnTestGuide)
109     EVT_MENU(MENU_STARCROSS_TEST, MyFrame::OnStarCrossTest)
110     EVT_MENU(MENU_PIERFLIP_TOOL, MyFrame::OnPierFlipTool)
111     EVT_MENU(MENU_XHAIR0, MyFrame::OnOverlay)
112     EVT_MENU(MENU_XHAIR1,MyFrame::OnOverlay)
113     EVT_MENU(MENU_XHAIR2,MyFrame::OnOverlay)
114     EVT_MENU(MENU_XHAIR3,MyFrame::OnOverlay)
115     EVT_MENU(MENU_XHAIR4,MyFrame::OnOverlay)
116     EVT_MENU(MENU_XHAIR5,MyFrame::OnOverlay)
117     EVT_MENU(MENU_SLIT_OVERLAY_COORDS, MyFrame::OnOverlaySlitCoords)
118     EVT_MENU(MENU_BOOKMARKS_SHOW, MyFrame::OnBookmarksShow)
119     EVT_MENU(MENU_BOOKMARKS_SET_AT_LOCK, MyFrame::OnBookmarksSetAtLockPos)
120     EVT_MENU(MENU_BOOKMARKS_SET_AT_STAR, MyFrame::OnBookmarksSetAtCurPos)
121     EVT_MENU(MENU_BOOKMARKS_CLEAR_ALL, MyFrame::OnBookmarksClearAll)
122     EVT_MENU(MENU_REFINEDEFECTMAP,MyFrame::OnRefineDefMap)
123     EVT_MENU(MENU_IMPORTCAMCAL,MyFrame::OnImportCamCal)
124 
125     EVT_CHAR_HOOK(MyFrame::OnCharHook)
126 
127 #if defined (V4L_CAMERA)
128     EVT_MENU(MENU_V4LSAVESETTINGS, MyFrame::OnSaveSettings)
129     EVT_MENU(MENU_V4LRESTORESETTINGS, MyFrame::OnRestoreSettings)
130 #endif
131 
132     EVT_MENU(MENU_TOOLBAR,MyFrame::OnToolBar)
133     EVT_MENU(MENU_GRAPH, MyFrame::OnGraph)
134     EVT_MENU(MENU_STATS, MyFrame::OnStats)
135     EVT_MENU(MENU_AO_GRAPH, MyFrame::OnAoGraph)
136     EVT_MENU(MENU_TARGET, MyFrame::OnTarget)
137     EVT_MENU(MENU_SERVER, MyFrame::OnServerMenu)
138     EVT_MENU(MENU_STARPROFILE, MyFrame::OnStarProfile)
139     EVT_MENU(MENU_RESTORE_WINDOWS, MyFrame::OnRestoreWindows)
140     EVT_MENU(MENU_AUTOSTAR,MyFrame::OnAutoStar)
141     EVT_TOOL(BUTTON_GEAR,MyFrame::OnSelectGear)
142     EVT_MENU(MENU_CONNECT,MyFrame::OnSelectGear)
143     EVT_TOOL(BUTTON_LOOP, MyFrame::OnButtonLoop)
144     EVT_MENU(MENU_LOOP, MyFrame::OnButtonLoop)
145     EVT_TOOL(BUTTON_STOP, MyFrame::OnButtonStop)
146     EVT_TOOL(BUTTON_AUTOSTAR, MyFrame::OnButtonAutoStar)
147     EVT_MENU(MENU_STOP, MyFrame::OnButtonStop)
148     EVT_TOOL(BUTTON_ADVANCED, MyFrame::OnAdvanced)
149     EVT_MENU(MENU_BRAIN, MyFrame::OnAdvanced)
150     EVT_TOOL(BUTTON_GUIDE,MyFrame::OnButtonGuide)
151     EVT_MENU(MENU_GUIDE,MyFrame::OnButtonGuide)
152     EVT_MENU(BUTTON_ALERT_CLOSE,MyFrame::OnAlertButton) // Bit of a hack -- not actually on the menu but need an event to accelerate
153     EVT_TOOL(BUTTON_CAM_PROPERTIES,MyFrame::OnSetupCamera)
154     EVT_MENU(MENU_CAM_SETTINGS, MyFrame::OnSetupCamera)
155     EVT_COMMAND_SCROLL(CTRL_GAMMA, MyFrame::OnGammaSlider)
156     EVT_COMBOBOX(BUTTON_DURATION, MyFrame::OnExposureDurationSelected)
157     EVT_SOCKET(SOCK_SERVER_ID, MyFrame::OnSockServerEvent)
158     EVT_SOCKET(SOCK_SERVER_CLIENT_ID, MyFrame::OnSockServerClientEvent)
159     EVT_CLOSE(MyFrame::OnClose)
160     EVT_THREAD(MYFRAME_WORKER_THREAD_EXPOSE_COMPLETE, MyFrame::OnExposeComplete)
161     EVT_THREAD(MYFRAME_WORKER_THREAD_MOVE_COMPLETE, MyFrame::OnMoveComplete)
162 
163     EVT_COMMAND(wxID_ANY, REQUEST_EXPOSURE_EVENT, MyFrame::OnRequestExposure)
164     EVT_COMMAND(wxID_ANY, WXMESSAGEBOX_PROXY_EVENT, MyFrame::OnMessageBoxProxy)
165 
166     EVT_THREAD(SET_STATUS_TEXT_EVENT, MyFrame::OnStatusMsg)
167     EVT_THREAD(ALERT_FROM_THREAD_EVENT, MyFrame::OnAlertFromThread)
168     EVT_THREAD(RECONNECT_CAMERA_EVENT, MyFrame::OnReconnectCameraFromThread)
169     EVT_THREAD(UPDATER_EVENT, MyFrame::OnUpdaterStateChanged)
170     EVT_COMMAND(wxID_ANY, REQUEST_MOUNT_MOVE_EVENT, MyFrame::OnRequestMountMove)
171     EVT_TIMER(STATUSBAR_TIMER_EVENT, MyFrame::OnStatusBarTimerEvent)
172 
173     EVT_AUI_PANE_CLOSE(MyFrame::OnPanelClose)
174 wxEND_EVENT_TABLE()
175 
176 struct FileDropTarget : public wxFileDropTarget
177 {
FileDropTargetFileDropTarget178     FileDropTarget() { }
179 
OnDragOverFileDropTarget180     wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult defResult)
181     {
182         if (!pFrame->CaptureActive)
183             return wxDragResult::wxDragCopy;
184         return wxDragResult::wxDragNone;
185     }
186 
OnDropFilesFileDropTarget187     bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
188     {
189         if (filenames.size() != 1)
190             return false;
191 
192         if (pFrame->CaptureActive)
193             return false;
194 
195         std::unique_ptr<usImage> img(new usImage());
196 
197         if (img->Load(filenames[0]))
198             return false;
199 
200         img->CalcStats();
201         pFrame->pGuider->DisplayImage(img.release());
202 
203         return true;
204     }
205 };
206 
207 // ---------------------- Main Frame -------------------------------------
208 // frame constructor
MyFrame()209 MyFrame::MyFrame()
210     :
211     wxFrame(nullptr, wxID_ANY, wxEmptyString),
212     m_showBookmarksAccel(0),
213     m_bookmarkLockPosAccel(0),
214     pStatsWin(nullptr)
215 {
216     m_mgr.SetManagedWindow(this);
217 
218     m_frameCounter = 0;
219     m_pPrimaryWorkerThread = nullptr;
220     StartWorkerThread(m_pPrimaryWorkerThread);
221     m_pSecondaryWorkerThread = nullptr;
222     StartWorkerThread(m_pSecondaryWorkerThread);
223 
224     m_statusbarTimer.SetOwner(this, STATUSBAR_TIMER_EVENT);
225 
226     SocketServer = nullptr;
227 
228     bool serverMode = pConfig->Global.GetBoolean("/ServerMode", DefaultServerMode);
229     SetServerMode(serverMode);
230 
231     m_sampling = 1.0;
232 
233     #include "icons/phd2_128.png.h"
234     wxBitmap phd2(wxBITMAP_PNG_FROM_DATA(phd2_128));
235     wxIcon icon;
236     icon.CopyFromBitmap(phd2);
237     SetIcon(icon);
238 
239     //SetIcon(wxIcon(_T("progicon")));
240     SetBackgroundColour(*wxLIGHT_GREY);
241 
242     // Setup menus
243     SetupMenuBar();
244 
245     // Setup button panel
246     SetupToolBar();
247 
248     // Setup Status bar
249     SetupStatusBar();
250 
251     LoadProfileSettings();
252 
253     // Setup container window for alert message info bar and guider window
254     wxWindow *guiderWin = new wxWindow(this, wxID_ANY);
255     wxSizer *sizer = new wxBoxSizer(wxVERTICAL);
256 
257     m_infoBar = new wxInfoBar(guiderWin);
258     m_infoBar->Connect(BUTTON_ALERT_ACTION, wxEVT_BUTTON,
259         wxCommandEventHandler(MyFrame::OnAlertButton), nullptr, this);
260     m_infoBar->Connect(BUTTON_ALERT_DONTSHOW, wxEVT_BUTTON,
261         wxCommandEventHandler(MyFrame::OnAlertButton), nullptr, this);
262     m_infoBar->Connect(BUTTON_ALERT_CLOSE, wxEVT_BUTTON,
263         wxCommandEventHandler(MyFrame::OnAlertButton), nullptr, this);
264     m_infoBar->Connect(BUTTON_ALERT_HELP, wxEVT_BUTTON,
265         wxCommandEventHandler(MyFrame::OnAlertHelp), nullptr, this);
266 
267     sizer->Add(m_infoBar, wxSizerFlags().Expand());
268 
269     pGuider = new GuiderMultiStar(guiderWin);
270     sizer->Add(pGuider, wxSizerFlags().Proportion(1).Expand());
271 
272     guiderWin->SetSizer(sizer);
273 
274     pGuider->LoadProfileSettings();
275 
276     bool sticky = pConfig->Global.GetBoolean("/StickyLockPosition", false);
277     pGuider->SetLockPosIsSticky(sticky);
278     tools_menu->Check(EEGG_STICKY_LOCK, sticky);
279 
280     SetMinSize(wxSize(300, 300));
281     SetSize(800, 600);
282 
283     wxString geometry = pConfig->Global.GetString("/geometry", wxEmptyString);
284     wxArrayString fields = wxSplit(geometry, ';');
285     long w, h, x, y;
286     if (fields.size() == 5 &&
287         fields[1].ToLong(&w) &&
288         fields[2].ToLong(&h) &&
289         fields[3].ToLong(&x) &&
290         fields[4].ToLong(&y))
291     {
292         wxSize screen = wxGetDisplaySize();
293         if (x + w <= screen.GetWidth() &&
294             x >= 0 &&
295             y + h <= screen.GetHeight() &&
296             y >= 0)
297         {
298             SetSize(w, h);
299             SetPosition(wxPoint(x, y));
300         }
301         else
302         {
303             // looks like screen size changed, ignore position and revert to default size
304         }
305         if (fields[0] == "1")
306         {
307             Maximize();
308         }
309     }
310 
311     // Setup some keyboard shortcuts
312     SetupKeyboardShortcuts();
313 
314     SetDropTarget(new FileDropTarget());
315 
316     m_mgr.AddPane(MainToolbar, wxAuiPaneInfo().
317         Name(_T("MainToolBar")).Caption(_T("Main tool bar")).
318         ToolbarPane().Bottom());
319 
320     guiderWin->SetMinSize(wxSize(XWinSize,YWinSize));
321     guiderWin->SetSize(wxSize(XWinSize,YWinSize));
322     m_mgr.AddPane(guiderWin, wxAuiPaneInfo().
323         Name(_T("Guider")).Caption(_T("Guider")).
324         CenterPane().MinSize(wxSize(XWinSize,YWinSize)));
325 
326     pGraphLog = new GraphLogWindow(this);
327     m_mgr.AddPane(pGraphLog, wxAuiPaneInfo().
328         Name(_T("GraphLog")).Caption(_("History")).
329         Hide());
330 
331     pStatsWin = new StatsWindow(this);
332     m_mgr.AddPane(pStatsWin, wxAuiPaneInfo().
333         Name(_T("Stats")).Caption(_("Guide Stats")).
334         Hide());
335 
336     pStepGuiderGraph = new GraphStepguiderWindow(this);
337     m_mgr.AddPane(pStepGuiderGraph, wxAuiPaneInfo().
338         Name(_T("AOPosition")).Caption(_("AO Position")).
339         Hide());
340 
341     pProfile = new ProfileWindow(this);
342     m_mgr.AddPane(pProfile, wxAuiPaneInfo().
343         Name(_T("Profile")).Caption(_("Star Profile")).
344         Hide());
345 
346     pTarget = new TargetWindow(this);
347     m_mgr.AddPane(pTarget, wxAuiPaneInfo().
348         Name(_T("Target")).Caption(_("Target")).
349         Hide());
350 
351     pAdvancedDialog = new AdvancedDialog(this);
352 
353     pGearDialog = new GearDialog(this);
354 
355     pDriftTool = nullptr;
356     pPolarDriftTool = nullptr;
357     pStaticPaTool = nullptr;
358     pManualGuide = nullptr;
359     pStarCrossDlg = nullptr;
360     pNudgeLock = nullptr;
361     pCometTool = nullptr;
362     pGuidingAssistant = nullptr;
363     pRefineDefMap = nullptr;
364     pCalSanityCheckDlg = nullptr;
365     pCalReviewDlg = nullptr;
366     pierFlipToolWin = nullptr;
367     m_starFindMode = Star::FIND_CENTROID;
368     m_rawImageMode = false;
369     m_rawImageModeWarningDone = false;
370 
371     UpdateTitle();
372 
373     SetupHelpFile();
374 
375     if (m_serverMode)
376     {
377         tools_menu->Check(MENU_SERVER,true);
378         StartServer(true);
379     }
380 
381     #include "xhair.xpm"
382     wxImage Cursor = wxImage(mac_xhair);
383     Cursor.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X,8);
384     Cursor.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y,8);
385     pGuider->SetCursor(wxCursor(Cursor));
386 
387     m_continueCapturing = false;
388     CaptureActive     = false;
389     m_exposurePending = false;
390 
391     m_singleExposure.enabled = false;
392     m_singleExposure.duration = 0;
393 
394     m_mgr.GetArtProvider()->SetColour(wxAUI_DOCKART_BACKGROUND_COLOUR, *wxBLACK);
395     m_mgr.GetArtProvider()->SetMetric(wxAUI_DOCKART_GRADIENT_TYPE, wxAUI_GRADIENT_VERTICAL);
396     m_mgr.GetArtProvider()->SetColor(wxAUI_DOCKART_INACTIVE_CAPTION_COLOUR, wxColour(0, 153, 255));
397     m_mgr.GetArtProvider()->SetColor(wxAUI_DOCKART_INACTIVE_CAPTION_GRADIENT_COLOUR, *wxBLACK);
398     m_mgr.GetArtProvider()->SetColor(wxAUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR, *wxWHITE);
399 
400     wxString perspective = pConfig->Global.GetString("/perspective", wxEmptyString);
401     if (perspective != wxEmptyString)
402     {
403         m_mgr.LoadPerspective(perspective);
404         m_mgr.GetPane(_T("MainToolBar")).Caption(_T("Main tool bar"));
405         m_mgr.GetPane(_T("Guider")).Caption(_T("Guider"));
406         m_mgr.GetPane(_T("GraphLog")).Caption(_("History"));
407         m_mgr.GetPane(_T("Stats")).Caption(_("Guide Stats"));
408         m_mgr.GetPane(_T("AOPosition")).Caption(_("AO Position"));
409         m_mgr.GetPane(_T("Profile")).Caption(_("Star Profile"));
410         m_mgr.GetPane(_T("Target")).Caption(_("Target"));
411         m_mgr.GetPane(_T("Guider")).PaneBorder(false);
412     }
413 
414     bool panel_state;
415 
416     panel_state = m_mgr.GetPane(_T("MainToolBar")).IsShown();
417     Menubar->Check(MENU_TOOLBAR, panel_state);
418 
419     panel_state = m_mgr.GetPane(_T("GraphLog")).IsShown();
420     pGraphLog->SetState(panel_state);
421     Menubar->Check(MENU_GRAPH, panel_state);
422 
423     panel_state = m_mgr.GetPane(_T("Stats")).IsShown();
424     pStatsWin->SetState(panel_state);
425     Menubar->Check(MENU_STATS, panel_state);
426 
427     panel_state = m_mgr.GetPane(_T("AOPosition")).IsShown();
428     pStepGuiderGraph->SetState(panel_state);
429     Menubar->Check(MENU_AO_GRAPH, panel_state);
430 
431     panel_state = m_mgr.GetPane(_T("Profile")).IsShown();
432     pProfile->SetState(panel_state);
433     Menubar->Check(MENU_STARPROFILE, panel_state);
434 
435     panel_state = m_mgr.GetPane(_T("Target")).IsShown();
436     pTarget->SetState(panel_state);
437     Menubar->Check(MENU_TARGET, panel_state);
438 
439     m_mgr.Update();
440 
441     // this forces force a resize of MainToolbar in case size changed from the saved perspective
442     MainToolbar->Realize();
443 }
444 
~MyFrame()445 MyFrame::~MyFrame()
446 {
447     delete pGearDialog;
448     pGearDialog = nullptr;
449 
450     pAdvancedDialog->Destroy();
451 
452     if (pDriftTool)
453         pDriftTool->Destroy();
454     if (pPolarDriftTool)
455         pPolarDriftTool->Destroy();
456     if (pStaticPaTool)
457         pStaticPaTool->Destroy();
458     if (pRefineDefMap)
459         pRefineDefMap->Destroy();
460     if (pCalSanityCheckDlg)
461         pCalSanityCheckDlg->Destroy();
462     if (pCalReviewDlg)
463         pCalReviewDlg->Destroy();
464     if (pStarCrossDlg)
465         pStarCrossDlg->Destroy();
466     if (pierFlipToolWin)
467         pierFlipToolWin->Destroy();
468 
469     m_mgr.UnInit();
470 
471     delete m_showBookmarksAccel;
472     delete m_bookmarkLockPosAccel;
473 }
474 
UpdateTitle()475 void MyFrame::UpdateTitle()
476 {
477     int inst = wxGetApp().GetInstanceNumber();
478     wxString prof = pConfig->GetCurrentProfile();
479 
480     wxString title = inst > 1 ?
481         wxString::Format(_T("%s(#%d) %s - %s"), APPNAME, inst, FULLVER, prof) :
482         wxString::Format(_T("%s %s - %s"), APPNAME, FULLVER, prof);
483 
484     SetTitle(title);
485 }
486 
SetupMenuBar()487 void MyFrame::SetupMenuBar()
488 {
489     wxMenu *file_menu = new wxMenu();
490     file_menu->Append(wxID_SAVE, _("&Save Image..."), _("Save current image"));
491     file_menu->Append(wxID_EXIT, _("E&xit\tAlt-X"), _("Quit this program"));
492 
493     wxMenu *guide_menu = new wxMenu();
494     m_connectMenuItem = guide_menu->Append(MENU_CONNECT, _("&Connect Equipment\tCtrl-C"), _("Connect or disconnect equipment"));
495     m_loopMenuItem = guide_menu->Append(MENU_LOOP, _("&Loop Exposures\tCtrl-L"), _("Begin looping exposures"));
496     m_loopMenuItem->Enable(false);
497     m_guideMenuItem = guide_menu->Append(MENU_GUIDE, _("&Guide\tCtrl-G"), _("Begin guiding"));
498     m_guideMenuItem->Enable(false);
499     m_stopMenuItem = guide_menu->Append(MENU_STOP, _("&Stop\tCtrl-S"), _("Stop looping and guiding"));
500     m_stopMenuItem->Enable(false);
501     m_brainMenuItem = guide_menu->Append(MENU_BRAIN, _("&Advanced Settings\tCtrl-A"), _("Advanced Settings"));
502     m_cameraMenuItem = guide_menu->Append(MENU_CAM_SETTINGS, _("&Camera Settings"), _("Camera settings"));
503     m_cameraMenuItem->Enable(false);
504 
505     tools_menu = new wxMenu;
506     tools_menu->Append(MENU_MANGUIDE, _("&Manual Guide"), _("Manual / test guide dialog"));
507     m_autoSelectStarMenuItem = tools_menu->Append(MENU_AUTOSTAR, _("&Auto-select Star\tAlt-S"), _("Automatically select star"));
508     tools_menu->Append(EEGG_REVIEWCAL, _("&Review Calibration Data\tAlt-C"), _("Review calibration data from last successful calibration"));
509 
510     wxMenu *calib_menu = new wxMenu;
511     calib_menu->Append(EEGG_RESTORECAL, _("Restore Calibration Data..."), _("Restore calibration data from last successful calibration"));
512     calib_menu->Append(EEGG_MANUALCAL, _("Enter Calibration Data..."), _("Manually enter the calibration data"));
513     calib_menu->Append(EEGG_FLIPCAL, _("Flip Calibration Now"), _("Flip the calibration data now"));
514     calib_menu->Append(EEGG_CLEARCAL, _("Clear Calibration Data..."), _("Clear calibration data currently in use"));
515     m_calibrationMenuItem = tools_menu->AppendSubMenu(calib_menu, _("Modify Calibration"));
516     m_calibrationMenuItem->Enable(false);
517 
518     tools_menu->Append(EEGG_MANUALLOCK, _("Adjust &Lock Position"), _("Adjust the lock position"));
519     tools_menu->Append(MENU_COMETTOOL, _("&Comet Tracking"), _("Run the Comet Tracking tool"));
520     tools_menu->Append(MENU_STARCROSS_TEST, _("Star-Cross Test"), _("Run a star-cross test for mount diagnostics"));
521     tools_menu->Append(MENU_PIERFLIP_TOOL, _("Calibrate meridian flip"), _("Automatically determine the correct meridian flip settings"));
522     tools_menu->Append(MENU_GUIDING_ASSISTANT, _("&Guiding Assistant"), _("Run the Guiding Assistant"));
523     tools_menu->Append(MENU_DRIFTTOOL, _("&Drift Align"), _("Align by analysing star drift near the celestial equator (Accurate)"));
524     tools_menu->Append(MENU_POLARDRIFTTOOL, _("&Polar Drift Align"), _("Align by analysing star drift near the celestial pole (Simple)"));
525     tools_menu->Append(MENU_STATICPATOOL, _("&Static Polar Align"), _("Align by measuring the RA axis offset from the celestial pole (Fast)"));
526     tools_menu->AppendSeparator();
527     tools_menu->AppendCheckItem(MENU_SERVER,_("Enable Server"),_("Enable PHD2 server capability"));
528     tools_menu->AppendCheckItem(EEGG_STICKY_LOCK,_("Sticky Lock Position"),_("Keep the same lock position when guiding starts"));
529 
530     view_menu = new wxMenu();
531     view_menu->AppendCheckItem(MENU_TOOLBAR,_("Display Toolbar"),_("Enable / disable tool bar"));
532     view_menu->AppendCheckItem(MENU_GRAPH,_("Display &Graph"),_("Enable / disable graph"));
533     view_menu->AppendCheckItem(MENU_STATS, _("Display &Stats"), _("Enable / disable guide stats"));
534     view_menu->AppendCheckItem(MENU_AO_GRAPH, _("Display &AO Graph"), _("Enable / disable AO graph"));
535     view_menu->AppendCheckItem(MENU_TARGET,_("Display &Target"),_("Enable / disable target"));
536     view_menu->AppendCheckItem(MENU_STARPROFILE,_("Display Star &Profile"),_("Enable / disable star profile view"));
537     view_menu->AppendSeparator();
538     view_menu->AppendRadioItem(MENU_XHAIR0, _("&No Overlay"),_("No additional crosshairs"));
539     view_menu->AppendRadioItem(MENU_XHAIR1, _("&Bullseye"),_("Centered bullseye overlay"));
540     view_menu->AppendRadioItem(MENU_XHAIR2, _("&Fine Grid"),_("Grid overlay"));
541     view_menu->AppendRadioItem(MENU_XHAIR3, _("&Coarse Grid"),_("Grid overlay"));
542     view_menu->AppendRadioItem(MENU_XHAIR4, _("&RA/Dec"),_("RA and Dec overlay"));
543     view_menu->AppendRadioItem(MENU_XHAIR5, _("Spectrograph S&lit"), _("Spectrograph slit overlay"));
544     view_menu->AppendSeparator();
545     view_menu->Append(MENU_SLIT_OVERLAY_COORDS, _("Slit Position..."));
546     view_menu->AppendSeparator();
547     view_menu->Append(MENU_RESTORE_WINDOWS, _("Restore Window Positions"), _("Restore all windows to their default/docked positions"));
548 
549     darks_menu = new wxMenu();
550     m_takeDarksMenuItem = darks_menu->Append(MENU_TAKEDARKS, _("Dark &Library..."), _("Build a dark library for this profile"));
551     m_refineDefMapMenuItem = darks_menu->Append(MENU_REFINEDEFECTMAP, _("Bad-pixel &Map..."), _("Adjust parameters to create or modify the bad-pixel map"));
552     m_importCamCalMenuItem = darks_menu->Append(MENU_IMPORTCAMCAL, _("Import From Profile..."), _("Import existing dark library/bad-pixel map from a different profile"));
553     darks_menu->AppendSeparator();
554     m_useDarksMenuItem =  darks_menu->AppendCheckItem(MENU_LOADDARK, _("Use &Dark Library"), _("Use the the dark library for this profile"));
555     m_useDefectMapMenuItem = darks_menu->AppendCheckItem(MENU_LOADDEFECTMAP, _("Use &Bad-pixel Map"), _("Use the bad-pixel map for this profile"));
556 
557 #if defined (V4L_CAMERA)
558     wxMenu *v4l_menu = new wxMenu();
559 
560     v4l_menu->Append(MENU_V4LSAVESETTINGS, _("&Save settings"), _("Save current camera settings"));
561     v4l_menu->Append(MENU_V4LRESTORESETTINGS, _("&Restore settings"), _("Restore camera settings"));
562 #endif
563 
564     bookmarks_menu = new wxMenu();
565     m_showBookmarksMenuItem = bookmarks_menu->AppendCheckItem(MENU_BOOKMARKS_SHOW, _("Show &Bookmarks\tb"), _("Hide or show bookmarks"));
566     m_showBookmarksAccel = m_showBookmarksMenuItem->GetAccel();
567     bookmarks_menu->Check(MENU_BOOKMARKS_SHOW, true);
568     m_bookmarkLockPosMenuItem = bookmarks_menu->Append(MENU_BOOKMARKS_SET_AT_LOCK, _("Bookmark &Lock Pos\tShift-B"), _("Set a bookmark at the current lock position"));
569     m_bookmarkLockPosAccel = m_bookmarkLockPosMenuItem->GetAccel();
570     bookmarks_menu->Append(MENU_BOOKMARKS_SET_AT_STAR, _("Bookmark &Star Pos"), _("Set a bookmark at the position of the currently selected star"));
571     bookmarks_menu->Append(MENU_BOOKMARKS_CLEAR_ALL, _("&Delete all\tCtrl-B"), _("Remove all bookmarks"));
572 
573     wxMenu *help_menu = new wxMenu;
574     help_menu->Append(wxID_ABOUT, _("&About..."), wxString::Format(_("About %s"), APPNAME));
575     m_upgradeMenuItem = help_menu->Append(MENU_HELP_UPGRADE, _("&Check for updates"), _("Check for PHD2 software updates"));
576     help_menu->Append(MENU_HELP_ONLINE,_("Online Support"),_("Ask for help in the PHD2 Forum"));
577     help_menu->Append(MENU_HELP_LOG_FOLDER, _("Open Log Folder"), _("Open the log folder"));
578     help_menu->Append(MENU_HELP_UPLOAD_LOGS, _("Upload Log Files..."), _("Upload log files for review"));
579     help_menu->Append(wxID_HELP_CONTENTS,_("&Contents...\tF1"),_("Full help"));
580     help_menu->Append(wxID_HELP_PROCEDURES,_("&Impatient Instructions"),_("Quick instructions for the impatient"));
581 
582     Menubar = new wxMenuBar();
583     Menubar->Append(file_menu, _("&File"));
584     Menubar->Append(guide_menu, _("&Guide"));
585 
586 #if defined (V4L_CAMERA)
587     Menubar->Append(v4l_menu, _T("&V4L"));
588 
589     Menubar->Enable(MENU_V4LSAVESETTINGS, false);
590     Menubar->Enable(MENU_V4LRESTORESETTINGS, false);
591 #endif
592 
593     Menubar->Append(tools_menu, _("&Tools"));
594     Menubar->Append(view_menu, _("&View"));
595     Menubar->Append(darks_menu, _("&Darks"));
596     Menubar->Append(bookmarks_menu, _("&Bookmarks"));
597     Menubar->Append(help_menu, _("&Help"));
598     SetMenuBar(Menubar);
599 }
600 
GetTextWidth(wxControl * pControl,const wxString & string)601 int MyFrame::GetTextWidth(wxControl *pControl, const wxString& string)
602 {
603     int width;
604 
605     pControl->GetTextExtent(string, &width, nullptr);
606 
607     return width;
608 }
609 
SetComboBoxWidth(wxComboBox * pComboBox,unsigned int extra)610 void MyFrame::SetComboBoxWidth(wxComboBox *pComboBox, unsigned int extra)
611 {
612     unsigned int i;
613     int width=-1;
614 
615     for (i = 0; i < pComboBox->GetCount(); i++)
616     {
617         int thisWidth = GetTextWidth(pComboBox, pComboBox->GetString(i));
618 
619         if (thisWidth > width)
620         {
621             width =  thisWidth;
622         }
623     }
624 
625     pComboBox->SetMinSize(wxSize(width + extra, -1));
626 }
627 
628 static std::vector<int> exposure_durations;
629 
GetExposureDurations() const630 const std::vector<int>& MyFrame::GetExposureDurations() const
631 {
632     return exposure_durations;
633 }
634 
SetCustomExposureDuration(int ms)635 bool MyFrame::SetCustomExposureDuration(int ms)
636 {
637     auto end = exposure_durations.end() - 1;
638     for (auto it = exposure_durations.begin(); it != end; ++it)
639         if (ms == *it)
640             return true; // error, duplicate value
641     if (m_exposureDuration == *end && *end != ms)
642     {
643         m_exposureDuration = ms;
644         NotifyExposureChanged();
645     }
646     *end = ms;
647     Dur_Choice->SetString(1 + exposure_durations.size() - 1, wxString::Format(_("Custom: %g s"), (double) ms / 1000.));
648     pConfig->Profile.SetInt("/CustomExposureDuration", ms);
649     return false;
650 }
651 
GetExposureInfo(int * currExpMs,bool * autoExp) const652 void MyFrame::GetExposureInfo(int *currExpMs, bool *autoExp) const
653 {
654     if (!pCamera || !pCamera->Connected)
655     {
656         *currExpMs = 0;
657         *autoExp = false;
658     }
659     else
660     {
661         *currExpMs = m_exposureDuration;
662         *autoExp = m_autoExp.enabled;
663     }
664 }
665 
dur_index(int duration)666 static int dur_index(int duration)
667 {
668     for (auto it = exposure_durations.begin(); it != exposure_durations.end(); ++it)
669         if (duration == *it)
670             return it - exposure_durations.begin();
671     return -1;
672 }
673 
SetExposureDuration(int val)674 bool MyFrame::SetExposureDuration(int val)
675 {
676     if (val < 0)
677     {
678         // Auto
679         Dur_Choice->SetSelection(0);
680     }
681     else
682     {
683         int idx = dur_index(val);
684         if (idx == -1)
685             return false;
686         Dur_Choice->SetSelection(idx + 1); // skip Auto
687     }
688 
689     wxCommandEvent dummy;
690     OnExposureDurationSelected(dummy);
691     return true;
692 }
693 
SetAutoExposureCfg(int minExp,int maxExp,double targetSNR)694 bool MyFrame::SetAutoExposureCfg(int minExp, int maxExp, double targetSNR)
695 {
696     Debug.Write(wxString::Format("AutoExp: config min = %d max = %d snr = %.2f\n", minExp, maxExp, targetSNR));
697 
698     pConfig->Profile.SetInt("/auto_exp/exposure_min", minExp);
699     pConfig->Profile.SetInt("/auto_exp/exposure_max", maxExp);
700     pConfig->Profile.SetDouble("/auto_exp/target_snr", targetSNR);
701 
702     bool changed =
703             m_autoExp.minExposure != minExp ||
704             m_autoExp.maxExposure != maxExp ||
705             m_autoExp.targetSNR != targetSNR;
706 
707     m_autoExp.minExposure = minExp;
708     m_autoExp.maxExposure = maxExp;
709     m_autoExp.targetSNR = targetSNR;
710 
711     return changed;
712 }
713 
ExposureDurationSummary() const714 wxString MyFrame::ExposureDurationSummary() const
715 {
716     if (m_autoExp.enabled)
717         return wxString::Format("Auto (min = %d ms, max = %d ms, SNR = %.2f)", m_autoExp.minExposure, m_autoExp.maxExposure, m_autoExp.targetSNR);
718     else
719         return wxString::Format("%d ms", m_exposureDuration);
720 }
721 
ResetAutoExposure()722 void MyFrame::ResetAutoExposure()
723 {
724     if (m_autoExp.enabled)
725     {
726         Debug.Write(wxString::Format("AutoExp: reset exp to %d\n", m_autoExp.maxExposure));
727         m_exposureDuration = m_autoExp.maxExposure;
728     }
729 }
730 
AdjustAutoExposure(double curSNR)731 void MyFrame::AdjustAutoExposure(double curSNR)
732 {
733     if (m_autoExp.enabled)
734     {
735         if (curSNR < 1.0)
736         {
737             Debug.Write(wxString::Format("AutoExp: low SNR (%.2f), reset exp to %d\n", curSNR, m_autoExp.maxExposure));
738             m_exposureDuration = m_autoExp.maxExposure;
739         }
740         else
741         {
742             double r = m_autoExp.targetSNR / curSNR;
743             double exp = (double) m_exposureDuration;
744             // assume snr ~ sqrt(exposure)
745             double newExp = exp * r * r;
746             // use hysteresis to avoid overshooting
747             // if our snr is below target, increase exposure rapidly (weak hysteresis, large alpha)
748             // if our snr is above target, decrease exposure slowly (strong hysteresis, small alpha)
749             static double const alpha_slow = .15; // low weighting for latest sample
750             static double const alpha_fast = .20; // high weighting for latest sample
751             double alpha = curSNR < m_autoExp.targetSNR ? alpha_fast : alpha_slow;
752             exp += alpha * (newExp - exp);
753             m_exposureDuration = (int) floor(exp + 0.5);
754             if (m_exposureDuration < m_autoExp.minExposure)
755                 m_exposureDuration = m_autoExp.minExposure;
756             else if (m_exposureDuration > m_autoExp.maxExposure)
757                 m_exposureDuration = m_autoExp.maxExposure;
758             Debug.Write(wxString::Format("AutoExp: adjust SNR=%.2f new exposure %d\n", curSNR, m_exposureDuration));
759         }
760     }
761 }
762 
ExposureDurationLabel(int duration)763 wxString MyFrame::ExposureDurationLabel(int duration)
764 {
765     if (duration >= 10000)
766         return wxString::Format(_("%g s"), (double) duration / 1000.);
767 
768     int digits = duration < 100 ? 2 : 1;
769     return wxString::Format(_("%.*f s"), digits, (double) duration / 1000.);
770 }
771 
SetStarFindMode(Star::FindMode mode)772 Star::FindMode MyFrame::SetStarFindMode(Star::FindMode mode)
773 {
774     Star::FindMode prev = m_starFindMode;
775     Debug.Write(wxString::Format("Setting StarFindMode = %d\n", mode));
776     m_starFindMode = mode;
777     return prev;
778 }
779 
SetRawImageMode(bool mode)780 bool MyFrame::SetRawImageMode(bool mode)
781 {
782     bool prev = m_rawImageMode;
783     Debug.Write(wxString::Format("Setting RawImageMode = %d\n", mode));
784     m_rawImageMode = mode;
785     if (mode)
786         m_rawImageModeWarningDone = false;
787     return prev;
788 }
789 
LoadImageLoggerSettings()790 static void LoadImageLoggerSettings()
791 {
792     ImageLoggerSettings settings;
793 
794     settings.loggingEnabled = pConfig->Profile.GetBoolean("/ImageLogger/LoggingEnabled", false);
795     settings.logFramesOverThreshRel = pConfig->Profile.GetBoolean("/ImageLogger/LogFramesOverThreshRel", false);
796     settings.logFramesOverThreshPx = pConfig->Profile.GetBoolean("/ImageLogger/LogFramesOverThreshPx", false);
797     settings.logFramesDropped = pConfig->Profile.GetBoolean("/ImageLogger/LogFramesDropped", false);
798     settings.logAutoSelectFrames = pConfig->Profile.GetBoolean("/ImageLogger/LogAutoSelectFrames", false);
799     settings.logNextNFrames = false;
800     settings.logNextNFramesCount = 1;
801     settings.guideErrorThreshRel = pConfig->Profile.GetDouble("/ImageLogger/ErrorThreshRel", 4.0);
802     settings.guideErrorThreshPx = pConfig->Profile.GetDouble("/ImageLogger/ErrorThreshPx", 4.0);
803 
804     ImageLogger::ApplySettings(settings);
805 }
806 
SaveImageLoggerSettings(const ImageLoggerSettings & settings)807 static void SaveImageLoggerSettings(const ImageLoggerSettings& settings)
808 {
809     pConfig->Profile.SetBoolean("/ImageLogger/LoggingEnabled", settings.loggingEnabled);
810     pConfig->Profile.SetBoolean("/ImageLogger/LogFramesOverThreshRel", settings.logFramesOverThreshRel);
811     pConfig->Profile.SetBoolean("/ImageLogger/LogFramesOverThreshPx", settings.logFramesOverThreshPx);
812     pConfig->Profile.SetBoolean("/ImageLogger/LogFramesDropped", settings.logFramesDropped);
813     pConfig->Profile.SetBoolean("/ImageLogger/LogAutoSelectFrames", settings.logAutoSelectFrames);
814     pConfig->Profile.SetDouble("/ImageLogger/ErrorThreshRel", settings.guideErrorThreshRel);
815     pConfig->Profile.SetDouble("/ImageLogger/ErrorThreshPx", settings.guideErrorThreshPx);
816 }
817 
818 enum {
819     GAMMA_MIN = 10,
820     GAMMA_MAX = 300,
821     GAMMA_DEFAULT = 100,
822 };
823 
LoadProfileSettings()824 void MyFrame::LoadProfileSettings()
825 {
826     int noiseReductionMethod = pConfig->Profile.GetInt("/NoiseReductionMethod", DefaultNoiseReductionMethod);
827     SetNoiseReductionMethod(noiseReductionMethod);
828 
829     double ditherScaleFactor = pConfig->Profile.GetDouble("/DitherScaleFactor", DefaultDitherScaleFactor);
830     SetDitherScaleFactor(ditherScaleFactor);
831 
832     bool ditherRaOnly = pConfig->Profile.GetBoolean("/DitherRaOnly", DefaultDitherRaOnly);
833     SetDitherRaOnly(ditherRaOnly);
834 
835     int ditherMode = pConfig->Profile.GetInt("/DitherMode", DefaultDitherMode);
836     SetDitherMode(ditherMode == DITHER_RANDOM ? DITHER_RANDOM : DITHER_SPIRAL);
837 
838     int timeLapse = pConfig->Profile.GetInt("/frame/timeLapse", DefaultTimelapse);
839     SetTimeLapse(timeLapse);
840 
841     // Don't re-save the setting here with a call to SetAutoLoadCalibration().  An un-initialized registry key (-1) will
842     // be populated after the 1st calibration
843     int autoLoad = pConfig->Profile.GetInt("/AutoLoadCalibration", -1);
844     m_autoLoadCalibration = (autoLoad == 1);        // new profile=> false
845 
846     int focalLength = pConfig->Profile.GetInt("/frame/focalLength", DefaultFocalLength);
847     SetFocalLength(focalLength);
848 
849     int minExp = pConfig->Profile.GetInt("/auto_exp/exposure_min", DefaultAutoExpMin);
850     int maxExp = pConfig->Profile.GetInt("/auto_exp/exposure_max", DefaultAutoExpMax);
851     double targetSNR = pConfig->Profile.GetDouble("/auto_exp/target_snr", DefaultAutoExpSNR);
852     SetAutoExposureCfg(minExp, maxExp, targetSNR);
853     // force reset of auto-exposure state
854     m_autoExp.enabled = true; // OnExposureDurationSelected below will set the actual value
855     ResetAutoExposure();
856 
857     // set custom exposure duration vector value and drop-down list string from profile setting
858     int customDuration = pConfig->Profile.GetInt("/CustomExposureDuration", 30000);
859     *exposure_durations.rbegin() = customDuration;
860     Dur_Choice->SetString(1 + exposure_durations.size() - 1, wxString::Format(_("Custom: %g s"), (double) customDuration / 1000.));
861 
862     // for backward compatibity:
863     // exposure duration used to be stored as the formatted string value appearing in the drop-down list,
864     // but this does not work if the locale is changed
865     // TODO: remove this code after a few releases (code added 5/30/2017)
866     // replace with:
867     // int exposureDuration = pConfig->Profile.GetInt("/ExposureDurationMs", DefaultExposureDuration);
868     int exposureDuration = DefaultExposureDuration;
869     if (pConfig->Profile.HasEntry("/ExposureDurationMs"))
870         exposureDuration = pConfig->Profile.GetInt("/ExposureDurationMs", DefaultExposureDuration);
871     else if (pConfig->Profile.HasEntry("/ExposureDuration"))
872     {
873         wxString s = pConfig->Profile.GetString("/ExposureDuration", wxEmptyString);
874         const wxStringCharType *start = s.wx_str();
875         wxStringCharType *end;
876         double d = wxStrtod(start, &end);
877         if (end != start)
878             exposureDuration = (int)(d * 1000.0);
879         else if (s == _("Auto"))
880             exposureDuration = -1;
881         pConfig->Profile.DeleteEntry("/ExposureDuration");
882     }
883     SetExposureDuration(exposureDuration);
884     m_beepForLostStar = pConfig->Profile.GetBoolean("/BeepForLostStar", true);
885 
886     int val = pConfig->Profile.GetInt("/Gamma", GAMMA_DEFAULT);
887     if (val < GAMMA_MIN) val = GAMMA_MIN;
888     if (val > GAMMA_MAX) val = GAMMA_MAX;
889     Stretch_gamma = (double) val / 100.0;
890     Gamma_Slider->SetValue(val);
891 
892     LoadImageLoggerSettings();
893     INDIConfig::LoadProfileSettings();
894 }
895 
SetupToolBar()896 void MyFrame::SetupToolBar()
897 {
898     MainToolbar = new wxAuiToolBar(this, -1, wxDefaultPosition, wxDefaultSize, wxAUI_TB_DEFAULT_STYLE);
899 
900 #   include "icons/loop.png.h"
901     wxBitmap loop_bmp(wxBITMAP_PNG_FROM_DATA(loop));
902 
903 #   include "icons/loop_disabled.png.h"
904     wxBitmap loop_bmp_disabled(wxBITMAP_PNG_FROM_DATA(loop_disabled));
905 
906 #   include "icons/guide.png.h"
907     wxBitmap guide_bmp(wxBITMAP_PNG_FROM_DATA(guide));
908 
909 #   include "icons/guide_disabled.png.h"
910     wxBitmap guide_bmp_disabled(wxBITMAP_PNG_FROM_DATA(guide_disabled));
911 
912 #   include "icons/stop.png.h"
913     wxBitmap stop_bmp(wxBITMAP_PNG_FROM_DATA(stop));
914 
915 #   include "icons/stop_disabled.png.h"
916     wxBitmap stop_bmp_disabled(wxBITMAP_PNG_FROM_DATA(stop_disabled));
917 
918 #   include "icons/auto_select.png.h"
919     wxBitmap auto_select_bmp(wxBITMAP_PNG_FROM_DATA(auto_select));
920 
921 #   include "icons/auto_select_disabled.png.h"
922     wxBitmap auto_select_disabled_bmp(wxBITMAP_PNG_FROM_DATA(auto_select_disabled));
923 
924 #   include "icons/connect.png.h"
925     wxBitmap connect_bmp(wxBITMAP_PNG_FROM_DATA(connect));
926 
927 #   include "icons/connect_disabled.png.h"
928     wxBitmap connect_bmp_disabled(wxBITMAP_PNG_FROM_DATA(connect_disabled));
929 
930 #   include "icons/brain.png.h"
931     wxBitmap brain_bmp(wxBITMAP_PNG_FROM_DATA(brain));
932 
933 #   include "icons/cam_setup.png.h"
934     wxBitmap cam_setup_bmp(wxBITMAP_PNG_FROM_DATA(cam_setup));
935 
936 #   include "icons/cam_setup_disabled.png.h"
937     wxBitmap cam_setup_bmp_disabled(wxBITMAP_PNG_FROM_DATA(cam_setup_disabled));
938 
939     int dur_values[] = {
940         10, 20, 50,
941         100, 200, 500, 1000, 1500,
942         2000, 2500, 3000, 3500, 4000, 4500, 5000,
943         6000, 7000, 8000, 9000, 10000, 15000,
944         30000, // final entry is custom value
945     };
946     for (unsigned int i = 0; i < WXSIZEOF(dur_values); i++)
947         exposure_durations.push_back(dur_values[i]);
948 
949     wxArrayString durs;
950     durs.Add(_("Auto"));
951     for (unsigned int i = 0; i < WXSIZEOF(dur_values) - 1; i++)
952         durs.Add(ExposureDurationLabel(dur_values[i]));
953     durs.Add(wxString::Format(_("Custom: %g s"), 9999.0));
954     durs.Add(_("Edit Custom..."));
955 
956     Dur_Choice = new wxComboBox(MainToolbar, BUTTON_DURATION, wxEmptyString, wxDefaultPosition, wxDefaultSize,
957         durs, wxCB_READONLY);
958     Dur_Choice->SetToolTip(_("Camera exposure duration"));
959     SetComboBoxWidth(Dur_Choice, 10);
960 
961     Gamma_Slider = new wxSlider(MainToolbar, CTRL_GAMMA, GAMMA_DEFAULT, GAMMA_MIN, GAMMA_MAX, wxPoint(-1,-1), wxSize(160,-1));
962     Gamma_Slider->SetBackgroundColour(wxColor(60, 60, 60));         // Slightly darker than toolbar background
963     Gamma_Slider->SetToolTip(_("Screen gamma (brightness)"));
964 
965     MainToolbar->AddTool(BUTTON_GEAR, connect_bmp, connect_bmp_disabled, false, 0, _("Connect to equipment. Shift-click to reconnect the same equipment last connected."));
966     MainToolbar->AddTool(BUTTON_LOOP, loop_bmp, loop_bmp_disabled, false, 0, _("Begin looping exposures for frame and focus"));
967     MainToolbar->AddTool(BUTTON_AUTOSTAR, auto_select_bmp, auto_select_disabled_bmp, false, 0, _("Auto-select Star. Shift-click to de-select star."));
968     MainToolbar->AddTool(BUTTON_GUIDE, guide_bmp, guide_bmp_disabled, false, 0, _("Begin guiding (PHD). Shift-click to force calibration."));
969     MainToolbar->AddTool(BUTTON_STOP, stop_bmp, stop_bmp_disabled, false, 0, _("Stop looping and guiding"));
970     MainToolbar->AddSeparator();
971     MainToolbar->AddControl(Dur_Choice, _("Exposure duration"));
972     MainToolbar->AddControl(Gamma_Slider, _("Gamma"));
973     MainToolbar->AddSeparator();
974     MainToolbar->AddTool(BUTTON_ADVANCED, _("Advanced Settings"), brain_bmp, _("Advanced Settings"));
975     MainToolbar->AddTool(BUTTON_CAM_PROPERTIES, cam_setup_bmp, cam_setup_bmp_disabled, false, 0, _("Camera settings"));
976     MainToolbar->EnableTool(BUTTON_CAM_PROPERTIES, false);
977     MainToolbar->EnableTool(BUTTON_LOOP, false);
978     MainToolbar->EnableTool(BUTTON_AUTOSTAR, false);
979     MainToolbar->EnableTool(BUTTON_GUIDE, false);
980     MainToolbar->EnableTool(BUTTON_STOP, false);
981     MainToolbar->Realize();
982 
983     MainToolbar->SetArtProvider(new PHDToolBarArt);             // Get the custom background we want
984 }
985 
SetupStatusBar()986 void MyFrame::SetupStatusBar()
987 {
988     m_statusbar = PHDStatusBar::CreateInstance(this, wxSTB_DEFAULT_STYLE);
989     SetStatusBar(m_statusbar);
990     PositionStatusBar();
991     UpdateStatusBarCalibrationStatus();
992 }
993 
SetupKeyboardShortcuts()994 void MyFrame::SetupKeyboardShortcuts()
995 {
996     wxAcceleratorEntry entries[] = {
997         wxAcceleratorEntry(wxACCEL_CTRL,  (int) '0', EEGG_CLEARCAL),
998         wxAcceleratorEntry(wxACCEL_CTRL,  (int) 'A', MENU_BRAIN),
999         wxAcceleratorEntry(wxACCEL_CTRL,  (int) 'C', MENU_CONNECT),
1000         wxAcceleratorEntry(wxACCEL_CTRL|wxACCEL_SHIFT,  (int) 'C', MENU_CONNECT),
1001         wxAcceleratorEntry(wxACCEL_CTRL,  (int) 'G', MENU_GUIDE),
1002         wxAcceleratorEntry(wxACCEL_CTRL,  (int) 'L', MENU_LOOP),
1003         wxAcceleratorEntry(wxACCEL_CTRL|wxACCEL_SHIFT,  (int) 'M', EEGG_MANUALCAL),
1004         wxAcceleratorEntry(wxACCEL_CTRL,  (int) 'S', MENU_STOP),
1005         wxAcceleratorEntry(wxACCEL_CTRL,  (int) 'D', BUTTON_ALERT_CLOSE),
1006     };
1007     wxAcceleratorTable accel(WXSIZEOF(entries), entries);
1008     SetAcceleratorTable(accel);
1009 }
1010 
1011 struct PHDHelpController : public wxHtmlHelpController
1012 {
PHDHelpControllerPHDHelpController1013     PHDHelpController()
1014     {
1015         UseConfig(pConfig->Global.GetWxConfig(), "/help");
1016     }
1017 };
1018 
SetupHelpFile()1019 void MyFrame::SetupHelpFile()
1020 {
1021     wxFileSystem::AddHandler(new wxZipFSHandler);
1022 
1023     int langid = wxGetApp().GetLocale().GetLanguage();
1024 
1025     // first try to find locale-specific help file
1026     wxString filename = wxGetApp().GetLocalesDir() + wxFILE_SEP_PATH
1027         + wxLocale::GetLanguageCanonicalName(langid) + wxFILE_SEP_PATH
1028         + _T("PHD2GuideHelp.zip");
1029 
1030     Debug.Write(wxString::Format("SetupHelpFile: langid=%d, locale-specific help = %s\n", langid, filename));
1031 
1032     if (!wxFileExists(filename))
1033     {
1034         filename = wxGetApp().GetPHDResourcesDir() + wxFILE_SEP_PATH
1035             + _T("PHD2GuideHelp.zip");
1036 
1037         Debug.Write(wxString::Format("SetupHelpFile: using default help %s\n", filename));
1038     }
1039 
1040     help = new PHDHelpController();
1041 
1042     if (!help->AddBook(filename))
1043     {
1044         Alert(wxString::Format(_("Could not find help file %s"), filename));
1045     }
1046 }
1047 
cond_update_tool(wxAuiToolBar * tb,int toolId,wxMenuItem * mi,bool enable)1048 static bool cond_update_tool(wxAuiToolBar *tb, int toolId, wxMenuItem *mi, bool enable)
1049 {
1050     bool ret = false;
1051     if (tb->GetToolEnabled(toolId) != enable)
1052     {
1053         tb->EnableTool(toolId, enable);
1054         mi->Enable(enable);
1055         ret = true;
1056     }
1057     return ret;
1058 }
1059 
UpdateButtonsStatus()1060 void MyFrame::UpdateButtonsStatus()
1061 {
1062     assert(wxThread::IsMain());
1063 
1064     bool need_update = false;
1065 
1066     bool const loop_enabled =
1067         (!CaptureActive || pGuider->IsCalibratingOrGuiding()) &&
1068         pCamera && pCamera->Connected;
1069 
1070     if (cond_update_tool(MainToolbar, BUTTON_LOOP, m_loopMenuItem, loop_enabled))
1071         need_update = true;
1072 
1073     if (cond_update_tool(MainToolbar, BUTTON_GEAR, m_connectMenuItem, !CaptureActive))
1074         need_update = true;
1075 
1076     if (cond_update_tool(MainToolbar, BUTTON_STOP, m_stopMenuItem, CaptureActive))
1077         need_update = true;
1078 
1079     if (cond_update_tool(MainToolbar, BUTTON_AUTOSTAR, m_autoSelectStarMenuItem, CaptureActive))
1080         need_update = true;
1081 
1082     bool dark_enabled = loop_enabled && !CaptureActive;
1083     if (dark_enabled ^ m_takeDarksMenuItem->IsEnabled())
1084     {
1085         m_takeDarksMenuItem->Enable(dark_enabled);
1086         need_update = true;
1087     }
1088 
1089     bool guiding_active = pGuider && pGuider->IsCalibratingOrGuiding();         // Not the same as 'bGuideable below
1090 
1091     if (!guiding_active ^ m_autoSelectStarMenuItem->IsEnabled())
1092     {
1093         m_autoSelectStarMenuItem->Enable(!guiding_active);
1094         cond_update_tool(MainToolbar, BUTTON_AUTOSTAR, m_autoSelectStarMenuItem, !guiding_active);
1095         need_update = true;
1096     }
1097 
1098     if (!guiding_active ^ m_refineDefMapMenuItem->IsEnabled())
1099     {
1100         m_refineDefMapMenuItem->Enable(!guiding_active);
1101         need_update = true;
1102     }
1103 
1104     bool mod_calibration_ok = !guiding_active && pMount && pMount->IsConnected();
1105     if (mod_calibration_ok ^ m_calibrationMenuItem->IsEnabled())
1106     {
1107         m_calibrationMenuItem->Enable(mod_calibration_ok);
1108         need_update = true;
1109     }
1110 
1111     bool bGuideable = pGuider->GetState() == STATE_SELECTED &&
1112         pMount && pMount->IsConnected();
1113 
1114     if (cond_update_tool(MainToolbar, BUTTON_GUIDE, m_guideMenuItem, bGuideable))
1115         need_update = true;
1116 
1117     bool cam_props = pCamera &&
1118         (pCamera->PropertyDialogType & PROPDLG_WHEN_CONNECTED) != 0 && pCamera->Connected;
1119 
1120     if (cond_update_tool(MainToolbar, BUTTON_CAM_PROPERTIES, m_cameraMenuItem, cam_props))
1121         need_update = true;
1122 
1123     if (pDriftTool)
1124     {
1125         // let the drift tool update its buttons too
1126         wxCommandEvent event(APPSTATE_NOTIFY_EVENT, GetId());
1127         event.SetEventObject(this);
1128         wxPostEvent(pDriftTool, event);
1129     }
1130     if (pPolarDriftTool)
1131     {
1132         // let the Polar drift tool update its buttons too
1133         wxCommandEvent event(APPSTATE_NOTIFY_EVENT, GetId());
1134         event.SetEventObject(this);
1135         wxPostEvent(pPolarDriftTool, event);
1136     }
1137     if (pStaticPaTool)
1138     {
1139         // let the static PA tool update its buttons too
1140         wxCommandEvent event(APPSTATE_NOTIFY_EVENT, GetId());
1141         event.SetEventObject(this);
1142         wxPostEvent(pStaticPaTool, event);
1143     }
1144 
1145     if (pCometTool)
1146         CometTool::UpdateCometToolControls(false);
1147 
1148     if (pGuidingAssistant)
1149         GuidingAssistant::UpdateUIControls();
1150 
1151     if (pierFlipToolWin)
1152         PierFlipTool::UpdateUIControls();
1153 
1154     if (need_update)
1155     {
1156         if (pGuider->GetState() < STATE_SELECTED)
1157             m_statusbar->ClearStarInfo();
1158         if (!guiding_active)
1159             m_statusbar->ClearGuiderInfo();
1160         Update();
1161         Refresh();
1162     }
1163 }
1164 
WrapText(wxWindow * win,const wxString & text,int width)1165 static wxString WrapText(wxWindow *win, const wxString& text, int width)
1166 {
1167     struct Wrapper : public wxTextWrapper
1168     {
1169         wxString m_str;
1170         Wrapper(wxWindow *win, const wxString& text, int width) {
1171             Wrap(win, text, width);
1172         }
1173         const wxString& Str() const { return m_str; }
1174         void OnOutputLine(const wxString& line) { m_str += line; }
1175         void OnNewLine() { m_str += '\n'; }
1176     };
1177     return Wrapper(win, text, width).Str();
1178 }
1179 
1180 struct alert_params
1181 {
1182     wxString msg;
1183     wxString buttonLabel;
1184     int flags;
1185     alert_fn *fnDontShow;
1186     alert_fn *fnSpecial;
1187     long arg;
1188     bool showHelp;
1189 };
1190 
OnAlertButton(wxCommandEvent & evt)1191 void MyFrame::OnAlertButton(wxCommandEvent& evt)
1192 {
1193     if (evt.GetId() == BUTTON_ALERT_ACTION && m_alertSpecialFn)
1194         (*m_alertSpecialFn)(m_alertFnArg);
1195     if (evt.GetId() == BUTTON_ALERT_DONTSHOW && m_alertDontShowFn)
1196     {
1197         (*m_alertDontShowFn)(m_alertFnArg);
1198         // Don't show should also mean close the window
1199         m_infoBar->Dismiss();
1200     }
1201     if (evt.GetId() == BUTTON_ALERT_CLOSE)
1202         m_infoBar->Dismiss();
1203 }
1204 
ClearAlert()1205 void MyFrame::ClearAlert()
1206 {
1207     m_infoBar->Dismiss();
1208 }
1209 
OnAlertHelp(wxCommandEvent & evt)1210 void MyFrame::OnAlertHelp(wxCommandEvent& evt)
1211 {
1212     // Any open help window will be re-directed
1213     help->Display(_("Trouble-shooting and Analysis"));
1214 }
1215 
1216 // Alerts may have a combination of 'Don't show', help, close, and 'Custom' buttons.  The 'close' button is added automatically if any of
1217 // the other buttons are present.
DoAlert(const alert_params & params)1218 void MyFrame::DoAlert(const alert_params& params)
1219 {
1220     Debug.Write(wxString::Format("Alert: %s\n", params.msg));
1221 
1222     m_alertDontShowFn = params.fnDontShow;
1223     m_alertSpecialFn = params.fnSpecial;
1224     m_alertFnArg = params.arg;
1225 
1226     int buttonSpace = 80;
1227     m_infoBar->RemoveButton(BUTTON_ALERT_ACTION);
1228     m_infoBar->RemoveButton(BUTTON_ALERT_CLOSE);
1229     m_infoBar->RemoveButton(BUTTON_ALERT_HELP);
1230     m_infoBar->RemoveButton(BUTTON_ALERT_DONTSHOW);
1231     if (params.fnDontShow)
1232     {
1233         m_infoBar->AddButton(BUTTON_ALERT_DONTSHOW, _("Don't show\n again"));
1234         buttonSpace += 80;
1235     }
1236     if (params.fnSpecial)
1237     {
1238         m_infoBar->AddButton(BUTTON_ALERT_ACTION, params.buttonLabel);
1239         buttonSpace += 80;
1240     }
1241     if (params.fnSpecial || params.fnDontShow || params.showHelp)
1242     {
1243         m_infoBar->AddButton(BUTTON_ALERT_CLOSE, _("Close"));
1244         buttonSpace += 80;
1245     }
1246     if (params.showHelp)
1247     {
1248         m_infoBar->AddButton(BUTTON_ALERT_HELP, _("Help"));
1249         buttonSpace += 80;
1250     }
1251     m_infoBar->ShowMessage(pFrame && pFrame->pGuider ? WrapText(m_infoBar, params.msg, wxMax(pFrame->pGuider->GetSize().GetWidth() - buttonSpace, 100)) : params.msg, params.flags);
1252     m_statusbar->UpdateStates();        // might have disconnected a device
1253     EvtServer.NotifyAlert(params.msg, params.flags);
1254 }
1255 
Alert(const wxString & msg,alert_fn * DontShowFn,const wxString & buttonLabel,alert_fn * SpecialFn,long arg,bool showHelpButton,int flags)1256 void MyFrame::Alert(const wxString& msg, alert_fn *DontShowFn, const wxString& buttonLabel,  alert_fn *SpecialFn, long arg, bool showHelpButton, int flags)
1257 {
1258     if (wxThread::IsMain())
1259     {
1260         alert_params params;
1261         params.msg = msg;
1262         params.buttonLabel = buttonLabel;
1263         params.flags = flags;
1264         params.fnDontShow = DontShowFn;
1265         params.fnSpecial = SpecialFn;
1266         params.arg = arg;
1267         params.showHelp = showHelpButton;
1268         DoAlert(params);
1269     }
1270     else
1271     {
1272         alert_params *params = new alert_params;
1273         params->msg = msg;
1274         params->buttonLabel = buttonLabel;
1275         params->flags = flags;
1276         params->fnDontShow = DontShowFn;
1277         params->fnSpecial = SpecialFn;
1278         params->arg = arg;
1279         params->showHelp = showHelpButton;
1280         wxThreadEvent *event = new wxThreadEvent(wxEVT_THREAD, ALERT_FROM_THREAD_EVENT);
1281         event->SetExtraLong((long) params);
1282         wxQueueEvent(this, event);
1283     }
1284 }
1285 
1286 // Standardized version for building an alert that has the "don't show again" option button.  Insures that debug log entry is made if
1287 // the user has blocked the alert for this type of problem
SuppressableAlert(const wxString & configPropKey,const wxString & msg,alert_fn * dontShowFn,long arg,bool showHelpButton,int flags)1288 void MyFrame::SuppressableAlert(const wxString& configPropKey, const wxString& msg, alert_fn *dontShowFn, long arg, bool showHelpButton, int flags)
1289 {
1290     if (pConfig->Global.GetBoolean(configPropKey, true))
1291     {
1292         Alert(msg, dontShowFn, wxEmptyString,  0, arg, showHelpButton);
1293     }
1294     else
1295         Debug.Write(wxString::Format("Suppressed alert:  %s\n", msg));
1296 }
1297 
Alert(const wxString & msg,int flags)1298 void MyFrame::Alert(const wxString& msg, int flags)
1299 {
1300     Alert(msg, 0, wxEmptyString, 0, 0, false, flags);
1301 }
1302 
OnAlertFromThread(wxThreadEvent & event)1303 void MyFrame::OnAlertFromThread(wxThreadEvent& event)
1304 {
1305     alert_params *params = (alert_params *) event.GetExtraLong();
1306     DoAlert(*params);
1307     delete params;
1308 }
1309 
OnReconnectCameraFromThread(wxThreadEvent & event)1310 void MyFrame::OnReconnectCameraFromThread(wxThreadEvent& event)
1311 {
1312     DoTryReconnect();
1313 }
1314 
TryReconnect()1315 void MyFrame::TryReconnect()
1316 {
1317     if (wxThread::IsMain())
1318         DoTryReconnect();
1319     else
1320     {
1321         Debug.Write("worker thread queueing reconnect event to GUI thread\n");
1322         wxThreadEvent *event = new wxThreadEvent(wxEVT_THREAD, RECONNECT_CAMERA_EVENT);
1323         wxQueueEvent(this, event);
1324     }
1325 }
1326 
DoTryReconnect()1327 void MyFrame::DoTryReconnect()
1328 {
1329     // do not reconnect more than 3 times in 1 minute
1330     enum { TIME_WINDOW = 60, MAX_ATTEMPTS = 3 };
1331     time_t now = wxDateTime::GetTimeNow();
1332     Debug.Write(wxString::Format("Try camera reconnect, now = %lu\n", (unsigned long) now));
1333     while (m_cameraReconnectAttempts.size() > 0 && now - m_cameraReconnectAttempts[0] > TIME_WINDOW)
1334         m_cameraReconnectAttempts.erase(m_cameraReconnectAttempts.begin());
1335     if (m_cameraReconnectAttempts.size() + 1 > MAX_ATTEMPTS)
1336     {
1337         Debug.Write(wxString::Format("More than %d camera reconnect attempts in less than %d seconds, "
1338             "return without reconnect.\n", MAX_ATTEMPTS, TIME_WINDOW));
1339         OnExposeComplete(0, true);
1340         return;
1341     }
1342     m_cameraReconnectAttempts.push_back(now);
1343 
1344     bool err = pGearDialog->ReconnectCamera();
1345     if (err)
1346     {
1347         Debug.Write("Camera Re-connect failed\n");
1348         // complete the pending exposure notification
1349         OnExposeComplete(0, true);
1350     }
1351     else
1352     {
1353         Debug.Write("Camera Re-connect succeeded, resume exposures\n");
1354         UpdateStatusBarStateLabels();
1355         m_exposurePending = false; // exposure no longer pending
1356         ScheduleExposure();
1357     }
1358 }
1359 
1360 /*
1361  * The base class wxFrame::StatusMsg() is not
1362  * safe to call from worker threads.
1363  *
1364  * For non-main threads this routine queues the request
1365  * to the frames event queue, and it gets displayed by the main
1366  * thread as part of event processing.
1367  *
1368  */
1369 
1370 // Use a timer to show a status message for 10 seconds, then revert back to basic state info
StartStatusBarTimer(wxTimer & timer)1371 static void StartStatusBarTimer(wxTimer& timer)
1372 {
1373     const int DISPLAY_MS = 10000;
1374     timer.Start(DISPLAY_MS, wxTIMER_ONE_SHOT);
1375 }
1376 
SetStatusMsg(PHDStatusBar * statusbar,const wxString & text)1377 static void SetStatusMsg(PHDStatusBar *statusbar, const wxString& text)
1378 {
1379     Debug.Write(wxString::Format("Status Line: %s\n", text));
1380     statusbar->StatusMsg(text);
1381 }
1382 
1383 enum StatusBarThreadMsgType
1384 {
1385     THR_SB_MSG_TEXT,
1386     THR_SB_STATE_LABELS,
1387     THR_SB_CALIBRATION,
1388     THR_SB_BUTTON_STATE,
1389 };
1390 
QueueStatusBarTextMsg(wxEvtHandler * frame,const wxString & text,bool withTimeout)1391 static void QueueStatusBarTextMsg(wxEvtHandler *frame, const wxString& text, bool withTimeout)
1392 {
1393     wxThreadEvent *event = new wxThreadEvent(wxEVT_THREAD, SET_STATUS_TEXT_EVENT);
1394     event->SetExtraLong(THR_SB_MSG_TEXT);
1395     event->SetString(text);
1396     event->SetInt(withTimeout);
1397     wxQueueEvent(frame, event);
1398 }
1399 
QueueStatusBarUpdateMsg(wxEvtHandler * frame,StatusBarThreadMsgType type)1400 static void QueueStatusBarUpdateMsg(wxEvtHandler *frame, StatusBarThreadMsgType type)
1401 {
1402     wxThreadEvent *event = new wxThreadEvent(wxEVT_THREAD, SET_STATUS_TEXT_EVENT);
1403     event->SetExtraLong(type);
1404     wxQueueEvent(frame, event);
1405 }
1406 
StatusMsg(const wxString & text)1407 void MyFrame::StatusMsg(const wxString& text)
1408 {
1409     if (wxThread::IsMain())
1410     {
1411         SetStatusMsg(m_statusbar, text);
1412         StartStatusBarTimer(m_statusbarTimer);
1413     }
1414     else
1415         QueueStatusBarTextMsg(this, text, true);
1416 }
1417 
StatusMsgNoTimeout(const wxString & text)1418 void MyFrame::StatusMsgNoTimeout(const wxString& text)
1419 {
1420     if (wxThread::IsMain())
1421         SetStatusMsg(m_statusbar, text);
1422     else
1423         QueueStatusBarTextMsg(this, text, false);
1424 }
1425 
OnStatusMsg(wxThreadEvent & event)1426 void MyFrame::OnStatusMsg(wxThreadEvent& event)
1427 {
1428     switch (event.GetExtraLong()) {
1429     case THR_SB_MSG_TEXT: {
1430         wxString msg(event.GetString());
1431         bool withTimeout = event.GetInt() ? true : false;
1432 
1433         SetStatusMsg(m_statusbar, msg);
1434 
1435         if (withTimeout)
1436             StartStatusBarTimer(m_statusbarTimer);
1437         break;
1438     }
1439     case THR_SB_STATE_LABELS:
1440         m_statusbar->UpdateStates();
1441         break;
1442     case THR_SB_CALIBRATION:
1443         UpdateStatusBarCalibrationStatus();
1444         break;
1445     case THR_SB_BUTTON_STATE:
1446         UpdateButtonsStatus();
1447         break;
1448     }
1449 }
1450 
UpdateStatusBarStarInfo(double SNR,bool Saturated)1451 void MyFrame::UpdateStatusBarStarInfo(double SNR, bool Saturated)
1452 {
1453     assert(wxThread::IsMain());
1454     m_statusbar->UpdateStarInfo(SNR, Saturated);
1455 }
1456 
UpdateStatusBarStateLabels()1457 void MyFrame::UpdateStatusBarStateLabels()
1458 {
1459     if (wxThread::IsMain())
1460         m_statusbar->UpdateStates();
1461     else
1462         QueueStatusBarUpdateMsg(this, THR_SB_STATE_LABELS);
1463 }
1464 
UpdateStatusBarCalibrationStatus()1465 void MyFrame::UpdateStatusBarCalibrationStatus()
1466 {
1467     if (wxThread::IsMain())
1468     {
1469         m_statusbar->UpdateStates();
1470         if (pStatsWin)
1471             pStatsWin->UpdateScopePointing();
1472     }
1473     else
1474     {
1475         QueueStatusBarUpdateMsg(this, THR_SB_CALIBRATION);
1476     }
1477 }
1478 
NotifyUpdateButtonsStatus()1479 void MyFrame::NotifyUpdateButtonsStatus()
1480 {
1481     if (wxThread::IsMain())
1482     {
1483         UpdateButtonsStatus();
1484     }
1485     else
1486     {
1487         QueueStatusBarUpdateMsg(this, THR_SB_BUTTON_STATE);
1488     }
1489 }
1490 
UpdateStatusBarGuiderInfo(const GuideStepInfo & info)1491 void MyFrame::UpdateStatusBarGuiderInfo(const GuideStepInfo& info)
1492 {
1493     Debug.Write(wxString::Format("GuideStep: %.1f px %d ms %s, %.1f px %d ms %s\n",
1494         info.mountOffset.X, info.durationRA, info.directionRA == EAST ? "EAST" : "WEST",
1495         info.mountOffset.Y, info.durationDec, info.directionDec == NORTH ? "NORTH" : "SOUTH"));
1496 
1497     assert(wxThread::IsMain());
1498     m_statusbar->UpdateGuiderInfo(info);
1499 }
1500 
ClearStatusBarGuiderInfo()1501 void MyFrame::ClearStatusBarGuiderInfo()
1502 {
1503     assert(wxThread::IsMain());
1504     m_statusbar->ClearGuiderInfo();
1505 }
1506 
OnUpgrade(wxCommandEvent & evt)1507 void MyFrame::OnUpgrade(wxCommandEvent& evt)
1508 {
1509     PHD2Updater::CheckNow();
1510 }
1511 
NotifyUpdaterStateChanged()1512 void MyFrame::NotifyUpdaterStateChanged()
1513 {
1514     wxThreadEvent *event = new wxThreadEvent(wxEVT_THREAD, UPDATER_EVENT);
1515     wxQueueEvent(this, event);
1516 }
1517 
OnUpdaterStateChanged(wxThreadEvent & event)1518 void MyFrame::OnUpdaterStateChanged(wxThreadEvent& event)
1519 {
1520     PHD2Updater::OnUpdaterStateChanged();
1521 }
1522 
StartWorkerThread(WorkerThread * & pWorkerThread)1523 bool MyFrame::StartWorkerThread(WorkerThread*& pWorkerThread)
1524 {
1525     bool bError = false;
1526     wxCriticalSectionLocker lock(m_CSpWorkerThread);
1527 
1528     try
1529     {
1530         Debug.Write(wxString::Format("StartWorkerThread(%p) begins\n", pWorkerThread));
1531 
1532         if (!pWorkerThread || !pWorkerThread->IsRunning())
1533         {
1534             delete pWorkerThread;
1535             pWorkerThread = new WorkerThread(this);
1536 
1537             if (pWorkerThread->Create() != wxTHREAD_NO_ERROR)
1538             {
1539                 throw ERROR_INFO("Could not Create() the worker thread!");
1540             }
1541 
1542             if (pWorkerThread->Run() != wxTHREAD_NO_ERROR)
1543             {
1544                 throw ERROR_INFO("Could not Run() the worker thread!");
1545             }
1546         }
1547     }
1548     catch (const wxString& Msg)
1549     {
1550         POSSIBLY_UNUSED(Msg);
1551         delete pWorkerThread;
1552         pWorkerThread = nullptr;
1553         bError = true;
1554     }
1555 
1556     Debug.Write(wxString::Format("StartWorkerThread(%p) ends\n", pWorkerThread));
1557 
1558     return bError;
1559 }
1560 
StopWorkerThread(WorkerThread * & pWorkerThread)1561 bool MyFrame::StopWorkerThread(WorkerThread*& pWorkerThread)
1562 {
1563     bool killed = false;
1564 
1565     wxCriticalSectionLocker lock(m_CSpWorkerThread);
1566 
1567     Debug.Write(wxString::Format("StopWorkerThread(0x%p) begins\n", pWorkerThread));
1568 
1569     if (pWorkerThread && pWorkerThread->IsRunning())
1570     {
1571         pWorkerThread->EnqueueWorkerThreadTerminateRequest();
1572 
1573         enum { TIMEOUT_MS = 1000 };
1574         wxStopWatch swatch;
1575         while (pWorkerThread->IsAlive() && swatch.Time() < TIMEOUT_MS)
1576             wxGetApp().Yield();
1577 
1578         if (pWorkerThread->IsAlive())
1579         {
1580             while (pWorkerThread->IsAlive() && !pWorkerThread->IsKillable())
1581             {
1582                 Debug.Write(wxString::Format("Worker thread 0x%p is not killable, waiting...\n", pWorkerThread));
1583                 wxStopWatch swatch2;
1584                 while (pWorkerThread->IsAlive() && !pWorkerThread->IsKillable() && swatch2.Time() < TIMEOUT_MS)
1585                     wxGetApp().Yield();
1586             }
1587             if (pWorkerThread->IsAlive())
1588             {
1589                 Debug.Write(wxString::Format("StopWorkerThread(0x%p) thread did not terminate, force kill\n", pWorkerThread));
1590                 pWorkerThread->Kill();
1591                 killed = true;
1592             }
1593         }
1594         else
1595         {
1596             wxThread::ExitCode threadExitCode = pWorkerThread->Wait();
1597             Debug.Write(wxString::Format("StopWorkerThread() threadExitCode=%d\n", threadExitCode));
1598         }
1599     }
1600 
1601     Debug.Write(wxString::Format("StopWorkerThread(0x%p) ends\n", pWorkerThread));
1602 
1603     delete pWorkerThread;
1604     pWorkerThread = nullptr;
1605 
1606     return killed;
1607 }
1608 
OnRequestExposure(wxCommandEvent & evt)1609 void MyFrame::OnRequestExposure(wxCommandEvent& evt)
1610 {
1611     EXPOSE_REQUEST *req = (EXPOSE_REQUEST *) evt.GetClientData();
1612     bool error = GuideCamera::Capture(pCamera, req->exposureDuration, *req->pImage, req->options, req->subframe);
1613     req->error = error;
1614     req->pSemaphore->Post();
1615 }
1616 
OnRequestMountMove(wxCommandEvent & evt)1617 void MyFrame::OnRequestMountMove(wxCommandEvent& evt)
1618 {
1619     MOVE_REQUEST *request = (MOVE_REQUEST *) evt.GetClientData();
1620 
1621     Debug.Write("OnRequestMountMove() begins\n");
1622 
1623     if (request->axisMove)
1624     {
1625         request->moveResult = request->mount->MoveAxis(request->direction, request->duration, request->moveOptions);
1626     }
1627     else
1628     {
1629         request->moveResult = request->mount->MoveOffset(&request->ofs, request->moveOptions);
1630     }
1631 
1632     request->semaphore->Post();
1633     Debug.Write("OnRequestMountMove() ends\n");
1634 }
1635 
OnStatusBarTimerEvent(wxTimerEvent & evt)1636 void MyFrame::OnStatusBarTimerEvent(wxTimerEvent& evt)
1637 {
1638     if (pGuider->IsGuiding())
1639         m_statusbar->StatusMsg(_("Guiding"));
1640     else if (CaptureActive)
1641         m_statusbar->StatusMsg(_("Looping"));
1642     else
1643         m_statusbar->StatusMsg(wxEmptyString);
1644 }
1645 
ScheduleExposure()1646 void MyFrame::ScheduleExposure()
1647 {
1648     int exposureDuration = RequestedExposureDuration();
1649     int exposureOptions = GetRawImageMode() ? CAPTURE_BPM_REVIEW : CAPTURE_LIGHT;
1650     const wxRect& subframe =
1651         m_singleExposure.enabled ? m_singleExposure.subframe : pGuider->GetBoundingBox();
1652 
1653     Debug.Write(wxString::Format("ScheduleExposure(%d,%x,%d) exposurePending=%d\n",
1654         exposureDuration, exposureOptions, !subframe.IsEmpty(), m_exposurePending));
1655 
1656     assert(wxThread::IsMain()); // m_exposurePending only updated in main thread
1657     assert(!m_exposurePending);
1658 
1659     m_exposurePending = true;
1660 
1661     usImage *img = new usImage();
1662 
1663     wxCriticalSectionLocker lock(m_CSpWorkerThread);
1664 
1665     if (m_pPrimaryWorkerThread) // can be null when app is shutting down (unlikely but possible)
1666         m_pPrimaryWorkerThread->EnqueueWorkerThreadExposeRequest(img, exposureDuration, exposureOptions, subframe);
1667 }
1668 
SchedulePrimaryMove(Mount * mount,const GuiderOffset & ofs,unsigned int moveOptions)1669 void MyFrame::SchedulePrimaryMove(Mount *mount, const GuiderOffset& ofs, unsigned int moveOptions)
1670 {
1671     Debug.Write(wxString::Format("SchedulePrimaryMove(%p, x=%.2f, y=%.2f, opts=%u)\n", mount, ofs.cameraOfs.X, ofs.cameraOfs.Y, moveOptions));
1672 
1673     wxCriticalSectionLocker lock(m_CSpWorkerThread);
1674 
1675     assert(mount);
1676 
1677     // Manual moves do not affect the request count for IsBusy()
1678     if ((moveOptions & MOVEOPT_MANUAL) == 0)
1679         mount->IncrementRequestCount();
1680 
1681     assert(m_pPrimaryWorkerThread);
1682     m_pPrimaryWorkerThread->EnqueueWorkerThreadMoveRequest(mount, ofs, moveOptions);
1683 }
1684 
ScheduleSecondaryMove(Mount * mount,const GuiderOffset & ofs,unsigned int moveOptions)1685 void MyFrame::ScheduleSecondaryMove(Mount *mount, const GuiderOffset& ofs, unsigned int moveOptions)
1686 {
1687     Debug.Write(wxString::Format("ScheduleSecondaryMove(%p, x=%.2f, y=%.2f, opts=%u)\n", mount, ofs.cameraOfs.X, ofs.cameraOfs.Y, moveOptions));
1688 
1689     wxCriticalSectionLocker lock(m_CSpWorkerThread);
1690 
1691     assert(mount);
1692 
1693     if (mount->SynchronousOnly())
1694     {
1695         // some mounts must run on the Primary thread even if the secondary is requested
1696         // to ensure synchronous ST4 guide / camera exposure
1697         SchedulePrimaryMove(mount, ofs, moveOptions);
1698     }
1699     else
1700     {
1701         if ((moveOptions & MOVEOPT_MANUAL) == 0)
1702             mount->IncrementRequestCount();
1703 
1704         assert(m_pSecondaryWorkerThread);
1705         m_pSecondaryWorkerThread->EnqueueWorkerThreadMoveRequest(mount, ofs, moveOptions);
1706     }
1707 }
1708 
ScheduleAxisMove(Mount * mount,const GUIDE_DIRECTION direction,int duration,unsigned int moveOptions)1709 void MyFrame::ScheduleAxisMove(Mount *mount, const GUIDE_DIRECTION direction, int duration, unsigned int moveOptions)
1710 {
1711     wxCriticalSectionLocker lock(m_CSpWorkerThread);
1712 
1713     assert(mount);
1714 
1715     if ((moveOptions & MOVEOPT_MANUAL) == 0)
1716         mount->IncrementRequestCount();
1717 
1718     assert(m_pPrimaryWorkerThread);
1719     m_pPrimaryWorkerThread->EnqueueWorkerThreadAxisMove(mount, direction, duration, moveOptions);
1720 }
1721 
ScheduleManualMove(Mount * mount,const GUIDE_DIRECTION direction,int duration)1722 void MyFrame::ScheduleManualMove(Mount *mount, const GUIDE_DIRECTION direction, int duration)
1723 {
1724     GuideLog.NotifyManualGuide(mount, direction, duration);
1725 
1726     GuideStepInfo step = { 0 };
1727     step.mount = mount;
1728     step.moveOptions = MOVEOPT_MANUAL;
1729     step.mountOffset.SetXY(0., 0.);
1730     switch (direction)
1731     {
1732     case GUIDE_DIRECTION::NORTH:
1733     case GUIDE_DIRECTION::SOUTH:
1734         step.durationDec = duration;
1735         step.directionDec = direction;
1736         break;
1737     case GUIDE_DIRECTION::EAST:
1738     case GUIDE_DIRECTION::WEST:
1739         step.durationRA = duration;
1740         step.directionRA = direction;
1741         break;
1742     default:
1743         break;
1744     }
1745     UpdateStatusBarGuiderInfo(step);
1746 
1747     ScheduleAxisMove(mount, direction, duration, MOVEOPT_MANUAL);
1748 }
1749 
StartSingleExposure(int duration,const wxRect & subframe)1750 bool MyFrame::StartSingleExposure(int duration, const wxRect& subframe)
1751 {
1752     Debug.Write(wxString::Format("StartSingleExposure duration=%d\n", duration));
1753 
1754     if (!pCamera || !pCamera->Connected)
1755     {
1756         Debug.Write("StartSingleExposure: camera not connected\n");
1757         return true;
1758     }
1759 
1760     StatusMsgNoTimeout(_("Capturing single exposure"));
1761 
1762     m_singleExposure.enabled = true;
1763     m_singleExposure.duration = duration;
1764     m_singleExposure.subframe = subframe;
1765     if (!m_singleExposure.subframe.IsEmpty())
1766         m_singleExposure.subframe.Intersect(wxRect(pCamera->FullSize));
1767 
1768     StartCapturing();
1769 
1770     return false;
1771 }
1772 
AutoSelectStar(const wxRect & roi)1773 bool MyFrame::AutoSelectStar(const wxRect& roi)
1774 {
1775     if (pGuider->IsCalibratingOrGuiding())
1776     {
1777         Debug.Write("cannot auto-select star while calibrating or guiding\n");
1778         return true; // error
1779     }
1780 
1781     return pGuider->AutoSelect(roi);
1782 }
1783 
StartCapturing()1784 void MyFrame::StartCapturing()
1785 {
1786     Debug.Write(wxString::Format("StartCapturing CaptureActive=%d continueCapturing=%d exposurePending=%d\n", CaptureActive, m_continueCapturing, m_exposurePending));
1787 
1788     if (!CaptureActive)
1789     {
1790         if (!m_singleExposure.enabled)
1791             m_continueCapturing = true;
1792 
1793         CaptureActive = true;
1794         m_frameCounter = 0;
1795 
1796         CheckDarkFrameGeometry();
1797         UpdateButtonsStatus();
1798 
1799         // m_exposurePending should always be false here since CaptureActive is cleared on exposure
1800         // completion, but be paranoid and check it anyway
1801 
1802         if (!m_exposurePending)
1803         {
1804             pCamera->InitCapture();
1805             ScheduleExposure();
1806         }
1807     }
1808 }
1809 
StopCapturing()1810 bool MyFrame::StopCapturing()
1811 {
1812     Debug.Write(wxString::Format("StopCapturing CaptureActive=%d continueCapturing=%d exposurePending=%d\n", CaptureActive, m_continueCapturing, m_exposurePending));
1813 
1814     bool finished = true;
1815     bool continueCapturing = m_continueCapturing;
1816 
1817     if (pGuider->IsPaused())
1818     {
1819         // setting m_continueCapturing to false before calling
1820         // SetPaused(PAUSE_NONE) ensures that SetPaused(PAUSE_NONE)
1821         // does not schedule another exposure
1822         m_continueCapturing = false;
1823         SetPaused(PAUSE_NONE);
1824     }
1825 
1826     if (continueCapturing || m_exposurePending)
1827     {
1828         StatusMsgNoTimeout(_("Waiting for devices..."));
1829         m_continueCapturing = false;
1830 
1831         if (m_exposurePending)
1832         {
1833             m_pPrimaryWorkerThread->RequestStop();
1834             finished = false;
1835         }
1836         else
1837         {
1838             CaptureActive = false;
1839             if (pGuider->IsCalibratingOrGuiding())
1840             {
1841                 pGuider->StopGuiding();
1842                 pGuider->UpdateImageDisplay();
1843             }
1844             FinishStop();
1845         }
1846     }
1847 
1848     return finished;
1849 }
1850 
SetPaused(PauseType pause)1851 void MyFrame::SetPaused(PauseType pause)
1852 {
1853     bool const isPaused = pGuider->IsPaused();
1854 
1855     Debug.Write(wxString::Format("SetPaused type=%d isPaused=%d exposurePending=%d\n", pause, isPaused, m_exposurePending));
1856 
1857     if (pause != PAUSE_NONE && !isPaused)
1858     {
1859         pGuider->SetPaused(pause);
1860         StatusMsgNoTimeout(_("Paused") + (pause == PAUSE_FULL ? _("/full") : _("/looping")));
1861         GuideLog.ServerCommand(pGuider, "PAUSE");
1862         EvtServer.NotifyPaused();
1863     }
1864     else if (pause == PAUSE_NONE && isPaused)
1865     {
1866         pGuider->SetPaused(PAUSE_NONE);
1867         if (pMount)
1868         {
1869             Debug.Write("un-pause: clearing mount guide algorithm history\n");
1870             pMount->NotifyGuidingResumed();
1871         }
1872         if (m_continueCapturing && !m_exposurePending)
1873             ScheduleExposure();
1874         StatusMsg(_("Resumed"));
1875         GuideLog.ServerCommand(pGuider, "RESUME");
1876         EvtServer.NotifyResumed();
1877     }
1878 }
1879 
StartLooping()1880 bool MyFrame::StartLooping()
1881 {
1882     bool error = false;
1883 
1884     try
1885     {
1886         if (!pCamera || !pCamera->Connected)
1887         {
1888             throw ERROR_INFO("Camera not connected");
1889         }
1890 
1891         if (CaptureActive)
1892         {
1893             // if we are guiding, stop guiding and go back to looping
1894             if (pGuider->IsCalibratingOrGuiding())
1895             {
1896                 pGuider->StopGuiding();
1897             }
1898             else
1899             {
1900                 // already looping, nothing to do
1901                 return false;
1902             }
1903         }
1904 
1905         StatusMsg(_("Looping"));
1906         StartCapturing();
1907     }
1908     catch (const wxString& Msg)
1909     {
1910         POSSIBLY_UNUSED(Msg);
1911         error = true;
1912     }
1913 
1914     return error;
1915 }
1916 
StartGuiding()1917 bool MyFrame::StartGuiding()
1918 {
1919     bool error = true;
1920 
1921     if (pRefineDefMap && pRefineDefMap->IsShown())
1922     {
1923         Alert(_("Cannot guide while refining a Bad-pixel Map. Please close the Refine Bad-pixel Map window."));
1924         return error;
1925     }
1926 
1927     wxGetApp().CheckLogRollover();
1928 
1929     if (pMount && pMount->IsConnected() &&
1930         pCamera && pCamera->Connected &&
1931         pGuider->GetState() >= STATE_SELECTED)
1932     {
1933         pGuider->StartGuiding();
1934         StartCapturing();
1935         UpdateButtonsStatus();
1936         // reset dither state when guiding starts
1937         m_ditherSpiral.Reset();
1938         error = false;
1939     }
1940 
1941     return error;
1942 }
1943 
Reset()1944 void DitherSpiral::Reset()
1945 {
1946     Debug.Write("reset dither spiral\n");
1947     x = y = 0;
1948     dx = -1;
1949     dy = 0;
1950     prevRaOnly = false;
1951 }
1952 
ROT(int & dx,int & dy)1953 inline static void ROT(int& dx, int& dy)
1954 {
1955     int t = -dx;
1956     dx = dy;
1957     dy = t;
1958 }
1959 
GetDither(double amount,bool raOnly,double * dRa,double * dDec)1960 void DitherSpiral::GetDither(double amount, bool raOnly, double *dRa, double *dDec)
1961 {
1962     // reset state when switching between ra only and ra/dec
1963     if (raOnly != prevRaOnly)
1964     {
1965         Reset();
1966         prevRaOnly = raOnly;
1967     }
1968 
1969     if (raOnly)
1970     {
1971         // x = 0,1,-1,-2,2,3,-3,-4,4,5,...
1972         ROT(dx, dy);
1973         int x0 = x;
1974         if (dy == 0)
1975             x = -x;
1976         else
1977             x += dy;
1978 
1979         *dRa = (double)(x - x0) * amount;
1980         *dDec = 0.0;
1981     }
1982     else
1983     {
1984         if (x == y || (x > 0 && x == -y) || (x <= 0 && y == 1 - x))
1985             ROT(dx, dy);
1986 
1987         x += dx;
1988         y += dy;
1989 
1990         *dRa  = (double) dx * amount;
1991         *dDec = (double) dy * amount;
1992     }
1993 }
1994 
SetDitherMode(DitherMode mode)1995 void MyFrame::SetDitherMode(DitherMode mode)
1996 {
1997     Debug.Write(wxString::Format("set dither mode %d\n", mode));
1998     m_ditherMode = mode;
1999     pConfig->Profile.SetInt("/DitherMode", mode);
2000 }
2001 
Dither(double amount,bool raOnly)2002 bool MyFrame::Dither(double amount, bool raOnly)
2003 {
2004     bool error = false;
2005 
2006     try
2007     {
2008         if (!pGuider->IsGuiding())
2009         {
2010             throw ERROR_INFO("cannot dither if not guiding");
2011         }
2012 
2013         amount *= m_ditherScaleFactor;
2014 
2015         double dRa = 0.0;
2016         double dDec = 0.0;
2017 
2018         if (m_ditherMode == DITHER_SPIRAL)
2019         {
2020             m_ditherSpiral.GetDither(amount, raOnly, &dRa, &dDec);
2021         }
2022         else
2023         {
2024             // DITHER_RANDOM
2025             dRa  =  amount * ((rand() / (double)RAND_MAX) * 2.0 - 1.0);
2026             dDec =  raOnly ? 0.0 : amount * ((rand() / (double)RAND_MAX) * 2.0 - 1.0);
2027         }
2028 
2029         Debug.Write(wxString::Format("dither: size=%.2f, dRA=%.2f dDec=%.2f\n", amount, dRa, dDec));
2030 
2031         bool err = pGuider->MoveLockPosition(PHD_Point(dRa, dDec));
2032         if (err)
2033         {
2034             throw ERROR_INFO("move lock failed");
2035         }
2036 
2037         StatusMsg(wxString::Format(_("Dither by %.2f,%.2f"), dRa, dDec));
2038         GuideLog.NotifyGuidingDithered(pGuider, dRa, dDec);
2039         EvtServer.NotifyGuidingDithered(dRa, dDec);
2040         DitherInfo info;
2041         info.timestamp = ::wxGetUTCTimeMillis().GetValue();
2042         info.dRa = dRa;
2043         info.dDec = dDec;
2044         pGraphLog->AppendData(info);
2045 
2046         if (pMount->IsStepGuider())
2047         {
2048             StepGuider *ao = static_cast<StepGuider *>(pMount);
2049             if (ao->GetBumpOnDither())
2050             {
2051                 Debug.Write("Dither: starting AO bump\n");
2052                 ao->ForceStartBump();
2053             }
2054         }
2055     }
2056     catch (const wxString& Msg)
2057     {
2058         POSSIBLY_UNUSED(Msg);
2059         error = true;
2060     }
2061 
2062     return error;
2063 }
2064 
GuidingRAOnly()2065 bool MyFrame::GuidingRAOnly()
2066 {
2067     const Scope *const scope = TheScope();
2068     return scope && scope->GetDecGuideMode() == DEC_NONE;
2069 }
2070 
CurrentGuideError() const2071 double MyFrame::CurrentGuideError() const
2072 {
2073     return pGuider->CurrentError(GuidingRAOnly());
2074 }
2075 
CurrentGuideErrorSmoothed() const2076 double MyFrame::CurrentGuideErrorSmoothed() const
2077 {
2078     return pGuider->CurrentErrorSmoothed(GuidingRAOnly());
2079 }
2080 
OnClose(wxCloseEvent & event)2081 void MyFrame::OnClose(wxCloseEvent& event)
2082 {
2083     if (CaptureActive && event.CanVeto())
2084     {
2085         bool confirmed = ConfirmDialog::Confirm(_("Are you sure you want to exit while capturing is active?"),
2086             "/quit_when_looping_ok", _("Confirm Exit"));
2087         if (!confirmed)
2088         {
2089             event.Veto();
2090             return;
2091         }
2092     }
2093 
2094     Debug.Write("MyFrame::OnClose proceeding\n");
2095 
2096     StopCapturing();
2097 
2098     bool killed = StopWorkerThread(m_pPrimaryWorkerThread);
2099     if (StopWorkerThread(m_pSecondaryWorkerThread))
2100         killed = true;
2101 
2102     // disconnect all gear
2103     pGearDialog->Shutdown(killed);
2104 
2105     PHD2Updater::StopUpdater();
2106 
2107     // stop the socket server and event server
2108     StartServer(false);
2109 
2110     GuideLog.CloseGuideLog();
2111 
2112     pConfig->Global.SetString("/perspective", m_mgr.SavePerspective());
2113     wxString geometry = wxString::Format("%c;%d;%d;%d;%d",
2114         this->IsMaximized() ? '1' : '0',
2115         this->GetSize().x, this->GetSize().y,
2116         this->GetScreenPosition().x, this->GetScreenPosition().y);
2117     pConfig->Global.SetString("/geometry", geometry);
2118 
2119     if (help->GetFrame())
2120         help->GetFrame()->Close();
2121     delete help;
2122     help = 0;
2123 
2124     Destroy();
2125 }
2126 
SetNoiseReductionMethod(int noiseReductionMethod)2127 bool MyFrame::SetNoiseReductionMethod(int noiseReductionMethod)
2128 {
2129     bool bError = false;
2130 
2131     try
2132     {
2133         switch (noiseReductionMethod)
2134         {
2135             case NR_NONE:
2136             case NR_2x2MEAN:
2137             case NR_3x3MEDIAN:
2138                 break;
2139             default:
2140                 throw ERROR_INFO("invalid noiseReductionMethod");
2141         }
2142         m_noiseReductionMethod = (NOISE_REDUCTION_METHOD)noiseReductionMethod;
2143     }
2144     catch (const wxString& Msg)
2145     {
2146         POSSIBLY_UNUSED(Msg);
2147 
2148         bError = true;
2149         m_noiseReductionMethod = (NOISE_REDUCTION_METHOD)DefaultNoiseReductionMethod;
2150     }
2151 
2152     pConfig->Profile.SetInt("/NoiseReductionMethod", m_noiseReductionMethod);
2153 
2154     return bError;
2155 }
2156 
SetDitherScaleFactor(double ditherScaleFactor)2157 bool MyFrame::SetDitherScaleFactor(double ditherScaleFactor)
2158 {
2159     bool bError = false;
2160 
2161     try
2162     {
2163         if (ditherScaleFactor <= 0)
2164         {
2165             throw ERROR_INFO("ditherScaleFactor <= 0");
2166         }
2167         m_ditherScaleFactor = ditherScaleFactor;
2168     }
2169     catch (const wxString& Msg)
2170     {
2171         POSSIBLY_UNUSED(Msg);
2172         bError = true;
2173         m_ditherScaleFactor = DefaultDitherScaleFactor;
2174     }
2175 
2176     pConfig->Profile.SetDouble("/DitherScaleFactor", m_ditherScaleFactor);
2177 
2178     return bError;
2179 }
2180 
SetDitherRaOnly(bool ditherRaOnly)2181 bool MyFrame::SetDitherRaOnly(bool ditherRaOnly)
2182 {
2183     bool bError = false;
2184 
2185     m_ditherRaOnly = ditherRaOnly;
2186 
2187     pConfig->Profile.SetBoolean("/DitherRaOnly", m_ditherRaOnly);
2188 
2189     return bError;
2190 }
2191 
NotifyGuidingStarted()2192 void MyFrame::NotifyGuidingStarted()
2193 {
2194     StatusMsg(_("Guiding"));
2195 
2196     m_guidingStarted = wxDateTime::UNow();
2197     m_guidingElapsed.Start();
2198     m_frameCounter = 0;
2199 
2200     if (pMount)
2201         pMount->NotifyGuidingStarted();
2202     if (pSecondaryMount)
2203         pSecondaryMount->NotifyGuidingStarted();
2204 
2205     GuideLog.GuidingStarted();
2206     EvtServer.NotifyGuidingStarted();
2207 }
2208 
NotifyGuidingStopped()2209 void MyFrame::NotifyGuidingStopped()
2210 {
2211     assert(!pMount || !pMount->IsBusy());
2212     assert(!pSecondaryMount || !pSecondaryMount->IsBusy());
2213 
2214     if (pMount)
2215         pMount->NotifyGuidingStopped();
2216     if (pSecondaryMount)
2217         pSecondaryMount->NotifyGuidingStopped();
2218 
2219     EvtServer.NotifyGuidingStopped();
2220     GuideLog.GuidingStopped();
2221     PhdController::AbortController("Guiding stopped");
2222 }
2223 
SetAutoLoadCalibration(bool val)2224 void MyFrame::SetAutoLoadCalibration(bool val)
2225 {
2226     if (m_autoLoadCalibration != val)
2227     {
2228         m_autoLoadCalibration = val;
2229         pConfig->Profile.SetBoolean("/AutoLoadCalibration", m_autoLoadCalibration);
2230     }
2231 }
2232 
guide_parity(int p)2233 inline static GuideParity guide_parity(int p)
2234 {
2235     switch (p) {
2236     case GUIDE_PARITY_EVEN: return GUIDE_PARITY_EVEN;
2237     case GUIDE_PARITY_ODD: return GUIDE_PARITY_ODD;
2238     default: return GUIDE_PARITY_UNKNOWN;
2239     }
2240 }
2241 
load_calibration(Mount * mnt)2242 static void load_calibration(Mount *mnt)
2243 {
2244     wxString prefix = "/" + mnt->GetMountClassName() + "/calibration/";
2245     if (!pConfig->Profile.HasEntry(prefix + "timestamp"))
2246         return;
2247 
2248     Calibration cal;
2249     cal.xRate = pConfig->Profile.GetDouble(prefix + "xRate", 1.0);
2250     cal.yRate = pConfig->Profile.GetDouble(prefix + "yRate", 1.0);
2251     cal.binning = (unsigned short) pConfig->Profile.GetInt(prefix + "binning", 1);
2252     cal.xAngle = pConfig->Profile.GetDouble(prefix + "xAngle", 0.0);
2253     cal.yAngle = pConfig->Profile.GetDouble(prefix + "yAngle", M_PI / 2.0);
2254     cal.declination = pConfig->Profile.GetDouble(prefix + "declination", 0.0);
2255     int t = pConfig->Profile.GetInt(prefix + "pierSide", PIER_SIDE_UNKNOWN);
2256     cal.pierSide = t == PIER_SIDE_EAST ? PIER_SIDE_EAST :
2257         t == PIER_SIDE_WEST ? PIER_SIDE_WEST : PIER_SIDE_UNKNOWN;
2258     cal.raGuideParity = guide_parity(pConfig->Profile.GetInt(prefix + "raGuideParity", GUIDE_PARITY_UNKNOWN));
2259     cal.decGuideParity = guide_parity(pConfig->Profile.GetInt(prefix + "decGuideParity", GUIDE_PARITY_UNKNOWN));
2260     cal.rotatorAngle = pConfig->Profile.GetDouble(prefix + "rotatorAngle", Rotator::POSITION_UNKNOWN);
2261     cal.isValid = true;
2262 
2263     mnt->SetCalibration(cal);
2264 }
2265 
LoadCalibration()2266 void MyFrame::LoadCalibration()
2267 {
2268     if (pMount)
2269     {
2270         load_calibration(pMount);
2271     }
2272     if (pSecondaryMount)
2273     {
2274         load_calibration(pSecondaryMount);
2275     }
2276 }
2277 
save_multi_darks(const ExposureImgMap & darks,const wxString & fname,const wxString & note)2278 static bool save_multi_darks(const ExposureImgMap& darks, const wxString& fname, const wxString& note)
2279 {
2280     bool bError = false;
2281 
2282     try
2283     {
2284         fitsfile *fptr;  // FITS file pointer
2285         int status = 0;  // CFITSIO status value MUST be initialized to zero!
2286 
2287         PHD_fits_create_file(&fptr, fname, true, &status);
2288         if (status)
2289             throw ERROR_INFO("fits_create_file failed");
2290 
2291         for (ExposureImgMap::const_iterator it = darks.begin(); it != darks.end(); ++it)
2292         {
2293             const usImage *const img = it->second;
2294             long fsize[] = {
2295                 (long)img->Size.GetWidth(),
2296                 (long)img->Size.GetHeight(),
2297             };
2298             if (!status)
2299                 fits_create_img(fptr, USHORT_IMG, 2, fsize, &status);
2300 
2301             float exposure = (float) img->ImgExpDur / 1000.0f;
2302             char *keyname = const_cast<char *>("EXPOSURE");
2303             char *comment = const_cast<char *>("Exposure time in seconds");
2304             if (!status) fits_write_key(fptr, TFLOAT, keyname, &exposure, comment, &status);
2305 
2306             if (!note.IsEmpty())
2307             {
2308                 char *USERNOTE = const_cast<char *>("USERNOTE");
2309                 if (!status) fits_write_key(fptr, TSTRING, USERNOTE, note.char_str(), nullptr, &status);
2310             }
2311 
2312             if (!status)
2313             {
2314                 long fpixel[3] = { 1, 1, 1 };
2315                 fits_write_pix(fptr, TUSHORT, fpixel, img->NPixels, img->ImageData, &status);
2316             }
2317 
2318             Debug.Write(wxString::Format("saving dark frame exposure = %d\n", img->ImgExpDur));
2319         }
2320 
2321         PHD_fits_close_file(fptr);
2322         bError = status ? true : false;
2323     }
2324     catch (const wxString& Msg)
2325     {
2326         POSSIBLY_UNUSED(Msg);
2327         bError = true;
2328     }
2329 
2330     return bError;
2331 }
2332 
load_multi_darks(GuideCamera * camera,const wxString & fname)2333 static bool load_multi_darks(GuideCamera *camera, const wxString& fname)
2334 {
2335     bool bError = false;
2336     fitsfile *fptr = 0;
2337     int status = 0;  // CFITSIO status value MUST be initialized to zero!
2338     long last_frame_size [] = { -1L, -1L };
2339 
2340     try
2341     {
2342         if (!wxFileExists(fname))
2343         {
2344             throw ERROR_INFO("File does not exist");
2345         }
2346 
2347         if (PHD_fits_open_diskfile(&fptr, fname, READONLY, &status) == 0)
2348         {
2349             int nhdus = 0;
2350             fits_get_num_hdus(fptr, &nhdus, &status);
2351 
2352             while (true)
2353             {
2354                 int hdutype;
2355                 fits_get_hdu_type(fptr, &hdutype, &status);
2356                 if (hdutype != IMAGE_HDU)
2357                 {
2358                     pFrame->Alert(wxString::Format(_("FITS file is not of an image: %s"), fname));
2359                     throw ERROR_INFO("FITS file is not an image");
2360                 }
2361 
2362                 int naxis;
2363                 fits_get_img_dim(fptr, &naxis, &status);
2364                 if (naxis != 2)
2365                 {
2366                     pFrame->Alert(wxString::Format(_("Unsupported type or read error loading FITS file %s"), fname));
2367                     throw ERROR_INFO("unsupported type");
2368                 }
2369 
2370                 long fsize[2];
2371                 fits_get_img_size(fptr, 2, fsize, &status);
2372                 if (last_frame_size[0] != -1L)
2373                 {
2374                     if (last_frame_size[0] != fsize[0] || last_frame_size[1] != fsize[1])
2375                     {
2376                         pFrame->Alert(_("Existing dark library has frames with incompatible formats - please rebuild the dark library from scratch."));
2377                         throw ERROR_INFO("Incompatible frame sizes in dark library");
2378                     }
2379                 }
2380                 last_frame_size[0] = fsize[0];
2381                 last_frame_size[1] = fsize[1];
2382 
2383                 std::unique_ptr<usImage> img(new usImage());
2384 
2385                 if (img->Init((int)fsize[0], (int)fsize[1]))
2386                 {
2387                     pFrame->Alert(wxString::Format(_("Memory allocation error reading FITS file %s"), fname));
2388                     throw ERROR_INFO("Memory Allocation failure");
2389                 }
2390 
2391                 long fpixel[] = { 1, 1, 1 };
2392                 if (fits_read_pix(fptr, TUSHORT, fpixel, fsize[0] * fsize[1], nullptr, img->ImageData, nullptr, &status))
2393                 {
2394                     pFrame->Alert(wxString::Format(_("Error reading data from %s"), fname));
2395                     throw ERROR_INFO("Error reading");
2396                 }
2397 
2398                 char keyname[] = "EXPOSURE";
2399                 float exposure;
2400                 if (fits_read_key(fptr, TFLOAT, keyname, &exposure, nullptr, &status))
2401                 {
2402                     exposure = (float)pFrame->RequestedExposureDuration() / 1000.0;
2403                     Debug.Write(wxString::Format("missing EXPOSURE value, assume %.3f\n", exposure));
2404                     status = 0;
2405                 }
2406                 img->ImgExpDur = ROUNDF(exposure * 1000.0);
2407 
2408                 img->CalcStats();
2409 
2410                 Debug.Write(wxString::Format("loaded dark frame exposure = %d, med = %u\n",
2411                                              img->ImgExpDur, img->MedianADU));
2412 
2413                 camera->AddDark(img.release());
2414 
2415                 // if this is the last hdu, we are done
2416                 int hdunr = 0;
2417                 fits_get_hdu_num(fptr, &hdunr);
2418                 if (status || hdunr >= nhdus)
2419                     break;
2420 
2421                 // move to the next hdu
2422                 fits_movrel_hdu(fptr, +1, nullptr, &status);
2423             }
2424         }
2425         else
2426         {
2427             pFrame->Alert(wxString::Format(_("Error opening FITS file %s"), fname));
2428             throw ERROR_INFO("error opening file");
2429         }
2430     }
2431     catch (const wxString& Msg)
2432     {
2433         POSSIBLY_UNUSED(Msg);
2434         bError = true;
2435     }
2436 
2437     if (fptr)
2438     {
2439         PHD_fits_close_file(fptr);
2440     }
2441 
2442     return bError;
2443 }
2444 
GetDarksDir()2445 wxString MyFrame::GetDarksDir()
2446 {
2447     wxString dirpath = GetDefaultFileDir() + PATHSEPSTR + "darks_defects";
2448     if (!wxDirExists(dirpath))
2449         if (!wxFileName::Mkdir(dirpath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL))
2450             dirpath = GetDefaultFileDir();             // should never happen
2451     return dirpath;
2452 }
2453 
DarkLibFileName(int profileId)2454 wxString MyFrame::DarkLibFileName(int profileId)
2455 {
2456     int inst = wxGetApp().GetInstanceNumber();
2457     return MyFrame::GetDarksDir() + PATHSEPSTR +
2458         wxString::Format("PHD2_dark_lib%s_%d.fit", inst > 1 ? wxString::Format("_%d", inst) : "", profileId);
2459 }
2460 
DarkLibExists(int profileId,bool showAlert)2461 bool MyFrame::DarkLibExists(int profileId, bool showAlert)
2462 {
2463     bool bOk = false;
2464     wxString fileName = MyFrame::DarkLibFileName(profileId);
2465 
2466     if (wxFileExists(fileName))
2467     {
2468         const wxSize& sensorSize = pCamera->DarkFrameSize();
2469         if (sensorSize == UNDEFINED_FRAME_SIZE)
2470         {
2471             bOk = true;
2472             Debug.Write("DarkLib check: undefined frame size for current camera\n");
2473         }
2474         else
2475         {
2476             fitsfile *fptr;
2477             int status = 0;  // CFITSIO status value MUST be initialized to zero!
2478 
2479             if (PHD_fits_open_diskfile(&fptr, fileName, READONLY, &status) == 0)
2480             {
2481                 long fsize[2];
2482                 fits_get_img_size(fptr, 2, fsize, &status);
2483                 if (status == 0 && fsize[0] == sensorSize.x && fsize[1] == sensorSize.y)
2484                     bOk = true;
2485                 else
2486                 {
2487                     Debug.Write(wxString::Format("DarkLib check: failed geometry check - fits status = %d, cam dimensions = {%d,%d}, "
2488                         " dark dimensions = {%d,%d}\n", status, sensorSize.x, sensorSize.y, fsize[0], fsize[1]));
2489 
2490                     if (showAlert)
2491                         Alert(_("Dark library does not match the camera in this profile - it needs to be replaced."));
2492                 }
2493 
2494                 PHD_fits_close_file(fptr);
2495             }
2496             else
2497                 Debug.Write(wxString::Format("DarkLib check: fitsio error on open_diskfile = %d\n", status));
2498         }
2499     }
2500 
2501     return bOk;
2502 }
2503 
2504 // Confirm that in-use darks or bpms have the same sensor size as the current camera.  Added to protect against
2505 // surprise changes in binning
CheckDarkFrameGeometry()2506 void MyFrame::CheckDarkFrameGeometry()
2507 {
2508     bool haveDefectMap = DefectMap::DefectMapExists(pConfig->GetCurrentProfileId(), m_useDefectMapMenuItem->IsEnabled());
2509     bool haveDarkLib = DarkLibExists(pConfig->GetCurrentProfileId(), m_useDarksMenuItem->IsEnabled());
2510     bool defectMapOk = true;
2511 
2512     if (m_useDefectMapMenuItem->IsEnabled())
2513     {
2514         if (!haveDefectMap)
2515         {
2516             if (m_useDefectMapMenuItem->IsChecked())
2517                 LoadDefectMapHandler(false);
2518             m_useDefectMapMenuItem->Enable(false);
2519             Debug.Write("CheckDarkFrameGeometry: BPM incompatibility found\n");
2520             defectMapOk = false;
2521         }
2522     }
2523     else if (haveDefectMap)
2524     {
2525         m_useDefectMapMenuItem->Enable(true);
2526     }
2527 
2528     if (m_useDarksMenuItem->IsEnabled())
2529     {
2530         if (!haveDarkLib)
2531         {
2532             if (m_useDarksMenuItem->IsChecked())
2533                 LoadDarkHandler(false);
2534             m_useDarksMenuItem->Enable(false);
2535             Debug.Write("CheckDarkFrameGeometry: Dark lib incompatibility found\n");
2536             if (!defectMapOk)
2537                 pFrame->Alert(_("Dark library and bad-pixel maps are incompatible with the current camera - both need to be replaced"));
2538         }
2539     }
2540     else if (haveDarkLib)
2541     {
2542         m_useDarksMenuItem->Enable(true);
2543     }
2544 
2545     m_prevDarkFrameSize = pCamera->DarkFrameSize();
2546     m_statusbar->UpdateStates();
2547 }
2548 
SetDarkMenuState()2549 void MyFrame::SetDarkMenuState()
2550 {
2551     bool haveDarkLib = DarkLibExists(pConfig->GetCurrentProfileId(), true);
2552     m_useDarksMenuItem->Enable(haveDarkLib);
2553     if (!haveDarkLib)
2554         m_useDarksMenuItem->Check(false);
2555     bool haveDefectMap = DefectMap::DefectMapExists(pConfig->GetCurrentProfileId(), true);
2556     m_useDefectMapMenuItem->Enable(haveDefectMap);
2557     if (!haveDefectMap)
2558         m_useDefectMapMenuItem->Check(false);
2559     m_statusbar->UpdateStates();
2560 }
2561 
LoadDarkLibrary()2562 bool MyFrame::LoadDarkLibrary()
2563 {
2564     wxString filename = MyFrame::DarkLibFileName(pConfig->GetCurrentProfileId());
2565 
2566     if (!pCamera || !pCamera->Connected)
2567     {
2568         Alert(_("You must connect a camera before loading dark frames"));
2569         return false;
2570     }
2571 
2572     if (load_multi_darks(pCamera, filename))
2573     {
2574         Debug.Write(wxString::Format("failed to load dark frames from %s\n", filename));
2575         StatusMsg(_("Darks not loaded"));
2576         return false;
2577     }
2578     else
2579     {
2580         Debug.Write(wxString::Format("loaded dark library from %s\n", filename));
2581         pCamera->SelectDark(m_exposureDuration);
2582         StatusMsg(_("Darks loaded"));
2583         return true;
2584     }
2585 }
2586 
SaveDarkLibrary(const wxString & note)2587 void MyFrame::SaveDarkLibrary(const wxString& note)
2588 {
2589     wxString filename = MyFrame::DarkLibFileName(pConfig->GetCurrentProfileId());
2590 
2591     Debug.Write("saving dark library\n");
2592 
2593     if (save_multi_darks(pCamera->Darks, filename, note))
2594     {
2595         Alert(wxString::Format(_("Error saving darks FITS file %s"), filename));
2596     }
2597 }
2598 
2599 // Delete both the dark library file and any defect map file for this profile
DeleteDarkLibraryFiles(int profileId)2600 void MyFrame::DeleteDarkLibraryFiles(int profileId)
2601 {
2602     wxString filename = MyFrame::DarkLibFileName(profileId);
2603 
2604     if (wxFileExists(filename))
2605     {
2606         Debug.Write(wxString::Format("Removing dark library file: %s\n", filename));
2607         wxRemoveFile(filename);
2608     }
2609 
2610     DefectMap::DeleteDefectMap(profileId);
2611 }
2612 
SetServerMode(bool serverMode)2613 bool MyFrame::SetServerMode(bool serverMode)
2614 {
2615     bool bError = false;
2616 
2617     m_serverMode = serverMode;
2618 
2619     pConfig->Global.SetBoolean("/ServerMode", m_serverMode);
2620 
2621     return bError;
2622 }
2623 
SetTimeLapse(int timeLapse)2624 bool MyFrame::SetTimeLapse(int timeLapse)
2625 {
2626     bool bError = false;
2627 
2628     try
2629     {
2630         if (timeLapse < 0)
2631         {
2632             throw ERROR_INFO("timeLapse < 0");
2633         }
2634 
2635         m_timeLapse = timeLapse;
2636     }
2637     catch (const wxString& Msg)
2638     {
2639         POSSIBLY_UNUSED(Msg);
2640         bError = true;
2641         m_timeLapse = DefaultTimelapse;
2642     }
2643 
2644     pConfig->Profile.SetInt("/frame/timeLapse", m_timeLapse);
2645 
2646     return bError;
2647 }
2648 
SetFocalLength(int focalLength)2649 bool MyFrame::SetFocalLength(int focalLength)
2650 {
2651     bool bError = false;
2652 
2653     try
2654     {
2655         if (focalLength < 0)
2656         {
2657             throw ERROR_INFO("focal length < 0");
2658         }
2659 
2660         m_focalLength = focalLength;
2661         if (pStatsWin)
2662             pStatsWin->ResetImageSize();
2663     }
2664     catch (const wxString& Msg)
2665     {
2666         POSSIBLY_UNUSED(Msg);
2667         bError = true;
2668         m_focalLength = DefaultFocalLength;
2669     }
2670 
2671     pConfig->Profile.SetInt("/frame/focalLength", m_focalLength);
2672 
2673     return bError;
2674 }
2675 
GetDefaultFileDir()2676 wxString MyFrame::GetDefaultFileDir()
2677 {
2678     wxStandardPathsBase& stdpath = wxStandardPaths::Get();
2679     wxString rslt = stdpath.GetUserLocalDataDir();          // Automatically includes app name
2680 
2681     if (!wxDirExists(rslt))
2682         if (!wxFileName::Mkdir(rslt, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL))
2683             rslt = stdpath.GetUserLocalDataDir();             // should never happen
2684 
2685     return rslt;
2686 }
2687 
GetCameraPixelScale() const2688 double MyFrame::GetCameraPixelScale() const
2689 {
2690     if (!pCamera || pCamera->GetCameraPixelSize() == 0.0 || m_focalLength == 0)
2691         return 1.0;
2692 
2693     return GetPixelScale(pCamera->GetCameraPixelSize(), m_focalLength, pCamera->Binning);
2694 }
2695 
PixelScaleSummary() const2696 wxString MyFrame::PixelScaleSummary() const
2697 {
2698     double pixelScale = GetCameraPixelScale();
2699     wxString scaleStr;
2700     if (pixelScale == 1.0)
2701         scaleStr = "unspecified";
2702     else
2703         scaleStr = wxString::Format("%.2f arc-sec/px", pixelScale);
2704 
2705     wxString focalLengthStr;
2706     if (m_focalLength == 0)
2707         focalLengthStr = "unspecified";
2708     else
2709         focalLengthStr = wxString::Format("%d mm", m_focalLength);
2710 
2711     return wxString::Format("Pixel scale = %s, Binning = %hu, Focal length = %s",
2712         scaleStr, pCamera->Binning, focalLengthStr);
2713 }
2714 
GetBeepForLostStar()2715 bool MyFrame::GetBeepForLostStar()
2716 {
2717     return m_beepForLostStar;
2718 }
2719 
SetBeepForLostStar(bool beep)2720 void MyFrame::SetBeepForLostStar(bool beep)
2721 {
2722     m_beepForLostStar = beep;
2723     pConfig->Profile.SetBoolean("/BeepForLostStar", beep);
2724     Debug.Write(wxString::Format("Beep for lost star set to %s\n", beep ? "true" : "false"));
2725 }
2726 
GetSettingsSummary() const2727 wxString MyFrame::GetSettingsSummary() const
2728 {
2729     // return a loggable summary of current global configs managed by MyFrame
2730     return wxString::Format("Dither = %s, Dither scale = %.3f, Image noise reduction = %s, Guide-frame time lapse = %d, Server %s\n"
2731         "%s\n",
2732         m_ditherRaOnly ? "RA only" : "both axes",
2733         m_ditherScaleFactor,
2734         m_noiseReductionMethod == NR_NONE ? "none" : m_noiseReductionMethod == NR_2x2MEAN ? "2x2 mean" : "3x3 median",
2735         m_timeLapse,
2736         m_serverMode ? "enabled" : "disabled",
2737         PixelScaleSummary()
2738     );
2739 }
2740 
RegisterTextCtrl(wxTextCtrl * ctrl)2741 void MyFrame::RegisterTextCtrl(wxTextCtrl *ctrl)
2742 {
2743     // Text controls gaining focus need to disable the Bookmarks Menu accelerators
2744     ctrl->Bind(wxEVT_SET_FOCUS, &MyFrame::OnTextControlSetFocus, this);
2745     ctrl->Bind(wxEVT_KILL_FOCUS, &MyFrame::OnTextControlKillFocus, this);
2746 }
2747 
2748 // Reset the guiding parameters and the various graphical displays when image scale is changed outside the AD UI.  Goal is to restore basic guiding behavior
2749 // until a fresh calibration is done.  Ratio of image scales is used because 1) info like mount guide speed may not be available and 2) the user may have already
2750 // adjusted the calibration step-size in the profile to get the results he wants.
HandleImageScaleChange(double NewToOldRatio)2751 void MyFrame::HandleImageScaleChange(double NewToOldRatio)
2752 {
2753     // Adjust the calibration step-size to facilitate a reasonable calibration of the real mount
2754     Scope *scope = TheScope();
2755     if (scope)
2756     {
2757         int stepSize = scope->GetCalibrationDuration();
2758         stepSize *= NewToOldRatio;              // Larger scale => larger step-size because calibration is targeted on fixed-size total pixel displacements
2759         scope->SetCalibrationDuration(stepSize);
2760 
2761     }
2762 
2763     // Leave the algo choices in place but force a reversion to default guiding params for in-use algos
2764     pAdvancedDialog->ResetGuidingParams();
2765 
2766     wxCommandEvent dummyEvt;
2767     if (pGraphLog)
2768     {
2769         pGraphLog->OnButtonClear(dummyEvt);
2770         pGraphLog->UpdateControls();
2771     }
2772 
2773     // Give the image-scale dependent windows a chance to reset
2774     if (pStepGuiderGraph)
2775         pStepGuiderGraph->OnButtonClear(dummyEvt);
2776 
2777     if (pTarget)
2778     {
2779         pTarget->OnButtonClear(dummyEvt);
2780         pTarget->UpdateControls();
2781     }
2782 
2783     Alert(_("Guiding parameters have been reset because the binning or pixel size changed unexpectedly. "
2784 	    "You should use separate profiles for different image scales."));
2785 }
2786 
GetConfigDialogPane(wxWindow * pParent)2787 MyFrameConfigDialogPane *MyFrame::GetConfigDialogPane(wxWindow *pParent)
2788 {
2789     return new MyFrameConfigDialogPane(pParent, this);
2790 }
2791 
MyFrameConfigDialogPane(wxWindow * pParent,MyFrame * pFrame)2792 MyFrameConfigDialogPane::MyFrameConfigDialogPane(wxWindow *pParent, MyFrame *pFrame)
2793     : ConfigDialogPane(_("Global Settings"), pParent)
2794 {
2795 }
2796 
LayoutControls(BrainCtrlIdMap & CtrlMap)2797 void MyFrameConfigDialogPane::LayoutControls(BrainCtrlIdMap& CtrlMap)
2798 {
2799     wxSizerFlags sizer_flags = wxSizerFlags(0).Border(wxALL, 5).Expand();
2800     wxSizerFlags grid_flags = wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL);
2801     wxFlexGridSizer *pTopGrid = new wxFlexGridSizer(2, 2, 15, 15);
2802 
2803     pTopGrid->Add(GetSizerCtrl(CtrlMap, AD_szLanguage), grid_flags);
2804     pTopGrid->Add(GetSingleCtrl(CtrlMap, AD_cbResetConfig), grid_flags);
2805     pTopGrid->Add(GetSingleCtrl(CtrlMap, AD_cbDontAsk), grid_flags);
2806     this->Add(pTopGrid, sizer_flags);
2807     this->Add(GetSizerCtrl(CtrlMap, AD_szSoftwareUpdate), sizer_flags);
2808     this->Add(GetSizerCtrl(CtrlMap, AD_szLogFileInfo), sizer_flags);
2809     this->Add(GetSingleCtrl(CtrlMap, AD_cbEnableImageLogging), sizer_flags);
2810     this->Add(GetSizerCtrl(CtrlMap, AD_szImageLoggingOptions), sizer_flags);
2811     this->Add(GetSizerCtrl(CtrlMap, AD_szDither), sizer_flags);
2812     Layout();
2813 }
2814 
GetConfigDlgCtrlSet(MyFrame * pFrame,AdvancedDialog * pAdvancedDialog,BrainCtrlIdMap & CtrlMap)2815 MyFrameConfigDialogCtrlSet *MyFrame::GetConfigDlgCtrlSet(MyFrame *pFrame, AdvancedDialog *pAdvancedDialog, BrainCtrlIdMap& CtrlMap)
2816 {
2817     return new MyFrameConfigDialogCtrlSet(pFrame, pAdvancedDialog, CtrlMap);
2818 }
2819 
2820 struct FocalLengthValidator : public wxIntegerValidator<int>
2821 {
2822     typedef wxIntegerValidator<int> Super;
2823     AdvancedDialog *m_dlg;
FocalLengthValidatorFocalLengthValidator2824     FocalLengthValidator(AdvancedDialog *dlg) : Super(nullptr, 0), m_dlg(dlg) { }
CloneFocalLengthValidator2825     wxObject *Clone() const override {
2826         return new FocalLengthValidator(*this);
2827     }
ValidateFocalLengthValidator2828     bool Validate(wxWindow *parent) override {
2829         bool ok = false;
2830         if (Super::Validate(parent))
2831         {
2832             long val;
2833             wxTextCtrl *ctrl = static_cast<wxTextCtrl *>(GetWindow());
2834             if (ctrl->GetValue().ToLong(&val))
2835                 ok = val <= AdvancedDialog::MAX_FOCAL_LENGTH && (val == 0 || val >= AdvancedDialog::MIN_FOCAL_LENGTH);
2836         }
2837         if (!ok)
2838         {
2839             m_dlg->ShowInvalid(GetWindow(),
2840                 wxString::Format(_("Enter a focal length in millimeters, between %.f and %.f,\n"
2841                 "or enter 0 if the focal length is not known"),
2842                 AdvancedDialog::MIN_FOCAL_LENGTH, AdvancedDialog::MAX_FOCAL_LENGTH));
2843         }
2844         return ok;
2845     }
2846 };
2847 
2848 #if defined(__LINUX__) || defined(__FreeBSD__)
2849 // ugly workaround for Issue 83 - link error on Linux
2850 //  undefined reference to wxPluralFormsCalculatorPtr::~wxPluralFormsCalculatorPtr
~wxPluralFormsCalculatorPtr()2851 wxPluralFormsCalculatorPtr::~wxPluralFormsCalculatorPtr() { }
2852 #endif
2853 
2854 // slow and iniefficient translation of a string to a given language
TranslateStrToLang(const wxString & s,int langid)2855 static wxString TranslateStrToLang(const wxString& s, int langid)
2856 {
2857     if (langid == wxLANGUAGE_DEFAULT)
2858         langid = wxLocale::GetSystemLanguage();
2859     if (langid == wxLANGUAGE_ENGLISH_US)
2860         return s;
2861     for (const auto& tr : wxTranslations::Get()->GetAvailableTranslations(PHD_MESSAGES_CATALOG))
2862     {
2863         const wxLanguageInfo *info = wxLocale::FindLanguageInfo(tr);
2864         if (info && info->Language == langid)
2865         {
2866             std::unique_ptr<wxFileTranslationsLoader> loader(new wxFileTranslationsLoader());
2867             std::unique_ptr<wxMsgCatalog> msgcat(loader->LoadCatalog("messages", info->CanonicalName));
2868             if (msgcat)
2869             {
2870                 const wxString *p = msgcat->GetString(s);
2871                 if (p)
2872                     return *p;
2873             }
2874             break;
2875         }
2876     }
2877     return s;
2878 }
2879 
2880 class AvailableLanguages
2881 {
2882     wxArrayString m_names;
2883     wxArrayInt m_ids;
2884 
Init()2885     void Init()
2886     {
2887         wxArrayString availableTranslations =
2888                 wxTranslations::Get()->GetAvailableTranslations(PHD_MESSAGES_CATALOG);
2889 
2890         availableTranslations.Sort();
2891 
2892         m_names.Add(_("System default"));
2893         m_names.Add("English");
2894         m_ids.Add(wxLANGUAGE_DEFAULT);
2895         m_ids.Add(wxLANGUAGE_ENGLISH_US);
2896         for (const auto& s : availableTranslations)
2897         {
2898             const wxLanguageInfo *info = wxLocale::FindLanguageInfo(s);
2899             const wxString *langDesc = &info->Description;
2900             std::unique_ptr<wxFileTranslationsLoader> loader(new wxFileTranslationsLoader());
2901             std::unique_ptr<wxMsgCatalog> msgcat(loader->LoadCatalog("messages", info->CanonicalName));
2902             if (msgcat)
2903             {
2904                 const wxString *p = msgcat->GetString(wxTRANSLATE("Language-Name"));
2905                 if (p)
2906                     langDesc = p;
2907             }
2908             m_names.Add(*langDesc);
2909             m_ids.Add(info->Language);
2910         }
2911     }
2912 
2913 public:
2914 
Names()2915     const wxArrayString& Names() {
2916         if (m_names.empty())
2917             Init();
2918         return m_names;
2919     }
2920 
Index(int langid)2921     int Index(int langid) {
2922         if (m_names.empty())
2923             Init();
2924         return m_ids.Index(langid);
2925     }
2926 
LangId(int index)2927     int LangId(int index) {
2928         if (m_names.empty())
2929             Init();
2930         return index >= 0 && index < m_names.size() ?
2931             m_ids[index] : wxLANGUAGE_DEFAULT;
2932     }
2933 };
2934 static AvailableLanguages PhdLanguages;
2935 
MyFrameConfigDialogCtrlSet(MyFrame * pFrame,AdvancedDialog * pAdvancedDialog,BrainCtrlIdMap & CtrlMap)2936 MyFrameConfigDialogCtrlSet::MyFrameConfigDialogCtrlSet(MyFrame *pFrame, AdvancedDialog *pAdvancedDialog, BrainCtrlIdMap& CtrlMap)
2937     : ConfigDialogCtrlSet(pFrame, pAdvancedDialog, CtrlMap)
2938 {
2939     int width;
2940     wxWindow *parent;
2941 
2942     m_pFrame = pFrame;
2943     m_pResetConfiguration = new wxCheckBox(GetParentWindow(AD_cbResetConfig), wxID_ANY, _("Reset Configuration"));
2944     AddCtrl(CtrlMap, AD_cbResetConfig, m_pResetConfiguration, _("Reset all configuration and program settings to fresh install status. This will require restarting PHD2"));
2945     m_pResetDontAskAgain = new wxCheckBox(GetParentWindow(AD_cbDontAsk), wxID_ANY, _("Reset \"Don't Show Again\" messages"));
2946     AddCtrl(CtrlMap, AD_cbDontAsk, m_pResetDontAskAgain, _("Restore any messages that were hidden when you checked \"Don't show this again\"."));
2947 
2948     wxString nralgo_choices[] =
2949     {
2950         _("None"), _("2x2 mean"), _("3x3 median")
2951     };
2952 
2953     width = StringArrayWidth(nralgo_choices, WXSIZEOF(nralgo_choices));
2954     parent = GetParentWindow(AD_szNoiseReduction);
2955     m_pNoiseReduction = new wxChoice(parent, wxID_ANY, wxPoint(-1, -1),
2956         wxSize(width + 35, -1), WXSIZEOF(nralgo_choices), nralgo_choices);
2957     AddLabeledCtrl(CtrlMap, AD_szNoiseReduction, _("Noise Reduction"), m_pNoiseReduction,
2958         _("Technique to reduce noise in images"));
2959 
2960     width = StringWidth(_T("00000"));
2961     parent = GetParentWindow(AD_szTimeLapse);
2962     m_pTimeLapse = pFrame->MakeSpinCtrl(parent, wxID_ANY, _T(" "), wxDefaultPosition,
2963         wxSize(width, -1), wxSP_ARROW_KEYS, 0, 10000, 0, _T("TimeLapse"));
2964     AddLabeledCtrl(CtrlMap, AD_szTimeLapse, _("Time Lapse (ms)"), m_pTimeLapse,
2965         _("How long should PHD wait between guide frames? Default = 0ms, useful when using very short exposures (e.g., using a video camera) but wanting to send guide commands less frequently"));
2966 
2967     parent = GetParentWindow(AD_szFocalLength);
2968     // Put a validator on this field to be sure that only digits are entered - avoids problem where
2969     // user face-plant on keyboard results in a focal length of zero
2970     m_pFocalLength = new wxTextCtrl(parent, wxID_ANY, _T(" "), wxDefaultPosition, wxSize(width + 30, -1), 0,
2971         FocalLengthValidator(pAdvancedDialog));
2972     AddLabeledCtrl(CtrlMap, AD_szFocalLength, _("Focal length (mm)"), m_pFocalLength,
2973         _("Guider telescope focal length, used with the camera pixel size to display guiding error in arc-sec."));
2974 
2975     width = StringWidth(_("System default"));
2976     parent = GetParentWindow(AD_szLanguage);
2977     m_pLanguage = new wxChoice(parent, wxID_ANY, wxPoint(-1, -1),
2978         wxSize(width + 35, -1), PhdLanguages.Names());
2979     AddLabeledCtrl(CtrlMap, AD_szLanguage, _("Language"), m_pLanguage,
2980         wxString::Format(_("%s Language. You'll have to restart PHD to take effect."), APPNAME));
2981 
2982     // Software Update
2983     {
2984         parent = GetParentWindow(AD_szSoftwareUpdate);
2985         wxStaticBoxSizer *sz = new wxStaticBoxSizer(wxHORIZONTAL, parent, _("Software Update"));
2986         m_updateEnabled = new wxCheckBox(parent, wxID_ANY, _("Automatically check for updates"));
2987         m_updateEnabled->SetToolTip(_("Check for software updates when PHD2 starts (recommended)"));
2988         sz->Add(m_updateEnabled, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 8));
2989         m_updateMajorOnly = new wxCheckBox(parent, wxID_ANY, _("Only check for major releases"));
2990         m_updateMajorOnly->SetToolTip(_("Ignore minor (development) releases when checking for updates"));
2991         sz->Add(m_updateMajorOnly, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 8));
2992         AddGroup(CtrlMap, AD_szSoftwareUpdate, sz);
2993     }
2994 
2995     // Log directory location - use a group box with a wide text edit control and a 'browse' button at the far right
2996     parent = GetParentWindow(AD_szLogFileInfo);
2997     wxStaticBoxSizer *pInputGroupBox = new wxStaticBoxSizer(wxHORIZONTAL, parent, _("Log File Location"));
2998     wxBoxSizer *pButtonSizer = new wxBoxSizer(wxHORIZONTAL);
2999     m_pLogDir = new wxTextCtrl(parent, wxID_ANY, _T(""), wxDefaultPosition, wxSize(450, -1));
3000     m_pLogDir->SetToolTip(_("Folder for guide and debug logs; empty string to restore the default location"));
3001     m_pSelectDir = new wxButton(parent, wxID_OK, _("Browse..."));
3002     pButtonSizer->Add(m_pSelectDir, wxSizerFlags(0).Align(wxRIGHT));
3003     m_pSelectDir->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyFrameConfigDialogCtrlSet::OnDirSelect, this);
3004 
3005     pInputGroupBox->Add(m_pLogDir, wxSizerFlags(0).Expand());
3006     pInputGroupBox->Add(pButtonSizer, wxSizerFlags(0).Align(wxRIGHT).Border(wxTop, 20));
3007     AddGroup(CtrlMap, AD_szLogFileInfo, pInputGroupBox);
3008 
3009     const int PAD = 6;
3010 
3011     // Image logging controls
3012     width = StringWidth(_T("00.0"));
3013     parent = GetParentWindow(AD_cbEnableImageLogging);
3014     m_EnableImageLogging = new wxCheckBox(parent, wxID_ANY, _("Enable diagnostic image logging"));
3015     AddCtrl(CtrlMap, AD_cbEnableImageLogging, m_EnableImageLogging, _("Save guider images based on options below"));
3016     parent = GetParentWindow(AD_szImageLoggingOptions);
3017     m_EnableImageLogging->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, &MyFrameConfigDialogCtrlSet::OnImageLogEnableChecked, this);
3018     m_LoggingOptions = new wxStaticBoxSizer(wxVERTICAL, parent, _("Save Guider Images"));
3019     wxFlexGridSizer *pOptionsGrid = new wxFlexGridSizer(3, 2, 0, PAD);
3020 
3021     m_LogDroppedFrames = new wxCheckBox(parent, wxID_ANY, _("For all lost-star frames"));
3022     m_LogDroppedFrames->SetToolTip(_("Save guider image whenever a lost-star event occurs"));
3023 
3024     m_LogAutoSelectFrames = new wxCheckBox(parent, wxID_ANY, _("For all Auto-select Star frames"));
3025     m_LogAutoSelectFrames->SetToolTip(_("Save guider image when a star auto-selection is made. Note: the image is always saved when star auto-selection fails, regardless of this setting."));
3026 
3027     wxBoxSizer *pHzRel = new wxBoxSizer(wxHORIZONTAL);
3028     m_LogRelErrors = new wxCheckBox(parent, wxID_ANY, _("When relative error exceeds"));
3029     m_LogRelErrors->SetToolTip(_("Save guider images when the error for the current frame exceeds the average error by this factor. "
3030         "For example, if the average (RMS) error is 0.5 pixels, and the current frame's error is 1.5 pixels, the relative error is 3"));
3031     m_LogRelErrorThresh = pFrame->MakeSpinCtrlDouble(parent, wxID_ANY, _(" "), wxDefaultPosition,
3032         wxSize(width, -1), wxSP_ARROW_KEYS, 1.0, 10.0, 4.0, 1.0);
3033     m_LogRelErrorThresh->SetToolTip(_("Relative error threshold. Relative error is the ratio between the current frame's error and the average error"));
3034     pHzRel->Add(m_LogRelErrors, wxSizerFlags().Border(wxALL, PAD).Align(wxALIGN_CENTER_VERTICAL));
3035     pHzRel->Add(m_LogRelErrorThresh, wxSizerFlags().Border(wxALL, PAD).Align(wxALIGN_CENTER_VERTICAL));
3036 
3037     wxBoxSizer *pHzAbs = new wxBoxSizer(wxHORIZONTAL);
3038     m_LogAbsErrors = new wxCheckBox(parent, wxID_ANY, _("When absolute error exceeds (pixels)"));
3039     m_LogAbsErrors->SetToolTip(_("Save guider images when the distance between the guide star and the lock position exceeds this many pixels"));
3040     width = StringWidth(_T("00.0"));
3041     m_LogAbsErrorThresh = pFrame->MakeSpinCtrlDouble(parent, wxID_ANY, _(" "), wxDefaultPosition,
3042         wxSize(width, -1), wxSP_ARROW_KEYS, 1.0, 10.0, 4.0, 1.0);
3043     m_LogAbsErrorThresh->SetToolTip(_("Absolute error threshold in pixels"));
3044     pHzAbs->Add(m_LogAbsErrors, wxSizerFlags().Border(wxALL, PAD).Align(wxALIGN_CENTER_VERTICAL));
3045     pHzAbs->Add(m_LogAbsErrorThresh, wxSizerFlags().Border(wxALL, PAD).Align(wxALIGN_CENTER_VERTICAL));
3046 
3047     wxBoxSizer *pHzN = new wxBoxSizer(wxHORIZONTAL);
3048     m_LogNextNFrames = new wxCheckBox(parent, wxID_ANY, _("Until this count is reached"));
3049     m_LogNextNFrames->SetToolTip(_("Save each guider image until the specified number of images have been saved"));
3050     m_LogNextNFramesCount = pFrame->MakeSpinCtrl(parent, wxID_ANY, "1", wxDefaultPosition, wxSize(width, -1), wxSP_ARROW_KEYS, 1, 100, 1);
3051     m_LogNextNFramesCount->SetToolTip(_("Number of images to save"));
3052     pHzN->Add(m_LogNextNFrames, wxSizerFlags().Border(wxALL, PAD).Align(wxALIGN_CENTER_VERTICAL));
3053     pHzN->Add(m_LogNextNFramesCount, wxSizerFlags().Border(wxALL, PAD).Align(wxALIGN_CENTER_VERTICAL));
3054 
3055     pOptionsGrid->Add(m_LogDroppedFrames, wxSizerFlags().Border(wxALL, PAD));
3056     pOptionsGrid->Add(m_LogAutoSelectFrames, wxSizerFlags().Border(wxALL, PAD));
3057     pOptionsGrid->Add(pHzRel);
3058     pOptionsGrid->Add(pHzN);
3059     pOptionsGrid->Add(pHzAbs);
3060     m_LoggingOptions->Add(pOptionsGrid);
3061 
3062     AddGroup(CtrlMap, AD_szImageLoggingOptions, m_LoggingOptions);
3063 
3064     // Dither
3065     parent = GetParentWindow(AD_szDither);
3066     wxStaticBoxSizer *ditherGroupBox = new wxStaticBoxSizer(wxVERTICAL, parent, _("Dither Settings"));
3067     m_ditherRandom = new wxRadioButton(parent, wxID_ANY, _("Random"));
3068     m_ditherRandom->SetToolTip(_("Each dither command moves the lock position a random distance on each axis"));
3069     m_ditherSpiral = new wxRadioButton(parent, wxID_ANY, _("Spiral"));
3070     m_ditherSpiral->SetToolTip(_("Each dither command moves the lock position along a spiral path"));
3071     wxBoxSizer *sz = new wxBoxSizer(wxHORIZONTAL);
3072     sz->Add(new wxStaticText(parent, wxID_ANY, _("Mode: ")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 8));
3073     sz->Add(m_ditherRandom, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 8));
3074     sz->Add(m_ditherSpiral, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 8));
3075     ditherGroupBox->Add(sz);
3076 
3077     m_ditherRaOnly = new wxCheckBox(parent, wxID_ANY, _("RA only"));
3078     m_ditherRaOnly->SetToolTip(_("Constrain dither to RA only"));
3079 
3080     width = StringWidth(_T("000.00"));
3081     m_ditherScaleFactor = pFrame->MakeSpinCtrlDouble(parent, wxID_ANY, _T(" "), wxDefaultPosition,
3082         wxSize(width, -1), wxSP_ARROW_KEYS, 0.1, 100.0, 0.0, 1.0);
3083     m_ditherScaleFactor->SetDigits(1);
3084     m_ditherScaleFactor->SetToolTip(_("Scaling for dither commands. Default = 1.0 (0.01-100.0)"));
3085 
3086     sz = new wxBoxSizer(wxHORIZONTAL);
3087     sz->Add(m_ditherRaOnly, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxLEFT, 8));
3088     sz->Add(new wxStaticText(parent, wxID_ANY, _("Scale") + _(": ")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxLEFT, 40));
3089     sz->Add(m_ditherScaleFactor, wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Border(wxLEFT, 10));
3090     ditherGroupBox->Add(sz);
3091 
3092     AddGroup(CtrlMap, AD_szDither, ditherGroupBox);
3093 
3094     parent = GetParentWindow(AD_cbAutoRestoreCal);
3095     m_pAutoLoadCalibration = new wxCheckBox(parent, wxID_ANY, _("Auto restore calibration"), wxDefaultPosition, wxDefaultSize);
3096     AddCtrl(CtrlMap, AD_cbAutoRestoreCal, m_pAutoLoadCalibration, _("For this equipment profile, automatically restore data from last successful calibration after gear is connected."));
3097 
3098     parent = GetParentWindow(AD_szAutoExposure);
3099 
3100     m_autoExpDurationMin = new wxComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
3101         wxArrayString(), wxCB_READONLY);
3102     m_autoExpDurationMax = new wxComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
3103         wxArrayString(), wxCB_READONLY);
3104     wxSize minsz(StringWidth(pFrame->ExposureDurationLabel(999990)) + 30, -1);
3105     m_autoExpDurationMin->SetMinSize(minsz);
3106     m_autoExpDurationMax->SetMinSize(minsz);
3107 
3108     width = StringWidth(_T("00.0"));
3109     m_autoExpSNR = pFrame->MakeSpinCtrlDouble(parent, wxID_ANY, _T(""), wxDefaultPosition,
3110         wxSize(width, -1), wxSP_ARROW_KEYS, 3.5, 99.9, 0.0, 1.0);
3111 
3112     wxFlexGridSizer *sz1 = new wxFlexGridSizer(1, 3, 10, 10);
3113     sz1->Add(MakeLabeledControl(AD_szAutoExposure, _("Min"), m_autoExpDurationMin, _("Auto exposure minimum duration")));
3114     sz1->Add(MakeLabeledControl(AD_szAutoExposure, _("Max"), m_autoExpDurationMax, _("Auto exposure maximum duration")), wxSizerFlags(0).Border(wxLEFT, 70));
3115     sz1->Add(MakeLabeledControl(AD_szAutoExposure, _("Target SNR"), m_autoExpSNR, _("Auto exposure target SNR value")), wxSizerFlags(0).Border(wxLEFT, 80));
3116     wxStaticBoxSizer *autoExp = new wxStaticBoxSizer(wxHORIZONTAL, parent, _("Auto Exposure"));
3117     autoExp->Add(sz1, wxSizerFlags(0).Expand());
3118 
3119     AddGroup(CtrlMap, AD_szAutoExposure, autoExp);
3120 }
3121 
LoadValues()3122 void MyFrameConfigDialogCtrlSet::LoadValues()
3123 {
3124     m_pResetConfiguration->SetValue(false);
3125     m_pResetConfiguration->Enable(!pFrame->CaptureActive);
3126     m_pResetDontAskAgain->SetValue(false);
3127     m_pNoiseReduction->SetSelection(pFrame->GetNoiseReductionMethod());
3128     if (m_pFrame->GetDitherMode() == DITHER_RANDOM)
3129         m_ditherRandom->SetValue(true);
3130     else
3131         m_ditherSpiral->SetValue(true);
3132     m_ditherRaOnly->SetValue(m_pFrame->GetDitherRaOnly());
3133     m_ditherScaleFactor->SetValue(m_pFrame->GetDitherScaleFactor());
3134     m_pTimeLapse->SetValue(m_pFrame->GetTimeLapse());
3135     SetFocalLength(m_pFrame->GetFocalLength());
3136     m_pFocalLength->Enable(!pFrame->CaptureActive);
3137 
3138     int language = wxGetApp().GetLocale().GetLanguage();
3139     m_oldLanguageChoice = PhdLanguages.Index(language);
3140     m_pLanguage->SetSelection(m_oldLanguageChoice);
3141     m_pLanguage->Enable(!pFrame->CaptureActive);
3142 
3143     m_pLogDir->SetValue(GuideLog.GetLogDir());
3144     m_pLogDir->Enable(!pFrame->CaptureActive);
3145     m_pSelectDir->Enable(!pFrame->CaptureActive);
3146     m_pAutoLoadCalibration->SetValue(m_pFrame->GetAutoLoadCalibration());
3147 
3148     const AutoExposureCfg& cfg = m_pFrame->GetAutoExposureCfg();
3149 
3150     std::vector<int> dur(pFrame->GetExposureDurations());
3151     std::sort(dur.begin(), dur.end());
3152     wxArrayString as;
3153     for (auto it = dur.begin(); it != dur.end(); ++it)
3154         as.Add(pFrame->ExposureDurationLabel(*it));
3155 
3156     m_autoExpDurationMin->Set(as);
3157     m_autoExpDurationMax->Set(as);
3158 
3159     auto pos = std::find(dur.begin(), dur.end(), cfg.minExposure);
3160     if (pos == dur.end())
3161         pos = std::find(dur.begin(), dur.end(), DefaultAutoExpMin);
3162     m_autoExpDurationMin->SetSelection(pos - dur.begin());
3163 
3164     pos = std::find(dur.begin(), dur.end(), cfg.maxExposure);
3165     if (pos == dur.end())
3166         pos = std::find(dur.begin(), dur.end(), DefaultAutoExpMax);
3167     m_autoExpDurationMax->SetSelection(pos - dur.begin());
3168 
3169     m_autoExpSNR->SetValue(cfg.targetSNR);
3170 
3171     ImageLoggerSettings imlSettings;
3172     ImageLogger::GetSettings(&imlSettings);
3173 
3174     m_EnableImageLogging->SetValue(imlSettings.loggingEnabled);
3175     m_LogDroppedFrames->SetValue(imlSettings.logFramesDropped);
3176     m_LogAutoSelectFrames->SetValue(imlSettings.logAutoSelectFrames);
3177     m_LogRelErrors->SetValue(imlSettings.logFramesOverThreshRel);
3178     m_LogRelErrorThresh->SetValue(imlSettings.guideErrorThreshRel);
3179     m_LogAbsErrors->SetValue(imlSettings.logFramesOverThreshPx);
3180     m_LogAbsErrorThresh->SetValue(imlSettings.guideErrorThreshPx);
3181     m_LogNextNFrames->SetValue(imlSettings.logNextNFrames);
3182     m_LogNextNFramesCount->SetValue(imlSettings.logNextNFramesCount);
3183 
3184     UpdaterSettings updSettings;
3185     PHD2Updater::GetSettings(&updSettings);
3186 
3187     m_updateEnabled->SetValue(updSettings.enabled);
3188     m_updateMajorOnly->SetValue(updSettings.series == UPD_SERIES_MAIN);
3189 
3190     wxCommandEvent dummy;
3191     OnImageLogEnableChecked(dummy);
3192 }
3193 
UnloadValues()3194 void MyFrameConfigDialogCtrlSet::UnloadValues()
3195 {
3196     try
3197     {
3198         if (m_pResetConfiguration->GetValue())
3199         {
3200             int choice = wxMessageBox(_("This will reset all PHD2 configuration values and restart the program.  Are you sure?"), _("Confirmation"), wxYES_NO);
3201 
3202             if (choice == wxYES)
3203             {
3204                 wxGetApp().ResetConfiguration();
3205                 wxGetApp().RestartApp();
3206             }
3207         }
3208 
3209         if (this->m_pResetDontAskAgain->GetValue())
3210         {
3211             ConfirmDialog::ResetAllDontAskAgain();
3212         }
3213 
3214         m_pFrame->SetNoiseReductionMethod(m_pNoiseReduction->GetSelection());
3215         m_pFrame->SetDitherMode(m_ditherRandom->GetValue() ? DITHER_RANDOM : DITHER_SPIRAL);
3216         m_pFrame->SetDitherRaOnly(m_ditherRaOnly->GetValue());
3217         m_pFrame->SetDitherScaleFactor(m_ditherScaleFactor->GetValue());
3218         m_pFrame->SetTimeLapse(m_pTimeLapse->GetValue());
3219         int oldFL = m_pFrame->GetFocalLength();
3220         int newFL = GetFocalLength();               // From UI control
3221         if (oldFL != newFL)
3222             m_pFrame->pAdvancedDialog->MakeImageScaleAdjustments();
3223         m_pFrame->SetFocalLength(GetFocalLength());
3224 
3225         int idx = m_pLanguage->GetSelection();
3226         int langid = PhdLanguages.LangId(idx);
3227         pConfig->Global.SetInt("/wxLanguage", langid);
3228         if (m_oldLanguageChoice != idx)
3229         {
3230             wxString title = TranslateStrToLang(wxTRANSLATE("Restart PHD2"), langid);
3231             wxString msg = TranslateStrToLang(wxTRANSLATE("You must restart PHD2 for the language change to take effect.\n"
3232                 "Would you like to restart PHD2 now?"), langid);
3233             int val = wxMessageBox(msg, title, wxYES_NO | wxCENTRE);
3234             if (val == wxYES)
3235                 wxGetApp().RestartApp();
3236         }
3237 
3238         wxString newdir = m_pLogDir->GetValue();
3239         if (!newdir.IsSameAs(GuideLog.GetLogDir()))
3240         {
3241             GuideLog.ChangeDirLog(newdir);
3242             Debug.ChangeDirLog(newdir);
3243         }
3244 
3245         m_pFrame->SetAutoLoadCalibration(m_pAutoLoadCalibration->GetValue());
3246 
3247         std::vector<int> dur(m_pFrame->GetExposureDurations());
3248         std::sort(dur.begin(), dur.end());
3249         int durationMin = dur[m_autoExpDurationMin->GetSelection()];
3250         int durationMax = dur[m_autoExpDurationMax->GetSelection()];
3251         if (durationMax < durationMin)
3252             durationMax = durationMin;
3253         bool cfg_changed = m_pFrame->SetAutoExposureCfg(durationMin, durationMax, m_autoExpSNR->GetValue());
3254         if (m_pFrame->m_autoExp.enabled && cfg_changed)
3255             m_pFrame->NotifyExposureChanged();
3256 
3257         ImageLoggerSettings imlSettings;
3258         ImageLogger::GetSettings(&imlSettings);
3259 
3260         imlSettings.loggingEnabled = m_EnableImageLogging->GetValue();
3261         if (imlSettings.loggingEnabled)
3262         {
3263             imlSettings.logFramesOverThreshRel = m_LogRelErrors->GetValue();
3264             imlSettings.logFramesOverThreshPx = m_LogAbsErrors->GetValue();
3265             imlSettings.logFramesDropped = m_LogDroppedFrames->GetValue();
3266             imlSettings.logAutoSelectFrames = m_LogAutoSelectFrames->GetValue();
3267             imlSettings.guideErrorThreshRel = m_LogRelErrorThresh->GetValue();
3268             imlSettings.guideErrorThreshPx = m_LogAbsErrorThresh->GetValue();
3269             imlSettings.logNextNFrames = m_LogNextNFrames->GetValue();
3270             imlSettings.logNextNFramesCount = m_LogNextNFramesCount->GetValue();
3271         }
3272 
3273         ImageLogger::ApplySettings(imlSettings);
3274         SaveImageLoggerSettings(imlSettings);
3275 
3276         UpdaterSettings updSettings;
3277         updSettings.enabled = m_updateEnabled->GetValue();
3278         updSettings.series = m_updateMajorOnly->GetValue() ? UPD_SERIES_MAIN : UPD_SERIES_DEV;
3279         PHD2Updater::SetSettings(updSettings);
3280     }
3281     catch (const wxString& Msg)
3282     {
3283         POSSIBLY_UNUSED(Msg);
3284     }
3285 }
3286 
3287 // Following are needed by step-size calculator to keep the UIs in-synch
GetFocalLength() const3288 int MyFrameConfigDialogCtrlSet::GetFocalLength() const
3289 {
3290     long val = 0;
3291     m_pFocalLength->GetValue().ToLong(&val);
3292     return (int)val;
3293 }
3294 
SetFocalLength(int val)3295 void MyFrameConfigDialogCtrlSet::SetFocalLength(int val)
3296 {
3297     m_pFocalLength->SetValue(wxString::Format(_T("%d"), val));
3298 }
3299 
OnDirSelect(wxCommandEvent & evt)3300 void MyFrameConfigDialogCtrlSet::OnDirSelect(wxCommandEvent& evt)
3301 {
3302     wxString sRtn = wxDirSelector("Choose a location", m_pLogDir->GetValue());
3303 
3304     if (sRtn.Len() > 0)
3305         m_pLogDir->SetValue(sRtn);
3306 }
3307 
OnImageLogEnableChecked(wxCommandEvent & event)3308 void MyFrameConfigDialogCtrlSet::OnImageLogEnableChecked(wxCommandEvent& event)
3309 {
3310     // wxStaticBoxSizer doesn't have an Enable method :-(
3311     bool setIt = m_EnableImageLogging->IsChecked();
3312     m_LogRelErrors->Enable(setIt);
3313     m_LogRelErrorThresh->Enable(setIt);
3314     m_LogAbsErrors->Enable(setIt);
3315     m_LogAbsErrorThresh->Enable(setIt);
3316     m_LogDroppedFrames->Enable(setIt);
3317     m_LogAutoSelectFrames->Enable(setIt);
3318     m_LogNextNFrames->Enable(setIt);
3319     m_LogNextNFramesCount->Enable(setIt);
3320 }
3321 
PlaceWindowOnScreen(wxWindow * win,int x,int y)3322 void MyFrame::PlaceWindowOnScreen(wxWindow *win, int x, int y)
3323 {
3324     if (x < 0 || x > wxSystemSettings::GetMetric(wxSYS_SCREEN_X) - 20 ||
3325         y < 0 || y > wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) - 20)
3326     {
3327         win->Centre(wxBOTH);
3328     }
3329     else
3330         win->Move(x, y);
3331 }
3332 
AdjustSpinnerWidth(wxSize * sz)3333 inline static void AdjustSpinnerWidth(wxSize *sz)
3334 {
3335 #ifdef __APPLE__
3336     // GetSizeFromTextSize() not working on OSX, so we need to add more padding
3337     enum { SPINNER_WIDTH_PAD = 20 };
3338     sz->SetWidth(sz->GetWidth() + SPINNER_WIDTH_PAD);
3339 #endif
3340 }
3341 
3342 // The spin control factories allow clients to specify a width based on the max width of the numeric values without having
3343 // to make guesses about the additional space required by the other parts of the control
MakeSpinCtrl(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,int min,int max,int initial,const wxString & name)3344 wxSpinCtrl* MyFrame::MakeSpinCtrl(wxWindow *parent, wxWindowID id, const wxString& value,
3345     const wxPoint& pos, const wxSize& size, long style,
3346     int min, int max, int initial, const wxString& name)
3347 {
3348     wxSpinCtrl *ctrl = new wxSpinCtrl(parent, id, value, pos, size, style, min, max, initial, name);
3349     wxSize initsize(ctrl->GetSizeFromTextSize(size));
3350     AdjustSpinnerWidth(&initsize);
3351     ctrl->SetInitialSize(initsize);
3352     return ctrl;
3353 }
3354 
MakeSpinCtrlDouble(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,double min,double max,double initial,double inc,const wxString & name)3355 wxSpinCtrlDouble* MyFrame::MakeSpinCtrlDouble(wxWindow *parent, wxWindowID id, const wxString& value,
3356     const wxPoint& pos, const wxSize& size, long style, double min, double max, double initial,
3357     double inc, const wxString& name)
3358 {
3359     wxSpinCtrlDouble *ctrl = new wxSpinCtrlDouble(parent, id, value, pos, size, style, min, max, initial, inc, name);
3360     wxSize initsize(ctrl->GetSizeFromTextSize(size));
3361     AdjustSpinnerWidth(&initsize);
3362     ctrl->SetInitialSize(initsize);
3363     return ctrl;
3364 }
3365 
3366 template<typename T>
NotifyGuidingParam(const wxString & name,T val)3367 static void NotifyGuidingParam(const wxString& name, T val)
3368 {
3369     GuideLog.SetGuidingParam(name, val);
3370     EvtServer.NotifyGuidingParam(name, val);
3371 }
3372 
NotifyGuidingParam(const wxString & name,double val)3373 void MyFrame::NotifyGuidingParam(const wxString& name, double val)
3374 {
3375     ::NotifyGuidingParam(name, val);
3376 }
3377 
NotifyGuidingParam(const wxString & name,int val)3378 void MyFrame::NotifyGuidingParam(const wxString& name, int val)
3379 {
3380     ::NotifyGuidingParam(name, val);
3381 }
3382 
NotifyGuidingParam(const wxString & name,bool val)3383 void MyFrame::NotifyGuidingParam(const wxString& name, bool val)
3384 {
3385     ::NotifyGuidingParam(name, val);
3386 }
3387 
NotifyGuidingParam(const wxString & name,const wxString & val)3388 void MyFrame::NotifyGuidingParam(const wxString& name, const wxString& val)
3389 {
3390     ::NotifyGuidingParam(name, val);
3391 }
3392 
3393 // Interface to force logging if guiding is not active
NotifyGuidingParam(const wxString & name,const wxString & val,bool ForceLog)3394 void MyFrame::NotifyGuidingParam(const wxString& name, const wxString& val, bool ForceLog)
3395 {
3396     GuideLog.SetGuidingParam(name, val, true);
3397     EvtServer.NotifyGuidingParam(name, val);
3398 }
3399