Kstars

capture.cpp
1 /*
2  SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "capture.h"
8 
9 #include "captureprocess.h"
10 #include "capturemodulestate.h"
11 #include "capturedeviceadaptor.h"
12 #include "captureadaptor.h"
13 #include "refocusstate.h"
14 #include "kstars.h"
15 #include "kstarsdata.h"
16 #include "Options.h"
17 #include "rotatorsettings.h"
18 #include "sequencejob.h"
19 #include "sequencequeue.h"
20 #include "placeholderpath.h"
21 #include "ui_calibrationoptions.h"
22 #include "auxiliary/ksmessagebox.h"
23 #include "ekos/manager.h"
24 #include "ekos/auxiliary/darklibrary.h"
25 #include "ekos/auxiliary/profilesettings.h"
26 #include "ekos/auxiliary/opticaltrainmanager.h"
27 #include "scriptsmanager.h"
28 #include "fitsviewer/fitsdata.h"
29 #include "indi/driverinfo.h"
30 #include "indi/indifilterwheel.h"
31 #include "indi/indicamera.h"
32 #include "indi/indirotator.h"
33 #include "oal/observeradd.h"
34 #include "ekos/guide/guide.h"
35 #include "exposurecalculator/exposurecalculatordialog.h"
36 #include "dslrinfodialog.h"
37 #include "ekos/auxiliary/rotatorutils.h"
38 #include <basedevice.h>
39 
40 #include <ekos_capture_debug.h>
41 
42 #define MF_TIMER_TIMEOUT 90000
43 #define MF_RA_DIFF_LIMIT 4
44 
45 // Qt version calming
46 #include <qtendl.h>
47 
48 namespace
49 {
50 
51 // Columns in the job table
52 enum JobTableColumnIndex
53 {
54  JOBTABLE_COL_STATUS = 0,
55  JOBTABLE_COL_FILTER,
56  JOBTABLE_COL_COUNTS,
57  JOBTABLE_COL_EXP,
58  JOBTABLE_COL_TYPE,
59  JOBTABLE_COL_BINNING,
60  JOBTABLE_COL_ISO,
61  JOBTABLE_COL_OFFSET
62 };
63 
64 // Encode and decode for storing stand-alone options which are really QStringLists.
65 QString standAloneEncode(const QStringList &list)
66 {
67  if (list.size() == 0)
68  return "";
69  QString encoding;
70  encoding.append(list[0]);
71  for (int i = 1; i < list.size(); ++i)
72  {
73  encoding.append(",");
74  encoding.append(list[i]);
75  }
76  return encoding;
77 }
78 
79 QStringList standAloneDecode(const QString &encoding)
80 {
81  auto dec = encoding.split(",");
82  if (dec.size() == 1 && dec[0] == "")
83  return QStringList();
84  return dec;
85 }
86 
87 // Adds the items to the QComboBox if they're not there already.
88 void addToCombo(QComboBox *combo, const QStringList &items)
89 {
90  if (items.size() == 0)
91  return;
92  QStringList existingItems;
93  for (int index = 0; index < combo->count(); index++)
94  existingItems << combo->itemText(index);
95 
96  for (const auto &item : items)
97  if (existingItems.indexOf(item) == -1)
98  combo->addItem(item);
99 }
100 
101 } // namespace
102 
103 namespace Ekos
104 {
105 
106 // There are many widgets that are not used in stand-alone mode and should be made invisible and disabled.
107 void Capture::initStandAlone()
108 {
109  QList<QWidget*> unusedWidgets =
110  {
111  opticalTrainLabel, opticalTrainCombo, trainB, cameraRowLabel, cameraLabel, restartCameraB,
112  clearConfigurationB, coolerOnB, coolerOffB, setTemperatureB, temperatureRegulationB,
113  previewB, loopB, liveVideoB, startB, pauseB, resetB, processGrid, darkB, darkLibraryB,
114  filterManagerB
115  };
116  for (auto &widget : unusedWidgets)
117  {
118  widget->setEnabled(false);
119  widget->setVisible(false);
120  }
121  CCDFWGroup->setTitle("Settings");
122 }
123 
124 // Gets called when the stand-alone editor gets a show event.
125 // Do this initialization here so that if the live capture module was
126 // used after startup, it will have set more recent remembered values.
127 void Capture::onStandAloneShow(QShowEvent* event)
128 {
129  Q_UNUSED(event);
131 
132  // Default comment if there is no previously saved Options::CaptureStandAlone... parameters.
133  QString comment = i18n("<b><font color=\"red\">Please run the Capture tab connected to INDI with your desired "
134  "camera/filterbank at least once before using the Sequence Editor. </font></b><p>");
135  if (Options::captureStandAloneTimestamp().size() > 0)
136  comment = i18n("<b>Using camera and filterwheel attributes from Capture session started at %1.</b>"
137  "<p>If you wish to use other cameras/filterbanks, please edit the sequence "
138  "using the Capture tab.<br>It is not recommended to overwrite a sequence file currently running, "
139  "please rename it instead.</p><p>", Options::captureStandAloneTimestamp());
140  sequenceEditorComment->setVisible(true);
141  sequenceEditorComment->setEnabled(true);
142  sequenceEditorComment->setStyleSheet("{color: #C0BBFE}");
143  sequenceEditorComment->setText(comment);
144 
145  // Add extra load and save buttons at the bottom of the window.
146  loadSaveBox->setEnabled(true);
147  loadSaveBox->setVisible(true);
148  connect(esqSaveAsB, &QPushButton::clicked, this, &Capture::saveSequenceQueueAs);
149  connect(esqLoadB, &QPushButton::clicked, this, static_cast<void(Capture::*)()>(&Capture::loadSequenceQueue));
150 
151  // This currently gets the filters from filter manager #0.
152  // Could try all of them?
153  bool ok = Manager::Instance()->getFilterManager(fm);
154  if (ok)
155  addToCombo(FilterPosCombo, fm->getFilterLabels());
156  addToCombo(FilterPosCombo, standAloneDecode(Options::captureStandAloneFilters()));
157 
158  if (FilterPosCombo->count() > 0)
159  filterEditB->setEnabled(true);
160 
161  captureGainN->setEnabled(true);
162  captureGainN->setValue(GainSpinSpecialValue);
163  captureGainN->setSpecialValueText(i18n("--"));
164 
165  captureOffsetN->setEnabled(true);
166  captureOffsetN->setValue(OffsetSpinSpecialValue);
167  captureOffsetN->setSpecialValueText(i18n("--"));
168 
169  // Always add these strings to the types menu. Might also add other ones
170  // that were used in the last capture session.
171  const QStringList frameTypes = {"Light", "Dark", "Bias", "Flat"};
172  captureTypeS->clear();
173  captureTypeS->addItems(frameTypes);
174  addToCombo(captureTypeS, standAloneDecode(Options::captureStandAloneTypes()));
175 
176  // Always add these strings to the encodings menu. Might also add other ones
177  // that were used in the last capture session.
178  const QStringList frameEncodings = {"FITS", "Native", "XISF"};
179  captureEncodingS->clear();
180  captureEncodingS->addItems(frameEncodings);
181  addToCombo(captureEncodingS, standAloneDecode(Options::captureStandAloneEncodings()));
182 
183  const QStringList frameFormats = {};
184  captureFormatS->clear();
185  if (frameFormats.size() > 0)
186  captureFormatS->addItems(frameFormats);
187  addToCombo(captureFormatS, standAloneDecode(Options::captureStandAloneFormats()));
188 
189  cameraTemperatureN->setEnabled(true);
190 
191  // No pre-configured ISOs are available--would be too much of a guess, but
192  // we will use ISOs from the last live capture session.
193  QStringList isoList = standAloneDecode(Options::captureStandAloneISOs());
194  if (isoList.size() > 0)
195  {
196  captureISOS->clear();
197  captureISOS->addItems(isoList);
198  captureISOS->setCurrentIndex(Options::captureStandAloneISOIndex());
199  captureISOS->blockSignals(false);
200  captureISOS->setEnabled(true);
201  }
202  else
203  {
204  captureISOS->blockSignals(true);
205  captureISOS->clear();
206  captureISOS->setEnabled(false);
207  }
208 
209  // Remember the sensor width and height from the last live session.
210  // The user can always edit the input box.
211  constexpr int maxFrame = 20000;
212  captureFrameXN->setMaximum(static_cast<int>(maxFrame));
213  captureFrameYN->setMaximum(static_cast<int>(maxFrame));
214  captureFrameWN->setMaximum(static_cast<int>(maxFrame));
215  captureFrameHN->setMaximum(static_cast<int>(maxFrame));
216  QStringList whList = standAloneDecode(Options::captureStandAloneWHGO());
217  if (whList.size() == 4)
218  {
219  captureFrameWN->setValue(whList[0].toInt());
220  captureFrameHN->setValue(whList[1].toInt());
221  m_standAloneUseCcdGain = whList[2] == "CCD_GAIN";
222  m_standAloneUseCcdOffset = whList[3] == "CCD_OFFSET";
223  }
224 
225  connect(captureGainN, &QDoubleSpinBox::editingFinished, this, [this]()
226  {
227  if (captureGainN->value() != GainSpinSpecialValue)
228  setGain(captureGainN->value());
229  else
230  setGain(-1);
231  });
232 
233  connect(captureOffsetN, &QDoubleSpinBox::editingFinished, this, [this]()
234  {
235  if (captureOffsetN->value() != OffsetSpinSpecialValue)
236  setOffset(captureOffsetN->value());
237  else
238  setOffset(-1);
239  });
240 }
241 
242 Capture::Capture(bool standAlone) : m_standAlone(standAlone)
243 {
244  setupUi(this);
245 
246  if (!m_standAlone)
247  {
248  qRegisterMetaType<CaptureState>("CaptureState");
249  qDBusRegisterMetaType<CaptureState>();
250  }
251  new CaptureAdaptor(this);
252  m_captureModuleState.reset(new CaptureModuleState());
253  m_captureDeviceAdaptor.reset(new CaptureDeviceAdaptor());
254  m_captureProcess = new CaptureProcess(state(), m_captureDeviceAdaptor);
255 
256  state()->getSequenceQueue()->loadOptions();
257 
258  if (m_standAlone)
259  initStandAlone();
260 
261  if (!m_standAlone)
262  {
263  QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Capture", this);
264  QPointer<QDBusInterface> ekosInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos",
266 
267  // Connecting DBus signals
268  QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "newModule", this,
269  SLOT(registerNewModule(QString)));
270 
271  // ensure that the mount interface is present
272  registerNewModule("Mount");
273  }
274  KStarsData::Instance()->userdb()->GetAllDSLRInfos(state()->DSLRInfos());
275 
276  if (state()->DSLRInfos().count() > 0)
277  {
278  qCDebug(KSTARS_EKOS_CAPTURE) << "DSLR Cameras Info:";
279  qCDebug(KSTARS_EKOS_CAPTURE) << state()->DSLRInfos();
280  }
281 
282  m_LimitsDialog = new QDialog(this);
283  m_LimitsUI.reset(new Ui::Limits());
284  m_LimitsUI->setupUi(m_LimitsDialog);
285  m_scriptsManager = new ScriptsManager(this);
286  if (m_standAlone)
287  {
288  // Prepend "Capture Sequence Editor" to the two pop-up window titles, to differentiate them
289  // from similar windows in the Capture tab.
290  auto title = i18n("Capture Sequence Editor: %1", m_LimitsDialog->windowTitle());
291  m_LimitsDialog->setWindowTitle(title);
292  title = i18n("Capture Sequence Editor: %1", m_scriptsManager->windowTitle());
293  m_scriptsManager->setWindowTitle(title);
294  }
295  dirPath = QUrl::fromLocalFile(QDir::homePath());
296 
297  //isAutoGuiding = false;
298 
299  // hide avg. download time and target drift initially
300  targetDriftLabel->setVisible(false);
301  targetDrift->setVisible(false);
302  targetDriftUnit->setVisible(false);
303  avgDownloadTime->setVisible(false);
304  avgDownloadLabel->setVisible(false);
305  secLabel->setVisible(false);
306 
307  state()->getCaptureDelayTimer().setSingleShot(true);
308  connect(&state()->getCaptureDelayTimer(), &QTimer::timeout, m_captureProcess, &CaptureProcess::captureImage);
309 
311  connect(pauseB, &QPushButton::clicked, this, &Capture::pause);
312  connect(darkLibraryB, &QPushButton::clicked, DarkLibrary::Instance(), &QDialog::show);
313  connect(limitsB, &QPushButton::clicked, m_LimitsDialog, &QDialog::show);
314  connect(temperatureRegulationB, &QPushButton::clicked, this, &Capture::showTemperatureRegulation);
315 
316  startB->setIcon(QIcon::fromTheme("media-playback-start"));
317  startB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
318  pauseB->setIcon(QIcon::fromTheme("media-playback-pause"));
319  pauseB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
320 
321  filterManagerB->setIcon(QIcon::fromTheme("view-filter"));
322  filterManagerB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
323 
324  connect(captureBinHN, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), captureBinVN, &QSpinBox::setValue);
325 
326  connect(liveVideoB, &QPushButton::clicked, this, &Capture::toggleVideo);
327 
328  connect(clearConfigurationB, &QPushButton::clicked, this, &Capture::clearCameraConfiguration);
329 
330  darkB->setChecked(Options::autoDark());
331  connect(darkB, &QAbstractButton::toggled, this, [this]()
332  {
333  Options::setAutoDark(darkB->isChecked());
334  });
335 
336  connect(restartCameraB, &QPushButton::clicked, this, [this]()
337  {
338  if (activeCamera())
339  restartCamera(activeCamera()->getDeviceName());
340  });
341 
342  connect(cameraTemperatureS, &QCheckBox::toggled, this, [this](bool toggled)
343  {
344  if (devices()->getActiveCamera())
345  {
346  QVariantMap auxInfo = devices()->getActiveCamera()->getDriverInfo()->getAuxInfo();
347  auxInfo[QString("%1_TC").arg(devices()->getActiveCamera()->getDeviceName())] = toggled;
348  devices()->getActiveCamera()->getDriverInfo()->setAuxInfo(auxInfo);
349  }
350  });
351 
352  connect(filterEditB, &QPushButton::clicked, this, &Capture::editFilterName);
353 
354  connect(FilterPosCombo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
355  [ = ]()
356  {
357  state()->updateHFRThreshold();
358  generatePreviewFilename();
359  });
362 
363  //connect( seqWatcher, SIGNAL(dirty(QString)), this, &Capture::checkSeqFile(QString)));
364 
365  connect(addToQueueB, &QPushButton::clicked, this, [this]()
366  {
367  if (m_JobUnderEdit)
368  editJobFinished();
369  else
370  createJob();
371  });
372  connect(queueUpB, &QPushButton::clicked, [this]()
373  {
374  moveJob(true);
375  });
376  connect(queueDownB, &QPushButton::clicked, [this]()
377  {
378  moveJob(false);
379  });
380  connect(removeFromQueueB, &QPushButton::clicked, this, &Capture::removeJobFromQueue);
381  connect(selectFileDirectoryB, &QPushButton::clicked, this, &Capture::saveFITSDirectory);
382  connect(queueSaveB, &QPushButton::clicked, this, static_cast<void(Capture::*)()>(&Capture::saveSequenceQueue));
383  connect(queueSaveAsB, &QPushButton::clicked, this, &Capture::saveSequenceQueueAs);
384  connect(queueLoadB, &QPushButton::clicked, this, static_cast<void(Capture::*)()>(&Capture::loadSequenceQueue));
385  connect(resetB, &QPushButton::clicked, this, &Capture::resetJobs);
386  connect(queueTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Capture::selectedJobChanged);
387  connect(queueTable, &QAbstractItemView::doubleClicked, this, &Capture::editJob);
388  connect(queueTable, &QTableWidget::itemSelectionChanged, this, [&]()
389  {
390  resetJobEdit(m_JobUnderEdit);
391  });
392  connect(setTemperatureB, &QPushButton::clicked, this, [&]()
393  {
394  if (devices()->getActiveCamera())
395  devices()->getActiveCamera()->setTemperature(cameraTemperatureN->value());
396  });
397  connect(coolerOnB, &QPushButton::clicked, this, [&]()
398  {
399  if (devices()->getActiveCamera())
400  devices()->getActiveCamera()->setCoolerControl(true);
401  });
402  connect(coolerOffB, &QPushButton::clicked, this, [&]()
403  {
404  if (devices()->getActiveCamera())
405  devices()->getActiveCamera()->setCoolerControl(false);
406  });
407  connect(cameraTemperatureN, &QDoubleSpinBox::editingFinished, setTemperatureB,
408  static_cast<void (QPushButton::*)()>(&QPushButton::setFocus));
409  connect(captureTypeS, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
410  &Capture::checkFrameType);
411  connect(resetFrameB, &QPushButton::clicked, m_captureProcess, &CaptureProcess::resetFrame);
412  connect(calibrationB, &QPushButton::clicked, this, &Capture::openCalibrationDialog);
413  // connect(rotatorB, &QPushButton::clicked, m_RotatorControlPanel.get(), &Capture::show);
414 
415  connect(generateDarkFlatsB, &QPushButton::clicked, this, &Capture::generateDarkFlats);
416  connect(scriptManagerB, &QPushButton::clicked, this, &Capture::handleScriptsManager);
417  connect(resetFormatB, &QPushButton::clicked, this, [this]()
418  {
419  placeholderFormatT->setText(KSUtils::getDefaultPath("PlaceholderFormat"));
420  });
421 
422  addToQueueB->setIcon(QIcon::fromTheme("list-add"));
423  addToQueueB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
424  removeFromQueueB->setIcon(QIcon::fromTheme("list-remove"));
425  removeFromQueueB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
426  queueUpB->setIcon(QIcon::fromTheme("go-up"));
427  queueUpB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
428  queueDownB->setIcon(QIcon::fromTheme("go-down"));
429  queueDownB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
430  selectFileDirectoryB->setIcon(QIcon::fromTheme("document-open-folder"));
431  selectFileDirectoryB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
432  queueLoadB->setIcon(QIcon::fromTheme("document-open"));
433  queueLoadB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
434  queueSaveB->setIcon(QIcon::fromTheme("document-save"));
435  queueSaveB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
436  queueSaveAsB->setIcon(QIcon::fromTheme("document-save-as"));
437  queueSaveAsB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
438  resetB->setIcon(QIcon::fromTheme("system-reboot"));
439  resetB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
440  resetFrameB->setIcon(QIcon::fromTheme("view-refresh"));
441  resetFrameB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
442  calibrationB->setIcon(QIcon::fromTheme("run-build"));
443  calibrationB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
444  generateDarkFlatsB->setIcon(QIcon::fromTheme("tools-wizard"));
445  generateDarkFlatsB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
446  // rotatorB->setIcon(QIcon::fromTheme("kstars_solarsystem"));
447  rotatorB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
448 
449  addToQueueB->setToolTip(i18n("Add job to sequence queue"));
450  removeFromQueueB->setToolTip(i18n("Remove job from sequence queue"));
451 
452  ////////////////////////////////////////////////////////////////////////
453  /// Device Adaptor
454  ////////////////////////////////////////////////////////////////////////
455  connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::newCCDTemperatureValue, this,
457  connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::newRotatorAngle, this,
458  &Capture::updateRotatorAngle, Qt::UniqueConnection);
459 
460  ////////////////////////////////////////////////////////////////////////
461  /// Settings
462  ////////////////////////////////////////////////////////////////////////
463  syncGUIToGeneralSettings();
464  // Start Guide Deviation Check
465  connect(m_LimitsUI->startGuiderDriftS, &QCheckBox::toggled, [ = ](bool checked)
466  {
467  // We don't want the editor to influence a concurrent live capture session.
468  if (!m_standAlone)
469  Options::setEnforceStartGuiderDrift(checked);
470  });
471 
472  // Start Guide Deviation Value
473  connect(m_LimitsUI->startGuiderDriftN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]()
474  {
475  // We don't want the editor to influence a concurrent live capture session.
476  if (!m_standAlone)
477  Options::setStartGuideDeviation(m_LimitsUI->startGuiderDriftN->value());
478  });
479 
480  // Abort Guide Deviation Check
481  connect(m_LimitsUI->limitGuideDeviationS, &QCheckBox::toggled, [ = ](bool checked)
482  {
483  // We don't want the editor to influence a concurrent live capture session.
484  if (!m_standAlone)
485  Options::setEnforceGuideDeviation(checked);
486  });
487 
488  // Per job dither frequency count
489  connect(m_LimitsUI->limitDitherFrequencyN, QOverload<int>::of(&QSpinBox::valueChanged), [this]()
490  {
491  // We don't want the editor to influence a concurrent live capture session.
492  if (!m_standAlone)
493  Options::setGuideDitherPerJobFrequency(m_LimitsUI->limitDitherFrequencyN->value());
494  });
495 
496  // Guide Deviation Value
497  connect(m_LimitsUI->limitGuideDeviationN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]()
498  {
499  // We don't want the editor to influence a concurrent live capture session.
500  if (!m_standAlone)
501  Options::setGuideDeviation(m_LimitsUI->limitGuideDeviationN->value());
502  });
503 
504  connect(m_LimitsUI->limitGuideDeviationRepsN, QOverload<int>::of(&QSpinBox::valueChanged), this, [this]()
505  {
506  // We don't want the editor to influence a concurrent live capture session.
507  if (!m_standAlone)
508  Options::setGuideDeviationReps(static_cast<uint>(m_LimitsUI->limitGuideDeviationRepsN->value()));
509  });
510 
511  // Autofocus HFR Check
512  connect(m_LimitsUI->limitFocusHFRS, &QCheckBox::toggled, [ = ](bool checked)
513  {
514  // We don't want the editor to influence a concurrent live capture session.
515  if (!m_standAlone)
516  Options::setEnforceAutofocusHFR(checked);
517  if (checked == false)
518  state()->getRefocusState()->setInSequenceFocus(false);
519  });
520  m_LimitsUI->limitFocusHFRN->setValue(Options::hFRDeviation());
521  connect(m_LimitsUI->limitFocusHFRN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]()
522  {
523  // We don't want the editor to influence a concurrent live capture session.
524  if (!m_standAlone)
525  Options::setHFRDeviation(m_LimitsUI->limitFocusHFRN->value());
526  });
527  m_LimitsUI->limitFocusHFRThresholdPercentage->setValue(Options::hFRThresholdPercentage());
528  connect(m_LimitsUI->limitFocusHFRThresholdPercentage, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]()
529  {
530  Options::setHFRThresholdPercentage(m_LimitsUI->limitFocusHFRThresholdPercentage->value());
531  Capture::updateHFRCheckAlgo();
532  });
533  m_LimitsUI->limitFocusHFRCheckFrames->setValue(Options::inSequenceCheckFrames());
534  connect(m_LimitsUI->limitFocusHFRCheckFrames, QOverload<int>::of(&QSpinBox::valueChanged), this, [this]()
535  {
536  Options::setInSequenceCheckFrames(m_LimitsUI->limitFocusHFRCheckFrames->value());
537  });
538  connect(m_captureModuleState.get(), &CaptureModuleState::newLimitFocusHFR, this, [this](double hfr)
539  {
540  m_LimitsUI->limitFocusHFRN->setValue(hfr);
541  });
542  m_LimitsUI->limitFocusHFRAlgorithm->setCurrentIndex(Options::hFRCheckAlgorithm());
543  updateHFRCheckAlgo();
544  connect(m_LimitsUI->limitFocusHFRAlgorithm, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
545  {
546  Options::setHFRCheckAlgorithm(index);
547  Capture::updateHFRCheckAlgo();
548  });
549 
550  // Autofocus temperature Check
551  connect(m_LimitsUI->limitFocusDeltaTS, &QCheckBox::toggled, this, [ = ](bool checked)
552  {
553  // We don't want the editor to influence a concurrent live capture session.
554  if (!m_standAlone)
555  Options::setEnforceAutofocusOnTemperature(checked);
556  });
557 
558  // Autofocus temperature Delta
559  connect(m_LimitsUI->limitFocusDeltaTN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]()
560  {
561  // We don't want the editor to influence a concurrent live capture session.
562  if (!m_standAlone)
563  Options::setMaxFocusTemperatureDelta(m_LimitsUI->limitFocusDeltaTN->value());
564  });
565 
566  // Refocus Every Check
567  connect(m_LimitsUI->limitRefocusS, &QCheckBox::toggled, this, [ = ](bool checked)
568  {
569  // We don't want the editor to influence a concurrent live capture session.
570  if (!m_standAlone)
571  Options::setEnforceRefocusEveryN(checked);
572  });
573 
574  // Refocus Every Value
575  connect(m_LimitsUI->limitRefocusN, QOverload<int>::of(&QSpinBox::valueChanged), this, [this]()
576  {
577  // We don't want the editor to influence a concurrent live capture session.
578  if (!m_standAlone)
579  Options::setRefocusEveryN(static_cast<uint>(m_LimitsUI->limitRefocusN->value()));
580  });
581 
582  // Refocus after meridian flip
583  m_LimitsUI->meridianRefocusS->setChecked(Options::refocusAfterMeridianFlip());
584  connect(m_LimitsUI->meridianRefocusS, &QCheckBox::toggled, [ = ](bool checked)
585  {
586  // We don't want the editor to influence a concurrent live capture session.
587  if (!m_standAlone)
588  Options::setRefocusAfterMeridianFlip(checked);
589  });
590 
591  QCheckBox * const checkBoxes[] =
592  {
593  m_LimitsUI->limitGuideDeviationS,
594  m_LimitsUI->startGuiderDriftS,
595  m_LimitsUI->limitRefocusS,
596  m_LimitsUI->limitFocusDeltaTS,
597  m_LimitsUI->limitFocusHFRS,
598  m_LimitsUI->meridianRefocusS,
599  };
600  for (const QCheckBox * control : checkBoxes)
601  connect(control, &QCheckBox::toggled, this, [&]()
602  {
603  state()->setDirty(true);
604  });
605 
606  QDoubleSpinBox * const dspinBoxes[]
607  {
608  m_LimitsUI->limitFocusHFRN,
609  m_LimitsUI->limitFocusHFRThresholdPercentage,
610  m_LimitsUI->limitFocusDeltaTN,
611  m_LimitsUI->limitGuideDeviationN,
612  m_LimitsUI->startGuiderDriftN
613  };
614  for (const QDoubleSpinBox * control : dspinBoxes)
615  connect(control, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, [&]()
616  {
617  state()->setDirty(true);
618  });
619 
620  connect(m_LimitsUI->limitFocusHFRCheckFrames, QOverload<int>::of(&QSpinBox::valueChanged), this, [&]()
621  {
622  state()->setDirty(true);
623  });
624  connect(m_LimitsUI->limitFocusHFRAlgorithm, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&]()
625  {
626  state()->setDirty(true);
627  });
628 
629  connect(fileUploadModeS, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [&]()
630  {
631  state()->setDirty(true);
632  });
633  connect(fileRemoteDirT, &QLineEdit::editingFinished, this, [&]()
634  {
635  state()->setDirty(true);
636  });
637 
638  observerB->setIcon(QIcon::fromTheme("im-user"));
639  observerB->setAttribute(Qt::WA_LayoutUsesWidgetRect);
640  connect(observerB, &QPushButton::clicked, this, &Capture::showObserverDialog);
641 
642  // Exposure Timeout
643  state()->getCaptureTimeout().setSingleShot(true);
644  connect(&state()->getCaptureTimeout(), &QTimer::timeout, m_captureProcess,
646 
647  // Remote directory
648  connect(fileUploadModeS, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
649  [&](int index)
650  {
651  fileRemoteDirT->setEnabled(index != 0);
652  });
653 
654  customPropertiesDialog.reset(new CustomProperties());
655  connect(customValuesB, &QPushButton::clicked, this, [&]()
656  {
657  customPropertiesDialog.get()->show();
658  customPropertiesDialog.get()->raise();
659  });
660  connect(customPropertiesDialog.get(), &CustomProperties::valueChanged, this, [&]()
661  {
662  const double newGain = getGain();
663  if (captureGainN && newGain >= 0)
664  captureGainN->setValue(newGain);
665  const int newOffset = getOffset();
666  if (newOffset >= 0)
667  captureOffsetN->setValue(newOffset);
668  });
669 
670  if(!Options::captureDirectory().isEmpty())
671  fileDirectoryT->setText(Options::captureDirectory());
672  else
673  {
674  fileDirectoryT->setText(QDir::homePath() + QDir::separator() + "Pictures");
675  Options::setCaptureDirectory(fileDirectoryT->text());
676  }
677 
678  connect(fileDirectoryT, &QLineEdit::textChanged, this, [&]()
679  {
680  Options::setCaptureDirectory(fileDirectoryT->text());
681  generatePreviewFilename();
682  });
683 
684  if (Options::remoteCaptureDirectory().isEmpty() == false)
685  {
686  fileRemoteDirT->setText(Options::remoteCaptureDirectory());
687  }
688  connect(fileRemoteDirT, &QLineEdit::editingFinished, this, [&]()
689  {
690  Options::setRemoteCaptureDirectory(fileRemoteDirT->text());
691  generatePreviewFilename();
692  });
693 
694  //Note: This is to prevent a button from being called the default button
695  //and then executing when the user hits the enter key such as when on a Text Box
696  QList<QPushButton *> qButtons = findChildren<QPushButton *>();
697  for (auto &button : qButtons)
698  button->setAutoDefault(false);
699 
700  DarkLibrary::Instance()->setCaptureModule(this);
701 
702  // display the capture status in the UI
703  connect(this, &Capture::newStatus, captureStatusWidget, &LedStatusWidget::setCaptureState);
704 
705  // react upon state changes
706  connect(m_captureModuleState.data(), &CaptureModuleState::captureBusy, this, &Capture::setBusy);
707  connect(m_captureModuleState.data(), &CaptureModuleState::startCapture, this, &Capture::start);
708  connect(m_captureModuleState.data(), &CaptureModuleState::abortCapture, this, &Capture::abort);
709  connect(m_captureModuleState.data(), &CaptureModuleState::suspendCapture, this, &Capture::suspend);
710  connect(m_captureModuleState.data(), &CaptureModuleState::executeActiveJob, m_captureProcess.data(),
712  connect(m_captureModuleState.data(), &CaptureModuleState::updatePrepareState, this, &Capture::updatePrepareState);
713  // forward signals from capture module state
714  connect(m_captureModuleState.data(), &CaptureModuleState::captureStarted, m_captureProcess.data(),
716  connect(m_captureModuleState.data(), &CaptureModuleState::newLog, this, &Capture::appendLogText);
717  connect(m_captureModuleState.data(), &CaptureModuleState::newStatus, this, &Capture::newStatus);
718  connect(m_captureModuleState.data(), &CaptureModuleState::sequenceChanged, this, &Capture::sequenceChanged);
719  connect(m_captureModuleState.data(), &CaptureModuleState::checkFocus, this, &Capture::checkFocus);
720  connect(m_captureModuleState.data(), &CaptureModuleState::runAutoFocus, this, &Capture::runAutoFocus);
721  connect(m_captureModuleState.data(), &CaptureModuleState::resetFocus, this, &Capture::resetFocus);
722  connect(m_captureModuleState.data(), &CaptureModuleState::adaptiveFocus, this, &Capture::adaptiveFocus);
723  connect(m_captureModuleState.data(), &CaptureModuleState::guideAfterMeridianFlip, this,
724  &Capture::guideAfterMeridianFlip);
725  connect(m_captureModuleState.data(), &CaptureModuleState::newFocusStatus, this, &Capture::updateFocusStatus);
726  connect(m_captureModuleState.data(), &CaptureModuleState::newMeridianFlipStage, this, &Capture::updateMeridianFlipStage);
727  connect(m_captureModuleState.data(), &CaptureModuleState::meridianFlipStarted, this, &Capture::meridianFlipStarted);
728 
729  // forward signals from capture process
730  connect(m_captureProcess.data(), &CaptureProcess::cameraReady, this, &Capture::ready);
731  connect(m_captureProcess.data(), &CaptureProcess::refreshCamera, this, &Capture::updateCamera);
732  connect(m_captureProcess.data(), &CaptureProcess::refreshCameraSettings, this, &Capture::refreshCameraSettings);
733  connect(m_captureProcess.data(), &CaptureProcess::refreshFilterSettings, this, &Capture::refreshFilterSettings);
734  connect(m_captureProcess.data(), &CaptureProcess::newExposureProgress, this, &Capture::newExposureProgress);
735  connect(m_captureProcess.data(), &CaptureProcess::newDownloadProgress, this, &Capture::updateDownloadProgress);
736  connect(m_captureProcess.data(), &CaptureProcess::updateCaptureCountDown, this, &Capture::updateCaptureCountDown);
737  connect(m_captureProcess.data(), &CaptureProcess::processingFITSfinished, this, &Capture::processingFITSfinished);
738  connect(m_captureProcess.data(), &CaptureProcess::newImage, this, &Capture::newImage);
739  connect(m_captureProcess.data(), &CaptureProcess::syncGUIToJob, this, &Capture::syncGUIToJob);
740  connect(m_captureProcess.data(), &CaptureProcess::captureComplete, this, &Capture::captureComplete);
741  connect(m_captureProcess.data(), &CaptureProcess::updateFrameProperties, this, &Capture::updateFrameProperties);
742  connect(m_captureProcess.data(), &CaptureProcess::jobExecutionPreparationStarted, this,
743  &Capture::jobExecutionPreparationStarted);
744  connect(m_captureProcess.data(), &CaptureProcess::sequenceChanged, this, &Capture::sequenceChanged);
745  connect(m_captureProcess.data(), &CaptureProcess::addJob, this, &Capture::addJob);
746  connect(m_captureProcess.data(), &CaptureProcess::createJob, [this](SequenceJob::SequenceJobType jobType)
747  {
748  // report the result back to the process
749  process()->jobCreated(createJob(jobType));
750  });
751  connect(m_captureProcess.data(), &CaptureProcess::jobPrepared, this, &Capture::jobPrepared);
752  connect(m_captureProcess.data(), &CaptureProcess::captureImageStarted, this, &Capture::captureImageStarted);
753  connect(m_captureProcess.data(), &CaptureProcess::captureTarget, this, &Capture::setTargetName);
754  connect(m_captureProcess.data(), &CaptureProcess::downloadingFrame, this, [this]()
755  {
756  captureStatusWidget->setStatus(i18n("Downloading..."), Qt::yellow);
757  });
758  connect(m_captureProcess.data(), &CaptureProcess::captureAborted, this, &Capture::captureAborted);
759  connect(m_captureProcess.data(), &CaptureProcess::captureStopped, this, &Capture::captureStopped);
760  connect(m_captureProcess.data(), &CaptureProcess::updateJobTable, this, &Capture::updateJobTable);
761  connect(m_captureProcess.data(), &CaptureProcess::abortFocus, this, &Capture::abortFocus);
762  connect(m_captureProcess.data(), &CaptureProcess::updateMeridianFlipStage, this, &Capture::updateMeridianFlipStage);
763  connect(m_captureProcess.data(), &CaptureProcess::darkFrameCompleted, this, &Capture::imageCapturingCompleted);
764  connect(m_captureProcess.data(), &CaptureProcess::newLog, this, &Capture::appendLogText);
765  connect(m_captureProcess.data(), &CaptureProcess::jobStarting, this, &Capture::jobStarting);
766  connect(m_captureProcess.data(), &CaptureProcess::captureRunning, this, &Capture::captureRunning);
767  connect(m_captureProcess.data(), &CaptureProcess::stopCapture, this, &Capture::stop);
768  connect(m_captureProcess.data(), &CaptureProcess::suspendGuiding, this, &Capture::suspendGuiding);
769  connect(m_captureProcess.data(), &CaptureProcess::resumeGuiding, this, &Capture::resumeGuiding);
770  connect(m_captureProcess.data(), &CaptureProcess::driverTimedout, this, &Capture::driverTimedout);
771  connect(m_captureProcess.data(), &CaptureProcess::rotatorReverseToggled, this, &Capture::setRotatorReversed);
772  // connections between state machine and device adaptor
773  connect(m_captureModuleState.data(), &CaptureModuleState::newFilterPosition,
774  m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::setFilterPosition);
775  connect(m_captureModuleState.data(), &CaptureModuleState::abortFastExposure,
776  m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::abortFastExposure);
777  connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::pierSideChanged,
778  m_captureModuleState.data(), &CaptureModuleState::setPierSide);
779  connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::newFilterWheel, this, &Capture::setFilterWheel);
780  connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::CameraConnected, this, [this](bool connected)
781  {
782  CCDFWGroup->setEnabled(connected);
783  sequenceBox->setEnabled(connected);
784  for (auto &oneChild : sequenceControlsButtonGroup->buttons())
785  oneChild->setEnabled(connected);
786 
787  if (! connected)
788  {
789  opticalTrainCombo->setEnabled(true);
790  trainLabel->setEnabled(true);
791  }
792  });
793  connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::FilterWheelConnected, this, [this](bool connected)
794  {
795  FilterPosLabel->setEnabled(connected);
796  FilterPosCombo->setEnabled(connected);
797  filterManagerB->setEnabled(connected);
798  });
799  connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::newRotator, this, &Capture::setRotator);
800 
801  setupOpticalTrainManager();
802 
803  // Generate Meridian Flip State
804  getMeridianFlipState();
805 
806  //Update the filename preview
807  placeholderFormatT->setText(Options::placeholderFormat());
808  connect(placeholderFormatT, &QLineEdit::textChanged, this, [this]()
809  {
810  Options::setPlaceholderFormat(placeholderFormatT->text());
811  generatePreviewFilename();
812  });
813  connect(formatSuffixN, QOverload<int>::of(&QSpinBox::valueChanged), this, &Capture::generatePreviewFilename);
814  connect(captureExposureN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
815  &Capture::generatePreviewFilename);
816  connect(targetNameT, &QLineEdit::textEdited, this, [ = ]()
817  {
818  generatePreviewFilename();
819  qCDebug(KSTARS_EKOS_CAPTURE) << "Changed target to" << targetNameT->text() << "because of user edit";
820  });
821  connect(captureTypeS, &QComboBox::currentTextChanged, this, &Capture::generatePreviewFilename);
822 
823  connect(exposureCalcB, &QPushButton::clicked, this, &Capture::openExposureCalculatorDialog);
824 
825 }
826 
827 Capture::~Capture()
828 {
829  qDeleteAll(state()->allJobs());
830  state()->allJobs().clear();
831 }
832 
833 void Capture::updateHFRCheckAlgo()
834 {
835  // Threshold % is not relevant for FIXED HFR do disable the field
836  const bool threshold = (m_LimitsUI->limitFocusHFRAlgorithm->currentIndex() != HFR_CHECK_FIXED);
837  m_LimitsUI->limitFocusHFRThresholdPercentage->setEnabled(threshold);
838  m_LimitsUI->limitFocusHFRThresholdLabel->setEnabled(threshold);
839  m_LimitsUI->limitFocusHFRPercentLabel->setEnabled(threshold);
840  state()->updateHFRThreshold();
841 }
842 
843 bool Capture::updateCamera()
844 {
845  auto isConnected = activeCamera() && activeCamera()->isConnected();
846  CCDFWGroup->setEnabled(isConnected);
847  sequenceBox->setEnabled(isConnected);
848  for (auto &oneChild : sequenceControlsButtonGroup->buttons())
849  oneChild->setEnabled(isConnected);
850 
851  QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain);
852 
853  if (activeCamera() && trainID.isValid())
854  {
855  opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(activeCamera()->getDeviceName(), currentScope()["name"].toString()));
856 
857  cameraLabel->setText(activeCamera()->getDeviceName());
858  }
859  else
860  {
861  cameraLabel->clear();
862  return false;
863  }
864 
865  if (devices()->filterWheel())
866  process()->updateFilterInfo();
867 
868  process()->checkCamera();
869 
870  emit settingsUpdated(getPresetSettings());
871 
872  return true;
873 }
874 
875 
876 
877 void Capture::setFilterWheel(QString name)
878 {
879  // Should not happen
880  if (m_standAlone)
881  return;
882 
883  if (devices()->filterWheel() && devices()->filterWheel()->getDeviceName() == name)
884  {
885  refreshFilterSettings();
886  return;
887  }
888 
889  auto isConnected = devices()->filterWheel() && devices()->filterWheel()->isConnected();
890  FilterPosLabel->setEnabled(isConnected);
891  FilterPosCombo->setEnabled(isConnected);
892  filterManagerB->setEnabled(isConnected);
893 
894  refreshFilterSettings();
895 
896  if (devices()->filterWheel())
897  emit settingsUpdated(getPresetSettings());
898 }
899 
900 bool Capture::setDome(ISD::Dome *device)
901 {
902  return m_captureProcess->setDome(device);
903 }
904 
905 void Capture::setRotator(QString name)
906 {
907  ISD::Rotator *Rotator = devices()->rotator();
908  // clear old rotator
909  rotatorB->setEnabled(false);
910  if (Rotator && !m_RotatorControlPanel.isNull())
911  m_RotatorControlPanel->close();
912 
913  // set new rotator
914  if (!name.isEmpty()) // start real rotator
915  {
916  Manager::Instance()->getRotatorController(name, m_RotatorControlPanel);
917  m_RotatorControlPanel->initRotator(opticalTrainCombo->currentText(), m_captureDeviceAdaptor.data(), Rotator);
918  connect(rotatorB, &QPushButton::clicked, this, [this]()
919  {
920  m_RotatorControlPanel->show();
921  m_RotatorControlPanel->raise();
922  });
923  rotatorB->setEnabled(true);
924  }
925  else if (Options::astrometryUseRotator()) // start at least rotatorutils for "manual rotator"
926  {
927  RotatorUtils::Instance()->initRotatorUtils(opticalTrainCombo->currentText());
928  }
929 }
930 
931 void Capture::pause()
932 {
933  process()->pauseCapturing();
934  updateStartButtons(false, true);
935 }
936 
937 void Capture::toggleSequence()
938 {
939  const CaptureState capturestate = state()->getCaptureState();
940  if (capturestate == CAPTURE_PAUSE_PLANNED || capturestate == CAPTURE_PAUSED)
941  updateStartButtons(true, false);
942 
943  process()->toggleSequence();
944 }
945 
946 void Capture::jobStarting()
947 {
948  if (m_LimitsUI->limitFocusHFRS->isChecked() && state()->getRefocusState()->isAutoFocusReady() == false)
949  appendLogText(i18n("Warning: in-sequence focusing is selected but autofocus process was not started."));
950  if (m_LimitsUI->limitFocusDeltaTS->isChecked() && state()->getRefocusState()->isAutoFocusReady() == false)
951  appendLogText(i18n("Warning: temperature delta check is selected but autofocus process was not started."));
952 
953  updateStartButtons(true, false);
954 }
955 
956 void Capture::registerNewModule(const QString &name)
957 {
958  if (m_standAlone)
959  return;
960  if (name == "Mount" && mountInterface == nullptr)
961  {
962  qCDebug(KSTARS_EKOS_CAPTURE) << "Registering new Module (" << name << ")";
963  mountInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos/Mount",
964  "org.kde.kstars.Ekos.Mount", QDBusConnection::sessionBus(), this);
965 
966  }
967 }
968 
969 QString Capture::camera()
970 {
971  if (devices()->getActiveCamera())
972  return devices()->getActiveCamera()->getDeviceName();
973 
974  return QString();
975 }
976 
977 void Capture::refreshCameraSettings()
978 {
979  // Make sure we have a valid chip and valid base device.
980  // Make sure we are not in capture process.
981  auto camera = activeCamera();
982  auto targetChip = devices()->getActiveChip();
983  // If camera is restarted, try again in one second
984  if (!m_standAlone && (!camera || !targetChip || !targetChip->getCCD() || targetChip->isCapturing()))
985  {
986  QTimer::singleShot(1000, this, &Capture::refreshCameraSettings);
987  return;
988  }
989 
990  if (camera->hasCoolerControl())
991  {
992  coolerOnB->setEnabled(true);
993  coolerOffB->setEnabled(true);
994  coolerOnB->setChecked(camera->isCoolerOn());
995  coolerOffB->setChecked(!camera->isCoolerOn());
996  }
997  else
998  {
999  coolerOnB->setEnabled(false);
1000  coolerOnB->setChecked(false);
1001  coolerOffB->setEnabled(false);
1002  coolerOffB->setChecked(false);
1003  }
1004 
1005  updateFrameProperties();
1006 
1007  updateCaptureFormats();
1008 
1009  customPropertiesDialog->setCCD(camera);
1010 
1011  liveVideoB->setEnabled(camera->hasVideoStream());
1012  if (camera->hasVideoStream())
1013  setVideoStreamEnabled(camera->isStreamingEnabled());
1014  else
1015  liveVideoB->setIcon(QIcon::fromTheme("camera-off"));
1016 
1017  connect(camera, &ISD::Camera::propertyUpdated, this, &Capture::processCameraNumber, Qt::UniqueConnection);
1018  connect(camera, &ISD::Camera::coolerToggled, this, &Capture::setCoolerToggled, Qt::UniqueConnection);
1019  connect(camera, &ISD::Camera::videoStreamToggled, this, &Capture::setVideoStreamEnabled, Qt::UniqueConnection);
1020  connect(camera, &ISD::Camera::ready, this, &Capture::ready, Qt::UniqueConnection);
1021  connect(camera, &ISD::Camera::error, m_captureProcess.data(), &CaptureProcess::processCaptureError,
1023 
1024  syncCameraInfo();
1025 
1026  // update values received by the device adaptor
1027  // connect(activeCamera(), &ISD::Camera::newTemperatureValue, this, &Capture::updateCCDTemperature, Qt::UniqueConnection);
1028 
1029  DarkLibrary::Instance()->checkCamera();
1030 }
1031 
1032 void Capture::updateCaptureFormats()
1033 {
1034  QStringList frameTypes = process()->frameTypes();
1035 
1036  captureTypeS->clear();
1037 
1038  if (frameTypes.isEmpty())
1039  captureTypeS->setEnabled(false);
1040  else
1041  {
1042  captureTypeS->setEnabled(true);
1043  captureTypeS->addItems(frameTypes);
1044  Options::setCaptureStandAloneTypes(standAloneEncode(frameTypes));
1045  captureTypeS->setCurrentIndex(devices()->getActiveChip()->getFrameType());
1046  }
1047 
1048  // Capture Format
1049  captureFormatS->blockSignals(true);
1050  captureFormatS->clear();
1051  captureFormatS->addItems(activeCamera()->getCaptureFormats());
1052  Options::setCaptureStandAloneFormats(standAloneEncode(activeCamera()->getCaptureFormats()));
1053  captureFormatS->setCurrentText(activeCamera()->getCaptureFormat());
1054  captureFormatS->blockSignals(false);
1055 
1056  // Encoding format
1057  captureEncodingS->blockSignals(true);
1058  captureEncodingS->clear();
1059  captureEncodingS->addItems(activeCamera()->getEncodingFormats());
1060  captureEncodingS->setCurrentText(activeCamera()->getEncodingFormat());
1061  Options::setCaptureStandAloneEncodings(standAloneEncode(activeCamera()->getEncodingFormats()));
1062  captureEncodingS->blockSignals(false);
1063 
1064  Options::setCaptureStandAloneTimestamp(KStarsData::Instance()->lt().toString("yyyy-MM-dd hh:mm"));
1065 }
1066 
1067 void Capture::syncCameraInfo()
1068 {
1069  if (!activeCamera())
1070  return;
1071 
1072  if (activeCamera()->hasCooler())
1073  {
1074  cameraTemperatureS->setEnabled(true);
1075  cameraTemperatureN->setEnabled(true);
1076 
1077  if (activeCamera()->getPermission("CCD_TEMPERATURE") != IP_RO)
1078  {
1079  double min, max, step;
1080  setTemperatureB->setEnabled(true);
1081  cameraTemperatureN->setReadOnly(false);
1082  cameraTemperatureS->setEnabled(true);
1083  temperatureRegulationB->setEnabled(true);
1084  activeCamera()->getMinMaxStep("CCD_TEMPERATURE", "CCD_TEMPERATURE_VALUE", &min, &max, &step);
1085  cameraTemperatureN->setMinimum(min);
1086  cameraTemperatureN->setMaximum(max);
1087  cameraTemperatureN->setSingleStep(1);
1088  bool isChecked = activeCamera()->getDriverInfo()->getAuxInfo().value(QString("%1_TC").arg(activeCamera()->getDeviceName()),
1089  false).toBool();
1090  cameraTemperatureS->setChecked(isChecked);
1091  }
1092  else
1093  {
1094  setTemperatureB->setEnabled(false);
1095  cameraTemperatureN->setReadOnly(true);
1096  cameraTemperatureS->setEnabled(false);
1097  cameraTemperatureS->setChecked(false);
1098  temperatureRegulationB->setEnabled(false);
1099  }
1100 
1101  double temperature = 0;
1102  if (activeCamera()->getTemperature(&temperature))
1103  {
1104  temperatureOUT->setText(QString("%L1").arg(temperature, 0, 'f', 2));
1105  if (cameraTemperatureN->cleanText().isEmpty())
1106  cameraTemperatureN->setValue(temperature);
1107  }
1108  }
1109  else
1110  {
1111  cameraTemperatureS->setEnabled(false);
1112  cameraTemperatureN->setEnabled(false);
1113  temperatureRegulationB->setEnabled(false);
1114  cameraTemperatureN->clear();
1115  temperatureOUT->clear();
1116  setTemperatureB->setEnabled(false);
1117  }
1118 
1119  auto isoList = devices()->getActiveChip()->getISOList();
1120  captureISOS->blockSignals(true);
1121  captureISOS->setEnabled(false);
1122  captureISOS->clear();
1123 
1124  // No ISO range available
1125  if (isoList.isEmpty())
1126  {
1127  captureISOS->setEnabled(false);
1128  Options::setCaptureStandAloneISOs("");
1129  }
1130  else
1131  {
1132  captureISOS->setEnabled(true);
1133  captureISOS->addItems(isoList);
1134  captureISOS->setCurrentIndex(devices()->getActiveChip()->getISOIndex());
1135  Options::setCaptureStandAloneISOs(standAloneEncode(isoList));
1136  Options::setCaptureStandAloneISOIndex(devices()->getActiveChip()->getISOIndex());
1137 
1138  uint16_t w, h;
1139  uint8_t bbp {8};
1140  double pixelX = 0, pixelY = 0;
1141  bool rc = devices()->getActiveChip()->getImageInfo(w, h, pixelX, pixelY, bbp);
1142  bool isModelInDB = state()->isModelinDSLRInfo(QString(activeCamera()->getDeviceName()));
1143  // If rc == true, then the property has been defined by the driver already
1144  // Only then we check if the pixels are zero
1145  if (rc == true && (pixelX == 0.0 || pixelY == 0.0 || isModelInDB == false))
1146  {
1147  // If model is already in database, no need to show dialog
1148  // The zeros above are the initial packets so we can safely ignore them
1149  if (isModelInDB == false)
1150  {
1151  createDSLRDialog();
1152  }
1153  else
1154  {
1155  QString model = QString(activeCamera()->getDeviceName());
1156  process()->syncDSLRToTargetChip(model);
1157  }
1158  }
1159  }
1160  captureISOS->blockSignals(false);
1161 
1162  // Gain Check
1163  if (activeCamera()->hasGain())
1164  {
1165  double min, max, step, value, targetCustomGain;
1166  activeCamera()->getGainMinMaxStep(&min, &max, &step);
1167 
1168  // Allow the possibility of no gain value at all.
1169  GainSpinSpecialValue = min - step;
1170  captureGainN->setRange(GainSpinSpecialValue, max);
1171  captureGainN->setSpecialValueText(i18n("--"));
1172  captureGainN->setEnabled(true);
1173  captureGainN->setSingleStep(step);
1174  activeCamera()->getGain(&value);
1175  currentGainLabel->setText(QString::number(value, 'f', 0));
1176 
1177  targetCustomGain = getGain();
1178 
1179  // Set the custom gain if we have one
1180  // otherwise it will not have an effect.
1181  if (targetCustomGain > 0)
1182  captureGainN->setValue(targetCustomGain);
1183  else
1184  captureGainN->setValue(GainSpinSpecialValue);
1185 
1186  captureGainN->setReadOnly(activeCamera()->getGainPermission() == IP_RO);
1187 
1188  connect(captureGainN, &QDoubleSpinBox::editingFinished, this, [this]()
1189  {
1190  if (captureGainN->value() != GainSpinSpecialValue)
1191  setGain(captureGainN->value());
1192  else
1193  setGain(-1);
1194  });
1195  }
1196  else
1197  {
1198  captureGainN->setEnabled(false);
1199  currentGainLabel->clear();
1200  }
1201 
1202  // Offset checks
1203  if (activeCamera()->hasOffset())
1204  {
1205  double min, max, step, value, targetCustomOffset;
1206  activeCamera()->getOffsetMinMaxStep(&min, &max, &step);
1207 
1208  // Allow the possibility of no Offset value at all.
1209  OffsetSpinSpecialValue = min - step;
1210  captureOffsetN->setRange(OffsetSpinSpecialValue, max);
1211  captureOffsetN->setSpecialValueText(i18n("--"));
1212  captureOffsetN->setEnabled(true);
1213  captureOffsetN->setSingleStep(step);
1214  activeCamera()->getOffset(&value);
1215  currentOffsetLabel->setText(QString::number(value, 'f', 0));
1216 
1217  targetCustomOffset = getOffset();
1218 
1219  // Set the custom Offset if we have one
1220  // otherwise it will not have an effect.
1221  if (targetCustomOffset > 0)
1222  captureOffsetN->setValue(targetCustomOffset);
1223  else
1224  captureOffsetN->setValue(OffsetSpinSpecialValue);
1225 
1226  captureOffsetN->setReadOnly(activeCamera()->getOffsetPermission() == IP_RO);
1227 
1228  connect(captureOffsetN, &QDoubleSpinBox::editingFinished, this, [this]()
1229  {
1230  if (captureOffsetN->value() != OffsetSpinSpecialValue)
1231  setOffset(captureOffsetN->value());
1232  else
1233  setOffset(-1);
1234  });
1235  }
1236  else
1237  {
1238  captureOffsetN->setEnabled(false);
1239  currentOffsetLabel->clear();
1240  }
1241 }
1242 
1243 void Capture::setGuideChip(ISD::CameraChip * guideChip)
1244 {
1245  // We should suspend guide in two scenarios:
1246  // 1. If guide chip is within the primary CCD, then we cannot download any data from guide chip while primary CCD is downloading.
1247  // 2. If we have two CCDs running from ONE driver (Multiple-Devices-Per-Driver mpdp is true). Same issue as above, only one download
1248  // at a time.
1249  // After primary CCD download is complete, we resume guiding.
1250  if (!devices()->getActiveCamera())
1251  return;
1252 
1253  state()->setSuspendGuidingOnDownload((devices()->getActiveCamera()->getChip(
1254  ISD::CameraChip::GUIDE_CCD) == guideChip) ||
1255  (guideChip->getCCD() == devices()->getActiveCamera() &&
1256  devices()->getActiveCamera()->getDriverInfo()->getAuxInfo().value("mdpd", false).toBool()));
1257 }
1258 
1259 void Capture::resetFrameToZero()
1260 {
1261  captureFrameXN->setMinimum(0);
1262  captureFrameXN->setMaximum(0);
1263  captureFrameXN->setValue(0);
1264 
1265  captureFrameYN->setMinimum(0);
1266  captureFrameYN->setMaximum(0);
1267  captureFrameYN->setValue(0);
1268 
1269  captureFrameWN->setMinimum(0);
1270  captureFrameWN->setMaximum(0);
1271  captureFrameWN->setValue(0);
1272 
1273  captureFrameHN->setMinimum(0);
1274  captureFrameHN->setMaximum(0);
1275  captureFrameHN->setValue(0);
1276 }
1277 
1278 void Capture::updateFrameProperties(int reset)
1279 {
1280  if (!devices()->getActiveCamera())
1281  return;
1282 
1283  int binx = 1, biny = 1;
1284  double min, max, step;
1285  int xstep = 0, ystep = 0;
1286 
1287  QString frameProp = state()->useGuideHead() ? QString("GUIDER_FRAME") : QString("CCD_FRAME");
1288  QString exposureProp = state()->useGuideHead() ? QString("GUIDER_EXPOSURE") : QString("CCD_EXPOSURE");
1289  QString exposureElem = state()->useGuideHead() ? QString("GUIDER_EXPOSURE_VALUE") :
1290  QString("CCD_EXPOSURE_VALUE");
1291  devices()->setActiveChip(state()->useGuideHead() ?
1292  devices()->getActiveCamera()->getChip(
1293  ISD::CameraChip::GUIDE_CCD) :
1294  devices()->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD));
1295 
1296  captureFrameWN->setEnabled(devices()->getActiveChip()->canSubframe());
1297  captureFrameHN->setEnabled(devices()->getActiveChip()->canSubframe());
1298  captureFrameXN->setEnabled(devices()->getActiveChip()->canSubframe());
1299  captureFrameYN->setEnabled(devices()->getActiveChip()->canSubframe());
1300 
1301  captureBinHN->setEnabled(devices()->getActiveChip()->canBin());
1302  captureBinVN->setEnabled(devices()->getActiveChip()->canBin());
1303 
1304  QList<double> exposureValues;
1305  exposureValues << 0.01 << 0.02 << 0.05 << 0.1 << 0.2 << 0.25 << 0.5 << 1 << 1.5 << 2 << 2.5 << 3 << 5 << 6 << 7 << 8 << 9 <<
1306  10 << 20 << 30 << 40 << 50 << 60 << 120 << 180 << 300 << 600 << 900 << 1200 << 1800;
1307 
1308  if (devices()->getActiveCamera()->getMinMaxStep(exposureProp, exposureElem, &min, &max, &step))
1309  {
1310  if (min < 0.001)
1311  captureExposureN->setDecimals(6);
1312  else
1313  captureExposureN->setDecimals(3);
1314  for(int i = 0; i < exposureValues.count(); i++)
1315  {
1316  double value = exposureValues.at(i);
1317  if(value < min || value > max)
1318  {
1319  exposureValues.removeAt(i);
1320  i--; //So we don't skip one
1321  }
1322  }
1323 
1324  exposureValues.prepend(min);
1325  exposureValues.append(max);
1326  }
1327 
1328  captureExposureN->setRecommendedValues(exposureValues);
1329  state()->setExposureRange(exposureValues.first(), exposureValues.last());
1330 
1331  if (devices()->getActiveCamera()->getMinMaxStep(frameProp, "WIDTH", &min, &max, &step))
1332  {
1333  if (min >= max)
1334  {
1335  resetFrameToZero();
1336  return;
1337  }
1338 
1339  if (step == 0.0)
1340  xstep = static_cast<int>(max * 0.05);
1341  else
1342  xstep = static_cast<int>(step);
1343 
1344  if (min >= 0 && max > 0)
1345  {
1346  captureFrameWN->setMinimum(static_cast<int>(min));
1347  captureFrameWN->setMaximum(static_cast<int>(max));
1348  captureFrameWN->setSingleStep(xstep);
1349  }
1350  }
1351  else
1352  return;
1353 
1354  if (devices()->getActiveCamera()->getMinMaxStep(frameProp, "HEIGHT", &min, &max, &step))
1355  {
1356  if (min >= max)
1357  {
1358  resetFrameToZero();
1359  return;
1360  }
1361 
1362  if (step == 0.0)
1363  ystep = static_cast<int>(max * 0.05);
1364  else
1365  ystep = static_cast<int>(step);
1366 
1367  if (min >= 0 && max > 0)
1368  {
1369  captureFrameHN->setMinimum(static_cast<int>(min));
1370  captureFrameHN->setMaximum(static_cast<int>(max));
1371  captureFrameHN->setSingleStep(ystep);
1372  }
1373  }
1374  else
1375  return;
1376 
1377  if (devices()->getActiveCamera()->getMinMaxStep(frameProp, "X", &min, &max, &step))
1378  {
1379  if (min >= max)
1380  {
1381  resetFrameToZero();
1382  return;
1383  }
1384 
1385  if (step == 0.0)
1386  step = xstep;
1387 
1388  if (min >= 0 && max > 0)
1389  {
1390  captureFrameXN->setMinimum(static_cast<int>(min));
1391  captureFrameXN->setMaximum(static_cast<int>(max));
1392  captureFrameXN->setSingleStep(static_cast<int>(step));
1393  }
1394  }
1395  else
1396  return;
1397 
1398  if (devices()->getActiveCamera()->getMinMaxStep(frameProp, "Y", &min, &max, &step))
1399  {
1400  if (min >= max)
1401  {
1402  resetFrameToZero();
1403  return;
1404  }
1405 
1406  if (step == 0.0)
1407  step = ystep;
1408 
1409  if (min >= 0 && max > 0)
1410  {
1411  captureFrameYN->setMinimum(static_cast<int>(min));
1412  captureFrameYN->setMaximum(static_cast<int>(max));
1413  captureFrameYN->setSingleStep(static_cast<int>(step));
1414  }
1415  }
1416  else
1417  return;
1418 
1419  // cull to camera limits, if there are any
1420  if (state()->useGuideHead() == false)
1421  cullToDSLRLimits();
1422 
1423  // Save the sensor's width and height for the stand-alone editor.
1424  Options::setCaptureStandAloneWHGO(
1425  standAloneEncode(
1426  QStringList({QString("%1").arg(captureFrameWN->value()),
1427  QString("%1").arg(captureFrameHN->value()),
1428  QString("%1").arg(devices()->getActiveCamera()->getProperty("CCD_GAIN") ? "CCD_GAIN" : "CCD_CONTROLS"),
1429  QString("%1").arg(devices()->getActiveCamera()->getProperty("CCD_OFFSET") ? "CCD_OFFSET" : "CCD_CONTROLS")})));
1430 
1431  if (reset == 1 || state()->frameSettings().contains(devices()->getActiveChip()) == false)
1432  {
1433  QVariantMap settings;
1434 
1435  settings["x"] = 0;
1436  settings["y"] = 0;
1437  settings["w"] = captureFrameWN->maximum();
1438  settings["h"] = captureFrameHN->maximum();
1439  settings["binx"] = captureBinHN->value();
1440  settings["biny"] = captureBinVN->value();
1441 
1442  state()->frameSettings()[devices()->getActiveChip()] = settings;
1443  }
1444  else if (reset == 2 && state()->frameSettings().contains(devices()->getActiveChip()))
1445  {
1446  QVariantMap settings = state()->frameSettings()[devices()->getActiveChip()];
1447  int x, y, w, h;
1448 
1449  x = settings["x"].toInt();
1450  y = settings["y"].toInt();
1451  w = settings["w"].toInt();
1452  h = settings["h"].toInt();
1453 
1454  // Bound them
1455  x = qBound(captureFrameXN->minimum(), x, captureFrameXN->maximum() - 1);
1456  y = qBound(captureFrameYN->minimum(), y, captureFrameYN->maximum() - 1);
1457  w = qBound(captureFrameWN->minimum(), w, captureFrameWN->maximum());
1458  h = qBound(captureFrameHN->minimum(), h, captureFrameHN->maximum());
1459 
1460  settings["x"] = x;
1461  settings["y"] = y;
1462  settings["w"] = w;
1463  settings["h"] = h;
1464  settings["binx"] = captureBinHN->value();
1465  settings["biny"] = captureBinVN->value();
1466 
1467  state()->frameSettings()[devices()->getActiveChip()] = settings;
1468  }
1469 
1470  if (state()->frameSettings().contains(devices()->getActiveChip()))
1471  {
1472  QVariantMap settings = state()->frameSettings()[devices()->getActiveChip()];
1473  int x = settings["x"].toInt();
1474  int y = settings["y"].toInt();
1475  int w = settings["w"].toInt();
1476  int h = settings["h"].toInt();
1477 
1478  if (devices()->getActiveChip()->canBin())
1479  {
1480  devices()->getActiveChip()->getMaxBin(&binx, &biny);
1481  captureBinHN->setMaximum(binx);
1482  captureBinVN->setMaximum(biny);
1483 
1484  captureBinHN->setValue(settings["binx"].toInt());
1485  captureBinVN->setValue(settings["biny"].toInt());
1486  }
1487  else
1488  {
1489  captureBinHN->setValue(1);
1490  captureBinVN->setValue(1);
1491  }
1492 
1493  if (x >= 0)
1494  captureFrameXN->setValue(x);
1495  if (y >= 0)
1496  captureFrameYN->setValue(y);
1497  if (w > 0)
1498  captureFrameWN->setValue(w);
1499  if (h > 0)
1500  captureFrameHN->setValue(h);
1501  }
1502 }
1503 
1504 void Capture::processCameraNumber(INDI::Property prop)
1505 {
1506  if (devices()->getActiveCamera() == nullptr)
1507  return;
1508 
1509  if ((prop.isNameMatch("CCD_FRAME") && state()->useGuideHead() == false) ||
1510  (prop.isNameMatch("GUIDER_FRAME") && state()->useGuideHead()))
1511  updateFrameProperties();
1512  else if ((prop.isNameMatch("CCD_INFO") && state()->useGuideHead() == false) ||
1513  (prop.isNameMatch("GUIDER_INFO") && state()->useGuideHead()))
1514  updateFrameProperties(1);
1515  else if (prop.isNameMatch("CCD_TRANSFER_FORMAT") || prop.isNameMatch("CCD_CAPTURE_FORMAT"))
1516  updateCaptureFormats();
1517  else if (prop.isNameMatch("CCD_CONTROLS"))
1518  {
1519  auto nvp = prop.getNumber();
1520  auto gain = nvp->findWidgetByName("Gain");
1521  if (gain)
1522  currentGainLabel->setText(QString::number(gain->value, 'f', 0));
1523  auto offset = nvp->findWidgetByName("Offset");
1524  if (offset)
1525  currentOffsetLabel->setText(QString::number(offset->value, 'f', 0));
1526  }
1527  else if (prop.isNameMatch("CCD_GAIN"))
1528  {
1529  auto nvp = prop.getNumber();
1530  currentGainLabel->setText(QString::number(nvp->at(0)->getValue(), 'f', 0));
1531  }
1532  else if (prop.isNameMatch("CCD_OFFSET"))
1533  {
1534  auto nvp = prop.getNumber();
1535  currentOffsetLabel->setText(QString::number(nvp->at(0)->getValue(), 'f', 0));
1536  }
1537 }
1538 
1539 void Capture::syncFrameType(const QString &name)
1540 {
1541  if (!activeCamera() || name != activeCamera()->getDeviceName())
1542  return;
1543 
1544  QStringList frameTypes = process()->frameTypes();
1545 
1546  captureTypeS->clear();
1547 
1548  if (frameTypes.isEmpty())
1549  captureTypeS->setEnabled(false);
1550  else
1551  {
1552  captureTypeS->setEnabled(true);
1553  captureTypeS->addItems(frameTypes);
1554  ISD::CameraChip *tChip = devices()->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD);
1555  captureTypeS->setCurrentIndex(tChip->getFrameType());
1556  }
1557 }
1558 
1559 QString Capture::filterWheel()
1560 {
1561  if (devices()->filterWheel())
1562  return devices()->filterWheel()->getDeviceName();
1563 
1564  return QString();
1565 }
1566 
1567 bool Capture::setFilter(const QString &filter)
1568 {
1569  if (devices()->filterWheel())
1570  {
1571  FilterPosCombo->setCurrentText(filter);
1572  return true;
1573  }
1574 
1575  return false;
1576 }
1577 
1578 QString Capture::filter()
1579 {
1580  return FilterPosCombo->currentText();
1581 }
1582 
1583 void Capture::updateCurrentFilterPosition()
1584 {
1585  const QString currentFilterText = FilterPosCombo->itemText(m_FilterManager->getFilterPosition() - 1);
1586  state()->setCurrentFilterPosition(m_FilterManager->getFilterPosition(),
1587  currentFilterText,
1588  m_FilterManager->getFilterLock(currentFilterText));
1589 }
1590 
1591 void Capture::refreshFilterSettings()
1592 {
1593  FilterPosCombo->clear();
1594 
1595  if (!devices()->filterWheel())
1596  {
1597  FilterPosLabel->setEnabled(false);
1598  FilterPosCombo->setEnabled(false);
1599  filterEditB->setEnabled(false);
1600 
1601  devices()->setFilterManager(m_FilterManager);
1602  return;
1603  }
1604 
1605  FilterPosLabel->setEnabled(true);
1606  FilterPosCombo->setEnabled(true);
1607  filterEditB->setEnabled(true);
1608 
1609  setupFilterManager();
1610 
1611  process()->updateFilterInfo();
1612 
1613  FilterPosCombo->addItems(process()->filterLabels());
1614  Options::setCaptureStandAloneFilters(standAloneEncode(process()->filterLabels()));
1615 
1616  updateCurrentFilterPosition();
1617 
1618  filterEditB->setEnabled(state()->getCurrentFilterPosition() > 0);
1619 
1620  FilterPosCombo->setCurrentIndex(state()->getCurrentFilterPosition() - 1);
1621 }
1622 
1623 void Capture::processingFITSfinished(bool success)
1624 {
1625  // do nothing in case of failure
1626  if (success == false)
1627  return;
1628 
1629  // If this is a preview job, make sure to enable preview button after
1630  if (devices()->getActiveCamera()
1631  && devices()->getActiveCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL)
1632  previewB->setEnabled(true);
1633 
1634  imageCapturingCompleted();
1635 }
1636 
1637 void Capture::imageCapturingCompleted()
1638 {
1639  SequenceJob *thejob = activeJob();
1640 
1641  if (!thejob)
1642  return;
1643 
1644  // In case we're framing, let's return quickly to continue the process.
1645  if (state()->isLooping())
1646  {
1647  captureStatusWidget->setStatus(i18n("Framing..."), Qt::darkGreen);
1648  return;
1649  }
1650 
1651  // If fast exposure is off, disconnect exposure progress
1652  // otherwise, keep it going since it fires off from driver continuous capture process.
1653  if (devices()->getActiveCamera()->isFastExposureEnabled() == false)
1654  DarkLibrary::Instance()->disconnect(this);
1655 
1656  // Do not display notifications for very short captures
1657  if (thejob->getCoreProperty(SequenceJob::SJ_Exposure).toDouble() >= 1)
1658  KSNotification::event(QLatin1String("EkosCaptureImageReceived"), i18n("Captured image received"),
1659  KSNotification::Capture);
1660 
1661  // If it was initially set as pure preview job and NOT as preview for calibration
1662  if (thejob->jobType() == SequenceJob::JOBTYPE_PREVIEW)
1663  return;
1664 
1665  /* The image progress has now one more capture */
1666  imgProgress->setValue(thejob->getCompleted());
1667 }
1668 
1669 void Capture::captureStopped()
1670 {
1671  imgProgress->reset();
1672  imgProgress->setEnabled(false);
1673 
1674  frameRemainingTime->setText("--:--:--");
1675  jobRemainingTime->setText("--:--:--");
1676  frameInfoLabel->setText(i18n("Expose (-/-):"));
1677 
1678  // stopping to CAPTURE_IDLE means that capturing will continue automatically
1679  auto captureState = state()->getCaptureState();
1680  if (captureState == CAPTURE_ABORTED || captureState == CAPTURE_SUSPENDED || captureState == CAPTURE_COMPLETE)
1681  updateStartButtons(false, false);
1682 }
1683 
1684 void Capture::updateTargetDistance(double targetDiff)
1685 {
1686  // ensure that the drift is visible
1687  targetDriftLabel->setVisible(true);
1688  targetDrift->setVisible(true);
1689  targetDriftUnit->setVisible(true);
1690  // update the drift value
1691  targetDrift->setText(QString("%L1").arg(targetDiff, 0, 'd', 1));
1692 }
1693 
1694 void Capture::captureImageStarted()
1695 {
1696  if (devices()->filterWheel() != nullptr)
1697  {
1698  // JM 2021.08.23 Call filter info to set the active filter wheel in the camera driver
1699  // so that it may snoop on the active filter
1700  process()->updateFilterInfo();
1701  updateCurrentFilterPosition();
1702  }
1703 
1704  // necessary since the status widget doesn't store the calibration stage
1705  if (activeJob()->getCalibrationStage() == SequenceJobState::CAL_CALIBRATION)
1706  captureStatusWidget->setStatus(i18n("Calibrating..."), Qt::yellow);
1707 }
1708 
1709 namespace
1710 {
1711 QString frameLabel(CCDFrameType type, const QString &filter)
1712 {
1713  switch(type)
1714  {
1715  case FRAME_LIGHT:
1716  if (filter.size() == 0)
1717  return CCDFrameTypeNames[type];
1718  else
1719  return filter;
1720  break;
1721  case FRAME_FLAT:
1722  if (filter.size() == 0)
1723  return CCDFrameTypeNames[type];
1724  else
1725  return QString("%1 %2").arg(filter).arg(CCDFrameTypeNames[type]);
1726  break;
1727  case FRAME_BIAS:
1728  case FRAME_DARK:
1729  case FRAME_NONE:
1730  default:
1731  return CCDFrameTypeNames[type];
1732  }
1733 }
1734 }
1735 
1736 void Capture::captureRunning()
1737 {
1738  emit captureStarting(activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(),
1739  activeJob()->getCoreProperty(SequenceJob::SJ_Filter).toString());
1740  appendLogText(i18n("Capturing %1-second %2 image...",
1741  QString("%L1").arg(activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(), 0, 'f', 3),
1742  activeJob()->getCoreProperty(SequenceJob::SJ_Filter).toString()));
1743  frameInfoLabel->setText(QString("%1 (%L3/%L4):").arg(frameLabel(activeJob()->getFrameType(),
1744  activeJob()->getCoreProperty(SequenceJob::SJ_Filter).toString()))
1745  .arg(activeJob()->getCompleted()).arg(activeJob()->getCoreProperty(
1746  SequenceJob::SJ_Count).toInt()));
1747  // ensure that the download time label is visible
1748  avgDownloadTime->setVisible(true);
1749  avgDownloadLabel->setVisible(true);
1750  secLabel->setVisible(true);
1751  // show estimated download time
1752  avgDownloadTime->setText(QString("%L1").arg(state()->averageDownloadTime(), 0, 'd', 2));
1753 }
1754 
1755 void Capture::appendLogText(const QString &text)
1756 {
1757  m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2",
1758  KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text));
1759 
1760  qCInfo(KSTARS_EKOS_CAPTURE) << text;
1761 
1762  emit newLog(text);
1763 }
1764 
1765 void Capture::clearLog()
1766 {
1767  m_LogText.clear();
1768  emit newLog(QString());
1769 }
1770 
1771 void Capture::updateDownloadProgress(double downloadTimeLeft)
1772 {
1773  frameRemainingTime->setText(state()->imageCountDown().toString("hh:mm:ss"));
1774  emit newDownloadProgress(downloadTimeLeft);
1775 }
1776 
1777 void Capture::updateCaptureCountDown(int deltaMillis)
1778 {
1779  state()->imageCountDownAddMSecs(deltaMillis);
1780  state()->sequenceCountDownAddMSecs(deltaMillis);
1781  frameRemainingTime->setText(state()->imageCountDown().toString("hh:mm:ss"));
1782  jobRemainingTime->setText(state()->sequenceCountDown().toString("hh:mm:ss"));
1783 }
1784 
1785 void Capture::updateCCDTemperature(double value)
1786 {
1787  if (cameraTemperatureS->isEnabled() == false && devices()->getActiveCamera())
1788  {
1789  if (devices()->getActiveCamera()->getPermission("CCD_TEMPERATURE") != IP_RO)
1790  process()->checkCamera();
1791  }
1792 
1793  temperatureOUT->setText(QString("%L1").arg(value, 0, 'f', 2));
1794 
1795  if (cameraTemperatureN->cleanText().isEmpty())
1796  cameraTemperatureN->setValue(value);
1797 }
1798 
1799 void Capture::updateRotatorAngle(double value)
1800 {
1801  IPState RState = devices()->rotator()->absoluteAngleState();
1802  if (RState == IPS_OK)
1803  m_RotatorControlPanel->updateRotator(value);
1804  else
1805  m_RotatorControlPanel->updateGauge(value);
1806 }
1807 
1808 void Capture::addJob(SequenceJob *job)
1809 {
1810  // create a new row
1811  createNewJobTableRow(job);
1812 }
1813 
1814 SequenceJob *Capture::createJob(SequenceJob::SequenceJobType jobtype, FilenamePreviewType filenamePreview)
1815 {
1816  SequenceJob *job = new SequenceJob(devices(), state(), jobtype);
1817 
1818  updateJobFromUI(job, filenamePreview);
1819 
1820  // Nothing more to do if preview or for placeholder calculations
1821  if (jobtype == SequenceJob::JOBTYPE_PREVIEW || filenamePreview != NOT_PREVIEW)
1822  return job;
1823 
1824  // check if the upload paths are correct
1825  if (checkUploadPaths(filenamePreview) == false)
1826  return nullptr;
1827 
1828  // all other jobs will be added to the job list
1829  state()->allJobs().append(job);
1830 
1831  // create a new row
1832  createNewJobTableRow(job);
1833 
1834  return job;
1835 }
1836 
1837 void Ekos::Capture::createNewJobTableRow(SequenceJob *job)
1838 {
1839  int currentRow = queueTable->rowCount();
1840  queueTable->insertRow(currentRow);
1841 
1842  // create job table widgets
1843  QTableWidgetItem *status = new QTableWidgetItem();
1846 
1847  QTableWidgetItem *filter = new QTableWidgetItem();
1848  filter->setTextAlignment(Qt::AlignHCenter);
1849  filter->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1850 
1851  QTableWidgetItem *count = new QTableWidgetItem();
1854 
1855  QTableWidgetItem *exp = new QTableWidgetItem();
1858 
1859  QTableWidgetItem *type = new QTableWidgetItem();
1860  type->setTextAlignment(Qt::AlignHCenter);
1861  type->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1862 
1863  QTableWidgetItem *bin = new QTableWidgetItem();
1864  bin->setTextAlignment(Qt::AlignHCenter);
1865  bin->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1866 
1867  QTableWidgetItem *iso = new QTableWidgetItem();
1870 
1871  QTableWidgetItem *offset = new QTableWidgetItem();
1874 
1875  // add the widgets to the table
1876  queueTable->setItem(currentRow, JOBTABLE_COL_STATUS, status);
1877  queueTable->setItem(currentRow, JOBTABLE_COL_FILTER, filter);
1878  queueTable->setItem(currentRow, JOBTABLE_COL_COUNTS, count);
1879  queueTable->setItem(currentRow, JOBTABLE_COL_EXP, exp);
1880  queueTable->setItem(currentRow, JOBTABLE_COL_TYPE, type);
1881  queueTable->setItem(currentRow, JOBTABLE_COL_BINNING, bin);
1882  queueTable->setItem(currentRow, JOBTABLE_COL_ISO, iso);
1883  queueTable->setItem(currentRow, JOBTABLE_COL_OFFSET, offset);
1884 
1885  // full update to the job table row
1886  updateJobTable(job, true);
1887 
1888  // Create a new JSON object. Needs to be called after the new row has been filled
1889  QJsonObject jsonJob = createJsonJob(job, currentRow);
1890  state()->getSequence().append(jsonJob);
1891  emit sequenceChanged(state()->getSequence());
1892 
1893  removeFromQueueB->setEnabled(true);
1894 }
1895 
1896 
1898 {
1899  if (queueTable->currentRow() < 0)
1900  qCWarning(KSTARS_EKOS_CAPTURE()) << "Editing finished, but no row selected!";
1901 
1902  int currentRow = queueTable->currentRow();
1903  SequenceJob *job = state()->allJobs().at(currentRow);
1904  updateJobFromUI(job);
1905 
1906  // full update to the job table row
1907  updateJobTable(job, true);
1908 
1909  // Update the JSON object for the current row. Needs to be called after the new row has been filled
1910  QJsonObject jsonJob = createJsonJob(job, currentRow);
1911  state()->getSequence().replace(currentRow, jsonJob);
1912  emit sequenceChanged(state()->getSequence());
1913 
1914  resetJobEdit();
1915  appendLogText(i18n("Job #%1 changes applied.", currentRow + 1));
1916 }
1917 
1918 void Capture::removeJobFromQueue()
1919 {
1920  int currentRow = queueTable->currentRow();
1921 
1922  if (currentRow < 0)
1923  currentRow = queueTable->rowCount() - 1;
1924 
1925  removeJob(currentRow);
1926 
1927  // update selection
1928  if (queueTable->rowCount() == 0)
1929  return;
1930 
1931  if (currentRow > queueTable->rowCount())
1932  queueTable->selectRow(queueTable->rowCount() - 1);
1933  else
1934  queueTable->selectRow(currentRow);
1935 }
1936 
1937 bool Capture::removeJob(int index)
1938 {
1939  if (state()->getCaptureState() != CAPTURE_IDLE && state()->getCaptureState() != CAPTURE_ABORTED
1940  && state()->getCaptureState() != CAPTURE_COMPLETE)
1941  return false;
1942 
1943  if (m_JobUnderEdit)
1944  {
1945  resetJobEdit(true);
1946  return false;
1947  }
1948 
1949  if (index < 0 || index >= state()->allJobs().count())
1950  return false;
1951 
1952  queueTable->removeRow(index);
1953  QJsonArray seqArray = state()->getSequence();
1954  seqArray.removeAt(index);
1955  state()->setSequence(seqArray);
1956  emit sequenceChanged(seqArray);
1957 
1958  if (state()->allJobs().empty())
1959  return true;
1960 
1961  SequenceJob * job = state()->allJobs().at(index);
1962  // remove completed frame counts from frame count map
1963  state()->removeCapturedFrameCount(job->getSignature(), job->getCompleted());
1964  // remove the job
1965  state()->allJobs().removeOne(job);
1966  if (job == activeJob())
1967  state()->setActiveJob(nullptr);
1968 
1969  delete job;
1970 
1971  if (queueTable->rowCount() == 0)
1972  removeFromQueueB->setEnabled(false);
1973 
1974  if (queueTable->rowCount() == 1)
1975  {
1976  queueUpB->setEnabled(false);
1977  queueDownB->setEnabled(false);
1978  }
1979 
1980  if (index < queueTable->rowCount())
1981  queueTable->selectRow(index);
1982  else if (queueTable->rowCount() > 0)
1983  queueTable->selectRow(queueTable->rowCount() - 1);
1984 
1985  if (queueTable->rowCount() == 0)
1986  {
1987  queueSaveAsB->setEnabled(false);
1988  queueSaveB->setEnabled(false);
1989  resetB->setEnabled(false);
1990  }
1991 
1992  state()->setDirty(true);
1993 
1994  return true;
1995 }
1996 
1997 void Capture::moveJob(bool up)
1998 {
1999  int currentRow = queueTable->currentRow();
2000  int destinationRow = up ? currentRow - 1 : currentRow + 1;
2001 
2002  int columnCount = queueTable->columnCount();
2003 
2004  if (currentRow < 0 || destinationRow < 0 || destinationRow >= queueTable->rowCount())
2005  return;
2006 
2007  for (int i = 0; i < columnCount; i++)
2008  {
2009  QTableWidgetItem * selectedLine = queueTable->takeItem(currentRow, i);
2010  QTableWidgetItem * counterpart = queueTable->takeItem(destinationRow, i);
2011 
2012  queueTable->setItem(destinationRow, i, selectedLine);
2013  queueTable->setItem(currentRow, i, counterpart);
2014  }
2015 
2016  SequenceJob * job = state()->allJobs().takeAt(currentRow);
2017 
2018  state()->allJobs().removeOne(job);
2019  state()->allJobs().insert(destinationRow, job);
2020 
2021  QJsonArray seqArray = state()->getSequence();
2022  QJsonObject currentJob = seqArray[currentRow].toObject();
2023  seqArray.replace(currentRow, seqArray[destinationRow]);
2024  seqArray.replace(destinationRow, currentJob);
2025  emit sequenceChanged(seqArray);
2026 
2027  queueTable->selectRow(destinationRow);
2028 
2029  state()->setDirty(true);
2030 }
2031 
2033 {
2034  targetNameT->setText(name);
2035  generatePreviewFilename();
2036 }
2037 
2038 void Capture::setBusy(bool enable)
2039 {
2040  previewB->setEnabled(!enable);
2041  loopB->setEnabled(!enable);
2042  opticalTrainCombo->setEnabled(!enable);
2043  trainB->setEnabled(!enable);
2044 
2045  foreach (QAbstractButton * button, queueEditButtonGroup->buttons())
2046  button->setEnabled(!enable);
2047 }
2048 
2049 void Capture::jobPrepared(SequenceJob * job)
2050 {
2051 
2052  int index = state()->allJobs().indexOf(job);
2053  if (index >= 0)
2054  queueTable->selectRow(index);
2055 
2056  if (activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW)
2057  {
2058  // set the progress info
2059  imgProgress->setEnabled(true);
2060  imgProgress->setMaximum(activeJob()->getCoreProperty(SequenceJob::SJ_Count).toInt());
2061  imgProgress->setValue(activeJob()->getCompleted());
2062  }
2063 }
2064 
2065 void Capture::jobExecutionPreparationStarted()
2066 {
2067  if (activeJob() == nullptr)
2068  {
2069  // this should never happen
2070  qWarning(KSTARS_EKOS_CAPTURE) << "jobExecutionPreparationStarted with null state()->getActiveJob().";
2071  return;
2072  }
2073  if (activeJob()->jobType() == SequenceJob::JOBTYPE_PREVIEW)
2074  updateStartButtons(true, false);
2075 }
2076 
2077 void Capture::updatePrepareState(CaptureState prepareState)
2078 {
2079  state()->setCaptureState(prepareState);
2080 
2081  if (activeJob() == nullptr)
2082  {
2083  qWarning(KSTARS_EKOS_CAPTURE) << "updatePrepareState with null activeJob().";
2084  // Everything below depends on activeJob(). Just return.
2085  return;
2086  }
2087 
2088  switch (prepareState)
2089  {
2091  appendLogText(i18n("Setting temperature to %1 °C...", activeJob()->getTargetTemperature()));
2092  captureStatusWidget->setStatus(i18n("Set Temp to %1 °C...", activeJob()->getTargetTemperature()),
2093  Qt::yellow);
2094  break;
2095  case CAPTURE_GUIDER_DRIFT:
2096  appendLogText(i18n("Waiting for guide drift below %1\"...", Options::startGuideDeviation()));
2097  captureStatusWidget->setStatus(i18n("Wait for Guider < %1\"...", Options::startGuideDeviation()), Qt::yellow);
2098  break;
2099 
2101  appendLogText(i18n("Setting camera to %1 degrees E of N...", activeJob()->getTargetRotation()));
2102  captureStatusWidget->setStatus(i18n("Set Camera to %1 deg...", activeJob()->getTargetRotation()),
2103  Qt::yellow);
2104  break;
2105 
2106  default:
2107  break;
2108 
2109  }
2110 }
2111 
2112 void Capture::setFocusTemperatureDelta(double focusTemperatureDelta, double absTemperture)
2113 {
2114  Q_UNUSED(absTemperture);
2115  // This produces too much log spam
2116  // Maybe add a threshold to report later?
2117  //qCDebug(KSTARS_EKOS_CAPTURE) << "setFocusTemperatureDelta: " << focusTemperatureDelta;
2118  state()->getRefocusState()->setFocusTemperatureDelta(focusTemperatureDelta);
2119 }
2120 
2121 void Capture::setGuideDeviation(double delta_ra, double delta_dec)
2122 {
2123  const double deviation_rms = std::hypot(delta_ra, delta_dec);
2124 
2125  // forward it to the state machine
2126  state()->setGuideDeviation(deviation_rms);
2127 
2128 }
2129 
2130 void Capture::setFocusStatus(FocusState newstate)
2131 {
2132  // directly forward it to the state machine
2133  state()->updateFocusState(newstate);
2134 }
2135 
2136 void Capture::updateFocusStatus(FocusState newstate)
2137 {
2138  if ((state()->getRefocusState()->isRefocusing()
2139  || state()->getRefocusState()->isInSequenceFocus()) && activeJob()
2140  && activeJob()->getStatus() == JOB_BUSY)
2141  {
2142  switch (newstate)
2143  {
2144  case FOCUS_COMPLETE:
2145  appendLogText(i18n("Focus complete."));
2146  captureStatusWidget->setStatus(i18n("Focus complete."), Qt::yellow);
2147  break;
2148  case FOCUS_FAILED:
2149  case FOCUS_ABORTED:
2150  captureStatusWidget->setStatus(i18n("Autofocus failed."), Qt::darkRed);
2151  break;
2152  default:
2153  // otherwise do nothing
2154  break;
2155  }
2156  }
2157 }
2158 
2159 
2160 
2161 void Capture::updateMeridianFlipStage(MeridianFlipState::MFStage stage)
2162 {
2163  // update UI
2164  if (getMeridianFlipState()->getMeridianFlipStage() != stage)
2165  {
2166  switch (stage)
2167  {
2168  case MeridianFlipState::MF_READY:
2169  if (state()->getCaptureState() == CAPTURE_PAUSED)
2170  {
2171  // paused after meridian flip requested
2172  captureStatusWidget->setStatus(i18n("Paused..."), Qt::yellow);
2173  }
2174  break;
2175 
2176  case MeridianFlipState::MF_INITIATED:
2177  captureStatusWidget->setStatus(i18n("Meridian Flip..."), Qt::yellow);
2178  KSNotification::event(QLatin1String("MeridianFlipStarted"), i18n("Meridian flip started"), KSNotification::Capture);
2179  break;
2180 
2181  case MeridianFlipState::MF_COMPLETED:
2182  captureStatusWidget->setStatus(i18n("Flip complete."), Qt::yellow);
2183  break;
2184 
2185  default:
2186  break;
2187  }
2188  }
2189 }
2190 
2191 void Capture::setRotatorReversed(bool toggled)
2192 {
2193  m_RotatorControlPanel->reverseDirection->setEnabled(true);
2194 
2195  m_RotatorControlPanel->reverseDirection->blockSignals(true);
2196  m_RotatorControlPanel->reverseDirection->setChecked(toggled);
2197  m_RotatorControlPanel->reverseDirection->blockSignals(false);
2198 }
2199 
2200 void Capture::saveFITSDirectory()
2201 {
2202  QString dir =
2203  QFileDialog::getExistingDirectory(Manager::Instance(), i18nc("@title:window", "FITS Save Directory"),
2204  dirPath.toLocalFile());
2205  if (dir.isEmpty())
2206  return;
2207 
2208  fileDirectoryT->setText(QDir::toNativeSeparators(dir));
2209 }
2210 
2211 void Capture::loadSequenceQueue()
2212 {
2213  QUrl fileURL = QFileDialog::getOpenFileUrl(Manager::Instance(), i18nc("@title:window", "Open Ekos Sequence Queue"),
2214  dirPath,
2215  "Ekos Sequence Queue (*.esq)");
2216  if (fileURL.isEmpty())
2217  return;
2218 
2219  if (fileURL.isValid() == false)
2220  {
2221  QString message = i18n("Invalid URL: %1", fileURL.toLocalFile());
2222  KSNotification::sorry(message, i18n("Invalid URL"));
2223  return;
2224  }
2225 
2226  dirPath = QUrl(fileURL.url(QUrl::RemoveFilename));
2227 
2228  loadSequenceQueue(fileURL.toLocalFile());
2229 }
2230 
2231 bool Capture::loadSequenceQueue(const QString &fileURL, QString targetName)
2232 {
2233  QFile sFile(fileURL);
2234  if (!sFile.open(QIODevice::ReadOnly))
2235  {
2236  QString message = i18n("Unable to open file %1", fileURL);
2237  KSNotification::sorry(message, i18n("Could Not Open File"));
2238  return false;
2239  }
2240 
2241  state()->clearCapturedFramesMap();
2243 
2244  // !m_standAlone so the stand-alone editor doesn't influence a live capture sesion.
2245  const bool result = process()->loadSequenceQueue(fileURL, targetName, !m_standAlone);
2246  // cancel if loading fails
2247  if (result == false)
2248  return result;
2249 
2250  // update general settings
2251  setObserverName(state()->observerName());
2252  syncGUIToGeneralSettings();
2253 
2254  // select the first one of the loaded jobs
2255  if (state()->allJobs().size() > 0)
2256  syncGUIToJob(state()->allJobs().first());
2257 
2258  // update save button tool tip
2259  queueSaveB->setToolTip("Save to " + sFile.fileName());
2260 
2261  return true;
2262 }
2263 
2264 void Capture::saveSequenceQueue()
2265 {
2266  QUrl backupCurrent = state()->sequenceURL();
2267 
2268  if (state()->sequenceURL().toLocalFile().startsWith(QLatin1String("/tmp/"))
2269  || state()->sequenceURL().toLocalFile().contains("/Temp"))
2270  state()->setSequenceURL(QUrl(""));
2271 
2272  // If no changes made, return.
2273  if (state()->dirty() == false && !state()->sequenceURL().isEmpty())
2274  return;
2275 
2276  if (state()->sequenceURL().isEmpty())
2277  {
2278  state()->setSequenceURL(QFileDialog::getSaveFileUrl(Manager::Instance(), i18nc("@title:window",
2279  "Save Ekos Sequence Queue"),
2280  dirPath,
2281  "Ekos Sequence Queue (*.esq)"));
2282  // if user presses cancel
2283  if (state()->sequenceURL().isEmpty())
2284  {
2285  state()->setSequenceURL(backupCurrent);
2286  return;
2287  }
2288 
2289  dirPath = QUrl(state()->sequenceURL().url(QUrl::RemoveFilename));
2290 
2291  if (state()->sequenceURL().toLocalFile().endsWith(QLatin1String(".esq")) == false)
2292  state()->setSequenceURL(QUrl("file:" + state()->sequenceURL().toLocalFile() + ".esq"));
2293 
2294  }
2295 
2296  if (state()->sequenceURL().isValid())
2297  {
2298  // !m_standAlone so the stand-alone editor doesn't influence a live capture sesion.
2299  if ((process()->saveSequenceQueue(state()->sequenceURL().toLocalFile(), !m_standAlone)) == false)
2300  {
2301  KSNotification::error(i18n("Failed to save sequence queue"), i18n("Save"));
2302  return;
2303  }
2304 
2305  state()->setDirty(false);
2306  }
2307  else
2308  {
2309  QString message = i18n("Invalid URL: %1", state()->sequenceURL().url());
2310  KSNotification::sorry(message, i18n("Invalid URL"));
2311  }
2312 }
2313 
2314 void Capture::saveSequenceQueueAs()
2315 {
2316  state()->setSequenceURL(QUrl(""));
2318 }
2319 
2320 bool Capture::saveSequenceQueue(const QString &path)
2321 {
2322  // forward it to the process engine
2323  return process()->saveSequenceQueue(path);
2324 }
2325 
2326 void Capture::resetJobs()
2327 {
2328  // Stop any running capture
2329  stop();
2330 
2331  // If a job is selected for edit, reset only that job
2332  if (m_JobUnderEdit == true)
2333  {
2334  SequenceJob * job = state()->allJobs().at(queueTable->currentRow());
2335  if (nullptr != job)
2336  {
2337  job->resetStatus();
2338  updateJobTable(job);
2339  }
2340  }
2341  else
2342  {
2344  nullptr, i18n("Are you sure you want to reset status of all jobs?"), i18n("Reset job status"),
2345  KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "reset_job_status_warning") != KMessageBox::Continue)
2346  {
2347  return;
2348  }
2349 
2350  foreach (SequenceJob * job, state()->allJobs())
2351  {
2352  job->resetStatus();
2353  updateJobTable(job);
2354  }
2355  }
2356 
2357  // Also reset the storage count for all jobs
2358  state()->clearCapturedFramesMap();
2359 
2360  // We're not controlled by the Scheduler, restore progress option
2361  state()->setIgnoreJobProgress(Options::alwaysResetSequenceWhenStarting());
2362 
2363  // enable start button
2364  startB->setEnabled(true);
2365 }
2366 
2368 {
2369  // This function is called independently from the Scheduler or the UI, so honor the change
2370  state()->setIgnoreJobProgress(true);
2371 }
2372 
2373 void Capture::syncGUIToJob(SequenceJob * job)
2374 {
2375  if (job == nullptr)
2376  {
2377  qWarning(KSTARS_EKOS_CAPTURE) << "syncGuiToJob with null job.";
2378  // Everything below depends on job. Just return.
2379  return;
2380  }
2381 
2382  const auto roi = job->getCoreProperty(SequenceJob::SJ_ROI).toRect();
2383 
2384  captureFormatS->setCurrentText(job->getCoreProperty(SequenceJob::SJ_Format).toString());
2385  captureEncodingS->setCurrentText(job->getCoreProperty(SequenceJob::SJ_Encoding).toString());
2386  captureExposureN->setValue(job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble());
2387  captureBinHN->setValue(job->getCoreProperty(SequenceJob::SJ_Binning).toPoint().x());
2388  captureBinVN->setValue(job->getCoreProperty(SequenceJob::SJ_Binning).toPoint().y());
2389  captureFrameXN->setValue(roi.x());
2390  captureFrameYN->setValue(roi.y());
2391  captureFrameWN->setValue(roi.width());
2392  captureFrameHN->setValue(roi.height());
2393  FilterPosCombo->setCurrentIndex(job->getTargetFilter() - 1);
2394  captureTypeS->setCurrentIndex(job->getFrameType());
2395  captureCountN->setValue(job->getCoreProperty(SequenceJob::SJ_Count).toInt());
2396  captureDelayN->setValue(job->getCoreProperty(SequenceJob::SJ_Delay).toInt() / 1000);
2397  targetNameT->setText(job->getCoreProperty(SequenceJob::SJ_TargetName).toString());
2398  fileDirectoryT->setText(job->getCoreProperty(SequenceJob::SJ_LocalDirectory).toString());
2399  fileUploadModeS->setCurrentIndex(job->getUploadMode());
2400  fileRemoteDirT->setEnabled(fileUploadModeS->currentIndex() != 0);
2401  fileRemoteDirT->setText(job->getCoreProperty(SequenceJob::SJ_RemoteDirectory).toString());
2402  placeholderFormatT->setText(job->getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString());
2403  formatSuffixN->setValue(job->getCoreProperty(SequenceJob::SJ_PlaceholderSuffix).toUInt());
2404  m_LimitsUI->limitDitherFrequencyN->setValue(job->getCoreProperty(SequenceJob::SJ_DitherPerJobFrequency).toInt());
2405 
2406  // Temperature Options
2407  cameraTemperatureS->setChecked(job->getCoreProperty(SequenceJob::SJ_EnforceTemperature).toBool());
2408  if (job->getCoreProperty(SequenceJob::SJ_EnforceTemperature).toBool())
2409  cameraTemperatureN->setValue(job->getTargetTemperature());
2410 
2411  // Start guider drift options
2412  m_LimitsUI->startGuiderDriftS->setChecked(Options::enforceStartGuiderDrift());
2413  if (Options::enforceStartGuiderDrift())
2414  m_LimitsUI->startGuiderDriftN->setValue(Options::startGuideDeviation());
2415 
2416  // Flat field options
2417  calibrationB->setEnabled(job->getFrameType() != FRAME_LIGHT);
2418  generateDarkFlatsB->setEnabled(job->getFrameType() != FRAME_LIGHT);
2419  state()->setFlatFieldDuration(job->getFlatFieldDuration());
2420  state()->setCalibrationPreAction(job->getCalibrationPreAction());
2421  state()->setTargetADU(job->getCoreProperty(SequenceJob::SJ_TargetADU).toDouble());
2422  state()->setTargetADUTolerance(job->getCoreProperty(SequenceJob::SJ_TargetADUTolerance).toDouble());
2423  state()->setWallCoord(job->getWallCoord());
2424  m_scriptsManager->setScripts(job->getScripts());
2425 
2426  // Custom Properties
2427  customPropertiesDialog->setCustomProperties(job->getCustomProperties());
2428 
2429  if (captureISOS)
2430  captureISOS->setCurrentIndex(job->getCoreProperty(SequenceJob::SJ_ISOIndex).toInt());
2431 
2432  double gain = getGain();
2433  if (gain >= 0)
2434  captureGainN->setValue(gain);
2435  else
2436  captureGainN->setValue(GainSpinSpecialValue);
2437 
2438  double offset = getOffset();
2439  if (offset >= 0)
2440  captureOffsetN->setValue(offset);
2441  else
2442  captureOffsetN->setValue(OffsetSpinSpecialValue);
2443 
2444  // update place holder typ
2445  generatePreviewFilename();
2446 
2447  if (m_RotatorControlPanel) // only if rotator is registered
2448  {
2449  if (job->getTargetRotation() != Ekos::INVALID_VALUE)
2450  {
2451  // remove enforceJobPA m_RotatorControlPanel->setRotationEnforced(true);
2452  m_RotatorControlPanel->setCameraPA(job->getTargetRotation());
2453  }
2454  // remove enforceJobPA
2455  // else
2456  // m_RotatorControlPanel->setRotationEnforced(false);
2457  }
2458 
2459  // hide target drift if align check frequency is == 0
2460  if (Options::alignCheckFrequency() == 0)
2461  {
2462  targetDriftLabel->setVisible(false);
2463  targetDrift->setVisible(false);
2464  targetDriftUnit->setVisible(false);
2465  }
2466 
2467  emit settingsUpdated(getPresetSettings());
2468 }
2469 
2470 void Capture::syncGUIToGeneralSettings()
2471 {
2472  m_LimitsUI->startGuiderDriftS->setChecked(Options::enforceStartGuiderDrift());
2473  m_LimitsUI->startGuiderDriftN->setValue(Options::startGuideDeviation());
2474  m_LimitsUI->limitGuideDeviationS->setChecked(Options::enforceGuideDeviation());
2475  m_LimitsUI->limitGuideDeviationN->setValue(Options::guideDeviation());
2476  m_LimitsUI->limitGuideDeviationRepsN->setValue(static_cast<int>(Options::guideDeviationReps()));
2477  m_LimitsUI->limitFocusHFRS->setChecked(Options::enforceAutofocusHFR());
2478  m_LimitsUI->limitFocusHFRThresholdPercentage->setValue(Options::hFRThresholdPercentage());
2479  m_LimitsUI->limitFocusHFRN->setValue(Options::hFRDeviation());
2480  m_LimitsUI->limitFocusHFRCheckFrames->setValue(Options::inSequenceCheckFrames());
2481  m_LimitsUI->limitFocusHFRAlgorithm->setCurrentIndex(Options::hFRCheckAlgorithm());
2482  m_LimitsUI->limitFocusDeltaTS->setChecked(Options::enforceAutofocusOnTemperature());
2483  m_LimitsUI->limitFocusDeltaTN->setValue(Options::maxFocusTemperatureDelta());
2484  m_LimitsUI->limitRefocusS->setChecked(Options::enforceRefocusEveryN());
2485  m_LimitsUI->limitRefocusN->setValue(static_cast<int>(Options::refocusEveryN()));
2486  m_LimitsUI->meridianRefocusS->setChecked(Options::refocusAfterMeridianFlip());
2487 }
2488 
2490 {
2491  QJsonObject settings;
2492 
2493  // Try to get settings value
2494  // if not found, fallback to camera value
2495  double gain = -1;
2496  if (GainSpinSpecialValue > INVALID_VALUE && captureGainN->value() > GainSpinSpecialValue)
2497  gain = captureGainN->value();
2498  else if (devices()->getActiveCamera() && devices()->getActiveCamera()->hasGain())
2499  devices()->getActiveCamera()->getGain(&gain);
2500 
2501  double offset = -1;
2502  if (OffsetSpinSpecialValue > INVALID_VALUE && captureOffsetN->value() > OffsetSpinSpecialValue)
2503  offset = captureOffsetN->value();
2504  else if (devices()->getActiveCamera() && devices()->getActiveCamera()->hasOffset())
2505  devices()->getActiveCamera()->getOffset(&offset);
2506 
2507  int iso = -1;
2508  if (captureISOS)
2509  iso = captureISOS->currentIndex();
2510  else if (devices()->getActiveCamera())
2511  iso = devices()->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD)->getISOIndex();
2512 
2513  settings.insert("optical_train", opticalTrainCombo->currentText());
2514  settings.insert("filter", FilterPosCombo->currentText());
2515  settings.insert("dark", darkB->isChecked());
2516  settings.insert("exp", captureExposureN->value());
2517  settings.insert("bin", captureBinHN->value());
2518  settings.insert("iso", iso);
2519  settings.insert("frameType", captureTypeS->currentIndex());
2520  settings.insert("captureFormat", captureFormatS->currentIndex());
2521  settings.insert("transferFormat", captureEncodingS->currentIndex());
2522  settings.insert("gain", gain);
2523  settings.insert("offset", offset);
2524  settings.insert("temperature", cameraTemperatureN->value());
2525  settings.insert("ditherPerJobFrequency", m_LimitsUI->limitDitherFrequencyN->value());
2526 
2527  return settings;
2528 }
2529 
2530 void Capture::selectedJobChanged(QModelIndex current, QModelIndex previous)
2531 {
2532  Q_UNUSED(previous)
2533  selectJob(current);
2534 }
2535 
2536 bool Capture::selectJob(QModelIndex i)
2537 {
2538  if (i.row() < 0 || (i.row() + 1) > state()->allJobs().size())
2539  return false;
2540 
2541  SequenceJob * job = state()->allJobs().at(i.row());
2542 
2543  if (job == nullptr || job->jobType() == SequenceJob::JOBTYPE_DARKFLAT)
2544  return false;
2545 
2546  syncGUIToJob(job);
2547 
2548  if (state()->isBusy())
2549  return false;
2550 
2551  if (state()->allJobs().size() >= 2)
2552  {
2553  queueUpB->setEnabled(i.row() > 0);
2554  queueDownB->setEnabled(i.row() + 1 < state()->allJobs().size());
2555  }
2556 
2557  return true;
2558 }
2559 
2560 void Capture::editJob(QModelIndex i)
2561 {
2562  // Try to select a job. If job not found or not editable return.
2563  if (selectJob(i) == false)
2564  return;
2565 
2566  appendLogText(i18n("Editing job #%1...", i.row() + 1));
2567 
2568  addToQueueB->setIcon(QIcon::fromTheme("dialog-ok-apply"));
2569  addToQueueB->setToolTip(i18n("Apply job changes."));
2570  removeFromQueueB->setToolTip(i18n("Cancel job changes."));
2571 
2572  // Make it sure if user presses enter, the job is validated.
2573  previewB->setDefault(false);
2574  addToQueueB->setDefault(true);
2575 
2576  m_JobUnderEdit = true;
2577 }
2578 
2579 void Capture::resetJobEdit(bool cancelled)
2580 {
2581  if (cancelled == true)
2582  appendLogText(i18n("Editing job canceled."));
2583 
2584  m_JobUnderEdit = false;
2585  addToQueueB->setIcon(QIcon::fromTheme("list-add"));
2586 
2587  addToQueueB->setToolTip(i18n("Add job to sequence queue"));
2588  removeFromQueueB->setToolTip(i18n("Remove job from sequence queue"));
2589 
2590  addToQueueB->setDefault(false);
2591  previewB->setDefault(true);
2592 }
2593 
2594 void Capture::setMaximumGuidingDeviation(bool enable, double value)
2595 {
2596  m_LimitsUI->limitGuideDeviationS->setChecked(enable);
2597  if (enable)
2598  m_LimitsUI->limitGuideDeviationN->setValue(value);
2599 }
2600 
2601 void Capture::setInSequenceFocus(bool enable, double HFR)
2602 {
2603  m_LimitsUI->limitFocusHFRS->setChecked(enable);
2604  if (enable)
2605  m_LimitsUI->limitFocusHFRN->setValue(HFR);
2606 }
2607 
2609 {
2610  state()->setActiveJob(nullptr);
2611  while (queueTable->rowCount() > 0)
2612  queueTable->removeRow(0);
2613  qDeleteAll(state()->allJobs());
2614  state()->allJobs().clear();
2615 
2616  while (state()->getSequence().count())
2617  state()->getSequence().pop_back();
2618  emit sequenceChanged(state()->getSequence());
2619 }
2620 
2621 void Capture::setAlignStatus(AlignState newstate)
2622 {
2623  // forward it directly to the state machine
2624  state()->setAlignState(newstate);
2625 }
2626 
2627 void Capture::setGuideStatus(GuideState newstate)
2628 {
2629  // forward it directly to the state machine
2630  state()->setGuideState(newstate);
2631 }
2632 
2633 void Capture::checkFrameType(int index)
2634 {
2635  calibrationB->setEnabled(index != FRAME_LIGHT);
2636  generateDarkFlatsB->setEnabled(index != FRAME_LIGHT);
2637 }
2638 
2640 {
2641  if (Options::hFRCheckAlgorithm() == HFR_CHECK_FIXED)
2642  return;
2643 
2644  m_LimitsUI->limitFocusHFRN->setValue(0);
2645  //firstAutoFocus = true;
2646 }
2647 
2648 void Capture::openCalibrationDialog()
2649 {
2650  QDialog calibrationDialog(this);
2651 
2652  Ui_calibrationOptions calibrationOptions;
2653  calibrationOptions.setupUi(&calibrationDialog);
2654 
2655  calibrationOptions.parkMountC->setEnabled(devices()->mount() && devices()->mount()->canPark());
2656  calibrationOptions.parkDomeC->setEnabled(devices()->dome() && devices()->dome()->canPark());
2657 
2658  calibrationOptions.parkMountC->setChecked(false);
2659  calibrationOptions.parkDomeC->setChecked(false);
2660  calibrationOptions.gotoWallC->setChecked(false);
2661 
2662  calibrationOptions.parkMountC->setChecked(state()->calibrationPreAction() & ACTION_PARK_MOUNT);
2663  calibrationOptions.parkDomeC->setChecked(state()->calibrationPreAction() & ACTION_PARK_DOME);
2664  if (state()->calibrationPreAction() & ACTION_WALL)
2665  {
2666  calibrationOptions.gotoWallC->setChecked(true);
2667  calibrationOptions.azBox->setText(state()->wallCoord().az().toDMSString());
2668  calibrationOptions.altBox->setText(state()->wallCoord().alt().toDMSString());
2669  }
2670 
2671  switch (state()->flatFieldDuration())
2672  {
2673  case DURATION_MANUAL:
2674  calibrationOptions.manualDurationC->setChecked(true);
2675  break;
2676 
2677  case DURATION_ADU:
2678  calibrationOptions.ADUC->setChecked(true);
2679  calibrationOptions.ADUValue->setValue(static_cast<int>(std::round(state()->targetADU())));
2680  calibrationOptions.ADUTolerance->setValue(static_cast<int>(std::round(state()->targetADUTolerance())));
2681  break;
2682  }
2683 
2684  // avoid combination of ACTION_WALL and ACTION_PARK_MOUNT
2685  connect(calibrationOptions.gotoWallC, &QCheckBox::clicked, [&](bool checked)
2686  {
2687  if (checked)
2688  calibrationOptions.parkMountC->setChecked(false);
2689  });
2690  connect(calibrationOptions.parkMountC, &QCheckBox::clicked, [&](bool checked)
2691  {
2692  if (checked)
2693  calibrationOptions.gotoWallC->setChecked(false);
2694  });
2695 
2696  if (calibrationDialog.exec() == QDialog::Accepted)
2697  {
2698  state()->setCalibrationPreAction(ACTION_NONE);
2699  if (calibrationOptions.parkMountC->isChecked())
2700  state()->setCalibrationPreAction(state()->calibrationPreAction() | ACTION_PARK_MOUNT);
2701  if (calibrationOptions.parkDomeC->isChecked())
2702  state()->setCalibrationPreAction(state()->calibrationPreAction() | ACTION_PARK_DOME);
2703  if (calibrationOptions.gotoWallC->isChecked())
2704  {
2705  dms wallAz, wallAlt;
2706  bool azOk = false, altOk = false;
2707 
2708  wallAz = calibrationOptions.azBox->createDms(&azOk);
2709  wallAlt = calibrationOptions.altBox->createDms(&altOk);
2710 
2711  if (azOk && altOk)
2712  {
2713  state()->setCalibrationPreAction((state()->calibrationPreAction() & ~ACTION_PARK_MOUNT) | ACTION_WALL);
2714  state()->wallCoord().setAz(wallAz);
2715  state()->wallCoord().setAlt(wallAlt);
2716  }
2717  else
2718  {
2719  calibrationOptions.gotoWallC->setChecked(false);
2720  KSNotification::error(i18n("Wall coordinates are invalid."));
2721  }
2722  }
2723 
2724  if (calibrationOptions.manualDurationC->isChecked())
2725  state()->setFlatFieldDuration(DURATION_MANUAL);
2726  else
2727  {
2728  state()->setFlatFieldDuration(DURATION_ADU);
2729  state()->setTargetADU(calibrationOptions.ADUValue->value());
2730  state()->setTargetADUTolerance(calibrationOptions.ADUTolerance->value());
2731  }
2732 
2733  state()->setDirty(true);
2734 
2735  if (!m_standAlone)
2736  {
2737  Options::setCalibrationPreActionIndex(state()->calibrationPreAction());
2738  Options::setCalibrationFlatDurationIndex(state()->flatFieldDuration());
2739  Options::setCalibrationWallAz(state()->wallCoord().az().Degrees());
2740  Options::setCalibrationWallAlt(state()->wallCoord().alt().Degrees());
2741  Options::setCalibrationADUValue(static_cast<uint>(std::round(state()->targetADU())));
2742  Options::setCalibrationADUValueTolerance(static_cast<uint>(std::round(state()->targetADUTolerance())));
2743  }
2744  }
2745 }
2746 
2747 bool Capture::setVideoLimits(uint16_t maxBufferSize, uint16_t maxPreviewFPS)
2748 {
2749  if (devices()->getActiveCamera() == nullptr)
2750  return false;
2751 
2752  return devices()->getActiveCamera()->setStreamLimits(maxBufferSize, maxPreviewFPS);
2753 }
2754 
2755 void Capture::setVideoStreamEnabled(bool enabled)
2756 {
2757  if (enabled)
2758  {
2759  liveVideoB->setChecked(true);
2760  liveVideoB->setIcon(QIcon::fromTheme("camera-on"));
2761  }
2762  else
2763  {
2764  liveVideoB->setChecked(false);
2765  liveVideoB->setIcon(QIcon::fromTheme("camera-ready"));
2766  }
2767 }
2768 
2769 void Capture::setMountStatus(ISD::Mount::Status newState)
2770 {
2771  switch (newState)
2772  {
2773  case ISD::Mount::MOUNT_PARKING:
2774  case ISD::Mount::MOUNT_SLEWING:
2775  case ISD::Mount::MOUNT_MOVING:
2776  previewB->setEnabled(false);
2777  liveVideoB->setEnabled(false);
2778  // Only disable when button is "Start", and not "Stopped"
2779  // If mount is in motion, Stopped button should always be enabled to terminate
2780  // the sequence
2781  if (state()->isBusy() == false)
2782  startB->setEnabled(false);
2783  break;
2784 
2785  default:
2786  if (state()->isBusy() == false)
2787  {
2788  previewB->setEnabled(true);
2789  if (devices()->getActiveCamera())
2790  liveVideoB->setEnabled(devices()->getActiveCamera()->hasVideoStream());
2791  startB->setEnabled(true);
2792  }
2793 
2794  break;
2795  }
2796 }
2797 
2798 void Capture::showObserverDialog()
2799 {
2800  QList<OAL::Observer *> m_observerList;
2801  KStars::Instance()->data()->userdb()->GetAllObservers(m_observerList);
2802  QStringList observers;
2803  for (auto &o : m_observerList)
2804  observers << QString("%1 %2").arg(o->name(), o->surname());
2805 
2806  QDialog observersDialog(this);
2807  observersDialog.setWindowTitle(i18nc("@title:window", "Select Current Observer"));
2808 
2809  QLabel label(i18n("Current Observer:"));
2810 
2811  QComboBox observerCombo(&observersDialog);
2812  observerCombo.addItems(observers);
2813  observerCombo.setCurrentText(getObserverName());
2814  observerCombo.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
2815 
2816  QPushButton manageObserver(&observersDialog);
2817  manageObserver.setFixedSize(QSize(32, 32));
2818  manageObserver.setIcon(QIcon::fromTheme("document-edit"));
2819  manageObserver.setAttribute(Qt::WA_LayoutUsesWidgetRect);
2820  manageObserver.setToolTip(i18n("Manage Observers"));
2821  connect(&manageObserver, &QPushButton::clicked, this, [&]()
2822  {
2823  ObserverAdd add;
2824  add.exec();
2825 
2826  QList<OAL::Observer *> m_observerList;
2827  KStars::Instance()->data()->userdb()->GetAllObservers(m_observerList);
2828  QStringList observers;
2829  for (auto &o : m_observerList)
2830  observers << QString("%1 %2").arg(o->name(), o->surname());
2831 
2832  observerCombo.clear();
2833  observerCombo.addItems(observers);
2834  observerCombo.setCurrentText(getObserverName());
2835 
2836  });
2837 
2838  QHBoxLayout * layout = new QHBoxLayout;
2839  layout->addWidget(&label);
2840  layout->addWidget(&observerCombo);
2841  layout->addWidget(&manageObserver);
2842 
2843  observersDialog.setLayout(layout);
2844 
2845  observersDialog.exec();
2846  setObserverName(observerCombo.currentText());
2847 }
2848 
2849 void Capture::setAlignResults(double solverPA, double ra, double de, double pixscale)
2850 {
2851  Q_UNUSED(ra)
2852  Q_UNUSED(de)
2853  Q_UNUSED(pixscale)
2854  if (devices()->rotator() && m_RotatorControlPanel)
2855  m_RotatorControlPanel->refresh(solverPA);
2856 }
2857 
2858 void Capture::setFilterStatus(FilterState filterState)
2859 {
2860  if (filterState != state()->getFilterManagerState())
2861  qCDebug(KSTARS_EKOS_CAPTURE) << "Filter state changed from" << Ekos::getFilterStatusString(
2862  state()->getFilterManagerState()) << "to" << Ekos::getFilterStatusString(filterState);
2863  if (state()->getCaptureState() == CAPTURE_CHANGING_FILTER)
2864  {
2865  switch (filterState)
2866  {
2867  case FILTER_OFFSET:
2868  appendLogText(i18n("Changing focus offset by %1 steps...",
2869  m_FilterManager->getTargetFilterOffset()));
2870  break;
2871 
2872  case FILTER_CHANGE:
2873  appendLogText(i18n("Changing filter to %1...",
2874  FilterPosCombo->itemText(m_FilterManager->getTargetFilterPosition() - 1)));
2875  break;
2876 
2877  case FILTER_AUTOFOCUS:
2878  appendLogText(i18n("Auto focus on filter change..."));
2880  break;
2881 
2882  case FILTER_IDLE:
2883  if (state()->getFilterManagerState() == FILTER_CHANGE)
2884  {
2885  appendLogText(i18n("Filter set to %1.",
2886  FilterPosCombo->itemText(m_FilterManager->getTargetFilterPosition() - 1)));
2887  }
2888  break;
2889 
2890  default:
2891  break;
2892  }
2893  }
2894  state()->setFilterManagerState(filterState);
2895 }
2896 
2897 void Capture::setupFilterManager()
2898 {
2899  // Do we have an existing filter manager?
2900  if (m_FilterManager)
2901  m_FilterManager->disconnect(this);
2902 
2903  // Create new or refresh device
2904  Manager::Instance()->createFilterManager(devices()->filterWheel());
2905 
2906  // Return global filter manager for this filter wheel.
2907  Manager::Instance()->getFilterManager(devices()->filterWheel()->getDeviceName(), m_FilterManager);
2908 
2909  devices()->setFilterManager(m_FilterManager);
2910 
2911  connect(m_FilterManager.get(), &FilterManager::updated, this, [this]()
2912  {
2913  emit filterManagerUpdated(devices()->filterWheel());
2914  });
2915 
2916  // display capture status changes
2917  connect(m_FilterManager.get(), &FilterManager::newStatus, this, &Capture::newFilterStatus);
2918 
2919  connect(filterManagerB, &QPushButton::clicked, this, [this]()
2920  {
2921  m_FilterManager->refreshFilterModel();
2922  m_FilterManager->show();
2923  m_FilterManager->raise();
2924  });
2925 
2926  connect(m_FilterManager.get(), &FilterManager::ready, this, &Capture::updateCurrentFilterPosition);
2927 
2928  connect(m_FilterManager.get(), &FilterManager::failed, this, [this]()
2929  {
2930  if (activeJob())
2931  {
2932  appendLogText(i18n("Filter operation failed."));
2933  abort();
2934  }
2935  });
2936 
2937  // filter changes
2938  connect(m_FilterManager.get(), &FilterManager::newStatus, this, &Capture::setFilterStatus);
2939 
2940  // display capture status changes
2941  connect(m_FilterManager.get(), &FilterManager::newStatus, captureStatusWidget, &LedStatusWidget::setFilterState);
2942 
2943  connect(m_FilterManager.get(), &FilterManager::labelsChanged, this, [this]()
2944  {
2945  FilterPosCombo->clear();
2946  FilterPosCombo->addItems(m_FilterManager->getFilterLabels());
2947  FilterPosCombo->setCurrentIndex(m_FilterManager->getFilterPosition() - 1);
2948  updateCurrentFilterPosition();
2949  });
2950 
2951  connect(m_FilterManager.get(), &FilterManager::positionChanged, this, [this]()
2952  {
2953  FilterPosCombo->setCurrentIndex(m_FilterManager->getFilterPosition() - 1);
2954  updateCurrentFilterPosition();
2955  });
2956 }
2957 
2958 void Capture::addDSLRInfo(const QString &model, uint32_t maxW, uint32_t maxH, double pixelW, double pixelH)
2959 {
2960  // Check if model already exists
2961  auto pos = std::find_if(state()->DSLRInfos().begin(), state()->DSLRInfos().end(), [model](const auto & oneDSLRInfo)
2962  {
2963  return (oneDSLRInfo["Model"] == model);
2964  });
2965 
2966  if (pos != state()->DSLRInfos().end())
2967  {
2968  KStarsData::Instance()->userdb()->DeleteDSLRInfo(model);
2969  state()->DSLRInfos().removeOne(*pos);
2970  }
2971 
2972  QMap<QString, QVariant> oneDSLRInfo;
2973  oneDSLRInfo["Model"] = model;
2974  oneDSLRInfo["Width"] = maxW;
2975  oneDSLRInfo["Height"] = maxH;
2976  oneDSLRInfo["PixelW"] = pixelW;
2977  oneDSLRInfo["PixelH"] = pixelH;
2978 
2979  KStarsData::Instance()->userdb()->AddDSLRInfo(oneDSLRInfo);
2980  KStarsData::Instance()->userdb()->GetAllDSLRInfos(state()->DSLRInfos());
2981 
2982  updateFrameProperties();
2983  process()->resetFrame();
2984  process()->syncDSLRToTargetChip(model);
2985 
2986  // In case the dialog was opened, let's close it
2987  if (dslrInfoDialog)
2988  dslrInfoDialog.reset();
2989 }
2990 
2991 void Capture::cullToDSLRLimits()
2992 {
2993  QString model(devices()->getActiveCamera()->getDeviceName());
2994 
2995  // Check if model already exists
2996  auto pos = std::find_if(state()->DSLRInfos().begin(),
2997  state()->DSLRInfos().end(), [model](QMap<QString, QVariant> &oneDSLRInfo)
2998  {
2999  return (oneDSLRInfo["Model"] == model);
3000  });
3001 
3002  if (pos != state()->DSLRInfos().end())
3003  {
3004  if (captureFrameWN->maximum() == 0 || captureFrameWN->maximum() > (*pos)["Width"].toInt())
3005  {
3006  captureFrameWN->setValue((*pos)["Width"].toInt());
3007  captureFrameWN->setMaximum((*pos)["Width"].toInt());
3008  }
3009 
3010  if (captureFrameHN->maximum() == 0 || captureFrameHN->maximum() > (*pos)["Height"].toInt())
3011  {
3012  captureFrameHN->setValue((*pos)["Height"].toInt());
3013  captureFrameHN->setMaximum((*pos)["Height"].toInt());
3014  }
3015  }
3016 }
3017 
3019 {
3020  auto opticalTrain = settings["optical_train"].toString(opticalTrainCombo->currentText());
3021  auto targetFilter = settings["filter"].toString(FilterPosCombo->currentText());
3022 
3023  opticalTrainCombo->setCurrentText(opticalTrain);
3024  FilterPosCombo->setCurrentText(targetFilter);
3025 
3026  captureExposureN->setValue(settings["exp"].toDouble(1));
3027 
3028  int bin = settings["bin"].toInt(1);
3029  setBinning(bin, bin);
3030 
3031  if (settings["temperature"].isString() && settings["temperature"].toString() == "--")
3032  setForceTemperature(false);
3033  else
3034  {
3035  double temperature = settings["temperature"].toDouble(INVALID_VALUE);
3036  if (temperature > INVALID_VALUE && devices()->getActiveCamera()
3037  && devices()->getActiveCamera()->hasCoolerControl())
3038  {
3039  setForceTemperature(true);
3040  setTargetTemperature(temperature);
3041  }
3042  else
3043  setForceTemperature(false);
3044  }
3045 
3046  if (settings["gain"].isString() && settings["gain"].toString() == "--")
3047  captureGainN->setValue(GainSpinSpecialValue);
3048  else
3049  {
3050  double gain = settings["gain"].toDouble(GainSpinSpecialValue);
3051  if (devices()->getActiveCamera() && devices()->getActiveCamera()->hasGain())
3052  {
3053  if (gain == GainSpinSpecialValue)
3054  captureGainN->setValue(GainSpinSpecialValue);
3055  else
3056  setGain(gain);
3057  }
3058  }
3059 
3060  if (settings["offset"].isString() && settings["offset"].toString() == "--")
3061  captureOffsetN->setValue(OffsetSpinSpecialValue);
3062  else
3063  {
3064  double offset = settings["offset"].toDouble(OffsetSpinSpecialValue);
3065  if (devices()->getActiveCamera() && devices()->getActiveCamera()->hasOffset())
3066  {
3067  if (offset == OffsetSpinSpecialValue)
3068  captureOffsetN->setValue(OffsetSpinSpecialValue);
3069  else
3070  setOffset(offset);
3071  }
3072  }
3073 
3074  int transferFormat = settings["transferFormat"].toInt(-1);
3075  if (transferFormat >= 0)
3076  {
3077  captureEncodingS->setCurrentIndex(transferFormat);
3078  }
3079 
3080  QString captureFormat = settings["captureFormat"].toString(captureFormatS->currentText());
3081  if (captureFormat != captureFormatS->currentText())
3082  captureFormatS->setCurrentText(captureFormat);
3083 
3084  captureTypeS->setCurrentIndex(qMax(0, settings["frameType"].toInt(0)));
3085 
3086  // ISO
3087  int isoIndex = settings["iso"].toInt(-1);
3088  if (isoIndex >= 0)
3089  setISO(isoIndex);
3090 
3091  bool dark = settings["dark"].toBool(darkB->isChecked());
3092  if (dark != darkB->isChecked())
3093  darkB->setChecked(dark);
3094 
3095  int ditherPerJobFrequency = settings["ditherPerJobFrequency"].toInt(0);
3096  m_LimitsUI->limitDitherFrequencyN->setValue(ditherPerJobFrequency);
3097 }
3098 
3100 {
3101  const auto prefix = settings["prefix"].toString(targetNameT->text());
3102  const auto directory = settings["directory"].toString(fileDirectoryT->text());
3103  const auto upload = settings["upload"].toInt(fileUploadModeS->currentIndex());
3104  const auto remote = settings["remote"].toString(fileRemoteDirT->text());
3105  const auto format = settings["format"].toString(placeholderFormatT->text());
3106  const auto suffix = settings["suffix"].toInt(formatSuffixN->value());
3107 
3108  targetNameT->setText(prefix);
3109  fileDirectoryT->setText(directory);
3110  fileUploadModeS->setCurrentIndex(upload);
3111  fileRemoteDirT->setText(remote);
3112  placeholderFormatT->setText(format);
3113  formatSuffixN->setValue(suffix);
3114 }
3115 
3117 {
3118  QJsonObject settings =
3119  {
3120  {"prefix", targetNameT->text()},
3121  {"directory", fileDirectoryT->text()},
3122  {"format", placeholderFormatT->text()},
3123  {"suffix", formatSuffixN->value()},
3124  {"upload", fileUploadModeS->currentIndex()},
3125  {"remote", fileRemoteDirT->text()}
3126  };
3127 
3128  return settings;
3129 }
3130 
3132 {
3133  const bool deviationCheck = settings["deviationCheck"].toBool(Options::enforceGuideDeviation());
3134  const double deviationValue = settings["deviationValue"].toDouble(Options::guideDeviation());
3135  const bool focusHFRCheck = settings["focusHFRCheck"].toBool(m_LimitsUI->limitFocusHFRS->isChecked());
3136  const double focusHFRThresholdPercentage = settings["hFRThresholdPercentage"].toDouble(
3137  m_LimitsUI->limitFocusHFRThresholdPercentage->value());
3138  const double focusHFRValue = settings["focusHFRValue"].toDouble(m_LimitsUI->limitFocusHFRN->value());
3139  const int focusHFRCheckFrames = settings["inSequenceCheckFrames"].toInt(m_LimitsUI->limitFocusHFRCheckFrames->value());
3140  const int focusHFRAlgorithm = settings["hFRCheckAlgorithm"].toInt(m_LimitsUI->limitFocusHFRAlgorithm->currentIndex());
3141  const bool focusDeltaTCheck = settings["focusDeltaTCheck"].toBool(m_LimitsUI->limitFocusDeltaTS->isChecked());
3142  const double focusDeltaTValue = settings["focusDeltaTValue"].toDouble(m_LimitsUI->limitFocusDeltaTN->value());
3143  const bool refocusNCheck = settings["refocusNCheck"].toBool(m_LimitsUI->limitRefocusS->isChecked());
3144  const int refocusNValue = settings["refocusNValue"].toInt(m_LimitsUI->limitRefocusN->value());
3145  const int ditherPerJobFrequency = settings["ditherPerJobFrequency"].toInt(m_LimitsUI->limitDitherFrequencyN->value());
3146 
3147  if (deviationCheck)
3148  {
3149  m_LimitsUI->limitGuideDeviationS->setChecked(true);
3150  m_LimitsUI->limitGuideDeviationN->setValue(deviationValue);
3151  }
3152  else
3153  m_LimitsUI->limitGuideDeviationS->setChecked(false);
3154 
3155  if (focusHFRCheck)
3156  {
3157  m_LimitsUI->limitFocusHFRS->setChecked(true);
3158  m_LimitsUI->limitFocusHFRThresholdPercentage->setValue(focusHFRThresholdPercentage);
3159  m_LimitsUI->limitFocusHFRN->setValue(focusHFRValue);
3160  m_LimitsUI->limitFocusHFRCheckFrames->setValue(focusHFRCheckFrames);
3161  m_LimitsUI->limitFocusHFRAlgorithm->setCurrentIndex(focusHFRAlgorithm);
3162  }
3163  else
3164  m_LimitsUI->limitFocusHFRS->setChecked(false);
3165 
3166  if (focusDeltaTCheck)
3167  {
3168  m_LimitsUI->limitFocusDeltaTS->setChecked(true);
3169  m_LimitsUI->limitFocusDeltaTN->setValue(focusDeltaTValue);
3170  }
3171  else
3172  m_LimitsUI->limitFocusDeltaTS->setChecked(false);
3173 
3174  if (refocusNCheck)
3175  {
3176  m_LimitsUI->limitRefocusS->setChecked(true);
3177  m_LimitsUI->limitRefocusN->setValue(refocusNValue);
3178  }
3179  else
3180  m_LimitsUI->limitRefocusS->setChecked(false);
3181 
3182  m_LimitsUI->limitDitherFrequencyN->setValue(ditherPerJobFrequency);
3183 
3184  syncRefocusOptionsFromGUI();
3185 }
3186 
3188 {
3189  QJsonObject settings =
3190  {
3191  {"deviationCheck", Options::enforceGuideDeviation()},
3192  {"deviationValue", Options::guideDeviation()},
3193  {"ditherPerJobFrequency", m_LimitsUI->limitDitherFrequencyN->value()},
3194  {"focusHFRCheck", m_LimitsUI->limitFocusHFRS->isChecked()},
3195  {"hFRThresholdPercentage", m_LimitsUI->limitFocusHFRThresholdPercentage->value()},
3196  {"focusHFRValue", m_LimitsUI->limitFocusHFRN->value()},
3197  {"inSequenceCheckFrames", m_LimitsUI->limitFocusHFRCheckFrames->value()},
3198  {"hFRCheckAlgorithm", m_LimitsUI->limitFocusHFRAlgorithm->currentIndex()},
3199  {"focusDeltaTCheck", m_LimitsUI->limitFocusDeltaTS->isChecked()},
3200  {"focusDeltaTValue", m_LimitsUI->limitFocusDeltaTN->value()},
3201  {"refocusNCheck", m_LimitsUI->limitRefocusS->isChecked()},
3202  {"refocusNValue", m_LimitsUI->limitRefocusN->value()},
3203  };
3204 
3205  return settings;
3206 }
3207 
3208 void Capture::clearCameraConfiguration()
3209 {
3210  connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this]()
3211  {
3212  //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr);
3213  KSMessageBox::Instance()->disconnect(this);
3214  devices()->getActiveCamera()->setConfig(PURGE_CONFIG);
3215  KStarsData::Instance()->userdb()->DeleteDSLRInfo(devices()->getActiveCamera()->getDeviceName());
3216 
3217  QStringList shutterfulCCDs = Options::shutterfulCCDs();
3218  QStringList shutterlessCCDs = Options::shutterlessCCDs();
3219 
3220  // Remove camera from shutterful and shutterless CCDs
3221  if (shutterfulCCDs.contains(devices()->getActiveCamera()->getDeviceName()))
3222  {
3223  shutterfulCCDs.removeOne(devices()->getActiveCamera()->getDeviceName());
3224  Options::setShutterfulCCDs(shutterfulCCDs);
3225  }
3226  if (shutterlessCCDs.contains(devices()->getActiveCamera()->getDeviceName()))
3227  {
3228  shutterlessCCDs.removeOne(devices()->getActiveCamera()->getDeviceName());
3229  Options::setShutterlessCCDs(shutterlessCCDs);
3230  }
3231 
3232  // For DSLRs, immediately ask them to enter the values again.
3233  if (captureISOS && captureISOS->count() > 0)
3234  {
3235  createDSLRDialog();
3236  }
3237  });
3238 
3239  KSMessageBox::Instance()->questionYesNo( i18n("Reset %1 configuration to default?",
3240  devices()->getActiveCamera()->getDeviceName()),
3241  i18n("Confirmation"), 30);
3242 }
3243 
3244 void Capture::updateJobTable(SequenceJob *job, bool full)
3245 {
3246  if (job == nullptr)
3247  {
3248  QListIterator<SequenceJob *> iter(state()->allJobs());
3249  while (iter.hasNext())
3250  updateJobTable(iter.next(), full);
3251  }
3252  else
3253  {
3254  // find the job's row
3255  int row = state()->allJobs().indexOf(job);
3256  if (row >= 0 && row < queueTable->rowCount())
3257  {
3258  updateRowStyle(job);
3259  QTableWidgetItem *status = queueTable->item(row, JOBTABLE_COL_STATUS);
3260  QTableWidgetItem *count = queueTable->item(row, JOBTABLE_COL_COUNTS);
3261  status->setText(job->getStatusString());
3262  updateJobTableCountCell(job, count);
3263 
3264  if (full)
3265  {
3266  bool isDarkFlat = job->jobType() == SequenceJob::JOBTYPE_DARKFLAT;
3267 
3268  QTableWidgetItem *filter = queueTable->item(row, JOBTABLE_COL_FILTER);
3269  if (FilterPosCombo->findText(job->getCoreProperty(SequenceJob::SJ_Filter).toString()) >= 0 &&
3270  (captureTypeS->currentIndex() == FRAME_LIGHT || captureTypeS->currentIndex() == FRAME_FLAT || isDarkFlat) )
3271  filter->setText(job->getCoreProperty(SequenceJob::SJ_Filter).toString());
3272  else
3273  filter->setText("--");
3274 
3275  QTableWidgetItem *exp = queueTable->item(row, JOBTABLE_COL_EXP);
3276  exp->setText(QString("%L1").arg(job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(), 0, 'f',
3277  captureExposureN->decimals()));
3278 
3279  QTableWidgetItem *type = queueTable->item(row, JOBTABLE_COL_TYPE);
3280  type->setText(isDarkFlat ? i18n("Dark Flat") : CCDFrameTypeNames[job->getFrameType()]);
3281 
3282  QTableWidgetItem *bin = queueTable->item(row, JOBTABLE_COL_BINNING);
3283  QPoint binning = job->getCoreProperty(SequenceJob::SJ_Binning).toPoint();
3284  bin->setText(QString("%1x%2").arg(binning.x()).arg(binning.y()));
3285 
3286  QTableWidgetItem *iso = queueTable->item(row, JOBTABLE_COL_ISO);
3287  if (job->getCoreProperty(SequenceJob::SJ_ISOIndex).toInt() != -1)
3288  iso->setText(captureISOS->itemText(job->getCoreProperty(SequenceJob::SJ_ISOIndex).toInt()));
3289  else if (job->getCoreProperty(SequenceJob::SJ_Gain).toDouble() >= 0)
3290  iso->setText(QString::number(job->getCoreProperty(SequenceJob::SJ_Gain).toDouble(), 'f', 1));
3291  else
3292  iso->setText("--");
3293 
3294  QTableWidgetItem *offset = queueTable->item(row, JOBTABLE_COL_OFFSET);
3295  if (job->getCoreProperty(SequenceJob::SJ_Offset).toDouble() >= 0)
3296  offset->setText(QString::number(job->getCoreProperty(SequenceJob::SJ_Offset).toDouble(), 'f', 1));
3297  else
3298  offset->setText("--");
3299  }
3300 
3301  // update button enablement
3302  if (queueTable->rowCount() > 0)
3303  {
3304  queueSaveAsB->setEnabled(true);
3305  queueSaveB->setEnabled(true);
3306  resetB->setEnabled(true);
3307  state()->setDirty(true);
3308  }
3309 
3310  if (queueTable->rowCount() > 1)
3311  {
3312  queueUpB->setEnabled(true);
3313  queueDownB->setEnabled(true);
3314  }
3315  }
3316  }
3317 }
3318 
3319 void Capture::updateRowStyle(SequenceJob *job)
3320 {
3321  if (job == nullptr)
3322  return;
3323 
3324  // find the job's row
3325  int row = state()->allJobs().indexOf(job);
3326  if (row >= 0 && row < queueTable->rowCount())
3327  {
3328  updateCellStyle(queueTable->item(row, JOBTABLE_COL_STATUS), job->getStatus() == JOB_BUSY);
3329  updateCellStyle(queueTable->item(row, JOBTABLE_COL_FILTER), job->getStatus() == JOB_BUSY);
3330  updateCellStyle(queueTable->item(row, JOBTABLE_COL_COUNTS), job->getStatus() == JOB_BUSY);
3331  updateCellStyle(queueTable->item(row, JOBTABLE_COL_EXP), job->getStatus() == JOB_BUSY);
3332  updateCellStyle(queueTable->item(row, JOBTABLE_COL_TYPE), job->getStatus() == JOB_BUSY);
3333  updateCellStyle(queueTable->item(row, JOBTABLE_COL_BINNING), job->getStatus() == JOB_BUSY);
3334  updateCellStyle(queueTable->item(row, JOBTABLE_COL_ISO), job->getStatus() == JOB_BUSY);
3335  updateCellStyle(queueTable->item(row, JOBTABLE_COL_OFFSET), job->getStatus() == JOB_BUSY);
3336  }
3337 }
3338 
3339 void Capture::updateCellStyle(QTableWidgetItem *cell, bool active)
3340 {
3341  if (cell == nullptr)
3342  return;
3343 
3344  QFont font(cell->font());
3345  font.setBold(active);
3346  font.setItalic(active);
3347  cell->setFont(font);
3348 }
3349 
3350 void Capture::updateJobTableCountCell(SequenceJob *job, QTableWidgetItem *countCell)
3351 {
3352  countCell->setText(QString("%L1/%L2").arg(job->getCompleted()).arg(job->getCoreProperty(SequenceJob::SJ_Count).toInt()));
3353 }
3354 
3355 bool Capture::checkUploadPaths(FilenamePreviewType filenamePreview)
3356 {
3357  // only relevant if we do not generate file name previews
3358  if (filenamePreview != NOT_PREVIEW)
3359  return true;
3360 
3361  if (fileUploadModeS->currentIndex() != ISD::Camera::UPLOAD_CLIENT && fileRemoteDirT->text().isEmpty())
3362  {
3363  KSNotification::error(i18n("You must set remote directory for Local & Both modes."));
3364  return false;
3365  }
3366 
3367  if (fileUploadModeS->currentIndex() != ISD::Camera::UPLOAD_LOCAL && fileDirectoryT->text().isEmpty())
3368  {
3369  KSNotification::error(i18n("You must set local directory for Client & Both modes."));
3370  return false;
3371  }
3372  // everything OK
3373  return true;
3374 }
3375 
3376 QJsonObject Capture::createJsonJob(SequenceJob *job, int currentRow)
3377 {
3378  if (job == nullptr)
3379  return QJsonObject();
3380 
3381  QJsonObject jsonJob = {{"Status", "Idle"}};
3382  bool isDarkFlat = job->jobType() == SequenceJob::JOBTYPE_DARKFLAT;
3383  jsonJob.insert("Filter", FilterPosCombo->currentText());
3384  jsonJob.insert("Count", queueTable->item(currentRow, JOBTABLE_COL_COUNTS)->text());
3385  jsonJob.insert("Exp", queueTable->item(currentRow, JOBTABLE_COL_EXP)->text());
3386  jsonJob.insert("Type", isDarkFlat ? i18n("Dark Flat") : queueTable->item(currentRow, JOBTABLE_COL_TYPE)->text());
3387  jsonJob.insert("Bin", queueTable->item(currentRow, JOBTABLE_COL_BINNING)->text());
3388  jsonJob.insert("ISO/Gain", queueTable->item(currentRow, JOBTABLE_COL_ISO)->text());
3389  jsonJob.insert("Offset", queueTable->item(currentRow, JOBTABLE_COL_OFFSET)->text());
3390 
3391  return jsonJob;
3392 }
3393 
3394 void Capture::setCoolerToggled(bool enabled)
3395 {
3396  auto isToggled = (!enabled && coolerOnB->isChecked()) || (enabled && coolerOffB->isChecked());
3397 
3398  coolerOnB->blockSignals(true);
3399  coolerOnB->setChecked(enabled);
3400  coolerOnB->blockSignals(false);
3401 
3402  coolerOffB->blockSignals(true);
3403  coolerOffB->setChecked(!enabled);
3404  coolerOffB->blockSignals(false);
3405 
3406  if (isToggled)
3407  appendLogText(enabled ? i18n("Cooler is on") : i18n("Cooler is off"));
3408 }
3409 
3410 void Capture::createDSLRDialog()
3411 {
3412  dslrInfoDialog.reset(new DSLRInfo(this, devices()->getActiveCamera()));
3413 
3414  connect(dslrInfoDialog.get(), &DSLRInfo::infoChanged, this, [this]()
3415  {
3416  if (devices()->getActiveCamera())
3417  addDSLRInfo(QString(devices()->getActiveCamera()->getDeviceName()),
3418  dslrInfoDialog->sensorMaxWidth,
3419  dslrInfoDialog->sensorMaxHeight,
3420  dslrInfoDialog->sensorPixelW,
3421  dslrInfoDialog->sensorPixelH);
3422  });
3423 
3424  dslrInfoDialog->show();
3425 
3426  emit dslrInfoRequested(devices()->getActiveCamera()->getDeviceName());
3427 }
3428 
3429 void Capture::setStandAloneGain(double value)
3430 {
3431  QMap<QString, QMap<QString, QVariant> > propertyMap = customPropertiesDialog->getCustomProperties();
3432 
3433  if (m_standAloneUseCcdGain)
3434  {
3435  if (value >= 0)
3436  {
3437  QMap<QString, QVariant> ccdGain;
3438  ccdGain["GAIN"] = value;
3439  propertyMap["CCD_GAIN"] = ccdGain;
3440  }
3441  else
3442  {
3443  propertyMap["CCD_GAIN"].remove("GAIN");
3444  if (propertyMap["CCD_GAIN"].size() == 0)
3445  propertyMap.remove("CCD_GAIN");
3446  }
3447  }
3448  else
3449  {
3450  if (value >= 0)
3451  {
3452  QMap<QString, QVariant> ccdGain = propertyMap["CCD_CONTROLS"];
3453  ccdGain["Gain"] = value;
3454  propertyMap["CCD_CONTROLS"] = ccdGain;
3455  }
3456  else
3457  {
3458  propertyMap["CCD_CONTROLS"].remove("Gain");
3459  if (propertyMap["CCD_CONTROLS"].size() == 0)
3460  propertyMap.remove("CCD_CONTROLS");
3461  }
3462  }
3463 
3464  customPropertiesDialog->setCustomProperties(propertyMap);
3465 }
3466 
3467 void Capture::setStandAloneOffset(double value)
3468 {
3469  QMap<QString, QMap<QString, QVariant> > propertyMap = customPropertiesDialog->getCustomProperties();
3470 
3471  if (m_standAloneUseCcdOffset)
3472  {
3473  if (value >= 0)
3474  {
3475  QMap<QString, QVariant> ccdOffset;
3476  ccdOffset["OFFSET"] = value;
3477  propertyMap["CCD_OFFSET"] = ccdOffset;
3478  }
3479  else
3480  {
3481  propertyMap["CCD_OFFSET"].remove("OFFSET");
3482  if (propertyMap["CCD_OFFSET"].size() == 0)
3483  propertyMap.remove("CCD_OFFSET");
3484  }
3485  }
3486  else
3487  {
3488  if (value >= 0)
3489  {
3490  QMap<QString, QVariant> ccdOffset = propertyMap["CCD_CONTROLS"];
3491  ccdOffset["Offset"] = value;
3492  propertyMap["CCD_CONTROLS"] = ccdOffset;
3493  }
3494  else
3495  {
3496  propertyMap["CCD_CONTROLS"].remove("Offset");
3497  if (propertyMap["CCD_CONTROLS"].size() == 0)
3498  propertyMap.remove("CCD_CONTROLS");
3499  }
3500  }
3501 
3502  customPropertiesDialog->setCustomProperties(propertyMap);
3503 }
3504 void Capture::setGain(double value)
3505 {
3506  if (m_standAlone)
3507  {
3508  setStandAloneGain(value);
3509  return;
3510  }
3511  if (!devices()->getActiveCamera())
3512  return;
3513 
3514  QMap<QString, QMap<QString, QVariant> > customProps = customPropertiesDialog->getCustomProperties();
3515  process()->updateGain(value, customProps);
3516  customPropertiesDialog->setCustomProperties(customProps);
3517 }
3518 
3519 void Capture::setOffset(double value)
3520 {
3521  if (m_standAlone)
3522  {
3523  setStandAloneOffset(value);
3524  return;
3525  }
3526  if (!devices()->getActiveCamera())
3527  return;
3528 
3529  QMap<QString, QMap<QString, QVariant> > customProps = customPropertiesDialog->getCustomProperties();
3530 
3531  process()->updateOffset(value, customProps);
3532  customPropertiesDialog->setCustomProperties(customProps);
3533 }
3534 
3535 void Capture::editFilterName()
3536 {
3537  if (m_standAlone)
3538  {
3539  QStringList labels;
3540  for (int index = 0; index < FilterPosCombo->count(); index++)
3541  labels << FilterPosCombo->itemText(index);
3542  QStringList newLabels;
3543  if (editFilterNameInternal(labels, newLabels))
3544  {
3545  FilterPosCombo->clear();
3546  FilterPosCombo->addItems(newLabels);
3547  }
3548  }
3549  else
3550  {
3551  if (devices()->filterWheel() == nullptr || state()->getCurrentFilterPosition() < 1)
3552  return;
3553 
3554  QStringList labels = m_FilterManager->getFilterLabels();
3555  QStringList newLabels;
3556  if (editFilterNameInternal(labels, newLabels))
3557  m_FilterManager->setFilterNames(newLabels);
3558  }
3559 }
3560 
3561 bool Capture::editFilterNameInternal(const QStringList &labels, QStringList &newLabels)
3562 {
3563  QDialog filterDialog;
3564 
3565  QFormLayout *formLayout = new QFormLayout(&filterDialog);
3566  QVector<QLineEdit *> newLabelEdits;
3567 
3568  for (uint8_t i = 0; i < labels.count(); i++)
3569  {
3570  QLabel *existingLabel = new QLabel(QString("%1. <b>%2</b>").arg(i + 1).arg(labels[i]), &filterDialog);
3571  QLineEdit *newLabel = new QLineEdit(labels[i], &filterDialog);
3572  newLabelEdits.append(newLabel);
3573  formLayout->addRow(existingLabel, newLabel);
3574  }
3575 
3576  QString title = m_standAlone ?
3577  "Edit Filter Names" : devices()->filterWheel()->getDeviceName();
3578  filterDialog.setWindowTitle(title);
3579  filterDialog.setLayout(formLayout);
3581  connect(buttonBox, &QDialogButtonBox::accepted, &filterDialog, &QDialog::accept);
3582  connect(buttonBox, &QDialogButtonBox::rejected, &filterDialog, &QDialog::reject);
3583  filterDialog.layout()->addWidget(buttonBox);
3584 
3585  if (filterDialog.exec() == QDialog::Accepted)
3586  {
3587  QStringList results;
3588  for (uint8_t i = 0; i < labels.count(); i++)
3589  results << newLabelEdits[i]->text();
3590  newLabels = results;
3591  return true;
3592  }
3593  return false;
3594 }
3595 
3596 void Capture::handleScriptsManager()
3597 {
3598  QMap<ScriptTypes, QString> old_scripts = m_scriptsManager->getScripts();
3599 
3600  if (m_scriptsManager->exec() != QDialog::Accepted)
3601  // reset to old value
3602  m_scriptsManager->setScripts(old_scripts);
3603 }
3604 
3606 {
3607  if (!devices()->getActiveCamera())
3608  return;
3609 
3610  double currentRamp, currentThreshold;
3611  if (!devices()->getActiveCamera()->getTemperatureRegulation(currentRamp, currentThreshold))
3612  return;
3613 
3614 
3615  double rMin, rMax, rStep, tMin, tMax, tStep;
3616 
3617  devices()->getActiveCamera()->getMinMaxStep("CCD_TEMP_RAMP", "RAMP_SLOPE", &rMin, &rMax, &rStep);
3618  devices()->getActiveCamera()->getMinMaxStep("CCD_TEMP_RAMP", "RAMP_THRESHOLD", &tMin, &tMax, &tStep);
3619 
3620  QLabel rampLabel(i18nc("Maximum temperature variation over time when regulating.", "Ramp (°C/min):"));
3621  QDoubleSpinBox rampSpin;
3622  rampSpin.setMinimum(rMin);
3623  rampSpin.setMaximum(rMax);
3624  rampSpin.setSingleStep(rStep);
3625  rampSpin.setValue(currentRamp);
3626  rampSpin.setToolTip(i18n("<html><body>"
3627  "<p>Maximum temperature change per minute when cooling or warming the camera. Set zero to disable."
3628  "<p>This setting is read from and stored in the INDI camera driver configuration."
3629  "</body></html>"));
3630 
3631  QLabel thresholdLabel(i18nc("Temperature threshold above which regulation triggers.", "Threshold (°C):"));
3632  QDoubleSpinBox thresholdSpin;
3633  thresholdSpin.setMinimum(tMin);
3634  thresholdSpin.setMaximum(tMax);
3635  thresholdSpin.setSingleStep(tStep);
3636  thresholdSpin.setValue(currentThreshold);
3637  thresholdSpin.setToolTip(i18n("<html><body>"
3638  "<p>Maximum difference between camera and target temperatures triggering regulation."
3639  "<p>This setting is read from and stored in the INDI camera driver configuration."
3640  "</body></html>"));
3641 
3643  layout.addRow(&rampLabel, &rampSpin);
3644  layout.addRow(&thresholdLabel, &thresholdSpin);
3645 
3646  QPointer<QDialog> dialog = new QDialog(this);
3648  connect(&buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
3649  connect(&buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
3650  dialog->setWindowTitle(i18nc("@title:window", "Set Temperature Regulation"));
3651  layout.addWidget(&buttonBox);
3652  dialog->setLayout(&layout);
3653  dialog->setMinimumWidth(300);
3654 
3655  if (dialog->exec() == QDialog::Accepted)
3656  {
3657  if (devices()->getActiveCamera())
3658  devices()->getActiveCamera()->setTemperatureRegulation(rampSpin.value(), thresholdSpin.value());
3659  }
3660 }
3661 
3662 void Capture::updateStartButtons(bool start, bool pause)
3663 {
3664  if (start)
3665  {
3666  // start capturing, therefore next possible action is stopping
3667  startB->setIcon(QIcon::fromTheme("media-playback-stop"));
3668  startB->setToolTip(i18n("Stop Sequence"));
3669  }
3670  else
3671  {
3672  // stop capturing, therefore next possible action is starting
3673  startB->setIcon(QIcon::fromTheme("media-playback-start"));
3674  startB->setToolTip(i18n(pause ? "Resume Sequence" : "Start Sequence"));
3675  }
3676  pauseB->setEnabled(start && !pause);
3677 
3678 }
3679 
3681 {
3682  const auto existingJobs = state()->allJobs().size();
3683  uint8_t jobsAdded = 0;
3684 
3685  for (int i = 0; i < existingJobs; i++)
3686  {
3687  if (state()->allJobs().at(i)->getFrameType() != FRAME_FLAT)
3688  continue;
3689 
3690  syncGUIToJob(state()->allJobs().at(i));
3691 
3692  captureTypeS->setCurrentIndex(FRAME_DARK);
3693  createJob(SequenceJob::JOBTYPE_DARKFLAT);
3694  jobsAdded++;
3695  }
3696 
3697  if (jobsAdded > 0)
3698  {
3699  appendLogText(i18np("One dark flats job was created.", "%1 dark flats jobs were created.", jobsAdded));
3700  }
3701 }
3702 
3703 void Capture::updateJobFromUI(SequenceJob * job, FilenamePreviewType filenamePreview)
3704 {
3705  job->setCoreProperty(SequenceJob::SJ_Format, captureFormatS->currentText());
3706  job->setCoreProperty(SequenceJob::SJ_Encoding, captureEncodingS->currentText());
3707 
3708  if (captureISOS)
3709  job->setISO(captureISOS->currentIndex());
3710 
3711  job->setCoreProperty(SequenceJob::SJ_Gain, getGain());
3712  job->setCoreProperty(SequenceJob::SJ_Offset, getOffset());
3713 
3714  if (cameraTemperatureN->isEnabled())
3715  {
3716  job->setCoreProperty(SequenceJob::SJ_EnforceTemperature, cameraTemperatureS->isChecked());
3717  job->setTargetTemperature(cameraTemperatureN->value());
3718  }
3719 
3720  job->setScripts(m_scriptsManager->getScripts());
3721  job->setUploadMode(static_cast<ISD::Camera::UploadMode>(fileUploadModeS->currentIndex()));
3722  job->setFlatFieldDuration(state()->flatFieldDuration());
3723  job->setCalibrationPreAction(state()->calibrationPreAction());
3724  job->setWallCoord(state()->wallCoord());
3725  job->setCoreProperty(SequenceJob::SJ_TargetADU, state()->targetADU());
3726  job->setCoreProperty(SequenceJob::SJ_TargetADUTolerance, state()->targetADUTolerance());
3727  job->setFrameType(static_cast<CCDFrameType>(qMax(0, captureTypeS->currentIndex())));
3728 
3729  if (FilterPosCombo->currentIndex() != -1 && (m_standAlone || devices()->filterWheel() != nullptr))
3730  job->setTargetFilter(FilterPosCombo->currentIndex() + 1, FilterPosCombo->currentText());
3731 
3732  job->setCoreProperty(SequenceJob::SJ_Exposure, captureExposureN->value());
3733 
3734  job->setCoreProperty(SequenceJob::SJ_Count, captureCountN->value());
3735 
3736  job->setCoreProperty(SequenceJob::SJ_Binning, QPoint(captureBinHN->value(), captureBinVN->value()));
3737 
3738  /* in ms */
3739  job->setCoreProperty(SequenceJob::SJ_Delay, captureDelayN->value() * 1000);
3740 
3741  // Custom Properties
3742  job->setCustomProperties(customPropertiesDialog->getCustomProperties());
3743 
3744  job->setCoreProperty(SequenceJob::SJ_ROI, QRect(captureFrameXN->value(), captureFrameYN->value(), captureFrameWN->value(),
3745  captureFrameHN->value()));
3746  job->setCoreProperty(SequenceJob::SJ_RemoteDirectory, fileRemoteDirT->text());
3747  job->setCoreProperty(SequenceJob::SJ_LocalDirectory, fileDirectoryT->text());
3748  job->setCoreProperty(SequenceJob::SJ_TargetName, targetNameT->text());
3749  job->setCoreProperty(SequenceJob::SJ_PlaceholderFormat, placeholderFormatT->text());
3750  job->setCoreProperty(SequenceJob::SJ_PlaceholderSuffix, formatSuffixN->value());
3751 
3752  job->setCoreProperty(SequenceJob::SJ_DitherPerJobFrequency, m_LimitsUI->limitDitherFrequencyN->value());
3753 
3754  auto placeholderPath = PlaceholderPath();
3755  placeholderPath.addJob(job, placeholderFormatT->text());
3756 
3757  QString signature = placeholderPath.generateSequenceFilename(*job,
3758  filenamePreview != REMOTE_PREVIEW, true, 1,
3759  ".fits", "", false, true);
3760  job->setCoreProperty(SequenceJob::SJ_Signature, signature);
3761 
3762  auto remoteUpload = placeholderPath.generateSequenceFilename(*job,
3763  false,
3764  true,
3765  1,
3766  ".fits",
3767  "",
3768  false,
3769  true);
3770 
3771  auto lastSeparator = remoteUpload.lastIndexOf(QDir::separator());
3772  auto remoteDirectory = remoteUpload.mid(0, lastSeparator);
3773  auto remoteFilename = QString("%1_XXX").arg(remoteUpload.mid(lastSeparator + 1));
3774  job->setCoreProperty(SequenceJob::SJ_RemoteFormatDirectory, remoteDirectory);
3775  job->setCoreProperty(SequenceJob::SJ_RemoteFormatFilename, remoteFilename);
3776 }
3777 
3778 void Capture::setMeridianFlipState(QSharedPointer<MeridianFlipState> newstate)
3779 {
3780  state()->setMeridianFlipState(newstate);
3781  connect(state()->getMeridianFlipState().get(), &MeridianFlipState::newLog, this, &Capture::appendLogText);
3782 }
3783 
3784 void Capture::syncRefocusOptionsFromGUI()
3785 {
3786  Options::setEnforceAutofocusHFR(m_LimitsUI->limitFocusHFRS->isChecked());
3787  Options::setHFRThresholdPercentage(m_LimitsUI->limitFocusHFRThresholdPercentage->value());
3788  Options::setHFRDeviation(m_LimitsUI->limitFocusHFRN->value());
3789  Options::setInSequenceCheckFrames(m_LimitsUI->limitFocusHFRCheckFrames->value());
3790  Options::setHFRCheckAlgorithm(m_LimitsUI->limitFocusHFRAlgorithm->currentIndex());
3791  Options::setEnforceAutofocusOnTemperature(m_LimitsUI->limitFocusDeltaTS->isChecked());
3792  Options::setMaxFocusTemperatureDelta(m_LimitsUI->limitFocusDeltaTN->value());
3793  Options::setEnforceRefocusEveryN(m_LimitsUI->limitRefocusS->isChecked());
3794  Options::setRefocusEveryN(static_cast<uint>(m_LimitsUI->limitRefocusN->value()));
3795  Options::setRefocusAfterMeridianFlip(m_LimitsUI->meridianRefocusS->isChecked());
3796 }
3797 
3798 QJsonObject Capture::currentScope()
3799 {
3800  QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain);
3801  if (activeCamera() && trainID.isValid())
3802  {
3803  auto id = trainID.toUInt();
3804  auto name = OpticalTrainManager::Instance()->name(id);
3805  return OpticalTrainManager::Instance()->getScope(name);
3806  }
3807  // return empty JSON object
3808  return QJsonObject();
3809 }
3810 
3811 double Capture::currentReducer()
3812 {
3813  QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain);
3814  if (activeCamera() && trainID.isValid())
3815  {
3816  auto id = trainID.toUInt();
3817  auto name = OpticalTrainManager::Instance()->name(id);
3818  return OpticalTrainManager::Instance()->getReducer(name);
3819  }
3820  // no reducer available
3821  return 1.0;
3822 }
3823 
3824 double Capture::currentAperture()
3825 {
3826  auto scope = currentScope();
3827 
3828  double focalLength = scope["focal_length"].toDouble(-1);
3829  double aperture = scope["aperture"].toDouble(-1);
3830  double focalRatio = scope["focal_ratio"].toDouble(-1);
3831 
3832  // DSLR Lens Aperture
3833  if (aperture < 0 && focalRatio > 0)
3834  aperture = focalLength * focalRatio;
3835 
3836  return aperture;
3837 }
3838 
3839 void Capture::setupOpticalTrainManager()
3840 {
3841  connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &Capture::refreshOpticalTrain);
3842  connect(trainB, &QPushButton::clicked, this, [this]()
3843  {
3844  OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
3845  });
3846  connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
3847  {
3848  ProfileSettings::Instance()->setOneSetting(ProfileSettings::CaptureOpticalTrain,
3849  OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index)));
3850  refreshOpticalTrain();
3851  emit trainChanged();
3852  });
3853 }
3854 
3855 void Capture::refreshOpticalTrain()
3856 {
3857  opticalTrainCombo->blockSignals(true);
3858  opticalTrainCombo->clear();
3859  opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
3860  trainB->setEnabled(true);
3861 
3862  QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain);
3863 
3864  if (trainID.isValid())
3865  {
3866  auto id = trainID.toUInt();
3867 
3868  // If train not found, select the first one available.
3869  if (OpticalTrainManager::Instance()->exists(id) == false)
3870  {
3871  qCWarning(KSTARS_EKOS_CAPTURE) << "Optical train doesn't exist for id" << id;
3872  id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
3873  }
3874 
3875  auto name = OpticalTrainManager::Instance()->name(id);
3876 
3877  opticalTrainCombo->setCurrentText(name);
3878  process()->refreshOpticalTrain(name);
3879  }
3880 
3881  opticalTrainCombo->blockSignals(false);
3882 }
3883 
3884 void Capture::generatePreviewFilename()
3885 {
3886  if (state()->isCaptureRunning() == false)
3887  {
3888  placeholderFormatT->setToolTip(previewFilename( LOCAL_PREVIEW ));
3889  emit newLocalPreview(placeholderFormatT->toolTip());
3890 
3891  if (fileUploadModeS->currentIndex() != 0)
3892  fileRemoteDirT->setToolTip(previewFilename( REMOTE_PREVIEW ));
3893  }
3894 }
3895 
3896 QString Capture::previewFilename(FilenamePreviewType previewType)
3897 {
3898  QString previewText;
3899  QString m_format;
3900  auto separator = QDir::separator();
3901 
3902  if (previewType == LOCAL_PREVIEW)
3903  {
3904  if(!fileDirectoryT->text().endsWith(separator) && !placeholderFormatT->text().startsWith(separator))
3905  placeholderFormatT->setText(separator + placeholderFormatT->text());
3906  m_format = fileDirectoryT->text() + placeholderFormatT->text() + formatSuffixN->prefix() + formatSuffixN->cleanText();
3907  }
3908  else if (previewType == REMOTE_PREVIEW)
3909  m_format = fileRemoteDirT->text();
3910 
3911  //Guard against an empty format to avoid the empty directory warning pop-up in addjob
3912  if (m_format.isEmpty())
3913  return previewText;
3914  // Tags %d & %p disable for now for simplicity
3915  // else if (state()->sequenceURL().toLocalFile().isEmpty() && (m_format.contains("%d") || m_format.contains("%p")
3916  // || m_format.contains("%f")))
3917  else if (state()->sequenceURL().toLocalFile().isEmpty() && m_format.contains("%f"))
3918  previewText = ("Save the sequence file to show filename preview");
3919  else
3920  {
3921  // create temporarily a sequence job
3922  SequenceJob *m_job = createJob(SequenceJob::JOBTYPE_PREVIEW, previewType);
3923  if (m_job == nullptr)
3924  return previewText;
3925 
3926  QString previewSeq;
3927  if (state()->sequenceURL().toLocalFile().isEmpty())
3928  {
3929  if (m_format.startsWith(separator))
3930  previewSeq = m_format.left(m_format.lastIndexOf(separator));
3931  }
3932  else
3933  previewSeq = state()->sequenceURL().toLocalFile();
3934  auto m_placeholderPath = PlaceholderPath(previewSeq);
3935 
3936  QString extension;
3937  if (captureEncodingS->currentText() == "FITS")
3938  extension = ".fits";
3939  else if (captureEncodingS->currentText() == "XISF")
3940  extension = ".xisf";
3941  else
3942  extension = ".[NATIVE]";
3943  previewText = m_placeholderPath.generateSequenceFilename(*m_job, previewType == LOCAL_PREVIEW, true, 1,
3944  extension, "", false);
3945  previewText = QDir::toNativeSeparators(previewText);
3946  // we do not use it any more
3947  m_job->deleteLater();
3948  }
3949 
3950  // Must change directory separate to UNIX style for remote
3951  if (previewType == REMOTE_PREVIEW)
3952  previewText.replace(separator, "/");
3953 
3954  return previewText;
3955 }
3956 
3957 void Capture::openExposureCalculatorDialog()
3958 {
3959  qCInfo(KSTARS_EKOS_CAPTURE) << "Instantiating an Exposure Calculator";
3960 
3961  // Learn how to read these from indi
3962  double preferredSkyQuality = 20.5;
3963 
3964  auto scope = currentScope();
3965  double focalRatio = scope["focal_ratio"].toDouble(-1);
3966 
3967  auto reducedFocalLength = currentReducer() * scope["focal_length"].toDouble(-1);
3968  auto aperture = currentAperture();
3969  auto reducedFocalRatio = (focalRatio > 0 || aperture == 0) ? focalRatio : reducedFocalLength / aperture;
3970 
3971  if (devices()->getActiveCamera() != nullptr)
3972  {
3973  qCInfo(KSTARS_EKOS_CAPTURE) << "set ExposureCalculator preferred camera to active camera id: "
3974  << devices()->getActiveCamera()->getDeviceName();
3975  }
3976 
3977  QPointer<ExposureCalculatorDialog> anExposureCalculatorDialog(new ExposureCalculatorDialog(KStars::Instance(),
3978  preferredSkyQuality,
3979  reducedFocalRatio,
3980  devices()->getActiveCamera()->getDeviceName()));
3981  anExposureCalculatorDialog->setAttribute(Qt::WA_DeleteOnClose);
3982  anExposureCalculatorDialog->show();
3983 }
3984 
3986 {
3987  return process()->hasCoolerControl();
3988 }
3989 
3990 bool Capture::setCoolerControl(bool enable)
3991 {
3992  return process()->setCoolerControl(enable);
3993 }
3994 
3996 {
3997  process()->removeDevice(device);
3998 }
3999 
4001 {
4002  process()->startNextPendingJob();
4003 }
4004 
4005 void Capture::stop(CaptureState targetState)
4006 {
4007  process()->stopCapturing(targetState);
4008 }
4009 
4010 void Capture::toggleVideo(bool enabled)
4011 {
4012  process()->toggleVideo(enabled);
4013 }
4014 
4015 void Capture::setTargetName(const QString &newTargetName)
4016 {
4017  // target is changed only if no job is running
4018  if (activeJob() == nullptr)
4019  {
4020  // set the target name in the currently selected job
4021  targetNameT->setText(newTargetName);
4022  auto rows = queueTable->selectionModel()->selectedRows();
4023  if(rows.count() > 0)
4024  {
4025  // take the first one, since we are in single selection mode
4026  int pos = rows.constFirst().row();
4027 
4028  if (state()->allJobs().size() > pos)
4029  state()->allJobs().at(pos)->setCoreProperty(SequenceJob::SJ_TargetName, newTargetName);
4030  }
4031 
4032  emit captureTarget(newTargetName);
4033  }
4034 }
4035 
4036 QString Capture::getTargetName()
4037 {
4038  if (activeJob())
4039  return activeJob()->getCoreProperty(SequenceJob::SJ_TargetName).toString();
4040  else
4041  return "";
4042 }
4043 
4045 {
4046  process()->restartCamera(name);
4047 }
4048 
4050 {
4051  process()->capturePreview();
4052 }
4053 
4055 {
4056  process()->capturePreview(true);
4057 }
4058 
4059 double Capture::getGain()
4060 {
4061  return devices()->cameraGain(customPropertiesDialog->getCustomProperties());
4062 }
4063 
4064 double Capture::getOffset()
4065 {
4066  return devices()->cameraOffset(customPropertiesDialog->getCustomProperties());
4067 }
4068 
4069 void Capture::setHFR(double newHFR, int, bool inAutofocus)
4070 {
4071  state()->getRefocusState()->setFocusHFR(newHFR, inAutofocus);
4072 }
4073 
4074 ISD::Camera *Capture::activeCamera()
4075 {
4076  return m_captureDeviceAdaptor->getActiveCamera();
4077 }
4078 }
void addJob(SequenceJob *job)
addJob Add a new job to the UI.
Definition: capture.cpp:1808
void append(const T &value)
T & first()
const QJsonArray & getSequence() const
getSequence Return the JSON representation of the current sequeue queue
Definition: capture.h:457
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
void setTargetTemperature(double temperature)
setTemperature Set the target CCD temperature in the GUI settings.
Definition: capture.h:775
@ CAPTURE_COMPLETE
Definition: ekos.h:112
QUrl getOpenFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options, const QStringList &supportedSchemes)
AlignHCenter
void doubleClicked(const QModelIndex &index)
T * data() const const
bool isValid() const const
void updateJobFromUI(SequenceJob *job, FilenamePreviewType filenamePreview=NOT_PREVIEW)
updateJobFromUI Update all job attributes from the UI settings.
Definition: capture.cpp:3703
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void addRow(QWidget *label, QWidget *field)
QString number(int n, int base)
void setTextAlignment(int alignment)
virtual void reject()
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
Q_SCRIPTABLE Q_NOREPLY void ignoreSequenceHistory()
DBUS interface function.
Definition: capture.cpp:2367
Type type(const QSqlDatabase &db)
Ekos is an advanced Astrophotography tool for Linux. It is based on a modular extensible framework to...
Definition: align.cpp:77
virtual bool open(QIODevice::OpenMode mode) override
void generateDarkFlats()
generateDarkFlats Generate a list of dark flat jobs from available flat frames.
Definition: capture.cpp:3680
Q_SCRIPTABLE Q_NOREPLY void stop(CaptureState targetState=CAPTURE_IDLE)
DBUS interface function.
Definition: capture.cpp:4005
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
int count(const T &value) const const
QLayout * layout() const const
QChar separator()
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
void captureImage()
captureImage Initiates image capture in the active job.
void clear()
QString url(QUrl::FormattingOptions options) const const
void append(const T &value)
void clicked(bool checked)
virtual bool event(QEvent *event) override
Q_SCRIPTABLE Q_NOREPLY void start()
DBUS interface function.
Definition: capture.cpp:4000
QIcon fromTheme(const QString &name)
void editingFinished()
void captureRunning()
captureStarted Manage the result when capturing has been started
Definition: capture.cpp:1736
void updateStartButtons(bool start, bool pause=false)
updateStartButtons Update the start and the pause button to new states of capturing
Definition: capture.cpp:3662
bool GetAllObservers(QList< OAL::Observer * > &observer_list)
Updates the passed reference of observer_list with all observers The original content of the list is ...
Definition: ksuserdb.cpp:713
QSharedPointer< MeridianFlipState > getMeridianFlipState()
MeridianFlipState Access to the meridian flip state machine.
Definition: capture.h:755
void editJobFinished()
jobEditFinished Editing of an existing job finished, update its attributes from the UI settings.
Definition: capture.cpp:1897
KSUserDB * userdb()
Definition: kstarsdata.h:215
@ CAPTURE_SETTING_ROTATOR
Definition: ekos.h:108
void updateCCDTemperature(double value)
updateCCDTemperature Update CCD temperature in capture module.
Definition: capture.cpp:1785
bool registerObject(const QString &path, QObject *object, QDBusConnection::RegisterOptions options)
Capture(bool standAlone=false)
Definition: capture.cpp:242
int x() const const
int y() const const
Q_SCRIPTABLE Q_NOREPLY void setInSequenceFocus(bool enable, double HFR)
DBUS interface function.
Definition: capture.cpp:2601
QString homePath()
void setValue(double val)
void itemSelectionChanged()
QPointer< CaptureProcess > process() const
process shortcut for the process engine
Definition: capture.h:649
Q_SCRIPTABLE Q_NOREPLY void toggleVideo(bool enabled)
DBUS interface function.
Definition: capture.cpp:4010
KIOCORE_EXPORT QStringList list(const QString &fileClass)
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
void setRotator(QString name)
Add new Rotator.
Definition: capture.cpp:905
Q_SCRIPTABLE bool setCoolerControl(bool enable)
DBUS interface function.
Definition: capture.cpp:3990
Q_SCRIPTABLE Q_NOREPLY void setMaximumGuidingDeviation(bool enable, double value)
DBUS interface function.
Definition: capture.cpp:2594
void jobStarting()
captureStarted Change the UI after the capturing process has been started.
Definition: capture.cpp:946
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual QString fileName() const const override
void valueChanged(double d)
Q_SCRIPTABLE Q_NOREPLY void clearSequenceQueue()
DBUS interface function.
Definition: capture.cpp:2608
void toggled(bool checked)
void removeAt(int i)
QString itemText(int index) const const
static KStars * Instance()
Definition: kstars.h:123
KGuiItem cancel()
int remove(const Key &key)
void showTemperatureRegulation()
showTemperatureRegulation Toggle temperature regulation dialog which sets temperature ramp and thresh...
Definition: capture.cpp:3605
bool isValid() const const
Q_SCRIPTABLE Q_NOREPLY void restartCamera(const QString &name)
DBus interface function.
Definition: capture.cpp:4044
SequenceJob * createJob(SequenceJob::SequenceJobType jobtype=SequenceJob::JOBTYPE_BATCH, FilenamePreviewType filenamePreview=NOT_PREVIEW)
createJob Create a new job with the settings given in the GUI.
Definition: capture.cpp:1814
int size() const const
void replace(int i, const QJsonValue &value)
void prepend(const T &value)
Q_SCRIPTABLE Q_NOREPLY void setObserverName(const QString &value)
DBus interface function.
Definition: capture.h:634
Sequence Job is a container for the details required to capture a series of images.
Definition: sequencejob.h:16
ItemIsSelectable
@ CAPTURE_SETTING_TEMPERATURE
Definition: ekos.h:107
QString i18n(const char *text, const TYPE &arg...)
QDBusConnection sessionBus()
QJsonObject::iterator insert(const QString &key, const QJsonValue &value)
bool isEmpty() const const
void setLimitSettings(const QJsonObject &settings)
setLimitSettings Set limit settings
Definition: capture.cpp:3131
RemoveFilename
void setFocusTemperatureDelta(double focusTemperatureDelta, double absTemperature)
updateAdaptiveFocusStatus Handle new focus state
Definition: capture.cpp:2112
void currentTextChanged(const QString &text)
bool removeOne(const T &value)
The CaptureProcess class holds the entire business logic to control capturing execution.
void textChanged(const QString &text)
void timeout()
bool isEmpty() const const
QUrl getSaveFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options, const QStringList &supportedSchemes)
QUrl fromLocalFile(const QString &localFile)
QString toNativeSeparators(const QString &pathName)
void setText(const QString &text)
QTextStream & bin(QTextStream &stream)
@ CAPTURE_IDLE
Definition: ekos.h:93
void setWindowTitle(const QString &)
const T & at(int i) const const
uint toUInt(bool *ok) const const
void currentRowChanged(const QModelIndex &current, const QModelIndex &previous)
CaptureState
Capture states.
Definition: ekos.h:91
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
virtual void accept()
void captureStarted(CaptureModuleState::CAPTUREResult rc)
captureStarted Manage the result when capturing has been started
void clear()
UniqueConnection
Q_SCRIPTABLE Q_NOREPLY void pause()
DBUS interface function.
Definition: capture.cpp:931
bool isEmpty() const const
Q_SCRIPTABLE Q_NOREPLY void suspend()
DBUS interface function.
Definition: capture.h:592
QString toLocalFile() const const
QJsonValue value(const QString &key) const const
QTextStream & dec(QTextStream &stream)
void removeDevice(const QSharedPointer< ISD::GenericDevice > &device)
Generic method for removing any connected device.
Definition: capture.cpp:3995
void setHFR(double newHFR, int position, bool inAutofocus)
setHFR Receive the measured HFR value of the latest frame
Definition: capture.cpp:4069
QJsonObject getPresetSettings()
getSettings get current capture settings as a JSON Object
Definition: capture.cpp:2489
QString name(StandardAction id)
virtual int exec()
void startFraming()
startFraming Like captureOne but repeating.
Definition: capture.cpp:4054
void setupUi(QWidget *widget)
int indexOf(QStringView str, int from) const const
Q_SCRIPTABLE bool hasCoolerControl()
DBUS interface function.
Definition: capture.cpp:3985
QCA_EXPORT QVariant getProperty(const QString &name)
void setEnabled(bool)
T * get() const const
Q_SCRIPTABLE bool loadSequenceQueue(const QString &fileURL, QString targetName="")
DBUS interface function.
Definition: capture.cpp:2231
QString & replace(int position, int n, QChar after)
@ CAPTURE_ABORTED
Definition: ekos.h:99
T & last()
void show()
void setPresetSettings(const QJsonObject &settings)
setSettings Set capture settings
Definition: capture.cpp:3018
int row() const const
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void setFilterWheel(QString name)
Add new Filter Wheel.
Definition: capture.cpp:877
QString label(StandardShortcut id)
An angle, stored as degrees, but expressible in many ways.
Definition: dms.h:37
QFont font() const const
bool setVideoLimits(uint16_t maxBufferSize, uint16_t maxPreviewFPS)
setVideoLimits sets the buffer size and max preview fps for live preview
Definition: capture.cpp:2747
Captures single or sequence of images from a CCD. The capture class support capturing single or multi...
Definition: capture.h:95
void resetFrame()
resetFrame Reset frame settings of the camera
void updateCurrentFilterPosition()
shortcut for updating the current filter information for the state machine
Definition: capture.cpp:1583
void setMinimum(double min)
void textEdited(const QString &text)
Q_SCRIPTABLE Q_NOREPLY void toggleSequence()
DBUS interface function.
Definition: capture.cpp:937
bool updateCamera()
Add new Camera.
Definition: capture.cpp:843
void setFont(const QFont &font)
Q_SCRIPTABLE Q_NOREPLY void abort()
DBUS interface function.
Definition: capture.h:580
void newTargetName(const QString &name)
updateTargetName React upon a new capture target name
Definition: capture.cpp:2032
KIOCORE_EXPORT QString dir(const QString &fileClass)
void refreshCameraSettings()
checkCamera Refreshes the CCD information in the capture module.
Definition: capture.cpp:977
KGuiItem add()
void setFlags(Qt::ItemFlags flags)
QJsonObject getLimitSettings()
getLimitSettings Get Limit Settings
Definition: capture.cpp:3187
void setToolTip(const QString &)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void editingFinished()
QString left(int n) const const
void setValue(int val)
Q_SCRIPTABLE Q_NOREPLY void setTargetName(const QString &newTargetName)
DBus interface function.
Definition: capture.cpp:4015
bool isValid(QStringView ifopt)
void addWidget(QWidget *w)
void currentIndexChanged(int index)
@ CAPTURE_PAUSED
Definition: ekos.h:97
KGuiItem cont()
void clear()
Q_SCRIPTABLE Q_NOREPLY void clearAutoFocusHFR()
DBUS interface function.
Definition: capture.cpp:2639
QString i18nc(const char *context, const char *text, const TYPE &arg...)
@ CAPTURE_CHANGING_FILTER
Definition: ekos.h:105
void capturePreview()
capturePreview Capture a single preview image
Definition: capture.cpp:4049
void registerNewModule(const QString &name)
registerNewModule Register an Ekos module as it arrives via DBus and create the appropriate DBus inte...
Definition: capture.cpp:956
void addItem(const QString &text, const QVariant &userData)
void setFileSettings(const QJsonObject &settings)
setFileSettings Set File Settings
Definition: capture.cpp:3099
KStarsData * data() const
Definition: kstars.h:135
void executeJob()
executeJob Start the execution of #activeJob by initiating updatePreCaptureCalibrationStatus().
@ CAPTURE_PAUSE_PLANNED
Definition: ekos.h:96
void setFocusStatus(FocusState newstate)
setFocusStatus Forward the new focus state to the capture module state machine
Definition: capture.cpp:2130
void refreshFilterSettings()
checkFilter Refreshes the filter wheel information in the capture module.
Definition: capture.cpp:1591
bool removeJob(int index=-1)
removeJob Remove a job sequence from the queue
Definition: capture.cpp:1937
void valueChanged(int i)
void removeAt(int i)
void moveJob(bool up)
moveJobUp Move the job in the sequence queue one place up or down.
Definition: capture.cpp:1997
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
void setLayout(QLayout *layout)
void addDSLRInfo(const QString &model, uint32_t maxW, uint32_t maxH, double pixelW, double pixelH)
addDSLRInfo Save DSLR Info the in the database.
Definition: capture.cpp:2958
AlignState
Definition: ekos.h:144
QJsonObject getFileSettings()
getFileSettings Compile file setting
Definition: capture.cpp:3116
QString getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options)
void setGuideDeviation(double delta_ra, double delta_dec)
setGuideDeviation Set the guiding deviation as measured by the guiding module.
Definition: capture.cpp:2121
void setFocus()
void processCaptureTimeout()
processCaptureTimeout If exposure timed out, let's handle it.
Q_SCRIPTABLE bool saveSequenceQueue(const QString &path)
DBUS interface function.
Definition: capture.cpp:2320
void setSingleStep(double val)
@ CAPTURE_GUIDER_DRIFT
Definition: ekos.h:106
WA_LayoutUsesWidgetRect
QString & append(QChar ch)
darkGreen
void updateFocusStatus(FocusState newstate)
updateFocusStatus Handle new focus state
Definition: capture.cpp:2136
void setMaximum(double max)
char * toString(const EngineQuery &query)
void accepted()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Thu Feb 15 2024 04:02:53 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.