1 /*
2  *  frame_events.cpp
3  *  PHD Guiding
4  *
5  *  Created by Craig Stark.
6  *  Copyright (c) 2006-2010 Craig Stark.
7  *  All rights reserved.
8  *
9  *  This source code is distributed under the following "BSD" license
10  *  Redistribution and use in source and binary forms, with or without
11  *  modification, are permitted provided that the following conditions are met:
12  *    Redistributions of source code must retain the above copyright notice,
13  *     this list of conditions and the following disclaimer.
14  *    Redistributions in binary form must reproduce the above copyright notice,
15  *     this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution.
17  *    Neither the name of Craig Stark, Stark Labs nor the names of its
18  *     contributors may be used to endorse or promote products derived from
19  *     this software without specific prior written permission.
20  *
21  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  *  POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34 
35 #include "phd.h"
36 
37 #include "about_dialog.h"
38 #include "aui_controls.h"
39 #include "camcal_import_dialog.h"
40 #include "darks_dialog.h"
41 #include "image_math.h"
42 #include "log_uploader.h"
43 #include "pierflip_tool.h"
44 #include "Refine_DefMap.h"
45 #include "starcross_test.h"
46 
47 #include <algorithm>
48 #include <memory>
49 
50 #include <wx/spinctrl.h>
51 #include <wx/stdpaths.h>
52 #include <wx/textdlg.h>
53 #include <wx/textfile.h>
54 #include <wx/txtstrm.h>
55 #include <wx/utils.h>
56 #include <wx/wfstream.h>
57 
58 wxDEFINE_EVENT(APPSTATE_NOTIFY_EVENT, wxCommandEvent);
59 
OnExposureDurationSelected(wxCommandEvent & evt)60 void MyFrame::OnExposureDurationSelected(wxCommandEvent& evt)
61 {
62     unsigned int idx = Dur_Choice->GetSelection();
63 
64     if (idx == 0)
65     {
66         // Auto-exposure
67         if (!m_autoExp.enabled)
68         {
69             Debug.AddLine("AutoExp: enabled");
70             m_autoExp.enabled = true;
71             NotifyExposureChanged();
72         }
73     }
74     else if (idx == Dur_Choice->GetCount() - 1)
75     {
76         // edit custom
77 
78         int customVal = *(GetExposureDurations().end() - 1);
79         wxTextEntryDialog dlg(this, _("Custom exposure duration (seconds)"), _("Edit custom exposure"),
80             wxString::Format("%g", (double) customVal / 1000.), wxOK | wxCANCEL);
81         if (dlg.ShowModal() == wxID_OK)
82         {
83             double val;
84             if (dlg.GetValue().ToDouble(&val) && val > 0.0 && val < 3600.)
85             {
86                 int ms = (int)(val * 1000.0);
87                 SetCustomExposureDuration(ms);
88             }
89         }
90 
91         // restore the actual selection
92         if (m_autoExp.enabled)
93         {
94             Dur_Choice->SetSelection(0);
95         }
96         else
97         {
98             const std::vector<int>& dur(GetExposureDurations());
99             auto pos = std::find(dur.begin(), dur.end(), m_exposureDuration);
100             if (pos == dur.end())
101                 pos = std::find(dur.begin(), dur.end(), 1000); // safe fall-back, should not happen
102             Dur_Choice->SetSelection(pos - dur.begin() + 1); // skip Auto
103         }
104     }
105     else
106     {
107         // ordinary selection
108 
109         const std::vector<int>& dur(GetExposureDurations());
110         int duration = dur[idx - 1];
111 
112         if (m_autoExp.enabled || m_exposureDuration != duration)
113         {
114             Debug.Write(wxString::Format("OnExposureDurationSelected: duration = %d\n", duration));
115 
116             m_exposureDuration = duration;
117             m_autoExp.enabled = false;
118 
119             NotifyExposureChanged();
120 
121             if (pCamera)
122             {
123                 // select the best matching dark frame
124                 pCamera->SelectDark(m_exposureDuration);
125             }
126         }
127     }
128 }
129 
NotifyExposureChanged()130 void MyFrame::NotifyExposureChanged()
131 {
132     NotifyGuidingParam("Exposure", ExposureDurationSummary());
133     pConfig->Profile.SetInt("/ExposureDurationMs", m_autoExp.enabled ? -1 : m_exposureDuration);
134 }
135 
RequestedExposureDuration()136 int MyFrame::RequestedExposureDuration()
137 {
138     if (!pCamera || !pCamera->Connected)
139         return 0;
140 
141     return m_singleExposure.enabled ? m_singleExposure.duration : m_exposureDuration;
142 }
143 
OnMenuHighlight(wxMenuEvent & evt)144 void MyFrame::OnMenuHighlight(wxMenuEvent& evt)
145 {
146     wxMenuItem *mi = pFrame->GetMenuBar()->FindItem(evt.GetMenuId());
147     if (mi)
148     {
149         const wxString& help = mi->GetHelp();
150         m_statusbar->OverlayMsg(help);
151     }
152     else
153     {
154         m_statusbar->ClearOverlayMsg();
155     }
156 }
157 
OnAnyMenu(wxCommandEvent & evt)158 void MyFrame::OnAnyMenu(wxCommandEvent& evt)
159 {
160     m_statusbar->ClearOverlayMsg();
161     evt.Skip();
162 }
163 
OnAnyMenuClose(wxMenuEvent & evt)164 void MyFrame::OnAnyMenuClose(wxMenuEvent& evt)
165 {
166     m_statusbar->ClearOverlayMsg();
167     evt.Skip();
168 }
169 
OnQuit(wxCommandEvent & WXUNUSED (event))170 void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
171 {
172     Close(false);
173 }
174 
OnInstructions(wxCommandEvent & WXUNUSED (event))175 void MyFrame::OnInstructions(wxCommandEvent& WXUNUSED(event))
176 {
177     wxMessageBox(wxString::Format(_("Welcome to PHD2 (Push Here Dummy, Gen2) Guiding\n\n \
178 Basic operation is quite simple (hence the 'PHD')\n\n \
179   1) Press the green 'USB' button, select your camera and mount, click on 'Connect All'\n \
180   2) Pick an exposure duration from the drop-down list. Try 2 seconds to start.\n \
181   3) Hit the 'Loop' button, adjust your focus if necessary\n \
182   4) Click on a star away from the edge or use Alt-S to auto-select a star\n \
183   5) Press the guide (green target) icon\n\n \
184 PHD2 will then calibrate itself and begin guiding.  That's it!\n\n \
185 To stop guiding, simply press the 'Loop' or 'Stop' buttons. If you need to \n \
186 tweak any options, click on the 'Brain' button to bring up the 'Advanced' \n \
187 panel. Use the 'View' menu to watch your guiding performance. If you have\n \
188 problems, read the 'Best Practices' document and the help files! ")),_("Instructions"));
189 
190 }
191 
OnHelp(wxCommandEvent & WXUNUSED (event))192 void MyFrame::OnHelp(wxCommandEvent& WXUNUSED(event))
193 {
194     help->Display(_("Introduction"));
195 }
196 
OnAbout(wxCommandEvent & WXUNUSED (event))197 void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
198 {
199     AboutDialog dlg;
200     dlg.ShowModal();
201 }
202 
OnHelpOnline(wxCommandEvent & evt)203 void MyFrame::OnHelpOnline(wxCommandEvent& evt)
204 {
205     wxLaunchDefaultBrowser("https://openphdguiding.org/getting-help/");
206 }
207 
_shell_open(const wxString & loc)208 static void _shell_open(const wxString& loc)
209 {
210 #if defined(__WXMSW__)
211     ::ShellExecute(NULL, _T("open"), loc.fn_str(), NULL, NULL, SW_SHOWNORMAL);
212 #elif defined(__WXOSX__)
213     ::wxExecute("/usr/bin/open '" + loc + "'", wxEXEC_ASYNC);
214 #else
215     ::wxExecute("xdg-open '" + loc + "'", wxEXEC_ASYNC);
216 #endif
217 }
218 
OnHelpLogFolder(wxCommandEvent & evt)219 void MyFrame::OnHelpLogFolder(wxCommandEvent& evt)
220 {
221     _shell_open(Debug.GetLogDir());
222 }
223 
OnHelpUploadLogs(wxCommandEvent & evt)224 void MyFrame::OnHelpUploadLogs(wxCommandEvent& evt)
225 {
226     LogUploader::UploadLogs();
227 }
228 
OnOverlay(wxCommandEvent & evt)229 void MyFrame::OnOverlay(wxCommandEvent& evt)
230 {
231     pGuider->SetOverlayMode(evt.GetId() - MENU_XHAIR0);
232 }
233 
234 struct SlitPropertiesDlg : public wxDialog
235 {
236     wxSpinCtrl *m_x;
237     wxSpinCtrl *m_y;
238     wxSpinCtrl *m_width;
239     wxSpinCtrl *m_height;
240     wxSpinCtrl *m_angle;
241 
242     SlitPropertiesDlg(wxWindow *parent, wxWindowID id = wxID_ANY, const wxString& title = _("Spectrograph Slit Overlay"),
243         const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(390, 181), long style = wxDEFAULT_DIALOG_STYLE);
244 };
245 
SlitPropertiesDlg(wxWindow * parent,wxWindowID id,const wxString & title,const wxPoint & pos,const wxSize & size,long style)246 SlitPropertiesDlg::SlitPropertiesDlg(wxWindow *parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style)
247     : wxDialog(parent, id, title, pos, size, style)
248 {
249     wxSize textSz = pFrame->GetTextExtent("888888");
250     wxBoxSizer *vSizer = new wxBoxSizer(wxVERTICAL);
251     wxBoxSizer *hSizer = new wxBoxSizer(wxHORIZONTAL);
252     wxStaticBoxSizer *szPosition = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Position (Center)")), wxVERTICAL);
253     wxStaticBoxSizer *szSlitSize = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Size")), wxVERTICAL);
254     // Position controls
255     wxBoxSizer *hXSizer = new wxBoxSizer(wxHORIZONTAL);
256     wxStaticText *xLabel = new wxStaticText(this, wxID_ANY, _("X"), wxDefaultPosition, wxDefaultSize, 0);
257     hXSizer->Add(xLabel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
258     m_x = pFrame->MakeSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, textSz, wxSP_ARROW_KEYS, 0, 8000, 0);
259     hXSizer->Add(m_x, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
260     szPosition->Add(hXSizer, 0, wxEXPAND, 5);
261 
262     wxBoxSizer *hYSizer = new wxBoxSizer(wxHORIZONTAL);
263     wxStaticText* yLabel = new wxStaticText(this, wxID_ANY, _("Y"), wxDefaultPosition, wxDefaultSize, 0);
264     hYSizer->Add(yLabel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
265     m_y = pFrame->MakeSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, textSz, wxSP_ARROW_KEYS, 0, 8000, 0);
266     hYSizer->Add(m_y, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
267     szPosition->Add(hYSizer, 1, wxEXPAND, 5);
268     hSizer->Add(szPosition, 0, 0, 5);
269 
270     // Size controls
271     wxBoxSizer *hWidthSizer = new wxBoxSizer(wxHORIZONTAL);
272     wxStaticText *widthLabel = new wxStaticText(this, wxID_ANY, _("Width"), wxDefaultPosition, wxDefaultSize, 0);
273     hWidthSizer->Add(widthLabel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
274     m_width = pFrame->MakeSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, textSz, wxSP_ARROW_KEYS, 2, 1000, 2);
275     hWidthSizer->Add(m_width, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
276     szSlitSize->Add(hWidthSizer, 1, wxEXPAND, 5);
277 
278     wxBoxSizer *hHeightSizer = new wxBoxSizer(wxHORIZONTAL);
279     wxStaticText *heightLabel = new wxStaticText(this, wxID_ANY, _("Height"), wxDefaultPosition, wxDefaultSize, 0);
280     hHeightSizer->Add(heightLabel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
281     m_height = pFrame->MakeSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, textSz, wxSP_ARROW_KEYS, 2, 1000, 2);
282     hHeightSizer->Add(m_height, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
283     szSlitSize->Add(hHeightSizer, 1, wxEXPAND, 5);
284     hSizer->Add(szSlitSize, 0, 0, 5);
285 
286     int ww = StringWidth(this, widthLabel->GetLabel());
287     int wh = StringWidth(this, heightLabel->GetLabel());
288     wxSize sz(wxMax(ww, wh), -1);
289     widthLabel->SetMinSize(sz);
290     heightLabel->SetMinSize(sz);
291 
292     vSizer->Add(hSizer, 0, wxEXPAND, 5);
293     // Angle controls
294     wxBoxSizer* hAngleSizer = new wxBoxSizer(wxHORIZONTAL);
295     wxStaticText* staticText1 = new wxStaticText(this, wxID_ANY, _("Angle (degrees)"), wxDefaultPosition, wxDefaultSize, 0);
296     //staticText1->Wrap(-1);
297     hAngleSizer->Add(staticText1, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
298     m_angle = pFrame->MakeSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, textSz, wxSP_ARROW_KEYS, -90, 90, 0);
299     hAngleSizer->Add(m_angle, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
300 
301     vSizer->Add(hAngleSizer, 0, wxEXPAND, 5);
302 
303     // ok/cancel buttons
304     vSizer->Add(
305         CreateButtonSizer(wxOK | wxCANCEL),
306         wxSizerFlags(0).Expand().Border(wxALL, 10));
307 
308     SetSizerAndFit(vSizer);
309 }
310 
311 struct SlitPosCtx : public wxObject
312 {
313     SlitPropertiesDlg *dlg;
314     Guider *guider;
SlitPosCtxSlitPosCtx315     SlitPosCtx(SlitPropertiesDlg *d, Guider *g) : dlg(d), guider(g) { }
316 };
317 
UpdateSlitPos(wxSpinEvent & event)318 static void UpdateSlitPos(wxSpinEvent& event)
319 {
320     SlitPosCtx *ctx = static_cast<SlitPosCtx *>(event.GetEventUserData());
321     wxPoint center(ctx->dlg->m_x->GetValue(), ctx->dlg->m_y->GetValue());
322     wxSize size(ctx->dlg->m_width->GetValue(), ctx->dlg->m_height->GetValue());
323     int angle = ctx->dlg->m_angle->GetValue();
324     ctx->guider->SetOverlaySlitCoords(center, size, angle);
325 }
326 
OnOverlaySlitCoords(wxCommandEvent & evt)327 void MyFrame::OnOverlaySlitCoords(wxCommandEvent& evt)
328 {
329     wxPoint center;
330     wxSize size;
331     int angle;
332     pGuider->GetOverlaySlitCoords(&center, &size, &angle);
333 
334     SlitPropertiesDlg dlg(this);
335 
336     dlg.m_x->SetValue(center.x);
337     dlg.m_y->SetValue(center.y);
338     dlg.m_width->SetValue(size.GetWidth());
339     dlg.m_height->SetValue(size.GetHeight());
340     dlg.m_angle->SetValue(angle);
341 
342     dlg.Bind(wxEVT_SPINCTRL, &UpdateSlitPos, wxID_ANY, wxID_ANY, new SlitPosCtx(&dlg, pGuider));
343 
344     if (dlg.ShowModal() != wxID_OK)
345     {
346         // revert to original values
347         pGuider->SetOverlaySlitCoords(center, size, angle);
348     }
349 }
350 
OnSave(wxCommandEvent & WXUNUSED (event))351 void MyFrame::OnSave(wxCommandEvent& WXUNUSED(event))
352 {
353     if (!pGuider->CurrentImage()->ImageData)
354         return;
355 
356     wxString fname = wxFileSelector( _("Save FITS Image"), (const wxChar *)NULL,
357                           (const wxChar *)NULL,
358                            wxT("fit"), wxT("FITS files (*.fit)|*.fit"),wxFD_SAVE | wxFD_OVERWRITE_PROMPT,
359                            this);
360 
361     if (fname.IsEmpty())
362         return;  // Check for canceled dialog
363 
364     if (pGuider->SaveCurrentImage(fname))
365     {
366         Alert(wxString::Format(_("The image could not be saved to %s"), fname));
367     }
368     else
369     {
370         pFrame->StatusMsg(wxString::Format(_("%s saved"), wxFileName(fname).GetFullName()));
371     }
372 }
373 
OnIdle(wxIdleEvent & WXUNUSED (event))374 void MyFrame::OnIdle(wxIdleEvent& WXUNUSED(event))
375 {
376 }
377 
StartLoopingInteractive(const wxString & context)378 void MyFrame::StartLoopingInteractive(const wxString& context)
379 {
380     Debug.Write(wxString::Format("StartLoopingInteractive: %s\n", context));
381 
382     if (!pCamera || !pCamera->Connected)
383     {
384         Debug.Write(_T("Camera not connected\n"));
385         wxMessageBox(_("Please connect to a camera first"), _("Info"));
386         return;
387     }
388 
389     if (CaptureActive && !pGuider->IsCalibratingOrGuiding())
390     {
391         Debug.Write(_T("cannot start looping when capture active\n"));
392         return;
393     }
394 
395     StartLooping();
396 }
397 
OnButtonLoop(wxCommandEvent & WXUNUSED (event))398 void MyFrame::OnButtonLoop(wxCommandEvent& WXUNUSED(event))
399 {
400     StartLoopingInteractive(_T("Loop button clicked"));
401 }
402 
FinishStop(void)403 void MyFrame::FinishStop(void)
404 {
405     assert(!CaptureActive);
406     m_singleExposure.enabled = false;
407     EvtServer.NotifyLoopingStopped();
408     // when looping resumes, start with at least one full frame. This enables applications
409     // controlling PHD to auto-select a new star if the star is lost while looping was stopped.
410     pGuider->ForceFullFrame();
411     ResetAutoExposure();
412     UpdateButtonsStatus();
413     StatusMsg(_("Stopped."));
414     PhdController::AbortController("Stopped capturing");
415 }
416 
RawModeWarningKey(void)417 static wxString RawModeWarningKey(void)
418 {
419     return wxString::Format("/Confirm/%d/RawModeWarningEnabled", pConfig->GetCurrentProfileId());
420 }
421 
SuppressRawModeWarning(long)422 static void SuppressRawModeWarning(long)
423 {
424     pConfig->Global.SetBoolean(RawModeWarningKey(), false);
425 }
426 
WarnRawImageMode(void)427 static void WarnRawImageMode(void)
428 {
429     if (pCamera->FullSize != pCamera->DarkFrameSize())
430     {
431         pFrame->SuppressableAlert(RawModeWarningKey(), _("For refining the Bad-pixel Map PHD2 is now displaying raw camera data frames, which are a different size from ordinary guide frames for this camera."),
432             SuppressRawModeWarning, 0);
433     }
434 }
435 
436 /*
437  * OnExposeComplete is the dispatch routine that is called when an image has been taken
438  * by the background thread.
439  *
440  * It:
441  * - causes the image to be redrawn by calling pGuider->UpateImageDisplay()
442  * - calls the routine to update the guider state (which may do nothing)
443  * - calls any other appropriate state update routine depending upon the current state
444  * - updates button state based on appropriate state variables
445  * - schedules another exposure if CaptureActive is stil true
446  *
447  */
OnExposeComplete(usImage * pNewFrame,bool err)448 void MyFrame::OnExposeComplete(usImage *pNewFrame, bool err)
449 {
450     try
451     {
452         Debug.Write("OnExposeComplete: enter\n");
453 
454         m_exposurePending = false;
455 
456         if (pGuider->GetPauseType() == PAUSE_FULL)
457         {
458             delete pNewFrame;
459             Debug.Write("guider is paused, ignoring frame, not scheduling exposure\n");
460             return;
461         }
462 
463         if (err)
464         {
465             Debug.Write("OnExposeComplete: Capture Error reported\n");
466 
467             delete pNewFrame;
468 
469             bool stopping = !m_continueCapturing;
470             StopCapturing();
471             if (pGuider->IsCalibratingOrGuiding())
472             {
473                 pGuider->StopGuiding();
474                 pGuider->UpdateImageDisplay();
475             }
476             m_singleExposure.enabled = false;
477             EvtServer.NotifyLoopingStopped();
478             pGuider->Reset(false);
479             CaptureActive = m_continueCapturing;
480             UpdateButtonsStatus();
481             PhdController::AbortController(stopping ? "Image capture stopped" : "Error reported capturing image");
482             StatusMsg(_("Stopped."));
483 
484             // some camera drivers disconnect the camera on error
485             if (!pCamera->Connected)
486                 m_statusbar->UpdateStates();
487 
488             throw ERROR_INFO("Error reported capturing image");
489         }
490 
491         pNewFrame->FrameNum = ++m_frameCounter;
492 
493         if (m_rawImageMode && !m_rawImageModeWarningDone)
494         {
495             WarnRawImageMode();
496             m_rawImageModeWarningDone = true;
497         }
498 
499         // check for dark frame compatibility in case the frame size changed (binning changed)
500         if (pCamera->DarkFrameSize() != m_prevDarkFrameSize)
501         {
502             CheckDarkFrameGeometry();
503         }
504 
505         pGuider->UpdateGuideState(pNewFrame, !m_continueCapturing);
506         pNewFrame = NULL; // the guider owns it now
507 
508         PhdController::UpdateControllerState();
509 
510         Debug.Write(wxString::Format("OnExposeComplete: CaptureActive=%d m_continueCapturing=%d\n",
511             CaptureActive, m_continueCapturing));
512 
513         CaptureActive = m_continueCapturing;
514 
515         if (CaptureActive)
516         {
517             ScheduleExposure();
518         }
519         else
520         {
521             FinishStop();
522         }
523     }
524     catch (const wxString& Msg)
525     {
526         POSSIBLY_UNUSED(Msg);
527         UpdateButtonsStatus();
528     }
529 }
530 
OnExposeComplete(wxThreadEvent & event)531 void MyFrame::OnExposeComplete(wxThreadEvent& event)
532 {
533     usImage *image = event.GetPayload<usImage *>();
534     bool err = event.GetInt() != 0;
535     OnExposeComplete(image, err);
536 }
537 
OnMoveComplete(wxThreadEvent & event_)538 void MyFrame::OnMoveComplete(wxThreadEvent& event_)
539 {
540     try
541     {
542         MoveCompleteEvent& event = static_cast<MoveCompleteEvent&>(event_);
543 
544         if (event.moveOptions & MOVEOPT_MANUAL)
545         {
546             Debug.Write(wxString::Format("Manual Move completed, result = %d\n", event.result));
547             ClearStatusBarGuiderInfo();
548             return;
549         }
550 
551         Mount *mount = event.mount;
552         assert(mount->IsBusy());
553         mount->DecrementRequestCount();
554 
555         Mount::MOVE_RESULT moveResult = event.result;
556 
557         mount->LogGuideStepInfo();
558 
559         // deliver the outstanding GuidingStopped notification if this is a late-arriving
560         // move completion event
561         if (!pGuider->IsCalibratingOrGuiding() &&
562             (!pMount || !pMount->IsBusy()) &&
563             (!pSecondaryMount || !pSecondaryMount->IsBusy()))
564         {
565             pFrame->NotifyGuidingStopped();
566         }
567 
568         if (moveResult != Mount::MOVE_OK)
569         {
570             mount->IncrementErrorCount();
571 
572             if (moveResult == Mount::MOVE_ERROR_SLEWING)
573             {
574                 Debug.Write("mount move error indicates guiding should stop\n");
575                 pGuider->StopGuiding();
576             }
577             else if (moveResult == Mount::MOVE_ERROR_AO_LIMIT_REACHED)
578             {
579                 const StepInfo& step = TheAO()->GetFailedStepInfo();
580                 int newval = step.dx ? abs(step.x + step.dx) : abs(step.y + step.dy);
581 
582                 if (pGuider->IsCalibrating())
583                 {
584                     pFrame->Alert(wxString::Format(_("The AO exceeded its travel limit stepping"
585                         " from (%d, %d) to (%d, %d) and calibration cannot proceed. Try reducing"
586                         " the AO Travel setting to %d or lower and try again."),
587                         step.x, step.y, step.x + step.dx, step.y + step.dy, newval));
588 
589                     pGuider->StopGuiding();
590                 }
591                 else // Guiding
592                 {
593                     pFrame->Alert(wxString::Format(_("The AO exceeded its travel limit stepping"
594                         " from (%d, %d) to (%d, %d) and has been re-centered. Try reducing the AO"
595                         " Travel setting to %d or lower."),
596                         step.x, step.y, step.x + step.dx, step.y + step.dy, newval));
597 
598                     // Attempt to restart guiding after centering, otherwise the AO will
599                     // just bounce right back to the failing position
600                     bool saveSticky = pGuider->LockPosIsSticky();
601                     pGuider->SetLockPosIsSticky(false);
602                     pGuider->StopGuiding();
603                     pGuider->StartGuiding();
604                     pGuider->SetLockPosIsSticky(saveSticky);
605                 }
606             }
607 
608             throw ERROR_INFO("Error reported moving");
609         }
610     }
611     catch (const wxString& Msg)
612     {
613         POSSIBLY_UNUSED(Msg);
614     }
615 }
616 
OnButtonStop(wxCommandEvent & WXUNUSED (event))617 void MyFrame::OnButtonStop(wxCommandEvent& WXUNUSED(event))
618 {
619     Debug.Write("Stop button clicked\n");
620     StopCapturing();
621 }
622 
OnButtonAutoStar(wxCommandEvent & WXUNUSED (event))623 void MyFrame::OnButtonAutoStar(wxCommandEvent& WXUNUSED(event))
624 {
625     if (!wxGetKeyState(WXK_SHIFT))
626         AutoSelectStar();
627     else
628         pGuider->InvalidateCurrentPosition(true);
629 }
630 
OnGammaSlider(wxScrollEvent & WXUNUSED (event))631 void MyFrame::OnGammaSlider(wxScrollEvent& WXUNUSED(event))
632 {
633     int val = Gamma_Slider->GetValue();
634     pConfig->Profile.SetInt("/Gamma", val);
635     Stretch_gamma = (double) val / 100.0;
636     pGuider->UpdateImageDisplay();
637 }
638 
OnDark(wxCommandEvent & WXUNUSED (event))639 void MyFrame::OnDark(wxCommandEvent& WXUNUSED(event))
640 {
641     if (!pCamera || !pCamera->Connected)
642     {
643         wxMessageBox(_("Please connect to a camera first"), _("Info"));
644         return;
645     }
646 
647     DarksDialog dlg(this, true);
648     dlg.ShowModal();
649 
650     pCamera->SelectDark(RequestedExposureDuration());       // Might be req'd if user cancelled in midstream
651 }
652 
653 // Outside event handler because loading a dark library will automatically unload a defect map
LoadDarkHandler(bool checkIt)654 bool MyFrame::LoadDarkHandler(bool checkIt)
655 {
656     if (!pCamera || !pCamera->Connected)
657     {
658         Alert(_("You must connect a camera before loading a dark library"));
659         m_useDarksMenuItem->Check(false);
660         return false;
661     }
662     pConfig->Profile.SetBoolean("/camera/AutoLoadDarks", checkIt);
663     if (checkIt)  // enable it
664     {
665         m_useDarksMenuItem->Check(true);
666         if (pCamera->CurrentDefectMap)
667             LoadDefectMapHandler(false);
668         if (LoadDarkLibrary())
669             return true;
670         else
671         {
672             m_useDarksMenuItem->Check(false);
673             return false;
674         }
675     }
676     else
677     {
678         if (!pCamera->CurrentDarkFrame)
679         {
680             m_useDarksMenuItem->Check(false);      // shouldn't have gotten here
681             return false;
682         }
683         pCamera->ClearDarks();
684         m_useDarksMenuItem->Check(false);
685         StatusMsg(_("Dark library unloaded"));
686         return true;
687     }
688 }
689 
OnLoadDark(wxCommandEvent & evt)690 void MyFrame::OnLoadDark(wxCommandEvent& evt)
691 {
692     LoadDarkHandler(evt.IsChecked());
693     pFrame->UpdateStatusBarStateLabels();
694 }
695 
696 // Outside event handler because loading a defect map will automatically unload a dark library
LoadDefectMapHandler(bool checkIt)697 void MyFrame::LoadDefectMapHandler(bool checkIt)
698 {
699     if (!pCamera || !pCamera->Connected)
700     {
701         Alert(_("You must connect a camera before loading a bad-pixel map"));
702         darks_menu->FindItem(MENU_LOADDEFECTMAP)->Check(false);
703         return;
704     }
705     pConfig->Profile.SetBoolean("/camera/AutoLoadDefectMap", checkIt);
706     if (checkIt)
707     {
708         DefectMap *defectMap = DefectMap::LoadDefectMap(pConfig->GetCurrentProfileId());
709         if (defectMap)
710         {
711             if (pCamera->CurrentDarkFrame)
712                 LoadDarkHandler(false);
713             pCamera->SetDefectMap(defectMap);
714             m_useDarksMenuItem->Check(false);
715             m_useDefectMapMenuItem->Check(true);
716             StatusMsg(_("Defect map loaded"));
717         }
718         else
719         {
720             StatusMsg(_("Defect map not loaded"));
721         }
722     }
723     else
724     {
725         if (!pCamera->CurrentDefectMap)
726         {
727             m_useDefectMapMenuItem->Check(false);  // Shouldn't have gotten here
728             return;
729         }
730         pCamera->ClearDefectMap();
731         m_useDefectMapMenuItem->Check(false);
732         StatusMsg(_("Bad-pixel map unloaded"));
733     }
734 }
735 
OnLoadDefectMap(wxCommandEvent & evt)736 void MyFrame::OnLoadDefectMap(wxCommandEvent& evt)
737 {
738     LoadDefectMapHandler(evt.IsChecked());
739     pFrame->UpdateStatusBarStateLabels();
740 }
741 
OnRefineDefMap(wxCommandEvent & evt)742 void MyFrame::OnRefineDefMap(wxCommandEvent& evt)
743 {
744     if (!pCamera || !pCamera->Connected)
745     {
746         wxMessageBox(_("Please connect to a camera first"), _("Info"));
747         return;
748     }
749 
750     if (!pRefineDefMap)
751         pRefineDefMap = new RefineDefMap(this);
752 
753     if (pRefineDefMap->InitUI())                    // Required data present, UI built and ready to go
754     {
755         pRefineDefMap->Show();
756 
757         // Don't let the user build a new defect map while we're trying to refine one; and it almost certainly makes sense
758         // to have a defect map loaded if the user wants to refine it
759         m_takeDarksMenuItem->Enable(false);             // Dialog restores it when its window is closed
760         LoadDefectMapHandler(true);
761     }
762     else
763         pRefineDefMap->Destroy();                       // user cancelled out before starting the process
764 }
765 
OnImportCamCal(wxCommandEvent & evt)766 void MyFrame::OnImportCamCal(wxCommandEvent& evt)
767 {
768     if (!pCamera)
769     {
770         wxMessageBox(_("Please connect a camera first."));
771         return;
772     }
773 
774     CamCalImportDialog dlg(this);
775 
776     dlg.ShowModal();
777 }
778 
OnToolBar(wxCommandEvent & evt)779 void MyFrame::OnToolBar(wxCommandEvent& evt)
780 {
781     if (evt.IsChecked())
782     {
783         //wxSize toolBarSize = MainToolbar->GetSize();
784         m_mgr.GetPane(_T("MainToolBar")).Show().Bottom()/*.MinSize(toolBarSize)*/;
785     }
786     else
787     {
788         m_mgr.GetPane(_T("MainToolBar")).Hide();
789     }
790     m_mgr.Update();
791 }
792 
OnGraph(wxCommandEvent & evt)793 void MyFrame::OnGraph(wxCommandEvent& evt)
794 {
795     if (evt.IsChecked())
796     {
797         m_mgr.GetPane(_T("GraphLog")).Show().Bottom().Position(0).MinSize(-1, 240);
798     }
799     else
800     {
801         m_mgr.GetPane(_T("GraphLog")).Hide();
802     }
803     pGraphLog->SetState(evt.IsChecked());
804     m_mgr.Update();
805 }
806 
OnStats(wxCommandEvent & evt)807 void MyFrame::OnStats(wxCommandEvent& evt)
808 {
809     if (evt.IsChecked())
810     {
811         m_mgr.GetPane(_T("Stats")).Show().Bottom().Position(0).MinSize(-1, 240);
812     }
813     else
814     {
815         m_mgr.GetPane(_T("Stats")).Hide();
816     }
817     pStatsWin->SetState(evt.IsChecked());
818     m_mgr.Update();
819 }
820 
OnAoGraph(wxCommandEvent & evt)821 void MyFrame::OnAoGraph(wxCommandEvent& evt)
822 {
823     if (pStepGuiderGraph->SetState(evt.IsChecked()))
824     {
825         m_mgr.GetPane(_T("AOPosition")).Show().Right().Position(1).MinSize(293,208);
826     }
827     else
828     {
829         m_mgr.GetPane(_T("AOPosition")).Hide();
830     }
831     m_mgr.Update();
832 }
833 
OnStarProfile(wxCommandEvent & evt)834 void MyFrame::OnStarProfile(wxCommandEvent& evt)
835 {
836     if (evt.IsChecked())
837     {
838 #if defined (__APPLE__)
839         m_mgr.GetPane(_T("Profile")).Show().Float().MinSize(110,72);
840 #else
841         m_mgr.GetPane(_T("Profile")).Show().Right().Position(0).MinSize(115,85);
842         //m_mgr.GetPane(_T("Profile")).Show().Bottom().Layer(1).Position(2).MinSize(115,85);
843 #endif
844     }
845     else
846     {
847         m_mgr.GetPane(_T("Profile")).Hide();
848     }
849     pProfile->SetState(evt.IsChecked());
850     m_mgr.Update();
851 }
852 
OnTarget(wxCommandEvent & evt)853 void MyFrame::OnTarget(wxCommandEvent& evt)
854 {
855     if (evt.IsChecked())
856     {
857         m_mgr.GetPane(_T("Target")).Show().Right().Position(2).MinSize(293,208);
858     }
859     else
860     {
861         m_mgr.GetPane(_T("Target")).Hide();
862     }
863     pTarget->SetState(evt.IsChecked());
864     m_mgr.Update();
865 }
866 
867 // Redock windows and restore main window to size/position where everything should be readily accessible
OnRestoreWindows(wxCommandEvent & evt)868 void MyFrame::OnRestoreWindows(wxCommandEvent& evt)
869 {
870     wxAuiPaneInfoArray& panes = m_mgr.GetAllPanes();
871 
872     // Start by restoring the main window although it doesn't seem like this could be much of a problem
873     pFrame->SetSize(wxSize(800, 600));
874     pFrame->SetPosition(wxPoint(20, 20));           // Should work on any screen size
875     // Now re-dock all the windows that are being managed by wxAuiManager
876     int lim = panes.GetCount();
877     for (int i = 0; i < lim; i++)
878     {
879         panes.Item(i).Dock();                       // Already docked, shown or not, doesn't matter
880         if (panes.Item(i).name == _("Guider"))
881             panes.Item(i).Show(true);
882     }
883     m_mgr.Update();
884 
885     if (pCometTool)
886         pCometTool->Center();
887     if (pDriftTool)
888         pDriftTool->Center();
889     if (pGuidingAssistant)
890         pGuidingAssistant->Center();
891     if (pierFlipToolWin)
892         pierFlipToolWin->Center();
893     if (pNudgeLock)
894         pNudgeLock->Center();
895 }
896 
FlipCalibrationData()897 bool MyFrame::FlipCalibrationData()
898 {
899     bool bError = false;
900     Mount *scope = TheScope();
901 
902     if (scope)
903     {
904         bError = scope->FlipCalibration();
905 
906         if (!bError)
907         {
908             EvtServer.NotifyCalibrationDataFlipped(scope);
909         }
910     }
911 
912     return bError;
913 }
914 
OnAutoStar(wxCommandEvent & WXUNUSED (evt))915 void MyFrame::OnAutoStar(wxCommandEvent& WXUNUSED(evt))
916 {
917     AutoSelectStar();
918 }
919 
OnSetupCamera(wxCommandEvent & WXUNUSED (event))920 void MyFrame::OnSetupCamera(wxCommandEvent& WXUNUSED(event))
921 {
922     if (pCamera &&
923         (((pCamera->PropertyDialogType & PROPDLG_WHEN_CONNECTED) != 0 && pCamera->Connected) ||
924          ((pCamera->PropertyDialogType & PROPDLG_WHEN_DISCONNECTED) != 0 && !pCamera->Connected)))
925     {
926         pCamera->ShowPropertyDialog();
927     }
928 }
929 
OnAdvanced(wxCommandEvent & WXUNUSED (event))930 void MyFrame::OnAdvanced(wxCommandEvent& WXUNUSED(event))
931 {
932     pAdvancedDialog->LoadValues();
933 
934     if (pAdvancedDialog->ShowModal() == wxID_OK)
935     {
936         Debug.Write("User exited setup dialog with 'ok'\n");
937         pAdvancedDialog->UnloadValues();
938         pGraphLog->UpdateControls();
939         TestGuide::ManualGuideUpdateControls();
940         pConfig->Flush();
941     }
942     else
943     {
944         // Cancel event may require non-trivial undos
945         Debug.Write("User exited setup dialog with 'cancel'\n");
946         pAdvancedDialog->Undo();
947     }
948 }
949 
DarksWarningEnabledKey()950 static wxString DarksWarningEnabledKey()
951 {
952     // we want the key to be under "/Confirm" so ConfirmDialog::ResetAllDontAskAgain() resets it, but we also want the setting to be per-profile
953     return wxString::Format("/Confirm/%d/DarksWarningEnabled", pConfig->GetCurrentProfileId());
954 }
955 
SuppressDarksAlert(long)956 static void SuppressDarksAlert(long)
957 {
958     pConfig->Global.SetBoolean(DarksWarningEnabledKey(), false);
959 }
960 
ValidateDarksLoaded(void)961 static void ValidateDarksLoaded(void)
962 {
963     if (!pCamera->CurrentDarkFrame && !pCamera->CurrentDefectMap)
964     {
965         pFrame->SuppressableAlert(DarksWarningEnabledKey(),
966             _("For best results, use a Dark Library or a Bad-pixel Map "
967             "while guiding. This will help prevent PHD from locking on to a hot pixel. "
968             "Use the Darks menu to build a Dark Library or Bad-pixel Map."), SuppressDarksAlert, 0);
969     }
970 }
971 
GuideButtonClick(bool interactive,const wxString & context)972 void MyFrame::GuideButtonClick(bool interactive, const wxString& context)
973 {
974     Debug.Write(wxString::Format(_T("GuideButtonClick i=%d ctx=%s\n"), interactive, context));
975 
976     try
977     {
978         if (pMount == NULL)
979         {
980             // no mount selected -- should never happen
981             throw ERROR_INFO("pMount == NULL");
982         }
983 
984         if (!pMount->IsConnected())
985         {
986             throw ERROR_INFO("Unable to guide with no scope Connected");
987         }
988 
989         if (!pCamera || !pCamera->Connected)
990         {
991             throw ERROR_INFO("Unable to guide with no camera Connected");
992         }
993 
994         if (pGuider->GetState() < STATE_SELECTED)
995         {
996             wxMessageBox(_T("Please select a guide star before attempting to guide"));
997             throw ERROR_INFO("Unable to guide with state < STATE_SELECTED");
998         }
999 
1000         ValidateDarksLoaded();
1001 
1002         if (wxGetKeyState(WXK_SHIFT))
1003         {
1004             bool recalibrate = true;
1005             if (pMount->IsCalibrated() || (pSecondaryMount && pSecondaryMount->IsCalibrated()))
1006             {
1007                 recalibrate = ConfirmDialog::Confirm(_("Are you sure you want force recalibration?"),
1008                     "/force_recalibration_ok", _("Force Recalibration"));
1009             }
1010             if (recalibrate)
1011             {
1012                 pMount->ClearCalibration();
1013                 if (pSecondaryMount)
1014                     pSecondaryMount->ClearCalibration();
1015             }
1016         }
1017 
1018         if (interactive && pPointingSource && pPointingSource->IsConnected() && pPointingSource->CanReportPosition())
1019         {
1020             bool proceed = true;
1021             bool error = pPointingSource->PreparePositionInteractive();
1022 
1023             if (!error && fabs(pPointingSource->GetDeclination()) > Scope::DEC_COMP_LIMIT && !TheScope()->IsCalibrated() )
1024             {
1025                 proceed = ConfirmDialog::Confirm(
1026                     _("Calibration this far from the celestial equator will be error-prone.  For best results, calibrate at a declination of -20 to +20."),
1027                     "/highdec_calibration_ok", _("Confirm Calibration at Large Declination")
1028                     );
1029             }
1030             if (error || !proceed)
1031                 return;
1032         }
1033 
1034         StartGuiding();
1035     }
1036     catch (const wxString& Msg)
1037     {
1038         POSSIBLY_UNUSED(Msg);
1039         pGuider->Reset(false);
1040     }
1041 }
1042 
OnButtonGuide(wxCommandEvent & WXUNUSED (event))1043 void MyFrame::OnButtonGuide(wxCommandEvent& WXUNUSED(event))
1044 {
1045     GuideButtonClick(true, _T("Guide button clicked"));
1046 }
1047 
OnTestGuide(wxCommandEvent & WXUNUSED (evt))1048 void MyFrame::OnTestGuide(wxCommandEvent& WXUNUSED(evt))
1049 {
1050     if (!pMount || !pMount->IsConnected())
1051     {
1052         if (!pSecondaryMount || !pSecondaryMount->IsConnected())
1053         {
1054             wxMessageBox(_("Please connect a mount first."), _("Manual Guide"));
1055             return;
1056         }
1057     }
1058 
1059     if (!pManualGuide)
1060         pManualGuide = TestGuide::CreateManualGuideWindow();
1061 
1062     pManualGuide->Show();
1063 }
1064 
OnStarCrossTest(wxCommandEvent & evt)1065 void MyFrame::OnStarCrossTest(wxCommandEvent& evt)
1066 {
1067     if (!pMount || !pMount->IsConnected())
1068     {
1069         if (!pSecondaryMount || !pSecondaryMount->IsConnected())
1070         {
1071             wxMessageBox(_("Please connect a mount first."), _("Star-Cross Test"));
1072             return;
1073         }
1074     }
1075 
1076     if (!pStarCrossDlg)
1077         pStarCrossDlg = new StarCrossDialog(this);
1078 
1079     pStarCrossDlg->Show();
1080 }
1081 
OnPierFlipTool(wxCommandEvent & evt)1082 void MyFrame::OnPierFlipTool(wxCommandEvent& evt)
1083 {
1084     wxString error;
1085     if (!PierFlipTool::CanRunTool(&error))
1086     {
1087         wxMessageBox(error, _("Meridian Flip Calibration Tool"));
1088         return;
1089     }
1090     PierFlipTool::ShowPierFlipCalTool();
1091 }
1092 
OnPanelClose(wxAuiManagerEvent & evt)1093 void MyFrame::OnPanelClose(wxAuiManagerEvent& evt)
1094 {
1095     wxAuiPaneInfo *p = evt.GetPane();
1096     if (p->name == _T("MainToolBar"))
1097     {
1098         Menubar->Check(MENU_TOOLBAR, false);
1099     }
1100     if (p->name == _T("GraphLog"))
1101     {
1102         Menubar->Check(MENU_GRAPH, false);
1103         pGraphLog->SetState(false);
1104     }
1105     if (p->name == _T("Stats"))
1106     {
1107         Menubar->Check(MENU_STATS, false);
1108         pStatsWin->SetState(false);
1109     }
1110     if (p->name == _T("Profile"))
1111     {
1112         Menubar->Check(MENU_STARPROFILE, false);
1113         pProfile->SetState(false);
1114     }
1115     if (p->name == _T("AOPosition"))
1116     {
1117         Menubar->Check(MENU_AO_GRAPH, false);
1118         pStepGuiderGraph->SetState(false);
1119     }
1120     if (p->name == _T("Target"))
1121     {
1122         Menubar->Check(MENU_TARGET, false);
1123         pTarget->SetState(false);
1124     }
1125 }
1126 
AlertSetRAOnly(long param)1127 static void AlertSetRAOnly(long param)
1128 {
1129     pFrame->SetDitherRaOnly(true);
1130     pFrame->m_infoBar->Dismiss();
1131 }
1132 
CheckDecGuideModeAlert()1133 static void CheckDecGuideModeAlert()
1134 {
1135     // One-time, per-profile check to highlight new dithering behavior when Dec guide mode is north or south
1136 
1137     if (pConfig->Profile.GetBoolean("/ShowDecModeWarning", true))
1138     {
1139         if (pMount && !pMount->IsStepGuider())
1140         {
1141             Scope *scope = static_cast<Scope *>(pMount);
1142             DEC_GUIDE_MODE dgm = scope->GetDecGuideMode();
1143 
1144             if ((dgm == DEC_NORTH || dgm == DEC_SOUTH) && !pFrame->GetDitherRaOnly())
1145             {
1146                 wxString msg = _("With your current setting for Dec guide mode, this version of PHD2 will "
1147                                  "dither in Declination. To restore the old behavior you can set the RA-only "
1148                                  "option in the Dither Settings of the Advanced Dialog.");
1149                 pFrame->Alert(msg, 0,
1150                               _("Set RA-only now"), AlertSetRAOnly, 0);
1151 
1152                pConfig->Profile.SetBoolean("/ShowDecModeWarning", false);
1153             }
1154         }
1155     }
1156 }
1157 
OnSelectGear(wxCommandEvent & evt)1158 void MyFrame::OnSelectGear(wxCommandEvent& evt)
1159 {
1160     try
1161     {
1162         if (CaptureActive)
1163         {
1164             throw ERROR_INFO("OnSelectGear called while CaptureActive");
1165         }
1166 
1167         if (pConfig->NumProfiles() == 1 && pGearDialog->IsEmptyProfile())
1168         {
1169             if (ConfirmDialog::Confirm(
1170                 _("It looks like this is a first-time connection to your camera and mount. The Setup Wizard can help\n"
1171                   "you with that and will also establish baseline guiding parameters for your new configuration.\n"
1172                   "Would you like to use the Setup Wizard now?"),
1173                   "/use_new_profile_wizard", _("Yes"), _("No"), _("Setup Wizard Recommendation")))
1174             {
1175                 pGearDialog->ShowProfileWizard(evt);
1176                 return;
1177             }
1178         }
1179 
1180         pGearDialog->ShowGearDialog(wxGetKeyState(WXK_SHIFT));
1181 
1182         CheckDecGuideModeAlert();
1183     }
1184     catch (const wxString& Msg)
1185     {
1186         POSSIBLY_UNUSED(Msg);
1187     }
1188 }
1189 
OnBookmarksShow(wxCommandEvent & evt)1190 void MyFrame::OnBookmarksShow(wxCommandEvent& evt)
1191 {
1192     pGuider->SetBookmarksShown(evt.IsChecked());
1193 }
1194 
OnBookmarksSetAtLockPos(wxCommandEvent & evt)1195 void MyFrame::OnBookmarksSetAtLockPos(wxCommandEvent& evt)
1196 {
1197     pGuider->BookmarkLockPosition();
1198 }
1199 
OnBookmarksSetAtCurPos(wxCommandEvent & evt)1200 void MyFrame::OnBookmarksSetAtCurPos(wxCommandEvent& evt)
1201 {
1202     pGuider->BookmarkCurPosition();
1203 }
1204 
OnBookmarksClearAll(wxCommandEvent & evt)1205 void MyFrame::OnBookmarksClearAll(wxCommandEvent& evt)
1206 {
1207     pGuider->DeleteAllBookmarks();
1208 }
1209 
OnTextControlSetFocus(wxFocusEvent & evt)1210 void MyFrame::OnTextControlSetFocus(wxFocusEvent& evt)
1211 {
1212     m_showBookmarksMenuItem->SetAccel(0);
1213     m_bookmarkLockPosMenuItem->SetAccel(0);
1214     evt.Skip();
1215 }
1216 
OnTextControlKillFocus(wxFocusEvent & evt)1217 void MyFrame::OnTextControlKillFocus(wxFocusEvent& evt)
1218 {
1219     m_showBookmarksMenuItem->SetAccel(m_showBookmarksAccel);
1220     m_bookmarkLockPosMenuItem->SetAccel(m_bookmarkLockPosAccel);
1221     evt.Skip();
1222 }
1223 
OnCharHook(wxKeyEvent & evt)1224 void MyFrame::OnCharHook(wxKeyEvent& evt)
1225 {
1226     bool handled = false;
1227 
1228     // This never gets called on OSX (since we moved to wxWidgets-3.0.0), so we
1229     // rely on the menu accelerators on the MyFrame to provide the keyboard
1230     // responses. For Windows and Linux, we keep this here so the keystrokes
1231     // work when other windows like the Drift Tool window have focus.
1232 
1233     if (evt.GetKeyCode() == 'B')
1234     {
1235         if (!evt.GetEventObject()->IsKindOf(wxCLASSINFO(wxTextCtrl)))
1236         {
1237             int modifiers;
1238 #ifdef __WXOSX__
1239             modifiers = 0;
1240             if (wxGetKeyState(WXK_ALT))
1241                 modifiers |= wxMOD_ALT;
1242             if (wxGetKeyState(WXK_CONTROL))
1243                 modifiers |= wxMOD_CONTROL;
1244             if (wxGetKeyState(WXK_SHIFT))
1245                 modifiers |= wxMOD_SHIFT;
1246             if (wxGetKeyState(WXK_RAW_CONTROL))
1247                 modifiers |= wxMOD_RAW_CONTROL;
1248 #else
1249             modifiers = evt.GetModifiers();
1250 #endif
1251             if (!modifiers)
1252             {
1253                 pGuider->ToggleShowBookmarks();
1254                 bookmarks_menu->Check(MENU_BOOKMARKS_SHOW, pGuider->GetBookmarksShown());
1255                 handled = true;
1256             }
1257             else if (modifiers == wxMOD_CONTROL)
1258             {
1259                 pGuider->DeleteAllBookmarks();
1260                 handled = true;
1261             }
1262             else if (modifiers == wxMOD_SHIFT)
1263             {
1264                 pGuider->BookmarkLockPosition();
1265                 handled = true;
1266             }
1267         }
1268     }
1269 
1270     if (!handled)
1271     {
1272         evt.Skip();
1273     }
1274 }
1275