1 /*
2 SPDX-FileCopyrightText: 2008 Jason Harris <kstars@30doradus.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "calendarwidget.h"
8
9 #include "ksalmanac.h"
10 #include "kssun.h"
11 #include "kstarsdata.h"
12 #include "skycalendar.h"
13
14 #include <KLocalizedString>
15 #include <KPlotting/KPlotObject>
16
17 #include <QPainter>
18 #include <QDebug>
19
20 #define BIGTICKSIZE 10
21 #define SMALLTICKSIZE 4
22
CalendarWidget(QWidget * parent)23 CalendarWidget::CalendarWidget(QWidget *parent) : KPlotWidget(parent)
24 {
25 setAntialiasing(true);
26
27 setTopPadding(40);
28 setLeftPadding(60);
29 setRightPadding(100);
30
31 maxRTime = 12.0;
32 minSTime = -12.0;
33 }
34
paintEvent(QPaintEvent * e)35 void CalendarWidget::paintEvent(QPaintEvent *e)
36 {
37 Q_UNUSED(e)
38
39 QPainter p;
40
41 p.begin(this);
42 p.setRenderHint(QPainter::Antialiasing, antialiasing());
43 p.fillRect(rect(), backgroundColor());
44 p.translate(leftPadding(), topPadding());
45
46 setPixRect();
47 p.setClipRect(pixRect());
48 p.setClipping(true);
49
50 drawHorizon(&p);
51
52 foreach (KPlotObject *po, plotObjects())
53 {
54 po->draw(&p, this);
55 }
56
57 p.setClipping(false);
58 drawAxes(&p);
59 }
60
setHorizon()61 void CalendarWidget::setHorizon()
62 {
63 KSSun thesun;
64 SkyCalendar *skycal = (SkyCalendar *)topLevelWidget();
65 KStarsDateTime kdt(QDate(skycal->year(), 1, 1), QTime(12, 0, 0));
66
67 maxRTime = 0.0;
68 minSTime = 0.0;
69
70 // Clear date, rise and set time lists
71 dateList.clear();
72 riseTimeList.clear();
73 setTimeList.clear();
74
75 float rTime, sTime;
76
77 // Get rise and set time every 7 days for 1 year
78 while (skycal->year() == kdt.date().year())
79 {
80 QTime tmp_rTime = thesun.riseSetTime(KStarsDateTime(kdt.djd() + 1.0), skycal->get_geo(), true, true);
81 QTime tmp_sTime = thesun.riseSetTime(KStarsDateTime(kdt.djd()), skycal->get_geo(), false, true);
82
83 /* riseSetTime seems buggy since it sometimes returns the same time for rise and set (01:00:00).
84 * In this case, we just reset tmp_rTime and tmp_sTime so they will be considered invalid
85 * in the following lines.
86 * NOTE: riseSetTime should be fix now, this test is no longer necessary*/
87 if (tmp_rTime == tmp_sTime)
88 {
89 tmp_rTime = QTime();
90 tmp_sTime = QTime();
91 }
92
93 // If rise and set times are valid, the sun rise and set...
94 if (tmp_rTime.isValid() && tmp_sTime.isValid())
95 {
96 // Compute X-coordinate value for rise and set time
97 QTime midday(12, 0, 0);
98 rTime = tmp_rTime.secsTo(midday) * 24.0 / 86400.0;
99 sTime = tmp_sTime.secsTo(midday) * 24.0 / 86400.0;
100
101 if (tmp_rTime <= midday)
102 rTime = 12.0 - rTime;
103 else
104 rTime = -12.0 - rTime;
105
106 if (tmp_sTime <= midday)
107 sTime = 12.0 - sTime;
108 else
109 sTime = -12.0 - sTime;
110 }
111 /* else, the sun don't rise and/or don't set.
112 * we look at the altitude of the sun at transit time, if it is above the horizon,
113 * there is no night, else there is no day. */
114 else
115 {
116 if (thesun.transitAltitude(KStarsDateTime(kdt.djd()), skycal->get_geo()).degree() > 0)
117 {
118 rTime = -4.0;
119 sTime = 4.0;
120 }
121 else
122 {
123 rTime = 12.0;
124 sTime = -12.0;
125 }
126 }
127
128 // Get max rise time and min set time
129 if (rTime > maxRTime)
130 maxRTime = rTime;
131 if (sTime < minSTime)
132 minSTime = sTime;
133
134 // Keep the day, rise time and set time in lists
135 dateList.append(kdt.date());
136 riseTimeList.append(rTime);
137 setTimeList.append(sTime);
138
139 // Next week
140 kdt = kdt.addDays(skycal->scUI->spinBox_Interval->value());
141 }
142
143 // Set widget limits
144 maxRTime = ceil(maxRTime) + 1.0;
145 if ((int)maxRTime % 2 != 0)
146 maxRTime++;
147 if (maxRTime > 12.0)
148 maxRTime = 12.0;
149 minSTime = floor(minSTime) - 1.0;
150 if ((int)minSTime % 2 != 0)
151 minSTime--;
152 if (minSTime < -12.0)
153 minSTime = -12.0;
154 setLimits(minSTime, maxRTime, 0.0, 366.0);
155 setPixRect();
156 }
157
drawHorizon(QPainter * p)158 void CalendarWidget::drawHorizon(QPainter *p)
159 {
160 polySunRise.clear();
161 polySunSet.clear();
162
163 for (int date = 0; date < dateList.size(); date++)
164 {
165 int day = dateList.at(date).daysInYear() - dateList.at(date).dayOfYear();
166 polySunRise << mapToWidget(QPointF(riseTimeList.at(date), day));
167 polySunSet << mapToWidget(QPointF(setTimeList.at(date), day));
168 }
169
170 //Finish polygons by adding pixRect corners
171 polySunRise << mapToWidget(QPointF(riseTimeList.last(), dataRect().top()))
172 << mapToWidget(QPointF(dataRect().right(), dataRect().top()))
173 << mapToWidget(QPointF(dataRect().right(), dataRect().bottom()))
174 << mapToWidget(QPointF(riseTimeList.first(), dataRect().bottom()));
175 polySunSet << mapToWidget(QPointF(setTimeList.last(), dataRect().top()))
176 << mapToWidget(QPointF(dataRect().left(), pixRect().top()))
177 << mapToWidget(QPointF(dataRect().left(), pixRect().bottom()))
178 << mapToWidget(QPointF(setTimeList.first(), dataRect().bottom()));
179
180 p->setPen(Qt::darkGreen);
181 p->setBrush(Qt::darkGreen);
182 p->drawPolygon(polySunRise);
183 p->drawPolygon(polySunSet);
184 }
185
drawAxes(QPainter * p)186 void CalendarWidget::drawAxes(QPainter *p)
187 {
188 SkyCalendar *skycal = (SkyCalendar *)topLevelWidget();
189
190 p->setPen(foregroundColor());
191 p->setBrush(Qt::NoBrush);
192
193 //Draw bounding box for the plot
194 p->drawRect(pixRect());
195
196 //set small font for axis labels
197 QFont f = p->font();
198 int s = f.pointSize();
199 f.setPointSize(s - 2);
200 p->setFont(f);
201
202 // Top axis label
203 p->drawText(0, -38, pixRect().width(), pixRect().height(), Qt::AlignHCenter | Qt::AlignTop | Qt::TextDontClip,
204 i18n("Local time"));
205 // Bottom axis label
206 p->drawText(0, 0, pixRect().width(), pixRect().height() + 35, Qt::AlignHCenter | Qt::AlignBottom | Qt::TextDontClip,
207 i18n("Universal time"));
208 // Left axis label
209 p->save();
210 p->rotate(90.0);
211 p->drawText(0, 0, pixRect().height(), leftPadding() - 5, Qt::AlignHCenter | Qt::AlignBottom | Qt::TextDontClip,
212 i18n("Month"));
213 // Right axis label
214 p->translate(0.0, -1 * frameRect().width() + 30);
215 p->drawText(0, 0, pixRect().height(), leftPadding() - 5, Qt::AlignHCenter | Qt::AlignBottom | Qt::TextDontClip,
216 i18n("Julian date"));
217 p->restore();
218
219 //Top/Bottom axis tickmarks and time labels
220 for (float xx = minSTime; xx <= maxRTime; xx += 1.0)
221 {
222 int h = int(xx);
223 if (h < 0)
224 h += 24;
225 QTime time(h, 0, 0);
226 QString sTime = QLocale().toString(time, "hh:mm");
227
228 QString sUtTime = QLocale().toString(time.addSecs(skycal->get_geo()->TZ() * -3600), "hh:mm");
229
230 // Draw a small tick every hours and a big tick every two hours.
231 QPointF pBottomTick = mapToWidget(QPointF(xx, dataRect().y()));
232 QPointF pTopTick = QPointF(pBottomTick.x(), 0.0);
233 if (h % 2)
234 {
235 // Draw small bottom tick
236 p->drawLine(pBottomTick, QPointF(pBottomTick.x(), pBottomTick.y() - SMALLTICKSIZE));
237 // Draw small top tick
238 p->drawLine(pTopTick, QPointF(pTopTick.x(), pTopTick.y() + SMALLTICKSIZE));
239 }
240 else
241 {
242 // Draw big bottom tick
243 p->drawLine(pBottomTick, QPointF(pBottomTick.x(), pBottomTick.y() - BIGTICKSIZE));
244 QRectF r(pBottomTick.x() - BIGTICKSIZE, pBottomTick.y() + 0.5 * BIGTICKSIZE, 2 * BIGTICKSIZE, BIGTICKSIZE);
245 p->drawText(r, Qt::AlignCenter | Qt::TextDontClip, sUtTime);
246 // Draw big top tick
247 p->drawLine(pTopTick, QPointF(pTopTick.x(), pTopTick.y() + BIGTICKSIZE));
248 r.moveTop(-2.0 * BIGTICKSIZE);
249 p->drawText(r, Qt::AlignCenter | Qt::TextDontClip, sTime);
250 }
251
252 // Vertical grid
253 if (skycal->scUI->checkBox_GridVertical->isChecked())
254 {
255 QColor c = p->pen().color();
256 c.setAlpha(100);
257 p->setPen(c);
258 p->drawLine(pTopTick, pBottomTick);
259 c.setAlpha(255);
260 p->setPen(c);
261 }
262 }
263
264 // Month dividers
265 int y = skycal->year();
266 for (int imonth = 2; imonth <= 12; ++imonth)
267 {
268 QDate dt(y, imonth, 1);
269 float doy = float(dt.daysInYear() - dt.dayOfYear());
270
271 // Draw a tick every months on left axis
272 QPointF pMonthTick = mapToWidget(QPointF(dataRect().x(), doy));
273 p->drawLine(pMonthTick, QPointF(pMonthTick.x() + BIGTICKSIZE, pMonthTick.y()));
274
275 // Draw month labels
276 QRectF rMonth(mapToWidget(QPointF(0.0, float(dt.daysInYear() - dt.addMonths(-1).dayOfYear()))),
277 mapToWidget(QPointF(dataRect().left() - 0.1, doy)));
278 QLocale locale;
279 p->drawText(rMonth, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, locale.monthName(imonth - 1, QLocale::ShortFormat));
280 if (imonth == 12) // December
281 {
282 rMonth = QRectF(mapToWidget(QPointF(0.0, doy)), mapToWidget(QPointF(dataRect().left() - 0.1, 0.0)));
283 p->drawText(rMonth, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, locale.monthName(imonth, QLocale::ShortFormat));
284 }
285
286 // Draw dividers
287 if (skycal->scUI->checkBox_GridMonths->isChecked())
288 {
289 QColor c = p->pen().color();
290 c.setAlpha(100);
291 p->setPen(c);
292 p->drawLine(pMonthTick, QPointF(pixRect().right(), pMonthTick.y()));
293 c.setAlpha(255);
294 p->setPen(c);
295 }
296 }
297
298 // Interval dividers
299 QFont origFont = p->font();
300 p->setFont(QFont("Monospace", origFont.pointSize() - 1));
301 for (KStarsDateTime kdt(QDate(y, 1, 1), QTime(12, 0, 0)); kdt.date().year() == y;
302 kdt = kdt.addDays(skycal->scUI->spinBox_Interval->value() > 7 ? skycal->scUI->spinBox_Interval->value() : 7))
303 {
304 // Draw ticks
305 float doy = float(kdt.date().daysInYear() - kdt.date().dayOfYear());
306 QPointF pWeekTick = mapToWidget(QPointF(dataRect().right(), doy));
307 p->drawLine(pWeekTick, QPointF(pWeekTick.x() - BIGTICKSIZE, pWeekTick.y()));
308
309 // Draw julian date
310 QRectF rJd(mapToWidget(QPointF(dataRect().right() + 0.1, doy + 2)),
311 mapToWidget(QPointF(dataRect().right(), doy)));
312 p->drawText(rJd, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip,
313 QString().setNum(double(kdt.djd()), 'f', 1));
314
315 // Draw dividers
316 if (skycal->scUI->checkBox_GridWeeks->isChecked())
317 {
318 QColor c = p->pen().color();
319 c.setAlpha(50);
320 p->setPen(c);
321 p->drawLine(pWeekTick, QPointF(pixRect().left(), pWeekTick.y()));
322 c.setAlpha(255);
323 p->setPen(c);
324 }
325 }
326
327 // Current day
328 if (skycal->scUI->checkBox_GridToday->isChecked())
329 {
330 p->setPen(QColor(Qt::yellow));
331 QDate today = QDate::currentDate();
332 float doy = float(today.daysInYear() - today.dayOfYear());
333 p->drawLine(mapToWidget(QPointF(dataRect().left(), doy)), mapToWidget(QPointF(dataRect().right(), doy)));
334 p->drawText(mapToWidget(QPointF(dataRect().left() + 0.1, doy + 2.0)), today.toString());
335 }
336
337 //Draw month labels along each horizon curve
338 // if ( skycal->scUI->checkBox_LabelMonths->isChecked() ) {
339 // p->setFont( QFont( "Monospace", origFont.pointSize() + 5 ) );
340 // int textFlags = Qt::TextSingleLine | Qt::AlignCenter;
341 // QFontMetricsF fm( p->font(), p->device() );
342 //
343 // for ( int date=0; date<dateList.size(); date++ ) {
344 // if ( dateList.at( date ).day() < 12 || dateList.at( date ).day() > 18 )
345 // continue;
346 //
347 // bool noNight = false;
348 // if ( riseTimeList.at( date ) < setTimeList.at( date ) )
349 // noNight = true;
350 //
351 // int imonth = dateList.at( date ).month();
352 //
353 // QString shortMonthName = QDate::shortMonthName( dateList.at( date ).month() );
354 // QRectF riseLabelRect = fm.boundingRect( QRectF(0,0,1,1), textFlags, shortMonthName );
355 // QRectF setLabelRect = fm.boundingRect( QRectF(0,0,1,1), textFlags, shortMonthName );
356 //
357 // QDate dt( y, imonth, 15 );
358 // float doy = float( dt.daysInYear() - dt.dayOfYear() );
359 // float xRiseLabel, xSetLabel;
360 // if ( noNight ) {
361 // xRiseLabel = 0.0;
362 // xSetLabel = 0.0;
363 // } else {
364 // xRiseLabel = riseTimeList.at( date ) + 0.6;
365 // xSetLabel = setTimeList.at( date )- 0.6;
366 // }
367 // QPointF pRiseLabel = mapToWidget( QPointF( xRiseLabel, doy ) );
368 // QPointF pSetLabel = mapToWidget( QPointF( xSetLabel, doy ) );
369 //
370 // //Determine rotation angle for month labels
371 // QDate dt1( y, imonth, 1 );
372 // float doy1 = float( dt1.daysInYear() - dt1.dayOfYear() );
373 // QDate dt2( y, imonth, dt1.daysInMonth() );
374 // float doy2 = float( dt2.daysInYear() - dt2.dayOfYear() );
375 //
376 // QPointF p1, p2;
377 // float rAngle, sAngle;
378 // if ( noNight ) {
379 // rAngle = 90.0;
380 // } else {
381 // p1 = mapToWidget( QPointF( riseTimeList.at( date-2 ), doy1 ) );
382 // p2 = mapToWidget( QPointF( riseTimeList.at( date+2 ), doy2 ) );
383 // rAngle = atan2( p2.y() - p1.y(), p2.x() - p1.x() )/dms::DegToRad;
384 //
385 // p1 = mapToWidget( QPointF( setTimeList.at( date-2 ), doy1 ) );
386 // p2 = mapToWidget( QPointF( setTimeList.at( date+2 ), doy2 ) );
387 // sAngle = atan2( p2.y() - p1.y(), p2.x() - p1.x() )/dms::DegToRad;
388 // }
389 //
390 // p->save();
391 // p->translate( pRiseLabel );
392 // p->rotate( rAngle );
393 // p->drawText( riseLabelRect, textFlags, shortMonthName );
394 // p->restore();
395 //
396 // if ( ! noNight ) {
397 // p->save();
398 // p->translate( pSetLabel );
399 // p->rotate( sAngle );
400 // p->drawText( setLabelRect, textFlags, shortMonthName );
401 // p->restore();
402 // }
403 // }
404 // }
405
406 p->setFont(origFont);
407 }
408