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

0001 /*
0002     SPDX-FileCopyrightText: 2017 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "buildfilteroffsets.h"
0008 #include <kstars_debug.h>
0009 
0010 #include "indi_debug.h"
0011 #include "kstarsdata.h"
0012 #include "kstars.h"
0013 #include "ekos/focus/focus.h"
0014 #include "ekos/manager.h"
0015 #include "Options.h"
0016 #include "auxiliary/kspaths.h"
0017 #include "auxiliary/ksmessagebox.h"
0018 #include "ekos/auxiliary/tabledelegate.h"
0019 
0020 #include <QTimer>
0021 #include <QSqlTableModel>
0022 #include <QSqlDatabase>
0023 #include <QSqlRecord>
0024 
0025 #include <basedevice.h>
0026 
0027 #include <algorithm>
0028 
0029 namespace Ekos
0030 {
0031 
0032 FilterManager::FilterManager(QWidget *parent) : QDialog(parent)
0033 {
0034 #ifdef Q_OS_OSX
0035     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0036 #endif
0037 
0038     setupUi(this);
0039 
0040     connect(buttonBox, SIGNAL(accepted()), this, SLOT(close()));
0041     connect(buttonBox, SIGNAL(rejected()), this, SLOT(close()));
0042 
0043     connect(buildOffsetsButton, &QPushButton::clicked, this, &FilterManager::buildFilterOffsets);
0044 
0045     kcfg_FlatSyncFocus->setChecked(Options::flatSyncFocus());
0046     connect(kcfg_FlatSyncFocus, &QCheckBox::toggled, this, [this]()
0047     {
0048         Options::setFlatSyncFocus(kcfg_FlatSyncFocus->isChecked());
0049     });
0050 
0051     createFilterModel();
0052 
0053     // No Edit delegate
0054     noEditDelegate = new NotEditableDelegate(m_FilterView);
0055     m_FilterView->setItemDelegateForColumn(FM_LABEL, noEditDelegate);
0056 
0057     // Exposure delegate
0058     exposureDelegate = new DoubleDelegate(m_FilterView, 0.001, 3600, 1);
0059     m_FilterView->setItemDelegateForColumn(FM_EXPOSURE, exposureDelegate);
0060 
0061     // Offset delegate
0062     offsetDelegate = new IntegerDelegate(m_FilterView, -10000, 10000, 1);
0063     m_FilterView->setItemDelegateForColumn(FM_OFFSET, offsetDelegate);
0064 
0065     // Auto Focus delegate
0066     useAutoFocusDelegate = new ToggleDelegate(m_FilterView);
0067     m_FilterView->setItemDelegateForColumn(FM_AUTO_FOCUS, useAutoFocusDelegate);
0068 
0069     // Set Delegates
0070     lockDelegate = new ComboDelegate(m_FilterView);
0071     m_FilterView->setItemDelegateForColumn(FM_LOCK_FILTER, lockDelegate);
0072     lockDelegate->setValues(getLockDelegates());
0073 
0074     // Last AF solution delegate. Set by Autofocus but make editable in case bad data
0075     // corrections need to be made
0076     lastAFSolutionDelegate = new IntegerDelegate(m_FilterView, 0, 1000000, 1);
0077     m_FilterView->setItemDelegateForColumn(FM_LAST_AF_SOLUTION, lastAFSolutionDelegate);
0078 
0079     // Last AF solution temperature delegate
0080     lastAFTempDelegate = new DoubleDelegate(m_FilterView, -60.0, 60.0, 1.0);
0081     m_FilterView->setItemDelegateForColumn(FM_LAST_AF_TEMP, lastAFTempDelegate);
0082 
0083     // Last AF solution altitude delegate
0084     lastAFAltDelegate = new DoubleDelegate(m_FilterView, 0.0, 90.0, 1.0);
0085     m_FilterView->setItemDelegateForColumn(FM_LAST_AF_ALT, lastAFAltDelegate);
0086 
0087     // Ticks / °C delegate
0088     ticksPerTempDelegate = new DoubleDelegate(m_FilterView, -10000.0, 10000.0, 1.0);
0089     m_FilterView->setItemDelegateForColumn(FM_TICKS_PER_TEMP, ticksPerTempDelegate);
0090 
0091     // Ticks / °Altitude delegate
0092     ticksPerAltDelegate = new DoubleDelegate(m_FilterView, -10000.0, 10000.0, 1.0);
0093     m_FilterView->setItemDelegateForColumn(FM_TICKS_PER_ALT, ticksPerAltDelegate);
0094 
0095     // Wavelength delegate
0096     wavelengthDelegate = new IntegerDelegate(m_FilterView, 200, 1000, 50);
0097     m_FilterView->setItemDelegateForColumn(FM_WAVELENGTH, wavelengthDelegate);
0098 }
0099 
0100 void FilterManager::createFilterModel()
0101 {
0102     QSqlDatabase userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName());
0103 
0104     m_FilterModel = new QSqlTableModel(this, userdb);
0105     m_FilterView->setModel(m_FilterModel);
0106     m_FilterModel->setTable("filter");
0107     m_FilterModel->setEditStrategy(QSqlTableModel::OnFieldChange);
0108 
0109     m_FilterModel->setHeaderData(FM_LABEL, Qt::Horizontal, i18n("Filter"));
0110 
0111     m_FilterModel->setHeaderData(FM_EXPOSURE, Qt::Horizontal, i18n("Filter exposure time during focus"), Qt::ToolTipRole);
0112     m_FilterModel->setHeaderData(FM_EXPOSURE, Qt::Horizontal, i18n("Exposure"));
0113 
0114     m_FilterModel->setHeaderData(FM_OFFSET, Qt::Horizontal, i18n("Relative offset in steps"), Qt::ToolTipRole);
0115     m_FilterModel->setHeaderData(FM_OFFSET, Qt::Horizontal, i18n("Offset"));
0116 
0117     m_FilterModel->setHeaderData(FM_AUTO_FOCUS, Qt::Horizontal, i18n("Start Auto Focus when filter is activated"),
0118                                  Qt::ToolTipRole);
0119     m_FilterModel->setHeaderData(FM_AUTO_FOCUS, Qt::Horizontal, i18n("Auto Focus"));
0120 
0121     m_FilterModel->setHeaderData(FM_LOCK_FILTER, Qt::Horizontal, i18n("Lock specific filter when running Auto Focus"),
0122                                  Qt::ToolTipRole);
0123     m_FilterModel->setHeaderData(FM_LOCK_FILTER, Qt::Horizontal, i18n("Lock Filter"));
0124 
0125     m_FilterModel->setHeaderData(FM_LAST_AF_SOLUTION, Qt::Horizontal,
0126                                  i18n("Last AF solution. Updated automatically by the autofocus process."), Qt::ToolTipRole);
0127     m_FilterModel->setHeaderData(FM_LAST_AF_SOLUTION, Qt::Horizontal, i18n("Last AF Solution"));
0128 
0129     m_FilterModel->setHeaderData(FM_LAST_AF_TEMP, Qt::Horizontal,
0130                                  i18n("The temperature of the last AF solution. Updated automatically by the autofocus process."), Qt::ToolTipRole);
0131     m_FilterModel->setHeaderData(FM_LAST_AF_TEMP, Qt::Horizontal, i18n("Last AF Temp (°C)"));
0132 
0133     m_FilterModel->setHeaderData(FM_LAST_AF_ALT, Qt::Horizontal,
0134                                  i18n("The altitude of the last AF solution. Updated automatically by the autofocus process."), Qt::ToolTipRole);
0135     m_FilterModel->setHeaderData(FM_LAST_AF_ALT, Qt::Horizontal, i18n("Last AF Alt (°Alt)"));
0136 
0137     m_FilterModel->setHeaderData(FM_TICKS_PER_TEMP, Qt::Horizontal,
0138                                  i18n("The number of ticks per °C increase in temperature. +ve for outward focuser movement"), Qt::ToolTipRole);
0139     m_FilterModel->setHeaderData(FM_TICKS_PER_TEMP, Qt::Horizontal, i18n("Ticks / °C"));
0140 
0141     m_FilterModel->setHeaderData(FM_TICKS_PER_ALT, Qt::Horizontal,
0142                                  i18n("The number of ticks per degree increase in altitude. +ve for outward focuser movement"), Qt::ToolTipRole);
0143     m_FilterModel->setHeaderData(FM_TICKS_PER_ALT, Qt::Horizontal, i18n("Ticks / °Alt"));
0144 
0145     m_FilterModel->setHeaderData(FM_WAVELENGTH, Qt::Horizontal, i18n("Mid-point wavelength of filter in nm"), Qt::ToolTipRole);
0146     m_FilterModel->setHeaderData(FM_WAVELENGTH, Qt::Horizontal, i18n("Wavelength"));
0147 
0148     connect(m_FilterModel, &QSqlTableModel::dataChanged, this, &FilterManager::updated);
0149 
0150     connect(m_FilterModel, &QSqlTableModel::dataChanged, this, [this](const QModelIndex & topLeft, const QModelIndex &,
0151             const QVector<int> &)
0152     {
0153         reloadFilters();
0154         if (topLeft.column() == FM_EXPOSURE)
0155             emit exposureChanged(m_FilterModel->data(topLeft).toDouble());
0156         else if (topLeft.column() == FM_LOCK_FILTER)
0157         {
0158             // Don't allow the lock filter to be set to the current filter - circular dependancy
0159             // Don't allow the lock to be set if this filter is a lock for another filter - nested dependancy
0160             QString lock = m_FilterModel->data(m_FilterModel->index(topLeft.row(), topLeft.column())).toString();
0161             QString filter = m_FilterModel->data(m_FilterModel->index(topLeft.row(), FM_LABEL)).toString();
0162             bool alreadyALock = false;
0163             for (int i = 0; i < m_ActiveFilters.count(); i++)
0164             {
0165                 if (m_ActiveFilters[i]->lockedFilter() == filter)
0166                 {
0167                     alreadyALock = true;
0168                     break;
0169                 }
0170             }
0171             if (alreadyALock || (lock == filter))
0172             {
0173                 m_FilterModel->setData(m_FilterModel->index(topLeft.row(), topLeft.column()), NULL_FILTER);
0174             }
0175             // Update the acceptable values in the lockDelegate
0176             lockDelegate->setValues(getLockDelegates());
0177         }
0178         else if (topLeft.column() == FM_TICKS_PER_TEMP)
0179             emit ticksPerTempChanged();
0180         else if (topLeft.column() == FM_TICKS_PER_ALT)
0181             emit ticksPerAltChanged();
0182         else if (topLeft.column() == FM_WAVELENGTH)
0183             emit wavelengthChanged();
0184     });
0185 }
0186 
0187 void FilterManager::refreshFilterModel()
0188 {
0189     if (m_FilterWheel == nullptr || m_currentFilterLabels.empty())
0190         return;
0191 
0192     // In case filter model was cleared due to a device disconnect
0193     if (m_FilterModel == nullptr)
0194         createFilterModel();
0195 
0196     QString vendor(m_FilterWheel->getDeviceName());
0197     m_FilterModel->setFilter(QString("vendor='%1'").arg(vendor));
0198     m_FilterModel->select();
0199 
0200     m_FilterView->hideColumn(0);
0201     m_FilterView->hideColumn(1);
0202     m_FilterView->hideColumn(2);
0203     m_FilterView->hideColumn(3);
0204 
0205     // If we have an existing table but it doesn't match the number of current filters
0206     // then we remove it.
0207     if (m_FilterModel->rowCount() > 0 && m_FilterModel->rowCount() != m_currentFilterLabels.count())
0208     {
0209         for (int i = 0; i < m_FilterModel->rowCount(); i++)
0210             m_FilterModel->removeRow(i);
0211 
0212         m_FilterModel->select();
0213     }
0214 
0215     // If it is first time, let's populate data
0216     if (m_FilterModel->rowCount() == 0)
0217     {
0218         filterProperties *fp = new filterProperties(vendor, "", "", "");
0219         for (QString &filter : m_currentFilterLabels)
0220         {
0221             fp->color = filter;
0222             KStarsData::Instance()->userdb()->AddFilter(fp);
0223         }
0224 
0225         m_FilterModel->select();
0226     }
0227     // Make sure all the filter colors match DB. If not update model to sync with INDI filter values
0228     else
0229     {
0230         for (int i = 0; i < m_FilterModel->rowCount(); ++i)
0231         {
0232             QModelIndex index = m_FilterModel->index(i, FM_LABEL);
0233             if (m_FilterModel->data(index).toString() != m_currentFilterLabels[i])
0234             {
0235                 m_FilterModel->setData(index, m_currentFilterLabels[i]);
0236             }
0237         }
0238     }
0239 
0240     lockDelegate->setValues(getLockDelegates());
0241 
0242     reloadFilters();
0243     resizeDialog();
0244 }
0245 
0246 void FilterManager::resizeDialog()
0247 {
0248     // Resize the columns to the data and then the dialog to the rows and columns
0249     m_FilterView->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
0250     int width = m_FilterView->horizontalHeader()->length() + 50;
0251     int height = label->height() + m_FilterView->verticalHeader()->length() + label_2->height() + buttonBox->height() + 100;
0252     this->resize(width, height);
0253 }
0254 // This function processes the list of active filters and returns a list of filters that could be lock filters
0255 // i.e. that don't themselves have locks.
0256 QStringList FilterManager::getLockDelegates()
0257 {
0258     QStringList lockDelegates;
0259 
0260     for (int i = 0; i < m_ActiveFilters.count(); i++)
0261     {
0262         if (m_ActiveFilters[i]->lockedFilter() == NULL_FILTER)
0263             lockDelegates.append(m_ActiveFilters[i]->color());
0264     }
0265     return lockDelegates;
0266 }
0267 
0268 void FilterManager::reloadFilters()
0269 {
0270     qDeleteAll(m_ActiveFilters);
0271     currentFilter = nullptr;
0272     targetFilter = nullptr;
0273     m_ActiveFilters.clear();
0274     operationQueue.clear();
0275 
0276     filterProperties *fp = new filterProperties("", "", "", "");
0277 
0278     for (int i = 0; i < m_FilterModel->rowCount(); ++i)
0279     {
0280         QSqlRecord record     = m_FilterModel->record(i);
0281         QString id            = record.value("id").toString();
0282 
0283         fp->vendor            = record.value("Vendor").toString();
0284         fp->model             = record.value("Model").toString();
0285         fp->type              = record.value("Type").toString();
0286         fp->color             = record.value("Color").toString();
0287         fp->exposure          = record.value("Exposure").toDouble();
0288         fp->offset            = record.value("Offset").toInt();
0289         fp->lockedFilter      = record.value("LockedFilter").toString();
0290         fp->useAutoFocus      = record.value("UseAutoFocus").toInt() == 1;
0291         fp->absFocusPos       = record.value("AbsoluteFocusPosition").toInt();
0292         fp->focusTemperature  = record.value("FocusTemperature").toDouble();
0293         fp->focusAltitude     = record.value("FocusAltitude").toDouble();
0294         fp->focusTicksPerTemp = record.value("FocusTicksPerTemp").toDouble();
0295         fp->focusTicksPerAlt  = record.value("FocusTicksPerAlt").toDouble();
0296         fp->wavelength        = record.value("Wavelength").toInt();
0297         OAL::Filter *o        = new OAL::Filter(id, fp);
0298         m_ActiveFilters.append(o);
0299     }
0300 }
0301 
0302 void FilterManager::setFilterWheel(ISD::FilterWheel *filter)
0303 {
0304     // Return if same device and we already initialized the properties.
0305     if (m_FilterWheel == filter && m_FilterNameProperty && m_FilterPositionProperty)
0306         return;
0307     else if (m_FilterWheel)
0308         m_FilterWheel->disconnect(this);
0309 
0310     m_FilterWheel = filter;
0311 
0312     m_FilterNameProperty = nullptr;
0313     m_FilterPositionProperty = nullptr;
0314     m_FilterConfirmSet = nullptr;
0315 
0316     if (!m_FilterWheel)
0317         return;
0318 
0319     connect(m_FilterWheel, &ISD::ConcreteDevice::propertyUpdated, this, &FilterManager::updateProperty);
0320     connect(m_FilterWheel, &ISD::ConcreteDevice::Disconnected, this, &FilterManager::processDisconnect);
0321 
0322     refreshFilterProperties();
0323 }
0324 
0325 void FilterManager::refreshFilterProperties()
0326 {
0327     if (m_FilterNameProperty && m_FilterPositionProperty)
0328     {
0329         if (m_FilterConfirmSet == nullptr)
0330             m_FilterConfirmSet = m_FilterWheel->getSwitch("CONFIRM_FILTER_SET");
0331 
0332         // All filters are synced up?
0333         if (m_currentFilterLabels.count() == m_FilterNameProperty->ntp)
0334             return;
0335     }
0336 
0337     filterNameLabel->setText(m_FilterWheel->getDeviceName());
0338 
0339     m_currentFilterLabels.clear();
0340 
0341     m_FilterNameProperty = m_FilterWheel->getText("FILTER_NAME");
0342     m_FilterPositionProperty = m_FilterWheel->getNumber("FILTER_SLOT");
0343     m_FilterConfirmSet = m_FilterWheel->getSwitch("CONFIRM_FILTER_SET");
0344 
0345     refreshFilterLabels();
0346     refreshFilterPosition();
0347 
0348     if (m_currentFilterPosition >= 1 && m_currentFilterPosition <= m_ActiveFilters.count())
0349         lastFilterOffset = m_ActiveFilters[m_currentFilterPosition - 1]->offset();
0350 }
0351 
0352 QStringList FilterManager::getFilterLabels(bool forceRefresh)
0353 {
0354     if ((!m_currentFilterLabels.empty() && forceRefresh == false) || !m_FilterNameProperty || !m_FilterPositionProperty)
0355         return m_currentFilterLabels;
0356 
0357     QStringList filterList;
0358 
0359     for (int i = 0; i < m_FilterPositionProperty->np[0].max; i++)
0360     {
0361         if (m_FilterNameProperty != nullptr && (i < m_FilterNameProperty->ntp))
0362             filterList.append(m_FilterNameProperty->tp[i].text);
0363     }
0364 
0365     return filterList;
0366 }
0367 
0368 int FilterManager::getFilterPosition(bool forceRefresh)
0369 {
0370     if (forceRefresh == false || m_FilterPositionProperty == nullptr)
0371         return m_currentFilterPosition;
0372 
0373     return static_cast<int>(m_FilterPositionProperty->np[0].value);
0374 }
0375 
0376 void FilterManager::refreshFilterLabels()
0377 {
0378     QList filters = getFilterLabels(true);
0379 
0380     if (filters != m_currentFilterLabels)
0381     {
0382         m_currentFilterLabels = filters;
0383         refreshFilterModel();
0384 
0385         emit labelsChanged(filters);
0386 
0387         // refresh position after filter changes
0388         refreshFilterPosition();
0389     }
0390 }
0391 
0392 void FilterManager::refreshFilterPosition()
0393 {
0394 
0395     int pos = getFilterPosition(true);
0396     if (pos != m_currentFilterPosition)
0397     {
0398         m_currentFilterPosition = pos;
0399         emit positionChanged(pos);
0400     }
0401 }
0402 
0403 bool FilterManager::setFilterPosition(uint8_t position, FilterPolicy policy)
0404 {
0405     // Position 1 to Max
0406     if (position > m_ActiveFilters.count())
0407         return false;
0408 
0409     m_Policy = policy;
0410     currentFilter = m_ActiveFilters[m_currentFilterPosition - 1];
0411     targetFilter = m_ActiveFilters[position - 1];
0412 
0413     if (currentFilter == targetFilter)
0414     {
0415         emit ready();
0416         return true;
0417     }
0418 
0419     buildOperationQueue(FILTER_CHANGE);
0420 
0421     executeOperationQueue();
0422 
0423     return true;
0424 }
0425 
0426 
0427 void FilterManager::updateProperty(INDI::Property prop)
0428 {
0429     if (m_FilterWheel == nullptr || m_FilterWheel->getDeviceName() != prop.getDeviceName())
0430         return;
0431 
0432     if (prop.isNameMatch("FILTER_NAME"))
0433     {
0434         auto tvp = prop.getText();
0435         m_FilterNameProperty = tvp;
0436 
0437         refreshFilterLabels();
0438     }
0439     else if (prop.isNameMatch("FILTER_SLOT"))
0440     {
0441         auto nvp = prop.getNumber();
0442         if (nvp->s != IPS_OK)
0443             return;
0444 
0445         m_FilterPositionProperty = nvp;
0446         refreshFilterPosition();
0447 
0448         if (state == FILTER_CHANGE)
0449             executeOperationQueue();
0450         // If filter is changed externally, record its current offset as the starting offset.
0451         else if (state == FILTER_IDLE && m_ActiveFilters.count() >= m_currentFilterPosition)
0452             lastFilterOffset = m_ActiveFilters[m_currentFilterPosition - 1]->offset();
0453     }
0454 }
0455 
0456 
0457 void FilterManager::processDisconnect()
0458 {
0459     m_currentFilterLabels.clear();
0460     m_currentFilterPosition = -1;
0461     m_FilterNameProperty = nullptr;
0462     m_FilterPositionProperty = nullptr;
0463 }
0464 
0465 void FilterManager::buildOperationQueue(FilterState operation)
0466 {
0467     operationQueue.clear();
0468     m_useTargetFilter = false;
0469 
0470     switch (operation)
0471     {
0472         case FILTER_CHANGE:
0473         {
0474             if ( (m_Policy & CHANGE_POLICY) && targetFilter != currentFilter)
0475                 m_useTargetFilter = true;
0476 
0477             if (m_useTargetFilter)
0478             {
0479                 operationQueue.enqueue(FILTER_CHANGE);
0480                 if (m_FocusReady && (m_Policy & OFFSET_POLICY))
0481                     operationQueue.enqueue(FILTER_OFFSET);
0482                 else
0483                 {
0484                     // Keep track of filter and offset either here or after the offset has been processed
0485                     lastFilterOffset = targetFilter->offset();
0486                     currentFilter = targetFilter;
0487                 }
0488 
0489             }
0490 
0491             if (m_FocusReady && (m_Policy & AUTOFOCUS_POLICY) && targetFilter->useAutoFocus())
0492                 operationQueue.enqueue(FILTER_AUTOFOCUS);
0493         }
0494         break;
0495 
0496         default:
0497             break;
0498     }
0499 }
0500 
0501 bool FilterManager::executeOperationQueue()
0502 {
0503     if (operationQueue.isEmpty())
0504     {
0505         state = FILTER_IDLE;
0506         emit newStatus(state);
0507         emit ready();
0508         return false;
0509     }
0510 
0511     FilterState nextOperation = operationQueue.dequeue();
0512 
0513     bool actionRequired = true;
0514 
0515     switch (nextOperation)
0516     {
0517         case FILTER_CHANGE:
0518         {
0519             if (m_ConfirmationPending)
0520                 return true;
0521 
0522             state = FILTER_CHANGE;
0523             if (m_useTargetFilter)
0524                 targetFilterPosition = m_ActiveFilters.indexOf(targetFilter) + 1;
0525             m_FilterWheel->setPosition(targetFilterPosition);
0526 
0527             emit newStatus(state);
0528 
0529             if (m_FilterConfirmSet)
0530             {
0531                 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this]()
0532                 {
0533                     KSMessageBox::Instance()->disconnect(this);
0534                     m_ConfirmationPending = false;
0535                     m_FilterWheel->confirmFilter();
0536                 });
0537                 connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [this]()
0538                 {
0539                     KSMessageBox::Instance()->disconnect(this);
0540                     m_ConfirmationPending = false;
0541                 });
0542 
0543                 m_ConfirmationPending = true;
0544 
0545                 KSMessageBox::Instance()->questionYesNo(i18n("Set filter to %1. Is filter set?", targetFilter->color()),
0546                                                         i18n("Confirm Filter"));
0547             }
0548         }
0549         break;
0550 
0551         case FILTER_OFFSET:
0552         {
0553             state = FILTER_OFFSET;
0554             if (m_useTargetFilter)
0555             {
0556                 targetFilterOffset = targetFilter->offset() - lastFilterOffset;
0557                 lastFilterOffset   = targetFilter->offset();
0558                 currentFilter = targetFilter;
0559                 m_useTargetFilter = false;
0560             }
0561             if (targetFilterOffset == 0)
0562                 actionRequired = false;
0563             else
0564             {
0565                 emit newFocusOffset(targetFilterOffset, false);
0566                 emit newStatus(state);
0567             }
0568         }
0569         break;
0570 
0571         case FILTER_AUTOFOCUS:
0572             state = FILTER_AUTOFOCUS;
0573             qCDebug(KSTARS) << "FilterManager.cpp is triggering autofocus.";
0574             emit newStatus(state);
0575             emit runAutoFocus(false);
0576             break;
0577 
0578         default:
0579             break;
0580     }
0581 
0582     // If an additional action is required, return return and continue later
0583     if (actionRequired)
0584         return true;
0585     // Otherwise, continue processing the queue
0586     else
0587         return executeOperationQueue();
0588 }
0589 
0590 bool FilterManager::executeOneOperation(FilterState operation)
0591 {
0592     bool actionRequired = false;
0593 
0594     switch (operation)
0595     {
0596         default:
0597             break;
0598     }
0599 
0600     return actionRequired;
0601 }
0602 
0603 void FilterManager::setFocusOffsetComplete()
0604 {
0605     if (state == FILTER_OFFSET)
0606         executeOperationQueue();
0607 }
0608 
0609 double FilterManager::getFilterExposure(const QString &name) const
0610 {
0611     auto filterDetails = getFilterByName(name);
0612     if (filterDetails)
0613         return filterDetails->exposure();
0614 
0615     // Default value
0616     return 1;
0617 }
0618 
0619 bool FilterManager::setFilterExposure(int index, double exposure)
0620 {
0621     if (m_currentFilterLabels.empty())
0622         return false;
0623 
0624     QString color = m_currentFilterLabels[index];
0625     for (int i = 0; i < m_ActiveFilters.count(); i++)
0626     {
0627         if (color == m_ActiveFilters[i]->color())
0628         {
0629             m_FilterModel->setData(m_FilterModel->index(i, FM_EXPOSURE), exposure);
0630             m_FilterModel->submitAll();
0631             refreshFilterModel();
0632             return true;
0633         }
0634     }
0635 
0636     return false;
0637 }
0638 
0639 int FilterManager::getFilterOffset(const QString &name) const
0640 {
0641     int offset = INVALID_VALUE;
0642     auto filterDetails = getFilterByName(name);
0643     if (filterDetails)
0644         offset = filterDetails->offset();
0645 
0646     return offset;
0647 }
0648 
0649 bool FilterManager::setFilterOffset(QString color, int offset)
0650 {
0651     if (m_currentFilterLabels.empty())
0652         return false;
0653 
0654     for (int i = 0; i < m_ActiveFilters.count(); i++)
0655     {
0656         if (color == m_ActiveFilters[i]->color())
0657         {
0658             m_FilterModel->setData(m_FilterModel->index(i, FM_OFFSET), offset);
0659             m_FilterModel->submitAll();
0660             refreshFilterModel();
0661             return true;
0662         }
0663     }
0664 
0665     return false;
0666 }
0667 
0668 bool FilterManager::getFilterAbsoluteFocusDetails(const QString &name, int &focusPos, double &focusTemp,
0669         double &focusAlt) const
0670 {
0671     auto filterDetails = getFilterByName(name);
0672     if (filterDetails)
0673     {
0674         focusPos = filterDetails->absoluteFocusPosition();
0675         focusTemp = filterDetails->focusTemperature();
0676         focusAlt = filterDetails->focusAltitude();
0677         return true;
0678     }
0679 
0680     return false;
0681 }
0682 
0683 bool FilterManager::setFilterAbsoluteFocusDetails(int index, int focusPos, double focusTemp, double focusAlt)
0684 {
0685     if (index < 0 || index >= m_currentFilterLabels.count())
0686         return false;
0687 
0688     QString color = m_currentFilterLabels[index];
0689     for (int i = 0; i < m_ActiveFilters.count(); i++)
0690     {
0691         if (color == m_ActiveFilters[i]->color())
0692         {
0693             m_FilterModel->setData(m_FilterModel->index(i, FM_LAST_AF_SOLUTION), focusPos);
0694             m_FilterModel->setData(m_FilterModel->index(i, FM_LAST_AF_TEMP), focusTemp);
0695             m_FilterModel->setData(m_FilterModel->index(i, FM_LAST_AF_ALT), focusAlt);
0696             m_FilterModel->submitAll();
0697             refreshFilterModel();
0698             return true;
0699         }
0700     }
0701 
0702     return false;
0703 }
0704 
0705 QString FilterManager::getFilterLock(const QString &name) const
0706 {
0707     auto filterDetails = getFilterByName(name);
0708     if (filterDetails)
0709         return filterDetails->lockedFilter();
0710 
0711     // Default value
0712     return NULL_FILTER;
0713 }
0714 
0715 bool FilterManager::setFilterLock(int index, QString name)
0716 {
0717     if (m_currentFilterLabels.empty())
0718         return false;
0719 
0720     QString color = m_currentFilterLabels[index];
0721     for (int i = 0; i < m_ActiveFilters.count(); i++)
0722     {
0723         if (color == m_ActiveFilters[i]->color())
0724         {
0725             m_FilterModel->setData(m_FilterModel->index(i, FM_LOCK_FILTER), name);
0726             m_FilterModel->submitAll();
0727             refreshFilterModel();
0728             return true;
0729         }
0730     }
0731 
0732     return false;
0733 }
0734 
0735 int FilterManager::getFilterWavelength(const QString &name) const
0736 {
0737     auto filterDetails = getFilterByName(name);
0738     if (filterDetails)
0739         return filterDetails->wavelength();
0740 
0741     // Default value
0742     return 500;
0743 }
0744 
0745 double FilterManager::getFilterTicksPerTemp(const QString &name) const
0746 {
0747     auto filterDetails = getFilterByName(name);
0748     if (filterDetails)
0749         return filterDetails->focusTicksPerTemp();
0750 
0751     // Something's wrong so return 0
0752     return 0.0;
0753 }
0754 
0755 double FilterManager::getFilterTicksPerAlt(const QString &name) const
0756 {
0757     auto filterDetails = getFilterByName(name);
0758     if (filterDetails)
0759         return filterDetails->focusTicksPerAlt();
0760 
0761     // Something's wrong so return 0
0762     return 0.0;
0763 }
0764 
0765 OAL::Filter * FilterManager::getFilterByName(const QString &name) const
0766 {
0767     if (m_currentFilterLabels.empty() ||
0768             m_currentFilterPosition < 1 ||
0769             m_currentFilterPosition > m_currentFilterLabels.count())
0770         return nullptr;
0771 
0772     QString color = name;
0773     if (color.isEmpty())
0774         color = m_currentFilterLabels[m_currentFilterPosition - 1];
0775 
0776     auto pos = std::find_if(m_ActiveFilters.begin(), m_ActiveFilters.end(), [color](OAL::Filter * oneFilter)
0777     {
0778         return (oneFilter->color() == color);
0779     });
0780 
0781     if (pos != m_ActiveFilters.end())
0782         return (*pos);
0783     else
0784         return nullptr;
0785 }
0786 
0787 void FilterManager::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
0788 {
0789     if (m_FilterWheel && (m_FilterWheel->getDeviceName() == device->getDeviceName()))
0790     {
0791         m_FilterNameProperty = nullptr;
0792         m_FilterPositionProperty = nullptr;
0793         m_FilterWheel = nullptr;
0794         m_currentFilterLabels.clear();
0795         m_currentFilterPosition = 0;
0796         qDeleteAll(m_ActiveFilters);
0797         m_ActiveFilters.clear();
0798         delete(m_FilterModel);
0799         m_FilterModel = nullptr;
0800     }
0801 }
0802 
0803 void FilterManager::setFocusStatus(Ekos::FocusState focusState)
0804 {
0805     if (state == FILTER_AUTOFOCUS)
0806     {
0807         switch (focusState)
0808         {
0809             case FOCUS_COMPLETE:
0810                 executeOperationQueue();
0811                 break;
0812 
0813             case FOCUS_FAILED:
0814                 if (++retries == 3)
0815                 {
0816                     retries = 0;
0817                     emit failed();
0818                     return;
0819                 }
0820                 // Restart again
0821                 emit runAutoFocus(false);
0822                 break;
0823 
0824             default:
0825                 break;
0826 
0827         }
0828     }
0829 }
0830 
0831 bool FilterManager::syncAbsoluteFocusPosition(int index)
0832 {
0833     if (index < 0 || index > m_ActiveFilters.count())
0834     {
0835         // We've been asked to set the focus position but something's wrong because
0836         // the passed in filter index is bad. Give up and return true - returning false
0837         // just results in an infinite retry loop.
0838         qCWarning(KSTARS_INDI) << __FUNCTION__ << "index" << index << "is out of bounds.";
0839         return true;
0840     }
0841 
0842     // By default filter absolute focus offset is zero
0843     // JM 2023.07.03: So if it is zero, we return immediately.
0844     auto absFocusPos = m_ActiveFilters[index]->absoluteFocusPosition();
0845 
0846     if (m_FocusAbsPosition == absFocusPos || absFocusPos <= 0)
0847     {
0848         m_FocusAbsPositionPending = false;
0849         return true;
0850     }
0851     else if (m_FocusAbsPositionPending == false)
0852     {
0853         m_FocusAbsPositionPending = true;
0854         emit newFocusOffset(absFocusPos, true);
0855     }
0856 
0857     return false;
0858 }
0859 
0860 bool FilterManager::setFilterNames(const QStringList &newLabels)
0861 {
0862     if (m_FilterWheel == nullptr || m_currentFilterLabels.empty())
0863         return false;
0864 
0865     m_FilterWheel->setLabels(newLabels);
0866     return true;
0867 }
0868 
0869 QJsonObject FilterManager::toJSON()
0870 {
0871     if (!m_FilterWheel)
0872         return QJsonObject();
0873 
0874     QJsonArray filters;
0875 
0876     for (int i = 0; i < m_FilterModel->rowCount(); ++i)
0877     {
0878         QJsonObject oneFilter =
0879         {
0880             {"index", i},
0881             {"label", m_FilterModel->data(m_FilterModel->index(i, FM_LABEL)).toString()},
0882             {"exposure", m_FilterModel->data(m_FilterModel->index(i, FM_EXPOSURE)).toDouble()},
0883             {"offset", m_FilterModel->data(m_FilterModel->index(i, FM_OFFSET)).toInt()},
0884             {"autofocus", m_FilterModel->data(m_FilterModel->index(i, FM_AUTO_FOCUS)).toBool()},
0885             {"lock", m_FilterModel->data(m_FilterModel->index(i, FM_LOCK_FILTER)).toString()},
0886             {"lastafsolution", m_FilterModel->data(m_FilterModel->index(i, FM_LAST_AF_SOLUTION)).toInt()},
0887             {"lastaftemp", m_FilterModel->data(m_FilterModel->index(i, FM_LAST_AF_TEMP)).toDouble()},
0888             {"lastafalt", m_FilterModel->data(m_FilterModel->index(i, FM_LAST_AF_ALT)).toDouble()},
0889             {"tickspertemp", m_FilterModel->data(m_FilterModel->index(i, FM_TICKS_PER_TEMP)).toDouble()},
0890             {"ticksperalt", m_FilterModel->data(m_FilterModel->index(i, FM_TICKS_PER_ALT)).toDouble()},
0891             {"wavelength", m_FilterModel->data(m_FilterModel->index(i, FM_WAVELENGTH)).toInt()},
0892         };
0893 
0894         filters.append(oneFilter);
0895     }
0896 
0897     QJsonObject data =
0898     {
0899         {"device", m_FilterWheel->getDeviceName()},
0900         {"filters", filters}
0901     };
0902 
0903     return data;
0904 
0905 }
0906 
0907 void FilterManager::setFilterData(const QJsonObject &settings)
0908 {
0909     if (!m_FilterWheel)
0910         return;
0911 
0912     if (settings["device"].toString() != m_FilterWheel->getDeviceName())
0913         return;
0914 
0915     QJsonArray filters = settings["filters"].toArray();
0916     QStringList labels = getFilterLabels();
0917 
0918     for (auto oneFilterRef : filters)
0919     {
0920         QJsonObject oneFilter = oneFilterRef.toObject();
0921         int row = oneFilter["index"].toInt();
0922 
0923         labels[row] = oneFilter["label"].toString();
0924         m_FilterModel->setData(m_FilterModel->index(row, FM_LABEL), oneFilter["label"].toString());
0925         m_FilterModel->setData(m_FilterModel->index(row, FM_EXPOSURE), oneFilter["exposure"].toDouble());
0926         m_FilterModel->setData(m_FilterModel->index(row, FM_OFFSET), oneFilter["offset"].toInt());
0927         m_FilterModel->setData(m_FilterModel->index(row, FM_AUTO_FOCUS), oneFilter["autofocus"].toBool());
0928         m_FilterModel->setData(m_FilterModel->index(row, FM_LOCK_FILTER), oneFilter["lock"].toString());
0929         m_FilterModel->setData(m_FilterModel->index(row, FM_LAST_AF_SOLUTION), oneFilter["lastafsolution"].toInt());
0930         m_FilterModel->setData(m_FilterModel->index(row, FM_LAST_AF_TEMP), oneFilter["lastaftemp"].toDouble());
0931         m_FilterModel->setData(m_FilterModel->index(row, FM_LAST_AF_ALT), oneFilter["lastafalt"].toDouble());
0932         m_FilterModel->setData(m_FilterModel->index(row, FM_TICKS_PER_TEMP), oneFilter["tickspertemp"].toDouble());
0933         m_FilterModel->setData(m_FilterModel->index(row, FM_TICKS_PER_ALT), oneFilter["ticksperalt"].toDouble());
0934         m_FilterModel->setData(m_FilterModel->index(row, FM_WAVELENGTH), oneFilter["wavelength"].toInt());
0935     }
0936 
0937     m_FilterModel->submitAll();
0938     setFilterNames(labels);
0939 
0940     refreshFilterModel();
0941 }
0942 
0943 void FilterManager::buildFilterOffsets()
0944 {
0945     // Launch the Build Filter Offsets utility. The utility uses a sync call to launch the dialog
0946     QSharedPointer<FilterManager> filterManager;
0947     Ekos::Manager::Instance()->getFilterManager(m_FilterWheel->getDeviceName(), filterManager);
0948     BuildFilterOffsets bfo(filterManager);
0949 }
0950 
0951 void FilterManager::signalRunAutoFocus(bool buildFilterOffsets)
0952 {
0953     // BuildFilterOffsets signalled runAutoFocus so pass signal to Focus
0954     emit runAutoFocus(buildFilterOffsets);
0955 }
0956 
0957 void FilterManager::autoFocusComplete(FocusState completionState, int currentPosition, double currentTemperature,
0958                                       double currentAlt)
0959 {
0960     // Focus signalled Autofocus completed so pass signal to BuildFilterOffsets
0961     emit autoFocusDone(completionState, currentPosition, currentTemperature, currentAlt);
0962 }
0963 
0964 void FilterManager::signalAbortAutoFocus()
0965 {
0966     // BuildFilterOffsets signalled abortAutoFocus so pass signal to Focus
0967     emit abortAutoFocus();
0968 }
0969 
0970 }