1 /*
2     SPDX-FileCopyrightText: 2002-2003 Pablo de Vicente <vicente@oan.es>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "altvstime.h"
8 
9 #include "avtplotwidget.h"
10 #include "dms.h"
11 #include "ksalmanac.h"
12 #include "kstarsdata.h"
13 #include "kstarsdatetime.h"
14 #include "ksnumbers.h"
15 #include "simclock.h"
16 #include "kssun.h"
17 #include "dialogs/finddialog.h"
18 #include "dialogs/locationdialog.h"
19 #include "geolocation.h"
20 #include "skyobjects/skypoint.h"
21 #include "skyobjects/skyobject.h"
22 #include "skyobjects/starobject.h"
23 
24 #include <KLocalizedString>
25 #include <kplotwidget.h>
26 
27 #include <QVBoxLayout>
28 #include <QFrame>
29 #include <QDialog>
30 #include <QPainter>
31 #include <QtPrintSupport/QPrinter>
32 #include <QtPrintSupport/QPrintDialog>
33 
34 #include "kstars_debug.h"
35 
AltVsTimeUI(QWidget * p)36 AltVsTimeUI::AltVsTimeUI(QWidget *p) : QFrame(p)
37 {
38     setupUi(this);
39 }
40 
AltVsTime(QWidget * parent)41 AltVsTime::AltVsTime(QWidget *parent) : QDialog(parent)
42 {
43 #ifdef Q_OS_OSX
44     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
45 #endif
46 
47     setWindowTitle(i18nc("@title:window", "Altitude vs. Time"));
48 
49     setModal(false);
50 
51     QVBoxLayout *topLayout = new QVBoxLayout;
52     setLayout(topLayout);
53     topLayout->setContentsMargins(0, 0, 0, 0);
54     avtUI = new AltVsTimeUI(this);
55 
56     // Layers for setting up the plot's priority: the current curve should be above the other curves.
57     // The Rise/Set/Transit markers should be on top, with highest priority.
58     avtUI->View->addLayer("currentCurveLayer", avtUI->View->layer("main"), QCustomPlot::limAbove);
59     avtUI->View->addLayer("markersLayer", avtUI->View->layer("currentCurveLayer"), QCustomPlot::limAbove);
60 
61     // Set up the Graph Window:
62     avtUI->View->setBackground(QBrush(QColor(0, 0, 0)));
63     avtUI->View->xAxis->grid()->setVisible(false);
64     avtUI->View->yAxis->grid()->setVisible(false);
65     QColor axisColor(Qt::white);
66     QPen axisPen(axisColor, 1);
67     avtUI->View->xAxis->setBasePen(axisPen);
68     avtUI->View->xAxis->setTickPen(axisPen);
69     avtUI->View->xAxis->setSubTickPen(axisPen);
70     avtUI->View->xAxis->setTickLabelColor(axisColor);
71     avtUI->View->xAxis->setLabelColor(axisColor);
72 
73     avtUI->View->xAxis2->setBasePen(axisPen);
74     avtUI->View->xAxis2->setTickPen(axisPen);
75     avtUI->View->xAxis2->setSubTickPen(axisPen);
76     avtUI->View->xAxis2->setTickLabelColor(axisColor);
77     avtUI->View->xAxis2->setLabelColor(axisColor);
78 
79     avtUI->View->yAxis->setBasePen(axisPen);
80     avtUI->View->yAxis->setTickPen(axisPen);
81     avtUI->View->yAxis->setSubTickPen(axisPen);
82     avtUI->View->yAxis->setTickLabelColor(axisColor);
83     avtUI->View->yAxis->setLabelColor(axisColor);
84 
85     avtUI->View->yAxis2->setBasePen(axisPen);
86     avtUI->View->yAxis2->setTickPen(axisPen);
87     avtUI->View->yAxis2->setSubTickPen(axisPen);
88     avtUI->View->yAxis2->setTickLabelColor(axisColor);
89     avtUI->View->yAxis2->setLabelColor(axisColor);
90 
91     // give the axis some labels:
92     avtUI->View->xAxis2->setLabel(i18n("Local Sidereal Time"));
93     avtUI->View->xAxis2->setVisible(true);
94     avtUI->View->yAxis2->setVisible(true);
95     avtUI->View->yAxis2->setTickLength(0, 0);
96     avtUI->View->xAxis->setLabel(i18n("Local Time"));
97     avtUI->View->yAxis->setLabel(i18n("Altitude"));
98     avtUI->View->xAxis->setRange(43200, 129600);
99     avtUI->View->xAxis2->setRange(61200, 147600);
100 
101     // configure the bottom axis to show time instead of number:
102     QSharedPointer<QCPAxisTickerTime> xAxisTimeTicker(new QCPAxisTickerTime);
103     xAxisTimeTicker->setTimeFormat("%h:%m");
104     xAxisTimeTicker->setTickCount(12);
105     xAxisTimeTicker->setTickStepStrategy(QCPAxisTicker::tssReadability);
106     xAxisTimeTicker->setTickOrigin(Qt::UTC);
107     avtUI->View->xAxis->setTicker(xAxisTimeTicker);
108 
109     // configure the top axis to show time instead of number:
110     QSharedPointer<QCPAxisTickerTime> xAxis2TimeTicker(new QCPAxisTickerTime);
111     xAxis2TimeTicker->setTimeFormat("%h:%m");
112     xAxis2TimeTicker->setTickCount(12);
113     xAxis2TimeTicker->setTickStepStrategy(QCPAxisTicker::tssReadability);
114     xAxis2TimeTicker->setTickOrigin(Qt::UTC);
115     avtUI->View->xAxis2->setTicker(xAxis2TimeTicker);
116 
117     // set up the Zoom/Pan features for the Top X Axis
118     avtUI->View->axisRect()->setRangeDragAxes(avtUI->View->xAxis2, avtUI->View->yAxis);
119     avtUI->View->axisRect()->setRangeZoomAxes(avtUI->View->xAxis2, avtUI->View->yAxis);
120 
121     // set up the margins, for a nice view
122     avtUI->View->axisRect()->setAutoMargins(QCP::msBottom | QCP::msLeft | QCP::msTop);
123     avtUI->View->axisRect()->setMargins(QMargins(0, 0, 7, 0));
124 
125     // set up the interaction set:
126     avtUI->View->setInteraction(QCP::iRangeZoom, true);
127     avtUI->View->setInteraction(QCP::iRangeDrag, true);
128 
129     // set up the selection tolerance for checking if a certain graph is or not selected:
130     avtUI->View->setSelectionTolerance(5);
131 
132     // draw the gradient:
133     drawGradient();
134 
135     // set up the background image:
136     background = new QCPItemPixmap(avtUI->View);
137     background->setPixmap(*gradient);
138     background->topLeft->setType(QCPItemPosition::ptPlotCoords);
139     background->bottomRight->setType(QCPItemPosition::ptPlotCoords);
140     background->setScaled(true, Qt::IgnoreAspectRatio);
141     background->setLayer("background");
142     background->setVisible(true);
143 
144     avtUI->raBox->setDegType(false);
145     avtUI->decBox->setDegType(true);
146 
147     //FIXME:
148     //Doesn't make sense to manually adjust long/lat unless we can modify TZ also
149     avtUI->longBox->setReadOnly(true);
150     avtUI->latBox->setReadOnly(true);
151 
152     topLayout->addWidget(avtUI);
153 
154     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
155     topLayout->addWidget(buttonBox);
156     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
157 
158     QPushButton *printB = new QPushButton(QIcon::fromTheme("document-print"), i18n("&Print..."));
159     printB->setToolTip(i18n("Print the Altitude vs. time plot"));
160     buttonBox->addButton(printB, QDialogButtonBox::ActionRole);
161     connect(printB, SIGNAL(clicked()), this, SLOT(slotPrint()));
162 
163     geo = KStarsData::Instance()->geo();
164 
165     DayOffset = 0;
166     // set up the initial minimum and maximum altitude
167     minAlt = 0;
168     maxAlt = 0;
169     showCurrentDate();
170     if (getDate().time().hour() > 12)
171         DayOffset = 1;
172 
173     avtUI->longBox->show(geo->lng());
174     avtUI->latBox->show(geo->lat());
175 
176     computeSunRiseSetTimes();
177     setLSTLimits();
178     setDawnDusk();
179 
180     connect(avtUI->View->yAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(onYRangeChanged(QCPRange)));
181     connect(avtUI->View->xAxis2, SIGNAL(rangeChanged(QCPRange)), this, SLOT(onXRangeChanged(QCPRange)));
182     connect(avtUI->View, SIGNAL(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)), this,
183             SLOT(plotMousePress(QCPAbstractPlottable*,int,QMouseEvent*)));
184     connect(avtUI->View, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(mouseOverLine(QMouseEvent*)));
185 
186     connect(avtUI->browseButton, SIGNAL(clicked()), this, SLOT(slotBrowseObject()));
187     connect(avtUI->cityButton, SIGNAL(clicked()), this, SLOT(slotChooseCity()));
188     connect(avtUI->updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateDateLoc()));
189     connect(avtUI->clearButton, SIGNAL(clicked()), this, SLOT(slotClear()));
190     connect(avtUI->addButton, SIGNAL(clicked()), this, SLOT(slotAddSource()));
191     connect(avtUI->nameBox, SIGNAL(returnPressed()), this, SLOT(slotAddSource()));
192     connect(avtUI->raBox, SIGNAL(returnPressed()), this, SLOT(slotAddSource()));
193     connect(avtUI->decBox, SIGNAL(returnPressed()), this, SLOT(slotAddSource()));
194     connect(avtUI->clearFieldsButton, SIGNAL(clicked()), this, SLOT(slotClearBoxes()));
195     connect(avtUI->longBox, SIGNAL(returnPressed()), this, SLOT(slotAdvanceFocus()));
196     connect(avtUI->latBox, SIGNAL(returnPressed()), this, SLOT(slotAdvanceFocus()));
197     connect(avtUI->PlotList, SIGNAL(currentRowChanged(int)), this, SLOT(slotHighlight(int)));
198     connect(avtUI->computeButton, SIGNAL(clicked()), this, SLOT(slotComputeAltitudeByTime()));
199     connect(avtUI->riseButton, SIGNAL(clicked()), this, SLOT(slotMarkRiseTime()));
200     connect(avtUI->setButton, SIGNAL(clicked()), this, SLOT(slotMarkSetTime()));
201     connect(avtUI->transitButton, SIGNAL(clicked()), this, SLOT(slotMarkTransitTime()));
202 
203     // Set up the Rise/Set/Transit buttons' icons:
204 
205     QPixmap redButton(100, 100);
206     redButton.fill(Qt::transparent);
207     QPainter p;
208     p.begin(&redButton);
209     p.setRenderHint(QPainter::Antialiasing, true);
210     QPen pen(Qt::red, 2);
211     p.setPen(pen);
212     QBrush brush(Qt::red);
213     p.setBrush(brush);
214     p.drawEllipse(15, 15, 80, 80);
215     p.end();
216 
217     QPixmap blueButton(100, 100);
218     blueButton.fill(Qt::transparent);
219     QPainter p1;
220     p1.begin(&blueButton);
221     p1.setRenderHint(QPainter::Antialiasing, true);
222     QPen pen1(Qt::blue, 2);
223     p1.setPen(pen1);
224     QBrush brush1(Qt::blue);
225     p1.setBrush(brush1);
226     p1.drawEllipse(15, 15, 80, 80);
227     p1.end();
228 
229     QPixmap greenButton(100, 100);
230     greenButton.fill(Qt::transparent);
231     QPainter p2;
232     p2.begin(&greenButton);
233     p2.setRenderHint(QPainter::Antialiasing, true);
234     QPen pen2(Qt::green, 2);
235     p2.setPen(pen2);
236     QBrush brush2(Qt::green);
237     p2.setBrush(brush2);
238     p2.drawEllipse(15, 15, 80, 80);
239     p2.end();
240 
241     avtUI->riseButton->setIcon(QIcon(redButton));
242     avtUI->setButton->setIcon(QIcon(blueButton));
243     avtUI->transitButton->setIcon(QIcon(greenButton));
244 
245     setMouseTracking(true);
246 }
247 
~AltVsTime()248 AltVsTime::~AltVsTime()
249 {
250     //WARNING: need to delete deleteList items!
251 }
slotAddSource()252 void AltVsTime::slotAddSource()
253 {
254     SkyObject *obj = KStarsData::Instance()->objectNamed(avtUI->nameBox->text());
255 
256     if (obj)
257     {
258         //An object with the current name exists.  If the object is not already
259         //in the avt list, add it.
260         bool found = false;
261         foreach (SkyObject *o, pList)
262         {
263             //if ( o->name() == obj->name() ) {
264             if (getObjectName(o, false) == getObjectName(obj, false))
265             {
266                 found = true;
267                 break;
268             }
269         }
270         if (!found)
271             processObject(obj);
272     }
273     else
274     {
275         //Object with the current name doesn't exist.  It's possible that the
276         //user is trying to add a custom object.  Assume this is the case if
277         //the RA and Dec fields are filled in.
278 
279         if (!avtUI->nameBox->text().isEmpty() && !avtUI->raBox->text().isEmpty() && !avtUI->decBox->text().isEmpty())
280         {
281             bool okRA, okDec;
282             dms newRA  = avtUI->raBox->createDms(false, &okRA);
283             dms newDec = avtUI->decBox->createDms(true, &okDec);
284             if (!okRA || !okDec)
285                 return;
286 
287             //If the epochName is blank (or any non-double), we assume J2000
288             //Otherwise, precess to J2000.
289             KStarsDateTime dt;
290             dt.setFromEpoch(getEpoch(avtUI->epochName->text()));
291             long double jd = dt.djd();
292             if (jd != J2000)
293             {
294                 SkyPoint ptest(newRA, newDec);
295                 //ptest.precessFromAnyEpoch(jd, J2000);
296                 ptest.catalogueCoord(jd);
297                 newRA.setH(ptest.ra().Hours());
298                 newDec.setD(ptest.dec().Degrees());
299             }
300 
301             //make sure the coords do not already exist from another object
302             bool found = false;
303             foreach (SkyObject *p, pList)
304             {
305                 //within an arcsecond?
306                 if (fabs(newRA.Degrees() - p->ra().Degrees()) < 0.0003 &&
307                     fabs(newDec.Degrees() - p->dec().Degrees()) < 0.0003)
308                 {
309                     found = true;
310                     break;
311                 }
312             }
313             if (!found)
314             {
315                 SkyObject *obj = new SkyObject(8, newRA, newDec, 1.0, avtUI->nameBox->text());
316                 deleteList.append(obj); //this object will be deleted when window is destroyed
317                 processObject(obj);
318             }
319         }
320 
321         //If the Ra and Dec boxes are filled, but the name field is empty,
322         //move input focus to nameBox.  If either coordinate box is empty,
323         //move focus there
324         if (avtUI->nameBox->text().isEmpty())
325         {
326             avtUI->nameBox->QWidget::setFocus();
327         }
328         if (avtUI->raBox->text().isEmpty())
329         {
330             avtUI->raBox->QWidget::setFocus();
331         }
332         else
333         {
334             if (avtUI->decBox->text().isEmpty())
335                 avtUI->decBox->QWidget::setFocus();
336         }
337     }
338 
339     avtUI->View->update();
340 }
341 
342 //Use find dialog to choose an object
slotBrowseObject()343 void AltVsTime::slotBrowseObject()
344 {
345     if (FindDialog::Instance()->exec() == QDialog::Accepted)
346     {
347         SkyObject *o = FindDialog::Instance()->targetObject();
348         processObject(o);
349     }
350 
351     avtUI->View->update();
352     avtUI->View->replot();
353 }
354 
processObject(SkyObject * o,bool forceAdd)355 void AltVsTime::processObject(SkyObject *o, bool forceAdd)
356 {
357     if (!o)
358         return;
359 
360     KSNumbers *num    = new KSNumbers(getDate().djd());
361     KSNumbers *oldNum = nullptr;
362 
363     //If the object is in the solar system, recompute its position for the given epochLabel
364     KStarsData *data = KStarsData::Instance();
365     if (o->isSolarSystem())
366     {
367         oldNum = new KSNumbers(data->ut().djd());
368         o->updateCoords(num, true, geo->lat(), data->lst(), true);
369     }
370 
371     //precess coords to target epoch
372     o->updateCoordsNow(num);
373 
374     // vector used for computing the points needed for drawing the graph
375     QVector<double> y(100), t(100);
376 
377     //If this point is not in list already, add it to list
378     bool found(false);
379     foreach (SkyObject *p, pList)
380     {
381         if (o->ra().Degrees() == p->ra().Degrees() && o->dec().Degrees() == p->dec().Degrees())
382         {
383             found = true;
384             break;
385         }
386     }
387     if (found && !forceAdd)
388     {
389         qCWarning(KSTARS) << "This point is already displayed; It will not be duplicated.";
390     }
391     else
392     {
393         pList.append(o);
394 
395         // make sure existing curves are thin and red:
396 
397         for (int i = 0; i < avtUI->View->graphCount(); i++)
398         {
399             if (avtUI->View->graph(i)->pen().color() == Qt::white)
400             {
401                 avtUI->View->graph(i)->setPen(QPen(Qt::red, 2));
402             }
403         }
404 
405         // SET up the curve's name
406         avtUI->View->addGraph()->setName(o->name());
407 
408         // compute the current graph:
409         // time range: 24h
410 
411         int offset = 3;
412         for (double h = -12.0, i = 0; h <= 12.0; h += 0.25, i++)
413         {
414             y[i] = findAltitude(o, h);
415             if (y[i] > maxAlt)
416                 maxAlt = y[i];
417             if (y[i] < minAlt)
418                 minAlt = y[i];
419             t[i] = i * 900 + 43200;
420             avtUI->View->graph(avtUI->View->graphCount() - 1)->addData(t[i], y[i]);
421         }
422         avtUI->View->graph(avtUI->View->graphCount() - 1)->setPen(QPen(Qt::white, 3));
423 
424         // Go into initial state: without Zoom/Pan
425         avtUI->View->xAxis->setRange(43200, 129600);
426         avtUI->View->xAxis2->setRange(61200, 147600);
427         if (abs(minAlt) > maxAlt)
428             maxAlt = abs(minAlt);
429         else
430             minAlt = -maxAlt;
431 
432         avtUI->View->yAxis->setRange(minAlt - offset, maxAlt + offset);
433 
434         // Update background coordinates:
435         background->topLeft->setCoords(avtUI->View->xAxis->range().lower, avtUI->View->yAxis->range().upper);
436         background->bottomRight->setCoords(avtUI->View->xAxis->range().upper, avtUI->View->yAxis->range().lower);
437 
438         avtUI->View->replot();
439 
440         avtUI->PlotList->addItem(getObjectName(o));
441         avtUI->PlotList->setCurrentRow(avtUI->PlotList->count() - 1);
442         avtUI->raBox->showInHours(o->ra());
443         avtUI->decBox->showInDegrees(o->dec());
444         avtUI->nameBox->setText(getObjectName(o));
445 
446         //Set epochName to epoch shown in date tab
447         avtUI->epochName->setText(QString().setNum(getDate().epoch()));
448     }
449     //qCDebug() << "Currently, there are " << avtUI->View->graphCount() << " objects displayed.";
450 
451     //restore original position
452     if (o->isSolarSystem())
453     {
454         o->updateCoords(oldNum, true, data->geo()->lat(), data->lst(), true);
455         delete oldNum;
456     }
457     o->EquatorialToHorizontal(data->lst(), data->geo()->lat());
458     delete num;
459 }
460 
findAltitude(SkyPoint * p,double hour)461 double AltVsTime::findAltitude(SkyPoint *p, double hour)
462 {
463     hour += 24.0 * DayOffset;
464 
465     //getDate converts the user-entered local time to UT
466     KStarsDateTime ut = getDate().addSecs(hour * 3600.0);
467 
468     CachingDms LST = geo->GSTtoLST(ut.gst());
469     p->EquatorialToHorizontal(&LST, geo->lat());
470     return p->alt().Degrees();
471 }
472 
slotHighlight(int row)473 void AltVsTime::slotHighlight(int row)
474 {
475     if (row < 0)
476         return;
477 
478     int rowIndex = 0;
479     //highlight the curve of the selected object
480     for (int i = 0; i < avtUI->View->graphCount(); i++)
481     {
482         if (i == row)
483             rowIndex = row;
484         else
485         {
486             avtUI->View->graph(i)->setPen(QPen(Qt::red, 2));
487             avtUI->View->graph(i)->setLayer("main");
488         }
489     }
490     avtUI->View->graph(rowIndex)->setPen(QPen(Qt::white, 3));
491     avtUI->View->graph(rowIndex)->setLayer("currentCurveLayer");
492     avtUI->View->update();
493     avtUI->View->replot();
494 
495     if (row >= 0 && row < pList.size())
496     {
497         SkyObject *p = pList.at(row);
498         avtUI->raBox->showInHours(p->ra());
499         avtUI->decBox->showInDegrees(p->dec());
500         avtUI->nameBox->setText(avtUI->PlotList->currentItem()->text());
501     }
502 
503     SkyObject *selectedObject = KStarsData::Instance()->objectNamed(avtUI->nameBox->text());
504     const KStarsDateTime &ut  = KStarsData::Instance()->ut();
505     if (selectedObject)
506     {
507         QTime rt = selectedObject->riseSetTime(ut, geo, true); //true = use rise time
508         if (rt.isValid() == false)
509         {
510             avtUI->riseButton->setEnabled(false);
511             avtUI->setButton->setEnabled(false);
512         }
513         else
514         {
515             avtUI->riseButton->setEnabled(true);
516             avtUI->setButton->setEnabled(true);
517         }
518     }
519 }
520 
onXRangeChanged(const QCPRange & range)521 void AltVsTime::onXRangeChanged(const QCPRange &range)
522 {
523     QCPRange aux = avtUI->View->xAxis2->range();
524     avtUI->View->xAxis->setRange(aux -= 18000);
525     avtUI->View->xAxis2->setRange(range.bounded(61200, 147600));
526     // if ZOOM is detected then remove the gold lines that indicate current position:
527     if (avtUI->View->xAxis->range().size() != 86400)
528     {
529         // Refresh the background:
530         background->setScaled(false);
531         background->setScaled(true, Qt::IgnoreAspectRatio);
532         background->setPixmap(*gradient);
533 
534         avtUI->View->update();
535         avtUI->View->replot();
536     }
537 }
538 
onYRangeChanged(const QCPRange & range)539 void AltVsTime::onYRangeChanged(const QCPRange &range)
540 {
541     int offset = 3;
542     avtUI->View->yAxis->setRange(range.bounded(minAlt - offset, maxAlt + offset));
543 }
544 
plotMousePress(QCPAbstractPlottable * abstractPlottable,int dataIndex,QMouseEvent * event)545 void AltVsTime::plotMousePress(QCPAbstractPlottable *abstractPlottable, int dataIndex, QMouseEvent *event)
546 {
547     //Do we need this?
548     Q_UNUSED(dataIndex);
549 
550     if (event->button() == Qt::RightButton)
551     {
552         QCPAbstractPlottable *plottable = abstractPlottable;
553         if (plottable)
554         {
555             double x = avtUI->View->xAxis->pixelToCoord(event->localPos().x());
556             double y = avtUI->View->yAxis->pixelToCoord(event->localPos().y());
557 
558             QCPGraph *graph = qobject_cast<QCPGraph *>(plottable);
559 
560             if (graph)
561             {
562                 double yValue = y;
563                 double xValue = x;
564 
565                 // Compute time value:
566                 QTime localTime(0, 0, 0, 0);
567                 QTime localSiderealTime(5, 0, 0, 0);
568 
569                 localTime         = localTime.addSecs(int(xValue));
570                 localSiderealTime = localSiderealTime.addSecs(int(xValue));
571 
572                 QToolTip::hideText();
573                 QToolTip::showText(event->globalPos(),
574                                    i18n("<table>"
575                                       "<tr>"
576                                       "<th colspan=\"2\">%1</th>"
577                                       "</tr>"
578                                       "<tr>"
579                                       "<td>LST:   </td>"
580                                       "<td>%3</td>"
581                                       "</tr>"
582                                       "<tr>"
583                                       "<td>LT:   </td>"
584                                       "<td>%2</td>"
585                                       "</tr>"
586                                       "<tr>"
587                                       "<td>Altitude:   </td>"
588                                       "<td>%4</td>"
589                                       "</tr>"
590                                       "</table>",
591                                       graph->name().isEmpty() ? "???" : graph->name(),
592                                       localTime.toString(),
593                                       localSiderealTime.toString(),
594                                       QString::number(yValue, 'f', 2) + ' ' + QChar(176)),
595                                    avtUI->View, avtUI->View->rect());
596             }
597         }
598     }
599 }
600 
601 //move input focus to the next logical widget
slotAdvanceFocus()602 void AltVsTime::slotAdvanceFocus()
603 {
604     if (sender()->objectName() == QString("nameBox"))
605         avtUI->addButton->setFocus();
606     if (sender()->objectName() == QString("raBox"))
607         avtUI->decBox->setFocus();
608     if (sender()->objectName() == QString("decbox"))
609         avtUI->addButton->setFocus();
610     if (sender()->objectName() == QString("longBox"))
611         avtUI->latBox->setFocus();
612     if (sender()->objectName() == QString("latBox"))
613         avtUI->updateButton->setFocus();
614 }
615 
slotClear()616 void AltVsTime::slotClear()
617 {
618     pList.clear();
619     //Need to delete the pointers in deleteList
620     while (!deleteList.isEmpty())
621         delete deleteList.takeFirst();
622 
623     avtUI->PlotList->clear();
624     avtUI->nameBox->clear();
625     avtUI->raBox->clear();
626     avtUI->decBox->clear();
627     avtUI->epochName->clear();
628     // remove all graphs from the plot:
629     avtUI->View->clearGraphs();
630     // we remove all the dots (rise/set/transit) from the chart
631     // without removing the background image
632     int indexItem = 0, noItems = avtUI->View->itemCount();
633     QCPAbstractItem *item;
634     QCPItemPixmap *background;
635     // remove every item at a time:
636     while (noItems > 1 && indexItem < noItems)
637     {
638         // test if the current item is the background:
639         item       = avtUI->View->item(indexItem);
640         background = qobject_cast<QCPItemPixmap *>(item);
641         if (background)
642             indexItem++;
643         else
644         {
645             // if not, then remove this item:
646             avtUI->View->removeItem(indexItem);
647             noItems--;
648         }
649     }
650     // update & replot the chart:
651     avtUI->View->update();
652     avtUI->View->replot();
653 }
654 
slotClearBoxes()655 void AltVsTime::slotClearBoxes()
656 {
657     avtUI->nameBox->clear();
658     avtUI->raBox->clear();
659     avtUI->decBox->clear();
660     avtUI->epochName->clear();
661 }
662 
slotComputeAltitudeByTime()663 void AltVsTime::slotComputeAltitudeByTime()
664 {
665     SkyObject *selectedObject = pList.at(avtUI->PlotList->currentRow());
666     if (selectedObject == nullptr)
667     {
668         qCWarning(KSTARS) << "slotComputeAltitudeByTime: Unable to find" << avtUI->PlotList->currentItem()->text();
669         return;
670     }
671 
672     // Get Local Date & Time
673     KStarsDateTime lt = KStarsDateTime(avtUI->DateWidget->date(), avtUI->timeSpin->time(), Qt::LocalTime);
674     // Convert to UT
675     KStarsDateTime ut = geo->LTtoUT(lt);
676     // Get LST from GST
677     CachingDms LST = geo->GSTtoLST(ut.gst());
678     SkyObject *tempObject = selectedObject->clone();
679     // Update coords
680     KSNumbers num(ut.djd());
681     tempObject->updateCoords(&num, true, geo->lat(), &LST);
682     // Find Horizontal coordinates from LST & Latitude
683     selectedObject->EquatorialToHorizontal(&LST, geo->lat());
684 
685     // Set altitude
686     avtUI->altitudeBox->setText(selectedObject->altRefracted().toDMSString(true));
687 
688     delete (tempObject);
689 }
690 
slotMarkRiseTime()691 void AltVsTime::slotMarkRiseTime()
692 {
693     const KStarsDateTime &ut  = KStarsData::Instance()->ut();
694     SkyObject *selectedObject = pList.at(avtUI->PlotList->currentRow());
695     if (selectedObject == nullptr)
696     {
697         qCWarning(KSTARS) << "Mark Rise Time: Unable to find" << avtUI->PlotList->currentItem()->text();
698         return;
699     }
700 
701     QCPItemTracer *riseTimeTracer;
702     // check if at least one graph exists in the plot
703     if (avtUI->View->graphCount() > 0)
704     {
705         double time = 0;
706         double hours, minutes;
707 
708         QCPGraph *selectedGraph = avtUI->View->graph(avtUI->PlotList->currentRow());
709 
710         QTime rt = selectedObject->riseSetTime(ut, geo, true); //true = use rise time
711         // mark the Rise time with a solid red circle
712         if (rt.isValid() && selectedGraph)
713         {
714             hours   = rt.hour();
715             minutes = rt.minute();
716             if (hours < 12)
717                 hours += 24;
718             time           = hours * 3600 + minutes * 60;
719             riseTimeTracer = new QCPItemTracer(avtUI->View);
720             riseTimeTracer->setLayer("markersLayer");
721             riseTimeTracer->setGraph(selectedGraph);
722             riseTimeTracer->setInterpolating(true);
723             riseTimeTracer->setStyle(QCPItemTracer::tsCircle);
724             riseTimeTracer->setPen(QPen(Qt::red));
725             riseTimeTracer->setBrush(Qt::red);
726             riseTimeTracer->setSize(10);
727             riseTimeTracer->setGraphKey(time);
728             riseTimeTracer->setVisible(true);
729             avtUI->View->update();
730             avtUI->View->replot();
731         }
732     }
733 }
734 
slotMarkSetTime()735 void AltVsTime::slotMarkSetTime()
736 {
737     const KStarsDateTime &ut  = KStarsData::Instance()->ut();
738      SkyObject *selectedObject = pList.at(avtUI->PlotList->currentRow());
739     if (selectedObject == nullptr)
740     {
741         qCWarning(KSTARS) << "Mark Set Time: Unable to find" << avtUI->PlotList->currentItem()->text();
742         return;
743     }
744     QCPItemTracer *setTimeTracer;
745     // check if at least one graph exists in the plot
746     if (avtUI->View->graphCount() > 0)
747     {
748         double time = 0;
749         double hours, minutes;
750 
751         QCPGraph *selectedGraph = avtUI->View->graph(avtUI->PlotList->currentRow());
752 
753         QTime rt = selectedObject->riseSetTime(ut, geo, true); //true = use rise time
754         //If set time is before rise time, use set time for tomorrow
755         QTime st = selectedObject->riseSetTime(ut, geo, false); //false = use set time
756         if (st < rt)
757             st = selectedObject->riseSetTime(ut.addDays(1), geo, false); //false = use set time
758         // mark the Set time with a solid blue circle
759         if (rt.isValid())
760         {
761             hours   = st.hour();
762             minutes = st.minute();
763             if (hours < 12)
764                 hours += 24;
765             time          = hours * 3600 + minutes * 60;
766             setTimeTracer = new QCPItemTracer(avtUI->View);
767             setTimeTracer->setLayer("markersLayer");
768             setTimeTracer->setGraph(selectedGraph);
769             setTimeTracer->setInterpolating(true);
770             setTimeTracer->setStyle(QCPItemTracer::tsCircle);
771             setTimeTracer->setPen(QPen(Qt::blue));
772             setTimeTracer->setBrush(Qt::blue);
773             setTimeTracer->setSize(10);
774             setTimeTracer->setGraphKey(time);
775             setTimeTracer->setVisible(true);
776             avtUI->View->update();
777             avtUI->View->replot();
778         }
779     }
780 }
781 
slotMarkTransitTime()782 void AltVsTime::slotMarkTransitTime()
783 {
784     const KStarsDateTime &ut  = KStarsData::Instance()->ut();
785     SkyObject *selectedObject = pList.at(avtUI->PlotList->currentRow());
786     if (selectedObject == nullptr)
787     {
788         qCWarning(KSTARS) << "Mark Transit Time: Unable to find" << avtUI->PlotList->currentItem()->text();
789         return;
790     }
791     QCPItemTracer *transitTimeTracer;
792     // check if at least one graph exists in the plot
793     if (avtUI->View->graphCount() > 0)
794     {
795         double time = 0;
796         double hours, minutes;
797 
798         QCPGraph *selectedGraph = avtUI->View->graph(avtUI->PlotList->currentRow());
799 
800         QTime rt = selectedObject->riseSetTime(ut, geo, true); //true = use rise time
801         //If transit time is before rise time, use transit time for tomorrow
802         QTime tt = selectedObject->transitTime(ut, geo);
803 
804         if (tt < rt)
805             tt = selectedObject->transitTime(ut.addDays(1), geo);
806         // mark the Transit time with a solid green circle
807         hours   = tt.hour();
808         minutes = tt.minute();
809         if (hours < 12)
810             hours += 24;
811         time              = hours * 3600 + minutes * 60;
812         transitTimeTracer = new QCPItemTracer(avtUI->View);
813         transitTimeTracer->setLayer("markersLayer");
814         transitTimeTracer->setGraph(selectedGraph);
815         transitTimeTracer->setInterpolating(true);
816         transitTimeTracer->setStyle(QCPItemTracer::tsCircle);
817         transitTimeTracer->setPen(QPen(Qt::green));
818         transitTimeTracer->setBrush(Qt::green);
819         transitTimeTracer->setSize(10);
820         transitTimeTracer->setGraphKey(time);
821         transitTimeTracer->setVisible(true);
822         avtUI->View->update();
823         avtUI->View->replot();
824     }
825 }
826 
computeSunRiseSetTimes()827 void AltVsTime::computeSunRiseSetTimes()
828 {
829     //Determine the time of sunset and sunrise for the desired date and location
830     //expressed as doubles, the fraction of a full day.
831 
832     /* KSAlmanac ksal(getDate(), geo); */
833 
834     /* ... */
835 }
836 
837 //FIXME
838 /*
839 void AltVsTime::mouseOverLine(QMouseEvent *event){
840     // Get the mouse position's coordinates relative to axes:
841     double x = avtUI->View->xAxis->pixelToCoord(event->pos().x());
842     double y = avtUI->View->yAxis->pixelToCoord(event->pos().y());
843     // Save the actual values:
844     double yValue = y;
845     double xValue = x;
846     // The offset used for the Y axis: top/bottom
847     int offset = 3;
848     // Compute the Y axis maximum value:
849     int yAxisMaxValue = maxAlt + offset;
850     // Compute the X axis minimum and maximum values:
851     int xAxisMinValue = 43200;
852     int xAxisMaxValue = 129600;
853     // Ignore the upper and left margins:
854     y = yAxisMaxValue - y;
855     x -= xAxisMinValue;
856     // We make a copy to gradient background in order to have one set of lines at a time:
857     // Otherwise, the chart would have been covered by lines
858     QPixmap copy = gradient->copy(gradient->rect());
859     // If ZOOM is not active, then draw the gold lines that indicate current mouse pisition:
860     if(avtUI->View->xAxis->range().size() == 86400){
861         QPainter p;
862 
863         p.begin(&copy);
864         p.setPen( QPen( QBrush("gold"), 2, Qt::SolidLine ) );
865 
866         // Get the gradient background's width and height:
867         int pW = gradient->rect().width();
868         int pH = gradient->rect().height();
869 
870         // Compute the real coordinates within the chart:
871         y = (y*pH/2)/yAxisMaxValue;
872         x = (x*pW)/(xAxisMaxValue-xAxisMinValue);
873 
874         // Draw the horizontal line (altitude):
875         p.drawLine( QLineF( 0.5, y, avtUI->View->rect().width()-0.5,y ) );
876         // Draw the altitude value:
877         p.setPen( QPen( QBrush("gold"), 3, Qt::SolidLine ) );
878         p.drawText( 25, y + 15, QString::number(yValue,'f',2) + QChar(176) );
879         p.setPen( QPen( QBrush("gold"), 1, Qt::SolidLine ) );
880         // Draw the vertical line (time):
881         p.drawLine( QLineF( x, 0.5, x, avtUI->View->rect().height()-0.5 ) );
882         // Compute and draw the time value:
883         QTime localTime(0,0,0,0);
884         localTime = localTime.addSecs(int(xValue));
885         p.save();
886         p.translate( x + 10, pH - 20 );
887         p.rotate(-90);
888         p.setPen( QPen( QBrush("gold"), 3, Qt::SolidLine ) );
889         p.drawText( 5, 5, QLocale().toString( localTime, QLocale::ShortFormat ) ); // short format necessary to avoid false time-zone labeling
890         p.restore();
891         p.end();
892     }
893     // Refresh the background:
894     background->setScaled(false);
895     background->setScaled(true, Qt::IgnoreAspectRatio);
896     background->setPixmap(copy);
897 
898     avtUI->View->update();
899     avtUI->View->replot();
900 }
901 */
902 
mouseOverLine(QMouseEvent * event)903 void AltVsTime::mouseOverLine(QMouseEvent *event)
904 {
905     double x                            = avtUI->View->xAxis->pixelToCoord(event->localPos().x());
906     double y                            = avtUI->View->yAxis->pixelToCoord(event->localPos().y());
907     QCPAbstractPlottable *abstractGraph = avtUI->View->plottableAt(event->pos(), false);
908     QCPGraph *graph                     = qobject_cast<QCPGraph *>(abstractGraph);
909 
910     if (x > avtUI->View->xAxis->range().lower && x < avtUI->View->xAxis->range().upper)
911         if (y > avtUI->View->yAxis->range().lower && y < avtUI->View->yAxis->range().upper)
912         {
913             if (graph)
914             {
915                 double yValue = y;
916                 double xValue = x;
917 
918                 // Compute time value:
919                 QTime localTime(0, 0, 0, 0);
920                 QTime localSiderealTime(5, 0, 0, 0);
921 
922                 localTime         = localTime.addSecs(int(xValue));
923                 localSiderealTime = localSiderealTime.addSecs(int(xValue));
924 
925                 QToolTip::hideText();
926                 QToolTip::showText(event->globalPos(),
927                                    i18n("<table>"
928                                       "<tr>"
929                                       "<th colspan=\"2\">%1</th>"
930                                       "</tr>"
931                                       "<tr>"
932                                       "<td>LST:   </td>"
933                                       "<td>%3</td>"
934                                       "</tr>"
935                                       "<tr>"
936                                       "<td>LT:   </td>"
937                                       "<td>%2</td>"
938                                       "</tr>"
939                                       "<tr>"
940                                       "<td>Altitude:   </td>"
941                                       "<td>%4</td>"
942                                       "</tr>"
943                                       "</table>",
944                                       graph->name().isEmpty() ? "???" : graph->name(),
945                                       localTime.toString(), localSiderealTime.toString(),
946                                       QString::number(yValue, 'f', 2) + ' ' + QChar(176)),
947                                    avtUI->View, avtUI->View->rect());
948             }
949             else
950                 QToolTip::hideText();
951         }
952 
953     avtUI->View->update();
954     avtUI->View->replot();
955 }
956 
slotUpdateDateLoc()957 void AltVsTime::slotUpdateDateLoc()
958 {
959     KStarsData *data     = KStarsData::Instance();
960     KStarsDateTime today = getDate();
961     KSNumbers *num       = new KSNumbers(today.djd());
962     KSNumbers *oldNum    = nullptr;
963     CachingDms LST       = geo->GSTtoLST(today.gst());
964 
965     //First determine time of sunset and sunrise
966     computeSunRiseSetTimes();
967     // Determine dawn/dusk time and min/max sun elevation
968     setDawnDusk();
969 
970     for (int i = 0; i < pList.count(); ++i)
971     {
972         SkyObject *o = pList.at(i);
973         if (o)
974         {
975             //If the object is in the solar system, recompute its position for the given date
976             if (o->isSolarSystem())
977             {
978                 oldNum = new KSNumbers(data->ut().djd());
979                 o->updateCoords(num, true, geo->lat(), &LST, true);
980             }
981 
982             //precess coords to target epoch
983             o->updateCoordsNow(num);
984 
985             //update pList entry
986             pList.replace(i, o);
987 
988             // We are creating a new data set (time, altitude) for the new date:
989             QVector<double> time_dataSet, altitude_dataSet;
990             double point_altitudeValue, point_timeValue;
991             // compute the new graph values:
992             // time range: 24h
993             int offset = 3;
994             for (double h = -12.0, i = 0; h <= 12.0; h += 0.25, i++)
995             {
996                 point_altitudeValue = findAltitude(o, h);
997                 altitude_dataSet.push_back(point_altitudeValue);
998                 if (point_altitudeValue > maxAlt)
999                     maxAlt = point_altitudeValue;
1000                 if (point_altitudeValue < minAlt)
1001                     minAlt = point_altitudeValue;
1002                 point_timeValue = i * 900 + 43200;
1003                 time_dataSet.push_back(point_timeValue);
1004             }
1005 
1006             // Replace graph data set:
1007             avtUI->View->graph(i)->setData(time_dataSet, altitude_dataSet);
1008 
1009             // Go into initial state: without Zoom/Pan
1010             avtUI->View->xAxis->setRange(43200, 129600);
1011             avtUI->View->xAxis2->setRange(61200, 147600);
1012 
1013             // Center the altitude axis in 0 value:
1014             if (abs(minAlt) > maxAlt)
1015                 maxAlt = abs(minAlt);
1016             else
1017                 minAlt = -maxAlt;
1018             avtUI->View->yAxis->setRange(minAlt - offset, maxAlt + offset);
1019 
1020             // Update background coordinates:
1021             background->topLeft->setCoords(avtUI->View->xAxis->range().lower, avtUI->View->yAxis->range().upper);
1022             background->bottomRight->setCoords(avtUI->View->xAxis->range().upper, avtUI->View->yAxis->range().lower);
1023 
1024             // Redraw the plot:
1025             avtUI->View->replot();
1026 
1027             //restore original position
1028             if (o->isSolarSystem())
1029             {
1030                 o->updateCoords(oldNum, true, data->geo()->lat(), data->lst());
1031                 delete oldNum;
1032                 oldNum = nullptr;
1033             }
1034             o->EquatorialToHorizontal(data->lst(), data->geo()->lat());
1035         }
1036         else //assume unfound object is a custom object
1037         {
1038             pList.at(i)->updateCoordsNow(num); //precess to desired epoch
1039 
1040             // We are creating a new data set (time, altitude) for the new date:
1041             QVector<double> time_dataSet, altitude_dataSet;
1042             double point_altitudeValue, point_timeValue;
1043             // compute the new graph values:
1044             // time range: 24h
1045             int offset = 3;
1046             for (double h = -12.0, i = 0; h <= 12.0; h += 0.25, i++)
1047             {
1048                 point_altitudeValue = findAltitude(pList.at(i), h);
1049                 altitude_dataSet.push_back(point_altitudeValue);
1050                 if (point_altitudeValue > maxAlt)
1051                     maxAlt = point_altitudeValue;
1052                 if (point_altitudeValue < minAlt)
1053                     minAlt = point_altitudeValue;
1054                 point_timeValue = i * 900 + 43200;
1055                 time_dataSet.push_back(point_timeValue);
1056             }
1057 
1058             // Replace graph data set:
1059             avtUI->View->graph(i)->setData(time_dataSet, altitude_dataSet);
1060 
1061             // Go into initial state: without Zoom/Pan
1062             avtUI->View->xAxis->setRange(43200, 129600);
1063             avtUI->View->xAxis2->setRange(61200, 147600);
1064 
1065             // Center the altitude axis in 0 value:
1066             if (abs(minAlt) > maxAlt)
1067                 maxAlt = abs(minAlt);
1068             else
1069                 minAlt = -maxAlt;
1070             avtUI->View->yAxis->setRange(minAlt - offset, maxAlt + offset);
1071 
1072             // Update background coordinates:
1073             background->topLeft->setCoords(avtUI->View->xAxis->range().lower, avtUI->View->yAxis->range().upper);
1074             background->bottomRight->setCoords(avtUI->View->xAxis->range().upper, avtUI->View->yAxis->range().lower);
1075 
1076             // Redraw the plot:
1077             avtUI->View->replot();
1078         }
1079     }
1080 
1081     if (getDate().time().hour() > 12)
1082         DayOffset = 1;
1083     else
1084         DayOffset = 0;
1085 
1086     setLSTLimits();
1087     slotHighlight(avtUI->PlotList->currentRow());
1088     avtUI->View->update();
1089 
1090     delete num;
1091 }
1092 
slotChooseCity()1093 void AltVsTime::slotChooseCity()
1094 {
1095     QPointer<LocationDialog> ld = new LocationDialog(this);
1096     if (ld->exec() == QDialog::Accepted)
1097     {
1098         GeoLocation *newGeo = ld->selectedCity();
1099         if (newGeo)
1100         {
1101             geo = newGeo;
1102             avtUI->latBox->showInDegrees(geo->lat());
1103             avtUI->longBox->showInDegrees(geo->lng());
1104         }
1105     }
1106     delete ld;
1107 }
1108 
1109 // FIXME: should we remove this method?
setLSTLimits()1110 void AltVsTime::setLSTLimits()
1111 {
1112     /*
1113     //UT at noon on target date
1114     KStarsDateTime ut = getDate().addSecs(((double)DayOffset + 0.5)*86400.);
1115 
1116     dms lst = geo->GSTtoLST(ut.gst());
1117     double h1 = lst.Hours();
1118     if(h1 > 12.0)
1119         h1 -= 24.0;
1120     double h2 = h1 + 24.0;
1121     avtUI->View->setSecondaryLimits(h1, h2, -90.0, 90.0);
1122     */
1123 }
1124 
showCurrentDate()1125 void AltVsTime::showCurrentDate()
1126 {
1127     KStarsDateTime dt = KStarsDateTime::currentDateTime();
1128     if (dt.time() > QTime(12, 0, 0))
1129         dt = dt.addDays(1);
1130     avtUI->DateWidget->setDate(dt.date());
1131 }
1132 
drawGradient()1133 void AltVsTime::drawGradient()
1134 {
1135     // Things needed for Gradient:
1136     KStarsDateTime dtt  = KStarsDateTime::currentDateTime();
1137     GeoLocation *geoLoc = KStarsData::Instance()->geo();
1138     QDateTime midnight  = QDateTime(dtt.date(), QTime());
1139     KStarsDateTime const utt  = geoLoc->LTtoUT(KStarsDateTime(midnight));
1140 
1141     // Variables needed for Gradient:
1142     double SunRise, SunSet, Dawn, Dusk, SunMinAlt, SunMaxAlt;
1143     double MoonRise, MoonSet, MoonIllum;
1144 
1145     KSAlmanac ksal(utt, geoLoc);
1146 
1147     // Get the values:
1148     SunRise   = ksal.getSunRise();
1149     SunSet    = ksal.getSunSet();
1150     SunMaxAlt = ksal.getSunMaxAlt();
1151     SunMinAlt = ksal.getSunMinAlt();
1152     MoonRise  = ksal.getMoonRise();
1153     MoonSet   = ksal.getMoonSet();
1154     MoonIllum = ksal.getMoonIllum();
1155     Dawn      = ksal.getDawnAstronomicalTwilight();
1156     Dusk      = ksal.getDuskAstronomicalTwilight();
1157 
1158     gradient = new QPixmap(avtUI->View->rect().width(), avtUI->View->rect().height());
1159 
1160     QPainter p;
1161 
1162     p.begin(gradient);
1163     KPlotWidget *kPW = new KPlotWidget;
1164     p.setRenderHint(QPainter::Antialiasing, kPW->antialiasing());
1165     p.fillRect(gradient->rect(), kPW->backgroundColor());
1166 
1167     p.setClipRect(gradient->rect());
1168     p.setClipping(true);
1169 
1170     int pW = gradient->rect().width();
1171     int pH = gradient->rect().height();
1172 
1173     QColor SkyColor(0, 100, 200);
1174     //    TODO
1175     //    if( Options::darkAppColors() )
1176     //        SkyColor = QColor( 200, 0, 0 ); // use something red, visible through a red filter
1177 
1178     // Draw gradient representing lunar interference in the sky
1179     if (MoonIllum > 0.01) // do this only if Moon illumination is reasonable so it's important
1180     {
1181         int moonrise = int(pW * (0.5 + MoonRise));
1182         int moonset  = int(pW * (MoonSet - 0.5));
1183         if (moonset < 0)
1184             moonset += pW;
1185         if (moonrise > pW)
1186             moonrise -= pW;
1187         int moonalpha = int(10 + MoonIllum * 130);
1188         int fadewidth =
1189             pW *
1190             0.01; // pW * fraction of day to fade the moon brightness over (0.01 corresponds to roughly 15 minutes, 0.007 to 10 minutes), both before and after actual set.
1191         QColor MoonColor(255, 255, 255, moonalpha);
1192 
1193         if (moonset < moonrise)
1194         {
1195             QLinearGradient grad =
1196                 QLinearGradient(QPointF(moonset - fadewidth, 0.0), QPointF(moonset + fadewidth, 0.0));
1197             grad.setColorAt(0, MoonColor);
1198             grad.setColorAt(1, Qt::transparent);
1199             p.fillRect(QRectF(0.0, 0.0, moonset + fadewidth, pH),
1200                        grad); // gradient should be padded until moonset - fadewidth (see QLinearGradient docs)
1201             grad.setStart(QPointF(moonrise + fadewidth, 0.0));
1202             grad.setFinalStop(QPointF(moonrise - fadewidth, 0.0));
1203             p.fillRect(QRectF(moonrise - fadewidth, 0.0, pW - moonrise + fadewidth, pH), grad);
1204         }
1205         else
1206         {
1207             qreal opacity = p.opacity();
1208             p.setOpacity(opacity / 4);
1209             p.fillRect(QRectF(moonrise + fadewidth, 0.0, moonset - moonrise - 2 * fadewidth, pH), MoonColor);
1210             QLinearGradient grad =
1211                 QLinearGradient(QPointF(moonrise + fadewidth, 0.0), QPointF(moonrise - fadewidth, 0.0));
1212             grad.setColorAt(0, MoonColor);
1213             grad.setColorAt(1, Qt::transparent);
1214             p.fillRect(QRectF(0.0, 0.0, moonrise + fadewidth, pH), grad);
1215             grad.setStart(QPointF(moonset - fadewidth, 0.0));
1216             grad.setFinalStop(QPointF(moonset + fadewidth, 0.0));
1217             p.fillRect(QRectF(moonset - fadewidth, 0.0, pW - moonset, pH), grad);
1218             p.setOpacity(opacity);
1219         }
1220     }
1221 
1222     //draw daytime sky if the Sun rises for the current date/location
1223     if (SunMaxAlt > -18.0)
1224     {
1225         //Display centered on midnight, so need to modulate dawn/dusk by 0.5
1226         int rise = int(pW * (0.5 + SunRise));
1227         int set  = int(pW * (SunSet - 0.5));
1228         int da   = int(pW * (0.5 + Dawn));
1229         int du   = int(pW * (Dusk - 0.5));
1230 
1231         if (SunMinAlt > 0.0)
1232         {
1233             // The sun never set and the sky is always blue
1234             p.fillRect(rect(), SkyColor);
1235         }
1236         else if (SunMaxAlt < 0.0 && SunMinAlt < -18.0)
1237         {
1238             // The sun never rise but the sky is not completely dark
1239             QLinearGradient grad = QLinearGradient(QPointF(0.0, 0.0), QPointF(du, 0.0));
1240 
1241             QColor gradStartColor = SkyColor;
1242             gradStartColor.setAlpha((1 - (SunMaxAlt / -18.0)) * 255);
1243 
1244             grad.setColorAt(0, gradStartColor);
1245             grad.setColorAt(1, Qt::transparent);
1246             p.fillRect(QRectF(0.0, 0.0, du, pH), grad);
1247             grad.setStart(QPointF(pW, 0.0));
1248             grad.setFinalStop(QPointF(da, 0.0));
1249             p.fillRect(QRectF(da, 0.0, pW, pH), grad);
1250         }
1251         else if (SunMaxAlt < 0.0 && SunMinAlt > -18.0)
1252         {
1253             // The sun never rise but the sky is NEVER completely dark
1254             QLinearGradient grad = QLinearGradient(QPointF(0.0, 0.0), QPointF(pW, 0.0));
1255 
1256             QColor gradStartEndColor = SkyColor;
1257             gradStartEndColor.setAlpha((1 - (SunMaxAlt / -18.0)) * 255);
1258             QColor gradMidColor = SkyColor;
1259             gradMidColor.setAlpha((1 - (SunMinAlt / -18.0)) * 255);
1260 
1261             grad.setColorAt(0, gradStartEndColor);
1262             grad.setColorAt(0.5, gradMidColor);
1263             grad.setColorAt(1, gradStartEndColor);
1264             p.fillRect(QRectF(0.0, 0.0, pW, pH), grad);
1265         }
1266         else if (Dawn < 0.0)
1267         {
1268             // The sun sets and rises but the sky is never completely dark
1269             p.fillRect(0, 0, set, int(0.5 * pH), SkyColor);
1270             p.fillRect(rise, 0, pW, int(0.5 * pH), SkyColor);
1271 
1272             QLinearGradient grad = QLinearGradient(QPointF(set, 0.0), QPointF(rise, 0.0));
1273 
1274             QColor gradMidColor = SkyColor;
1275             gradMidColor.setAlpha((1 - (SunMinAlt / -18.0)) * 255);
1276 
1277             grad.setColorAt(0, SkyColor);
1278             grad.setColorAt(0.5, gradMidColor);
1279             grad.setColorAt(1, SkyColor);
1280             p.fillRect(QRectF(set, 0.0, rise - set, pH), grad);
1281         }
1282         else
1283         {
1284             p.fillRect(0, 0, set, pH, SkyColor);
1285             p.fillRect(rise, 0, pW, pH, SkyColor);
1286 
1287             QLinearGradient grad = QLinearGradient(QPointF(set, 0.0), QPointF(du, 0.0));
1288             grad.setColorAt(0, SkyColor);
1289             grad.setColorAt(
1290                 1,
1291                 Qt::transparent); // FIXME?: The sky appears black well before the actual end of twilight if the gradient is too slow (eg: latitudes above arctic circle)
1292             p.fillRect(QRectF(set, 0.0, du - set, pH), grad);
1293 
1294             grad.setStart(QPointF(rise, 0.0));
1295             grad.setFinalStop(QPointF(da, 0.0));
1296             p.fillRect(QRectF(da, 0.0, rise - da, pH), grad);
1297         }
1298     }
1299 
1300     p.fillRect(0, int(0.5 * pH), pW, int(0.5 * pH), KStarsData::Instance()->colorScheme()->colorNamed("HorzColor"));
1301 
1302     p.setClipping(false);
1303 
1304     // Add vertical line indicating "now"
1305     // Convert the current system clock time to the TZ corresponding to geo
1306     QTime t = geoLoc->UTtoLT(KStarsDateTime::currentDateTimeUtc()).time();
1307     double x = 12.0 + t.hour() + t.minute() / 60.0 + t.second() / 3600.0;
1308 
1309     while (x > 24.0)
1310         x -= 24.0;
1311 
1312     // Convert to screen pixel coords
1313     int ix = int(x * pW / 24.0);
1314 
1315     p.setPen(QPen(QBrush("white"), 2.0, Qt::DotLine));
1316     p.drawLine(ix, 0, ix, pH);
1317 
1318     QFont largeFont = p.font();
1319 
1320     largeFont.setPointSize(largeFont.pointSize() + 1);
1321     // Label this vertical line with the current time
1322     p.save();
1323     p.setFont(largeFont);
1324     p.translate(ix + 15, pH - 20);
1325     p.rotate(-90);
1326     // Short format necessary to avoid false time-zone labeling
1327     p.drawText(0, 0, QLocale().toString(t, QLocale::ShortFormat));
1328     p.restore();
1329     p.end();
1330 }
1331 
getDate()1332 KStarsDateTime AltVsTime::getDate()
1333 {
1334     //convert midnight local time to UT:
1335     QDateTime lt(avtUI->DateWidget->date(), QTime());
1336     return geo->LTtoUT(KStarsDateTime(lt));
1337 }
1338 
getEpoch(const QString & eName)1339 double AltVsTime::getEpoch(const QString &eName)
1340 {
1341     //If Epoch field not a double, assume J2000
1342     bool ok;
1343     double epoch = eName.toDouble(&ok);
1344     if (!ok)
1345     {
1346         qCWarning(KSTARS) << "Invalid Epoch.  Assuming 2000.0.";
1347         return 2000.0;
1348     }
1349     return epoch;
1350 }
1351 
setDawnDusk()1352 void AltVsTime::setDawnDusk()
1353 {
1354     /* TODO */
1355 
1356     /*
1357     KSAlmanac almanac(getDate(), geo);
1358 
1359     avtUI->View->setDawnDuskTimes(almanac.getDawnAstronomicalTwilight(), almanac.getDuskAstronomicalTwilight());
1360     avtUI->View->setMinMaxSunAlt(almanac.getSunMinAlt(), almanac.getSunMaxAlt());
1361     */
1362 
1363     /* ... */
1364 }
1365 
slotPrint()1366 void AltVsTime::slotPrint()
1367 {
1368     QPainter p;            // Our painter object
1369     QPrinter printer;      // Our printer object
1370     QString str_legend;    // Text legend
1371     int text_height = 200; // Height of legend text zone in points
1372     QSize plot_size;       // Initial plot widget size
1373     QFont plot_font;       // Initial plot widget font
1374     int plot_font_size;    // Initial plot widget font size
1375 
1376     // Set printer resolution to 300 dpi
1377     printer.setResolution(300);
1378 
1379     // Open print dialog
1380     //QPointer<QPrintDialog> dialog( KdePrint::createPrintDialog( &printer, this ) );
1381     //QPointer<QPrintDialog> dialog( &printer, this );
1382     QPrintDialog dialog(&printer, this);
1383     dialog.setWindowTitle(i18nc("@title:window", "Print elevation vs time plot"));
1384     if (dialog.exec() == QDialog::Accepted)
1385     {
1386         // Change mouse cursor
1387         QApplication::setOverrideCursor(Qt::WaitCursor);
1388 
1389         // Save plot widget font
1390         plot_font = avtUI->View->font();
1391         // Save plot widget font size
1392         plot_font_size = plot_font.pointSize();
1393         // Save calendar widget size
1394         plot_size = avtUI->View->size();
1395 
1396         // Set text legend
1397         str_legend = i18n("Elevation vs. Time Plot");
1398         str_legend += '\n';
1399         str_legend += geo->fullName();
1400         str_legend += " - ";
1401         str_legend += avtUI->DateWidget->date().toString("dd/MM/yyyy");
1402 
1403         // Create a rectangle for legend text zone
1404         QRect text_rect(0, 0, printer.width(), text_height);
1405 
1406         // Increase plot widget font size so it looks good in 300 dpi
1407         plot_font.setPointSize(plot_font_size * 2.5);
1408         avtUI->View->setFont(plot_font);
1409         // Increase plot widget size to fit the entire page
1410         avtUI->View->resize(printer.width(), printer.height() - text_height);
1411 
1412         // Create a pixmap and render plot widget into it
1413         QPixmap pixmap(avtUI->View->size());
1414         avtUI->View->render(&pixmap);
1415 
1416         // Begin painting on printer
1417         p.begin(&printer);
1418         // Draw legend
1419         p.drawText(text_rect, Qt::AlignLeft, str_legend);
1420         // Draw plot widget
1421         p.drawPixmap(0, text_height, pixmap);
1422         // Ending painting
1423         p.end();
1424 
1425         // Restore plot widget font size
1426         plot_font.setPointSize(plot_font_size);
1427         avtUI->View->setFont(plot_font);
1428         // Restore calendar widget size
1429         avtUI->View->resize(plot_size);
1430 
1431         // Restore mouse cursor
1432         QApplication::restoreOverrideCursor();
1433     }
1434     //delete dialog;
1435 }
1436 
getObjectName(const SkyObject * o,bool translated)1437 QString AltVsTime::getObjectName(const SkyObject *o, bool translated)
1438 {
1439     QString finalObjectName;
1440     if (o->name() == "star")
1441     {
1442         StarObject *s = (StarObject *)o;
1443 
1444         // JM: Enable HD Index stars to be added to the observing list.
1445         if (s->getHDIndex() != 0)
1446             finalObjectName = QString("HD %1").arg(QString::number(s->getHDIndex()));
1447     }
1448     else
1449         finalObjectName = translated ? o->translatedName() : o->name();
1450 
1451     return finalObjectName;
1452 }
1453