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

0001 /*
0002     SPDX-FileCopyrightText: 2015 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "mount.h"
0008 
0009 #include <QQuickView>
0010 #include <QQuickItem>
0011 #include <indicom.h>
0012 
0013 #include <KNotifications/KNotification>
0014 #include <KLocalizedContext>
0015 #include <KActionCollection>
0016 
0017 #include "Options.h"
0018 
0019 #include "ksmessagebox.h"
0020 #include "indi/driverinfo.h"
0021 #include "indi/indicommon.h"
0022 #include "indi/clientmanager.h"
0023 #include "indi/indigps.h"
0024 
0025 
0026 #include "mountadaptor.h"
0027 
0028 #include "ekos/manager.h"
0029 #include "ekos/auxiliary/opticaltrainmanager.h"
0030 #include "ekos/auxiliary/profilesettings.h"
0031 #include "ekos/auxiliary/opticaltrainsettings.h"
0032 #include "ekos/manager/meridianflipstate.h"
0033 #include "ekos/align/polaralignmentassistant.h"
0034 
0035 #include "kstars.h"
0036 #include "skymapcomposite.h"
0037 #include "dialogs/finddialog.h"
0038 #include "kstarsdata.h"
0039 
0040 #include <basedevice.h>
0041 
0042 #include <ekos_mount_debug.h>
0043 
0044 extern const char *libindi_strings_context;
0045 
0046 #define ABORT_DISPATCH_LIMIT 3
0047 
0048 namespace Ekos
0049 {
0050 
0051 Mount::Mount()
0052 {
0053     setupUi(this);
0054 
0055     new MountAdaptor(this);
0056     QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Mount", this);
0057     // Set up DBus interfaces
0058     QPointer<QDBusInterface> ekosInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos",
0059             QDBusConnection::sessionBus(), this);
0060     qDBusRegisterMetaType<SkyPoint>();
0061 
0062     // Connecting DBus signals
0063     QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "newModule", this,
0064                                           SLOT(registerNewModule(QString)));
0065 
0066     m_Mount = nullptr;
0067 
0068     // initialize the state machine
0069     mf_state.reset(new MeridianFlipState());
0070     // connect to the MF state maichine
0071     getMeridianFlipState()->connectMount(this);
0072 
0073     // set the status message in the mount tab and write it to the log
0074     connect(mf_state.get(), &MeridianFlipState::newMeridianFlipMountStatusText, [&](const QString & text)
0075     {
0076         meridianFlipStatusWidget->setStatus(text);
0077         if (mf_state->getMeridianFlipMountState() != MeridianFlipState::MOUNT_FLIP_NONE &&
0078                 mf_state->getMeridianFlipMountState() != MeridianFlipState::MOUNT_FLIP_PLANNED)
0079             appendLogText(text);
0080     });
0081     connect(mountToolBoxB, &QPushButton::clicked, this, &Mount::toggleMountToolBox);
0082 
0083     connect(clearAlignmentModelB, &QPushButton::clicked, this, &Mount::resetModel);
0084 
0085     connect(clearParkingB, &QPushButton::clicked, this, [this]()
0086     {
0087         if (m_Mount)
0088             m_Mount->clearParking();
0089 
0090     });
0091 
0092     connect(purgeConfigB, &QPushButton::clicked, this, [this]()
0093     {
0094         if (m_Mount)
0095         {
0096             if (KMessageBox::questionYesNo(KStars::Instance(),
0097                                            i18n("Are you sure you want to clear all mount configurations?"),
0098                                            i18n("Mount Configuration"), KStandardGuiItem::yes(), KStandardGuiItem::no(),
0099                                            "purge_mount_settings_dialog") == KMessageBox::Yes)
0100             {
0101                 resetModel();
0102                 m_Mount->clearParking();
0103                 m_Mount->setConfig(PURGE_CONFIG);
0104             }
0105         }
0106     });
0107 
0108     connect(enableAltitudeLimits, &QCheckBox::toggled, this, [this](bool toggled)
0109     {
0110         m_AltitudeLimitEnabled = toggled;
0111         setAltitudeLimits(toggled);
0112 
0113     });
0114     m_AltitudeLimitEnabled = enableAltitudeLimits->isChecked();
0115     connect(enableHaLimit, &QCheckBox::toggled, this, &Mount::enableHourAngleLimits);
0116 
0117     // meridian flip
0118     connect(mf_state.get(), &MeridianFlipState::newMeridianFlipMountStatusText, meridianFlipStatusWidget,
0119             &MeridianFlipStatusWidget::setStatus);
0120 
0121     connect(&autoParkTimer, &QTimer::timeout, this, &Mount::startAutoPark);
0122     connect(startTimerB, &QPushButton::clicked, this, &Mount::startParkTimer);
0123     connect(stopTimerB, &QPushButton::clicked, this, &Mount::stopParkTimer);
0124 
0125     stopTimerB->setEnabled(false);
0126 
0127     if (parkEveryDay->isChecked())
0128         startTimerB->animateClick();
0129 
0130     // QML Stuff
0131     m_BaseView = new QQuickView();
0132 
0133     // Must set context *before* loading the QML file.
0134     m_Ctxt = m_BaseView->rootContext();
0135     ///Use instead of KDeclarative
0136     m_Ctxt->setContextObject(new KLocalizedContext(m_BaseView));
0137 
0138     m_Ctxt->setContextProperty("mount", this);
0139 
0140     // Load QML file after setting context
0141     m_BaseView->setSource(QUrl("qrc:/qml/mount/mountbox.qml"));
0142 
0143     m_BaseView->setTitle(i18n("Mount Control"));
0144 #ifdef Q_OS_OSX
0145     m_BaseView->setFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0146 #else
0147     m_BaseView->setFlags(Qt::WindowStaysOnTopHint | Qt::WindowCloseButtonHint);
0148 #endif
0149 
0150     // Theming?
0151     m_BaseView->setColor(Qt::black);
0152 
0153     m_BaseObj = m_BaseView->rootObject();
0154 
0155     m_BaseView->setResizeMode(QQuickView::SizeViewToRootObject);
0156 
0157     m_SpeedSlider  = m_BaseObj->findChild<QQuickItem *>("speedSliderObject");
0158     m_SpeedLabel   = m_BaseObj->findChild<QQuickItem *>("speedLabelObject");
0159     m_raValue      = m_BaseObj->findChild<QQuickItem *>("raValueObject");
0160     m_deValue      = m_BaseObj->findChild<QQuickItem *>("deValueObject");
0161     m_azValue      = m_BaseObj->findChild<QQuickItem *>("azValueObject");
0162     m_altValue     = m_BaseObj->findChild<QQuickItem *>("altValueObject");
0163     m_haValue      = m_BaseObj->findChild<QQuickItem *>("haValueObject");
0164     m_zaValue      = m_BaseObj->findChild<QQuickItem *>("zaValueObject");
0165     m_targetText   = m_BaseObj->findChild<QQuickItem *>("targetTextObject");
0166     m_targetRAText = m_BaseObj->findChild<QQuickItem *>("targetRATextObject");
0167     m_targetDEText = m_BaseObj->findChild<QQuickItem *>("targetDETextObject");
0168     m_J2000Check   = m_BaseObj->findChild<QQuickItem *>("j2000CheckObject");
0169     m_JNowCheck    = m_BaseObj->findChild<QQuickItem *>("jnowCheckObject");
0170     m_Park         = m_BaseObj->findChild<QQuickItem *>("parkButtonObject");
0171     m_Unpark       = m_BaseObj->findChild<QQuickItem *>("unparkButtonObject");
0172     m_statusText   = m_BaseObj->findChild<QQuickItem *>("statusTextObject");
0173     m_equatorialCheck = m_BaseObj->findChild<QQuickItem *>("equatorialCheckObject");
0174     m_horizontalCheck = m_BaseObj->findChild<QQuickItem *>("horizontalCheckObject");
0175     m_haEquatorialCheck = m_BaseObj->findChild<QQuickItem *>("haEquatorialCheckObject");
0176     m_leftRightCheck = m_BaseObj->findChild<QQuickItem *>("leftRightCheckObject");
0177     m_upDownCheck = m_BaseObj->findChild<QQuickItem *>("upDownCheckObject");
0178 
0179     m_leftRightCheck->setProperty("checked", Options::leftRightReversed());
0180     m_upDownCheck->setProperty("checked", Options::upDownReversed());
0181 
0182     //Note:  This is to prevent a button from being called the default button
0183     //and then executing when the user hits the enter key such as when on a Text Box
0184     QList<QPushButton *> qButtons = findChildren<QPushButton *>();
0185     for (auto &button : qButtons)
0186         button->setAutoDefault(false);
0187 
0188     loadGlobalSettings();
0189     connectSettings();
0190 
0191     setupOpticalTrainManager();
0192 }
0193 
0194 Mount::~Mount()
0195 {
0196     autoParkTimer.stop();
0197     delete(m_Ctxt);
0198     delete(m_BaseObj);
0199 }
0200 
0201 void Mount::setupParkUI()
0202 {
0203     if (m_Mount == nullptr)
0204         return;
0205 
0206     if (m_Mount->canPark())
0207     {
0208         switch(m_Mount->parkStatus())
0209         {
0210             case ISD::PARK_PARKED:
0211                 parkingTitle->setTitle("Parked");
0212                 break;
0213             case ISD::PARK_PARKING:
0214                 parkingTitle->setTitle("Parking");
0215                 break;
0216             case ISD::PARK_UNPARKING:
0217                 parkingTitle->setTitle("Unparking");
0218                 break;
0219             case ISD::PARK_UNPARKED:
0220                 parkingTitle->setTitle("Unparked");
0221                 break;
0222             case ISD::PARK_ERROR:
0223                 parkingTitle->setTitle("Park Error");
0224                 break;
0225             case ISD::PARK_UNKNOWN:
0226                 parkingTitle->setTitle("Park Status Unknown");
0227                 break;
0228         }
0229         parkB->setEnabled(m_Mount->parkStatus() == ISD::PARK_UNPARKED);
0230         unparkB->setEnabled(m_Mount->parkStatus() == ISD::PARK_PARKED);
0231     }
0232     else
0233     {
0234         parkB->setEnabled(false);
0235         unparkB->setEnabled(false);
0236         parkingTitle->setTitle("");
0237     }
0238 }
0239 
0240 bool Mount::setMount(ISD::Mount *device)
0241 {
0242     if (device && device == m_Mount)
0243     {
0244         syncTelescopeInfo();
0245         return false;
0246     }
0247 
0248     if (m_Mount)
0249         m_Mount->disconnect(m_Mount, nullptr, this, nullptr);
0250 
0251     m_Mount = device;
0252 
0253     if (m_Mount)
0254     {
0255         connect(m_Mount, &ISD::ConcreteDevice::Connected, this, [this]()
0256         {
0257             setEnabled(true);
0258         });
0259         connect(m_Mount, &ISD::ConcreteDevice::Disconnected, this, [this]()
0260         {
0261             setEnabled(false);
0262             opticalTrainCombo->setEnabled(true);
0263             trainLabel->setEnabled(true);
0264         });
0265     }
0266     else
0267         return false;
0268 
0269     mainLayout->setEnabled(true);
0270 
0271     // forward the new mount to the meridian flip state machine
0272     mf_state->setMountConnected(device != nullptr);
0273 
0274     if (m_GPS != nullptr)
0275         syncGPS();
0276 
0277     connect(m_Mount, &ISD::Mount::propertyUpdated, this, &Mount::updateProperty);
0278     connect(m_Mount, &ISD::Mount::newTarget, this, &Mount::newTarget);
0279     connect(m_Mount, &ISD::Mount::newTargetName, this, &Mount::newTargetName);
0280     connect(m_Mount, &ISD::Mount::newCoords, this, &Mount::newCoords);
0281     connect(m_Mount, &ISD::Mount::newCoords, this, &Mount::updateTelescopeCoords);
0282     connect(m_Mount, &ISD::Mount::slewRateChanged, this, &Mount::slewRateChanged);
0283     connect(m_Mount, &ISD::Mount::pierSideChanged, this, &Mount::pierSideChanged);
0284     connect(m_Mount, &ISD::Mount::axisReversed, this, &Mount::syncAxisReversed);
0285     connect(m_Mount, &ISD::Mount::Disconnected, this, [this]()
0286     {
0287         m_BaseView->hide();
0288     });
0289     connect(m_Mount, &ISD::Mount::newParkStatus, this, [&](ISD::ParkStatus status)
0290     {
0291         m_ParkStatus = status;
0292         emit newParkStatus(status);
0293 
0294         setupParkUI();
0295 
0296         // If mount is unparked AND every day auto-paro check is ON
0297         // AND auto park timer is not yet started, we try to initiate it.
0298         if (status == ISD::PARK_UNPARKED && parkEveryDay->isChecked() && autoParkTimer.isActive() == false)
0299             startTimerB->animateClick();
0300     });
0301 
0302     // If mount is ready then let's set it up.
0303     if (m_Mount->isReady())
0304     {
0305         if (enableAltitudeLimits->isChecked())
0306             m_Mount->setAltLimits(minimumAltLimit->value(), maximumAltLimit->value());
0307         else
0308             m_Mount->setAltLimits(-91, +91);
0309 
0310         syncTelescopeInfo();
0311 
0312         // Send initial status
0313         m_Status = m_Mount->status();
0314         emit newStatus(m_Status);
0315 
0316         m_ParkStatus = m_Mount->parkStatus();
0317         emit newParkStatus(m_ParkStatus);
0318         emit ready();
0319     }
0320     // Otherwise, let's wait for mount to be ready
0321     else
0322     {
0323         connect(m_Mount, &ISD::Mount::ready, this, [this]()
0324         {
0325             if (enableAltitudeLimits->isChecked())
0326                 m_Mount->setAltLimits(minimumAltLimit->value(), maximumAltLimit->value());
0327             else
0328                 m_Mount->setAltLimits(-91, +91);
0329 
0330             syncTelescopeInfo();
0331 
0332             // Send initial status
0333             m_Status = m_Mount->status();
0334             emit newStatus(m_Status);
0335 
0336             m_ParkStatus = m_Mount->parkStatus();
0337             emit newParkStatus(m_ParkStatus);
0338             emit ready();
0339         });
0340     }
0341 
0342     return true;
0343 }
0344 
0345 bool Mount::addGPS(ISD::GPS * device)
0346 {
0347     // No duplicates
0348     for (auto &oneGPS : m_GPSes)
0349     {
0350         if (oneGPS->getDeviceName() == device->getDeviceName())
0351             return false;
0352     }
0353 
0354     for (auto &oneGPS : m_GPSes)
0355         oneGPS->disconnect(this);
0356 
0357     m_GPSes.append(device);
0358 
0359     auto executeSetGPS = [this, device]()
0360     {
0361         m_GPS = device;
0362         connect(m_GPS, &ISD::GPS::propertyUpdated, this, &Ekos::Mount::updateProperty, Qt::UniqueConnection);
0363         appendLogText(i18n("GPS driver detected. KStars and mount time and location settings are now synced to the GPS driver."));
0364         syncGPS();
0365     };
0366 
0367     if (Options::useGPSSource() == false)
0368     {
0369         connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, executeSetGPS]()
0370         {
0371             KSMessageBox::Instance()->disconnect(this);
0372             Options::setUseKStarsSource(false);
0373             Options::setUseMountSource(false);
0374             Options::setUseGPSSource(true);
0375             executeSetGPS();
0376         });
0377 
0378         KSMessageBox::Instance()->questionYesNo(i18n("GPS is detected. Do you want to switch time and location source to GPS?"),
0379                                                 i18n("GPS Settings"), 10);
0380     }
0381     else
0382         executeSetGPS();
0383 
0384     return true;
0385 }
0386 
0387 void Mount::syncGPS()
0388 {
0389     // We only update when location is OK
0390     auto location = m_GPS->getNumber("GEOGRAPHIC_COORD");
0391     if (!location || location->getState() != IPS_OK)
0392         return;
0393 
0394     // Sync name
0395     if (m_Mount)
0396     {
0397         auto activeDevices = m_Mount->getText("ACTIVE_DEVICES");
0398         if (activeDevices)
0399         {
0400             auto activeGPS = activeDevices->findWidgetByName("ACTIVE_GPS");
0401             if (activeGPS)
0402             {
0403                 if (activeGPS->getText() != m_GPS->getDeviceName())
0404                 {
0405                     activeGPS->setText(m_GPS->getDeviceName().toLatin1().constData());
0406                     m_Mount->sendNewProperty(activeDevices);
0407                 }
0408             }
0409         }
0410     }
0411 
0412     // GPS Refresh should only be called once automatically.
0413     if (GPSInitialized == false)
0414     {
0415         auto refreshGPS = m_GPS->getSwitch("GPS_REFRESH");
0416         if (refreshGPS)
0417         {
0418             refreshGPS->at(0)->setState(ISS_ON);
0419             m_GPS->sendNewProperty(refreshGPS);
0420             GPSInitialized = true;
0421         }
0422     }
0423 }
0424 
0425 void Mount::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
0426 {
0427     if (m_Mount && m_Mount->getDeviceName() == device->getDeviceName())
0428     {
0429         m_Mount->disconnect(this);
0430         m_BaseView->hide();
0431         qCDebug(KSTARS_EKOS_MOUNT) << "Removing mount driver" << m_Mount->getDeviceName();
0432         m_Mount = nullptr;
0433     }
0434 
0435     for (auto &oneGPS : m_GPSes)
0436     {
0437         if (oneGPS->getDeviceName() == device->getDeviceName())
0438         {
0439             oneGPS->disconnect(this);
0440             m_GPSes.removeOne(oneGPS);
0441             m_GPS = nullptr;
0442             break;
0443         }
0444     }
0445 }
0446 
0447 void Mount::syncTelescopeInfo()
0448 {
0449     if (!m_Mount || m_Mount->isConnected() == false)
0450         return;
0451 
0452     auto svp = m_Mount->getSwitch("TELESCOPE_SLEW_RATE");
0453 
0454     if (svp)
0455     {
0456         int index = svp->findOnSwitchIndex();
0457 
0458         // QtQuick
0459         m_SpeedSlider->setEnabled(true);
0460         m_SpeedSlider->setProperty("maximumValue", svp->count() - 1);
0461         m_SpeedSlider->setProperty("value", index);
0462 
0463         m_SpeedLabel->setProperty("text", i18nc(libindi_strings_context, svp->at(index)->getLabel()));
0464         m_SpeedLabel->setEnabled(true);
0465     }
0466     else
0467     {
0468         // QtQuick
0469         m_SpeedSlider->setEnabled(false);
0470         m_SpeedLabel->setEnabled(false);
0471     }
0472 
0473     if (m_Mount->canPark())
0474     {
0475         connect(parkB, &QPushButton::clicked, m_Mount, &ISD::Mount::park, Qt::UniqueConnection);
0476         connect(unparkB, &QPushButton::clicked, m_Mount, &ISD::Mount::unpark, Qt::UniqueConnection);
0477 
0478         // QtQuick
0479         m_Park->setEnabled(!m_Mount->isParked());
0480         m_Unpark->setEnabled(m_Mount->isParked());
0481     }
0482     else
0483     {
0484         disconnect(parkB, &QPushButton::clicked, m_Mount, &ISD::Mount::park);
0485         disconnect(unparkB, &QPushButton::clicked, m_Mount, &ISD::Mount::unpark);
0486 
0487         // QtQuick
0488         m_Park->setEnabled(false);
0489         m_Unpark->setEnabled(false);
0490     }
0491     setupParkUI();
0492 
0493     // Tracking State
0494     svp = m_Mount->getSwitch("TELESCOPE_TRACK_STATE");
0495     if (svp)
0496     {
0497         trackingGroup->setEnabled(true);
0498         trackOnB->disconnect();
0499         trackOffB->disconnect();
0500         connect(trackOnB, &QPushButton::clicked, this, [&]()
0501         {
0502             m_Mount->setTrackEnabled(true);
0503         });
0504         connect(trackOffB, &QPushButton::clicked, this, [&]()
0505         {
0506             if (KMessageBox::questionYesNo(KStars::Instance(),
0507                                            i18n("Are you sure you want to turn off mount tracking?"),
0508                                            i18n("Mount Tracking"), KStandardGuiItem::yes(), KStandardGuiItem::no(),
0509                                            "turn_off_mount_tracking_dialog") == KMessageBox::Yes)
0510                 m_Mount->setTrackEnabled(false);
0511         });
0512     }
0513     else
0514     {
0515         trackOnB->setChecked(false);
0516         trackOffB->setChecked(false);
0517         trackingGroup->setEnabled(false);
0518     }
0519 
0520     m_leftRightCheck->setProperty("checked", m_Mount->isReversed(AXIS_RA));
0521     m_upDownCheck->setProperty("checked", m_Mount->isReversed(AXIS_DE));
0522 }
0523 
0524 void Mount::registerNewModule(const QString &name)
0525 {
0526     if (name == "Capture")
0527     {
0528         hasCaptureInterface = true;
0529         mf_state->setHasCaptureInterface(true);
0530     }
0531 
0532 }
0533 
0534 void Mount::updateTelescopeCoords(const SkyPoint &position, ISD::Mount::PierSide pierSide, const dms &ha)
0535 {
0536     if (m_Mount == nullptr || !m_Mount->isConnected())
0537         return;
0538 
0539     telescopeCoord = position;
0540 
0541     // No need to update coords if we are still parked.
0542     if (m_Status == ISD::Mount::MOUNT_PARKED && m_Status == m_Mount->status())
0543         return;
0544 
0545     // Ekos Mount Tab coords are always in JNow
0546     raOUT->setText(telescopeCoord.ra().toHMSString());
0547     decOUT->setText(telescopeCoord.dec().toDMSString());
0548 
0549     // Mount Control Panel coords depend on the switch
0550     if (m_JNowCheck->property("checked").toBool())
0551     {
0552         m_raValue->setProperty("text", telescopeCoord.ra().toHMSString());
0553         m_deValue->setProperty("text", telescopeCoord.dec().toDMSString());
0554     }
0555     else
0556     {
0557         m_raValue->setProperty("text", telescopeCoord.ra0().toHMSString());
0558         m_deValue->setProperty("text", telescopeCoord.dec0().toDMSString());
0559     }
0560 
0561     // Get horizontal coords
0562     azOUT->setText(telescopeCoord.az().toDMSString());
0563     m_azValue->setProperty("text", telescopeCoord.az().toDMSString());
0564     altOUT->setText(telescopeCoord.alt().toDMSString());
0565     m_altValue->setProperty("text", telescopeCoord.alt().toDMSString());
0566 
0567     dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
0568     dms haSigned(ha);
0569     QChar sgn('+');
0570 
0571     if (haSigned.Hours() > 12.0)
0572     {
0573         haSigned.setH(24.0 - haSigned.Hours());
0574         sgn = '-';
0575     }
0576 
0577     haOUT->setText(QString("%1%2").arg(sgn).arg(haSigned.toHMSString()));
0578 
0579     m_haValue->setProperty("text", haOUT->text());
0580     lstOUT->setText(lst.toHMSString());
0581 
0582     double currentAlt = telescopeCoord.altRefracted().Degrees();
0583 
0584     m_zaValue->setProperty("text", dms(90 - currentAlt).toDMSString());
0585 
0586     if (minimumAltLimit->isEnabled() && (currentAlt < minimumAltLimit->value() || currentAlt > maximumAltLimit->value()))
0587     {
0588         if (currentAlt < minimumAltLimit->value())
0589         {
0590             // Only stop if current altitude is less than last altitude indicate worse situation
0591             if (currentAlt < m_LastAltitude &&
0592                     (m_AbortAltDispatch == -1 ||
0593                      (m_Mount->isInMotion() /* && ++abortDispatch > ABORT_DISPATCH_LIMIT*/)))
0594             {
0595                 appendLogText(i18n("Telescope altitude is below minimum altitude limit of %1. Aborting motion...",
0596                                    QString::number(minimumAltLimit->value(), 'g', 3)));
0597                 m_Mount->abort();
0598                 m_Mount->setTrackEnabled(false);
0599                 //KNotification::event( QLatin1String( "OperationFailed" ));
0600                 KNotification::beep();
0601                 m_AbortAltDispatch++;
0602             }
0603         }
0604         else
0605         {
0606             // Only stop if current altitude is higher than last altitude indicate worse situation
0607             if (currentAlt > m_LastAltitude &&
0608                     (m_AbortAltDispatch == -1 ||
0609                      (m_Mount->isInMotion() /* && ++abortDispatch > ABORT_DISPATCH_LIMIT*/)))
0610             {
0611                 appendLogText(i18n("Telescope altitude is above maximum altitude limit of %1. Aborting motion...",
0612                                    QString::number(maximumAltLimit->value(), 'g', 3)));
0613                 m_Mount->abort();
0614                 m_Mount->setTrackEnabled(false);
0615                 //KNotification::event( QLatin1String( "OperationFailed" ));
0616                 KNotification::beep();
0617                 m_AbortAltDispatch++;
0618             }
0619         }
0620     }
0621     else
0622         m_AbortAltDispatch = -1;
0623 
0624     //qCDebug(KSTARS_EKOS_MOUNT) << "MaximumHaLimit " << MaximumHaLimit->isEnabled() << " value " << MaximumHaLimit->value();
0625 
0626     double haHours = rangeHA(ha.Hours());
0627     // handle Ha limit:
0628     // Telescope must report Pier Side
0629     // MaximumHaLimit must be enabled
0630     // for PierSide West -> East if Ha > MaximumHaLimit stop tracking
0631     // for PierSide East -> West if Ha > MaximumHaLimit - 12 stop Tracking
0632     if (maximumHaLimit->isEnabled())
0633     {
0634         // get hour angle limit
0635         double haLimit = maximumHaLimit->value();
0636         bool haLimitReached = false;
0637         switch(pierSide)
0638         {
0639             case ISD::Mount::PierSide::PIER_WEST:
0640                 haLimitReached = haHours > haLimit;
0641                 break;
0642             case ISD::Mount::PierSide::PIER_EAST:
0643                 haLimitReached = rangeHA(haHours + 12.0) > haLimit;
0644                 break;
0645             default:
0646                 // can't tell so always false
0647                 haLimitReached = false;
0648                 break;
0649         }
0650 
0651         qCDebug(KSTARS_EKOS_MOUNT) << "Ha: " << haHours <<
0652                                    " haLimit " << haLimit <<
0653                                    " " << ISD::Mount::pierSideStateString(m_Mount->pierSide()) <<
0654                                    " haLimitReached " << (haLimitReached ? "true" : "false") <<
0655                                    " lastHa " << m_LastHourAngle;
0656 
0657         // compare with last ha to avoid multiple calls
0658         if (haLimitReached && (rangeHA(haHours - m_LastHourAngle) >= 0 ) &&
0659                 (m_AbortHADispatch == -1 ||
0660                  m_Mount->isInMotion()))
0661         {
0662             // moved past the limit, so stop
0663             appendLogText(i18n("Telescope hour angle is more than the maximum hour angle of %1. Aborting motion...",
0664                                QString::number(maximumHaLimit->value(), 'g', 3)));
0665             m_Mount->abort();
0666             m_Mount->setTrackEnabled(false);
0667             //KNotification::event( QLatin1String( "OperationFailed" ));
0668             KNotification::beep();
0669             m_AbortHADispatch++;
0670             // ideally we pause and wait until we have passed the pier flip limit,
0671             // then do a pier flip and try to resume
0672             // this will need changing to use a target position because the current HA has stopped.
0673         }
0674     }
0675     else
0676         m_AbortHADispatch = -1;
0677 
0678     m_LastAltitude = currentAlt;
0679     m_LastHourAngle = haHours;
0680 
0681     ISD::Mount::Status currentStatus = m_Mount->status();
0682     if (m_Status != currentStatus)
0683     {
0684         qCDebug(KSTARS_EKOS_MOUNT) << "Mount status changed from " << m_Mount->statusString(m_Status)
0685                                    << " to " << m_Mount->statusString(currentStatus);
0686 
0687         //setScopeStatus(currentStatus);
0688 
0689         m_statusText->setProperty("text", m_Mount->statusString(currentStatus));
0690         m_Status = currentStatus;
0691         // forward
0692         emit newStatus(m_Status);
0693 
0694         setupParkUI();
0695         m_Park->setEnabled(!m_Mount->isParked());
0696         m_Unpark->setEnabled(m_Mount->isParked());
0697 
0698         QAction *a = KStars::Instance()->actionCollection()->action("telescope_track");
0699         if (a != nullptr)
0700             a->setChecked(currentStatus == ISD::Mount::MOUNT_TRACKING);
0701     }
0702 
0703     bool isTracking = (currentStatus == ISD::Mount::MOUNT_TRACKING);
0704     if (trackingGroup->isEnabled())
0705     {
0706         trackOnB->setChecked(isTracking);
0707         trackOffB->setChecked(!isTracking);
0708     }
0709 
0710     // handle pier side display
0711     pierSideLabel->setText(ISD::Mount::pierSideStateString(m_Mount->pierSide()));
0712 
0713     // Auto Park Timer
0714     if (autoParkTimer.isActive())
0715     {
0716         QTime remainingTime(0, 0, 0);
0717         remainingTime = remainingTime.addMSecs(autoParkTimer.remainingTime());
0718         countdownLabel->setText(remainingTime.toString("hh:mm:ss"));
0719         emit autoParkCountdownUpdated(countdownLabel->text());
0720     }
0721 }
0722 
0723 void Mount::updateProperty(INDI::Property prop)
0724 {
0725     if (prop.isNameMatch("GEOGRAPHIC_COORD") &&
0726             m_GPS != nullptr &&
0727             (prop.getDeviceName() == m_GPS->getDeviceName()) &&
0728             prop.getState() == IPS_OK)
0729     {
0730         syncGPS();
0731     }
0732     else if (prop.isNameMatch("EQUATORIAL_EOD_COORD") || prop.isNameMatch("EQUATORIAL_COORD"))
0733     {
0734         auto nvp = prop.getNumber();
0735 
0736         // if the meridian flip state machine is not initialized, return
0737         if (getMeridianFlipState().isNull())
0738             return;
0739 
0740         switch (getMeridianFlipState()->getMeridianFlipStage())
0741         {
0742             case MeridianFlipState::MF_INITIATED:
0743                 if (nvp->s == IPS_BUSY && m_Mount != nullptr && m_Mount->isSlewing())
0744                     getMeridianFlipState()->updateMeridianFlipStage(MeridianFlipState::MF_FLIPPING);
0745                 break;
0746 
0747             default:
0748                 break;
0749         }
0750     }
0751     else if (prop.isNameMatch("TELESCOPE_SLEW_RATE"))
0752     {
0753         auto svp = prop.getSwitch();
0754         auto index = svp->findOnSwitchIndex();
0755         m_SpeedSlider->setProperty("value", index);
0756         m_SpeedLabel->setProperty("text", i18nc(libindi_strings_context, svp->at(index)->getLabel()));
0757     }
0758 }
0759 
0760 bool Mount::setSlewRate(int index)
0761 {
0762     if (m_Mount)
0763         return m_Mount->setSlewRate(index);
0764 
0765     return false;
0766 }
0767 
0768 void Mount::setUpDownReversed(bool enabled)
0769 {
0770     Options::setUpDownReversed(enabled);
0771     if (m_Mount)
0772         m_Mount->setReversedEnabled(AXIS_DE, enabled);
0773 }
0774 
0775 void Mount::setLeftRightReversed(bool enabled)
0776 {
0777     Options::setLeftRightReversed(enabled);
0778     if (m_Mount)
0779         m_Mount->setReversedEnabled(AXIS_RA, enabled);
0780 }
0781 
0782 void Mount::setMeridianFlipValues(bool activate, double degrees)
0783 {
0784     executeMeridianFlip->setChecked(activate);
0785     meridianFlipOffsetDegrees->setValue(degrees);
0786 }
0787 
0788 void Mount::paaStageChanged(int stage)
0789 {
0790     // Clear the current target position is necessary due to a bug in some mount drivers
0791     // which report a mount slew instead of a mount motion. For these mounts, ending a slew
0792     // leads to setting the current target position, which is necessary for meridian flips
0793     // Since we want to avoid meridian flips during and after finishing PAA, it needs to
0794     // be set to nullptr.
0795 
0796     if (stage != PolarAlignmentAssistant::PAH_IDLE)
0797         mf_state->clearTargetPosition();
0798 
0799     switch (stage)
0800     {
0801         // deactivate the meridian flip when the first capture is taken
0802         case PolarAlignmentAssistant::PAH_FIRST_CAPTURE:
0803         case PolarAlignmentAssistant::PAH_FIRST_SOLVE:
0804             if (mf_state->isEnabled())
0805             {
0806                 appendLogText(i18n("Meridian flip set inactive during polar alignment."));
0807                 mf_state->setEnabled(false);
0808             }
0809             break;
0810         // activate it when the last rotation is finished or stopped
0811         // for safety reasons, we add all stages after the last rotation
0812         case PolarAlignmentAssistant::PAH_THIRD_CAPTURE:
0813         case PolarAlignmentAssistant::PAH_THIRD_SOLVE:
0814         case PolarAlignmentAssistant::PAH_STAR_SELECT:
0815         case PolarAlignmentAssistant::PAH_REFRESH:
0816         case PolarAlignmentAssistant::PAH_POST_REFRESH:
0817         case PolarAlignmentAssistant::PAH_IDLE:
0818             if (executeMeridianFlip->isChecked() && mf_state->isEnabled() == false)
0819             {
0820                 appendLogText(i18n("Polar alignment motions finished, meridian flip activated."));
0821                 mf_state->setEnabled(executeMeridianFlip->isChecked());
0822             }
0823             break;
0824     }
0825 }
0826 
0827 void Mount::appendLogText(const QString &text)
0828 {
0829     m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
0830                               KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
0831 
0832     qCInfo(KSTARS_EKOS_MOUNT) << text;
0833 
0834     emit newLog(text);
0835 }
0836 
0837 void Mount::updateLog(int messageID)
0838 {
0839     if (m_Mount == nullptr)
0840         return;
0841 
0842     auto message = m_Mount->getMessage(messageID);
0843     m_LogText.insert(0, i18nc("Message shown in Ekos Mount module", "%1", message));
0844 
0845     emit newLog(message);
0846 }
0847 
0848 void Mount::clearLog()
0849 {
0850     m_LogText.clear();
0851     emit newLog(QString());
0852 }
0853 
0854 void Mount::motionCommand(int command, int NS, int WE)
0855 {
0856     if (m_Mount == nullptr || !m_Mount->isConnected())
0857         return;
0858 
0859     if (NS != -1)
0860     {
0861         m_Mount->MoveNS(static_cast<ISD::Mount::VerticalMotion>(NS),
0862                         static_cast<ISD::Mount::MotionCommand>(command));
0863     }
0864 
0865     if (WE != -1)
0866     {
0867         m_Mount->MoveWE(static_cast<ISD::Mount::HorizontalMotion>(WE),
0868                         static_cast<ISD::Mount::MotionCommand>(command));
0869     }
0870 }
0871 
0872 
0873 void Mount::doPulse(GuideDirection ra_dir, int ra_msecs, GuideDirection dec_dir, int dec_msecs)
0874 {
0875     if (m_Mount == nullptr || !m_Mount->isConnected())
0876         return;
0877 
0878     m_Mount->doPulse(ra_dir, ra_msecs, dec_dir, dec_msecs);
0879 }
0880 
0881 void Mount::saveLimits()
0882 {
0883     if (m_Mount == nullptr)
0884         return;
0885 
0886     m_Mount->setAltLimits(minimumAltLimit->value(), maximumAltLimit->value());
0887 }
0888 
0889 void Mount::setAltitudeLimits(bool enable)
0890 {
0891     if (enable)
0892     {
0893         minAltLabel->setEnabled(true);
0894         maxAltLabel->setEnabled(true);
0895 
0896         minimumAltLimit->setEnabled(true);
0897         maximumAltLimit->setEnabled(true);
0898 
0899         if (m_Mount)
0900             m_Mount->setAltLimits(minimumAltLimit->value(), maximumAltLimit->value());
0901     }
0902     else
0903     {
0904         minAltLabel->setEnabled(false);
0905         maxAltLabel->setEnabled(false);
0906 
0907         minimumAltLimit->setEnabled(false);
0908         maximumAltLimit->setEnabled(false);
0909 
0910         if (m_Mount)
0911             m_Mount->setAltLimits(-91, +91);
0912     }
0913 }
0914 
0915 // Used for meridian flip
0916 void Mount::resumeAltLimits()
0917 {
0918     //Only enable if it was already enabled before and the MinimumAltLimit is currently disabled.
0919     if (m_AltitudeLimitEnabled && minimumAltLimit->isEnabled() == false)
0920         setAltitudeLimits(true);
0921 }
0922 
0923 // Used for meridian flip
0924 void Mount::suspendAltLimits()
0925 {
0926     m_AltitudeLimitEnabled = enableAltitudeLimits->isChecked();
0927     setAltitudeLimits(false);
0928 }
0929 
0930 void Mount::enableHourAngleLimits(bool enable)
0931 {
0932     maxHaLabel->setEnabled(enable);
0933     maximumHaLimit->setEnabled(enable);
0934 }
0935 
0936 void Mount::enableHaLimits()
0937 {
0938     //Only enable if it was already enabled before and the minHaLimit is currently disabled.
0939     if (m_HourAngleLimitEnabled && maximumHaLimit->isEnabled() == false)
0940         enableHourAngleLimits(true);
0941 }
0942 
0943 void Mount::disableHaLimits()
0944 {
0945     m_HourAngleLimitEnabled = enableHaLimit->isChecked();
0946 
0947     enableHourAngleLimits(false);
0948 }
0949 
0950 QList<double> Mount::altitudeLimits()
0951 {
0952     QList<double> limits;
0953 
0954     limits.append(minimumAltLimit->value());
0955     limits.append(maximumAltLimit->value());
0956 
0957     return limits;
0958 }
0959 
0960 void Mount::setAltitudeLimits(QList<double> limits)
0961 {
0962     minimumAltLimit->setValue(limits[0]);
0963     maximumAltLimit->setValue(limits[1]);
0964 }
0965 
0966 void Mount::setAltitudeLimitsEnabled(bool enable)
0967 {
0968     enableAltitudeLimits->setChecked(enable);
0969 }
0970 
0971 bool Mount::altitudeLimitsEnabled()
0972 {
0973     return enableAltitudeLimits->isChecked();
0974 }
0975 
0976 double Mount::hourAngleLimit()
0977 {
0978     return maximumHaLimit->value();
0979 }
0980 
0981 void Mount::setHourAngleLimit(double limit)
0982 {
0983     maximumHaLimit->setValue(limit);
0984 }
0985 
0986 void Mount::setHourAngleLimitEnabled(bool enable)
0987 {
0988     enableHaLimit->setChecked(enable);
0989 }
0990 
0991 bool Mount::hourAngleLimitEnabled()
0992 {
0993     return enableHaLimit->isChecked();
0994 }
0995 
0996 void Mount::setJ2000Enabled(bool enabled)
0997 {
0998     m_J2000Check->setProperty("checked", enabled);
0999 }
1000 
1001 bool Mount::gotoTarget(const QString &target)
1002 {
1003     SkyObject *object = KStarsData::Instance()->skyComposite()->findByName(target, false);
1004 
1005     if (object != nullptr)
1006     {
1007         object->updateCoordsNow(KStarsData::Instance()->updateNum());
1008         return slew(object->ra().Hours(), object->dec().Degrees());
1009     }
1010 
1011     return false;
1012 }
1013 
1014 bool Mount::gotoTarget(const SkyPoint &target)
1015 {
1016     return slew(target.ra().Hours(), target.dec().Degrees());
1017 }
1018 
1019 bool Mount::syncTarget(const QString &target)
1020 {
1021     SkyObject *object = KStarsData::Instance()->skyComposite()->findByName(target, false);
1022 
1023     if (object != nullptr)
1024     {
1025         object->updateCoordsNow(KStarsData::Instance()->updateNum());
1026         return sync(object->ra().Hours(), object->dec().Degrees());
1027     }
1028 
1029     return false;
1030 }
1031 
1032 bool Mount::slew(const QString &RA, const QString &DEC)
1033 {
1034     dms ra, de;
1035 
1036     if (m_equatorialCheck->property("checked").toBool())
1037     {
1038         ra = dms::fromString(RA, false);
1039         de = dms::fromString(DEC, true);
1040     }
1041 
1042     if (m_horizontalCheck->property("checked").toBool())
1043     {
1044         dms az = dms::fromString(RA, true);
1045         dms at = dms::fromString(DEC, true);
1046         SkyPoint target;
1047         target.setAz(az);
1048         target.setAlt(at);
1049         target.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
1050         ra = target.ra();
1051         de = target.dec();
1052     }
1053 
1054     if (m_haEquatorialCheck->property("checked").toBool())
1055     {
1056         dms ha = dms::fromString(RA, false);
1057         de = dms::fromString(DEC, true);
1058         dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1059         ra = (lst - ha + dms(360.0)).reduce();
1060     }
1061 
1062     // If J2000 was checked and the Mount is _not_ already using native J2000 coordinates
1063     // then we need to convert J2000 to JNow. Otherwise, we send J2000 as is.
1064     if (m_J2000Check->property("checked").toBool() && m_Mount && m_Mount->isJ2000() == false)
1065     {
1066         // J2000 ---> JNow
1067         SkyPoint J2000Coord(ra, de);
1068         J2000Coord.setRA0(ra);
1069         J2000Coord.setDec0(de);
1070         J2000Coord.apparentCoord(static_cast<long double>(J2000), KStars::Instance()->data()->ut().djd());
1071 
1072         ra = J2000Coord.ra();
1073         de = J2000Coord.dec();
1074     }
1075 
1076     return slew(ra.Hours(), de.Degrees());
1077 }
1078 
1079 bool Mount::slew(double RA, double DEC)
1080 {
1081     if (m_Mount == nullptr || m_Mount->isConnected() == false)
1082         return false;
1083 
1084     // calculate the new target
1085     targetPosition = new SkyPoint(RA, DEC);
1086     SkyPoint J2000Coord(targetPosition->ra(), targetPosition->dec());
1087     J2000Coord.catalogueCoord(KStarsData::Instance()->ut().djd());
1088     targetPosition->setRA0(J2000Coord.ra());
1089     targetPosition->setDec0(J2000Coord.dec());
1090 
1091     mf_state->setTargetPosition(targetPosition);
1092     mf_state->resetMeridianFlip();
1093 
1094     qCDebug(KSTARS_EKOS_MOUNT) << "Slewing to RA=" <<
1095                                targetPosition->ra().toHMSString() <<
1096                                "DEC=" << targetPosition->dec().toDMSString();
1097     qCDebug(KSTARS_EKOS_MOUNT) << "Initial HA " << initialHA() << ", flipDelayHrs " << mf_state->getFlipDelayHrs() <<
1098                                "MFStatus " << MeridianFlipState::meridianFlipStatusString(mf_state->getMeridianFlipMountState());
1099 
1100     // start the slew
1101     return(m_Mount->Slew(targetPosition));
1102 }
1103 
1104 
1105 SkyPoint Mount::currentTarget()
1106 {
1107     if (targetPosition != nullptr)
1108         return *targetPosition;
1109 
1110     qCWarning(KSTARS_EKOS_MOUNT) << "No target position defined!";
1111     // since we need to answer something, we take the current mount position
1112     return telescopeCoord;
1113 }
1114 
1115 
1116 bool Mount::sync(const QString &RA, const QString &DEC)
1117 {
1118     dms ra, de;
1119 
1120     if (m_equatorialCheck->property("checked").toBool())
1121     {
1122         ra = dms::fromString(RA, false);
1123         de = dms::fromString(DEC, true);
1124     }
1125 
1126     if (m_horizontalCheck->property("checked").toBool())
1127     {
1128         dms az = dms::fromString(RA, true);
1129         dms at = dms::fromString(DEC, true);
1130         SkyPoint target;
1131         target.setAz(az);
1132         target.setAlt(at);
1133         target.HorizontalToEquatorial(KStars::Instance()->data()->lst(), KStars::Instance()->data()->geo()->lat());
1134         ra = target.ra();
1135         de = target.dec();
1136     }
1137 
1138     if (m_haEquatorialCheck->property("checked").toBool())
1139     {
1140         dms ha = dms::fromString(RA, false);
1141         de = dms::fromString(DEC, true);
1142         dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1143         ra = (lst - ha + dms(360.0)).reduce();
1144     }
1145 
1146     if (m_J2000Check->property("checked").toBool())
1147     {
1148         // J2000 ---> JNow
1149         SkyPoint J2000Coord(ra, de);
1150         J2000Coord.setRA0(ra);
1151         J2000Coord.setDec0(de);
1152         J2000Coord.updateCoordsNow(KStarsData::Instance()->updateNum());
1153 
1154         ra = J2000Coord.ra();
1155         de = J2000Coord.dec();
1156     }
1157 
1158     return sync(ra.Hours(), de.Degrees());
1159 }
1160 
1161 bool Mount::sync(double RA, double DEC)
1162 {
1163     if (m_Mount == nullptr || m_Mount->isConnected() == false)
1164         return false;
1165 
1166     return m_Mount->Sync(RA, DEC);
1167 }
1168 
1169 bool Mount::abort()
1170 {
1171     if (m_Mount == nullptr)
1172         return false;
1173 
1174     return m_Mount->abort();
1175 }
1176 
1177 IPState Mount::slewStatus()
1178 {
1179     if (m_Mount == nullptr)
1180         return IPS_ALERT;
1181 
1182     return m_Mount->getState("EQUATORIAL_EOD_COORD");
1183 }
1184 
1185 QList<double> Mount::equatorialCoords()
1186 {
1187     double ra {0}, dec {0};
1188     QList<double> coords;
1189 
1190     if (m_Mount)
1191         m_Mount->getEqCoords(&ra, &dec);
1192     coords.append(ra);
1193     coords.append(dec);
1194 
1195     return coords;
1196 }
1197 
1198 QList<double> Mount::horizontalCoords()
1199 {
1200     QList<double> coords;
1201 
1202     coords.append(telescopeCoord.az().Degrees());
1203     coords.append(telescopeCoord.alt().Degrees());
1204 
1205     return coords;
1206 }
1207 
1208 ///
1209 /// \brief Mount::hourAngle
1210 /// \return returns the current mount hour angle in hours in the range -12 to +12
1211 ///
1212 double Mount::hourAngle()
1213 {
1214     dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1215     dms ha(lst.Degrees() - telescopeCoord.ra().Degrees());
1216     return rangeHA(ha.Hours());
1217 }
1218 
1219 bool Mount::canPark()
1220 {
1221     if (m_Mount == nullptr)
1222         return false;
1223 
1224     return m_Mount->canPark();
1225 }
1226 
1227 bool Mount::park()
1228 {
1229     if (m_Mount == nullptr || m_Mount->canPark() == false)
1230         return false;
1231 
1232     return m_Mount->park();
1233 }
1234 
1235 bool Mount::unpark()
1236 {
1237     if (m_Mount == nullptr || m_Mount->canPark() == false)
1238         return false;
1239 
1240     return m_Mount->unpark();
1241 }
1242 
1243 
1244 void Mount::toggleMountToolBox()
1245 {
1246     if (m_BaseView->isVisible())
1247     {
1248         m_BaseView->hide();
1249         QAction *a = KStars::Instance()->actionCollection()->action("show_mount_box");
1250         if (a)
1251             a->setChecked(false);
1252     }
1253     else
1254     {
1255         m_BaseView->show();
1256         QAction *a = KStars::Instance()->actionCollection()->action("show_mount_box");
1257         if (a)
1258             a->setChecked(true);
1259     }
1260 }
1261 
1262 void Mount::findTarget()
1263 {
1264     if (FindDialog::Instance()->execWithParent(Ekos::Manager::Instance()) == QDialog::Accepted)
1265     {
1266         SkyObject *object = FindDialog::Instance()->targetObject();
1267         if (object != nullptr)
1268         {
1269             KStarsData * const data = KStarsData::Instance();
1270 
1271             SkyObject *o = object->clone();
1272             o->updateCoords(data->updateNum(), true, data->geo()->lat(), data->lst(), false);
1273 
1274             m_equatorialCheck->setProperty("checked", true);
1275 
1276             m_targetText->setProperty("text", o->name());
1277 
1278             if (m_JNowCheck->property("checked").toBool())
1279             {
1280                 m_targetRAText->setProperty("text", o->ra().toHMSString());
1281                 m_targetDEText->setProperty("text", o->dec().toDMSString());
1282             }
1283             else
1284             {
1285                 m_targetRAText->setProperty("text", o->ra0().toHMSString());
1286                 m_targetDEText->setProperty("text", o->dec0().toDMSString());
1287             }
1288         }
1289     }
1290 }
1291 
1292 //++++ converters for target coordinate display in Mount Control box
1293 
1294 bool Mount::raDecToAzAlt(QString qsRA, QString qsDec)
1295 {
1296     dms RA, Dec;
1297 
1298     if (!RA.setFromString(qsRA, false) || !Dec.setFromString(qsDec, true))
1299         return false;
1300 
1301     SkyPoint targetCoord(RA, Dec);
1302 
1303     targetCoord.EquatorialToHorizontal(KStarsData::Instance()->lst(),
1304                                        KStarsData::Instance()->geo()->lat());
1305 
1306     m_targetRAText->setProperty("text", targetCoord.az().toDMSString());
1307     m_targetDEText->setProperty("text", targetCoord.alt().toDMSString());
1308 
1309     return true;
1310 }
1311 
1312 bool  Mount::raDecToHaDec(QString qsRA)
1313 {
1314     dms RA;
1315 
1316     if (!RA.setFromString(qsRA, false))
1317         return false;
1318 
1319     dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1320 
1321     dms HA = (lst - RA + dms(360.0)).reduce();
1322 
1323     QChar sgn('+');
1324     if (HA.Hours() > 12.0)
1325     {
1326         HA.setH(24.0 - HA.Hours());
1327         sgn = '-';
1328     }
1329 
1330     m_targetRAText->setProperty("text", QString("%1%2").arg(sgn).arg(HA.toHMSString()));
1331 
1332     return true;
1333 }
1334 
1335 bool  Mount::azAltToRaDec(QString qsAz, QString qsAlt)
1336 {
1337     dms Az, Alt;
1338 
1339     if (!Az.setFromString(qsAz, true) || !Alt.setFromString(qsAlt, true))
1340         return false;
1341 
1342     SkyPoint targetCoord;
1343     targetCoord.setAz(Az);
1344     targetCoord.setAlt(Alt);
1345 
1346     targetCoord.HorizontalToEquatorial(KStars::Instance()->data()->lst(),
1347                                        KStars::Instance()->data()->geo()->lat());
1348 
1349     m_targetRAText->setProperty("text", targetCoord.ra().toHMSString());
1350     m_targetDEText->setProperty("text", targetCoord.dec().toDMSString());
1351 
1352     return true;
1353 }
1354 
1355 bool  Mount::azAltToHaDec(QString qsAz, QString qsAlt)
1356 {
1357     dms Az, Alt;
1358 
1359     if (!Az.setFromString(qsAz, true) || !Alt.setFromString(qsAlt, true))
1360         return false;
1361 
1362     SkyPoint targetCoord;
1363     targetCoord.setAz(Az);
1364     targetCoord.setAlt(Alt);
1365 
1366     dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1367 
1368     targetCoord.HorizontalToEquatorial(&lst, KStars::Instance()->data()->geo()->lat());
1369 
1370     dms HA = (lst - targetCoord.ra() + dms(360.0)).reduce();
1371 
1372     QChar sgn('+');
1373     if (HA.Hours() > 12.0)
1374     {
1375         HA.setH(24.0 - HA.Hours());
1376         sgn = '-';
1377     }
1378 
1379     m_targetRAText->setProperty("text", QString("%1%2").arg(sgn).arg(HA.toHMSString()));
1380     m_targetDEText->setProperty("text", targetCoord.dec().toDMSString());
1381 
1382 
1383     return true;
1384 }
1385 
1386 bool  Mount::haDecToRaDec(QString qsHA)
1387 {
1388     dms HA;
1389 
1390     if (!HA.setFromString(qsHA, false))
1391         return false;
1392 
1393     dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1394     dms RA = (lst - HA + dms(360.0)).reduce();
1395 
1396     m_targetRAText->setProperty("text", RA.toHMSString());
1397 
1398     return true;
1399 }
1400 
1401 bool  Mount::haDecToAzAlt(QString qsHA, QString qsDec)
1402 {
1403     dms HA, Dec;
1404 
1405     if (!HA.setFromString(qsHA, false) || !Dec.setFromString(qsDec, true))
1406         return false;
1407 
1408     dms lst = KStarsData::Instance()->geo()->GSTtoLST(KStarsData::Instance()->clock()->utc().gst());
1409     dms RA = (lst - HA + dms(360.0)).reduce();
1410 
1411     SkyPoint targetCoord;
1412     targetCoord.setRA(RA);
1413     targetCoord.setDec(Dec);
1414 
1415     targetCoord.EquatorialToHorizontal(&lst, KStars::Instance()->data()->geo()->lat());
1416 
1417     m_targetRAText->setProperty("text", targetCoord.az().toDMSString());
1418     m_targetDEText->setProperty("text", targetCoord.alt().toDMSString());
1419 
1420     return true;
1421 }
1422 
1423 //---- end: converters for target coordinate display in Mount Control box
1424 
1425 void Mount::centerMount()
1426 {
1427     if (m_Mount)
1428         m_Mount->find();
1429 }
1430 
1431 bool Mount::resetModel()
1432 {
1433     if (m_Mount == nullptr)
1434         return false;
1435 
1436     if (m_Mount->hasAlignmentModel() == false)
1437         return false;
1438 
1439     if (m_Mount->clearAlignmentModel())
1440     {
1441         appendLogText(i18n("Alignment Model cleared."));
1442         return true;
1443     }
1444 
1445     appendLogText(i18n("Failed to clear Alignment Model."));
1446     return false;
1447 }
1448 
1449 
1450 void Mount::setScopeStatus(ISD::Mount::Status status)
1451 {
1452     if (m_Status != status)
1453     {
1454         m_statusText->setProperty("text", m_Mount->statusString(status));
1455         m_Status = status;
1456         // forward
1457         emit newStatus(status);
1458     }
1459 }
1460 
1461 
1462 
1463 void Mount::setTrackEnabled(bool enabled)
1464 {
1465     if (enabled)
1466         trackOnB->click();
1467     else
1468         trackOffB->click();
1469 }
1470 
1471 int Mount::slewRate()
1472 {
1473     if (m_Mount == nullptr)
1474         return -1;
1475 
1476     return m_Mount->getSlewRate();
1477 }
1478 
1479 //QJsonArray Mount::getScopes() const
1480 //{
1481 //    QJsonArray scopes;
1482 //    if (currentTelescope == nullptr)
1483 //        return scopes;
1484 
1485 //    QJsonObject primary =
1486 //    {
1487 //        {"name", "Primary"},
1488 //        {"mount", currentTelescope->getDeviceName()},
1489 //        {"aperture", primaryScopeApertureIN->value()},
1490 //        {"focalLength", primaryScopeFocalIN->value()},
1491 //    };
1492 
1493 //    scopes.append(primary);
1494 
1495 //    QJsonObject guide =
1496 //    {
1497 //        {"name", "Guide"},
1498 //        {"mount", currentTelescope->getDeviceName()},
1499 //        {"aperture", primaryScopeApertureIN->value()},
1500 //        {"focalLength", primaryScopeFocalIN->value()},
1501 //    };
1502 
1503 //    scopes.append(guide);
1504 
1505 //    return scopes;
1506 //}
1507 
1508 bool Mount::autoParkEnabled()
1509 {
1510     return autoParkTimer.isActive();
1511 }
1512 
1513 void Mount::setAutoParkEnabled(bool enable)
1514 {
1515     if (enable)
1516         startParkTimer();
1517     else
1518         stopParkTimer();
1519 }
1520 
1521 void Mount::setAutoParkDailyEnabled(bool enabled)
1522 {
1523     parkEveryDay->setChecked(enabled);
1524 }
1525 
1526 void Mount::setAutoParkStartup(QTime startup)
1527 {
1528     autoParkTime->setTime(startup);
1529 }
1530 
1531 bool Mount::meridianFlipEnabled()
1532 {
1533     return executeMeridianFlip->isChecked();
1534 }
1535 
1536 double Mount::meridianFlipValue()
1537 {
1538     return meridianFlipOffsetDegrees->value();
1539 }
1540 
1541 void Mount::stopTimers()
1542 {
1543     autoParkTimer.stop();
1544     if (m_Mount)
1545         m_Mount->stopTimers();
1546 }
1547 
1548 void Mount::startParkTimer()
1549 {
1550     if (m_Mount == nullptr || m_ParkStatus == ISD::PARK_UNKNOWN)
1551         return;
1552 
1553     if (m_Mount->isParked())
1554     {
1555         appendLogText(i18n("Mount already parked."));
1556         return;
1557     }
1558 
1559     auto parkTime = autoParkTime->time();
1560 
1561     qCDebug(KSTARS_EKOS_MOUNT) << "Parking time is" << parkTime.toString();
1562     QDateTime currentDateTime = KStarsData::Instance()->lt();
1563     QDateTime parkDateTime(currentDateTime);
1564 
1565     parkDateTime.setTime(parkTime);
1566     qint64 parkMilliSeconds = parkDateTime.msecsTo(currentDateTime);
1567     qCDebug(KSTARS_EKOS_MOUNT) << "Until parking time:" << parkMilliSeconds << "ms or" << parkMilliSeconds / (60 * 60 * 1000)
1568                                << "hours";
1569     if (parkMilliSeconds > 0)
1570     {
1571         qCDebug(KSTARS_EKOS_MOUNT) << "Added a day to parking time...";
1572         parkDateTime = parkDateTime.addDays(1);
1573         parkMilliSeconds = parkDateTime.msecsTo(currentDateTime);
1574 
1575         int hours = static_cast<int>(parkMilliSeconds / (1000 * 60 * 60));
1576         if (hours > 0)
1577         {
1578             // No need to display warning for every day check
1579             if (parkEveryDay->isChecked() == false)
1580                 appendLogText(i18n("Parking time cannot be in the past."));
1581             return;
1582         }
1583     }
1584 
1585     parkMilliSeconds = std::abs(parkMilliSeconds);
1586 
1587     if (parkMilliSeconds > 24 * 60 * 60 * 1000)
1588     {
1589         appendLogText(i18n("Parking time must be within 24 hours of current time."));
1590         return;
1591     }
1592 
1593     if (parkMilliSeconds > 12 * 60 * 60 * 1000)
1594         appendLogText(i18n("Warning! Parking time is more than 12 hours away."));
1595 
1596     appendLogText(i18n("Caution: do not use Auto Park while scheduler is active."));
1597 
1598     autoParkTimer.setInterval(static_cast<int>(parkMilliSeconds));
1599     autoParkTimer.start();
1600 
1601     startTimerB->setEnabled(false);
1602     stopTimerB->setEnabled(true);
1603 }
1604 
1605 void Mount::stopParkTimer()
1606 {
1607     autoParkTimer.stop();
1608     countdownLabel->setText("00:00:00");
1609     emit autoParkCountdownUpdated("00:00:00");
1610     stopTimerB->setEnabled(false);
1611     startTimerB->setEnabled(true);
1612 }
1613 
1614 void Mount::startAutoPark()
1615 {
1616     appendLogText(i18n("Parking timer is up."));
1617     autoParkTimer.stop();
1618     startTimerB->setEnabled(true);
1619     stopTimerB->setEnabled(false);
1620     countdownLabel->setText("00:00:00");
1621     emit autoParkCountdownUpdated("00:00:00");
1622     if (m_Mount)
1623     {
1624         if (m_Mount->isParked() == false)
1625         {
1626             appendLogText(i18n("Starting auto park..."));
1627             park();
1628         }
1629     }
1630 }
1631 
1632 void Mount::syncAxisReversed(INDI_EQ_AXIS axis, bool reversed)
1633 {
1634     if (axis == AXIS_RA)
1635         m_leftRightCheck->setProperty("checked", reversed);
1636     else
1637         m_upDownCheck->setProperty("checked", reversed);
1638 }
1639 
1640 void Mount::setupOpticalTrainManager()
1641 {
1642     connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &Mount::refreshOpticalTrain);
1643     connect(trainB, &QPushButton::clicked, this, [this]()
1644     {
1645         OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
1646     });
1647     connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
1648     {
1649         ProfileSettings::Instance()->setOneSetting(ProfileSettings::MountOpticalTrain,
1650                 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index)));
1651         refreshOpticalTrain();
1652         emit trainChanged();
1653     });
1654 }
1655 
1656 void Mount::refreshOpticalTrain()
1657 {
1658     opticalTrainCombo->blockSignals(true);
1659     opticalTrainCombo->clear();
1660     opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
1661     trainB->setEnabled(true);
1662 
1663     QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::MountOpticalTrain);
1664 
1665     if (trainID.isValid())
1666     {
1667         auto id = trainID.toUInt();
1668 
1669         // If train not found, select the first one available.
1670         if (OpticalTrainManager::Instance()->exists(id) == false)
1671         {
1672             qCWarning(KSTARS_EKOS_MOUNT) << "Optical train doesn't exist for id" << id;
1673             id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
1674         }
1675 
1676         auto name = OpticalTrainManager::Instance()->name(id);
1677 
1678         opticalTrainCombo->setCurrentText(name);
1679 
1680         auto mount = OpticalTrainManager::Instance()->getMount(name);
1681         setMount(mount);
1682 
1683         auto scope = OpticalTrainManager::Instance()->getScope(name);
1684         opticalTrainCombo->setToolTip(scope["name"].toString());
1685 
1686         // Load train settings
1687         OpticalTrainSettings::Instance()->setOpticalTrainID(id);
1688         auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::Mount);
1689         if (settings.isValid())
1690             setAllSettings(settings.toJsonObject().toVariantMap());
1691         else
1692             m_Settings = m_GlobalSettings;
1693     }
1694 
1695     opticalTrainCombo->blockSignals(false);
1696 }
1697 
1698 ///////////////////////////////////////////////////////////////////////////////////////////
1699 ///
1700 ///////////////////////////////////////////////////////////////////////////////////////////
1701 QVariantMap Mount::getAllSettings() const
1702 {
1703     QVariantMap settings;
1704 
1705     // All Combo Boxes
1706     for (auto &oneWidget : findChildren<QComboBox*>())
1707         settings.insert(oneWidget->objectName(), oneWidget->currentText());
1708 
1709     // All Double Spin Boxes
1710     for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1711         settings.insert(oneWidget->objectName(), oneWidget->value());
1712 
1713     // All Spin Boxes
1714     for (auto &oneWidget : findChildren<QSpinBox*>())
1715         settings.insert(oneWidget->objectName(), oneWidget->value());
1716 
1717     // All Checkboxes
1718     for (auto &oneWidget : findChildren<QCheckBox*>())
1719         settings.insert(oneWidget->objectName(), oneWidget->isChecked());
1720 
1721     // All Time
1722     for (auto &oneWidget : findChildren<QTimeEdit*>())
1723         settings.insert(oneWidget->objectName(), oneWidget->time().toString());
1724 
1725     return settings;
1726 }
1727 
1728 ///////////////////////////////////////////////////////////////////////////////////////////
1729 ///
1730 ///////////////////////////////////////////////////////////////////////////////////////////
1731 void Mount::setAllSettings(const QVariantMap &settings)
1732 {
1733     // Disconnect settings that we don't end up calling syncSettings while
1734     // performing the changes.
1735     disconnectSyncSettings();
1736 
1737     for (auto &name : settings.keys())
1738     {
1739         // Combo
1740         auto comboBox = findChild<QComboBox*>(name);
1741         if (comboBox)
1742         {
1743             syncControl(settings, name, comboBox);
1744             continue;
1745         }
1746 
1747         // Double spinbox
1748         auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
1749         if (doubleSpinBox)
1750         {
1751             syncControl(settings, name, doubleSpinBox);
1752             continue;
1753         }
1754 
1755         // spinbox
1756         auto spinBox = findChild<QSpinBox*>(name);
1757         if (spinBox)
1758         {
1759             syncControl(settings, name, spinBox);
1760             continue;
1761         }
1762 
1763         // checkbox
1764         auto checkbox = findChild<QCheckBox*>(name);
1765         if (checkbox)
1766         {
1767             syncControl(settings, name, checkbox);
1768             continue;
1769         }
1770 
1771         // timeEdit
1772         auto timeEdit = findChild<QTimeEdit*>(name);
1773         if (timeEdit)
1774         {
1775             syncControl(settings, name, timeEdit);
1776             continue;
1777         }
1778     }
1779 
1780     // Sync to options
1781     for (auto &key : settings.keys())
1782     {
1783         auto value = settings[key];
1784         // Save immediately
1785         Options::self()->setProperty(key.toLatin1(), value);
1786         Options::self()->save();
1787 
1788         m_Settings[key] = value;
1789         m_GlobalSettings[key] = value;
1790     }
1791 
1792     emit settingsUpdated(getAllSettings());
1793 
1794     // Save to optical train specific settings as well
1795     OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
1796     OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Mount, m_Settings);
1797 
1798     // Restablish connections
1799     connectSyncSettings();
1800 }
1801 
1802 ///////////////////////////////////////////////////////////////////////////////////////////
1803 ///
1804 ///////////////////////////////////////////////////////////////////////////////////////////
1805 bool Mount::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget)
1806 {
1807     QSpinBox *pSB = nullptr;
1808     QDoubleSpinBox *pDSB = nullptr;
1809     QCheckBox *pCB = nullptr;
1810     QComboBox *pComboBox = nullptr;
1811     QTimeEdit *pTimeEdit = nullptr;
1812     bool ok = false;
1813 
1814     if ((pSB = qobject_cast<QSpinBox *>(widget)))
1815     {
1816         const int value = settings[key].toInt(&ok);
1817         if (ok)
1818         {
1819             pSB->setValue(value);
1820             return true;
1821         }
1822     }
1823     else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
1824     {
1825         const double value = settings[key].toDouble(&ok);
1826         if (ok)
1827         {
1828             pDSB->setValue(value);
1829             return true;
1830         }
1831     }
1832     else if ((pCB = qobject_cast<QCheckBox *>(widget)))
1833     {
1834         const bool value = settings[key].toBool();
1835         pCB->setChecked(value);
1836         return true;
1837     }
1838     // ONLY FOR STRINGS, not INDEX
1839     else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
1840     {
1841         const QString value = settings[key].toString();
1842         pComboBox->setCurrentText(value);
1843         return true;
1844     }
1845     else if ((pTimeEdit = qobject_cast<QTimeEdit *>(widget)))
1846     {
1847         const QString value = settings[key].toString();
1848         pTimeEdit->setTime(QTime::fromString(value));
1849         return true;
1850     }
1851 
1852     return false;
1853 };
1854 
1855 ///////////////////////////////////////////////////////////////////////////////////////////
1856 ///
1857 ///////////////////////////////////////////////////////////////////////////////////////////
1858 void Mount::syncSettings()
1859 {
1860     QDoubleSpinBox *dsb = nullptr;
1861     QSpinBox *sb = nullptr;
1862     QCheckBox *cb = nullptr;
1863     QComboBox *cbox = nullptr;
1864     QTimeEdit *timeEdit = nullptr;
1865 
1866     QString key;
1867     QVariant value;
1868 
1869     if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
1870     {
1871         key = dsb->objectName();
1872         value = dsb->value();
1873 
1874     }
1875     else if ( (sb = qobject_cast<QSpinBox*>(sender())))
1876     {
1877         key = sb->objectName();
1878         value = sb->value();
1879     }
1880     else if ( (cb = qobject_cast<QCheckBox*>(sender())))
1881     {
1882         key = cb->objectName();
1883         value = cb->isChecked();
1884     }
1885     else if ( (cbox = qobject_cast<QComboBox*>(sender())))
1886     {
1887         key = cbox->objectName();
1888         value = cbox->currentText();
1889     }
1890     else if ( (timeEdit = qobject_cast<QTimeEdit*>(sender())))
1891     {
1892         key = timeEdit->objectName();
1893         value = timeEdit->time().toString();
1894     }
1895 
1896     // Save immediately
1897     Options::self()->setProperty(key.toLatin1(), value);
1898     Options::self()->save();
1899 
1900     m_Settings[key] = value;
1901     m_GlobalSettings[key] = value;
1902 
1903     emit settingsUpdated(getAllSettings());
1904 
1905     // Save to optical train specific settings as well
1906     OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
1907     OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::Mount, m_Settings);
1908 }
1909 
1910 ///////////////////////////////////////////////////////////////////////////////////////////
1911 ///
1912 ///////////////////////////////////////////////////////////////////////////////////////////
1913 void Mount::loadGlobalSettings()
1914 {
1915     QString key;
1916     QVariant value;
1917 
1918     QVariantMap settings;
1919     // All Combo Boxes
1920     for (auto &oneWidget : findChildren<QComboBox*>())
1921     {
1922         if (oneWidget->objectName() == "opticalTrainCombo")
1923             continue;
1924 
1925         key = oneWidget->objectName();
1926         value = Options::self()->property(key.toLatin1());
1927         if (value.isValid())
1928         {
1929             oneWidget->setCurrentText(value.toString());
1930             settings[key] = value;
1931         }
1932     }
1933 
1934     // All Double Spin Boxes
1935     for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1936     {
1937         key = oneWidget->objectName();
1938         value = Options::self()->property(key.toLatin1());
1939         if (value.isValid())
1940         {
1941             oneWidget->setValue(value.toDouble());
1942             settings[key] = value;
1943         }
1944     }
1945 
1946     // All Spin Boxes
1947     for (auto &oneWidget : findChildren<QSpinBox*>())
1948     {
1949         key = oneWidget->objectName();
1950         value = Options::self()->property(key.toLatin1());
1951         if (value.isValid())
1952         {
1953             oneWidget->setValue(value.toInt());
1954             settings[key] = value;
1955         }
1956     }
1957 
1958     // All Checkboxes
1959     for (auto &oneWidget : findChildren<QCheckBox*>())
1960     {
1961         key = oneWidget->objectName();
1962         value = Options::self()->property(key.toLatin1());
1963         if (value.isValid())
1964         {
1965             oneWidget->setChecked(value.toBool());
1966             settings[key] = value;
1967         }
1968     }
1969 
1970     // initialize meridian flip state machine values
1971     mf_state->setEnabled(Options::executeMeridianFlip());
1972     mf_state->setOffset(Options::meridianFlipOffsetDegrees());
1973 
1974     m_GlobalSettings = m_Settings = settings;
1975 }
1976 
1977 ///////////////////////////////////////////////////////////////////////////////////////////
1978 ///
1979 ///////////////////////////////////////////////////////////////////////////////////////////
1980 void Mount::connectSyncSettings()
1981 {
1982     // All Combo Boxes
1983     for (auto &oneWidget : findChildren<QComboBox*>())
1984         connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Mount::syncSettings);
1985 
1986     // All Double Spin Boxes
1987     for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1988         connect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Mount::syncSettings);
1989 
1990     // All Spin Boxes
1991     for (auto &oneWidget : findChildren<QSpinBox*>())
1992         connect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Mount::syncSettings);
1993 
1994     // All Checkboxes
1995     for (auto &oneWidget : findChildren<QCheckBox*>())
1996         connect(oneWidget, &QCheckBox::toggled, this, &Ekos::Mount::syncSettings);
1997 
1998     // All QDateTimeEdit
1999     for (auto &oneWidget : findChildren<QDateTimeEdit*>())
2000         connect(oneWidget, &QDateTimeEdit::editingFinished, this, &Ekos::Mount::syncSettings);
2001 }
2002 
2003 ///////////////////////////////////////////////////////////////////////////////////////////
2004 ///
2005 ///////////////////////////////////////////////////////////////////////////////////////////
2006 void Mount::disconnectSyncSettings()
2007 {
2008     // All Combo Boxes
2009     for (auto &oneWidget : findChildren<QComboBox*>())
2010         disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Mount::syncSettings);
2011 
2012     // All Double Spin Boxes
2013     for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
2014         disconnect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::Mount::syncSettings);
2015 
2016     // All Spin Boxes
2017     for (auto &oneWidget : findChildren<QSpinBox*>())
2018         disconnect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::Mount::syncSettings);
2019 
2020     // All Checkboxes
2021     for (auto &oneWidget : findChildren<QCheckBox*>())
2022         disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::Mount::syncSettings);
2023 
2024     for (auto &oneWidget : findChildren<QDateTimeEdit*>())
2025         disconnect(oneWidget, &QDateTimeEdit::editingFinished, this, &Ekos::Mount::syncSettings);
2026 }
2027 
2028 ///////////////////////////////////////////////////////////////////////////////////////////
2029 ///
2030 ///////////////////////////////////////////////////////////////////////////////////////////
2031 void Mount::connectSettings()
2032 {
2033     connectSyncSettings();
2034 
2035     // connections to the meridian flip state machine
2036     connect(executeMeridianFlip, &QCheckBox::toggled, mf_state.get(), &MeridianFlipState::setEnabled);
2037     connect(meridianFlipOffsetDegrees, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
2038             mf_state.get(), &MeridianFlipState::setOffset);
2039     connect(this, &Mount::newParkStatus, mf_state.get(), &MeridianFlipState::setMountParkStatus);
2040     connect(mf_state.get(), &MeridianFlipState::slewTelescope, [&](SkyPoint pos)
2041     {
2042         if (m_Mount)
2043             m_Mount->Slew(&pos, (m_Mount->canFlip() && Options::forcedFlip()));
2044     });
2045 
2046     // Train combo box should NOT be synced.
2047     disconnect(opticalTrainCombo, QOverload<int>::of(&QComboBox::activated), this, &Ekos::Mount::syncSettings);
2048 }
2049 
2050 ///////////////////////////////////////////////////////////////////////////////////////////
2051 ///
2052 ///////////////////////////////////////////////////////////////////////////////////////////
2053 void Mount::disconnectSettings()
2054 {
2055     disconnectSyncSettings();
2056 
2057     // cut connections to the meridian flip state machine
2058     disconnect(executeMeridianFlip, &QCheckBox::toggled, mf_state.get(), &MeridianFlipState::setEnabled);
2059     disconnect(meridianFlipOffsetDegrees, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
2060                mf_state.get(), &MeridianFlipState::setOffset);
2061     disconnect(this, &Mount::newParkStatus, mf_state.get(), &MeridianFlipState::setMountParkStatus);
2062     disconnect(mf_state.get(), &MeridianFlipState::slewTelescope, nullptr, nullptr);
2063 }
2064 
2065 double Mount::initialHA()
2066 {
2067     return mf_state->initialPositionHA();
2068 }
2069 }