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