File indexing completed on 2024-04-14 14:10:26

0001 /*
0002     SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "profileeditor.h"
0008 
0009 #include "geolocation.h"
0010 #include "kstarsdata.h"
0011 #include "ksnotification.h"
0012 #include "Options.h"
0013 #include "guide/guide.h"
0014 #include "indi/driverinfo.h"
0015 #include "indi/drivermanager.h"
0016 #include "profilescriptdialog.h"
0017 #include "ui_indihub.h"
0018 
0019 #include "ekos_debug.h"
0020 
0021 #include <QNetworkInterface>
0022 
0023 ProfileEditorUI::ProfileEditorUI(QWidget *p) : QFrame(p)
0024 {
0025     setupUi(this);
0026 }
0027 
0028 ProfileEditor::ProfileEditor(QWidget *w) : QDialog(w)
0029 {
0030     setObjectName("profileEditorDialog");
0031 #ifdef Q_OS_OSX
0032     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0033 #endif
0034     ui = new ProfileEditorUI(this);
0035 
0036     pi = nullptr;
0037 
0038     m_MountModel = new QStandardItemModel(this);
0039     m_CameraModel = new QStandardItemModel(this);
0040     m_GuiderModel = new QStandardItemModel(this);
0041     m_FocuserModel = new QStandardItemModel(this);
0042 
0043     QVBoxLayout *mainLayout = new QVBoxLayout;
0044     mainLayout->addWidget(ui);
0045     setLayout(mainLayout);
0046 
0047     setWindowTitle(i18nc("@title:window", "Profile Editor"));
0048 
0049     // Create button box and link it to save and reject functions
0050     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Close, this);
0051 
0052     buttonBox->setObjectName("dialogButtons");
0053     mainLayout->addWidget(buttonBox);
0054     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
0055     connect(buttonBox, SIGNAL(accepted()), this, SLOT(saveProfile()));
0056 
0057     connect(ui->scriptsB, &QPushButton::clicked, this, &ProfileEditor::executeScriptEditor);
0058 
0059     connect(ui->openWebManagerB, &QPushButton::clicked, this, [this]()
0060     {
0061         QUrl url(QString("http://" + ui->remoteHost->text() + ":8624"));
0062         QDesktopServices::openUrl(url);
0063     });
0064 
0065     connect(ui->INDIWebManagerCheck, &QCheckBox::toggled, this, [&](bool enabled)
0066     {
0067         ui->openWebManagerB->setEnabled(enabled);
0068         ui->remoteDrivers->setEnabled(enabled || ui->localMode->isChecked());
0069         ui->scriptsB->setEnabled(enabled || ui->localMode->isChecked());
0070     });
0071 
0072     connect(ui->guideTypeCombo, SIGNAL(activated(int)), this, SLOT(updateGuiderSelection(int)));
0073 
0074     connect(ui->scanB, &QPushButton::clicked, this, &ProfileEditor::scanNetwork);
0075 
0076 #ifdef Q_OS_WIN
0077     ui->remoteMode->setChecked(true);
0078     ui->localMode->setEnabled(false);
0079     setRemoteMode(true);
0080 #else
0081     connect(ui->remoteMode, SIGNAL(toggled(bool)), this, SLOT(setRemoteMode(bool)));
0082 #endif
0083 
0084     connect(ui->indihubB, &QPushButton::clicked, this, &ProfileEditor::showINDIHub);
0085 
0086     // Load all drivers
0087     loadDrivers();
0088 
0089     // Shared tooltips
0090     ui->remoteDrivers->setToolTip(ui->remoteDriversLabel->toolTip());
0091     ui->aux1Combo->setToolTip(ui->aux1Label->toolTip());
0092     ui->aux2Combo->setToolTip(ui->aux2Label->toolTip());
0093     ui->aux3Combo->setToolTip(ui->aux3Label->toolTip());
0094     ui->aux4Combo->setToolTip(ui->aux4Label->toolTip());
0095     ui->filterCombo->setToolTip(ui->filterLabel->toolTip());
0096     ui->AOCombo->setToolTip(ui->AOLabel->toolTip());
0097     ui->domeCombo->setToolTip(ui->domeLabel->toolTip());
0098     ui->weatherCombo->setToolTip(ui->weatherLabel->toolTip());
0099     ui->localMode->setToolTip(ui->modeLabel->toolTip());
0100     ui->remoteMode->setToolTip(ui->modeLabel->toolTip());
0101     ui->remoteHostLabel->setToolTip(ui->remoteHost->toolTip());
0102     ui->remotePortLabel->setToolTip(ui->remotePort->toolTip());
0103     ui->externalGuidePortLabel->setToolTip(ui->externalGuidePort->toolTip());
0104     ui->INDIWebManagerPortLabel->setToolTip(ui->INDIWebManagerPort->toolTip());
0105     ui->guideTypeCombo->setToolTip(ui->guidingTypeLabel->toolTip());
0106     ui->externalGuideHostLabel->setToolTip(ui->externalGuideHost->toolTip());
0107 }
0108 
0109 void ProfileEditor::saveProfile()
0110 {
0111     bool newProfile = (pi.isNull());
0112 
0113     if (ui->profileIN->text().isEmpty())
0114     {
0115         KSNotification::error(i18n("Cannot save an empty profile."));
0116         return;
0117     }
0118 
0119     if (newProfile)
0120     {
0121         QList<QSharedPointer<ProfileInfo>> existingProfiles;
0122         KStarsData::Instance()->userdb()->GetAllProfiles(existingProfiles);
0123         for (auto &profileInfo : existingProfiles)
0124         {
0125             if (ui->profileIN->text() == profileInfo->name)
0126             {
0127                 KSNotification::error(i18n("Profile name already exists."));
0128                 return;
0129             }
0130         }
0131         int id = KStarsData::Instance()->userdb()->AddProfile(ui->profileIN->text());
0132         pi.reset(new ProfileInfo(id, ui->profileIN->text()));
0133     }
0134     else
0135         pi->name = ui->profileIN->text();
0136 
0137     // Local Mode
0138     if (ui->localMode->isChecked())
0139     {
0140         pi->host.clear();
0141         pi->port               = -1;
0142         pi->INDIWebManagerPort = -1;
0143         //pi->customDrivers = ui->customDriversIN->text();
0144     }
0145     // Remote Mode
0146     else
0147     {
0148         pi->host = ui->remoteHost->text().trimmed();
0149         pi->port = ui->remotePort->text().toInt();
0150         if (ui->INDIWebManagerCheck->isChecked())
0151             pi->INDIWebManagerPort = ui->INDIWebManagerPort->text().toInt();
0152         else
0153             pi->INDIWebManagerPort = -1;
0154         //pi->customDrivers.clear();
0155     }
0156 
0157     // City Info
0158     if (ui->loadSiteCheck->isEnabled() && ui->loadSiteCheck->isChecked())
0159     {
0160         pi->city     = KStarsData::Instance()->geo()->name();
0161         pi->province = KStarsData::Instance()->geo()->province();
0162         pi->country  = KStarsData::Instance()->geo()->country();
0163     }
0164     else
0165     {
0166         pi->city.clear();
0167         pi->province.clear();
0168         pi->country.clear();
0169     }
0170 
0171     pi->indihub = m_INDIHub;
0172 
0173     // Auto Connect
0174     pi->autoConnect = ui->autoConnectCheck->isChecked();
0175     // Port Selector
0176     pi->portSelector = ui->portSelectorCheck->isChecked();
0177 
0178     // Guider Type
0179     pi->guidertype = ui->guideTypeCombo->currentIndex();
0180     if (pi->guidertype != Ekos::Guide::GUIDE_INTERNAL)
0181     {
0182         pi->guiderhost = ui->externalGuideHost->text();
0183         pi->guiderport = ui->externalGuidePort->text().toInt();
0184 
0185         if (pi->guidertype == Ekos::Guide::GUIDE_PHD2)
0186         {
0187             Options::setPHD2Host(pi->guiderhost);
0188             Options::setPHD2Port(pi->guiderport);
0189         }
0190         else if (pi->guidertype == Ekos::Guide::GUIDE_LINGUIDER)
0191         {
0192             Options::setLinGuiderHost(pi->guiderhost);
0193             Options::setLinGuiderPort(pi->guiderport);
0194         }
0195     }
0196 
0197     if (ui->mountCombo->currentText().isEmpty() || ui->mountCombo->currentText() == "--")
0198         pi->drivers.remove("Mount");
0199     else
0200         pi->drivers["Mount"] = ui->mountCombo->currentText();
0201 
0202     if (ui->ccdCombo->currentText().isEmpty() || ui->ccdCombo->currentText() == "--")
0203         pi->drivers.remove("CCD");
0204     else
0205         pi->drivers["CCD"] = ui->ccdCombo->currentText();
0206 
0207     if (ui->guiderCombo->currentText().isEmpty() || ui->guiderCombo->currentText() == "--")
0208         pi->drivers.remove("Guider");
0209     else
0210         pi->drivers["Guider"] = ui->guiderCombo->currentText();
0211 
0212     if (ui->focuserCombo->currentText().isEmpty() || ui->focuserCombo->currentText() == "--")
0213         pi->drivers.remove("Focuser");
0214     else
0215         pi->drivers["Focuser"] = ui->focuserCombo->currentText();
0216 
0217     if (ui->filterCombo->currentText().isEmpty() || ui->filterCombo->currentText() == "--")
0218         pi->drivers.remove("Filter");
0219     else
0220         pi->drivers["Filter"] = ui->filterCombo->currentText();
0221 
0222     if (ui->AOCombo->currentText().isEmpty() || ui->AOCombo->currentText() == "--")
0223         pi->drivers.remove("AO");
0224     else
0225         pi->drivers["AO"] = ui->AOCombo->currentText();
0226 
0227     if (ui->domeCombo->currentText().isEmpty() || ui->domeCombo->currentText() == "--")
0228         pi->drivers.remove("Dome");
0229     else
0230         pi->drivers["Dome"] = ui->domeCombo->currentText();
0231 
0232     if (ui->weatherCombo->currentText().isEmpty() || ui->weatherCombo->currentText() == "--")
0233         pi->drivers.remove("Weather");
0234     else
0235         pi->drivers["Weather"] = ui->weatherCombo->currentText();
0236 
0237     if (ui->aux1Combo->currentText().isEmpty() || ui->aux1Combo->currentText() == "--")
0238         pi->drivers.remove("Aux1");
0239     else
0240         pi->drivers["Aux1"] = ui->aux1Combo->currentText();
0241 
0242     if (ui->aux2Combo->currentText().isEmpty() || ui->aux2Combo->currentText() == "--")
0243         pi->drivers.remove("Aux2");
0244     else
0245         pi->drivers["Aux2"] = ui->aux2Combo->currentText();
0246 
0247     if (ui->aux3Combo->currentText().isEmpty() || ui->aux3Combo->currentText() == "--")
0248         pi->drivers.remove("Aux3");
0249     else
0250         pi->drivers["Aux3"] = ui->aux3Combo->currentText();
0251 
0252     if (ui->aux4Combo->currentText().isEmpty() || ui->aux4Combo->currentText() == "--")
0253         pi->drivers.remove("Aux4");
0254     else
0255         pi->drivers["Aux4"] = ui->aux4Combo->currentText();
0256 
0257     pi->remotedrivers = ui->remoteDrivers->text();
0258 
0259     KStarsData::Instance()->userdb()->SaveProfile(pi);
0260 
0261     // Ekos manager will reload and new profiles will be created
0262     if (newProfile)
0263         pi.clear();
0264 
0265     accept();
0266 }
0267 
0268 void ProfileEditor::setRemoteMode(bool enable)
0269 {
0270     loadDrivers(); //This is needed to reload the drivers because some may not be available locally
0271 
0272     ui->remoteHost->setEnabled(enable);
0273     ui->remoteHostLabel->setEnabled(enable);
0274     ui->remotePort->setEnabled(enable);
0275     ui->remotePortLabel->setEnabled(enable);
0276 
0277     //ui->customLabel->setEnabled(!enable);
0278     //ui->customDriversIN->setEnabled(!enable);
0279 
0280     ui->mountCombo->setEditable(enable);
0281     ui->ccdCombo->setEditable(enable);
0282     ui->guiderCombo->setEditable(enable);
0283     ui->focuserCombo->setEditable(enable);
0284     ui->filterCombo->setEditable(enable);
0285     ui->AOCombo->setEditable(enable);
0286     ui->domeCombo->setEditable(enable);
0287     ui->weatherCombo->setEditable(enable);
0288     ui->aux1Combo->setEditable(enable);
0289     ui->aux2Combo->setEditable(enable);
0290     ui->aux3Combo->setEditable(enable);
0291     ui->aux4Combo->setEditable(enable);
0292 
0293     ui->remoteDrivers->setEnabled(!enable);
0294 
0295     ui->loadSiteCheck->setEnabled(enable);
0296 
0297     ui->INDIWebManagerCheck->setEnabled(enable);
0298     if (enable == false)
0299         ui->INDIWebManagerCheck->setChecked(false);
0300     ui->INDIWebManagerPort->setEnabled(enable);
0301 
0302     ui->scriptsB->setEnabled(!enable || ui->INDIWebManagerCheck->isChecked());
0303 }
0304 
0305 void ProfileEditor::setPi(const QSharedPointer<ProfileInfo> &newProfile)
0306 {
0307     pi = newProfile;
0308 
0309     ui->profileIN->setText(pi->name);
0310 
0311     ui->loadSiteCheck->setChecked(!pi->city.isEmpty());
0312     ui->autoConnectCheck->setChecked(pi->autoConnect);
0313     ui->portSelectorCheck->setChecked(pi->portSelector);
0314 
0315     if (pi->city.isEmpty() == false)
0316     {
0317         if (pi->province.isEmpty())
0318             ui->loadSiteCheck->setText(ui->loadSiteCheck->text() + QString(" (%1, %2)").arg(pi->country, pi->city));
0319         else
0320             ui->loadSiteCheck->setText(ui->loadSiteCheck->text() +
0321                                        QString(" (%1, %2, %3)").arg(pi->country, pi->province, pi->city));
0322     }
0323 
0324     if (pi->host.isEmpty() == false)
0325     {
0326         ui->remoteHost->setText(pi->host);
0327         ui->remotePort->setText(QString::number(pi->port));
0328 
0329         ui->remoteMode->setChecked(true);
0330 
0331         if (pi->INDIWebManagerPort > 0)
0332         {
0333             ui->INDIWebManagerCheck->setChecked(true);
0334             ui->INDIWebManagerPort->setText(QString::number(pi->INDIWebManagerPort));
0335         }
0336         else
0337         {
0338             ui->INDIWebManagerCheck->setChecked(false);
0339             ui->INDIWebManagerPort->setText("8624");
0340         }
0341     }
0342 
0343     if (pi->remotedrivers.isEmpty() == false)
0344         ui->remoteDrivers->setText(pi->remotedrivers);
0345 
0346     ui->guideTypeCombo->setCurrentIndex(pi->guidertype);
0347     updateGuiderSelection(ui->guideTypeCombo->currentIndex());
0348     if (pi->guidertype == Ekos::Guide::GUIDE_PHD2)
0349     {
0350         Options::setPHD2Host(pi->guiderhost);
0351         Options::setPHD2Port(pi->guiderport);
0352     }
0353     else if (pi->guidertype == Ekos::Guide::GUIDE_LINGUIDER)
0354     {
0355         Options::setLinGuiderHost(pi->guiderhost);
0356         Options::setLinGuiderPort(pi->guiderport);
0357     }
0358 
0359     QMapIterator<QString, QString> i(pi->drivers);
0360 
0361     while (i.hasNext())
0362     {
0363         int row = 0;
0364         i.next();
0365 
0366         QString key   = i.key();
0367         QString value = i.value();
0368 
0369         if (key == "Mount")
0370         {
0371             // If driver doesn't exist, let's add it to the list
0372             if ((row = ui->mountCombo->findText(value)) == -1)
0373             {
0374                 ui->mountCombo->addItem(value);
0375                 row = ui->mountCombo->count() - 1;
0376             }
0377 
0378             // Set index to our driver
0379             ui->mountCombo->setCurrentIndex(row);
0380         }
0381         else if (key == "CCD")
0382         {
0383             if ((row = ui->ccdCombo->findText(value)) == -1)
0384             {
0385                 ui->ccdCombo->addItem(value);
0386                 row = ui->ccdCombo->count() - 1;
0387             }
0388 
0389             ui->ccdCombo->setCurrentIndex(row);
0390         }
0391         else if (key == "Guider")
0392         {
0393             if ((row = ui->guiderCombo->findText(value)) == -1)
0394             {
0395                 ui->guiderCombo->addItem(value);
0396                 row = ui->guiderCombo->count() - 1;
0397             }
0398 
0399             ui->guiderCombo->setCurrentIndex(row);
0400         }
0401         else if (key == "Focuser")
0402         {
0403             if ((row = ui->focuserCombo->findText(value)) == -1)
0404             {
0405                 ui->focuserCombo->addItem(value);
0406                 row = ui->focuserCombo->count() - 1;
0407             }
0408 
0409             ui->focuserCombo->setCurrentIndex(row);
0410         }
0411         else if (key == "Filter")
0412         {
0413             if ((row = ui->filterCombo->findText(value)) == -1)
0414             {
0415                 ui->filterCombo->addItem(value);
0416                 row = ui->filterCombo->count() - 1;
0417             }
0418 
0419             ui->filterCombo->setCurrentIndex(row);
0420         }
0421         else if (key == "AO")
0422         {
0423             if ((row = ui->AOCombo->findText(value)) == -1)
0424             {
0425                 ui->AOCombo->addItem(value);
0426                 row = ui->AOCombo->count() - 1;
0427             }
0428 
0429             ui->AOCombo->setCurrentIndex(row);
0430         }
0431         else if (key == "Dome")
0432         {
0433             if ((row = ui->domeCombo->findText(value)) == -1)
0434             {
0435                 ui->domeCombo->addItem(value);
0436                 row = ui->domeCombo->count() - 1;
0437             }
0438 
0439             ui->domeCombo->setCurrentIndex(row);
0440         }
0441         else if (key == "Weather")
0442         {
0443             if ((row = ui->weatherCombo->findText(value)) == -1)
0444             {
0445                 ui->weatherCombo->addItem(value);
0446                 row = ui->weatherCombo->count() - 1;
0447             }
0448 
0449             ui->weatherCombo->setCurrentIndex(row);
0450         }
0451         else if (key == "Aux1")
0452         {
0453             if ((row = ui->aux1Combo->findText(value)) == -1)
0454             {
0455                 ui->aux1Combo->addItem(value);
0456                 row = ui->aux1Combo->count() - 1;
0457             }
0458 
0459             ui->aux1Combo->setCurrentIndex(row);
0460         }
0461         else if (key == "Aux2")
0462         {
0463             if ((row = ui->aux2Combo->findText(value)) == -1)
0464             {
0465                 ui->aux2Combo->addItem(value);
0466                 row = ui->aux2Combo->count() - 1;
0467             }
0468 
0469             ui->aux2Combo->setCurrentIndex(row);
0470         }
0471         else if (key == "Aux3")
0472         {
0473             if ((row = ui->aux3Combo->findText(value)) == -1)
0474             {
0475                 ui->aux3Combo->addItem(value);
0476                 row = ui->aux3Combo->count() - 1;
0477             }
0478 
0479             ui->aux3Combo->setCurrentIndex(row);
0480         }
0481         else if (key == "Aux4")
0482         {
0483             if ((row = ui->aux4Combo->findText(value)) == -1)
0484             {
0485                 ui->aux4Combo->addItem(value);
0486                 row = ui->aux4Combo->count() - 1;
0487             }
0488 
0489             ui->aux4Combo->setCurrentIndex(row);
0490         }
0491     }
0492 
0493     m_INDIHub = pi->indihub;
0494 }
0495 
0496 QString ProfileEditor::getTooltip(const QSharedPointer<DriverInfo> &driver)
0497 {
0498     bool locallyAvailable = false;
0499     if (driver->getAuxInfo().contains("LOCALLY_AVAILABLE"))
0500         locallyAvailable = driver->getAuxInfo().value("LOCALLY_AVAILABLE", false).toBool();
0501     QString toolTipText;
0502     if (!locallyAvailable)
0503         toolTipText = i18n(
0504                           "<nobr>Available as <b>Remote</b> Driver. To use locally, install the corresponding driver.<nobr/>");
0505     else
0506         toolTipText = i18n("<nobr><b>Label</b>: %1 &#9473; <b>Driver</b>: %2 &#9473; <b>Exec</b>: %3<nobr/>",
0507                            driver->getLabel(), driver->getName(), driver->getExecutable());
0508 
0509     return toolTipText;
0510 }
0511 
0512 void ProfileEditor::loadDrivers()
0513 {
0514     // We need to save this now since we have two models for the mounts
0515     QString selectedMount = ui->mountCombo->currentText();
0516     QString selectedCamera = ui->ccdCombo->currentText();
0517     QString selectedGuider = ui->guiderCombo->currentText();
0518     QString selectedFocuser = ui->focuserCombo->currentText();
0519     QString selectedAux1 = ui->aux1Combo->currentText();
0520     QString selectedAux2 = ui->aux2Combo->currentText();
0521     QString selectedAux3 = ui->aux3Combo->currentText();
0522     QString selectedAux4 = ui->aux4Combo->currentText();
0523 
0524     QVector<QComboBox *> boxes;
0525     boxes.append(ui->mountCombo);
0526     boxes.append(ui->ccdCombo);
0527     boxes.append(ui->guiderCombo);
0528     boxes.append(ui->AOCombo);
0529     boxes.append(ui->focuserCombo);
0530     boxes.append(ui->filterCombo);
0531     boxes.append(ui->domeCombo);
0532     boxes.append(ui->weatherCombo);
0533     boxes.append(ui->aux1Combo);
0534     boxes.append(ui->aux2Combo);
0535     boxes.append(ui->aux3Combo);
0536     boxes.append(ui->aux4Combo);
0537 
0538     QVector<QString> selectedItems;
0539 
0540     for (QComboBox *box : boxes)
0541     {
0542         selectedItems.append(box->currentText());
0543         box->clear();
0544         box->addItem("--");
0545         box->setMaxVisibleItems(20);
0546     }
0547 
0548     QIcon remoteIcon = QIcon::fromTheme("network-modem");
0549 
0550     // Create the model
0551     delete (m_MountModel);
0552     m_MountModel = new QStandardItemModel(this);
0553     delete (m_CameraModel);
0554     m_CameraModel = new QStandardItemModel(this);
0555     delete (m_GuiderModel);
0556     m_GuiderModel = new QStandardItemModel(this);
0557     delete (m_FocuserModel);
0558     m_FocuserModel = new QStandardItemModel(this);
0559     delete (m_Aux1Model);
0560     m_Aux1Model = new QStandardItemModel(this);
0561     delete (m_Aux2Model);
0562     m_Aux2Model = new QStandardItemModel(this);
0563     delete (m_Aux3Model);
0564     m_Aux3Model = new QStandardItemModel(this);
0565     delete (m_Aux4Model);
0566     m_Aux4Model = new QStandardItemModel(this);
0567 
0568     const bool isLocal = ui->localMode->isChecked();
0569     const QList<DeviceFamily> auxFamily = QList<DeviceFamily>()
0570                                           << KSTARS_AUXILIARY
0571                                           << KSTARS_CCD
0572                                           << KSTARS_FOCUSER
0573                                           << KSTARS_FILTER
0574                                           << KSTARS_WEATHER
0575                                           << KSTARS_SPECTROGRAPHS
0576                                           << KSTARS_DETECTORS;
0577 
0578     populateManufacturerCombo(m_MountModel, ui->mountCombo, selectedMount, isLocal, QList<DeviceFamily>() << KSTARS_TELESCOPE);
0579     populateManufacturerCombo(m_CameraModel, ui->ccdCombo, selectedCamera, isLocal, QList<DeviceFamily>() << KSTARS_CCD);
0580     populateManufacturerCombo(m_GuiderModel, ui->guiderCombo, selectedGuider, isLocal, QList<DeviceFamily>() << KSTARS_CCD);
0581     populateManufacturerCombo(m_FocuserModel, ui->focuserCombo, selectedFocuser, isLocal,
0582                               QList<DeviceFamily>() << KSTARS_FOCUSER);
0583     populateManufacturerCombo(m_Aux1Model, ui->aux1Combo, selectedAux1, isLocal, auxFamily);
0584     populateManufacturerCombo(m_Aux2Model, ui->aux2Combo, selectedAux2, isLocal, auxFamily);
0585     populateManufacturerCombo(m_Aux3Model, ui->aux3Combo, selectedAux3, isLocal, auxFamily);
0586     populateManufacturerCombo(m_Aux4Model, ui->aux4Combo, selectedAux4, isLocal, auxFamily);
0587 
0588     for (QSharedPointer<DriverInfo>driver : DriverManager::Instance()->getDrivers())
0589     {
0590         bool locallyAvailable = false;
0591         QIcon icon;
0592         if (driver->getAuxInfo().contains("LOCALLY_AVAILABLE"))
0593             locallyAvailable = driver->getAuxInfo().value("LOCALLY_AVAILABLE", false).toBool();
0594         if (!locallyAvailable)
0595         {
0596             if (ui->localMode->isChecked())
0597                 continue;
0598             else
0599                 icon = remoteIcon;
0600         }
0601 
0602         QString toolTipText = getTooltip(driver);
0603 
0604         switch (driver->getType())
0605         {
0606             case KSTARS_CCD:
0607                 break;
0608 
0609             case KSTARS_ADAPTIVE_OPTICS:
0610             {
0611                 ui->AOCombo->addItem(icon, driver->getLabel());
0612                 ui->AOCombo->setItemData(ui->AOCombo->count() - 1, toolTipText, Qt::ToolTipRole);
0613             }
0614             break;
0615 
0616             case KSTARS_FOCUSER:
0617                 break;
0618 
0619             case KSTARS_FILTER:
0620             {
0621                 ui->filterCombo->addItem(icon, driver->getLabel());
0622                 ui->filterCombo->setItemData(ui->filterCombo->count() - 1, toolTipText, Qt::ToolTipRole);
0623             }
0624             break;
0625 
0626             case KSTARS_DOME:
0627             {
0628                 ui->domeCombo->addItem(icon, driver->getLabel());
0629                 ui->domeCombo->setItemData(ui->domeCombo->count() - 1, toolTipText, Qt::ToolTipRole);
0630             }
0631             break;
0632 
0633             case KSTARS_WEATHER:
0634             {
0635                 ui->weatherCombo->addItem(icon, driver->getLabel());
0636                 ui->weatherCombo->setItemData(ui->weatherCombo->count() - 1, toolTipText, Qt::ToolTipRole);
0637             }
0638             break;
0639 
0640             case KSTARS_AUXILIARY:
0641             case KSTARS_SPECTROGRAPHS:
0642             case KSTARS_DETECTORS:
0643                 break;
0644 
0645             default:
0646                 continue;
0647         }
0648     }
0649 
0650     // Skip mount/ccd/guider/focuser since we handled it above
0651     for (int i = 4; i < boxes.count(); i++)
0652     {
0653         QComboBox *box           = boxes.at(i);
0654         QString selectedItemText = selectedItems.at(i);
0655         int index                = box->findText(selectedItemText);
0656         if (index == -1)
0657         {
0658             if (ui->localMode->isChecked())
0659                 box->setCurrentIndex(0);
0660             else
0661                 box->addItem(remoteIcon, selectedItemText);
0662         }
0663         else
0664         {
0665             box->setCurrentIndex(index);
0666         }
0667 
0668         box->model()->sort(0);
0669     }
0670 }
0671 
0672 void ProfileEditor::setProfileName(const QString &name)
0673 {
0674     ui->profileIN->setText(name);
0675 }
0676 
0677 void ProfileEditor::setAuxDrivers(const QStringList &aux)
0678 {
0679     QStringList auxList(aux);
0680 
0681     if (auxList.isEmpty())
0682         return;
0683     ui->aux1Combo->setCurrentText(auxList.first());
0684     auxList.removeFirst();
0685 
0686     if (auxList.isEmpty())
0687         return;
0688     ui->aux2Combo->setCurrentText(auxList.first());
0689     auxList.removeFirst();
0690 
0691     if (auxList.isEmpty())
0692         return;
0693     ui->aux3Combo->setCurrentText(auxList.first());
0694     auxList.removeFirst();
0695 
0696     if (auxList.isEmpty())
0697         return;
0698     ui->aux4Combo->setCurrentText(auxList.first());
0699 }
0700 
0701 void ProfileEditor::setHostPort(const QString &host, const QString &port)
0702 {
0703     ui->remoteMode->setChecked(true);
0704     ui->remoteHost->setText(host);
0705     ui->remotePort->setText(port);
0706 }
0707 
0708 void ProfileEditor::setWebManager(bool enabled, const QString &port)
0709 {
0710     ui->INDIWebManagerCheck->setChecked(enabled);
0711     ui->INDIWebManagerPort->setText(port);
0712 }
0713 
0714 void ProfileEditor::setGuiderType(int type)
0715 {
0716     ui->guideTypeCombo->setCurrentIndex(type);
0717     if (type != Ekos::Guide::GUIDE_INTERNAL)
0718     {
0719         ui->externalGuideHostLabel->setEnabled(true);
0720         ui->externalGuideHost->setEnabled(true);
0721         ui->externalGuidePortLabel->setEnabled(true);
0722         ui->externalGuidePort->setEnabled(true);
0723     }
0724 }
0725 
0726 void ProfileEditor::setConnectionOptionsEnabled(bool enable)
0727 {
0728     // Enable or disable connection related options
0729     ui->modeLabel->setEnabled(enable);
0730     ui->localMode->setEnabled(enable);
0731     ui->remoteMode->setEnabled(enable);
0732     ui->remoteHostLabel->setEnabled(enable);
0733     ui->remoteHost->setEnabled(enable);
0734     ui->remotePortLabel->setEnabled(enable);
0735     ui->remotePort->setEnabled(enable);
0736     ui->INDIWebManagerCheck->setEnabled(enable);
0737     ui->INDIWebManagerPort->setEnabled(enable);
0738     ui->INDIWebManagerPortLabel->setEnabled(enable);
0739     ui->guidingTypeLabel->setEnabled(enable);
0740     ui->guideTypeCombo->setEnabled(enable);
0741     ui->remoteDrivers->setEnabled(enable);
0742 
0743     updateGuiderSelection(ui->guideTypeCombo->currentIndex());
0744 
0745     if (enable == false)
0746         ui->mountCombo->setFocus();
0747 }
0748 
0749 void ProfileEditor::updateGuiderSelection(int id)
0750 {
0751 
0752     if (id == Ekos::Guide::GUIDE_INTERNAL)
0753     {
0754         ui->externalGuideHost->setText("localhost");
0755         ui->externalGuidePort->clear();
0756 
0757         ui->externalGuideHost->setEnabled(false);
0758         ui->externalGuideHostLabel->setEnabled(false);
0759         ui->externalGuidePort->setEnabled(false);
0760         ui->externalGuidePortLabel->setEnabled(false);
0761         return;
0762     }
0763 
0764     QString host;
0765     int port = -1;
0766 
0767     ui->externalGuideHost->setEnabled(true);
0768     ui->externalGuideHostLabel->setEnabled(true);
0769     ui->externalGuidePort->setEnabled(true);
0770     ui->externalGuidePortLabel->setEnabled(true);
0771 
0772     if (pi && pi->guidertype == id)
0773     {
0774         host = pi->guiderhost;
0775         port = pi->guiderport;
0776     }
0777 
0778     if (id == Ekos::Guide::GUIDE_PHD2)
0779     {
0780         if (host.isEmpty())
0781             host = Options::pHD2Host();
0782         if (port < 0)
0783             port = Options::pHD2Port();
0784     }
0785     else if (id == Ekos::Guide::GUIDE_LINGUIDER)
0786     {
0787         if (host.isEmpty())
0788             host = Options::linGuiderHost();
0789         if (port < 0)
0790             port = Options::linGuiderPort();
0791     }
0792 
0793     ui->externalGuideHost->setText(host);
0794     ui->externalGuidePort->setText(QString::number(port));
0795 
0796 }
0797 
0798 void ProfileEditor::setSettings(const QJsonObject &profile)
0799 {
0800     ui->profileIN->setText(profile["name"].toString());
0801     ui->autoConnectCheck->setChecked(profile["auto_connect"].toBool(true));
0802     ui->portSelectorCheck->setChecked(profile["port_selector"].toBool(false));
0803     ui->localMode->setChecked(profile["mode"].toString() == "local");
0804     ui->remoteMode->setChecked(profile["mode"].toString() == "remote");
0805     ui->remoteHost->setText(profile["remote_host"].toString("localhost"));
0806     ui->remotePort->setText(profile["remote_port"].toString("7624"));
0807     ui->guideTypeCombo->setCurrentText(profile["guiding"].toString(i18n("Internal")));
0808     ui->externalGuideHost->setText(profile["remote_guiding_host"].toString(("localhost")));
0809     ui->externalGuideHost->setText(profile["remote_guiding_port"].toString());
0810     ui->INDIWebManagerCheck->setChecked(profile["use_web_manager"].toBool());
0811     ui->remoteDrivers->setText(profile["remote"].toString(ui->remoteDrivers->text()));
0812 
0813     m_INDIHub = profile["indihub"].toInt(m_INDIHub);
0814 
0815     // Drivers
0816     const QString mount = profile["mount"].toString("--");
0817     if (mount == "--")
0818         ui->mountCombo->setCurrentIndex(0);
0819     else
0820     {
0821         ui->mountCombo->addItem(mount);
0822         ui->mountCombo->setCurrentIndex(ui->mountCombo->count() - 1);
0823     }
0824 
0825     const QString ccd = profile["ccd"].toString("--");
0826     if (ccd == "--")
0827         ui->ccdCombo->setCurrentIndex(0);
0828     else
0829     {
0830         ui->ccdCombo->addItem(ccd);
0831         ui->ccdCombo->setCurrentIndex(ui->ccdCombo->count() - 1);
0832     }
0833 
0834     const QString guider = profile["guider"].toString("--");
0835     if (guider == "--")
0836         ui->guiderCombo->setCurrentIndex(0);
0837     else
0838     {
0839         ui->guiderCombo->addItem(guider);
0840         ui->guiderCombo->setCurrentIndex(ui->guiderCombo->count() - 1);
0841     }
0842 
0843     const QString focuser = profile["focuser"].toString("--");
0844     if (focuser == "--")
0845         ui->focuserCombo->setCurrentIndex(0);
0846     else
0847     {
0848         ui->focuserCombo->addItem(focuser);
0849         ui->focuserCombo->setCurrentIndex(ui->focuserCombo->count() - 1);
0850     }
0851 
0852     ui->filterCombo->setCurrentText(profile["filter"].toString("--"));
0853     ui->AOCombo->setCurrentText(profile["ao"].toString("--"));
0854     ui->domeCombo->setCurrentText(profile["dome"].toString("--"));
0855     ui->weatherCombo->setCurrentText(profile["weather"].toString("--"));
0856 
0857     const auto aux1 = profile["aux1"].toString("--");
0858     if (aux1.isEmpty() || aux1 == "--")
0859         ui->aux1Combo->setCurrentIndex(0);
0860     else
0861     {
0862         ui->aux1Combo->addItem(aux1);
0863         ui->aux1Combo->setCurrentIndex(ui->aux1Combo->count() - 1);
0864     }
0865 
0866     const auto aux2 = profile["aux2"].toString("--");
0867     if (aux2.isEmpty() || aux2 == "--")
0868         ui->aux2Combo->setCurrentIndex(0);
0869     else
0870     {
0871         ui->aux2Combo->addItem(aux2);
0872         ui->aux2Combo->setCurrentIndex(ui->aux2Combo->count() - 1);
0873     }
0874 
0875     const auto aux3 = profile["aux3"].toString("--");
0876     if (aux3.isEmpty() || aux3 == "--")
0877         ui->aux3Combo->setCurrentIndex(0);
0878     else
0879     {
0880         ui->aux3Combo->addItem(aux3);
0881         ui->aux3Combo->setCurrentIndex(ui->aux3Combo->count() - 1);
0882     }
0883 
0884     const auto aux4 = profile["aux4"].toString("--");
0885     if (aux4.isEmpty() || aux4 == "--")
0886         ui->aux4Combo->setCurrentIndex(0);
0887     else
0888     {
0889         ui->aux4Combo->addItem(aux4);
0890         ui->aux4Combo->setCurrentIndex(ui->aux4Combo->count() - 1);
0891     }
0892 }
0893 
0894 void ProfileEditor::scanNetwork()
0895 {
0896     delete (m_ProgressDialog);
0897     m_ProgressDialog = new QProgressDialog(this);
0898     m_ProgressDialog->setWindowTitle(i18nc("@title:window", "Scanning Network"));
0899     m_ProgressDialog->setLabelText(i18n("Scanning network for INDI Web Managers..."));
0900     connect(m_ProgressDialog, &QProgressDialog::canceled, this, [this]()
0901     {
0902         m_CancelScan = true;
0903         clearAllRequests();
0904     });
0905     m_ProgressDialog->setMinimum(0);
0906     m_ProgressDialog->setMaximum(0);
0907     m_ProgressDialog->show();
0908     m_ProgressDialog->raise();
0909 
0910     m_CancelScan = false;
0911 
0912     QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
0913     std::sort(addresses.begin(), addresses.end(), [](const QHostAddress & a, const QHostAddress & b) -> bool
0914     { return a.toString() < b.toString();});
0915 
0916     for(QHostAddress address : addresses)
0917     {
0918         if (address.isLoopback() || address.protocol() & QAbstractSocket::IPv6Protocol)
0919             continue;
0920 
0921         QString ipv4 = address.toString();
0922 
0923         if (ipv4.startsWith("10.250"))
0924         {
0925             scanIP("10.250.250.1");
0926         }
0927         else
0928         {
0929             QString prefixIP = ipv4.remove(ipv4.lastIndexOf("."), 10);
0930             // Blind search all over subnet
0931             // TODO better subnet detection instead of assuming it finishes at 254
0932             for (int i = 1; i <= 254; i++)
0933             {
0934                 scanIP(prefixIP + "." + QString::number(i));
0935             }
0936         }
0937     }
0938 
0939 }
0940 
0941 void ProfileEditor::scanIP(const QString &ip)
0942 {
0943     QUrl url(QString("http://%1:8624/api/server/status").arg(ip));
0944 
0945     qCDebug(KSTARS_EKOS) << "Scanning" << url;
0946 
0947     QNetworkReply *response = m_Manager.get(QNetworkRequest(url));
0948     m_Replies.append(response);
0949     connect(response, &QNetworkReply::finished, [this, response, ip]()
0950     {
0951         m_Replies.removeOne(response);
0952         response->deleteLater();
0953         if (m_CancelScan)
0954             return;
0955         if (response->error() == QNetworkReply::NoError)
0956         {
0957             clearAllRequests();
0958             m_ProgressDialog->close();
0959             ui->remoteHost->setText(ip);
0960 
0961             qCDebug(KSTARS_EKOS) << "Found Web Manager server at" << ip;
0962 
0963             KSNotification::info(i18n("Found INDI Web Manager at %1", ip));
0964         }
0965     });
0966 }
0967 
0968 void ProfileEditor::clearAllRequests()
0969 {
0970     for (QNetworkReply *oneReply : m_Replies)
0971     {
0972         oneReply->abort();
0973         oneReply->deleteLater();
0974     }
0975 
0976     m_Replies.clear();
0977 }
0978 
0979 void ProfileEditor::showINDIHub()
0980 {
0981     QDialog hub;
0982     Ui::INDIHub indihub;
0983     indihub.setupUi(&hub);
0984 
0985     indihub.modeButtonGroup->setId(indihub.offR, 0);
0986     indihub.modeButtonGroup->setId(indihub.solorR, 1);
0987     indihub.modeButtonGroup->setId(indihub.shareR, 2);
0988     indihub.modeButtonGroup->setId(indihub.roboticR, 3);
0989 
0990     indihub.logoLabel->setPixmap(QIcon(":/icons/indihub_logo.svg").pixmap(QSize(128, 128)));
0991 
0992     indihub.modeButtonGroup->button(m_INDIHub)->setChecked(true);
0993     connect(indihub.closeB, &QPushButton::clicked, &hub, &QDialog::close);
0994 
0995     hub.exec();
0996 
0997     m_INDIHub = indihub.modeButtonGroup->checkedId();
0998 }
0999 
1000 void ProfileEditor::populateManufacturerCombo(QStandardItemModel *model, QComboBox *combo, const QString &selectedDriver,
1001         bool isLocal, const QList<DeviceFamily> &families)
1002 {
1003     if (isLocal)
1004     {
1005         QStandardItem *selectedItem = nullptr;
1006         model->appendRow(new QStandardItem("--"));
1007         for (QSharedPointer<DriverInfo>driver : DriverManager::Instance()->getDrivers())
1008         {
1009             if (!families.contains(driver->getType()))
1010                 continue;
1011 
1012             QString manufacturer = driver->manufacturer();
1013             QList<QStandardItem*> manufacturers = model->findItems(manufacturer);
1014 
1015             QStandardItem *parentItem = nullptr;
1016             if (model->findItems(manufacturer).empty())
1017             {
1018                 parentItem = new QStandardItem(manufacturer);
1019                 parentItem->setSelectable(false);
1020                 model->appendRow(parentItem);
1021             }
1022             else
1023             {
1024                 parentItem = manufacturers.first();
1025             }
1026 
1027             QStandardItem *item = new QStandardItem(driver->getLabel());
1028             item->setData(getTooltip(driver), Qt::ToolTipRole);
1029             parentItem->appendRow(item);
1030             if (selectedDriver == driver->getLabel())
1031                 selectedItem = item;
1032         }
1033         QTreeView *view = new QTreeView(this);
1034         view->setModel(model);
1035         view->sortByColumn(0, Qt::AscendingOrder);
1036         combo->setView(view);
1037         combo->setModel(model);
1038         if (selectedItem)
1039         {
1040             // JM: Only way to make it the QTreeView sets the current index
1041             // in the combo way
1042 
1043             QModelIndex index = model->indexFromItem(selectedItem);
1044 
1045             // First set current index to the child
1046             combo->setRootModelIndex(index.parent());
1047             combo->setModelColumn(index.column());
1048             combo->setCurrentIndex(index.row());
1049 
1050             // Now reset
1051             combo->setRootModelIndex(QModelIndex());
1052             view->setCurrentIndex(index);
1053         }
1054     }
1055     else
1056     {
1057         QIcon remoteIcon = QIcon::fromTheme("network-modem");
1058         combo->setView(new QListView(this));
1059         model->appendRow(new QStandardItem("--"));
1060         QIcon icon;
1061         for (QSharedPointer<DriverInfo>driver : DriverManager::Instance()->getDrivers())
1062         {
1063             if (!families.contains(driver->getType()))
1064                 continue;
1065 
1066             bool locallyAvailable = false;
1067             if (driver->getAuxInfo().contains("LOCALLY_AVAILABLE"))
1068                 locallyAvailable = driver->getAuxInfo().value("LOCALLY_AVAILABLE", false).toBool();
1069             icon = locallyAvailable ? QIcon() : remoteIcon;
1070 
1071             QStandardItem *mount = new QStandardItem(icon, driver->getLabel());
1072             mount->setData(getTooltip(driver), Qt::ToolTipRole);
1073             model->appendRow(mount);
1074         }
1075         combo->setModel(model);
1076         combo->setCurrentText(selectedDriver);
1077     }
1078 }
1079 
1080 void ProfileEditor::executeScriptEditor()
1081 {
1082     if (pi == nullptr)
1083         return;
1084     QStringList currentDrivers;
1085     for (auto &oneCombo : ui->driversGroupBox->findChildren<QComboBox *>())
1086         currentDrivers << oneCombo->currentText();
1087     currentDrivers.removeAll("--");
1088     currentDrivers.removeAll("");
1089     currentDrivers.sort();
1090     ProfileScriptDialog dialog(currentDrivers, pi->scripts, this);
1091     dialog.exec();
1092     auto settings = dialog.jsonSettings();
1093     pi->scripts = QJsonDocument(settings).toJson(QJsonDocument::Compact);
1094 }