1 /*
2     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3     SPDX-FileCopyrightText: 2021 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "guidedriftgraph.h"
9 #include "klocalizedstring.h"
10 #include "ksnotification.h"
11 #include "kstarsdata.h"
12 #include "Options.h"
13 
GuideDriftGraph(QWidget * parent)14 GuideDriftGraph::GuideDriftGraph(QWidget *parent)
15 {
16     Q_UNUSED(parent);
17     // Drift Graph Color Settings
18     setBackground(QBrush(Qt::black));
19     xAxis->setBasePen(QPen(Qt::white, 1));
20     yAxis->setBasePen(QPen(Qt::white, 1));
21     xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
22     yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
23     xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
24     yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
25     xAxis->grid()->setZeroLinePen(Qt::NoPen);
26     yAxis->grid()->setZeroLinePen(QPen(Qt::white, 1));
27     xAxis->setBasePen(QPen(Qt::white, 1));
28     yAxis->setBasePen(QPen(Qt::white, 1));
29     yAxis2->setBasePen(QPen(Qt::white, 1));
30     xAxis->setTickPen(QPen(Qt::white, 1));
31     yAxis->setTickPen(QPen(Qt::white, 1));
32     yAxis2->setTickPen(QPen(Qt::white, 1));
33     xAxis->setSubTickPen(QPen(Qt::white, 1));
34     yAxis->setSubTickPen(QPen(Qt::white, 1));
35     yAxis2->setSubTickPen(QPen(Qt::white, 1));
36     xAxis->setTickLabelColor(Qt::white);
37     yAxis->setTickLabelColor(Qt::white);
38     yAxis2->setTickLabelColor(Qt::white);
39     xAxis->setLabelColor(Qt::white);
40     yAxis->setLabelColor(Qt::white);
41     yAxis2->setLabelColor(Qt::white);
42 
43     snrAxis = axisRect()->addAxis(QCPAxis::atLeft, 0);
44     snrAxis->setVisible(false);
45     // This will be reset to the actual data values.
46     snrAxis->setRange(-100, 100);
47 
48     //Horizontal Axis Time Ticker Settings
49     QSharedPointer<QCPAxisTickerTime> timeTicker(new QCPAxisTickerTime);
50     timeTicker->setTimeFormat("%m:%s");
51     xAxis->setTicker(timeTicker);
52 
53     // Axis Labels Settings
54     yAxis2->setVisible(true);
55     yAxis2->setTickLabels(true);
56     yAxis->setLabelFont(QFont(font().family(), 10));
57     yAxis2->setLabelFont(QFont(font().family(), 10));
58     xAxis->setTickLabelFont(QFont(font().family(), 9));
59     yAxis->setTickLabelFont(QFont(font().family(), 9));
60     yAxis2->setTickLabelFont(QFont(font().family(), 9));
61     yAxis->setLabelPadding(1);
62     yAxis2->setLabelPadding(1);
63     yAxis->setLabel(i18n("drift (arcsec)"));
64     yAxis2->setLabel(i18n("pulse (ms)"));
65 
66     setupNSEWLabels();
67 
68     int scale =
69         50;  //This is a scaling value between the left and the right axes of the driftGraph, it could be stored in kstars kcfg
70 
71     //Sets the default ranges
72     xAxis->setRange(0, 120, Qt::AlignRight);
73     yAxis->setRange(-3, 3);
74     yAxis2->setRange(-3 * scale, 3 * scale);
75 
76     //This sets up the legend
77     legend->setVisible(true);
78     legend->setFont(QFont(font().family(), 7));
79     legend->setTextColor(Qt::white);
80     legend->setBrush(QBrush(Qt::black));
81     legend->setIconSize(4, 12);
82     legend->setFillOrder(QCPLegend::foColumnsFirst);
83     axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignLeft | Qt::AlignBottom);
84 
85     // RA Curve
86     addGraph(xAxis, yAxis);
87     graph(GuideGraph::G_RA)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")));
88     graph(GuideGraph::G_RA)->setName("RA");
89     graph(GuideGraph::G_RA)->setLineStyle(QCPGraph::lsStepLeft);
90 
91     // DE Curve
92     addGraph(xAxis, yAxis);
93     graph(GuideGraph::G_DEC)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")));
94     graph(GuideGraph::G_DEC)->setName("DE");
95     graph(GuideGraph::G_DEC)->setLineStyle(QCPGraph::lsStepLeft);
96 
97     // RA highlighted Point
98     addGraph(xAxis, yAxis);
99     graph(GuideGraph::G_RA_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone);
100     graph(GuideGraph::G_RA_HIGHLIGHT)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")));
101     graph(GuideGraph::G_RA_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle,
102             QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 10));
103 
104     // DE highlighted Point
105     addGraph(xAxis, yAxis);
106     graph(GuideGraph::G_DEC_HIGHLIGHT)->setLineStyle(QCPGraph::lsNone);
107     graph(GuideGraph::G_DEC_HIGHLIGHT)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")));
108     graph(GuideGraph::G_DEC_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle,
109             QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 10));
110 
111     // RA Pulse
112     addGraph(xAxis, yAxis2);
113     QColor raPulseColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"));
114     raPulseColor.setAlpha(75);
115     graph(GuideGraph::G_RA_PULSE)->setPen(QPen(raPulseColor));
116     graph(GuideGraph::G_RA_PULSE)->setBrush(QBrush(raPulseColor, Qt::Dense4Pattern));
117     graph(GuideGraph::G_RA_PULSE)->setName("RA Pulse");
118     graph(GuideGraph::G_RA_PULSE)->setLineStyle(QCPGraph::lsStepLeft);
119 
120     // DEC Pulse
121     addGraph(xAxis, yAxis2);
122     QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"));
123     dePulseColor.setAlpha(75);
124     graph(GuideGraph::G_DEC_PULSE)->setPen(QPen(dePulseColor));
125     graph(GuideGraph::G_DEC_PULSE)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern));
126     graph(GuideGraph::G_DEC_PULSE)->setName("DEC Pulse");
127     graph(GuideGraph::G_DEC_PULSE)->setLineStyle(QCPGraph::lsStepLeft);
128 
129     // SNR
130     addGraph(xAxis, snrAxis);
131     graph(GuideGraph::G_SNR)->setPen(QPen(Qt::yellow));
132     graph(GuideGraph::G_SNR)->setName("SNR");
133     graph(GuideGraph::G_SNR)->setLineStyle(QCPGraph::lsStepLeft);
134 
135     // RA RMS
136     addGraph(xAxis, yAxis);
137     graph(GuideGraph::G_RA_RMS)->setPen(QPen(Qt::red));
138     graph(GuideGraph::G_RA_RMS)->setName("RA RMS");
139     graph(GuideGraph::G_RA_RMS)->setLineStyle(QCPGraph::lsStepLeft);
140 
141     // DEC RMS
142     addGraph(xAxis, yAxis);
143     graph(GuideGraph::G_DEC_RMS)->setPen(QPen(Qt::red));
144     graph(GuideGraph::G_DEC_RMS)->setName("DEC RMS");
145     graph(GuideGraph::G_DEC_RMS)->setLineStyle(QCPGraph::lsStepLeft);
146 
147     // Total RMS
148     addGraph(xAxis, yAxis);
149     graph(GuideGraph::G_RMS)->setPen(QPen(Qt::red));
150     graph(GuideGraph::G_RMS)->setName("RMS");
151     graph(GuideGraph::G_RMS)->setLineStyle(QCPGraph::lsStepLeft);
152 
153     //This will prevent the highlighted points and Pulses from showing up in the legend.
154     legend->removeItem(GuideGraph::G_DEC_RMS);
155     legend->removeItem(GuideGraph::G_RA_RMS);
156     legend->removeItem(GuideGraph::G_DEC_PULSE);
157     legend->removeItem(GuideGraph::G_RA_PULSE);
158     legend->removeItem(GuideGraph::G_DEC_HIGHLIGHT);
159     legend->removeItem(GuideGraph::G_RA_HIGHLIGHT);
160 
161     setInteractions(QCP::iRangeZoom);
162     axisRect()->setRangeZoom(Qt::Orientation::Vertical);
163     setInteraction(QCP::iRangeDrag, true);
164     //This sets the visibility of graph components to the stored values.
165     graph(GuideGraph::G_RA)->setVisible(Options::rADisplayedOnGuideGraph()); //RA data
166     graph(GuideGraph::G_DEC)->setVisible(Options::dEDisplayedOnGuideGraph()); //DEC data
167     graph(GuideGraph::G_RA_HIGHLIGHT)->setVisible(Options::rADisplayedOnGuideGraph()); //RA highlighted point
168     graph(GuideGraph::G_DEC_HIGHLIGHT)->setVisible(Options::dEDisplayedOnGuideGraph()); //DEC highlighted point
169     graph(GuideGraph::G_RA_PULSE)->setVisible(Options::rACorrDisplayedOnGuideGraph()); //RA Pulses
170     graph(GuideGraph::G_DEC_PULSE)->setVisible(Options::dECorrDisplayedOnGuideGraph()); //DEC Pulses
171     graph(GuideGraph::G_SNR)->setVisible(Options::sNRDisplayedOnGuideGraph()); //SNR
172     setRMSVisibility();
173 
174     updateCorrectionsScaleVisibility();
175 }
176 
guideHistory(int sliderValue,bool graphOnLatestPt)177 void GuideDriftGraph::guideHistory(int sliderValue, bool graphOnLatestPt)
178 {
179     graph(GuideGraph::G_RA_HIGHLIGHT)->data()->clear(); //Clear RA highlighted point
180     graph(GuideGraph::G_DEC_HIGHLIGHT)->data()->clear(); //Clear DEC highlighted point
181     double t = graph(GuideGraph::G_RA)->dataMainKey(sliderValue); //Get time from RA data
182     double ra = graph(GuideGraph::G_RA)->dataMainValue(sliderValue); //Get RA from RA data
183     double de = graph(GuideGraph::G_DEC)->dataMainValue(sliderValue); //Get DEC from DEC data
184     double raPulse = graph(GuideGraph::G_RA_PULSE)->dataMainValue(sliderValue); //Get RA Pulse from RA pulse data
185     double dePulse = graph(GuideGraph::G_DEC_PULSE)->dataMainValue(sliderValue); //Get DEC Pulse from DEC pulse data
186     graph(GuideGraph::G_RA_HIGHLIGHT)->addData(t, ra); //Set RA highlighted point
187     graph(GuideGraph::G_DEC_HIGHLIGHT)->addData(t, de); //Set DEC highlighted point
188 
189     //This will allow the graph to scroll left and right along with the guide slider
190     if (xAxis->range().contains(t) == false)
191     {
192         if(t < xAxis->range().lower)
193         {
194             xAxis->setRange(t, t + xAxis->range().size());
195         }
196         if(t > xAxis->range().upper)
197         {
198             xAxis->setRange(t - xAxis->range().size(), t);
199         }
200     }
201     replot();
202     double snr = 0;
203     if (graph(GuideGraph::G_SNR)->data()->size() > 0)
204         snr = graph(GuideGraph::G_SNR)->dataMainValue(sliderValue);
205     double rms = graph(GuideGraph::G_RMS)->dataMainValue(sliderValue);
206 
207     if(!graphOnLatestPt)
208     {
209         QTime localTime = guideTimer;
210         localTime = localTime.addSecs(t);
211 
212         QPoint localTooltipCoordinates = graph(GuideGraph::G_RA)->dataPixelPosition(sliderValue).toPoint();
213         QPoint globalTooltipCoordinates = mapToGlobal(localTooltipCoordinates);
214 
215         if(raPulse == 0 && dePulse == 0)
216         {
217             QToolTip::showText(
218                 globalTooltipCoordinates,
219                 i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is the RMS error in arcseconds; %5 is the SNR",
220                       "<table>"
221                       "<tr><td>LT:   </td><td>%1</td></tr>"
222                       "<tr><td>RA:   </td><td>%2 \"</td></tr>"
223                       "<tr><td>DE:   </td><td>%3 \"</td></tr>"
224                       "<tr><td>RMS:   </td><td>%4 \"</td></tr>"
225                       "<tr><td>SNR:   </td><td>%5 \"</td></tr>"
226                       "</table>",
227                       localTime.toString("hh:mm:ss AP"),
228                       QString::number(ra, 'f', 2),
229                       QString::number(de, 'f', 2),
230                       QString::number(rms, 'f', 2),
231                       QString::number(snr, 'f', 1)
232                      ));
233         }
234         else
235         {
236             QToolTip::showText(
237                 globalTooltipCoordinates,
238                 i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is the RMS error in arcseconds; %5 is the SNR; %6 is RA Pulse in ms; %7 is DE Pulse in ms",
239                       "<table>"
240                       "<tr><td>LT:   </td><td>%1</td></tr>"
241                       "<tr><td>RA:   </td><td>%2 \"</td></tr>"
242                       "<tr><td>DE:   </td><td>%3 \"</td></tr>"
243                       "<tr><td>RMS:   </td><td>%4 \"</td></tr>"
244                       "<tr><td>SNR:   </td><td>%5 \"</td></tr>"
245                       "<tr><td>RA Pulse:   </td><td>%6 ms</td></tr>"
246                       "<tr><td>DE Pulse:   </td><td>%7 ms</td></tr>"
247                       "</table>",
248                       localTime.toString("hh:mm:ss AP"),
249                       QString::number(ra, 'f', 2),
250                       QString::number(de, 'f', 2),
251                       QString::number(rms, 'f', 2),
252                       QString::number(snr, 'f', 1),
253                       QString::number(raPulse, 'f', 2),
254                       QString::number(dePulse, 'f', 2)
255                      )); //The pulses were divided by 100 before they were put on the graph.
256         }
257 
258     }
259 }
260 
handleVerticalPlotSizeChange()261 void GuideDriftGraph::handleVerticalPlotSizeChange()
262 {
263 }
264 
handleHorizontalPlotSizeChange()265 void GuideDriftGraph::handleHorizontalPlotSizeChange()
266 {
267 }
268 
setupNSEWLabels()269 void GuideDriftGraph::setupNSEWLabels()
270 {
271     //Labels for N/S/E/W
272     QColor raLabelColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"));
273     QColor deLabelColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"));
274 
275     QCPItemText *northLabel = new QCPItemText(this);
276     northLabel->setColor(deLabelColor);
277     northLabel->setFont(QFont(font().family(), 9));
278     northLabel->setText(i18nc("North", "N"));
279     northLabel->position->setType(QCPItemPosition::ptViewportRatio);
280     northLabel->position->setCoords(0.7, 0.12);
281     northLabel->setVisible(true);
282 
283     QCPItemText *southLabel = new QCPItemText(this);
284     southLabel->setColor(deLabelColor);
285     southLabel->setFont(QFont(font().family(), 9));
286     southLabel->setText(i18nc("South", "S"));
287     southLabel->position->setType(QCPItemPosition::ptViewportRatio);
288     southLabel->position->setCoords(0.7, 0.8);
289     southLabel->setVisible(true);
290 
291     QCPItemText *westLabel = new QCPItemText(this);
292     westLabel->setColor(raLabelColor);
293     westLabel->setFont(QFont(font().family(), 9));
294     westLabel->setText(i18nc("West", "W"));
295     westLabel->position->setType(QCPItemPosition::ptViewportRatio);
296     westLabel->position->setCoords(0.78, 0.12);
297     westLabel->setVisible(true);
298 
299     QCPItemText *eastLabel = new QCPItemText(this);
300     eastLabel->setColor(raLabelColor);
301     eastLabel->setFont(QFont(font().family(), 9));
302     eastLabel->setText(i18nc("East", "E"));
303     eastLabel->position->setType(QCPItemPosition::ptViewportRatio);
304     eastLabel->position->setCoords(0.8, 0.8);
305     eastLabel->setVisible(true);
306 
307 }
308 
autoScaleGraphs()309 void GuideDriftGraph::autoScaleGraphs()
310 {
311     yAxis->setRange(-3, 3);
312     // First bool below is only_enlarge, 2nd is only look at values that are visible in X.
313     // Net result is all RA & DEC points within the times being plotted should be visible.
314     // This is only called when the autoScale button is pressed.
315     graph(GuideGraph::G_RA)->rescaleValueAxis(false, true);
316     graph(GuideGraph::G_DEC)->rescaleValueAxis(true, true);
317     replot();
318 }
319 
zoomX(int zoomLevel)320 void GuideDriftGraph::zoomX(int zoomLevel)
321 {
322     double key = (guideTimer.isValid() || guideTimer.isNull()) ? 0 : guideTimer.elapsed() / 1000.0;
323 
324     // The # of seconds displayd on the x-axis of the drift-graph for the various zoom levels.
325     static std::vector<int> zoomLevels = {15, 30, 60, 120, 300, 900, 1800, 3600, 7200, 14400};
326 
327     zoomLevel = std::max(0, zoomLevel);
328     driftGraphZoomLevel = std::min(static_cast<int>(zoomLevels.size() - 1), zoomLevel);
329 
330     xAxis->setRange(key - zoomLevels[driftGraphZoomLevel], key);
331 }
332 
zoomInX()333 void GuideDriftGraph::zoomInX()
334 {
335     zoomX(driftGraphZoomLevel - 1);
336     replot();
337 }
338 
zoomOutX()339 void GuideDriftGraph::zoomOutX()
340 {
341     zoomX(driftGraphZoomLevel + 1);
342     replot();
343 }
344 
setCorrectionGraphScale(int value)345 void GuideDriftGraph::setCorrectionGraphScale(int value)
346 {
347     yAxis2->setRange(yAxis->range().lower * value,
348                      yAxis->range().upper * value);
349     replot();
350 }
351 
clear()352 void GuideDriftGraph::clear()
353 {
354     graph(GuideGraph::G_RA)->data()->clear(); //RA data
355     graph(GuideGraph::G_DEC)->data()->clear(); //DEC data
356     graph(GuideGraph::G_RA_HIGHLIGHT)->data()->clear(); //RA highlighted point
357     graph(GuideGraph::G_DEC_HIGHLIGHT)->data()->clear(); //DEC highlighted point
358     graph(GuideGraph::G_RA_PULSE)->data()->clear(); //RA Pulses
359     graph(GuideGraph::G_DEC_PULSE)->data()->clear(); //DEC Pulses
360     graph(GuideGraph::G_SNR)->data()->clear(); //SNR
361     graph(GuideGraph::G_RA_RMS)->data()->clear(); //RA RMS
362     graph(GuideGraph::G_DEC_RMS)->data()->clear(); //DEC RMS
363     graph(GuideGraph::G_RMS)->data()->clear(); //RMS
364     clearItems();  //Clears dither text items from the graph
365     setupNSEWLabels();
366     replot();
367 }
368 
toggleShowPlot(GuideGraph::DRIFT_GRAPH_INDICES plot,bool isChecked)369 void GuideDriftGraph::toggleShowPlot(GuideGraph::DRIFT_GRAPH_INDICES plot, bool isChecked)
370 {
371     switch (plot)
372     {
373         case GuideGraph::G_RA:
374             Options::setRADisplayedOnGuideGraph(isChecked);
375             graph(GuideGraph::G_RA)->setVisible(isChecked);
376             graph(GuideGraph::G_RA_HIGHLIGHT)->setVisible(isChecked);
377             setRMSVisibility();
378             replot();
379             break;
380         case GuideGraph::G_DEC:
381             Options::setDEDisplayedOnGuideGraph(isChecked);
382             graph(GuideGraph::G_DEC)->setVisible(isChecked);
383             graph(GuideGraph::G_DEC_HIGHLIGHT)->setVisible(isChecked);
384             setRMSVisibility();
385             replot();
386             break;
387         case GuideGraph::G_RA_PULSE:
388             Options::setRACorrDisplayedOnGuideGraph(isChecked);
389             graph(GuideGraph::G_RA_PULSE)->setVisible(isChecked);
390             updateCorrectionsScaleVisibility();
391             break;
392         case GuideGraph::G_DEC_PULSE:
393             Options::setDECorrDisplayedOnGuideGraph(isChecked);
394             graph(GuideGraph::G_DEC_PULSE)->setVisible(isChecked);
395             updateCorrectionsScaleVisibility();
396             break;
397         case GuideGraph::G_SNR:
398             Options::setSNRDisplayedOnGuideGraph(isChecked);
399             graph(GuideGraph::G_SNR)->setVisible(isChecked);
400             replot();
401             break;
402         case GuideGraph::G_RMS:
403             Options::setRMSDisplayedOnGuideGraph(isChecked);
404             setRMSVisibility();
405             replot();
406             break;
407         default:
408             break;
409     }
410 }
411 
setRMSVisibility()412 void GuideDriftGraph::setRMSVisibility()
413 {
414     if (!Options::rMSDisplayedOnGuideGraph())
415     {
416         graph(GuideGraph::G_RA_RMS)->setVisible(false);
417         graph(GuideGraph::G_DEC_RMS)->setVisible(false);
418         graph(GuideGraph::G_RMS)->setVisible(false);
419         return;
420     }
421 
422     if ((Options::dEDisplayedOnGuideGraph() && Options::rADisplayedOnGuideGraph()) ||
423             (!Options::dEDisplayedOnGuideGraph() && !Options::rADisplayedOnGuideGraph()))
424     {
425         graph(GuideGraph::G_RA_RMS)->setVisible(false);
426         graph(GuideGraph::G_DEC_RMS)->setVisible(false);
427         graph(GuideGraph::G_RMS)->setVisible(true);
428     }
429     else if (!Options::dEDisplayedOnGuideGraph() && Options::rADisplayedOnGuideGraph())
430     {
431         graph(GuideGraph::G_RA_RMS)->setVisible(true);
432         graph(GuideGraph::G_DEC_RMS)->setVisible(false);
433         graph(GuideGraph::G_RMS)->setVisible(false);
434     }
435     else
436     {
437         graph(GuideGraph::G_RA_RMS)->setVisible(false);
438         graph(GuideGraph::G_DEC_RMS)->setVisible(true);
439         graph(GuideGraph::G_RMS)->setVisible(false);
440     }
441 }
442 
exportGuideData()443 void GuideDriftGraph::exportGuideData()
444 {
445     int numPoints = graph(GuideGraph::G_RA)->dataCount();
446     if (numPoints == 0)
447         return;
448 
449     QUrl exportFile = QFileDialog::getSaveFileUrl(this, i18nc("@title:window", "Export Guide Data"), guideURLPath,
450                       "CSV File (*.csv)");
451     if (exportFile.isEmpty()) // if user presses cancel
452         return;
453     if (exportFile.toLocalFile().endsWith(QLatin1String(".csv")) == false)
454         exportFile.setPath(exportFile.toLocalFile() + ".csv");
455 
456     QString path = exportFile.toLocalFile();
457 
458     if (QFile::exists(path))
459     {
460         int r = KMessageBox::warningContinueCancel(nullptr,
461                 i18n("A file named \"%1\" already exists. "
462                      "Overwrite it?",
463                      exportFile.fileName()),
464                 i18n("Overwrite File?"), KStandardGuiItem::overwrite());
465         if (r == KMessageBox::Cancel)
466             return;
467     }
468 
469     if (!exportFile.isValid())
470     {
471         QString message = i18n("Invalid URL: %1", exportFile.url());
472         KSNotification::sorry(message, i18n("Invalid URL"));
473         return;
474     }
475 
476     QFile file;
477     file.setFileName(path);
478     if (!file.open(QIODevice::WriteOnly))
479     {
480         QString message = i18n("Unable to write to file %1", path);
481         KSNotification::sorry(message, i18n("Could Not Open File"));
482         return;
483     }
484 
485     QTextStream outstream(&file);
486 
487     outstream <<
488               "Frame #, Time Elapsed (sec), Local Time (HMS), RA Error (arcsec), DE Error (arcsec), RA Pulse  (ms), DE Pulse (ms)" <<
489               endl;
490 
491     for (int i = 0; i < numPoints; i++)
492     {
493         double t = graph(GuideGraph::G_RA)->dataMainKey(i);
494         double ra = graph(GuideGraph::G_RA)->dataMainValue(i);
495         double de = graph(GuideGraph::G_DEC)->dataMainValue(i);
496         double raPulse = graph(GuideGraph::G_RA_PULSE)->dataMainValue(i);
497         double dePulse = graph(GuideGraph::G_DEC_PULSE)->dataMainValue(i);
498 
499         QTime localTime = guideTimer;
500         localTime = localTime.addSecs(t);
501 
502         outstream << i << ',' << t << ',' << localTime.toString("hh:mm:ss AP") << ',' << ra << ',' << de << ',' << raPulse << ',' <<
503                   dePulse << ',' << endl;
504     }
505     file.close();
506 }
507 
resetTimer()508 void GuideDriftGraph::resetTimer()
509 {
510     guideTimer = QTime::currentTime();
511 }
512 
connectGuider(Ekos::GuideInterface * guider)513 void GuideDriftGraph::connectGuider(Ekos::GuideInterface *guider)
514 {
515     connect(guider, &Ekos::GuideInterface::newAxisDelta, this, &GuideDriftGraph::setAxisDelta);
516     connect(guider, &Ekos::GuideInterface::newAxisPulse, this, &GuideDriftGraph::setAxisPulse);
517     connect(guider, &Ekos::GuideInterface::newAxisSigma, this, &GuideDriftGraph::setAxisSigma);
518     connect(guider, &Ekos::GuideInterface::newSNR, this, &GuideDriftGraph::setSNR);
519 
520     resetTimer();
521 }
522 
setAxisDelta(double ra,double de)523 void GuideDriftGraph::setAxisDelta(double ra, double de)
524 {
525     // Time since timer started.
526     double key = guideTimer.elapsed() / 1000.0;
527 
528     // similar to same operation in Guide::setAxisDelta
529     ra = -ra;
530 
531     graph(GuideGraph::G_RA)->addData(key, ra);
532     graph(GuideGraph::G_DEC)->addData(key, de);
533 
534     if(graphOnLatestPt)
535     {
536         xAxis->setRange(key, xAxis->range().size(), Qt::AlignRight);
537         graph(GuideGraph::G_RA_HIGHLIGHT)->data()->clear(); //Clear highlighted RA point
538         graph(GuideGraph::G_DEC_HIGHLIGHT)->data()->clear(); //Clear highlighted DEC point
539         graph(GuideGraph::G_RA_HIGHLIGHT)->addData(key, ra); //Set highlighted RA point to latest point
540         graph(GuideGraph::G_DEC_HIGHLIGHT)->addData(key, de); //Set highlighted DEC point to latest point
541     }
542     replot();
543 }
544 
setAxisSigma(double ra,double de)545 void GuideDriftGraph::setAxisSigma(double ra, double de)
546 {
547     const double key = guideTimer.elapsed() / 1000.0;
548     const double total = std::hypot(ra, de);
549     graph(GuideGraph::G_RA_RMS)->addData(key, ra);
550     graph(GuideGraph::G_DEC_RMS)->addData(key, de);
551     graph(GuideGraph::G_RMS)->addData(key, total);
552 }
553 
setAxisPulse(double ra,double de)554 void GuideDriftGraph::setAxisPulse(double ra, double de)
555 {
556     double key = guideTimer.elapsed() / 1000.0;
557     graph(GuideGraph::G_RA_PULSE)->addData(key, ra);
558     graph(GuideGraph::G_DEC_PULSE)->addData(key, de);
559 }
560 
setSNR(double snr)561 void GuideDriftGraph::setSNR(double snr)
562 {
563     double key = guideTimer.elapsed() / 1000.0;
564     graph(GuideGraph::G_SNR)->addData(key, snr);
565 
566     // Sets the SNR axis to have the maximum be 95% of the way up from the middle to the top.
567     QCPGraphData snrMax = *std::min_element(graph(GuideGraph::G_SNR)->data()->begin(),
568                                             graph(GuideGraph::G_SNR)->data()->end(),
569                                             [](QCPGraphData const & s1, QCPGraphData const & s2)
570     {
571         return s1.value > s2.value;
572     });
573     snrAxis->setRange(-1.05 * snrMax.value, 1.05 * snrMax.value);
574 }
575 
updateCorrectionsScaleVisibility()576 void GuideDriftGraph::updateCorrectionsScaleVisibility()
577 {
578     bool isVisible = (Options::rACorrDisplayedOnGuideGraph() || Options::dECorrDisplayedOnGuideGraph());
579     yAxis2->setVisible(isVisible);
580     replot();
581 }
582 
mouseOverLine(QMouseEvent * event)583 void GuideDriftGraph::mouseOverLine(QMouseEvent *event)
584 {
585     double key = xAxis->pixelToCoord(event->localPos().x());
586 
587     if (xAxis->range().contains(key))
588     {
589         if (plottableAt(event->pos(), false))
590         {
591             int raIndex = graph(GuideGraph::G_RA)->findBegin(key);
592             int deIndex = graph(GuideGraph::G_DEC)->findBegin(key);
593             int rmsIndex = graph(GuideGraph::G_RMS)->findBegin(key);
594 
595             double raDelta = graph(GuideGraph::G_RA)->dataMainValue(raIndex);
596             double deDelta = graph(GuideGraph::G_DEC)->dataMainValue(deIndex);
597 
598             double raPulse = graph(GuideGraph::G_RA_PULSE)->dataMainValue(raIndex); //Get RA Pulse from RA pulse data
599             double dePulse = graph(GuideGraph::G_DEC_PULSE)->dataMainValue(deIndex); //Get DEC Pulse from DEC pulse data
600 
601             double rms = graph(GuideGraph::G_RMS)->dataMainValue(rmsIndex);
602             double snr = 0;
603             if (graph(GuideGraph::G_SNR)->data()->size() > 0)
604             {
605                 int snrIndex = graph(GuideGraph::G_SNR)->findBegin(key);
606                 snr = graph(GuideGraph::G_SNR)->dataMainValue(snrIndex);
607             }
608 
609             // Compute time value:
610             QTime localTime = guideTimer;
611 
612             localTime = localTime.addSecs(key);
613 
614             QToolTip::hideText();
615             if(raPulse == 0 && dePulse == 0)
616             {
617                 QToolTip::showText(
618                     event->globalPos(),
619                     i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is the RMS error in arcseconds; %5 is the SNR",
620                           "<table>"
621                           "<tr><td>LT:   </td><td>%1</td></tr>"
622                           "<tr><td>RA:   </td><td>%2 \"</td></tr>"
623                           "<tr><td>DE:   </td><td>%3 \"</td></tr>"
624                           "<tr><td>RMS:   </td><td>%4 \"</td></tr>"
625                           "<tr><td>SNR:   </td><td>%5 \"</td></tr>"
626                           "</table>",
627                           localTime.toString("hh:mm:ss AP"),
628                           QString::number(raDelta, 'f', 2), QString::number(deDelta, 'f', 2),
629                           QString::number(rms, 'f', 2), QString::number(snr, 'f', 1)));
630             }
631             else
632             {
633                 QToolTip::showText(
634                     event->globalPos(),
635                     i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is the RMS error in arcseconds; %5 is the SNR; %6 is RA Pulse in ms; %7 is DE Pulse in ms",
636                           "<table>"
637                           "<tr><td>LT:   </td><td>%1</td></tr>"
638                           "<tr><td>RA:   </td><td>%2 \"</td></tr>"
639                           "<tr><td>DE:   </td><td>%3 \"</td></tr>"
640                           "<tr><td>RMS:   </td><td>%4 \"</td></tr>"
641                           "<tr><td>SNR:   </td><td>%5 \"</td></tr>"
642                           "<tr><td>RA Pulse:   </td><td>%6 ms</td></tr>"
643                           "<tr><td>DE Pulse:   </td><td>%7 ms</td></tr>"
644                           "</table>",
645                           localTime.toString("hh:mm:ss AP"),
646                           QString::number(raDelta, 'f', 2),
647                           QString::number(deDelta, 'f', 2),
648                           QString::number(rms, 'f', 2),
649                           QString::number(snr, 'f', 1),
650                           QString::number(raPulse, 'f', 2),
651                           QString::number(dePulse, 'f', 2))); //The pulses were divided by 100 before they were put on the graph.
652             }
653         }
654         else
655             QToolTip::hideText();
656 
657         replot();
658     }
659 
660     if (xAxis->range().contains(key))
661     {
662         QCPGraph *qcpgraph = qobject_cast<QCPGraph *>(plottableAt(event->pos(), false));
663 
664         if (qcpgraph)
665         {
666             int raIndex = graph(GuideGraph::G_RA)->findBegin(key);
667             int deIndex = graph(GuideGraph::G_DEC)->findBegin(key);
668             int rmsIndex = graph(GuideGraph::G_RMS)->findBegin(key);
669 
670             double raDelta = graph(GuideGraph::G_RA)->dataMainValue(raIndex);
671             double deDelta = graph(GuideGraph::G_DEC)->dataMainValue(deIndex);
672 
673             double raPulse = graph(GuideGraph::G_RA_PULSE)->dataMainValue(raIndex); //Get RA Pulse from RA pulse data
674             double dePulse = graph(GuideGraph::G_DEC_PULSE)->dataMainValue(deIndex); //Get DEC Pulse from DEC pulse data
675 
676             double rms = graph(GuideGraph::G_RMS)->dataMainValue(rmsIndex);
677             double snr = 0;
678             if (graph(GuideGraph::G_SNR)->data()->size() > 0)
679             {
680                 int snrIndex = graph(GuideGraph::G_SNR)->findBegin(key);
681                 snr = graph(GuideGraph::G_SNR)->dataMainValue(snrIndex);
682             }
683 
684             // Compute time value:
685             QTime localTime = guideTimer;
686 
687             localTime = localTime.addSecs(key);
688 
689             QToolTip::hideText();
690             if(raPulse == 0 && dePulse == 0)
691             {
692                 QToolTip::showText(
693                     event->globalPos(),
694                     i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is the RMS error in arcseconds; %5 is the SNR",
695                           "<table>"
696                           "<tr><td>LT:   </td><td>%1</td></tr>"
697                           "<tr><td>RA:   </td><td>%2 \"</td></tr>"
698                           "<tr><td>DE:   </td><td>%3 \"</td></tr>"
699                           "<tr><td>RMS:   </td><td>%4 \"</td></tr>"
700                           "<tr><td>SNR:   </td><td>%5 \"</td></tr>"
701                           "</table>",
702                           localTime.toString("hh:mm:ss AP"),
703                           QString::number(raDelta, 'f', 2), QString::number(deDelta, 'f', 2),
704                           QString::number(rms, 'f', 2), QString::number(snr, 'f', 1)));
705             }
706             else
707             {
708                 QToolTip::showText(
709                     event->globalPos(),
710                     i18nc("Drift graphics tooltip; %1 is local time; %2 is RA deviation; %3 is DE deviation in arcseconds; %4 is the RMS error in arcseconds; %5 is the SNR; %6 is RA Pulse in ms; %7 is DE Pulse in ms",
711                           "<table>"
712                           "<tr><td>LT:   </td><td>%1</td></tr>"
713                           "<tr><td>RA:   </td><td>%2 \"</td></tr>"
714                           "<tr><td>DE:   </td><td>%3 \"</td></tr>"
715                           "<tr><td>RMS:   </td><td>%4 \"</td></tr>"
716                           "<tr><td>SNR:   </td><td>%5 \"</td></tr>"
717                           "<tr><td>RA Pulse:   </td><td>%6 ms</td></tr>"
718                           "<tr><td>DE Pulse:   </td><td>%7 ms</td></tr>"
719                           "</table>",
720                           localTime.toString("hh:mm:ss AP"),
721                           QString::number(raDelta, 'f', 2),
722                           QString::number(deDelta, 'f', 2),
723                           QString::number(rms, 'f', 2),
724                           QString::number(snr, 'f', 1),
725                           QString::number(raPulse, 'f', 2),
726                           QString::number(dePulse, 'f', 2))); //The pulses were divided by 100 before they were put on the graph.
727             }
728         }
729         else
730             QToolTip::hideText();
731 
732         replot();
733     }
734 }
735 
mouseClicked(QMouseEvent * event)736 void GuideDriftGraph::mouseClicked(QMouseEvent *event)
737 {
738     if (event->buttons() & Qt::RightButton)
739     {
740         yAxis->setRange(-3, 3);
741     }
742 }
743 
refreshColorScheme()744 void GuideDriftGraph::refreshColorScheme()
745 {
746     if (graph(GuideGraph::G_RA) && graph(GuideGraph::G_DEC) && graph(GuideGraph::G_RA_HIGHLIGHT)
747             && graph(GuideGraph::G_DEC_HIGHLIGHT) && graph(GuideGraph::G_RA_PULSE)
748             && graph(GuideGraph::G_DEC_PULSE))
749     {
750         graph(GuideGraph::G_RA)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")));
751         graph(GuideGraph::G_DEC)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")));
752         graph(GuideGraph::G_RA_HIGHLIGHT)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError")));
753         graph(GuideGraph::G_RA_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle,
754                 QPen(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"), 2), QBrush(), 10));
755         graph(GuideGraph::G_DEC_HIGHLIGHT)->setPen(QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError")));
756         graph(GuideGraph::G_DEC_HIGHLIGHT)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssPlusCircle,
757                 QPen(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"), 2), QBrush(), 10));
758 
759         QColor raPulseColor(KStarsData::Instance()->colorScheme()->colorNamed("RAGuideError"));
760         raPulseColor.setAlpha(75);
761         graph(GuideGraph::G_RA_PULSE)->setPen(QPen(raPulseColor));
762         graph(GuideGraph::G_RA_PULSE)->setBrush(QBrush(raPulseColor, Qt::Dense4Pattern));
763 
764         QColor dePulseColor(KStarsData::Instance()->colorScheme()->colorNamed("DEGuideError"));
765         dePulseColor.setAlpha(75);
766         graph(GuideGraph::G_DEC_PULSE)->setPen(QPen(dePulseColor));
767         graph(GuideGraph::G_DEC_PULSE)->setBrush(QBrush(dePulseColor, Qt::Dense4Pattern));
768     }
769 }
770