File indexing completed on 2024-03-24 15:18:02

0001 /*
0002     SPDX-FileCopyrightText: 2008 Jason Harris <kstars@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "calendarwidget.h"
0008 
0009 #include "ksalmanac.h"
0010 #include "kssun.h"
0011 #include "kstarsdata.h"
0012 #include "skycalendar.h"
0013 
0014 #include <KLocalizedString>
0015 #include <KPlotting/KPlotObject>
0016 
0017 #include <QPainter>
0018 #include <QDebug>
0019 
0020 #define BIGTICKSIZE   10
0021 #define SMALLTICKSIZE 4
0022 
0023 CalendarWidget::CalendarWidget(QWidget *parent) : KPlotWidget(parent)
0024 {
0025     setAntialiasing(true);
0026 
0027     setTopPadding(40);
0028     setLeftPadding(60);
0029     setRightPadding(100);
0030 
0031     maxRTime = 12.0;
0032     minSTime = -12.0;
0033 }
0034 
0035 void CalendarWidget::paintEvent(QPaintEvent *e)
0036 {
0037     Q_UNUSED(e)
0038 
0039     QPainter p;
0040 
0041     p.begin(this);
0042     p.setRenderHint(QPainter::Antialiasing, antialiasing());
0043     p.fillRect(rect(), backgroundColor());
0044     p.translate(leftPadding(), topPadding());
0045 
0046     setPixRect();
0047     p.setClipRect(pixRect());
0048     p.setClipping(true);
0049 
0050     drawHorizon(&p);
0051 
0052     foreach (KPlotObject *po, plotObjects())
0053     {
0054         po->draw(&p, this);
0055     }
0056 
0057     p.setClipping(false);
0058     drawAxes(&p);
0059 }
0060 
0061 void CalendarWidget::setHorizon()
0062 {
0063     KSSun thesun;
0064     SkyCalendar *skycal = (SkyCalendar *)topLevelWidget();
0065     KStarsDateTime kdt(QDate(skycal->year(), 1, 1), QTime(12, 0, 0));
0066 
0067     maxRTime = 0.0;
0068     minSTime = 0.0;
0069 
0070     // Clear date, rise and set time lists
0071     dateList.clear();
0072     riseTimeList.clear();
0073     setTimeList.clear();
0074 
0075     float rTime, sTime;
0076 
0077     // Get rise and set time every 7 days for 1 year
0078     while (skycal->year() == kdt.date().year())
0079     {
0080         QTime tmp_rTime = thesun.riseSetTime(KStarsDateTime(kdt.djd() + 1.0), skycal->get_geo(), true, true);
0081         QTime tmp_sTime = thesun.riseSetTime(KStarsDateTime(kdt.djd()), skycal->get_geo(), false, true);
0082 
0083         /* riseSetTime seems buggy since it sometimes returns the same time for rise and set (01:00:00).
0084          * In this case, we just reset tmp_rTime and tmp_sTime so they will be considered invalid
0085          * in the following lines.
0086          * NOTE: riseSetTime should be fix now, this test is no longer necessary*/
0087         if (tmp_rTime == tmp_sTime)
0088         {
0089             tmp_rTime = QTime();
0090             tmp_sTime = QTime();
0091         }
0092 
0093         // If rise and set times are valid, the sun rise and set...
0094         if (tmp_rTime.isValid() && tmp_sTime.isValid())
0095         {
0096             // Compute X-coordinate value for rise and set time
0097             QTime midday(12, 0, 0);
0098             rTime = tmp_rTime.secsTo(midday) * 24.0 / 86400.0;
0099             sTime = tmp_sTime.secsTo(midday) * 24.0 / 86400.0;
0100 
0101             if (tmp_rTime <= midday)
0102                 rTime = 12.0 - rTime;
0103             else
0104                 rTime = -12.0 - rTime;
0105 
0106             if (tmp_sTime <= midday)
0107                 sTime = 12.0 - sTime;
0108             else
0109                 sTime = -12.0 - sTime;
0110         }
0111         /* else, the sun don't rise and/or don't set.
0112          * we look at the altitude of the sun at transit time, if it is above the horizon,
0113          * there is no night, else there is no day. */
0114         else
0115         {
0116             if (thesun.transitAltitude(KStarsDateTime(kdt.djd()), skycal->get_geo()).degree() > 0)
0117             {
0118                 rTime = -4.0;
0119                 sTime = 4.0;
0120             }
0121             else
0122             {
0123                 rTime = 12.0;
0124                 sTime = -12.0;
0125             }
0126         }
0127 
0128         // Get max rise time and min set time
0129         if (rTime > maxRTime)
0130             maxRTime = rTime;
0131         if (sTime < minSTime)
0132             minSTime = sTime;
0133 
0134         // Keep the day, rise time and set time in lists
0135         dateList.append(kdt.date());
0136         riseTimeList.append(rTime);
0137         setTimeList.append(sTime);
0138 
0139         // Next week
0140         kdt = kdt.addDays(skycal->scUI->spinBox_Interval->value());
0141     }
0142 
0143     // Set widget limits
0144     maxRTime = ceil(maxRTime) + 1.0;
0145     if ((int)maxRTime % 2 != 0)
0146         maxRTime++;
0147     if (maxRTime > 12.0)
0148         maxRTime = 12.0;
0149     minSTime = floor(minSTime) - 1.0;
0150     if ((int)minSTime % 2 != 0)
0151         minSTime--;
0152     if (minSTime < -12.0)
0153         minSTime = -12.0;
0154     setLimits(minSTime, maxRTime, 0.0, 366.0);
0155     setPixRect();
0156 }
0157 
0158 void CalendarWidget::drawHorizon(QPainter *p)
0159 {
0160     polySunRise.clear();
0161     polySunSet.clear();
0162 
0163     for (int date = 0; date < dateList.size(); date++)
0164     {
0165         int day = dateList.at(date).daysInYear() - dateList.at(date).dayOfYear();
0166         polySunRise << mapToWidget(QPointF(riseTimeList.at(date), day));
0167         polySunSet << mapToWidget(QPointF(setTimeList.at(date), day));
0168     }
0169 
0170     //Finish polygons by adding pixRect corners
0171     polySunRise << mapToWidget(QPointF(riseTimeList.last(), dataRect().top()))
0172                 << mapToWidget(QPointF(dataRect().right(), dataRect().top()))
0173                 << mapToWidget(QPointF(dataRect().right(), dataRect().bottom()))
0174                 << mapToWidget(QPointF(riseTimeList.first(), dataRect().bottom()));
0175     polySunSet << mapToWidget(QPointF(setTimeList.last(), dataRect().top()))
0176                << mapToWidget(QPointF(dataRect().left(), pixRect().top()))
0177                << mapToWidget(QPointF(dataRect().left(), pixRect().bottom()))
0178                << mapToWidget(QPointF(setTimeList.first(), dataRect().bottom()));
0179 
0180     p->setPen(Qt::darkGreen);
0181     p->setBrush(Qt::darkGreen);
0182     p->drawPolygon(polySunRise);
0183     p->drawPolygon(polySunSet);
0184 }
0185 
0186 void CalendarWidget::drawAxes(QPainter *p)
0187 {
0188     SkyCalendar *skycal = (SkyCalendar *)topLevelWidget();
0189 
0190     p->setPen(foregroundColor());
0191     p->setBrush(Qt::NoBrush);
0192 
0193     //Draw bounding box for the plot
0194     p->drawRect(pixRect());
0195 
0196     //set small font for axis labels
0197     QFont f = p->font();
0198     int s   = f.pointSize();
0199     f.setPointSize(s - 2);
0200     p->setFont(f);
0201 
0202     // Top axis label
0203     p->drawText(0, -38, pixRect().width(), pixRect().height(), Qt::AlignHCenter | Qt::AlignTop | Qt::TextDontClip,
0204                 i18n("Local time"));
0205     // Bottom axis label
0206     p->drawText(0, 0, pixRect().width(), pixRect().height() + 35, Qt::AlignHCenter | Qt::AlignBottom | Qt::TextDontClip,
0207                 i18n("Universal time"));
0208     // Left axis label
0209     p->save();
0210     p->rotate(90.0);
0211     p->drawText(0, 0, pixRect().height(), leftPadding() - 5, Qt::AlignHCenter | Qt::AlignBottom | Qt::TextDontClip,
0212                 i18n("Month"));
0213     // Right axis label
0214     p->translate(0.0, -1 * frameRect().width() + 30);
0215     p->drawText(0, 0, pixRect().height(), leftPadding() - 5, Qt::AlignHCenter | Qt::AlignBottom | Qt::TextDontClip,
0216                 i18n("Julian date"));
0217     p->restore();
0218 
0219     //Top/Bottom axis tickmarks and time labels
0220     for (float xx = minSTime; xx <= maxRTime; xx += 1.0)
0221     {
0222         int h = int(xx);
0223         if (h < 0)
0224             h += 24;
0225         QTime time(h, 0, 0);
0226         QString sTime = QLocale().toString(time, "hh:mm");
0227 
0228         QString sUtTime = QLocale().toString(time.addSecs(skycal->get_geo()->TZ() * -3600), "hh:mm");
0229 
0230         // Draw a small tick every hours and a big tick every two hours.
0231         QPointF pBottomTick = mapToWidget(QPointF(xx, dataRect().y()));
0232         QPointF pTopTick    = QPointF(pBottomTick.x(), 0.0);
0233         if (h % 2)
0234         {
0235             // Draw small bottom tick
0236             p->drawLine(pBottomTick, QPointF(pBottomTick.x(), pBottomTick.y() - SMALLTICKSIZE));
0237             // Draw small top tick
0238             p->drawLine(pTopTick, QPointF(pTopTick.x(), pTopTick.y() + SMALLTICKSIZE));
0239         }
0240         else
0241         {
0242             // Draw big bottom tick
0243             p->drawLine(pBottomTick, QPointF(pBottomTick.x(), pBottomTick.y() - BIGTICKSIZE));
0244             QRectF r(pBottomTick.x() - BIGTICKSIZE, pBottomTick.y() + 0.5 * BIGTICKSIZE, 2 * BIGTICKSIZE, BIGTICKSIZE);
0245             p->drawText(r, Qt::AlignCenter | Qt::TextDontClip, sUtTime);
0246             // Draw big top tick
0247             p->drawLine(pTopTick, QPointF(pTopTick.x(), pTopTick.y() + BIGTICKSIZE));
0248             r.moveTop(-2.0 * BIGTICKSIZE);
0249             p->drawText(r, Qt::AlignCenter | Qt::TextDontClip, sTime);
0250         }
0251 
0252         // Vertical grid
0253         if (skycal->scUI->checkBox_GridVertical->isChecked())
0254         {
0255             QColor c = p->pen().color();
0256             c.setAlpha(100);
0257             p->setPen(c);
0258             p->drawLine(pTopTick, pBottomTick);
0259             c.setAlpha(255);
0260             p->setPen(c);
0261         }
0262     }
0263 
0264     // Month dividers
0265     int y = skycal->year();
0266     for (int imonth = 2; imonth <= 12; ++imonth)
0267     {
0268         QDate dt(y, imonth, 1);
0269         float doy = float(dt.daysInYear() - dt.dayOfYear());
0270 
0271         // Draw a tick every months on left axis
0272         QPointF pMonthTick = mapToWidget(QPointF(dataRect().x(), doy));
0273         p->drawLine(pMonthTick, QPointF(pMonthTick.x() + BIGTICKSIZE, pMonthTick.y()));
0274 
0275         // Draw month labels
0276         QRectF rMonth(mapToWidget(QPointF(0.0, float(dt.daysInYear() - dt.addMonths(-1).dayOfYear()))),
0277                       mapToWidget(QPointF(dataRect().left() - 0.1, doy)));
0278         QLocale locale;
0279         p->drawText(rMonth, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, locale.monthName(imonth - 1, QLocale::ShortFormat));
0280         if (imonth == 12) // December
0281         {
0282             rMonth = QRectF(mapToWidget(QPointF(0.0, doy)), mapToWidget(QPointF(dataRect().left() - 0.1, 0.0)));
0283             p->drawText(rMonth, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, locale.monthName(imonth, QLocale::ShortFormat));
0284         }
0285 
0286         // Draw dividers
0287         if (skycal->scUI->checkBox_GridMonths->isChecked())
0288         {
0289             QColor c = p->pen().color();
0290             c.setAlpha(100);
0291             p->setPen(c);
0292             p->drawLine(pMonthTick, QPointF(pixRect().right(), pMonthTick.y()));
0293             c.setAlpha(255);
0294             p->setPen(c);
0295         }
0296     }
0297 
0298     // Interval dividers
0299     QFont origFont = p->font();
0300     p->setFont(QFont("Monospace", origFont.pointSize() - 1));
0301     for (KStarsDateTime kdt(QDate(y, 1, 1), QTime(12, 0, 0)); kdt.date().year() == y;
0302          kdt = kdt.addDays(skycal->scUI->spinBox_Interval->value() > 7 ? skycal->scUI->spinBox_Interval->value() : 7))
0303     {
0304         // Draw ticks
0305         float doy         = float(kdt.date().daysInYear() - kdt.date().dayOfYear());
0306         QPointF pWeekTick = mapToWidget(QPointF(dataRect().right(), doy));
0307         p->drawLine(pWeekTick, QPointF(pWeekTick.x() - BIGTICKSIZE, pWeekTick.y()));
0308 
0309         // Draw julian date
0310         QRectF rJd(mapToWidget(QPointF(dataRect().right() + 0.1, doy + 2)),
0311                    mapToWidget(QPointF(dataRect().right(), doy)));
0312         p->drawText(rJd, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip,
0313                     QString().setNum(double(kdt.djd()), 'f', 1));
0314 
0315         // Draw dividers
0316         if (skycal->scUI->checkBox_GridWeeks->isChecked())
0317         {
0318             QColor c = p->pen().color();
0319             c.setAlpha(50);
0320             p->setPen(c);
0321             p->drawLine(pWeekTick, QPointF(pixRect().left(), pWeekTick.y()));
0322             c.setAlpha(255);
0323             p->setPen(c);
0324         }
0325     }
0326 
0327     // Current day
0328     if (skycal->scUI->checkBox_GridToday->isChecked())
0329     {
0330         p->setPen(QColor(Qt::yellow));
0331         QDate today = QDate::currentDate();
0332         float doy   = float(today.daysInYear() - today.dayOfYear());
0333         p->drawLine(mapToWidget(QPointF(dataRect().left(), doy)), mapToWidget(QPointF(dataRect().right(), doy)));
0334         p->drawText(mapToWidget(QPointF(dataRect().left() + 0.1, doy + 2.0)), today.toString());
0335     }
0336 
0337     //Draw month labels along each horizon curve
0338     //     if ( skycal->scUI->checkBox_LabelMonths->isChecked() ) {
0339     //         p->setFont( QFont( "Monospace", origFont.pointSize() + 5 ) );
0340     //         int textFlags = Qt::TextSingleLine | Qt::AlignCenter;
0341     //         QFontMetricsF fm( p->font(), p->device() );
0342     //
0343     //         for ( int date=0; date<dateList.size(); date++ ) {
0344     //             if ( dateList.at( date ).day() < 12 || dateList.at( date ).day() > 18 )
0345     //                 continue;
0346     //
0347     //             bool noNight = false;
0348     //             if ( riseTimeList.at( date ) < setTimeList.at( date ) )
0349     //                 noNight = true;
0350     //
0351     //             int imonth = dateList.at( date ).month();
0352     //
0353     //             QString shortMonthName = QDate::shortMonthName( dateList.at( date ).month() );
0354     //             QRectF riseLabelRect = fm.boundingRect( QRectF(0,0,1,1), textFlags, shortMonthName );
0355     //             QRectF setLabelRect = fm.boundingRect( QRectF(0,0,1,1), textFlags, shortMonthName );
0356     //
0357     //             QDate dt( y, imonth, 15 );
0358     //             float doy = float( dt.daysInYear() - dt.dayOfYear() );
0359     //             float xRiseLabel, xSetLabel;
0360     //             if ( noNight ) {
0361     //                 xRiseLabel = 0.0;
0362     //                 xSetLabel = 0.0;
0363     //             } else {
0364     //                 xRiseLabel = riseTimeList.at( date ) + 0.6;
0365     //                 xSetLabel = setTimeList.at( date )- 0.6;
0366     //             }
0367     //             QPointF pRiseLabel = mapToWidget( QPointF( xRiseLabel, doy ) );
0368     //             QPointF pSetLabel = mapToWidget( QPointF( xSetLabel, doy ) );
0369     //
0370     //             //Determine rotation angle for month labels
0371     //             QDate dt1( y, imonth, 1 );
0372     //             float doy1 = float( dt1.daysInYear() - dt1.dayOfYear() );
0373     //             QDate dt2( y, imonth, dt1.daysInMonth() );
0374     //             float doy2 = float( dt2.daysInYear() - dt2.dayOfYear() );
0375     //
0376     //             QPointF p1, p2;
0377     //             float rAngle, sAngle;
0378     //             if ( noNight ) {
0379     //                 rAngle = 90.0;
0380     //             } else {
0381     //                 p1 = mapToWidget( QPointF( riseTimeList.at( date-2 ), doy1 ) );
0382     //                 p2 = mapToWidget( QPointF( riseTimeList.at( date+2 ), doy2 ) );
0383     //                 rAngle = atan2( p2.y() - p1.y(), p2.x() - p1.x() )/dms::DegToRad;
0384     //
0385     //                 p1 = mapToWidget( QPointF( setTimeList.at( date-2 ), doy1 ) );
0386     //                 p2 = mapToWidget( QPointF( setTimeList.at( date+2 ), doy2 ) );
0387     //                 sAngle = atan2( p2.y() - p1.y(), p2.x() - p1.x() )/dms::DegToRad;
0388     //             }
0389     //
0390     //             p->save();
0391     //             p->translate( pRiseLabel );
0392     //             p->rotate( rAngle );
0393     //             p->drawText( riseLabelRect, textFlags, shortMonthName );
0394     //             p->restore();
0395     //
0396     //             if ( ! noNight ) {
0397     //                 p->save();
0398     //                 p->translate( pSetLabel );
0399     //                 p->rotate( sAngle );
0400     //                 p->drawText( setLabelRect, textFlags, shortMonthName );
0401     //                 p->restore();
0402     //             }
0403     //         }
0404     //     }
0405 
0406     p->setFont(origFont);
0407 }