Kstars

filtermanager.cpp
1/*
2 SPDX-FileCopyrightText: 2017 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "buildfilteroffsets.h"
8#include <kstars_debug.h>
9
10#include "indi_debug.h"
11#include "kstarsdata.h"
12#include "kstars.h"
13#include "ekos/focus/focus.h"
14#include "ekos/manager.h"
15#include "Options.h"
16#include "auxiliary/kspaths.h"
17#include "auxiliary/ksmessagebox.h"
18#include "ekos/auxiliary/tabledelegate.h"
19
20#include <QTimer>
21#include <QSqlTableModel>
22#include <QSqlDatabase>
23#include <QSqlRecord>
24
25#include <basedevice.h>
26
27#include <algorithm>
28
29namespace Ekos
30{
31
32FilterManager::FilterManager(QWidget *parent) : QDialog(parent)
33{
34#ifdef Q_OS_MACOS
35 setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
36#endif
37
38 setupUi(this);
39
40 connect(buttonBox, SIGNAL(accepted()), this, SLOT(close()));
41 connect(buttonBox, SIGNAL(rejected()), this, SLOT(close()));
42
43 connect(buildOffsetsButton, &QPushButton::clicked, this, &FilterManager::buildFilterOffsets);
44
45 kcfg_FlatSyncFocus->setChecked(Options::flatSyncFocus());
46 connect(kcfg_FlatSyncFocus, &QCheckBox::toggled, this, [this]()
47 {
48 Options::setFlatSyncFocus(kcfg_FlatSyncFocus->isChecked());
49 });
50
51 // 30 second timeout for filter change
52 m_FilterChangeTimeout.setSingleShot(true);
53 m_FilterChangeTimeout.setInterval(30000);
54 connect(&m_FilterChangeTimeout, &QTimer::timeout, this, &FilterManager::checkFilterChangeTimeout);
55
56 createFilterModel();
57
58 // No Edit delegate
59 noEditDelegate = new NotEditableDelegate(m_FilterView);
60 m_FilterView->setItemDelegateForColumn(FM_LABEL, noEditDelegate);
61
62 // Exposure delegate
63 exposureDelegate = new DoubleDelegate(m_FilterView, 0.001, 3600, 1);
64 m_FilterView->setItemDelegateForColumn(FM_EXPOSURE, exposureDelegate);
65
66 // Offset delegate
67 offsetDelegate = new IntegerDelegate(m_FilterView, -10000, 10000, 1);
68 m_FilterView->setItemDelegateForColumn(FM_OFFSET, offsetDelegate);
69
70 // Auto Focus delegate
71 useAutoFocusDelegate = new ToggleDelegate(m_FilterView);
72 m_FilterView->setItemDelegateForColumn(FM_AUTO_FOCUS, useAutoFocusDelegate);
73
74 // Set Delegates
75 lockDelegate = new ComboDelegate(m_FilterView);
76 m_FilterView->setItemDelegateForColumn(FM_LOCK_FILTER, lockDelegate);
77 lockDelegate->setValues(getLockDelegates());
78
79 // Last AF solution delegate. Set by Autofocus but make editable in case bad data
80 // corrections need to be made
81 lastAFSolutionDelegate = new IntegerDelegate(m_FilterView, 0, 1000000, 1);
82 m_FilterView->setItemDelegateForColumn(FM_LAST_AF_SOLUTION, lastAFSolutionDelegate);
83
84 // Last AF solution temperature delegate
85 lastAFTempDelegate = new DoubleDelegate(m_FilterView, -60.0, 60.0, 1.0);
86 m_FilterView->setItemDelegateForColumn(FM_LAST_AF_TEMP, lastAFTempDelegate);
87
88 // Last AF solution altitude delegate
89 lastAFAltDelegate = new DoubleDelegate(m_FilterView, 0.0, 90.0, 1.0);
90 m_FilterView->setItemDelegateForColumn(FM_LAST_AF_ALT, lastAFAltDelegate);
91
92 // Last AF solution datetime delegate
93 lastAFDTDelegate = new DatetimeDelegate(m_FilterView, DATETIME_FORMAT, "2025-01-01", "2100-01-01", false);
94 m_FilterView->setItemDelegateForColumn(FM_LAST_AF_DATETIME, lastAFDTDelegate);
95
96 // Ticks / °C delegate
97 ticksPerTempDelegate = new DoubleDelegate(m_FilterView, -10000.0, 10000.0, 1.0);
98 m_FilterView->setItemDelegateForColumn(FM_TICKS_PER_TEMP, ticksPerTempDelegate);
99
100 // Ticks / °Altitude delegate
101 ticksPerAltDelegate = new DoubleDelegate(m_FilterView, -10000.0, 10000.0, 1.0);
102 m_FilterView->setItemDelegateForColumn(FM_TICKS_PER_ALT, ticksPerAltDelegate);
103
104 // Wavelength delegate
105 wavelengthDelegate = new IntegerDelegate(m_FilterView, 200, 1000, 50);
106 m_FilterView->setItemDelegateForColumn(FM_WAVELENGTH, wavelengthDelegate);
107}
108
109void FilterManager::createFilterModel()
110{
111 QSqlDatabase userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName());
112
113 m_FilterModel = new QSqlTableModel(this, userdb);
114 m_FilterView->setModel(m_FilterModel);
115 m_FilterModel->setTable("filter");
116 m_FilterModel->setEditStrategy(QSqlTableModel::OnFieldChange);
117
118 m_FilterModel->setHeaderData(FM_LABEL, Qt::Horizontal, i18n("Filter"));
119
120 m_FilterModel->setHeaderData(FM_EXPOSURE, Qt::Horizontal, i18n("Filter exposure time during focus"), Qt::ToolTipRole);
121 m_FilterModel->setHeaderData(FM_EXPOSURE, Qt::Horizontal, i18n("Exposure"));
122
123 m_FilterModel->setHeaderData(FM_OFFSET, Qt::Horizontal, i18n("Relative offset in steps"), Qt::ToolTipRole);
124 m_FilterModel->setHeaderData(FM_OFFSET, Qt::Horizontal, i18n("Offset"));
125
126 m_FilterModel->setHeaderData(FM_AUTO_FOCUS, Qt::Horizontal, i18n("Start Auto Focus when filter is activated"),
128 m_FilterModel->setHeaderData(FM_AUTO_FOCUS, Qt::Horizontal, i18n("Auto Focus"));
129
130 m_FilterModel->setHeaderData(FM_LOCK_FILTER, Qt::Horizontal, i18n("Lock specific filter when running Auto Focus"),
132 m_FilterModel->setHeaderData(FM_LOCK_FILTER, Qt::Horizontal, i18n("Lock Filter"));
133
134 m_FilterModel->setHeaderData(FM_LAST_AF_SOLUTION, Qt::Horizontal,
135 i18n("Last AF solution. Updated automatically by the autofocus process."), Qt::ToolTipRole);
136 m_FilterModel->setHeaderData(FM_LAST_AF_SOLUTION, Qt::Horizontal, i18n("Last AF Solution"));
137
138 m_FilterModel->setHeaderData(FM_LAST_AF_TEMP, Qt::Horizontal,
139 i18n("The temperature of the last AF solution. Updated automatically by the autofocus process."), Qt::ToolTipRole);
140 m_FilterModel->setHeaderData(FM_LAST_AF_TEMP, Qt::Horizontal, i18n("Last AF Temp (°C)"));
141
142 m_FilterModel->setHeaderData(FM_LAST_AF_ALT, Qt::Horizontal,
143 i18n("The altitude of the last AF solution. Updated automatically by the autofocus process."), Qt::ToolTipRole);
144 m_FilterModel->setHeaderData(FM_LAST_AF_ALT, Qt::Horizontal, i18n("Last AF Alt (°Alt)"));
145
146 m_FilterModel->setHeaderData(FM_LAST_AF_DATETIME, Qt::Horizontal,
147 i18n("The datetime of the last AF solution. Updated automatically by the autofocus process."), Qt::ToolTipRole);
148 m_FilterModel->setHeaderData(FM_LAST_AF_DATETIME, Qt::Horizontal, i18n("Last AF Datetime"));
149
150 m_FilterModel->setHeaderData(FM_TICKS_PER_TEMP, Qt::Horizontal,
151 i18n("The number of ticks per °C increase in temperature. +ve for outward focuser movement"), Qt::ToolTipRole);
152 m_FilterModel->setHeaderData(FM_TICKS_PER_TEMP, Qt::Horizontal, i18n("Ticks / °C"));
153
154 m_FilterModel->setHeaderData(FM_TICKS_PER_ALT, Qt::Horizontal,
155 i18n("The number of ticks per degree increase in altitude. +ve for outward focuser movement"), Qt::ToolTipRole);
156 m_FilterModel->setHeaderData(FM_TICKS_PER_ALT, Qt::Horizontal, i18n("Ticks / °Alt"));
157
158 m_FilterModel->setHeaderData(FM_WAVELENGTH, Qt::Horizontal, i18n("Mid-point wavelength of filter in nm"), Qt::ToolTipRole);
159 m_FilterModel->setHeaderData(FM_WAVELENGTH, Qt::Horizontal, i18n("Wavelength"));
160
161 connect(m_FilterModel, &QSqlTableModel::dataChanged, this, &FilterManager::updated);
162
163 connect(m_FilterModel, &QSqlTableModel::dataChanged, this, [this](const QModelIndex & topLeft, const QModelIndex &,
164 const QVector<int> &)
165 {
166 reloadFilters();
167 if (topLeft.column() == FM_EXPOSURE)
168 emit exposureChanged(m_FilterModel->data(topLeft).toDouble());
169 else if (topLeft.column() == FM_LOCK_FILTER)
170 {
171 // Don't allow the lock filter to be set to the current filter - circular dependancy
172 // Don't allow the lock to be set if this filter is a lock for another filter - nested dependancy
173 QString lock = m_FilterModel->data(m_FilterModel->index(topLeft.row(), topLeft.column())).toString();
174 QString filter = m_FilterModel->data(m_FilterModel->index(topLeft.row(), FM_LABEL)).toString();
175 bool alreadyALock = false;
176 for (int i = 0; i < m_ActiveFilters.count(); i++)
177 {
178 if (m_ActiveFilters[i]->lockedFilter() == filter)
179 {
180 alreadyALock = true;
181 break;
182 }
183 }
184 if (alreadyALock || (lock == filter))
185 {
186 m_FilterModel->setData(m_FilterModel->index(topLeft.row(), topLeft.column()), NULL_FILTER);
187 }
188 // Update the acceptable values in the lockDelegate
189 lockDelegate->setValues(getLockDelegates());
190 }
191 else if (topLeft.column() == FM_TICKS_PER_TEMP)
192 emit ticksPerTempChanged();
193 else if (topLeft.column() == FM_TICKS_PER_ALT)
194 emit ticksPerAltChanged();
195 else if (topLeft.column() == FM_WAVELENGTH)
196 emit wavelengthChanged();
197 });
198}
199
200void FilterManager::refreshFilterModel()
201{
202 if (m_FilterWheel == nullptr || m_currentFilterLabels.empty())
203 return;
204
205 // In case filter model was cleared due to a device disconnect
206 if (m_FilterModel == nullptr)
207 createFilterModel();
208
209 QString vendor(m_FilterWheel->getDeviceName());
210 m_FilterModel->setFilter(QString("vendor='%1'").arg(vendor));
211 m_FilterModel->select();
212
213 m_FilterView->hideColumn(0);
214 m_FilterView->hideColumn(1);
215 m_FilterView->hideColumn(2);
216 m_FilterView->hideColumn(3);
217
218 // If we have an existing table but it doesn't match the number of current filters
219 // then we remove it.
220 if (m_FilterModel->rowCount() > 0 && m_FilterModel->rowCount() != m_currentFilterLabels.count())
221 {
222 for (int i = 0; i < m_FilterModel->rowCount(); i++)
223 m_FilterModel->removeRow(i);
224
225 m_FilterModel->select();
226 }
227
228 // If it is first time, let's populate data
229 if (m_FilterModel->rowCount() == 0)
230 {
231 filterProperties *fp = new filterProperties(vendor, "", "", "");
232 for (QString &filter : m_currentFilterLabels)
233 {
234 fp->color = filter;
235 KStarsData::Instance()->userdb()->AddFilter(fp);
236 }
237
238 m_FilterModel->select();
239 }
240 // Make sure all the filter colors match DB. If not update model to sync with INDI filter values
241 else
242 {
243 for (int i = 0; i < m_FilterModel->rowCount(); ++i)
244 {
245 QModelIndex index = m_FilterModel->index(i, FM_LABEL);
246 if (m_FilterModel->data(index).toString() != m_currentFilterLabels[i])
247 {
248 m_FilterModel->setData(index, m_currentFilterLabels[i]);
249 }
250 }
251 }
252
253 lockDelegate->setValues(getLockDelegates());
254
255 reloadFilters();
256 resizeDialog();
257}
258
259void FilterManager::resizeDialog()
260{
261 // Resize the columns to the data and then the dialog to the rows and columns
262 m_FilterView->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents);
263 int width = m_FilterView->horizontalHeader()->length() + 50;
264 int height = label->height() + m_FilterView->verticalHeader()->length() + label_2->height() + buttonBox->height() + 100;
265 this->resize(width, height);
266}
267// This function processes the list of active filters and returns a list of filters that could be lock filters
268// i.e. that don't themselves have locks.
269QStringList FilterManager::getLockDelegates()
270{
271 QStringList lockDelegates;
272
273 for (int i = 0; i < m_ActiveFilters.count(); i++)
274 {
275 if (m_ActiveFilters[i]->lockedFilter() == NULL_FILTER)
276 lockDelegates.append(m_ActiveFilters[i]->color());
277 }
278 return lockDelegates;
279}
280
281void FilterManager::reloadFilters()
282{
283 qDeleteAll(m_ActiveFilters);
284 currentFilter = nullptr;
285 targetFilter = nullptr;
286 m_ActiveFilters.clear();
287 operationQueue.clear();
288
289 filterProperties *fp = new filterProperties("", "", "", "");
290
291 for (int i = 0; i < m_FilterModel->rowCount(); ++i)
292 {
293 QSqlRecord record = m_FilterModel->record(i);
294 QString id = record.value("id").toString();
295
296 fp->vendor = record.value("Vendor").toString();
297 fp->model = record.value("Model").toString();
298 fp->type = record.value("Type").toString();
299 fp->color = record.value("Color").toString();
300 fp->exposure = record.value("Exposure").toDouble();
301 fp->offset = record.value("Offset").toInt();
302 fp->lockedFilter = record.value("LockedFilter").toString();
303 fp->useAutoFocus = record.value("UseAutoFocus").toInt() == 1;
304 fp->absFocusPos = record.value("AbsoluteFocusPosition").toInt();
305 fp->focusTemperature = record.value("FocusTemperature").toDouble();
306 fp->focusAltitude = record.value("FocusAltitude").toDouble();
307 fp->focusDatetime = record.value("FocusDatetime").toString();
308 fp->focusTicksPerTemp = record.value("FocusTicksPerTemp").toDouble();
309 fp->focusTicksPerAlt = record.value("FocusTicksPerAlt").toDouble();
310 fp->wavelength = record.value("Wavelength").toInt();
311 OAL::Filter *o = new OAL::Filter(id, fp);
312 m_ActiveFilters.append(o);
313 }
314}
315
316void FilterManager::setFilterWheel(ISD::FilterWheel *filter)
317{
318 // Return if same device and we already initialized the properties.
319 if (m_FilterWheel == filter && m_FilterNameProperty && m_FilterPositionProperty)
320 return;
321 else if (m_FilterWheel)
322 m_FilterWheel->disconnect(this);
323
324 m_FilterWheel = filter;
325
326 m_FilterNameProperty = nullptr;
327 m_FilterPositionProperty = nullptr;
328 m_FilterConfirmSet = nullptr;
329
330 if (!m_FilterWheel)
331 return;
332
333 connect(m_FilterWheel, &ISD::ConcreteDevice::propertyUpdated, this, &FilterManager::updateProperty);
334 connect(m_FilterWheel, &ISD::ConcreteDevice::Disconnected, this, &FilterManager::processDisconnect);
335
336 refreshFilterProperties();
337}
338
339void FilterManager::refreshFilterProperties()
340{
341 if (m_FilterNameProperty && m_FilterPositionProperty)
342 {
343 if (m_FilterConfirmSet == nullptr)
344 m_FilterConfirmSet = m_FilterWheel->getSwitch("CONFIRM_FILTER_SET");
345
346 // All filters are synced up?
347 if (m_currentFilterLabels.count() == m_FilterNameProperty->ntp)
348 return;
349 }
350
351 filterNameLabel->setText(m_FilterWheel->getDeviceName());
352
353 m_currentFilterLabels.clear();
354
355 m_FilterNameProperty = m_FilterWheel->getText("FILTER_NAME");
356 m_FilterPositionProperty = m_FilterWheel->getNumber("FILTER_SLOT");
357 m_FilterConfirmSet = m_FilterWheel->getSwitch("CONFIRM_FILTER_SET");
358
359 refreshFilterLabels();
360 refreshFilterPosition();
361
362 if (m_currentFilterPosition >= 1 && m_currentFilterPosition <= m_ActiveFilters.count())
363 lastFilterOffset = m_ActiveFilters[m_currentFilterPosition - 1]->offset();
364}
365
366QStringList FilterManager::getFilterLabels(bool forceRefresh)
367{
368 if ((!m_currentFilterLabels.empty() && forceRefresh == false) || !m_FilterNameProperty || !m_FilterPositionProperty)
369 return m_currentFilterLabels;
370
371 QStringList filterList;
372
373 for (int i = 0; i < m_FilterPositionProperty->np[0].max; i++)
374 {
375 if (m_FilterNameProperty != nullptr && (i < m_FilterNameProperty->ntp))
376 filterList.append(m_FilterNameProperty->tp[i].text);
377 }
378
379 return filterList;
380}
381
382int FilterManager::getFilterPosition(bool forceRefresh)
383{
384 if (forceRefresh == false || m_FilterPositionProperty == nullptr)
385 return m_currentFilterPosition;
386
387 return static_cast<int>(m_FilterPositionProperty->np[0].value);
388}
389
390void FilterManager::refreshFilterLabels()
391{
392 QList filters = getFilterLabels(true);
393
394 if (filters != m_currentFilterLabels)
395 {
396 m_currentFilterLabels = filters;
397 refreshFilterModel();
398
399 emit labelsChanged(filters);
400
401 // refresh position after filter changes
402 refreshFilterPosition();
403 }
404}
405
406void FilterManager::refreshFilterPosition()
407{
408
409 int pos = getFilterPosition(true);
410 if (pos != m_currentFilterPosition)
411 {
412 m_currentFilterPosition = pos;
413 emit positionChanged(pos);
414 }
415}
416
417bool FilterManager::setFilterPosition(uint8_t position, FilterPolicy policy)
418{
419 // Position 1 to Max
420 if (position > m_ActiveFilters.count())
421 return false;
422
423 m_Policy = policy;
424 currentFilter = m_ActiveFilters[m_currentFilterPosition - 1];
425 targetFilter = m_ActiveFilters[position - 1];
426
427 if (currentFilter == targetFilter)
428 {
429 emit ready();
430 return true;
431 }
432
433 buildOperationQueue(FILTER_CHANGE);
434
435 executeOperationQueue();
436
437 return true;
438}
439
440
441void FilterManager::updateProperty(INDI::Property prop)
442{
443 if (m_FilterWheel == nullptr || m_FilterWheel->getDeviceName() != prop.getDeviceName())
444 return;
445
446 if (prop.isNameMatch("FILTER_NAME"))
447 {
448 auto tvp = prop.getText();
449 m_FilterNameProperty = tvp;
450
451 refreshFilterLabels();
452 }
453 else if (prop.isNameMatch("FILTER_SLOT"))
454 {
455 m_FilterChangeTimeout.stop();
456
457 auto nvp = prop.getNumber();
458 // If filter fails to change position while we request that
459 // fail immediately.
460 if (state == FILTER_CHANGE && nvp->s == IPS_ALERT)
461 {
462 emit failed();
463 return;
464 }
465
466 if (nvp->s != IPS_OK)
467 return;
468
469 m_FilterPositionProperty = nvp;
470 refreshFilterPosition();
471
472 if (state == FILTER_CHANGE)
473 executeOperationQueue();
474 // If filter is changed externally, record its current offset as the starting offset.
475 else if (state == FILTER_IDLE && m_ActiveFilters.count() >= m_currentFilterPosition)
476 lastFilterOffset = m_ActiveFilters[m_currentFilterPosition - 1]->offset();
477 }
478}
479
480
481void FilterManager::processDisconnect()
482{
483 m_currentFilterLabels.clear();
484 m_currentFilterPosition = -1;
485 m_FilterNameProperty = nullptr;
486 m_FilterPositionProperty = nullptr;
487}
488
489void FilterManager::buildOperationQueue(FilterState operation)
490{
491 operationQueue.clear();
492 m_useTargetFilter = false;
493
494 switch (operation)
495 {
496 case FILTER_CHANGE:
497 {
498 if ( (m_Policy & CHANGE_POLICY) && targetFilter != currentFilter)
499 m_useTargetFilter = true;
500
501 if (m_useTargetFilter)
502 {
503 operationQueue.enqueue(FILTER_CHANGE);
504 if (m_FocusReady && (m_Policy & OFFSET_POLICY))
505 operationQueue.enqueue(FILTER_OFFSET);
506 else
507 {
508 // Keep track of filter and offset either here or after the offset has been processed
509 lastFilterOffset = targetFilter->offset();
510 currentFilter = targetFilter;
511 }
512
513 }
514
515 if (m_FocusReady && (m_Policy & AUTOFOCUS_POLICY) && targetFilter->useAutoFocus())
516 operationQueue.enqueue(FILTER_AUTOFOCUS);
517 }
518 break;
519
520 default:
521 break;
522 }
523}
524
525bool FilterManager::executeOperationQueue()
526{
527 if (operationQueue.isEmpty())
528 {
529 state = FILTER_IDLE;
530 emit newStatus(state);
531 emit ready();
532 return false;
533 }
534
535 FilterState nextOperation = operationQueue.dequeue();
536
537 bool actionRequired = true;
538
539 switch (nextOperation)
540 {
541 case FILTER_CHANGE:
542 {
543 m_FilterChangeTimeout.stop();
544
545 if (m_ConfirmationPending)
546 return true;
547
548 state = FILTER_CHANGE;
549 if (m_useTargetFilter)
550 targetFilterPosition = m_ActiveFilters.indexOf(targetFilter) + 1;
551 m_FilterWheel->setPosition(targetFilterPosition);
552
553 emit newStatus(state);
554
555 if (m_FilterConfirmSet)
556 {
557 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this]()
558 {
559 KSMessageBox::Instance()->disconnect(this);
560 m_ConfirmationPending = false;
561 m_FilterWheel->confirmFilter();
562 });
563 connect(KSMessageBox::Instance(), &KSMessageBox::rejected, this, [this]()
564 {
565 KSMessageBox::Instance()->disconnect(this);
566 m_ConfirmationPending = false;
567 });
568
569 m_ConfirmationPending = true;
570
571 KSMessageBox::Instance()->questionYesNo(i18n("Set filter to %1. Is filter set?", targetFilter->color()),
572 i18n("Confirm Filter"));
573 }
574 // If automatic filter change with filter wheel, we start operation timeout
575 else m_FilterChangeTimeout.start();
576 }
577 break;
578
579 case FILTER_OFFSET:
580 {
581 state = FILTER_OFFSET;
582 if (m_useTargetFilter)
583 {
584 targetFilterOffset = targetFilter->offset() - lastFilterOffset;
585 lastFilterOffset = targetFilter->offset();
586 currentFilter = targetFilter;
587 m_useTargetFilter = false;
588 }
589 if (targetFilterOffset == 0)
590 actionRequired = false;
591 else
592 {
593 emit newFocusOffset(targetFilterOffset, false);
594 emit newStatus(state);
595 }
596 }
597 break;
598
599 case FILTER_AUTOFOCUS:
600 state = FILTER_AUTOFOCUS;
601 qCDebug(KSTARS) << "FilterManager.cpp is triggering autofocus.";
602 emit newStatus(state);
603 emit runAutoFocus(AutofocusReason::FOCUS_FILTER, "");
604 break;
605
606 default:
607 break;
608 }
609
610 // If an additional action is required, return return and continue later
611 if (actionRequired)
612 return true;
613 // Otherwise, continue processing the queue
614 else
615 return executeOperationQueue();
616}
617
618bool FilterManager::executeOneOperation(FilterState operation)
619{
620 bool actionRequired = false;
621
622 switch (operation)
623 {
624 default:
625 break;
626 }
627
628 return actionRequired;
629}
630
631void FilterManager::setFocusOffsetComplete()
632{
633 if (state == FILTER_OFFSET)
634 executeOperationQueue();
635}
636
637double FilterManager::getFilterExposure(const QString &name) const
638{
639 auto filterDetails = getFilterByName(name);
640 if (filterDetails)
641 return filterDetails->exposure();
642
643 // Default value
644 return 1;
645}
646
647bool FilterManager::setFilterExposure(int index, double exposure)
648{
649 if (m_currentFilterLabels.empty())
650 return false;
651
652 QString color = m_currentFilterLabels[index];
653 for (int i = 0; i < m_ActiveFilters.count(); i++)
654 {
655 if (color == m_ActiveFilters[i]->color())
656 {
657 m_FilterModel->setData(m_FilterModel->index(i, FM_EXPOSURE), exposure);
658 m_FilterModel->submitAll();
659 refreshFilterModel();
660 return true;
661 }
662 }
663
664 return false;
665}
666
667int FilterManager::getFilterOffset(const QString &name) const
668{
669 int offset = INVALID_VALUE;
670 auto filterDetails = getFilterByName(name);
671 if (filterDetails)
672 offset = filterDetails->offset();
673
674 return offset;
675}
676
677bool FilterManager::setFilterOffset(QString color, int offset)
678{
679 if (m_currentFilterLabels.empty())
680 return false;
681
682 for (int i = 0; i < m_ActiveFilters.count(); i++)
683 {
684 if (color == m_ActiveFilters[i]->color())
685 {
686 m_FilterModel->setData(m_FilterModel->index(i, FM_OFFSET), offset);
687 m_FilterModel->submitAll();
688 refreshFilterModel();
689 return true;
690 }
691 }
692
693 return false;
694}
695
696bool FilterManager::getFilterAbsoluteFocusDetails(const QString &name, int &focusPos, double &focusTemp,
697 double &focusAlt) const
698{
699 auto filterDetails = getFilterByName(name);
700 if (filterDetails)
701 {
702 focusPos = filterDetails->absoluteFocusPosition();
703 focusTemp = filterDetails->focusTemperature();
704 focusAlt = filterDetails->focusAltitude();
705 return true;
706 }
707
708 return false;
709}
710
711bool FilterManager::setFilterAbsoluteFocusDetails(int index, int focusPos, double focusTemp, double focusAlt)
712{
713 if (index < 0 || index >= m_currentFilterLabels.count())
714 return false;
715
716 QString color = m_currentFilterLabels[index];
717 for (int i = 0; i < m_ActiveFilters.count(); i++)
718 {
719 if (color == m_ActiveFilters[i]->color())
720 {
721 m_FilterModel->setData(m_FilterModel->index(i, FM_LAST_AF_SOLUTION), focusPos);
722 m_FilterModel->setData(m_FilterModel->index(i, FM_LAST_AF_TEMP), focusTemp);
723 m_FilterModel->setData(m_FilterModel->index(i, FM_LAST_AF_ALT), focusAlt);
724 m_FilterModel->setData(m_FilterModel->index(i, FM_LAST_AF_DATETIME),
725 KStarsData::Instance()->lt().toString(DATETIME_FORMAT));
726 m_FilterModel->submitAll();
727 refreshFilterModel();
728 return true;
729 }
730 }
731
732 return false;
733}
734
735bool FilterManager::getAFDatetime(const QString &name, QDateTime &datetime) const
736{
737 auto filterDetails = getFilterByName(name);
738 if (filterDetails)
739 {
740 datetime = filterDetails->focusDatetime();
741 return true;
742 }
743 return false;
744}
745
746QString FilterManager::getFilterLock(const QString &name) const
747{
748 auto filterDetails = getFilterByName(name);
749 if (filterDetails)
750 return filterDetails->lockedFilter();
751
752 // Default value
753 return NULL_FILTER;
754}
755
756bool FilterManager::setFilterLock(int index, QString name)
757{
758 if (m_currentFilterLabels.empty())
759 return false;
760
761 QString color = m_currentFilterLabels[index];
762 for (int i = 0; i < m_ActiveFilters.count(); i++)
763 {
764 if (color == m_ActiveFilters[i]->color())
765 {
766 m_FilterModel->setData(m_FilterModel->index(i, FM_LOCK_FILTER), name);
767 m_FilterModel->submitAll();
768 refreshFilterModel();
769 return true;
770 }
771 }
772
773 return false;
774}
775
776int FilterManager::getFilterWavelength(const QString &name) const
777{
778 auto filterDetails = getFilterByName(name);
779 if (filterDetails)
780 return filterDetails->wavelength();
781
782 // Default value
783 return 500;
784}
785
786double FilterManager::getFilterTicksPerTemp(const QString &name) const
787{
788 auto filterDetails = getFilterByName(name);
789 if (filterDetails)
790 return filterDetails->focusTicksPerTemp();
791
792 // Something's wrong so return 0
793 return 0.0;
794}
795
796double FilterManager::getFilterTicksPerAlt(const QString &name) const
797{
798 auto filterDetails = getFilterByName(name);
799 if (filterDetails)
800 return filterDetails->focusTicksPerAlt();
801
802 // Something's wrong so return 0
803 return 0.0;
804}
805
806OAL::Filter * FilterManager::getFilterByName(const QString &name) const
807{
808 if (m_currentFilterLabels.empty() ||
809 m_currentFilterPosition < 1 ||
810 m_currentFilterPosition > m_currentFilterLabels.count())
811 return nullptr;
812
813 QString color = name;
814 if (color.isEmpty())
815 color = m_currentFilterLabels[m_currentFilterPosition - 1];
816
817 auto pos = std::find_if(m_ActiveFilters.begin(), m_ActiveFilters.end(), [color](OAL::Filter * oneFilter)
818 {
819 return (oneFilter->color() == color);
820 });
821
822 if (pos != m_ActiveFilters.end())
823 return (*pos);
824 else
825 return nullptr;
826}
827
828void FilterManager::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
829{
830 if (m_FilterWheel && (m_FilterWheel->getDeviceName() == device->getDeviceName()))
831 {
832 m_FilterNameProperty = nullptr;
833 m_FilterPositionProperty = nullptr;
834 m_FilterWheel = nullptr;
835 m_currentFilterLabels.clear();
836 m_currentFilterPosition = 0;
837 qDeleteAll(m_ActiveFilters);
838 m_ActiveFilters.clear();
839 delete(m_FilterModel);
840 m_FilterModel = nullptr;
841 }
842}
843
844void FilterManager::setFocusStatus(Ekos::FocusState focusState)
845{
846 if (state == FILTER_AUTOFOCUS)
847 {
848 switch (focusState)
849 {
850 case FOCUS_COMPLETE:
851 executeOperationQueue();
852 break;
853
854 case FOCUS_FAILED:
855 if (++retries == 3)
856 {
857 retries = 0;
858 emit failed();
859 return;
860 }
861 // Restart again
862 emit runAutoFocus(AutofocusReason::FOCUS_FILTER, "");
863 break;
864
865 default:
866 break;
867
868 }
869 }
870}
871
872bool FilterManager::syncAbsoluteFocusPosition(int index)
873{
874 if (m_FocusReady == false)
875 {
876 qCWarning(KSTARS_INDI) << __FUNCTION__ << "No Focuser detected.";
877 return true;
878 }
879 else if (index < 0 || index > m_ActiveFilters.count())
880 {
881 // We've been asked to set the focus position but something's wrong because
882 // the passed in filter index is bad. Give up and return true - returning false
883 // just results in an infinite retry loop.
884 qCWarning(KSTARS_INDI) << __FUNCTION__ << "index" << index << "is out of bounds.";
885 return true;
886 }
887
888 // By default filter absolute focus offset is zero
889 // JM 2023.07.03: So if it is zero, we return immediately.
890 auto absFocusPos = m_ActiveFilters[index]->absoluteFocusPosition();
891
892 if (m_FocusAbsPosition == absFocusPos || absFocusPos <= 0)
893 {
894 m_FocusAbsPositionPending = false;
895 return true;
896 }
897 else if (m_FocusAbsPositionPending == false)
898 {
899 m_FocusAbsPositionPending = true;
900 emit newFocusOffset(absFocusPos, true);
901 }
902
903 return false;
904}
905
906bool FilterManager::setFilterNames(const QStringList &newLabels)
907{
908 if (m_FilterWheel == nullptr || m_currentFilterLabels.empty())
909 return false;
910
911 m_FilterWheel->setLabels(newLabels);
912 return true;
913}
914
915QJsonObject FilterManager::toJSON()
916{
917 if (!m_FilterWheel)
918 return QJsonObject();
919
920 QJsonArray filters;
921
922 for (int i = 0; i < m_FilterModel->rowCount(); ++i)
923 {
924 QJsonObject oneFilter =
925 {
926 {"index", i},
927 {"label", m_FilterModel->data(m_FilterModel->index(i, FM_LABEL)).toString()},
928 {"exposure", m_FilterModel->data(m_FilterModel->index(i, FM_EXPOSURE)).toDouble()},
929 {"offset", m_FilterModel->data(m_FilterModel->index(i, FM_OFFSET)).toInt()},
930 {"autofocus", m_FilterModel->data(m_FilterModel->index(i, FM_AUTO_FOCUS)).toBool()},
931 {"lock", m_FilterModel->data(m_FilterModel->index(i, FM_LOCK_FILTER)).toString()},
932 {"lastafsolution", m_FilterModel->data(m_FilterModel->index(i, FM_LAST_AF_SOLUTION)).toInt()},
933 {"lastaftemp", m_FilterModel->data(m_FilterModel->index(i, FM_LAST_AF_TEMP)).toDouble()},
934 {"lastafalt", m_FilterModel->data(m_FilterModel->index(i, FM_LAST_AF_ALT)).toDouble()},
935 {"lastafdt", m_FilterModel->data(m_FilterModel->index(i, FM_LAST_AF_DATETIME)).toString()},
936 {"tickspertemp", m_FilterModel->data(m_FilterModel->index(i, FM_TICKS_PER_TEMP)).toDouble()},
937 {"ticksperalt", m_FilterModel->data(m_FilterModel->index(i, FM_TICKS_PER_ALT)).toDouble()},
938 {"wavelength", m_FilterModel->data(m_FilterModel->index(i, FM_WAVELENGTH)).toInt()},
939 };
940
941 filters.append(oneFilter);
942 }
943
944 QJsonObject data =
945 {
946 {"device", m_FilterWheel->getDeviceName()},
947 {"filters", filters}
948 };
949
950 return data;
951
952}
953
954void FilterManager::setFilterData(const QJsonObject &settings)
955{
956 if (!m_FilterWheel)
957 return;
958
959 if (settings["device"].toString() != m_FilterWheel->getDeviceName())
960 return;
961
962 QJsonArray filters = settings["filters"].toArray();
963 QStringList labels = getFilterLabels();
964
965 for (auto oneFilterRef : filters)
966 {
967 QJsonObject oneFilter = oneFilterRef.toObject();
968 int row = oneFilter["index"].toInt();
969
970 labels[row] = oneFilter["label"].toString();
971 m_FilterModel->setData(m_FilterModel->index(row, FM_LABEL), oneFilter["label"].toString());
972 m_FilterModel->setData(m_FilterModel->index(row, FM_EXPOSURE), oneFilter["exposure"].toDouble());
973 m_FilterModel->setData(m_FilterModel->index(row, FM_OFFSET), oneFilter["offset"].toInt());
974 m_FilterModel->setData(m_FilterModel->index(row, FM_AUTO_FOCUS), oneFilter["autofocus"].toBool());
975 m_FilterModel->setData(m_FilterModel->index(row, FM_LOCK_FILTER), oneFilter["lock"].toString());
976 m_FilterModel->setData(m_FilterModel->index(row, FM_LAST_AF_SOLUTION), oneFilter["lastafsolution"].toInt());
977 m_FilterModel->setData(m_FilterModel->index(row, FM_LAST_AF_TEMP), oneFilter["lastaftemp"].toDouble());
978 m_FilterModel->setData(m_FilterModel->index(row, FM_LAST_AF_ALT), oneFilter["lastafalt"].toDouble());
979 m_FilterModel->setData(m_FilterModel->index(row, FM_LAST_AF_DATETIME), oneFilter["lastafdt"].toString(DATETIME_FORMAT));
980 m_FilterModel->setData(m_FilterModel->index(row, FM_TICKS_PER_TEMP), oneFilter["tickspertemp"].toDouble());
981 m_FilterModel->setData(m_FilterModel->index(row, FM_TICKS_PER_ALT), oneFilter["ticksperalt"].toDouble());
982 m_FilterModel->setData(m_FilterModel->index(row, FM_WAVELENGTH), oneFilter["wavelength"].toInt());
983 }
984
985 m_FilterModel->submitAll();
986 setFilterNames(labels);
987
988 refreshFilterModel();
989}
990
991void FilterManager::buildFilterOffsets()
992{
993 // Launch the Build Filter Offsets utility. The utility uses a sync call to launch the dialog
994 QSharedPointer<FilterManager> filterManager;
995 Ekos::Manager::Instance()->getFilterManager(m_FilterWheel->getDeviceName(), filterManager);
996 BuildFilterOffsets bfo(filterManager);
997}
998
999void FilterManager::signalRunAutoFocus(AutofocusReason autofocusReason, const QString &reasonInfo)
1000{
1001 // BuildFilterOffsets signalled runAutoFocus so pass signal to Focus
1002 emit runAutoFocus(autofocusReason, reasonInfo);
1003}
1004
1005void FilterManager::autoFocusComplete(FocusState completionState, int currentPosition, double currentTemperature,
1006 double currentAlt)
1007{
1008 // Focus signalled Autofocus completed so pass signal to BuildFilterOffsets
1009 emit autoFocusDone(completionState, currentPosition, currentTemperature, currentAlt);
1010}
1011
1012void FilterManager::signalAbortAutoFocus()
1013{
1014 // BuildFilterOffsets signalled abortAutoFocus so pass signal to Focus
1015 emit abortAutoFocus();
1016}
1017
1018void FilterManager::checkFilterChangeTimeout()
1019{
1020 if (state == FILTER_CHANGE)
1021 {
1022 qCWarning(KSTARS) << "FilterManager.cpp filter change timed out.";
1023 emit failed();
1024 }
1025}
1026}
Information of user filters.
Definition filter.h:51
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:83
QString name(StandardAction id)
KGuiItem close()
QString label(StandardShortcut id)
void clicked(bool checked)
void toggled(bool checked)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
void accepted()
void rejected()
void append(const QJsonValue &value)
void append(QList< T > &&value)
int column() const const
int row() const const
QSqlDatabase database(const QString &connectionName, bool open)
QVariant value(const QString &name) const const
QChar * data()
bool isEmpty() const const
qsizetype length() const const
ToolTipRole
Horizontal
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 11:55:59 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.