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 }