File indexing completed on 2024-04-14 03:43:26

0001 /*
0002     SPDX-FileCopyrightText: 2008 Jason Harris <kstars@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "skycalendar.h"
0008 
0009 #include "geolocation.h"
0010 #include "ksplanetbase.h"
0011 #include "kstarsdata.h"
0012 #include "dialogs/locationdialog.h"
0013 #include "skycomponents/skymapcomposite.h"
0014 
0015 #include <KPlotObject>
0016 
0017 #include <QPainter>
0018 #include <QPixmap>
0019 #include <QPrintDialog>
0020 #include <QPrinter>
0021 #include <QPushButton>
0022 #include <QScreen>
0023 #include <QtConcurrent>
0024 
0025 SkyCalendarUI::SkyCalendarUI(QWidget *parent) : QFrame(parent)
0026 {
0027     setupUi(this);
0028 }
0029 
0030 SkyCalendar::SkyCalendar(QWidget *parent) : QDialog(parent)
0031 {
0032 #ifdef Q_OS_OSX
0033     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0034 #endif
0035 
0036     scUI = new SkyCalendarUI(this);
0037 
0038     QVBoxLayout *mainLayout = new QVBoxLayout;
0039 
0040     mainLayout->addWidget(scUI);
0041     setLayout(mainLayout);
0042 
0043     geo = KStarsData::Instance()->geo();
0044 
0045     setWindowTitle(i18nc("@title:window", "Sky Calendar"));
0046 
0047     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0048     mainLayout->addWidget(buttonBox);
0049     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
0050 
0051     QPushButton *printB = new QPushButton(QIcon::fromTheme("document-print"), i18n("&Print..."));
0052     printB->setToolTip(i18n("Print the Sky Calendar"));
0053     buttonBox->addButton(printB, QDialogButtonBox::ActionRole);
0054     connect(printB, SIGNAL(clicked()), this, SLOT(slotPrint()));
0055 
0056     setModal(false);
0057 
0058     //Adjust minimum size for small screens:
0059     if (QGuiApplication::primaryScreen()->geometry().height() <= scUI->CalendarView->height())
0060     {
0061         scUI->CalendarView->setMinimumSize(400, 600);
0062     }
0063 
0064     scUI->CalendarView->setShowGrid(false);
0065     scUI->Year->setValue(KStarsData::Instance()->lt().date().year());
0066 
0067     scUI->LocationButton->setText(geo->fullName());
0068 
0069     scUI->CalendarView->setHorizon();
0070 
0071     plotButtonText = scUI->CreateButton->text();
0072     connect(scUI->CreateButton, &QPushButton::clicked, this, [this]()
0073     {
0074         scUI->CreateButton->setText(i18n("Please Wait") + "...");
0075         slotFillCalendar();
0076     });
0077 
0078     connect(scUI->LocationButton, SIGNAL(clicked()), this, SLOT(slotLocation()));
0079 }
0080 
0081 int SkyCalendar::year()
0082 {
0083     return scUI->Year->value();
0084 }
0085 
0086 void SkyCalendar::slotFillCalendar()
0087 {
0088     scUI->CreateButton->setEnabled(false);
0089 
0090     scUI->CalendarView->resetPlot();
0091     scUI->CalendarView->setHorizon();
0092 
0093     if (scUI->checkBox_Mercury->isChecked())
0094         QtConcurrent::run(this, &SkyCalendar::addPlanetEvents, KSPlanetBase::MERCURY);
0095     if (scUI->checkBox_Venus->isChecked())
0096         QtConcurrent::run(this, &SkyCalendar::addPlanetEvents, KSPlanetBase::VENUS);
0097     if (scUI->checkBox_Mars->isChecked())
0098         QtConcurrent::run(this, &SkyCalendar::addPlanetEvents, KSPlanetBase::MARS);
0099     if (scUI->checkBox_Jupiter->isChecked())
0100         QtConcurrent::run(this, &SkyCalendar::addPlanetEvents, KSPlanetBase::JUPITER);
0101     if (scUI->checkBox_Saturn->isChecked())
0102         QtConcurrent::run(this, &SkyCalendar::addPlanetEvents, KSPlanetBase::SATURN);
0103     if (scUI->checkBox_Uranus->isChecked())
0104         QtConcurrent::run(this, &SkyCalendar::addPlanetEvents, KSPlanetBase::URANUS);
0105     if (scUI->checkBox_Neptune->isChecked())
0106         QtConcurrent::run(this, &SkyCalendar::addPlanetEvents, KSPlanetBase::NEPTUNE);
0107 
0108     scUI->CreateButton->setText(i18n("Plot Planetary Almanac"));
0109     scUI->CreateButton->setEnabled(true);
0110 }
0111 
0112 #if 0
0113 void SkyCalendar::slotCalculating()
0114 {
0115     while (calculating)
0116     {
0117         //QMutexLocker locker(&calculationMutex);
0118 
0119         if (!calculating)
0120             continue;
0121 
0122         scUI->CreateButton->setText(i18n("Please Wait") + ".  ");
0123         scUI->CreateButton->repaint();
0124         QThread::msleep(200);
0125         scUI->CreateButton->setText(i18n("Please Wait") + ".. ");
0126         scUI->CreateButton->repaint();
0127         QThread::msleep(200);
0128 
0129         scUI->CreateButton->repaint();
0130         QThread::msleep(200);
0131     }
0132     scUI->CreateButton->setText(plotButtonText);
0133 }
0134 #endif
0135 
0136 // FIXME: For the time being, adjust with dirty, cluttering labels that don't align to the line
0137 /*
0138 void SkyCalendar::drawEventLabel( float x1, float y1, float x2, float y2, QString LabelText ) {
0139     QFont origFont = p->font();
0140     p->setFont( QFont( "Bitstream Vera", 10 ) );
0141 
0142     int textFlags = Qt::AlignCenter; // TODO: See if Qt::SingleLine flag works better
0143     QFontMetricsF fm( p->font(), p->device() );
0144 
0145     QRectF LabelRect = fm.boundingRect( QRectF(0,0,1,1), textFlags, LabelText );
0146     QPointF LabelPoint = scUI->CalendarView->mapToWidget( QPointF( x, y ) );
0147 
0148     float LabelAngle = atan2( y2 - y1, x2 - x1 )/dms::DegToRad;
0149 
0150     p->save();
0151     p->translate( LabelPoint );
0152     p->rotate( LabelAngle );
0153     p->drawText( LabelRect, textFlags, LabelText );
0154     p->restore();
0155 
0156     p->setFont( origFont );
0157 }
0158 */
0159 
0160 void SkyCalendar::addPlanetEvents(int nPlanet)
0161 {
0162     KSPlanetBase *ksp = KStarsData::Instance()->skyComposite()->planet(nPlanet);
0163     QColor pColor     = ksp->color();
0164     //QVector<QPointF> vRise, vSet, vTransit;
0165     std::vector<QPointF> vRise, vSet, vTransit;
0166 
0167     for (KStarsDateTime kdt(QDate(year(), 1, 1), QTime(12, 0, 0)); kdt.date().year() == year();
0168             kdt = kdt.addDays(scUI->spinBox_Interval->value()))
0169     {
0170         float rTime, sTime, tTime;
0171 
0172         //Compute rise/set/transit times.  If they occur before noon,
0173         //recompute for the following day
0174         QTime tmp_rTime = ksp->riseSetTime(kdt, geo, true, true);  //rise time, exact
0175         QTime tmp_sTime = ksp->riseSetTime(kdt, geo, false, true); //set time, exact
0176         QTime tmp_tTime = ksp->transitTime(kdt, geo);
0177         QTime midday(12, 0, 0);
0178 
0179         // NOTE: riseSetTime should be fix now, this test is no longer necessary
0180         if (tmp_rTime == tmp_sTime)
0181         {
0182             tmp_rTime = QTime();
0183             tmp_sTime = QTime();
0184         }
0185 
0186         if (tmp_rTime.isValid() && tmp_sTime.isValid())
0187         {
0188             rTime = tmp_rTime.secsTo(midday) * 24.0 / 86400.0;
0189             sTime = tmp_sTime.secsTo(midday) * 24.0 / 86400.0;
0190 
0191             if (tmp_rTime <= midday)
0192                 rTime = 12.0 - rTime;
0193             else
0194                 rTime = -12.0 - rTime;
0195 
0196             if (tmp_sTime <= midday)
0197                 sTime = 12.0 - sTime;
0198             else
0199                 sTime = -12.0 - sTime;
0200         }
0201         else
0202         {
0203             if (ksp->transitAltitude(kdt, geo).degree() > 0)
0204             {
0205                 rTime = -24.0;
0206                 sTime = 24.0;
0207             }
0208             else
0209             {
0210                 rTime = 24.0;
0211                 sTime = -24.0;
0212             }
0213         }
0214 
0215         tTime = tmp_tTime.secsTo(midday) * 24.0 / 86400.0;
0216         if (tmp_tTime <= midday)
0217             tTime = 12.0 - tTime;
0218         else
0219             tTime = -12.0 - tTime;
0220 
0221         float dy = kdt.date().daysInYear() - kdt.date().dayOfYear();
0222         vRise.push_back(QPointF(rTime, dy));
0223         vSet.push_back(QPointF(sTime, dy));
0224         vTransit.push_back(QPointF(tTime, dy));
0225     }
0226 
0227     //Now, find continuous segments in each QVector and add each segment
0228     //as a separate KPlotObject
0229 
0230     KPlotObject *oRise    = new KPlotObject(pColor, KPlotObject::Lines, 2.0);
0231     KPlotObject *oSet     = new KPlotObject(pColor, KPlotObject::Lines, 2.0);
0232     KPlotObject *oTransit = new KPlotObject(pColor, KPlotObject::Lines, 2.0);
0233 
0234     float defaultSetTime  = -10.0;
0235     float defaultRiseTime = 8.0;
0236     QString label;
0237     bool initialRise       = true;
0238     bool initialSet        = true;
0239     bool initialTransit    = true;
0240     bool extraCheckRise    = true;
0241     bool extraCheckSet     = true;
0242     bool extraCheckTransit = true;
0243     bool needRiseLabel     = false;
0244     bool needSetLabel      = false;
0245     bool needTransertLabel = false;
0246 
0247     float maxRiseTime = 0.0;
0248     for (auto &item : vRise)
0249     {
0250         if (item.x() >= maxRiseTime)
0251             maxRiseTime = item.x();
0252     }
0253     maxRiseTime = qFloor(maxRiseTime) - 1.0;
0254 
0255     for (uint32_t i = 0; i < vRise.size(); ++i)
0256     {
0257         if (initialRise && (vRise.at(i).x() > defaultSetTime && vRise.at(i).x() < defaultRiseTime))
0258         {
0259             needRiseLabel = true;
0260             initialRise   = false;
0261         }
0262         else if (vRise.at(i).x() < defaultSetTime || vRise.at(i).x() > defaultRiseTime)
0263         {
0264             initialRise   = true;
0265             needRiseLabel = false;
0266         }
0267         else
0268             needRiseLabel = false;
0269 
0270         if (extraCheckRise && vRise.at(i).x() > defaultRiseTime && vRise.at(i).x() < maxRiseTime)
0271         {
0272             needRiseLabel  = true;
0273             extraCheckRise = false;
0274         }
0275 
0276         if (initialSet && (vSet.at(i).x() > defaultSetTime && vSet.at(i).x() < defaultRiseTime))
0277         {
0278             needSetLabel = true;
0279             initialSet   = false;
0280         }
0281         else if (vSet.at(i).x() < defaultSetTime || vSet.at(i).x() > defaultRiseTime)
0282         {
0283             initialSet   = true;
0284             needSetLabel = false;
0285         }
0286         else
0287             needSetLabel = false;
0288 
0289         if (extraCheckSet && vSet.at(i).x() > defaultRiseTime && vSet.at(i).x() < maxRiseTime)
0290         {
0291             needSetLabel  = true;
0292             extraCheckSet = false;
0293         }
0294 
0295         if (initialTransit && (vTransit.at(i).x() > defaultSetTime && vTransit.at(i).x() < defaultRiseTime))
0296         {
0297             needTransertLabel = true;
0298             initialTransit    = false;
0299         }
0300         else if (vTransit.at(i).x() < defaultSetTime || vTransit.at(i).x() > defaultRiseTime)
0301         {
0302             initialTransit    = true;
0303             needTransertLabel = false;
0304             ;
0305         }
0306         else
0307             needTransertLabel = false;
0308 
0309         if (extraCheckTransit && vTransit.at(i).x() > defaultRiseTime && vTransit.at(i).x() < maxRiseTime)
0310         {
0311             needTransertLabel = true;
0312             extraCheckTransit = false;
0313         }
0314 
0315         if (vRise.at(i).x() > -23.0 && vRise.at(i).x() < 23.0)
0316         {
0317             if (i > 0 && fabs(vRise.at(i).x() - vRise.at(i - 1).x()) > 6.0)
0318             {
0319                 scUI->CalendarView->addPlotObject(oRise);
0320                 oRise          = new KPlotObject(pColor, KPlotObject::Lines, 2.0);
0321                 extraCheckRise = true;
0322             }
0323 
0324             if (needRiseLabel)
0325                 label = i18nc("A planet rises from the horizon", "%1 rises", ksp->name());
0326             else
0327                 label = QString();
0328             // Add the current point to KPlotObject
0329             oRise->addPoint(vRise.at(i), label);
0330         }
0331         else
0332         {
0333             scUI->CalendarView->addPlotObject(oRise);
0334             oRise = new KPlotObject(pColor, KPlotObject::Lines, 2.0);
0335         }
0336 
0337         if (vSet.at(i).x() > -23.0 && vSet.at(i).x() < 23.0)
0338         {
0339             if (i > 0 && fabs(vSet.at(i).x() - vSet.at(i - 1).x()) > 6.0)
0340             {
0341                 scUI->CalendarView->addPlotObject(oSet);
0342                 oSet          = new KPlotObject(pColor, KPlotObject::Lines, 2.0);
0343                 extraCheckSet = true;
0344             }
0345 
0346             if (needSetLabel)
0347                 label = i18nc("A planet sets from the horizon", "%1 sets", ksp->name());
0348             else
0349                 label = QString();
0350 
0351             oSet->addPoint(vSet.at(i), label);
0352         }
0353         else
0354         {
0355             scUI->CalendarView->addPlotObject(oSet);
0356             oSet = new KPlotObject(pColor, KPlotObject::Lines, 2.0);
0357         }
0358 
0359         if (vTransit.at(i).x() > -23.0 && vTransit.at(i).x() < 23.0)
0360         {
0361             if (i > 0 && fabs(vTransit.at(i).x() - vTransit.at(i - 1).x()) > 6.0)
0362             {
0363                 scUI->CalendarView->addPlotObject(oTransit);
0364                 oTransit          = new KPlotObject(pColor, KPlotObject::Lines, 2.0);
0365                 extraCheckTransit = true;
0366             }
0367 
0368             if (needTransertLabel)
0369                 label = i18nc("A planet transits across the meridian", "%1 transits", ksp->name());
0370             else
0371                 label = QString();
0372 
0373             oTransit->addPoint(vTransit.at(i), label);
0374         }
0375         else
0376         {
0377             scUI->CalendarView->addPlotObject(oTransit);
0378             oTransit = new KPlotObject(pColor, KPlotObject::Lines, 2.0);
0379         }
0380     }
0381 
0382     // Add the last points
0383     scUI->CalendarView->addPlotObject(oRise);
0384     scUI->CalendarView->addPlotObject(oSet);
0385     scUI->CalendarView->addPlotObject(oTransit);
0386 }
0387 
0388 void SkyCalendar::slotPrint()
0389 {
0390     QPainter p;             // Our painter object
0391     QPrinter printer;       // Our printer object
0392     QString str_legend;     // Text legend
0393     QString str_year;       // Calendar's year
0394     int text_height = 200;  // Height of legend text zone in points
0395     QSize calendar_size;    // Initial calendar widget size
0396     QFont calendar_font;    // Initial calendar font
0397     int calendar_font_size; // Initial calendar font size
0398 
0399     // Set printer resolution to 300 dpi
0400     printer.setResolution(300);
0401 
0402     // Open print dialog
0403     //NOTE Changed from pointer to statically allocated object, what effect will it have?
0404     //QPointer<QPrintDialog> dialog( KdePrint::createPrintDialog( &printer, this ) );
0405     QPrintDialog dialog(&printer, this);
0406     dialog.setWindowTitle(i18nc("@title:window", "Print sky calendar"));
0407     if (dialog.exec() == QDialog::Accepted)
0408     {
0409         // Change mouse cursor
0410         QApplication::setOverrideCursor(Qt::WaitCursor);
0411 
0412         // Save calendar widget font
0413         calendar_font = scUI->CalendarView->font();
0414         // Save calendar widget font size
0415         calendar_font_size = calendar_font.pointSize();
0416         // Save calendar widget size
0417         calendar_size = scUI->CalendarView->size();
0418 
0419         // Set text legend
0420         str_year.setNum(year());
0421         str_legend = i18n("Sky Calendar");
0422         str_legend += '\n';
0423         str_legend += geo->fullName();
0424         str_legend += " - ";
0425         str_legend += str_year;
0426 
0427         // Create a rectangle for legend text zone
0428         QRect text_rect(0, 0, printer.width(), text_height);
0429 
0430         // Increase calendar widget font size so it looks good in 300 dpi
0431         calendar_font.setPointSize(calendar_font_size * 3);
0432         scUI->CalendarView->setFont(calendar_font);
0433         // Increase calendar widget size to fit the entire page
0434         scUI->CalendarView->resize(printer.width(), printer.height() - text_height);
0435 
0436         // Create a pixmap and render calendar widget into it
0437         QPixmap pixmap(scUI->CalendarView->size());
0438         scUI->CalendarView->render(&pixmap);
0439 
0440         // Begin painting on printer
0441         p.begin(&printer);
0442         // Draw legend
0443         p.drawText(text_rect, Qt::AlignLeft, str_legend);
0444         // Draw calendar
0445         p.drawPixmap(0, text_height, pixmap);
0446         // Ending painting
0447         p.end();
0448 
0449         // Restore calendar widget font size
0450         calendar_font.setPointSize(calendar_font_size);
0451         scUI->CalendarView->setFont(calendar_font);
0452         // Restore calendar widget size
0453         scUI->CalendarView->resize(calendar_size);
0454 
0455         // Restore mouse cursor
0456         QApplication::restoreOverrideCursor();
0457     }
0458     //delete dialog;
0459 }
0460 
0461 void SkyCalendar::slotLocation()
0462 {
0463     QPointer<LocationDialog> ld = new LocationDialog(this);
0464     if (ld->exec() == QDialog::Accepted)
0465     {
0466         GeoLocation *newGeo = ld->selectedCity();
0467         if (newGeo)
0468         {
0469             geo = newGeo;
0470             scUI->LocationButton->setText(geo->fullName());
0471         }
0472     }
0473     delete ld;
0474 
0475     scUI->CalendarView->setHorizon();
0476     slotFillCalendar();
0477 }
0478 
0479 GeoLocation *SkyCalendar::get_geo()
0480 {
0481     return geo;
0482 }