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

0001 /*
0002     SPDX-FileCopyrightText: 2022 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "opticaltrainmanager.h"
0008 #include <kstars_debug.h>
0009 
0010 #include "ksnotification.h"
0011 #include "kstarsdata.h"
0012 #include "kstars.h"
0013 #include "indi/indilistener.h"
0014 #include "ekos/auxiliary/profilesettings.h"
0015 #include "oal/equipmentwriter.h"
0016 
0017 #include <QTimer>
0018 #include <QSqlTableModel>
0019 #include <QSqlDatabase>
0020 #include <QSqlRecord>
0021 
0022 #include <basedevice.h>
0023 
0024 #include <algorithm>
0025 
0026 namespace Ekos
0027 {
0028 
0029 OpticalTrainManager *OpticalTrainManager::m_Instance = nullptr;
0030 
0031 ////////////////////////////////////////////////////////////////////////////
0032 ///
0033 ////////////////////////////////////////////////////////////////////////////
0034 OpticalTrainManager *OpticalTrainManager::Instance()
0035 {
0036     if (m_Instance == nullptr)
0037         m_Instance = new OpticalTrainManager();
0038 
0039     return m_Instance;
0040 }
0041 
0042 ////////////////////////////////////////////////////////////////////////////
0043 ///
0044 ////////////////////////////////////////////////////////////////////////////
0045 void OpticalTrainManager::release()
0046 {
0047     delete(m_Instance);
0048     m_Instance = nullptr;
0049 }
0050 
0051 ////////////////////////////////////////////////////////////////////////////
0052 ///
0053 ////////////////////////////////////////////////////////////////////////////
0054 OpticalTrainManager::OpticalTrainManager() : QDialog(KStars::Instance())
0055 {
0056 #ifdef Q_OS_OSX
0057     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0058 #endif
0059 
0060     setupUi(this);
0061 
0062     connect(this, &QDialog::finished, this, [this]()
0063     {
0064         emit configurationRequested(false);
0065     });
0066 
0067     // Mount Combo
0068     connect(mountComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0069             [this]()
0070     {
0071         updateOpticalTrainValue(mountComboBox, "mount");
0072     });
0073 
0074     // DustCap Combo
0075     connect(dustCapComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0076             [this]()
0077     {
0078         updateOpticalTrainValue(dustCapComboBox, "dustcap");
0079     });
0080 
0081     // Light Box
0082     connect(lightBoxComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0083             [this]()
0084     {
0085         updateOpticalTrainValue(lightBoxComboBox, "lightbox");
0086     });
0087 
0088     // Scope / Lens
0089     connect(scopeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0090             [this]()
0091     {
0092         updateOpticalTrainValue(scopeComboBox, "scope");
0093     });
0094 
0095     // Reducer
0096     connect(reducerSpinBox, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
0097             [this](double value)
0098     {
0099         updateOpticalTrainValue(value, "reducer");
0100     });
0101 
0102     // Rotator
0103     connect(rotatorComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0104             [this]()
0105     {
0106         updateOpticalTrainValue(rotatorComboBox, "rotator");
0107     });
0108 
0109     // Focuser
0110     connect(focusComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0111             [this]()
0112     {
0113         updateOpticalTrainValue(focusComboBox, "focuser");
0114     });
0115 
0116     // Filter Wheel
0117     connect(filterComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0118             [this]()
0119     {
0120         updateOpticalTrainValue(filterComboBox, "filterwheel");
0121     });
0122 
0123     // Camera
0124     connect(cameraComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0125             [this]()
0126     {
0127         updateOpticalTrainValue(cameraComboBox, "camera");
0128     });
0129 
0130     // Guider
0131     connect(guiderComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0132             [this]()
0133     {
0134         updateOpticalTrainValue(guiderComboBox, "guider");
0135     });
0136 
0137     connect(addB, &QPushButton::clicked, this, [this]()
0138     {
0139         addOpticalTrain(m_TrainNames.count(), i18n("New Train"));
0140         m_OpticalTrainsModel->select();
0141         refreshModel();
0142         trainNamesList->setCurrentRow(trainNamesList->count() - 1);
0143         selectOpticalTrain(trainNamesList->currentItem());
0144     });
0145 
0146     connect(removeB, &QPushButton::clicked, this, [this]()
0147     {
0148         if (trainNamesList->currentItem() != nullptr)
0149         {
0150             removeOpticalTrain(trainNamesList->currentItem()->text());
0151             removeB->setEnabled(false);
0152         }
0153     });
0154 
0155     connect(resetB, &QPushButton::clicked, this, &OpticalTrainManager::reset);
0156 
0157     connect(opticalElementsB, &QPushButton::clicked, this, [this]()
0158     {
0159         QScopedPointer<EquipmentWriter> writer(new EquipmentWriter());
0160         writer->loadEquipment();
0161         writer->exec();
0162         refreshOpticalElements();
0163     });
0164 
0165     connect(trainNamesList, &QListWidget::itemClicked, this, [this](QListWidgetItem * item)
0166     {
0167         selectOpticalTrain(item);
0168     });
0169     connect(trainNamesList, &QListWidget::itemChanged, this, [this](QListWidgetItem * item)
0170     {
0171         renameCurrentOpticalTrain(item->text());
0172     });
0173     connect(trainNamesList, &QListWidget::currentRowChanged, this, [this](int row)
0174     {
0175         if (row >= 0)
0176             selectOpticalTrain(trainNamesList->currentItem());
0177     });
0178 
0179     m_CheckMissingDevicesTimer.setInterval(5000);
0180     m_CheckMissingDevicesTimer.setSingleShot(true);
0181     connect(&m_CheckMissingDevicesTimer, &QTimer::timeout, this, &OpticalTrainManager::checkMissingDevices);
0182 
0183     m_DelegateTimer.setInterval(1000);
0184     m_DelegateTimer.setSingleShot(true);
0185     connect(&m_DelegateTimer, &QTimer::timeout, this, [this]()
0186     {
0187         if (m_Profile)
0188             setProfile(m_Profile);
0189     });
0190 
0191     initModel();
0192 }
0193 
0194 ////////////////////////////////////////////////////////////////////////////
0195 ///
0196 ////////////////////////////////////////////////////////////////////////////
0197 void OpticalTrainManager::initModel()
0198 {
0199     auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName());
0200     m_OpticalTrainsModel = new QSqlTableModel(this, userdb);
0201     connect(m_OpticalTrainsModel, &QSqlTableModel::dataChanged, this, [this]()
0202     {
0203         m_OpticalTrains.clear();
0204         for (int i = 0; i < m_OpticalTrainsModel->rowCount(); ++i)
0205         {
0206             QVariantMap recordMap;
0207             QSqlRecord record = m_OpticalTrainsModel->record(i);
0208             for (int j = 0; j < record.count(); j++)
0209                 recordMap[record.fieldName(j)] = record.value(j);
0210 
0211             m_OpticalTrains.append(recordMap);
0212         }
0213 
0214         m_TrainNames.clear();
0215         for (auto &oneTrain : m_OpticalTrains)
0216             m_TrainNames << oneTrain["name"].toString();
0217 
0218         trainNamesList->clear();
0219         trainNamesList->addItems(m_TrainNames);
0220         trainNamesList->setEditTriggers(QAbstractItemView::AllEditTriggers);
0221         emit updated();
0222     });
0223 }
0224 
0225 void OpticalTrainManager::syncDevices()
0226 {
0227     syncDelegatesToDevices();
0228     if (m_Profile)
0229     {
0230         refreshModel();
0231         emit updated();
0232     }
0233 }
0234 
0235 ////////////////////////////////////////////////////////////////////////////
0236 ///
0237 ////////////////////////////////////////////////////////////////////////////
0238 void OpticalTrainManager::refreshModel()
0239 {
0240     KStars::Instance()->data()->userdb()->GetOpticalTrains(m_Profile->id, m_OpticalTrains);
0241     m_TrainNames.clear();
0242     for (auto &oneTrain : m_OpticalTrains)
0243         m_TrainNames << oneTrain["name"].toString();
0244 
0245     trainNamesList->clear();
0246     trainNamesList->addItems(m_TrainNames);
0247 }
0248 
0249 ////////////////////////////////////////////////////////////////////////////
0250 ///
0251 ////////////////////////////////////////////////////////////////////////////
0252 void OpticalTrainManager::syncActiveDevices()
0253 {
0254     for (auto &oneTrain : m_OpticalTrains)
0255     {
0256         auto train = oneTrain["name"].toString();
0257         QSharedPointer<ISD::GenericDevice> device;
0258 
0259         if (getGenericDevice(train, Mount, device))
0260             syncActiveProperties(oneTrain, device);
0261         if (getGenericDevice(train, Camera, device))
0262             syncActiveProperties(oneTrain, device);
0263         if (getGenericDevice(train, GuideVia, device))
0264             syncActiveProperties(oneTrain, device);
0265         if (getGenericDevice(train, Focuser, device))
0266             syncActiveProperties(oneTrain, device);
0267         if (getGenericDevice(train, FilterWheel, device))
0268             syncActiveProperties(oneTrain, device);
0269         if (getGenericDevice(train, Rotator, device))
0270             syncActiveProperties(oneTrain, device);
0271         if (getGenericDevice(train, DustCap, device))
0272             syncActiveProperties(oneTrain, device);
0273         if (getGenericDevice(train, LightBox, device))
0274             syncActiveProperties(oneTrain, device);
0275     }
0276 }
0277 
0278 ////////////////////////////////////////////////////////////////////////////
0279 ///
0280 ////////////////////////////////////////////////////////////////////////////
0281 void OpticalTrainManager::syncActiveProperties(const QVariantMap &train, const QSharedPointer<ISD::GenericDevice> &device)
0282 {
0283     auto tvp = device->getProperty("ACTIVE_DEVICES");
0284     if (!tvp)
0285         return;
0286 
0287     auto name = train["name"].toString();
0288 
0289     for (auto &it : *tvp.getText())
0290     {
0291         QList<QSharedPointer<ISD::GenericDevice>> devs;
0292         QString elementText = it.getText();
0293         if (it.isNameMatch("ACTIVE_TELESCOPE"))
0294         {
0295             auto activeDevice = train["mount"].toString();
0296             if (activeDevice == "--")
0297                 elementText.clear();
0298             else if (activeDevice != elementText)
0299             {
0300                 QSharedPointer<ISD::GenericDevice> genericDevice;
0301                 if (getGenericDevice(name, Mount, genericDevice))
0302                     devs.append(genericDevice);
0303             }
0304         }
0305         else if (it.isNameMatch("ACTIVE_DOME"))
0306         {
0307             devs = INDIListener::devicesByInterface(INDI::BaseDevice::DOME_INTERFACE);
0308         }
0309         else if (it.isNameMatch("ACTIVE_GPS"))
0310         {
0311             devs = INDIListener::devicesByInterface(INDI::BaseDevice::GPS_INTERFACE);
0312         }
0313         else if (it.isNameMatch("ACTIVE_ROTATOR"))
0314         {
0315             auto activeDevice = train["rotator"].toString();
0316             if (activeDevice == "--")
0317                 elementText.clear();
0318             else if (activeDevice != elementText)
0319             {
0320                 QSharedPointer<ISD::GenericDevice> genericDevice;
0321                 if (getGenericDevice(name, Rotator, genericDevice))
0322                     devs.append(genericDevice);
0323             }
0324         }
0325         else if (it.isNameMatch("ACTIVE_FOCUSER"))
0326         {
0327             auto activeDevice = train["focuser"].toString();
0328             if (activeDevice == "--")
0329                 elementText.clear();
0330             else if (activeDevice != elementText)
0331             {
0332                 QSharedPointer<ISD::GenericDevice> genericDevice;
0333                 if (getGenericDevice(name, Focuser, genericDevice))
0334                     devs.append(genericDevice);
0335             }
0336         }
0337         else if (it.isNameMatch("ACTIVE_FILTER"))
0338         {
0339             auto activeDevice = train["filterwheel"].toString();
0340             if (activeDevice == "--")
0341                 elementText.clear();
0342             else if (activeDevice != elementText)
0343             {
0344                 QSharedPointer<ISD::GenericDevice> genericDevice;
0345                 if (getGenericDevice(name, FilterWheel, genericDevice))
0346                     devs.append(genericDevice);
0347             }
0348         }
0349 
0350         if (!devs.empty())
0351         {
0352             if (it.getText() != devs.first()->getDeviceName())
0353             {
0354                 it.setText(devs.first()->getDeviceName().toLatin1().constData());
0355                 device->sendNewProperty(tvp.getText());
0356             }
0357         }
0358         // Clear element if required
0359         else if (elementText.isEmpty() && !QString(it.getText()).isEmpty())
0360         {
0361             it.setText("");
0362             device->sendNewProperty(tvp.getText());
0363         }
0364     }
0365 
0366 }
0367 
0368 ////////////////////////////////////////////////////////////////////////////
0369 ///
0370 ////////////////////////////////////////////////////////////////////////////
0371 void OpticalTrainManager::setProfile(const QSharedPointer<ProfileInfo> &profile)
0372 {
0373     m_DelegateTimer.stop();
0374 
0375     // Load optical train model
0376     if (m_Profile != profile)
0377     {
0378         m_Profile = profile;
0379         refreshModel();
0380     }
0381 
0382     // Are we still updating delegates? If yes, return.
0383     if (syncDelegatesToDevices())
0384     {
0385         m_CheckMissingDevicesTimer.start();
0386 
0387         // Start delegate timer to ensure no more changes are pending.
0388         m_DelegateTimer.start();
0389 
0390         syncActiveDevices();
0391     }
0392     else
0393     {
0394         checkOpticalTrains();
0395     }
0396 }
0397 
0398 ////////////////////////////////////////////////////////////////////////////
0399 ///
0400 ////////////////////////////////////////////////////////////////////////////
0401 void OpticalTrainManager::checkOpticalTrains()
0402 {
0403     if (m_OpticalTrains.empty())
0404     {
0405         generateOpticalTrains();
0406         refreshModel();
0407         if (!m_OpticalTrains.empty())
0408         {
0409             auto primaryTrainID = m_OpticalTrains[0]["id"].toUInt();
0410             ProfileSettings::Instance()->setOneSetting(ProfileSettings::PrimaryOpticalTrain, primaryTrainID);
0411             ProfileSettings::Instance()->setOneSetting(ProfileSettings::CaptureOpticalTrain, primaryTrainID);
0412             ProfileSettings::Instance()->setOneSetting(ProfileSettings::FocusOpticalTrain, primaryTrainID);
0413             ProfileSettings::Instance()->setOneSetting(ProfileSettings::MountOpticalTrain, primaryTrainID);
0414             ProfileSettings::Instance()->setOneSetting(ProfileSettings::AlignOpticalTrain, primaryTrainID);
0415             ProfileSettings::Instance()->setOneSetting(ProfileSettings::DarkLibraryOpticalTrain, primaryTrainID);
0416             if (m_OpticalTrains.count() > 1)
0417                 ProfileSettings::Instance()->setOneSetting(ProfileSettings::GuideOpticalTrain, m_OpticalTrains[1]["id"].toInt());
0418             else
0419                 ProfileSettings::Instance()->setOneSetting(ProfileSettings::GuideOpticalTrain, primaryTrainID);
0420         }
0421 
0422         emit updated();
0423         show();
0424         raise();
0425         emit configurationRequested(true);
0426     }
0427     else
0428     {
0429         m_CheckMissingDevicesTimer.start();
0430         emit updated();
0431     }
0432 }
0433 ////////////////////////////////////////////////////////////////////////////
0434 /// This method tries to guess possible optical train configuration
0435 ////////////////////////////////////////////////////////////////////////////
0436 void OpticalTrainManager::generateOpticalTrains()
0437 {
0438     // We should have primary train
0439     addOpticalTrain(0, i18n("Primary"));
0440     // Check if need secondary train
0441     if (cameraComboBox->count() > 2)
0442         addOpticalTrain(1, i18n("Secondary"));
0443     // Check if need tertiary train
0444     if (cameraComboBox->count() > 3)
0445         addOpticalTrain(2, i18n("Tertiary"));
0446 }
0447 
0448 ////////////////////////////////////////////////////////////////////////////
0449 ///
0450 ////////////////////////////////////////////////////////////////////////////
0451 QString OpticalTrainManager::addOpticalTrain(uint8_t index, const QString &name)
0452 {
0453     QVariantMap train;
0454     train["profile"] = m_Profile->id;
0455     train["name"] = uniqueTrainName(name);
0456 
0457     train["mount"] = mountComboBox->itemText(mountComboBox->count() - 1);
0458     train["dustcap"] = dustCapComboBox->itemText(dustCapComboBox->count() - 1);
0459     train["lightbox"] = lightBoxComboBox->itemText(lightBoxComboBox->count() - 1);
0460     train["reducer"] = 1.0;
0461     train["rotator"] = rotatorComboBox->itemText(rotatorComboBox->count() - 1);
0462     train["focuser"] = focusComboBox->itemText(focusComboBox->count() - 1);
0463     train["filterwheel"] = filterComboBox->itemText(filterComboBox->count() - 1);
0464     train["guider"] = guiderComboBox->itemText(guiderComboBox->count() - 1);
0465 
0466     QJsonObject opticalElement;
0467     if (KStars::Instance()->data()->userdb()->getLastOpticalElement(opticalElement))
0468         train["scope"] = opticalElement["name"].toString();
0469 
0470     train["camera"] = "--";
0471     // Primary train
0472     if (index == 0 && cameraComboBox->count() > 1)
0473         train["camera"] = cameraComboBox->itemText(1);
0474     // Any other trains
0475     else if (index > 0)
0476     {
0477         // For 2nd train and beyond, we get the N camera appropiate for this train if one exist.
0478         // We add + 1 because first element in combobox is "--"
0479         auto cameraIndex = index + 1;
0480         if (cameraComboBox->count() >= cameraIndex)
0481             train["camera"] = cameraComboBox->itemText(cameraIndex);
0482     }
0483 
0484     KStarsData::Instance()->userdb()->AddOpticalTrain(train);
0485     return train["name"].toString();
0486 }
0487 
0488 ////////////////////////////////////////////////////////////////////////////
0489 ///
0490 ////////////////////////////////////////////////////////////////////////////
0491 void OpticalTrainManager::addOpticalTrain(const QJsonObject &value)
0492 {
0493     auto newTrain = value.toVariantMap();
0494     newTrain["profile"] = m_Profile->id;
0495     KStarsData::Instance()->userdb()->AddOpticalTrain(newTrain);
0496 
0497     refreshTrains();
0498 }
0499 
0500 ////////////////////////////////////////////////////////////////////////////
0501 ///
0502 ////////////////////////////////////////////////////////////////////////////
0503 bool OpticalTrainManager::setOpticalTrainValue(const QString &name, const QString &field, const QVariant &value)
0504 {
0505     for (auto &oneTrain : m_OpticalTrains)
0506     {
0507         if (oneTrain["name"].toString() == name)
0508         {
0509             // If value did not change, just return true
0510             if (oneTrain[field] == value)
0511                 return true;
0512 
0513             // Update field and database.
0514             oneTrain[field] = value;
0515             KStarsData::Instance()->userdb()->UpdateOpticalTrain(oneTrain, oneTrain["id"].toInt());
0516             syncActiveDevices();
0517             emit updated();
0518             return true;
0519         }
0520     }
0521     return false;
0522 }
0523 
0524 ////////////////////////////////////////////////////////////////////////////
0525 ///
0526 ////////////////////////////////////////////////////////////////////////////
0527 void OpticalTrainManager::renameCurrentOpticalTrain(const QString &name)
0528 {
0529     if (m_CurrentOpticalTrain != nullptr && (*m_CurrentOpticalTrain)["name"] != name)
0530     {
0531         auto pos = trainNamesList->currentRow();
0532         // ensure train name uniqueness
0533         auto unique = uniqueTrainName(name);
0534         // update the train database entry
0535         setOpticalTrainValue((*m_CurrentOpticalTrain)["name"].toString(), "name", unique);
0536         // propagate the unique name to the current selection
0537         trainNamesList->currentItem()->setText(unique);
0538         // refresh the trains
0539         refreshTrains();
0540         // refresh selection
0541         selectOpticalTrain(unique);
0542         trainNamesList->setCurrentRow(pos);
0543     }
0544 }
0545 
0546 ////////////////////////////////////////////////////////////////////////////
0547 ///
0548 ////////////////////////////////////////////////////////////////////////////
0549 bool OpticalTrainManager::setOpticalTrain(const QJsonObject &train)
0550 {
0551     auto oneOpticalTrain = getOpticalTrain(train["id"].toInt());
0552     if (!oneOpticalTrain.empty())
0553     {
0554         KStarsData::Instance()->userdb()->UpdateOpticalTrain(train.toVariantMap(), oneOpticalTrain["id"].toInt());
0555         refreshTrains();
0556         return true;
0557     }
0558     return false;
0559 }
0560 
0561 ////////////////////////////////////////////////////////////////////////////
0562 ///
0563 ////////////////////////////////////////////////////////////////////////////
0564 bool OpticalTrainManager::removeOpticalTrain(const QString &name)
0565 {
0566     for (auto &oneTrain : m_OpticalTrains)
0567     {
0568         if (oneTrain["name"].toString() == name)
0569         {
0570             auto id = oneTrain["id"].toInt();
0571             KStarsData::Instance()->userdb()->DeleteOpticalTrain(id);
0572             KStarsData::Instance()->userdb()->DeleteOpticalTrainSettings(id);
0573             refreshTrains();
0574             selectOpticalTrain(nullptr);
0575             return true;
0576         }
0577     }
0578 
0579     return false;
0580 }
0581 
0582 ////////////////////////////////////////////////////////////////////////////
0583 ///
0584 ////////////////////////////////////////////////////////////////////////////
0585 bool OpticalTrainManager::syncDelegatesToDevices()
0586 {
0587     auto changed = false;
0588 
0589     // Mounts
0590     auto mounts = INDIListener::devicesByInterface(INDI::BaseDevice::TELESCOPE_INTERFACE);
0591     QStringList values;
0592     for (auto &oneMount : mounts)
0593         values << oneMount->getDeviceName();
0594     changed |= !values.empty() && values != m_MountNames;
0595     m_MountNames = values;
0596     auto currentMount = mountComboBox->currentText();
0597     mountComboBox->clear();
0598     mountComboBox->addItems(QStringList() << "--" << values);
0599     mountComboBox->setCurrentText(currentMount);
0600 
0601     // Dust Caps
0602     values.clear();
0603     auto dustcaps = INDIListener::devicesByInterface(INDI::BaseDevice::DUSTCAP_INTERFACE);
0604     for (auto &oneCap : dustcaps)
0605         values << oneCap->getDeviceName();
0606     changed |= !values.empty() && values != m_DustCapNames;
0607     m_DustCapNames = values;
0608     auto currentCap = dustCapComboBox->currentText();
0609     dustCapComboBox->clear();
0610     dustCapComboBox->addItems(QStringList() << "--" << values);
0611     dustCapComboBox->setCurrentText(currentCap);
0612 
0613     // Light Boxes
0614     values.clear();
0615     auto lightboxes = INDIListener::devicesByInterface(INDI::BaseDevice::LIGHTBOX_INTERFACE);
0616     for (auto &oneBox : lightboxes)
0617         values << oneBox->getDeviceName();
0618     changed |= !values.empty() && values != m_LightBoxNames;
0619     auto currentLightBox = lightBoxComboBox->currentText();
0620     m_LightBoxNames = values;
0621     lightBoxComboBox->clear();
0622     lightBoxComboBox->addItems(QStringList() << "--" << values);
0623     lightBoxComboBox->setCurrentText(currentLightBox);
0624 
0625     // Scopes
0626     values = KStars::Instance()->data()->userdb()->getOpticalElementNames();
0627     changed |= !values.empty() && values != m_ScopeNames;
0628     m_ScopeNames = values;
0629     auto currentScope = scopeComboBox->currentText();
0630     scopeComboBox->clear();
0631     scopeComboBox->addItems(QStringList() << "--" << values);
0632     scopeComboBox->setCurrentText(currentScope);
0633 
0634     // Rotators
0635     values.clear();
0636     auto rotators = INDIListener::devicesByInterface(INDI::BaseDevice::ROTATOR_INTERFACE);
0637     for (auto &oneRotator : rotators)
0638         values << oneRotator->getDeviceName();
0639     changed |= !values.empty() && values != m_RotatorNames;
0640     m_RotatorNames = values;
0641     auto currentRotator = rotatorComboBox->currentText();
0642     rotatorComboBox->clear();
0643     rotatorComboBox->addItems(QStringList() << "--" << values);
0644     rotatorComboBox->setCurrentText(currentRotator);
0645 
0646     // Focusers
0647     values.clear();
0648     auto focusers = INDIListener::devicesByInterface(INDI::BaseDevice::FOCUSER_INTERFACE);
0649     for (auto &oneFocuser : focusers)
0650         values << oneFocuser->getDeviceName();
0651     changed |= !values.empty() && values != m_FocuserNames;
0652     m_FocuserNames = values;
0653     auto currentFocuser = focusComboBox->currentText();
0654     focusComboBox->clear();
0655     focusComboBox->addItems(QStringList() << "--" << values);
0656     focusComboBox->setCurrentText(currentFocuser);
0657 
0658     // Filter Wheels
0659     values.clear();
0660     auto filterwheels = INDIListener::devicesByInterface(INDI::BaseDevice::FILTER_INTERFACE);
0661     for (auto &oneFilterWheel : filterwheels)
0662         values << oneFilterWheel->getDeviceName();
0663     changed |= !values.empty() && values != m_FilterWheelNames;
0664     m_FilterWheelNames = values;
0665     auto currentFilter = filterComboBox->currentText();
0666     filterComboBox->clear();
0667     filterComboBox->addItems(QStringList() << "--" << values);
0668     filterComboBox->setCurrentText(currentFilter);
0669 
0670     // Cameras
0671     values.clear();
0672     auto cameras = INDIListener::devicesByInterface(INDI::BaseDevice::CCD_INTERFACE);
0673     for (auto &oneCamera : cameras)
0674         values << oneCamera->getDeviceName();
0675     changed |= !values.empty() && values != m_CameraNames;
0676     m_CameraNames = values;
0677     auto currentCamera = cameraComboBox->currentText();
0678     cameraComboBox->clear();
0679     cameraComboBox->addItems(QStringList() << "--" << values);
0680     cameraComboBox->setCurrentText(currentCamera);
0681 
0682     // Guiders
0683     values.clear();
0684     auto guiders = INDIListener::devicesByInterface(INDI::BaseDevice::GUIDER_INTERFACE);
0685     for (auto &oneGuider : guiders)
0686         values << oneGuider->getDeviceName();
0687     changed |= !values.empty() && values != m_GuiderNames;
0688     m_GuiderNames = values;
0689     auto currentGuider = guiderComboBox->currentText();
0690     guiderComboBox->clear();
0691     guiderComboBox->addItems(QStringList() << "--" << values);
0692     guiderComboBox->setCurrentText(currentGuider);
0693 
0694     return changed;
0695 }
0696 
0697 ////////////////////////////////////////////////////////////////////////////
0698 ///
0699 ////////////////////////////////////////////////////////////////////////////
0700 QString OpticalTrainManager::uniqueTrainName(QString name)
0701 {
0702     QString result = name;
0703     int nr = 1;
0704     while (m_TrainNames.contains(result))
0705         result = QString("%1 (%2)").arg(name).arg(nr++);
0706 
0707     return result;
0708 }
0709 
0710 ////////////////////////////////////////////////////////////////////////////
0711 ///
0712 ////////////////////////////////////////////////////////////////////////////
0713 bool OpticalTrainManager::selectOpticalTrain(QListWidgetItem *item)
0714 {
0715     if (item != nullptr && selectOpticalTrain(item->text()))
0716     {
0717         item->setFlags(item->flags() | Qt::ItemIsEditable);
0718         return true;
0719     }
0720     return false;
0721 }
0722 
0723 ////////////////////////////////////////////////////////////////////////////
0724 ///
0725 ////////////////////////////////////////////////////////////////////////////
0726 QString OpticalTrainManager::findTrainContainingDevice(const QString &name, Role role)
0727 {
0728     for (auto &oneTrain : m_OpticalTrains)
0729     {
0730         auto train = oneTrain["name"].toString();
0731 
0732         switch (role)
0733         {
0734             case Mount:
0735                 if (oneTrain["mount"].toString() == name)
0736                     return train;
0737                 break;
0738             case Camera:
0739                 if (oneTrain["camera"].toString() == name)
0740                     return train;
0741                 break;
0742             case Rotator:
0743                 if (oneTrain["rotator"].toString() == name)
0744                     return train;
0745                 break;
0746             case GuideVia:
0747                 if (oneTrain["guider"].toString() == name)
0748                     return train;
0749                 break;
0750             case DustCap:
0751                 if (oneTrain["dustcap"].toString() == name)
0752                     return train;
0753                 break;
0754             case Scope:
0755                 if (oneTrain["scope"].toString() == name)
0756                     return train;
0757                 break;
0758             case FilterWheel:
0759                 if (oneTrain["filterwheel"].toString() == name)
0760                     return train;
0761                 break;
0762             case Focuser:
0763                 if (oneTrain["focuser"].toString() == name)
0764                     return train;
0765                 break;
0766             case Reducer:
0767                 if (oneTrain["reducer"].toString() == name)
0768                     return train;
0769                 break;
0770             case LightBox:
0771                 if (oneTrain["lightbox"].toString() == name)
0772                     return train;
0773                 break;
0774         }
0775 
0776     }
0777 
0778     return QString();
0779 }
0780 
0781 ////////////////////////////////////////////////////////////////////////////
0782 ///
0783 ////////////////////////////////////////////////////////////////////////////
0784 bool OpticalTrainManager::selectOpticalTrain(const QString &name)
0785 {
0786     for (auto &oneTrain : m_OpticalTrains)
0787     {
0788         if (oneTrain["name"].toString() == name)
0789         {
0790             m_Persistent = false;
0791             m_CurrentOpticalTrain = &oneTrain;
0792             mountComboBox->setCurrentText(oneTrain["mount"].toString());
0793             dustCapComboBox->setCurrentText(oneTrain["dustcap"].toString());
0794             lightBoxComboBox->setCurrentText(oneTrain["lightbox"].toString());
0795             scopeComboBox->setCurrentText(oneTrain["scope"].toString());
0796             reducerSpinBox->setValue(oneTrain["reducer"].toDouble());
0797             rotatorComboBox->setCurrentText(oneTrain["rotator"].toString());
0798             focusComboBox->setCurrentText(oneTrain["focuser"].toString());
0799             filterComboBox->setCurrentText(oneTrain["filterwheel"].toString());
0800             cameraComboBox->setCurrentText(oneTrain["camera"].toString());
0801             guiderComboBox->setCurrentText(oneTrain["guider"].toString());
0802             removeB->setEnabled(m_OpticalTrains.length() > 1);
0803             trainConfigBox->setEnabled(true);
0804             m_Persistent = true;
0805             return true;
0806         }
0807     }
0808 
0809     // none found
0810     m_Persistent = false;
0811     m_CurrentOpticalTrain = nullptr;
0812     mountComboBox->setCurrentText("--");
0813     dustCapComboBox->setCurrentText("--");
0814     lightBoxComboBox->setCurrentText("--");
0815     scopeComboBox->setCurrentText("--");
0816     reducerSpinBox->setValue(1.0);
0817     rotatorComboBox->setCurrentText("--");
0818     focusComboBox->setCurrentText("--");
0819     filterComboBox->setCurrentText("--");
0820     cameraComboBox->setCurrentText("--");
0821     guiderComboBox->setCurrentText("--");
0822     removeB->setEnabled(false);
0823     trainConfigBox->setEnabled(false);
0824     m_Persistent = true;
0825     return false;
0826 }
0827 
0828 ////////////////////////////////////////////////////////////////////////////
0829 ///
0830 ////////////////////////////////////////////////////////////////////////////
0831 void OpticalTrainManager::openEditor(const QString &name)
0832 {
0833     selectOpticalTrain(name);
0834     QList<QListWidgetItem*> matches = trainNamesList->findItems(name, Qt::MatchExactly);
0835     if (matches.count() > 0)
0836         trainNamesList->setCurrentItem(matches.first());
0837     emit configurationRequested(true);
0838     show();
0839 }
0840 
0841 ////////////////////////////////////////////////////////////////////////////
0842 ///
0843 ////////////////////////////////////////////////////////////////////////////
0844 bool OpticalTrainManager::getGenericDevice(const QString &train, Role role, QSharedPointer<ISD::GenericDevice> &generic)
0845 {
0846     for (auto &oneTrain : m_OpticalTrains)
0847     {
0848         if (oneTrain["name"].toString() == train)
0849         {
0850             switch (role)
0851             {
0852                 case Mount:
0853                     return INDIListener::findDevice(oneTrain["mount"].toString(), generic);
0854                 case Camera:
0855                     return INDIListener::findDevice(oneTrain["camera"].toString(), generic);
0856                 case Rotator:
0857                     return INDIListener::findDevice(oneTrain["rotator"].toString(), generic);
0858                 case GuideVia:
0859                     return INDIListener::findDevice(oneTrain["guider"].toString(), generic);
0860                 case DustCap:
0861                     return INDIListener::findDevice(oneTrain["dustcap"].toString(), generic);
0862                 case FilterWheel:
0863                     return INDIListener::findDevice(oneTrain["filterwheel"].toString(), generic);
0864                 case Focuser:
0865                     return INDIListener::findDevice(oneTrain["focuser"].toString(), generic);
0866                 case LightBox:
0867                     return INDIListener::findDevice(oneTrain["lightbox"].toString(), generic);
0868                 default:
0869                     break;
0870             }
0871         }
0872     }
0873 
0874     return false;
0875 }
0876 
0877 ////////////////////////////////////////////////////////////////////////////
0878 ///
0879 ////////////////////////////////////////////////////////////////////////////
0880 ISD::Mount *OpticalTrainManager::getMount(const QString &name)
0881 {
0882     for (auto &oneTrain : m_OpticalTrains)
0883     {
0884         if (oneTrain["name"].toString() == name)
0885         {
0886             QSharedPointer<ISD::GenericDevice> generic;
0887             if (INDIListener::findDevice(oneTrain["mount"].toString(), generic))
0888                 return generic->getMount();
0889         }
0890     }
0891 
0892     return nullptr;
0893 }
0894 
0895 ////////////////////////////////////////////////////////////////////////////
0896 ///
0897 ////////////////////////////////////////////////////////////////////////////
0898 ISD::DustCap *OpticalTrainManager::getDustCap(const QString &name)
0899 {
0900     for (auto &oneTrain : m_OpticalTrains)
0901     {
0902         if (oneTrain["name"].toString() == name)
0903         {
0904             QSharedPointer<ISD::GenericDevice> generic;
0905             if (INDIListener::findDevice(oneTrain["dustcap"].toString(), generic))
0906                 return generic->getDustCap();
0907         }
0908     }
0909 
0910     return nullptr;
0911 }
0912 
0913 ////////////////////////////////////////////////////////////////////////////
0914 ///
0915 ////////////////////////////////////////////////////////////////////////////
0916 ISD::LightBox *OpticalTrainManager::getLightBox(const QString &name)
0917 {
0918     for (auto &oneTrain : m_OpticalTrains)
0919     {
0920         if (oneTrain["name"].toString() == name)
0921         {
0922             QSharedPointer<ISD::GenericDevice> generic;
0923             if (INDIListener::findDevice(oneTrain["lightbox"].toString(), generic))
0924                 return generic->getLightBox();
0925         }
0926     }
0927 
0928     return nullptr;
0929 }
0930 
0931 ////////////////////////////////////////////////////////////////////////////
0932 ///
0933 ////////////////////////////////////////////////////////////////////////////
0934 QJsonObject OpticalTrainManager::getScope(const QString &name)
0935 {
0936     QJsonObject oneOpticalElement;
0937     for (auto &oneTrain : m_OpticalTrains)
0938     {
0939         if (oneTrain["name"].toString() == name)
0940         {
0941             if (KStars::Instance()->data()->userdb()->getOpticalElementByName(oneTrain["scope"].toString(), oneOpticalElement))
0942                 return oneOpticalElement;
0943         }
0944     }
0945 
0946     return oneOpticalElement;
0947 }
0948 
0949 ////////////////////////////////////////////////////////////////////////////
0950 ///
0951 ////////////////////////////////////////////////////////////////////////////
0952 double OpticalTrainManager::getReducer(const QString &name)
0953 {
0954     for (auto &oneTrain : m_OpticalTrains)
0955     {
0956         if (oneTrain["name"].toString() == name)
0957             return oneTrain["reducer"].toDouble();
0958     }
0959 
0960     return 1;
0961 }
0962 
0963 ////////////////////////////////////////////////////////////////////////////
0964 ///
0965 ////////////////////////////////////////////////////////////////////////////
0966 ISD::Rotator *OpticalTrainManager::getRotator(const QString &name)
0967 {
0968     for (auto &oneTrain : m_OpticalTrains)
0969     {
0970         if (oneTrain["name"].toString() == name)
0971         {
0972             QSharedPointer<ISD::GenericDevice> generic;
0973             if (INDIListener::findDevice(oneTrain["rotator"].toString(), generic))
0974                 return generic->getRotator();
0975         }
0976     }
0977 
0978     return nullptr;
0979 }
0980 
0981 ////////////////////////////////////////////////////////////////////////////
0982 ///
0983 ////////////////////////////////////////////////////////////////////////////
0984 ISD::Focuser *OpticalTrainManager::getFocuser(const QString &name)
0985 {
0986     for (auto &oneTrain : m_OpticalTrains)
0987     {
0988         if (oneTrain["name"].toString() == name)
0989         {
0990             QSharedPointer<ISD::GenericDevice> generic;
0991             if (INDIListener::findDevice(oneTrain["focuser"].toString(), generic))
0992                 return generic->getFocuser();
0993         }
0994     }
0995 
0996     return nullptr;
0997 }
0998 
0999 ////////////////////////////////////////////////////////////////////////////
1000 ///
1001 ////////////////////////////////////////////////////////////////////////////
1002 ISD::FilterWheel *OpticalTrainManager::getFilterWheel(const QString &name)
1003 {
1004     for (auto &oneTrain : m_OpticalTrains)
1005     {
1006         if (oneTrain["name"].toString() == name)
1007         {
1008             QSharedPointer<ISD::GenericDevice> generic;
1009             if (INDIListener::findDevice(oneTrain["filterwheel"].toString(), generic))
1010                 return generic->getFilterWheel();
1011         }
1012     }
1013 
1014     return nullptr;
1015 }
1016 
1017 ////////////////////////////////////////////////////////////////////////////
1018 ///
1019 ////////////////////////////////////////////////////////////////////////////
1020 ISD::Camera *OpticalTrainManager::getCamera(const QString &name)
1021 {
1022     for (auto &oneTrain : m_OpticalTrains)
1023     {
1024         if (oneTrain["name"].toString() == name)
1025         {
1026             QSharedPointer<ISD::GenericDevice> generic;
1027             if (INDIListener::findDevice(oneTrain["camera"].toString(), generic))
1028                 return generic->getCamera();
1029         }
1030     }
1031 
1032     return nullptr;
1033 }
1034 
1035 ////////////////////////////////////////////////////////////////////////////
1036 ///
1037 ////////////////////////////////////////////////////////////////////////////
1038 ISD::Guider *OpticalTrainManager::getGuider(const QString &name)
1039 {
1040     for (auto &oneTrain : m_OpticalTrains)
1041     {
1042         if (oneTrain["name"].toString() == name)
1043         {
1044             QSharedPointer<ISD::GenericDevice> generic;
1045             if (INDIListener::findDevice(oneTrain["guider"].toString(), generic))
1046                 return generic->getGuider();
1047         }
1048     }
1049 
1050     return nullptr;
1051 }
1052 
1053 ////////////////////////////////////////////////////////////////////////////
1054 ///
1055 ////////////////////////////////////////////////////////////////////////////
1056 ISD::AdaptiveOptics *OpticalTrainManager::getAdaptiveOptics(const QString &name)
1057 {
1058     // FIXME not implmeneted yet.
1059     // Need to add to database later
1060     for (auto &oneTrain : m_OpticalTrains)
1061     {
1062         if (oneTrain["name"].toString() == name)
1063         {
1064             QSharedPointer<ISD::GenericDevice> generic;
1065             if (INDIListener::findDevice(oneTrain["adaptiveoptics"].toString(), generic))
1066                 return generic->getAdaptiveOptics();
1067         }
1068     }
1069 
1070     return nullptr;
1071 }
1072 
1073 ////////////////////////////////////////////////////////////////////////////
1074 ///
1075 ////////////////////////////////////////////////////////////////////////////
1076 const QVariantMap OpticalTrainManager::getOpticalTrain(uint8_t id) const
1077 {
1078     for (auto &oneTrain : m_OpticalTrains)
1079     {
1080         if (oneTrain["id"].toInt() == id)
1081             return oneTrain;
1082     }
1083 
1084     return QVariantMap();
1085 }
1086 
1087 ////////////////////////////////////////////////////////////////////////////
1088 ///
1089 ////////////////////////////////////////////////////////////////////////////
1090 bool OpticalTrainManager::exists(uint8_t id) const
1091 {
1092     for (auto &oneTrain : m_OpticalTrains)
1093     {
1094         if (oneTrain["id"].toInt() == id)
1095             return true;
1096     }
1097 
1098     return false;
1099 }
1100 
1101 ////////////////////////////////////////////////////////////////////////////
1102 ///
1103 ////////////////////////////////////////////////////////////////////////////
1104 const QVariantMap OpticalTrainManager::getOpticalTrain(const QString &name) const
1105 {
1106     for (auto &oneTrain : m_OpticalTrains)
1107     {
1108         if (oneTrain["name"].toString() == name)
1109             return oneTrain;
1110     }
1111 
1112     return QVariantMap();
1113 }
1114 
1115 ////////////////////////////////////////////////////////////////////////////
1116 ///
1117 ////////////////////////////////////////////////////////////////////////////
1118 void OpticalTrainManager::refreshTrains()
1119 {
1120     refreshModel();
1121     emit updated();
1122 }
1123 
1124 ////////////////////////////////////////////////////////////////////////////
1125 ///
1126 ////////////////////////////////////////////////////////////////////////////
1127 void OpticalTrainManager::refreshOpticalElements()
1128 {
1129     m_ScopeNames = KStars::Instance()->data()->userdb()->getOpticalElementNames();
1130     syncDelegatesToDevices();
1131 }
1132 
1133 ////////////////////////////////////////////////////////////////////////////
1134 ///
1135 ////////////////////////////////////////////////////////////////////////////
1136 int OpticalTrainManager::id(const QString &name) const
1137 {
1138     for (auto &oneTrain : m_OpticalTrains)
1139     {
1140         if (oneTrain["name"].toString() == name)
1141             return oneTrain["id"].toUInt();
1142     }
1143 
1144     return -1;
1145 }
1146 
1147 ////////////////////////////////////////////////////////////////////////////
1148 ///
1149 ////////////////////////////////////////////////////////////////////////////
1150 QString OpticalTrainManager::name(int id) const
1151 {
1152     for (auto &oneTrain : m_OpticalTrains)
1153     {
1154         if (oneTrain["id"].toInt() == id)
1155             return oneTrain["name"].toString();
1156     }
1157 
1158     return QString();
1159 }
1160 
1161 ////////////////////////////////////////////////////////////////////////////
1162 ///
1163 ////////////////////////////////////////////////////////////////////////////
1164 void OpticalTrainManager::checkMissingDevices()
1165 {
1166     // Double check the sanity of the train. If devices are added or missing, then we need to show it to alert the user.
1167     auto devices = getMissingDevices();
1168     if (!devices.empty())
1169     {
1170         if (devices.count() == 1)
1171         {
1172             KSNotification::event(QLatin1String("IndiServerMessage"),
1173                                   i18n("Missing device detected (%1). Please reconfigure the optical trains before proceeding any further.",
1174                                        devices.first()),
1175                                   KSNotification::General, KSNotification::Warn);
1176         }
1177         else
1178         {
1179             KSNotification::event(QLatin1String("IndiServerMessage"),
1180                                   i18n("Missing devices detected (%1). Please reconfigure the optical trains before proceeding any further.",
1181                                        devices.join(", ")),
1182                                   KSNotification::General, KSNotification::Warn);
1183         }
1184         show();
1185         raise();
1186         emit configurationRequested(true);
1187     }
1188 }
1189 
1190 ////////////////////////////////////////////////////////////////////////////
1191 ///
1192 ////////////////////////////////////////////////////////////////////////////
1193 QStringList OpticalTrainManager::getMissingDevices() const
1194 {
1195     auto missing = QStringList();
1196     for (auto &oneTrain : m_OpticalTrains)
1197     {
1198         auto mount = oneTrain["mount"].toString();
1199         if (mount != "--" && m_MountNames.contains(mount) == false)
1200             missing << mount;
1201 
1202         auto camera = oneTrain["camera"].toString();
1203         if (camera != "--" && m_CameraNames.contains(camera) == false)
1204             missing << camera;
1205 
1206         auto dustcap = oneTrain["dustcap"].toString();
1207         if (dustcap != "--" && m_DustCapNames.contains(dustcap) == false)
1208             missing << dustcap;
1209 
1210         auto lightbox = oneTrain["lightbox"].toString();
1211         if (lightbox != "--" && m_LightBoxNames.contains(lightbox) == false)
1212             missing << lightbox;
1213 
1214         auto focuser = oneTrain["focuser"].toString();
1215         if (focuser != "--" && m_FocuserNames.contains(focuser) == false)
1216             missing << focuser;
1217 
1218         auto filterwheel = oneTrain["filterwheel"].toString();
1219         if (filterwheel != "--" && m_FilterWheelNames.contains(filterwheel) == false)
1220             missing << filterwheel;
1221 
1222         auto guider = oneTrain["guider"].toString();
1223         if (guider != "--" && m_GuiderNames.contains(guider) == false)
1224             missing << guider;
1225 
1226     }
1227 
1228     return missing;
1229 }
1230 
1231 ////////////////////////////////////////////////////////////////////////////
1232 ///
1233 ////////////////////////////////////////////////////////////////////////////
1234 void Ekos::OpticalTrainManager::updateOpticalTrainValue(QComboBox *cb, const QString &element)
1235 {
1236     if (trainNamesList->currentItem() != nullptr && m_Persistent == true)
1237         setOpticalTrainValue(trainNamesList->currentItem()->text(), element, cb->currentText());
1238 }
1239 
1240 ////////////////////////////////////////////////////////////////////////////
1241 ///
1242 ////////////////////////////////////////////////////////////////////////////
1243 void OpticalTrainManager::updateOpticalTrainValue(double value, const QString &element)
1244 {
1245     if (trainNamesList->currentItem() != nullptr && m_Persistent == true)
1246         setOpticalTrainValue(trainNamesList->currentItem()->text(), element, value);
1247 
1248 }
1249 
1250 ////////////////////////////////////////////////////////////////////////////
1251 /// Reset optical train to default values.
1252 ////////////////////////////////////////////////////////////////////////////
1253 void OpticalTrainManager::reset()
1254 {
1255     if (m_CurrentOpticalTrain != nullptr)
1256     {
1257         auto id = m_CurrentOpticalTrain->value("id");
1258         auto name = m_CurrentOpticalTrain->value("name");
1259         int row = trainNamesList->currentRow();
1260         m_CurrentOpticalTrain->clear();
1261 
1262         m_CurrentOpticalTrain->insert("id", id);
1263         m_CurrentOpticalTrain->insert("name", name);
1264         m_CurrentOpticalTrain->insert("mount", "--");
1265         m_CurrentOpticalTrain->insert("camera", "--");
1266         m_CurrentOpticalTrain->insert("rotator", "--");
1267         m_CurrentOpticalTrain->insert("guider", "--");
1268         m_CurrentOpticalTrain->insert("dustcap", "--");
1269         m_CurrentOpticalTrain->insert("scope", "--");
1270         m_CurrentOpticalTrain->insert("filterwheel", "--");
1271         m_CurrentOpticalTrain->insert("focuser", "--");
1272         m_CurrentOpticalTrain->insert("reducer", 1);
1273         m_CurrentOpticalTrain->insert("lightbox", "--");
1274 
1275         KStarsData::Instance()->userdb()->UpdateOpticalTrain(*m_CurrentOpticalTrain, id.toInt());
1276         refreshTrains();
1277         selectOpticalTrain(name.toString());
1278         trainNamesList->setCurrentRow(row);
1279     }
1280 }
1281 
1282 }