File indexing completed on 2024-04-28 15:09:03

0001 /*  Ekos Mount MOdel
0002     SPDX-FileCopyrightText: 2018 Robert Lancaster
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "mountmodel.h"
0008 
0009 #include "align.h"
0010 #include "kstars.h"
0011 #include "kstarsdata.h"
0012 #include "flagcomponent.h"
0013 #include "ksnotification.h"
0014 
0015 #include "skymap.h"
0016 #include "starobject.h"
0017 #include "skymapcomposite.h"
0018 #include "skyobject.h"
0019 #include "starobject.h"
0020 #include "dialogs/finddialog.h"
0021 #include "QProgressIndicator.h"
0022 
0023 #include <ekos_align_debug.h>
0024 
0025 #define AL_FORMAT_VERSION 1.0
0026 
0027 // Qt version calming
0028 #include <qtendl.h>
0029 
0030 namespace Ekos
0031 {
0032 MountModel::MountModel(Align *parent) : QDialog(parent)
0033 {
0034     setupUi(this);
0035 
0036     m_AlignInstance = parent;
0037 
0038     setWindowTitle("Mount Model Tool");
0039     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0040     alignTable->setColumnWidth(0, 70);
0041     alignTable->setColumnWidth(1, 75);
0042     alignTable->setColumnWidth(2, 130);
0043     alignTable->setColumnWidth(3, 30);
0044 
0045     wizardAlignB->setIcon(
0046         QIcon::fromTheme("tools-wizard"));
0047     wizardAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0048 
0049     clearAllAlignB->setIcon(
0050         QIcon::fromTheme("application-exit"));
0051     clearAllAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0052 
0053     removeAlignB->setIcon(QIcon::fromTheme("list-remove"));
0054     removeAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0055 
0056     addAlignB->setIcon(QIcon::fromTheme("list-add"));
0057     addAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0058 
0059     findAlignB->setIcon(QIcon::fromTheme("edit-find"));
0060     findAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0061 
0062     alignTable->verticalHeader()->setDragDropOverwriteMode(false);
0063     alignTable->verticalHeader()->setSectionsMovable(true);
0064     alignTable->verticalHeader()->setDragEnabled(true);
0065     alignTable->verticalHeader()->setDragDropMode(QAbstractItemView::InternalMove);
0066     connect(alignTable->verticalHeader(), SIGNAL(sectionMoved(int, int, int)), this,
0067             SLOT(moveAlignPoint(int, int, int)));
0068 
0069     loadAlignB->setIcon(
0070         QIcon::fromTheme("document-open"));
0071     loadAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0072 
0073     saveAlignB->setIcon(
0074         QIcon::fromTheme("document-save"));
0075     saveAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0076 
0077     previewB->setIcon(QIcon::fromTheme("kstars_grid"));
0078     previewB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0079     previewB->setCheckable(true);
0080 
0081     sortAlignB->setIcon(QIcon::fromTheme("svn-update"));
0082     sortAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0083 
0084     stopAlignB->setIcon(
0085         QIcon::fromTheme("media-playback-stop"));
0086     stopAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0087 
0088     startAlignB->setIcon(
0089         QIcon::fromTheme("media-playback-start"));
0090     startAlignB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
0091 
0092     connect(wizardAlignB, &QPushButton::clicked, this, &Ekos::MountModel::slotWizardAlignmentPoints);
0093     connect(alignTypeBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0094             &Ekos::MountModel::alignTypeChanged);
0095 
0096     connect(starListBox, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged), this,
0097             &Ekos::MountModel::slotStarSelected);
0098     connect(greekStarListBox, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
0099             this,
0100             &Ekos::MountModel::slotStarSelected);
0101 
0102     connect(loadAlignB, &QPushButton::clicked, this, &Ekos::MountModel::slotLoadAlignmentPoints);
0103     connect(saveAlignB, &QPushButton::clicked, this, &Ekos::MountModel::slotSaveAlignmentPoints);
0104     connect(clearAllAlignB, &QPushButton::clicked, this, &Ekos::MountModel::slotClearAllAlignPoints);
0105     connect(removeAlignB, &QPushButton::clicked, this, &Ekos::MountModel::slotRemoveAlignPoint);
0106     connect(addAlignB, &QPushButton::clicked, this, &Ekos::MountModel::slotAddAlignPoint);
0107     connect(findAlignB, &QPushButton::clicked, this, &Ekos::MountModel::slotFindAlignObject);
0108     connect(sortAlignB, &QPushButton::clicked, this, &Ekos::MountModel::slotSortAlignmentPoints);
0109 
0110     connect(previewB, &QPushButton::clicked, this, &Ekos::MountModel::togglePreviewAlignPoints);
0111     connect(stopAlignB, &QPushButton::clicked, this, &Ekos::MountModel::resetAlignmentProcedure);
0112     connect(startAlignB, &QPushButton::clicked, this, &Ekos::MountModel::startStopAlignmentProcedure);
0113 
0114     generateAlignStarList();
0115 
0116 }
0117 
0118 MountModel::~MountModel()
0119 {
0120 
0121 }
0122 
0123 void MountModel::generateAlignStarList()
0124 {
0125     alignStars.clear();
0126     starListBox->clear();
0127     greekStarListBox->clear();
0128 
0129     KStarsData *data = KStarsData::Instance();
0130     QVector<QPair<QString, const SkyObject *>> listStars;
0131     listStars.append(data->skyComposite()->objectLists(SkyObject::STAR));
0132     for (int i = 0; i < listStars.size(); i++)
0133     {
0134         QPair<QString, const SkyObject *> pair = listStars.value(i);
0135         const StarObject *star                 = dynamic_cast<const StarObject *>(pair.second);
0136         if (star)
0137         {
0138             StarObject *alignStar = star->clone();
0139             alignStar->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);
0140             alignStars.append(alignStar);
0141         }
0142     }
0143 
0144     QStringList boxNames;
0145     QStringList greekBoxNames;
0146 
0147     for (int i = 0; i < alignStars.size(); i++)
0148     {
0149         const StarObject *star = alignStars.value(i);
0150         if (star)
0151         {
0152             if (!isVisible(star))
0153             {
0154                 alignStars.remove(i);
0155                 i--;
0156             }
0157             else
0158             {
0159                 if (star->hasLatinName())
0160                     boxNames << star->name();
0161                 else
0162                 {
0163                     if (!star->gname().isEmpty())
0164                         greekBoxNames << star->gname().simplified();
0165                 }
0166             }
0167         }
0168     }
0169 
0170     boxNames.sort(Qt::CaseInsensitive);
0171     boxNames.removeDuplicates();
0172     greekBoxNames.removeDuplicates();
0173     std::sort(greekBoxNames.begin(), greekBoxNames.end(), [](const QString & a, const QString & b)
0174     {
0175         QStringList aParts = a.split(' ');
0176         QStringList bParts = b.split(' ');
0177         if (aParts.length() < 2 || bParts.length() < 2)
0178             return a < b; //This should not happen, they should all have 2 words in the string.
0179         if (aParts[1] == bParts[1])
0180         {
0181             return aParts[0] < bParts[0]; //This compares the greek letter when the constellation is the same
0182         }
0183         else
0184             return aParts[1] < bParts[1]; //This compares the constellation names
0185     });
0186 
0187     starListBox->addItem("Select one:");
0188     greekStarListBox->addItem("Select one:");
0189     for (int i = 0; i < boxNames.size(); i++)
0190         starListBox->addItem(boxNames.at(i));
0191     for (int i = 0; i < greekBoxNames.size(); i++)
0192         greekStarListBox->addItem(greekBoxNames.at(i));
0193 }
0194 
0195 bool MountModel::isVisible(const SkyObject *so)
0196 {
0197     return (getAltitude(so) > 30);
0198 }
0199 
0200 double MountModel::getAltitude(const SkyObject *so)
0201 {
0202     KStarsData *data  = KStarsData::Instance();
0203     SkyPoint sp       = so->recomputeCoords(data->ut(), data->geo());
0204 
0205     //check altitude of object at this time.
0206     sp.EquatorialToHorizontal(data->lst(), data->geo()->lat());
0207 
0208     return sp.alt().Degrees();
0209 }
0210 
0211 void MountModel::togglePreviewAlignPoints()
0212 {
0213     previewShowing = !previewShowing;
0214     previewB->setChecked(previewShowing);
0215     updatePreviewAlignPoints();
0216 }
0217 
0218 void MountModel::updatePreviewAlignPoints()
0219 {
0220     FlagComponent *flags = KStarsData::Instance()->skyComposite()->flags();
0221     for (int i = 0; i < flags->size(); i++)
0222     {
0223         if (flags->label(i).startsWith(QLatin1String("Align")))
0224         {
0225             flags->remove(i);
0226             i--;
0227         }
0228     }
0229     if (previewShowing)
0230     {
0231         for (int i = 0; i < alignTable->rowCount(); i++)
0232         {
0233             QTableWidgetItem *raCell      = alignTable->item(i, 0);
0234             QTableWidgetItem *deCell      = alignTable->item(i, 1);
0235             QTableWidgetItem *objNameCell = alignTable->item(i, 2);
0236 
0237             if (raCell && deCell && objNameCell)
0238             {
0239                 QString raString = raCell->text();
0240                 QString deString = deCell->text();
0241                 dms raDMS        = dms::fromString(raString, false);
0242                 dms decDMS       = dms::fromString(deString, true);
0243 
0244                 QString objString = objNameCell->text();
0245 
0246                 SkyPoint flagPoint(raDMS, decDMS);
0247                 flags->add(flagPoint, "J2000", "Default", "Align " + QString::number(i + 1) + ' ' + objString, "white");
0248             }
0249         }
0250     }
0251     KStars::Instance()->map()->forceUpdate(true);
0252 }
0253 
0254 void MountModel::slotLoadAlignmentPoints()
0255 {
0256     QUrl fileURL = QFileDialog::getOpenFileUrl(this, i18nc("@title:window", "Open Ekos Alignment List"),
0257                    alignURL,
0258                    "Ekos AlignmentList (*.eal)");
0259     if (fileURL.isEmpty())
0260         return;
0261 
0262     if (fileURL.isValid() == false)
0263     {
0264         QString message = i18n("Invalid URL: %1", fileURL.toLocalFile());
0265         KSNotification::sorry(message, i18n("Invalid URL"));
0266         return;
0267     }
0268 
0269     alignURL = fileURL;
0270 
0271     loadAlignmentPoints(fileURL.toLocalFile());
0272     if (previewShowing)
0273         updatePreviewAlignPoints();
0274 }
0275 
0276 bool MountModel::loadAlignmentPoints(const QString &fileURL)
0277 {
0278     QFile sFile;
0279     sFile.setFileName(fileURL);
0280 
0281     if (!sFile.open(QIODevice::ReadOnly))
0282     {
0283         QString message = i18n("Unable to open file %1", fileURL);
0284         KSNotification::sorry(message, i18n("Could Not Open File"));
0285         return false;
0286     }
0287 
0288     alignTable->setRowCount(0);
0289 
0290     LilXML *xmlParser = newLilXML();
0291 
0292     char errmsg[MAXRBUF];
0293     XMLEle *root = nullptr;
0294     char c;
0295 
0296     while (sFile.getChar(&c))
0297     {
0298         root = readXMLEle(xmlParser, c, errmsg);
0299 
0300         if (root)
0301         {
0302             double sqVersion = atof(findXMLAttValu(root, "version"));
0303             if (sqVersion < AL_FORMAT_VERSION)
0304             {
0305                 emit newLog(i18n("Deprecated sequence file format version %1. Please construct a new sequence file.",
0306                                  sqVersion));
0307                 return false;
0308             }
0309 
0310             XMLEle *ep    = nullptr;
0311             XMLEle *subEP = nullptr;
0312 
0313             int currentRow = 0;
0314 
0315             for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
0316             {
0317                 if (!strcmp(tagXMLEle(ep), "AlignmentPoint"))
0318                 {
0319                     alignTable->insertRow(currentRow);
0320 
0321                     subEP = findXMLEle(ep, "RA");
0322                     if (subEP)
0323                     {
0324                         QTableWidgetItem *RAReport = new QTableWidgetItem();
0325                         RAReport->setText(pcdataXMLEle(subEP));
0326                         RAReport->setTextAlignment(Qt::AlignHCenter);
0327                         alignTable->setItem(currentRow, 0, RAReport);
0328                     }
0329                     else
0330                         return false;
0331                     subEP = findXMLEle(ep, "DE");
0332                     if (subEP)
0333                     {
0334                         QTableWidgetItem *DEReport = new QTableWidgetItem();
0335                         DEReport->setText(pcdataXMLEle(subEP));
0336                         DEReport->setTextAlignment(Qt::AlignHCenter);
0337                         alignTable->setItem(currentRow, 1, DEReport);
0338                     }
0339                     else
0340                         return false;
0341                     subEP = findXMLEle(ep, "NAME");
0342                     if (subEP)
0343                     {
0344                         QTableWidgetItem *ObjReport = new QTableWidgetItem();
0345                         ObjReport->setText(pcdataXMLEle(subEP));
0346                         ObjReport->setTextAlignment(Qt::AlignHCenter);
0347                         alignTable->setItem(currentRow, 2, ObjReport);
0348                     }
0349                     else
0350                         return false;
0351                 }
0352                 currentRow++;
0353             }
0354             return true;
0355         }
0356     }
0357     return false;
0358 }
0359 
0360 void MountModel::slotSaveAlignmentPoints()
0361 {
0362     QUrl backupCurrent = alignURL;
0363 
0364     if (alignURL.toLocalFile().startsWith(QLatin1String("/tmp/")) || alignURL.toLocalFile().contains("/Temp"))
0365         alignURL.clear();
0366 
0367     alignURL = QFileDialog::getSaveFileUrl(this, i18nc("@title:window", "Save Ekos Alignment List"), alignURL,
0368                                            "Ekos Alignment List (*.eal)");
0369     // if user presses cancel
0370     if (alignURL.isEmpty())
0371     {
0372         alignURL = backupCurrent;
0373         return;
0374     }
0375 
0376     if (alignURL.toLocalFile().endsWith(QLatin1String(".eal")) == false)
0377         alignURL.setPath(alignURL.toLocalFile() + ".eal");
0378 
0379 
0380     if (alignURL.isValid())
0381     {
0382         if ((saveAlignmentPoints(alignURL.toLocalFile())) == false)
0383         {
0384             KSNotification::error(i18n("Failed to save alignment list"), i18n("Save"));
0385             return;
0386         }
0387     }
0388     else
0389     {
0390         QString message = i18n("Invalid URL: %1", alignURL.url());
0391         KSNotification::sorry(message, i18n("Invalid URL"));
0392     }
0393 }
0394 
0395 bool MountModel::saveAlignmentPoints(const QString &path)
0396 {
0397     QFile file;
0398     file.setFileName(path);
0399     if (!file.open(QIODevice::WriteOnly))
0400     {
0401         QString message = i18n("Unable to write to file %1", path);
0402         KSNotification::sorry(message, i18n("Could Not Open File"));
0403         return false;
0404     }
0405 
0406     QTextStream outstream(&file);
0407 
0408     outstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << Qt::endl;
0409     outstream << "<AlignmentList version='" << AL_FORMAT_VERSION << "'>" << Qt::endl;
0410 
0411     for (int i = 0; i < alignTable->rowCount(); i++)
0412     {
0413         QTableWidgetItem *raCell      = alignTable->item(i, 0);
0414         QTableWidgetItem *deCell      = alignTable->item(i, 1);
0415         QTableWidgetItem *objNameCell = alignTable->item(i, 2);
0416 
0417         if (!raCell || !deCell || !objNameCell)
0418             return false;
0419         QString raString  = raCell->text();
0420         QString deString  = deCell->text();
0421         QString objString = objNameCell->text();
0422 
0423         outstream << "<AlignmentPoint>" << Qt::endl;
0424         outstream << "<RA>" << raString << "</RA>" << Qt::endl;
0425         outstream << "<DE>" << deString << "</DE>" << Qt::endl;
0426         outstream << "<NAME>" << objString << "</NAME>" << Qt::endl;
0427         outstream << "</AlignmentPoint>" << Qt::endl;
0428     }
0429     outstream << "</AlignmentList>" << Qt::endl;
0430     emit newLog(i18n("Alignment List saved to %1", path));
0431     file.close();
0432     return true;
0433 }
0434 
0435 void MountModel::slotSortAlignmentPoints()
0436 {
0437     int firstAlignmentPt = findClosestAlignmentPointToTelescope();
0438     if (firstAlignmentPt != -1)
0439     {
0440         swapAlignPoints(firstAlignmentPt, 0);
0441     }
0442 
0443     for (int i = 0; i < alignTable->rowCount() - 1; i++)
0444     {
0445         int nextAlignmentPoint = findNextAlignmentPointAfter(i);
0446         if (nextAlignmentPoint != -1)
0447         {
0448             swapAlignPoints(nextAlignmentPoint, i + 1);
0449         }
0450     }
0451     if (previewShowing)
0452         updatePreviewAlignPoints();
0453 }
0454 
0455 int MountModel::findClosestAlignmentPointToTelescope()
0456 {
0457     dms bestDiff = dms(360);
0458     double index = -1;
0459 
0460     for (int i = 0; i < alignTable->rowCount(); i++)
0461     {
0462         QTableWidgetItem *raCell = alignTable->item(i, 0);
0463         QTableWidgetItem *deCell = alignTable->item(i, 1);
0464 
0465         if (raCell && deCell)
0466         {
0467             dms raDMS = dms::fromString(raCell->text(), false);
0468             dms deDMS = dms::fromString(deCell->text(), true);
0469 
0470             SkyPoint sk(raDMS, deDMS);
0471             dms thisDiff = telescopeCoord.angularDistanceTo(&sk);
0472             if (thisDiff.Degrees() < bestDiff.Degrees())
0473             {
0474                 index    = i;
0475                 bestDiff = thisDiff;
0476             }
0477         }
0478     }
0479     return index;
0480 }
0481 
0482 int MountModel::findNextAlignmentPointAfter(int currentSpot)
0483 {
0484     QTableWidgetItem *currentRACell = alignTable->item(currentSpot, 0);
0485     QTableWidgetItem *currentDECell = alignTable->item(currentSpot, 1);
0486 
0487     if (currentRACell && currentDECell)
0488     {
0489         dms thisRADMS = dms::fromString(currentRACell->text(), false);
0490         dms thisDEDMS = dms::fromString(currentDECell->text(), true);
0491 
0492         SkyPoint thisPt(thisRADMS, thisDEDMS);
0493 
0494         dms bestDiff = dms(360);
0495         double index = -1;
0496 
0497         for (int i = currentSpot + 1; i < alignTable->rowCount(); i++)
0498         {
0499             QTableWidgetItem *raCell = alignTable->item(i, 0);
0500             QTableWidgetItem *deCell = alignTable->item(i, 1);
0501 
0502             if (raCell && deCell)
0503             {
0504                 dms raDMS = dms::fromString(raCell->text(), false);
0505                 dms deDMS = dms::fromString(deCell->text(), true);
0506                 SkyPoint point(raDMS, deDMS);
0507                 dms thisDiff = thisPt.angularDistanceTo(&point);
0508 
0509                 if (thisDiff.Degrees() < bestDiff.Degrees())
0510                 {
0511                     index    = i;
0512                     bestDiff = thisDiff;
0513                 }
0514             }
0515         }
0516         return index;
0517     }
0518     else
0519         return -1;
0520 }
0521 
0522 void MountModel::slotWizardAlignmentPoints()
0523 {
0524     int points = alignPtNum->value();
0525     if (points <
0526             2)      //The minimum is 2 because the wizard calculations require the calculation of an angle between points.
0527         return; //It should not be less than 2 because the minimum in the spin box is 2.
0528 
0529     int minAlt       = minAltBox->value();
0530     KStarsData *data = KStarsData::Instance();
0531     GeoLocation *geo = data->geo();
0532     double lat       = geo->lat()->Degrees();
0533 
0534     if (alignTypeBox->currentIndex() == OBJECT_FIXED_DEC)
0535     {
0536         double decAngle = alignDec->value();
0537         //Dec that never rises.
0538         if (lat > 0)
0539         {
0540             if (decAngle < lat - 90 + minAlt) //Min altitude possible at minAlt deg above horizon
0541             {
0542                 KSNotification::sorry(i18n("DEC is below the altitude limit"));
0543                 return;
0544             }
0545         }
0546         else
0547         {
0548             if (decAngle > lat + 90 - minAlt) //Max altitude possible at minAlt deg above horizon
0549             {
0550                 KSNotification::sorry(i18n("DEC is below the altitude limit"));
0551                 return;
0552             }
0553         }
0554     }
0555 
0556     //If there are less than 6 points, keep them all in the same DEC,
0557     //any more, set the num per row to be the sqrt of the points to evenly distribute in RA and DEC
0558     int numRAperDEC = 5;
0559     if (points > 5)
0560         numRAperDEC = qSqrt(points);
0561 
0562     //These calculations rely on modulus and int division counting beginning at 0, but the #s start at 1.
0563     int decPoints       = (points - 1) / numRAperDEC + 1;
0564     int lastSetRAPoints = (points - 1) % numRAperDEC + 1;
0565 
0566     double decIncrement = -1;
0567     double initDEC      = -1;
0568     SkyPoint spTest;
0569 
0570     if (alignTypeBox->currentIndex() == OBJECT_FIXED_DEC)
0571     {
0572         decPoints    = 1;
0573         initDEC      = alignDec->value();
0574         decIncrement = 0;
0575     }
0576     else if (decPoints == 1)
0577     {
0578         decIncrement = 0;
0579         spTest.setAlt(
0580             minAlt); //The goal here is to get the point exactly West at the minAlt so that we can use that DEC
0581         spTest.setAz(270);
0582         spTest.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
0583         initDEC = spTest.dec().Degrees();
0584     }
0585     else
0586     {
0587         spTest.setAlt(
0588             minAlt +
0589             10); //We don't want to be right at the minAlt because there would be only 1 point on the dec circle above the alt.
0590         spTest.setAz(180);
0591         spTest.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
0592         initDEC = spTest.dec().Degrees();
0593         if (lat > 0)
0594             decIncrement = (80 - initDEC) / (decPoints); //Don't quite want to reach NCP
0595         else
0596             decIncrement = (initDEC - 80) / (decPoints); //Don't quite want to reach SCP
0597     }
0598 
0599     for (int d = 0; d < decPoints; d++)
0600     {
0601         double initRA      = -1;
0602         double raPoints    = -1;
0603         double raIncrement = -1;
0604         double dec;
0605 
0606         if (lat > 0)
0607             dec = initDEC + d * decIncrement;
0608         else
0609             dec = initDEC - d * decIncrement;
0610 
0611         if (alignTypeBox->currentIndex() == OBJECT_FIXED_DEC)
0612         {
0613             raPoints = points;
0614         }
0615         else if (d == decPoints - 1)
0616         {
0617             raPoints = lastSetRAPoints;
0618         }
0619         else
0620         {
0621             raPoints = numRAperDEC;
0622         }
0623 
0624         //This computes both the initRA and the raIncrement.
0625         calculateAngleForRALine(raIncrement, initRA, dec, lat, raPoints, minAlt);
0626 
0627         if (raIncrement == -1 || decIncrement == -1)
0628         {
0629             KSNotification::sorry(i18n("Point calculation error."));
0630             return;
0631         }
0632 
0633         for (int i = 0; i < raPoints; i++)
0634         {
0635             double ra = initRA + i * raIncrement;
0636 
0637             const SkyObject *original = getWizardAlignObject(ra, dec);
0638 
0639             QString ra_report, dec_report, name;
0640 
0641             if (original)
0642             {
0643                 SkyObject *o = original->clone();
0644                 o->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);
0645                 getFormattedCoords(o->ra0().Hours(), o->dec0().Degrees(), ra_report, dec_report);
0646                 name = o->longname();
0647             }
0648             else
0649             {
0650                 getFormattedCoords(dms(ra).Hours(), dec, ra_report, dec_report);
0651                 name = i18n("Sky Point");
0652             }
0653 
0654             int currentRow = alignTable->rowCount();
0655             alignTable->insertRow(currentRow);
0656 
0657             QTableWidgetItem *RAReport = new QTableWidgetItem();
0658             RAReport->setText(ra_report);
0659             RAReport->setTextAlignment(Qt::AlignHCenter);
0660             alignTable->setItem(currentRow, 0, RAReport);
0661 
0662             QTableWidgetItem *DECReport = new QTableWidgetItem();
0663             DECReport->setText(dec_report);
0664             DECReport->setTextAlignment(Qt::AlignHCenter);
0665             alignTable->setItem(currentRow, 1, DECReport);
0666 
0667             QTableWidgetItem *ObjNameReport = new QTableWidgetItem();
0668             ObjNameReport->setText(name);
0669             ObjNameReport->setTextAlignment(Qt::AlignHCenter);
0670             alignTable->setItem(currentRow, 2, ObjNameReport);
0671 
0672             QTableWidgetItem *disabledBox = new QTableWidgetItem();
0673             disabledBox->setFlags(Qt::ItemIsSelectable);
0674             alignTable->setItem(currentRow, 3, disabledBox);
0675         }
0676     }
0677     if (previewShowing)
0678         updatePreviewAlignPoints();
0679 }
0680 
0681 void MountModel::calculateAngleForRALine(double &raIncrement, double &initRA, double initDEC, double lat, double raPoints,
0682         double minAlt)
0683 {
0684     SkyPoint spEast;
0685     SkyPoint spWest;
0686 
0687     //Circumpolar dec
0688     if (fabs(initDEC) > (90 - fabs(lat) + minAlt))
0689     {
0690         if (raPoints > 1)
0691             raIncrement = 360 / (raPoints - 1);
0692         else
0693             raIncrement = 0;
0694         initRA = 0;
0695     }
0696     else
0697     {
0698         dms AZEast, AZWest;
0699         calculateAZPointsForDEC(dms(initDEC), dms(minAlt), AZEast, AZWest);
0700 
0701         spEast.setAlt(minAlt);
0702         spEast.setAz(AZEast.Degrees());
0703         spEast.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
0704 
0705         spWest.setAlt(minAlt);
0706         spWest.setAz(AZWest.Degrees());
0707         spWest.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
0708 
0709         dms angleSep = spEast.ra().deltaAngle(spWest.ra());
0710 
0711         initRA = spWest.ra().Degrees();
0712         if (raPoints > 1)
0713             raIncrement = fabs(angleSep.Degrees() / (raPoints - 1));
0714         else
0715             raIncrement = 0;
0716     }
0717 }
0718 
0719 void MountModel::calculateAZPointsForDEC(dms dec, dms alt, dms &AZEast, dms &AZWest)
0720 {
0721     KStarsData *data = KStarsData::Instance();
0722     GeoLocation *geo = data->geo();
0723     double AZRad;
0724 
0725     double sindec, cosdec, sinlat, coslat;
0726     double sinAlt, cosAlt;
0727 
0728     geo->lat()->SinCos(sinlat, coslat);
0729     dec.SinCos(sindec, cosdec);
0730     alt.SinCos(sinAlt, cosAlt);
0731 
0732     double arg = (sindec - sinlat * sinAlt) / (coslat * cosAlt);
0733     AZRad      = acos(arg);
0734     AZEast.setRadians(AZRad);
0735     AZWest.setRadians(2.0 * dms::PI - AZRad);
0736 }
0737 
0738 const SkyObject *MountModel::getWizardAlignObject(double ra, double dec)
0739 {
0740     double maxSearch = 5.0;
0741     switch (alignTypeBox->currentIndex())
0742     {
0743         case OBJECT_ANY_OBJECT:
0744             return KStarsData::Instance()->skyComposite()->objectNearest(new SkyPoint(dms(ra), dms(dec)), maxSearch);
0745         case OBJECT_FIXED_DEC:
0746         case OBJECT_FIXED_GRID:
0747             return nullptr;
0748 
0749         case OBJECT_ANY_STAR:
0750             return KStarsData::Instance()->skyComposite()->starNearest(new SkyPoint(dms(ra), dms(dec)), maxSearch);
0751     }
0752 
0753     //If they want named stars, then try to search for and return the closest Align Star to the requested location
0754 
0755     dms bestDiff = dms(360);
0756     double index = -1;
0757     for (int i = 0; i < alignStars.size(); i++)
0758     {
0759         const StarObject *star = alignStars.value(i);
0760         if (star)
0761         {
0762             if (star->hasName())
0763             {
0764                 SkyPoint thisPt(ra / 15.0, dec);
0765                 dms thisDiff = thisPt.angularDistanceTo(star);
0766                 if (thisDiff.Degrees() < bestDiff.Degrees())
0767                 {
0768                     index    = i;
0769                     bestDiff = thisDiff;
0770                 }
0771             }
0772         }
0773     }
0774     if (index == -1)
0775         return KStarsData::Instance()->skyComposite()->starNearest(new SkyPoint(dms(ra), dms(dec)), maxSearch);
0776     return alignStars.value(index);
0777 }
0778 
0779 void MountModel::alignTypeChanged(int alignType)
0780 {
0781     if (alignType == OBJECT_FIXED_DEC)
0782         alignDec->setEnabled(true);
0783     else
0784         alignDec->setEnabled(false);
0785 }
0786 
0787 void MountModel::slotStarSelected(const QString selectedStar)
0788 {
0789     for (int i = 0; i < alignStars.size(); i++)
0790     {
0791         const StarObject *star = alignStars.value(i);
0792         if (star)
0793         {
0794             if (star->name() == selectedStar || star->gname().simplified() == selectedStar)
0795             {
0796                 int currentRow = alignTable->rowCount();
0797                 alignTable->insertRow(currentRow);
0798 
0799                 QString ra_report, dec_report;
0800                 getFormattedCoords(star->ra0().Hours(), star->dec0().Degrees(), ra_report, dec_report);
0801 
0802                 QTableWidgetItem *RAReport = new QTableWidgetItem();
0803                 RAReport->setText(ra_report);
0804                 RAReport->setTextAlignment(Qt::AlignHCenter);
0805                 alignTable->setItem(currentRow, 0, RAReport);
0806 
0807                 QTableWidgetItem *DECReport = new QTableWidgetItem();
0808                 DECReport->setText(dec_report);
0809                 DECReport->setTextAlignment(Qt::AlignHCenter);
0810                 alignTable->setItem(currentRow, 1, DECReport);
0811 
0812                 QTableWidgetItem *ObjNameReport = new QTableWidgetItem();
0813                 ObjNameReport->setText(star->longname());
0814                 ObjNameReport->setTextAlignment(Qt::AlignHCenter);
0815                 alignTable->setItem(currentRow, 2, ObjNameReport);
0816 
0817                 QTableWidgetItem *disabledBox = new QTableWidgetItem();
0818                 disabledBox->setFlags(Qt::ItemIsSelectable);
0819                 alignTable->setItem(currentRow, 3, disabledBox);
0820 
0821                 starListBox->setCurrentIndex(0);
0822                 greekStarListBox->setCurrentIndex(0);
0823                 return;
0824             }
0825         }
0826     }
0827     if (previewShowing)
0828         updatePreviewAlignPoints();
0829 }
0830 
0831 
0832 void MountModel::getFormattedCoords(double ra, double dec, QString &ra_str, QString &dec_str)
0833 {
0834     dms ra_s, dec_s;
0835     ra_s.setH(ra);
0836     dec_s.setD(dec);
0837 
0838     ra_str = QString("%1:%2:%3")
0839              .arg(ra_s.hour(), 2, 10, QChar('0'))
0840              .arg(ra_s.minute(), 2, 10, QChar('0'))
0841              .arg(ra_s.second(), 2, 10, QChar('0'));
0842     if (dec_s.Degrees() < 0)
0843         dec_str = QString("-%1:%2:%3")
0844                   .arg(abs(dec_s.degree()), 2, 10, QChar('0'))
0845                   .arg(abs(dec_s.arcmin()), 2, 10, QChar('0'))
0846                   .arg(dec_s.arcsec(), 2, 10, QChar('0'));
0847     else
0848         dec_str = QString("%1:%2:%3")
0849                   .arg(dec_s.degree(), 2, 10, QChar('0'))
0850                   .arg(dec_s.arcmin(), 2, 10, QChar('0'))
0851                   .arg(dec_s.arcsec(), 2, 10, QChar('0'));
0852 }
0853 
0854 void MountModel::slotClearAllAlignPoints()
0855 {
0856     if (alignTable->rowCount() == 0)
0857         return;
0858 
0859     if (KMessageBox::questionYesNo(this, i18n("Are you sure you want to clear all the alignment points?"),
0860                                    i18n("Clear Align Points")) == KMessageBox::Yes)
0861         alignTable->setRowCount(0);
0862 
0863     if (previewShowing)
0864         updatePreviewAlignPoints();
0865 }
0866 
0867 void MountModel::slotRemoveAlignPoint()
0868 {
0869     alignTable->removeRow(alignTable->currentRow());
0870     if (previewShowing)
0871         updatePreviewAlignPoints();
0872 }
0873 
0874 void MountModel::moveAlignPoint(int logicalIndex, int oldVisualIndex, int newVisualIndex)
0875 {
0876     Q_UNUSED(logicalIndex)
0877 
0878     for (int i = 0; i < alignTable->columnCount(); i++)
0879     {
0880         QTableWidgetItem *oldItem = alignTable->takeItem(oldVisualIndex, i);
0881         QTableWidgetItem *newItem = alignTable->takeItem(newVisualIndex, i);
0882 
0883         alignTable->setItem(newVisualIndex, i, oldItem);
0884         alignTable->setItem(oldVisualIndex, i, newItem);
0885     }
0886     alignTable->verticalHeader()->blockSignals(true);
0887     alignTable->verticalHeader()->moveSection(newVisualIndex, oldVisualIndex);
0888     alignTable->verticalHeader()->blockSignals(false);
0889 
0890     if (previewShowing)
0891         updatePreviewAlignPoints();
0892 }
0893 
0894 void MountModel::swapAlignPoints(int firstPt, int secondPt)
0895 {
0896     for (int i = 0; i < alignTable->columnCount(); i++)
0897     {
0898         QTableWidgetItem *firstPtItem  = alignTable->takeItem(firstPt, i);
0899         QTableWidgetItem *secondPtItem = alignTable->takeItem(secondPt, i);
0900 
0901         alignTable->setItem(firstPt, i, secondPtItem);
0902         alignTable->setItem(secondPt, i, firstPtItem);
0903     }
0904 }
0905 
0906 void MountModel::slotAddAlignPoint()
0907 {
0908     int currentRow = alignTable->rowCount();
0909     alignTable->insertRow(currentRow);
0910 
0911     QTableWidgetItem *disabledBox = new QTableWidgetItem();
0912     disabledBox->setFlags(Qt::ItemIsSelectable);
0913     alignTable->setItem(currentRow, 3, disabledBox);
0914 }
0915 
0916 void MountModel::slotFindAlignObject()
0917 {
0918     if (FindDialog::Instance()->execWithParent(this) == QDialog::Accepted)
0919     {
0920         SkyObject *object = FindDialog::Instance()->targetObject();
0921         if (object != nullptr)
0922         {
0923             KStarsData * const data = KStarsData::Instance();
0924 
0925             SkyObject *o = object->clone();
0926             o->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);
0927             int currentRow = alignTable->rowCount();
0928             alignTable->insertRow(currentRow);
0929 
0930             QString ra_report, dec_report;
0931             getFormattedCoords(o->ra0().Hours(), o->dec0().Degrees(), ra_report, dec_report);
0932 
0933             QTableWidgetItem *RAReport = new QTableWidgetItem();
0934             RAReport->setText(ra_report);
0935             RAReport->setTextAlignment(Qt::AlignHCenter);
0936             alignTable->setItem(currentRow, 0, RAReport);
0937 
0938             QTableWidgetItem *DECReport = new QTableWidgetItem();
0939             DECReport->setText(dec_report);
0940             DECReport->setTextAlignment(Qt::AlignHCenter);
0941             alignTable->setItem(currentRow, 1, DECReport);
0942 
0943             QTableWidgetItem *ObjNameReport = new QTableWidgetItem();
0944             ObjNameReport->setText(o->longname());
0945             ObjNameReport->setTextAlignment(Qt::AlignHCenter);
0946             alignTable->setItem(currentRow, 2, ObjNameReport);
0947 
0948             QTableWidgetItem *disabledBox = new QTableWidgetItem();
0949             disabledBox->setFlags(Qt::ItemIsSelectable);
0950             alignTable->setItem(currentRow, 3, disabledBox);
0951         }
0952     }
0953     if (previewShowing)
0954         updatePreviewAlignPoints();
0955 }
0956 
0957 void MountModel::resetAlignmentProcedure()
0958 {
0959     alignTable->setCellWidget(currentAlignmentPoint, 3, new QWidget());
0960     QTableWidgetItem *statusReport = new QTableWidgetItem();
0961     statusReport->setFlags(Qt::ItemIsSelectable);
0962     statusReport->setIcon(QIcon(":/icons/AlignWarning.svg"));
0963     alignTable->setItem(currentAlignmentPoint, 3, statusReport);
0964 
0965     emit newLog(i18n("The Mount Model Tool is Reset."));
0966     startAlignB->setIcon(
0967         QIcon::fromTheme("media-playback-start"));
0968     m_IsRunning     = false;
0969     currentAlignmentPoint = 0;
0970     emit aborted();
0971 }
0972 
0973 bool MountModel::alignmentPointsAreBad()
0974 {
0975     for (int i = 0; i < alignTable->rowCount(); i++)
0976     {
0977         QTableWidgetItem *raCell = alignTable->item(i, 0);
0978         if (!raCell)
0979             return true;
0980         QString raString = raCell->text();
0981         if (dms().setFromString(raString, false) == false)
0982             return true;
0983 
0984         QTableWidgetItem *decCell = alignTable->item(i, 1);
0985         if (!decCell)
0986             return true;
0987         QString decString = decCell->text();
0988         if (dms().setFromString(decString, true) == false)
0989             return true;
0990     }
0991     return false;
0992 }
0993 
0994 void MountModel::startStopAlignmentProcedure()
0995 {
0996     if (!m_IsRunning)
0997     {
0998         if (alignTable->rowCount() > 0)
0999         {
1000             if (alignmentPointsAreBad())
1001             {
1002                 KSNotification::error(i18n("Please Check the Alignment Points."));
1003                 return;
1004             }
1005             if (m_AlignInstance->currentGOTOMode() == Align::GOTO_NOTHING)
1006             {
1007                 int r = KMessageBox::warningContinueCancel(
1008                             nullptr,
1009                             i18n("In the Align Module, \"Nothing\" is Selected for the Solver Action.  This means that the "
1010                                  "mount model tool will not sync/align your mount but will only report the pointing model "
1011                                  "errors.  Do you wish to continue?"),
1012                             i18n("Pointing Model Report Only?"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
1013                             "nothing_selected_warning");
1014                 if (r == KMessageBox::Cancel)
1015                     return;
1016             }
1017             if (currentAlignmentPoint == 0)
1018             {
1019                 for (int row = 0; row < alignTable->rowCount(); row++)
1020                 {
1021                     QTableWidgetItem *statusReport = new QTableWidgetItem();
1022                     statusReport->setIcon(QIcon());
1023                     alignTable->setItem(row, 3, statusReport);
1024                 }
1025             }
1026             startAlignB->setIcon(
1027                 QIcon::fromTheme("media-playback-pause"));
1028             m_IsRunning = true;
1029             emit newLog(i18n("The Mount Model Tool is Starting."));
1030             startAlignmentPoint();
1031         }
1032     }
1033     else
1034     {
1035         startAlignB->setIcon(
1036             QIcon::fromTheme("media-playback-start"));
1037         alignTable->setCellWidget(currentAlignmentPoint, 3, new QWidget());
1038         emit newLog(i18n("The Mount Model Tool is Paused."));
1039         emit aborted();
1040         m_IsRunning = false;
1041 
1042         QTableWidgetItem *statusReport = new QTableWidgetItem();
1043         statusReport->setFlags(Qt::ItemIsSelectable);
1044         statusReport->setIcon(QIcon(":/icons/AlignWarning.svg"));
1045         alignTable->setItem(currentAlignmentPoint, 3, statusReport);
1046     }
1047 }
1048 
1049 void MountModel::startAlignmentPoint()
1050 {
1051     if (m_IsRunning && currentAlignmentPoint >= 0 && currentAlignmentPoint < alignTable->rowCount())
1052     {
1053         QTableWidgetItem *raCell = alignTable->item(currentAlignmentPoint, 0);
1054         QString raString         = raCell->text();
1055         dms raDMS                = dms::fromString(raString, false);
1056         double raDeg             = raDMS.Degrees();
1057 
1058         QTableWidgetItem *decCell = alignTable->item(currentAlignmentPoint, 1);
1059         QString decString         = decCell->text();
1060         dms decDMS                = dms::fromString(decString, true);
1061         double dec                = decDMS.Degrees();
1062 
1063         QProgressIndicator *alignIndicator = new QProgressIndicator(this);
1064         alignTable->setCellWidget(currentAlignmentPoint, 3, alignIndicator);
1065         alignIndicator->startAnimation();
1066 
1067         const SkyObject *target = getWizardAlignObject(raDeg, dec);
1068         m_AlignInstance->setTarget(*target);
1069         m_AlignInstance->Slew();
1070     }
1071 }
1072 
1073 void MountModel::finishAlignmentPoint(bool solverSucceeded)
1074 {
1075     if (m_IsRunning && currentAlignmentPoint >= 0 && currentAlignmentPoint < alignTable->rowCount())
1076     {
1077         alignTable->setCellWidget(currentAlignmentPoint, 3, new QWidget());
1078         QTableWidgetItem *statusReport = new QTableWidgetItem();
1079         statusReport->setFlags(Qt::ItemIsSelectable);
1080         if (solverSucceeded)
1081             statusReport->setIcon(QIcon(":/icons/AlignSuccess.svg"));
1082         else
1083             statusReport->setIcon(QIcon(":/icons/AlignFailure.svg"));
1084         alignTable->setItem(currentAlignmentPoint, 3, statusReport);
1085 
1086         currentAlignmentPoint++;
1087 
1088         if (currentAlignmentPoint < alignTable->rowCount())
1089         {
1090             startAlignmentPoint();
1091         }
1092         else
1093         {
1094             m_IsRunning = false;
1095             startAlignB->setIcon(
1096                 QIcon::fromTheme("media-playback-start"));
1097             emit newLog(i18n("The Mount Model Tool is Finished."));
1098             currentAlignmentPoint = 0;
1099         }
1100     }
1101 }
1102 
1103 void MountModel::setAlignStatus(Ekos::AlignState state)
1104 {
1105     switch (state)
1106     {
1107         case ALIGN_COMPLETE:
1108             if (m_IsRunning)
1109                 finishAlignmentPoint(true);
1110             break;
1111 
1112         case ALIGN_FAILED:
1113             if (m_IsRunning)
1114                 finishAlignmentPoint(false);
1115             break;
1116         default:
1117             break;
1118     }
1119 }
1120 }