File indexing completed on 2024-04-21 14:46:51

0001 /*
0002     SPDX-FileCopyrightText: 2008 Akarsh Simha <akarshsimha@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 
0007     Much of the code here is taken from Pablo de Vicente's
0008     modcalcplanets.cpp
0009 
0010 */
0011 
0012 #include "conjunctions.h"
0013 
0014 #include "geolocation.h"
0015 #include "ksconjunct.h"
0016 #include "kstars.h"
0017 #include "ksnotification.h"
0018 #include "kstarsdata.h"
0019 #include "skymap.h"
0020 #include "dialogs/finddialog.h"
0021 #include "dialogs/locationdialog.h"
0022 #include "skycomponents/skymapcomposite.h"
0023 #include "skyobjects/kscomet.h"
0024 #include "skyobjects/kspluto.h"
0025 #include "ksplanetbase.h"
0026 
0027 #include <QFileDialog>
0028 #include <QProgressDialog>
0029 #include <QStandardItemModel>
0030 #include <QtConcurrent>
0031 
0032 ConjunctionsTool::ConjunctionsTool(QWidget *parentSplit) : QFrame(parentSplit)
0033 {
0034     setupUi(this);
0035 
0036     KStarsData *kd = KStarsData::Instance();
0037     KStarsDateTime dtStart(KStarsDateTime::currentDateTime());
0038     KStarsDateTime dtStop(dtStart.djd() + 365.24); // TODO: Refine
0039 
0040     //startDate -> setDateTime( dtStart.dateTime() );
0041     //stopDate -> setDateTime( dtStop.dateTime() );
0042     //TODO Check if this change works
0043     startDate->setDateTime(dtStart);
0044     stopDate->setDateTime(dtStop);
0045 
0046     geoPlace = kd->geo();
0047     LocationButton->setText(geoPlace->fullName());
0048 
0049 
0050     pNames[KSPlanetBase::MERCURY] = i18n("Mercury");
0051     pNames[KSPlanetBase::VENUS]   = i18n("Venus");
0052     pNames[KSPlanetBase::MARS]    = i18n("Mars");
0053     pNames[KSPlanetBase::JUPITER] = i18n("Jupiter");
0054     pNames[KSPlanetBase::SATURN]  = i18n("Saturn");
0055     pNames[KSPlanetBase::URANUS]  = i18n("Uranus");
0056     pNames[KSPlanetBase::NEPTUNE] = i18n("Neptune");
0057     //pNames[KSPlanetBase::PLUTO] = i18nc("Asteroid name (optional)", "Pluto");
0058     pNames[KSPlanetBase::SUN]  = i18n("Sun");
0059     pNames[KSPlanetBase::MOON] = i18n("Moon");
0060 
0061     // Initialize the Maximum Separation box to 1 degree
0062     maxSeparationBox->setUnits(dmsBox::DEGREES);
0063     maxSeparationBox->show(1.0_deg);
0064 
0065     //FilterEdit->showClearButton = true;
0066     ClearFilterButton->setIcon(QIcon::fromTheme("edit-clear"));
0067 
0068     // signals and slots connections
0069     connect(LocationButton, SIGNAL(clicked()), this, SLOT(slotLocation()));
0070     connect(Obj1FindButton, SIGNAL(clicked()), this, SLOT(slotFindObject()));
0071 
0072     // Mode Change
0073     connect(ModeSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ConjunctionsTool::setMode);
0074 
0075     //connect(ComputeButton, SIGNAL(clicked()), this, SLOT(slotCompute()));
0076     connect(ComputeButton, &QPushButton::clicked, [this]()
0077     {
0078         QtConcurrent::run(this, &ConjunctionsTool::slotCompute);
0079     });
0080     connect(FilterTypeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(slotFilterType(int)));
0081     connect(ClearButton, SIGNAL(clicked()), this, SLOT(slotClear()));
0082     connect(ExportButton, SIGNAL(clicked()), this, SLOT(slotExport()));
0083     connect(OutputList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotGoto()));
0084     connect(ClearFilterButton, SIGNAL(clicked()), FilterEdit, SLOT(clear()));
0085     connect(FilterEdit, SIGNAL(textChanged(QString)), this, SLOT(slotFilterReg(QString)));
0086 
0087     m_Model = new QStandardItemModel(0, 5, this);
0088 
0089     setMode(ModeSelector->currentIndex());
0090 
0091     // Init filter type combobox
0092     FilterTypeComboBox->clear();
0093     FilterTypeComboBox->addItem(i18n("Single Object"));
0094     FilterTypeComboBox->addItem(i18n("Any"));
0095     FilterTypeComboBox->addItem(i18n("Stars"));
0096     FilterTypeComboBox->addItem(i18n("Solar System"));
0097     FilterTypeComboBox->addItem(i18n("Planets"));
0098     FilterTypeComboBox->addItem(i18n("Comets"));
0099     FilterTypeComboBox->addItem(i18n("Asteroids"));
0100     FilterTypeComboBox->addItem(i18n("Open Clusters"));
0101     FilterTypeComboBox->addItem(i18n("Globular Clusters"));
0102     FilterTypeComboBox->addItem(i18n("Gaseous Nebulae"));
0103     FilterTypeComboBox->addItem(i18n("Planetary Nebulae"));
0104     FilterTypeComboBox->addItem(i18n("Galaxies"));
0105 
0106     Obj2ComboBox->clear();
0107     for (int i = 0; i < KSPlanetBase::UNKNOWN_PLANET; ++i)
0108     {
0109         //      Obj1ComboBox->insertItem( i, pNames[i] );
0110         Obj2ComboBox->insertItem(i, pNames[i]);
0111     }
0112 
0113     maxSeparationBox->setEnabled(true);
0114 
0115     //Set up the Table Views
0116     m_Model->setHorizontalHeaderLabels(QStringList() << i18n("Conjunction/Opposition") << i18n("Date & Time (UT)")
0117                                        << i18n("Object 1") << i18n("Object 2") << i18n("Separation"));
0118     m_SortModel = new QSortFilterProxyModel(this);
0119     m_SortModel->setSourceModel(m_Model);
0120     OutputList->setModel(m_SortModel);
0121     OutputList->setSortingEnabled(true);
0122     OutputList->horizontalHeader()->setStretchLastSection(true);
0123     OutputList->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
0124     OutputList->horizontalHeader()->resizeSection(2, 100);
0125     OutputList->horizontalHeader()->resizeSection(3, 100);
0126     OutputList->horizontalHeader()->resizeSection(4, 120); //is it bad way to fix default size of columns ?
0127 
0128     show();
0129 }
0130 
0131 void ConjunctionsTool::slotGoto()
0132 {
0133     int index      = m_SortModel->mapToSource(OutputList->currentIndex()).row(); // Get the number of the line
0134     long double jd = outputJDList.value(index);
0135     KStarsDateTime dt;
0136     KStars *ks       = KStars::Instance();
0137     KStarsData *data = KStarsData::Instance();
0138     SkyMap *map      = ks->map();
0139 
0140     // Show conjunction
0141     data->setLocation(*geoPlace);
0142     dt.setDJD(jd);
0143     data->changeDateTime(dt);
0144     map->setClickedObject(data->skyComposite()->findByName(m_Model->data(m_Model->index(index, 2)).toString()));
0145     map->setClickedPoint(map->clickedObject());
0146     map->slotCenter();
0147 }
0148 
0149 void ConjunctionsTool::slotFindObject()
0150 {
0151     if (FindDialog::Instance()->exec() == QDialog::Accepted)
0152     {
0153         if (!FindDialog::Instance()->targetObject())
0154             return;
0155         Object1 = SkyObject_s(FindDialog::Instance()->targetObject()->clone());
0156         if (Object1 != nullptr)
0157             Obj1FindButton->setText(Object1->name());
0158     }
0159 }
0160 
0161 void ConjunctionsTool::setMode(int new_mode)
0162 {
0163     // unlikely to happen
0164     if(new_mode == -1 || new_mode > 2)
0165     {
0166         ModeSelector->setCurrentIndex(0);
0167         return;
0168     }
0169 
0170     mode = static_cast<MODE>(new_mode);
0171 }
0172 
0173 void ConjunctionsTool::slotLocation()
0174 {
0175     QPointer<LocationDialog> ld(new LocationDialog(this));
0176     if (ld->exec() == QDialog::Accepted && ld)
0177     {
0178         geoPlace = ld->selectedCity();
0179         LocationButton->setText(geoPlace->fullName());
0180     }
0181     delete ld;
0182 }
0183 
0184 void ConjunctionsTool::slotFilterType(int)
0185 {
0186     // Disable find button if the user select an object type
0187     if (FilterTypeComboBox->currentIndex() == 0)
0188         Obj1FindButton->setEnabled(true);
0189     else
0190         Obj1FindButton->setEnabled(false);
0191 }
0192 
0193 void ConjunctionsTool::slotClear()
0194 {
0195     m_Model->setRowCount(0);
0196     outputJDList.clear();
0197     m_index = 0;
0198 }
0199 
0200 void ConjunctionsTool::slotExport()
0201 {
0202     int i, j;
0203     QByteArray line;
0204 
0205     //QFile file( KFileDialog::getSaveFileName( QDir::homePath(), "*|All files", this, "Save Conjunctions" ) );
0206     QFile file(QFileDialog::getSaveFileName(nullptr, i18nc("@title:window", "Save Conjunctions"), QDir::homePath(), "*|All files"));
0207 
0208     file.open(QIODevice::WriteOnly | QIODevice::Text);
0209 
0210     for (i = 0; i < m_Model->rowCount(); ++i)
0211     {
0212         for (j = 0; j < m_Model->columnCount(); ++j)
0213         {
0214             line.append(m_Model->data(m_Model->index(i, j)).toByteArray());
0215             if (j < m_Model->columnCount() - 1)
0216                 line.append(";");
0217             else
0218                 line.append("\n");
0219         }
0220         file.write(line);
0221         line.clear();
0222     }
0223 
0224     file.close();
0225 }
0226 
0227 void ConjunctionsTool::slotFilterReg(const QString &filter)
0228 {
0229     m_SortModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::RegExp));
0230     m_SortModel->setFilterKeyColumn(-1);
0231 }
0232 
0233 void ConjunctionsTool::slotCompute(void)
0234 {
0235     KStarsDateTime dtStart(startDate->dateTime()); // Start date
0236     KStarsDateTime dtStop(stopDate->dateTime());  // Stop date
0237     long double startJD    = dtStart.djd();         // Start julian day
0238     long double stopJD     = dtStop.djd();          // Stop julian day
0239     bool opposition        = false;                 // true=opposition, false=conjunction
0240     if (mode == OPPOSITION)
0241         opposition = true;
0242     QStringList objects; // List of sky object used as Object1
0243     KStarsData *data = KStarsData::Instance();
0244     int progress     = 0;
0245 
0246     // Check if we have a valid angle in maxSeparationBox
0247     dms maxSeparation(0.0);
0248     bool ok;
0249     maxSeparation = maxSeparationBox->createDms(&ok);
0250 
0251     if (!ok)
0252     {
0253         KSNotification::sorry(i18n("Maximum separation entered is not a valid angle. Use the What's this help feature "
0254                                    "for information on how to enter a valid angle"));
0255         return;
0256     }
0257 
0258     // Check if Object1 and Object2 are set
0259     if (FilterTypeComboBox->currentIndex() == 0 && Object1 == nullptr)
0260     {
0261         KSNotification::sorry(i18n("Please select an object to check conjunctions with, by clicking on the \'Find Object\' button."));
0262         return;
0263     }
0264     Object2.reset(KSPlanetBase::createPlanet(Obj2ComboBox->currentIndex()));
0265     if (FilterTypeComboBox->currentIndex() == 0 && Object1->name() == Object2->name())
0266     {
0267         // FIXME: Must free the created Objects
0268         KSNotification::sorry(i18n("Please select two different objects to check conjunctions with."));
0269         return;
0270     }
0271 
0272     // Init KSConjunct object
0273     KSConjunct ksc;
0274     connect(&ksc, SIGNAL(madeProgress(int)), this, SLOT(showProgress(int)));
0275     ksc.setGeoLocation(geoPlace);
0276 
0277     switch (FilterTypeComboBox->currentIndex())
0278     {
0279         case 1: // All object types
0280             foreach (int type, data->skyComposite()->objectNames().keys())
0281                 objects += data->skyComposite()->objectNames(type);
0282             break;
0283         case 2: // Stars
0284             objects += data->skyComposite()->objectNames(SkyObject::STAR);
0285             objects += data->skyComposite()->objectNames(SkyObject::CATALOG_STAR);
0286             break;
0287         case 3: // Solar system
0288             objects += data->skyComposite()->objectNames(SkyObject::PLANET);
0289             objects += data->skyComposite()->objectNames(SkyObject::COMET);
0290             objects += data->skyComposite()->objectNames(SkyObject::ASTEROID);
0291             objects += data->skyComposite()->objectNames(SkyObject::MOON);
0292             objects += i18n("Sun");
0293             // Remove Object2  planet
0294             objects.removeAll(Object2->name());
0295             break;
0296         case 4: // Planet
0297             objects += data->skyComposite()->objectNames(SkyObject::PLANET);
0298             // Remove Object2  planet
0299             objects.removeAll(Object2->name());
0300             break;
0301         case 5: // Comet
0302             objects += data->skyComposite()->objectNames(SkyObject::COMET);
0303             break;
0304         case 6: // Asteroid
0305             objects += data->skyComposite()->objectNames(SkyObject::ASTEROID);
0306             break;
0307         case 7: // Open Clusters
0308             objects = data->skyComposite()->objectNames(SkyObject::OPEN_CLUSTER);
0309             break;
0310         case 8: // Open Clusters
0311             objects = data->skyComposite()->objectNames(SkyObject::GLOBULAR_CLUSTER);
0312             break;
0313         case 9: // Gaseous nebulae
0314             objects = data->skyComposite()->objectNames(SkyObject::GASEOUS_NEBULA);
0315             break;
0316         case 10: // Planetary nebula
0317             objects = data->skyComposite()->objectNames(SkyObject::PLANETARY_NEBULA);
0318             break;
0319         case 11: // Galaxies
0320             objects = data->skyComposite()->objectNames(SkyObject::GALAXY);
0321             break;
0322     }
0323 
0324     // Remove all Jupiter and Saturn moons
0325     // KStars crash if we compute a conjunction between a planet and one of this moon
0326     if (FilterTypeComboBox->currentIndex() == 1 || FilterTypeComboBox->currentIndex() == 3 ||
0327             FilterTypeComboBox->currentIndex() == 6)
0328     {
0329         objects.removeAll("Io");
0330         objects.removeAll("Europa");
0331         objects.removeAll("Ganymede");
0332         objects.removeAll("Callisto");
0333         objects.removeAll("Mimas");
0334         objects.removeAll("Enceladus");
0335         objects.removeAll("Tethys");
0336         objects.removeAll("Dione");
0337         objects.removeAll("Rhea");
0338         objects.removeAll("Titan");
0339         objects.removeAll("Hyperion");
0340         objects.removeAll("Iapetus");
0341     }
0342 
0343     ksc.setMaxSeparation(maxSeparation);
0344     ksc.setObject2(Object2);
0345     ksc.setOpposition(opposition);
0346 
0347     if (FilterTypeComboBox->currentIndex() != 0)
0348     {
0349         // Show a progress dialog while processing
0350         QProgressDialog progressDlg(i18n("Compute conjunction..."), i18n("Abort"), 0, objects.count(), this);
0351         progressDlg.setWindowTitle(i18nc("@title:window", "Conjunction"));
0352         progressDlg.setWindowModality(Qt::WindowModal);
0353         progressDlg.setValue(0);
0354 
0355         for (auto &object : objects)
0356         {
0357             // If the user click on the 'cancel' button
0358             if (progressDlg.wasCanceled())
0359                 break;
0360 
0361             // Update progress dialog
0362             ++progress;
0363             progressDlg.setValue(progress);
0364             progressDlg.setLabelText(i18n("Compute conjunction between %1 and %2", Object2->name(), object));
0365 
0366             // Compute conjuction
0367             Object1 = std::shared_ptr<SkyObject>(data->skyComposite()->findByName(object)->clone());
0368             ksc.setObject1(Object1);
0369             showConjunctions(ksc.findClosestApproach(startJD, stopJD),
0370                              object, Object2->name());
0371         }
0372 
0373         progressDlg.setValue(objects.count());
0374     }
0375     else
0376     {
0377         // Change cursor while we search for conjunction
0378         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
0379 
0380         ComputeStack->setCurrentIndex(1);
0381 
0382         ksc.setObject1(Object1);
0383         showConjunctions(ksc.findClosestApproach(startJD, stopJD),
0384                          Object1->name(), Object2->name());
0385         ComputeStack->setCurrentIndex(0);
0386 
0387         // Restore cursor
0388         QApplication::restoreOverrideCursor();
0389     }
0390 
0391     Object2.reset();
0392 }
0393 
0394 void ConjunctionsTool::showProgress(int n)
0395 {
0396     progress->setValue(n);
0397 }
0398 
0399 void ConjunctionsTool::showConjunctions(const QMap<long double, dms> &conjunctionlist, const QString &object1,
0400                                         const QString &object2)
0401 {
0402     KStarsDateTime dt;
0403     QList<QStandardItem *> itemList;
0404 
0405     for (auto it = conjunctionlist.constBegin(); it != conjunctionlist.constEnd(); ++it)
0406     {
0407         dt.setDJD(it.key());
0408         QStandardItem *typeItem;
0409 
0410         if (mode == CONJUNCTION)
0411             typeItem = new QStandardItem(i18n("Conjunction"));
0412         else
0413             typeItem = new QStandardItem(i18n("Opposition"));
0414 
0415         itemList << typeItem
0416                  //FIXME TODO is this ISO date? is there a ready format to use?
0417                  //<< new QStandardItem( QLocale().toString( dt.dateTime(), "YYYY-MM-DDTHH:mm:SS" ) )
0418                  //<< new QStandardItem( QLocale().toString( dt, Qt::ISODate) )
0419                  << new QStandardItem(dt.toString(Qt::ISODate)) << new QStandardItem(object1)
0420                  << new QStandardItem(object2) << new QStandardItem(it.value().toDMSString());
0421         m_Model->appendRow(itemList);
0422         itemList.clear();
0423 
0424         outputJDList.insert(m_index, it.key());
0425         ++m_index;
0426     }
0427 }
0428 
0429 void ConjunctionsTool::setUpConjunctionOpposition()
0430 {
0431 
0432 }