File indexing completed on 2024-04-28 03:43:13
0001 /* 0002 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "capture.h" 0008 0009 #include "captureprocess.h" 0010 #include "capturemodulestate.h" 0011 #include "capturedeviceadaptor.h" 0012 #include "captureadaptor.h" 0013 #include "refocusstate.h" 0014 #include "kstars.h" 0015 #include "kstarsdata.h" 0016 #include "Options.h" 0017 #include "rotatorsettings.h" 0018 #include "sequencejob.h" 0019 #include "sequencequeue.h" 0020 #include "placeholderpath.h" 0021 #include "ui_calibrationoptions.h" 0022 #include "auxiliary/ksmessagebox.h" 0023 #include "ekos/manager.h" 0024 #include "ekos/auxiliary/darklibrary.h" 0025 #include "ekos/auxiliary/profilesettings.h" 0026 #include "ekos/auxiliary/opticaltrainmanager.h" 0027 #include "scriptsmanager.h" 0028 #include "fitsviewer/fitsdata.h" 0029 #include "indi/driverinfo.h" 0030 #include "indi/indifilterwheel.h" 0031 #include "indi/indicamera.h" 0032 #include "indi/indirotator.h" 0033 #include "oal/observeradd.h" 0034 #include "ekos/guide/guide.h" 0035 #include "exposurecalculator/exposurecalculatordialog.h" 0036 #include "dslrinfodialog.h" 0037 #include "ekos/auxiliary/rotatorutils.h" 0038 #include <basedevice.h> 0039 0040 #include <ekos_capture_debug.h> 0041 0042 #define MF_TIMER_TIMEOUT 90000 0043 #define MF_RA_DIFF_LIMIT 4 0044 0045 // Qt version calming 0046 #include <qtendl.h> 0047 0048 namespace 0049 { 0050 0051 // Columns in the job table 0052 enum JobTableColumnIndex 0053 { 0054 JOBTABLE_COL_STATUS = 0, 0055 JOBTABLE_COL_FILTER, 0056 JOBTABLE_COL_COUNTS, 0057 JOBTABLE_COL_EXP, 0058 JOBTABLE_COL_TYPE, 0059 JOBTABLE_COL_BINNING, 0060 JOBTABLE_COL_ISO, 0061 JOBTABLE_COL_OFFSET 0062 }; 0063 0064 // Encode and decode for storing stand-alone options which are really QStringLists. 0065 QString standAloneEncode(const QStringList &list) 0066 { 0067 if (list.size() == 0) 0068 return ""; 0069 QString encoding; 0070 encoding.append(list[0]); 0071 for (int i = 1; i < list.size(); ++i) 0072 { 0073 encoding.append(","); 0074 encoding.append(list[i]); 0075 } 0076 return encoding; 0077 } 0078 0079 QStringList standAloneDecode(const QString &encoding) 0080 { 0081 auto dec = encoding.split(","); 0082 if (dec.size() == 1 && dec[0] == "") 0083 return QStringList(); 0084 return dec; 0085 } 0086 0087 // Adds the items to the QComboBox if they're not there already. 0088 void addToCombo(QComboBox *combo, const QStringList &items) 0089 { 0090 if (items.size() == 0) 0091 return; 0092 QStringList existingItems; 0093 for (int index = 0; index < combo->count(); index++) 0094 existingItems << combo->itemText(index); 0095 0096 for (const auto &item : items) 0097 if (existingItems.indexOf(item) == -1) 0098 combo->addItem(item); 0099 } 0100 0101 } // namespace 0102 0103 namespace Ekos 0104 { 0105 0106 // There are many widgets that are not used in stand-alone mode and should be made invisible and disabled. 0107 void Capture::initStandAlone() 0108 { 0109 QList<QWidget*> unusedWidgets = 0110 { 0111 opticalTrainLabel, opticalTrainCombo, trainB, cameraRowLabel, cameraLabel, restartCameraB, 0112 clearConfigurationB, coolerOnB, coolerOffB, setTemperatureB, temperatureRegulationB, 0113 previewB, loopB, liveVideoB, startB, pauseB, resetB, processGrid, darkB, darkLibraryB, 0114 filterManagerB 0115 }; 0116 for (auto &widget : unusedWidgets) 0117 { 0118 widget->setEnabled(false); 0119 widget->setVisible(false); 0120 } 0121 CCDFWGroup->setTitle("Settings"); 0122 } 0123 0124 // Gets called when the stand-alone editor gets a show event. 0125 // Do this initialization here so that if the live capture module was 0126 // used after startup, it will have set more recent remembered values. 0127 void Capture::onStandAloneShow(QShowEvent* event) 0128 { 0129 Q_UNUSED(event); 0130 QSharedPointer<FilterManager> fm; 0131 0132 // Default comment if there is no previously saved Options::CaptureStandAlone... parameters. 0133 QString comment = i18n("<b><font color=\"red\">Please run the Capture tab connected to INDI with your desired " 0134 "camera/filterbank at least once before using the Sequence Editor. </font></b><p>"); 0135 if (Options::captureStandAloneTimestamp().size() > 0) 0136 comment = i18n("<b>Using camera and filterwheel attributes from Capture session started at %1.</b>" 0137 "<p>If you wish to use other cameras/filterbanks, please edit the sequence " 0138 "using the Capture tab.<br>It is not recommended to overwrite a sequence file currently running, " 0139 "please rename it instead.</p><p>", Options::captureStandAloneTimestamp()); 0140 sequenceEditorComment->setVisible(true); 0141 sequenceEditorComment->setEnabled(true); 0142 sequenceEditorComment->setStyleSheet("{color: #C0BBFE}"); 0143 sequenceEditorComment->setText(comment); 0144 0145 // Add extra load and save buttons at the bottom of the window. 0146 loadSaveBox->setEnabled(true); 0147 loadSaveBox->setVisible(true); 0148 connect(esqSaveAsB, &QPushButton::clicked, this, &Capture::saveSequenceQueueAs); 0149 connect(esqLoadB, &QPushButton::clicked, this, static_cast<void(Capture::*)()>(&Capture::loadSequenceQueue)); 0150 0151 // This currently gets the filters from filter manager #0. 0152 // Could try all of them? 0153 bool ok = Manager::Instance()->getFilterManager(fm); 0154 if (ok) 0155 addToCombo(FilterPosCombo, fm->getFilterLabels()); 0156 addToCombo(FilterPosCombo, standAloneDecode(Options::captureStandAloneFilters())); 0157 0158 if (FilterPosCombo->count() > 0) 0159 filterEditB->setEnabled(true); 0160 0161 captureGainN->setEnabled(true); 0162 captureGainN->setValue(GainSpinSpecialValue); 0163 captureGainN->setSpecialValueText(i18n("--")); 0164 0165 captureOffsetN->setEnabled(true); 0166 captureOffsetN->setValue(OffsetSpinSpecialValue); 0167 captureOffsetN->setSpecialValueText(i18n("--")); 0168 0169 // Always add these strings to the types menu. Might also add other ones 0170 // that were used in the last capture session. 0171 const QStringList frameTypes = {"Light", "Dark", "Bias", "Flat"}; 0172 captureTypeS->clear(); 0173 captureTypeS->addItems(frameTypes); 0174 addToCombo(captureTypeS, standAloneDecode(Options::captureStandAloneTypes())); 0175 0176 // Always add these strings to the encodings menu. Might also add other ones 0177 // that were used in the last capture session. 0178 const QStringList frameEncodings = {"FITS", "Native", "XISF"}; 0179 captureEncodingS->clear(); 0180 captureEncodingS->addItems(frameEncodings); 0181 addToCombo(captureEncodingS, standAloneDecode(Options::captureStandAloneEncodings())); 0182 0183 const QStringList frameFormats = {}; 0184 captureFormatS->clear(); 0185 if (frameFormats.size() > 0) 0186 captureFormatS->addItems(frameFormats); 0187 addToCombo(captureFormatS, standAloneDecode(Options::captureStandAloneFormats())); 0188 0189 cameraTemperatureN->setEnabled(true); 0190 0191 // No pre-configured ISOs are available--would be too much of a guess, but 0192 // we will use ISOs from the last live capture session. 0193 QStringList isoList = standAloneDecode(Options::captureStandAloneISOs()); 0194 if (isoList.size() > 0) 0195 { 0196 captureISOS->clear(); 0197 captureISOS->addItems(isoList); 0198 captureISOS->setCurrentIndex(Options::captureStandAloneISOIndex()); 0199 captureISOS->blockSignals(false); 0200 captureISOS->setEnabled(true); 0201 } 0202 else 0203 { 0204 captureISOS->blockSignals(true); 0205 captureISOS->clear(); 0206 captureISOS->setEnabled(false); 0207 } 0208 0209 // Remember the sensor width and height from the last live session. 0210 // The user can always edit the input box. 0211 constexpr int maxFrame = 20000; 0212 captureFrameXN->setMaximum(static_cast<int>(maxFrame)); 0213 captureFrameYN->setMaximum(static_cast<int>(maxFrame)); 0214 captureFrameWN->setMaximum(static_cast<int>(maxFrame)); 0215 captureFrameHN->setMaximum(static_cast<int>(maxFrame)); 0216 QStringList whList = standAloneDecode(Options::captureStandAloneWHGO()); 0217 if (whList.size() == 4) 0218 { 0219 captureFrameWN->setValue(whList[0].toInt()); 0220 captureFrameHN->setValue(whList[1].toInt()); 0221 m_standAloneUseCcdGain = whList[2] == "CCD_GAIN"; 0222 m_standAloneUseCcdOffset = whList[3] == "CCD_OFFSET"; 0223 } 0224 0225 connect(captureGainN, &QDoubleSpinBox::editingFinished, this, [this]() 0226 { 0227 if (captureGainN->value() != GainSpinSpecialValue) 0228 setGain(captureGainN->value()); 0229 else 0230 setGain(-1); 0231 }); 0232 0233 connect(captureOffsetN, &QDoubleSpinBox::editingFinished, this, [this]() 0234 { 0235 if (captureOffsetN->value() != OffsetSpinSpecialValue) 0236 setOffset(captureOffsetN->value()); 0237 else 0238 setOffset(-1); 0239 }); 0240 } 0241 0242 Capture::Capture(bool standAlone) : m_standAlone(standAlone) 0243 { 0244 setupUi(this); 0245 0246 if (!m_standAlone) 0247 { 0248 qRegisterMetaType<CaptureState>("CaptureState"); 0249 qDBusRegisterMetaType<CaptureState>(); 0250 } 0251 new CaptureAdaptor(this); 0252 m_captureModuleState.reset(new CaptureModuleState()); 0253 m_captureDeviceAdaptor.reset(new CaptureDeviceAdaptor()); 0254 m_captureProcess = new CaptureProcess(state(), m_captureDeviceAdaptor); 0255 0256 state()->getSequenceQueue()->loadOptions(); 0257 0258 if (m_standAlone) 0259 initStandAlone(); 0260 0261 if (!m_standAlone) 0262 { 0263 QDBusConnection::sessionBus().registerObject("/KStars/Ekos/Capture", this); 0264 QPointer<QDBusInterface> ekosInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", 0265 QDBusConnection::sessionBus(), this); 0266 0267 // Connecting DBus signals 0268 QDBusConnection::sessionBus().connect("org.kde.kstars", "/KStars/Ekos", "org.kde.kstars.Ekos", "newModule", this, 0269 SLOT(registerNewModule(QString))); 0270 0271 // ensure that the mount interface is present 0272 registerNewModule("Mount"); 0273 } 0274 KStarsData::Instance()->userdb()->GetAllDSLRInfos(state()->DSLRInfos()); 0275 0276 if (state()->DSLRInfos().count() > 0) 0277 { 0278 qCDebug(KSTARS_EKOS_CAPTURE) << "DSLR Cameras Info:"; 0279 qCDebug(KSTARS_EKOS_CAPTURE) << state()->DSLRInfos(); 0280 } 0281 0282 m_LimitsDialog = new QDialog(this); 0283 m_LimitsUI.reset(new Ui::Limits()); 0284 m_LimitsUI->setupUi(m_LimitsDialog); 0285 m_scriptsManager = new ScriptsManager(this); 0286 if (m_standAlone) 0287 { 0288 // Prepend "Capture Sequence Editor" to the two pop-up window titles, to differentiate them 0289 // from similar windows in the Capture tab. 0290 auto title = i18n("Capture Sequence Editor: %1", m_LimitsDialog->windowTitle()); 0291 m_LimitsDialog->setWindowTitle(title); 0292 title = i18n("Capture Sequence Editor: %1", m_scriptsManager->windowTitle()); 0293 m_scriptsManager->setWindowTitle(title); 0294 } 0295 dirPath = QUrl::fromLocalFile(QDir::homePath()); 0296 0297 //isAutoGuiding = false; 0298 0299 // hide avg. download time and target drift initially 0300 targetDriftLabel->setVisible(false); 0301 targetDrift->setVisible(false); 0302 targetDriftUnit->setVisible(false); 0303 avgDownloadTime->setVisible(false); 0304 avgDownloadLabel->setVisible(false); 0305 secLabel->setVisible(false); 0306 0307 connect(&state()->getSeqDelayTimer(), &QTimer::timeout, m_captureProcess, &CaptureProcess::captureImage); 0308 state()->getCaptureDelayTimer().setSingleShot(true); 0309 connect(&state()->getCaptureDelayTimer(), &QTimer::timeout, this, &Capture::start, Qt::UniqueConnection); 0310 0311 connect(startB, &QPushButton::clicked, this, &Capture::toggleSequence); 0312 connect(pauseB, &QPushButton::clicked, this, &Capture::pause); 0313 connect(darkLibraryB, &QPushButton::clicked, DarkLibrary::Instance(), &QDialog::show); 0314 connect(limitsB, &QPushButton::clicked, m_LimitsDialog, &QDialog::show); 0315 connect(temperatureRegulationB, &QPushButton::clicked, this, &Capture::showTemperatureRegulation); 0316 0317 startB->setIcon(QIcon::fromTheme("media-playback-start")); 0318 startB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0319 pauseB->setIcon(QIcon::fromTheme("media-playback-pause")); 0320 pauseB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0321 0322 filterManagerB->setIcon(QIcon::fromTheme("view-filter")); 0323 filterManagerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0324 0325 connect(captureBinHN, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), captureBinVN, &QSpinBox::setValue); 0326 0327 connect(liveVideoB, &QPushButton::clicked, this, &Capture::toggleVideo); 0328 0329 connect(clearConfigurationB, &QPushButton::clicked, this, &Capture::clearCameraConfiguration); 0330 0331 darkB->setChecked(Options::autoDark()); 0332 connect(darkB, &QAbstractButton::toggled, this, [this]() 0333 { 0334 Options::setAutoDark(darkB->isChecked()); 0335 }); 0336 0337 connect(restartCameraB, &QPushButton::clicked, this, [this]() 0338 { 0339 if (activeCamera()) 0340 restartCamera(activeCamera()->getDeviceName()); 0341 }); 0342 0343 connect(cameraTemperatureS, &QCheckBox::toggled, this, [this](bool toggled) 0344 { 0345 if (devices()->getActiveCamera()) 0346 { 0347 QVariantMap auxInfo = devices()->getActiveCamera()->getDriverInfo()->getAuxInfo(); 0348 auxInfo[QString("%1_TC").arg(devices()->getActiveCamera()->getDeviceName())] = toggled; 0349 devices()->getActiveCamera()->getDriverInfo()->setAuxInfo(auxInfo); 0350 } 0351 }); 0352 0353 connect(filterEditB, &QPushButton::clicked, this, &Capture::editFilterName); 0354 0355 connect(FilterPosCombo, static_cast<void(QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged), 0356 [ = ]() 0357 { 0358 state()->updateHFRThreshold(); 0359 generatePreviewFilename(); 0360 }); 0361 connect(previewB, &QPushButton::clicked, this, &Capture::capturePreview); 0362 connect(loopB, &QPushButton::clicked, this, &Capture::startFraming); 0363 0364 //connect( seqWatcher, SIGNAL(dirty(QString)), this, &Capture::checkSeqFile(QString))); 0365 0366 connect(addToQueueB, &QPushButton::clicked, this, [this]() 0367 { 0368 if (m_JobUnderEdit) 0369 editJobFinished(); 0370 else 0371 createJob(); 0372 }); 0373 connect(queueUpB, &QPushButton::clicked, [this]() 0374 { 0375 moveJob(true); 0376 }); 0377 connect(queueDownB, &QPushButton::clicked, [this]() 0378 { 0379 moveJob(false); 0380 }); 0381 connect(removeFromQueueB, &QPushButton::clicked, this, &Capture::removeJobFromQueue); 0382 connect(selectFileDirectoryB, &QPushButton::clicked, this, &Capture::saveFITSDirectory); 0383 connect(queueSaveB, &QPushButton::clicked, this, static_cast<void(Capture::*)()>(&Capture::saveSequenceQueue)); 0384 connect(queueSaveAsB, &QPushButton::clicked, this, &Capture::saveSequenceQueueAs); 0385 connect(queueLoadB, &QPushButton::clicked, this, static_cast<void(Capture::*)()>(&Capture::loadSequenceQueue)); 0386 connect(resetB, &QPushButton::clicked, this, &Capture::resetJobs); 0387 connect(queueTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Capture::selectedJobChanged); 0388 connect(queueTable, &QAbstractItemView::doubleClicked, this, &Capture::editJob); 0389 connect(queueTable, &QTableWidget::itemSelectionChanged, this, [&]() 0390 { 0391 resetJobEdit(m_JobUnderEdit); 0392 }); 0393 connect(setTemperatureB, &QPushButton::clicked, this, [&]() 0394 { 0395 if (devices()->getActiveCamera()) 0396 devices()->getActiveCamera()->setTemperature(cameraTemperatureN->value()); 0397 }); 0398 connect(coolerOnB, &QPushButton::clicked, this, [&]() 0399 { 0400 if (devices()->getActiveCamera()) 0401 devices()->getActiveCamera()->setCoolerControl(true); 0402 }); 0403 connect(coolerOffB, &QPushButton::clicked, this, [&]() 0404 { 0405 if (devices()->getActiveCamera()) 0406 devices()->getActiveCamera()->setCoolerControl(false); 0407 }); 0408 connect(cameraTemperatureN, &QDoubleSpinBox::editingFinished, setTemperatureB, 0409 static_cast<void (QPushButton::*)()>(&QPushButton::setFocus)); 0410 connect(captureTypeS, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, 0411 &Capture::checkFrameType); 0412 connect(resetFrameB, &QPushButton::clicked, m_captureProcess, &CaptureProcess::resetFrame); 0413 connect(calibrationB, &QPushButton::clicked, this, &Capture::openCalibrationDialog); 0414 // connect(rotatorB, &QPushButton::clicked, m_RotatorControlPanel.get(), &Capture::show); 0415 0416 connect(generateDarkFlatsB, &QPushButton::clicked, this, &Capture::generateDarkFlats); 0417 connect(scriptManagerB, &QPushButton::clicked, this, &Capture::handleScriptsManager); 0418 connect(resetFormatB, &QPushButton::clicked, this, [this]() 0419 { 0420 placeholderFormatT->setText(KSUtils::getDefaultPath("PlaceholderFormat")); 0421 }); 0422 0423 addToQueueB->setIcon(QIcon::fromTheme("list-add")); 0424 addToQueueB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0425 removeFromQueueB->setIcon(QIcon::fromTheme("list-remove")); 0426 removeFromQueueB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0427 queueUpB->setIcon(QIcon::fromTheme("go-up")); 0428 queueUpB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0429 queueDownB->setIcon(QIcon::fromTheme("go-down")); 0430 queueDownB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0431 selectFileDirectoryB->setIcon(QIcon::fromTheme("document-open-folder")); 0432 selectFileDirectoryB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0433 queueLoadB->setIcon(QIcon::fromTheme("document-open")); 0434 queueLoadB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0435 queueSaveB->setIcon(QIcon::fromTheme("document-save")); 0436 queueSaveB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0437 queueSaveAsB->setIcon(QIcon::fromTheme("document-save-as")); 0438 queueSaveAsB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0439 resetB->setIcon(QIcon::fromTheme("system-reboot")); 0440 resetB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0441 resetFrameB->setIcon(QIcon::fromTheme("view-refresh")); 0442 resetFrameB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0443 calibrationB->setIcon(QIcon::fromTheme("run-build")); 0444 calibrationB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0445 generateDarkFlatsB->setIcon(QIcon::fromTheme("tools-wizard")); 0446 generateDarkFlatsB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0447 // rotatorB->setIcon(QIcon::fromTheme("kstars_solarsystem")); 0448 rotatorB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0449 0450 addToQueueB->setToolTip(i18n("Add job to sequence queue")); 0451 removeFromQueueB->setToolTip(i18n("Remove job from sequence queue")); 0452 0453 //////////////////////////////////////////////////////////////////////// 0454 /// Device Adaptor 0455 //////////////////////////////////////////////////////////////////////// 0456 connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::newCCDTemperatureValue, this, 0457 &Capture::updateCCDTemperature, Qt::UniqueConnection); 0458 connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::newRotatorAngle, this, 0459 &Capture::updateRotatorAngle, Qt::UniqueConnection); 0460 0461 //////////////////////////////////////////////////////////////////////// 0462 /// Settings 0463 //////////////////////////////////////////////////////////////////////// 0464 syncGUIToGeneralSettings(); 0465 // Start Guide Deviation Check 0466 connect(m_LimitsUI->startGuiderDriftS, &QCheckBox::toggled, [ = ](bool checked) 0467 { 0468 // We don't want the editor to influence a concurrent live capture session. 0469 if (!m_standAlone) 0470 Options::setEnforceStartGuiderDrift(checked); 0471 }); 0472 0473 // Start Guide Deviation Value 0474 connect(m_LimitsUI->startGuiderDriftN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]() 0475 { 0476 // We don't want the editor to influence a concurrent live capture session. 0477 if (!m_standAlone) 0478 Options::setStartGuideDeviation(m_LimitsUI->startGuiderDriftN->value()); 0479 }); 0480 0481 // Abort Guide Deviation Check 0482 connect(m_LimitsUI->limitGuideDeviationS, &QCheckBox::toggled, [ = ](bool checked) 0483 { 0484 // We don't want the editor to influence a concurrent live capture session. 0485 if (!m_standAlone) 0486 Options::setEnforceGuideDeviation(checked); 0487 }); 0488 0489 // Per job dither frequency count 0490 connect(m_LimitsUI->limitDitherFrequencyN, QOverload<int>::of(&QSpinBox::valueChanged), [this]() 0491 { 0492 // We don't want the editor to influence a concurrent live capture session. 0493 if (!m_standAlone) 0494 Options::setGuideDitherPerJobFrequency(m_LimitsUI->limitDitherFrequencyN->value()); 0495 }); 0496 0497 // Guide Deviation Value 0498 connect(m_LimitsUI->limitGuideDeviationN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]() 0499 { 0500 // We don't want the editor to influence a concurrent live capture session. 0501 if (!m_standAlone) 0502 Options::setGuideDeviation(m_LimitsUI->limitGuideDeviationN->value()); 0503 }); 0504 0505 connect(m_LimitsUI->limitGuideDeviationRepsN, QOverload<int>::of(&QSpinBox::valueChanged), this, [this]() 0506 { 0507 // We don't want the editor to influence a concurrent live capture session. 0508 if (!m_standAlone) 0509 Options::setGuideDeviationReps(static_cast<uint>(m_LimitsUI->limitGuideDeviationRepsN->value())); 0510 }); 0511 0512 // Autofocus HFR Check 0513 connect(m_LimitsUI->limitFocusHFRS, &QCheckBox::toggled, [ = ](bool checked) 0514 { 0515 // We don't want the editor to influence a concurrent live capture session. 0516 if (!m_standAlone) 0517 Options::setEnforceAutofocusHFR(checked); 0518 if (checked == false) 0519 state()->getRefocusState()->setInSequenceFocus(false); 0520 }); 0521 m_LimitsUI->limitFocusHFRN->setValue(Options::hFRDeviation()); 0522 connect(m_LimitsUI->limitFocusHFRN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]() 0523 { 0524 // We don't want the editor to influence a concurrent live capture session. 0525 if (!m_standAlone) 0526 Options::setHFRDeviation(m_LimitsUI->limitFocusHFRN->value()); 0527 }); 0528 m_LimitsUI->limitFocusHFRThresholdPercentage->setValue(Options::hFRThresholdPercentage()); 0529 connect(m_LimitsUI->limitFocusHFRThresholdPercentage, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]() 0530 { 0531 Options::setHFRThresholdPercentage(m_LimitsUI->limitFocusHFRThresholdPercentage->value()); 0532 Capture::updateHFRCheckAlgo(); 0533 }); 0534 m_LimitsUI->limitFocusHFRCheckFrames->setValue(Options::inSequenceCheckFrames()); 0535 connect(m_LimitsUI->limitFocusHFRCheckFrames, QOverload<int>::of(&QSpinBox::valueChanged), this, [this]() 0536 { 0537 Options::setInSequenceCheckFrames(m_LimitsUI->limitFocusHFRCheckFrames->value()); 0538 }); 0539 connect(m_captureModuleState.get(), &CaptureModuleState::newLimitFocusHFR, this, [this](double hfr) 0540 { 0541 m_LimitsUI->limitFocusHFRN->setValue(hfr); 0542 }); 0543 m_LimitsUI->limitFocusHFRAlgorithm->setCurrentIndex(Options::hFRCheckAlgorithm()); 0544 updateHFRCheckAlgo(); 0545 connect(m_LimitsUI->limitFocusHFRAlgorithm, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) 0546 { 0547 Options::setHFRCheckAlgorithm(index); 0548 Capture::updateHFRCheckAlgo(); 0549 }); 0550 0551 // Autofocus temperature Check 0552 connect(m_LimitsUI->limitFocusDeltaTS, &QCheckBox::toggled, this, [ = ](bool checked) 0553 { 0554 // We don't want the editor to influence a concurrent live capture session. 0555 if (!m_standAlone) 0556 Options::setEnforceAutofocusOnTemperature(checked); 0557 }); 0558 0559 // Autofocus temperature Delta 0560 connect(m_LimitsUI->limitFocusDeltaTN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [this]() 0561 { 0562 // We don't want the editor to influence a concurrent live capture session. 0563 if (!m_standAlone) 0564 Options::setMaxFocusTemperatureDelta(m_LimitsUI->limitFocusDeltaTN->value()); 0565 }); 0566 0567 // Refocus Every Check 0568 connect(m_LimitsUI->limitRefocusS, &QCheckBox::toggled, this, [ = ](bool checked) 0569 { 0570 // We don't want the editor to influence a concurrent live capture session. 0571 if (!m_standAlone) 0572 Options::setEnforceRefocusEveryN(checked); 0573 }); 0574 0575 // Refocus Every Value 0576 connect(m_LimitsUI->limitRefocusN, QOverload<int>::of(&QSpinBox::valueChanged), this, [this]() 0577 { 0578 // We don't want the editor to influence a concurrent live capture session. 0579 if (!m_standAlone) 0580 Options::setRefocusEveryN(static_cast<uint>(m_LimitsUI->limitRefocusN->value())); 0581 }); 0582 0583 // Refocus after meridian flip 0584 m_LimitsUI->meridianRefocusS->setChecked(Options::refocusAfterMeridianFlip()); 0585 connect(m_LimitsUI->meridianRefocusS, &QCheckBox::toggled, [ = ](bool checked) 0586 { 0587 // We don't want the editor to influence a concurrent live capture session. 0588 if (!m_standAlone) 0589 Options::setRefocusAfterMeridianFlip(checked); 0590 }); 0591 0592 QCheckBox * const checkBoxes[] = 0593 { 0594 m_LimitsUI->limitGuideDeviationS, 0595 m_LimitsUI->startGuiderDriftS, 0596 m_LimitsUI->limitRefocusS, 0597 m_LimitsUI->limitFocusDeltaTS, 0598 m_LimitsUI->limitFocusHFRS, 0599 m_LimitsUI->meridianRefocusS, 0600 }; 0601 for (const QCheckBox * control : checkBoxes) 0602 connect(control, &QCheckBox::toggled, this, [&]() 0603 { 0604 state()->setDirty(true); 0605 }); 0606 0607 QDoubleSpinBox * const dspinBoxes[] 0608 { 0609 m_LimitsUI->limitFocusHFRN, 0610 m_LimitsUI->limitFocusHFRThresholdPercentage, 0611 m_LimitsUI->limitFocusDeltaTN, 0612 m_LimitsUI->limitGuideDeviationN, 0613 m_LimitsUI->startGuiderDriftN 0614 }; 0615 for (const QDoubleSpinBox * control : dspinBoxes) 0616 connect(control, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, [&]() 0617 { 0618 state()->setDirty(true); 0619 }); 0620 0621 connect(m_LimitsUI->limitFocusHFRCheckFrames, QOverload<int>::of(&QSpinBox::valueChanged), this, [&]() 0622 { 0623 state()->setDirty(true); 0624 }); 0625 connect(m_LimitsUI->limitFocusHFRAlgorithm, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&]() 0626 { 0627 state()->setDirty(true); 0628 }); 0629 0630 connect(fileUploadModeS, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [&]() 0631 { 0632 state()->setDirty(true); 0633 }); 0634 connect(fileRemoteDirT, &QLineEdit::editingFinished, this, [&]() 0635 { 0636 state()->setDirty(true); 0637 }); 0638 0639 observerB->setIcon(QIcon::fromTheme("im-user")); 0640 observerB->setAttribute(Qt::WA_LayoutUsesWidgetRect); 0641 connect(observerB, &QPushButton::clicked, this, &Capture::showObserverDialog); 0642 0643 // Exposure Timeout 0644 state()->getCaptureTimeout().setSingleShot(true); 0645 connect(&state()->getCaptureTimeout(), &QTimer::timeout, m_captureProcess, 0646 &CaptureProcess::processCaptureTimeout); 0647 0648 // Remote directory 0649 connect(fileUploadModeS, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, 0650 [&](int index) 0651 { 0652 fileRemoteDirT->setEnabled(index != 0); 0653 }); 0654 0655 customPropertiesDialog.reset(new CustomProperties()); 0656 connect(customValuesB, &QPushButton::clicked, this, [&]() 0657 { 0658 customPropertiesDialog.get()->show(); 0659 customPropertiesDialog.get()->raise(); 0660 }); 0661 connect(customPropertiesDialog.get(), &CustomProperties::valueChanged, this, [&]() 0662 { 0663 const double newGain = getGain(); 0664 if (captureGainN && newGain >= 0) 0665 captureGainN->setValue(newGain); 0666 const int newOffset = getOffset(); 0667 if (newOffset >= 0) 0668 captureOffsetN->setValue(newOffset); 0669 }); 0670 0671 if(!Options::captureDirectory().isEmpty()) 0672 fileDirectoryT->setText(Options::captureDirectory()); 0673 else 0674 { 0675 fileDirectoryT->setText(QDir::homePath() + QDir::separator() + "Pictures"); 0676 Options::setCaptureDirectory(fileDirectoryT->text()); 0677 } 0678 0679 connect(fileDirectoryT, &QLineEdit::textChanged, this, [&]() 0680 { 0681 Options::setCaptureDirectory(fileDirectoryT->text()); 0682 generatePreviewFilename(); 0683 }); 0684 0685 if (Options::remoteCaptureDirectory().isEmpty() == false) 0686 { 0687 fileRemoteDirT->setText(Options::remoteCaptureDirectory()); 0688 } 0689 connect(fileRemoteDirT, &QLineEdit::editingFinished, this, [&]() 0690 { 0691 Options::setRemoteCaptureDirectory(fileRemoteDirT->text()); 0692 generatePreviewFilename(); 0693 }); 0694 0695 //Note: This is to prevent a button from being called the default button 0696 //and then executing when the user hits the enter key such as when on a Text Box 0697 QList<QPushButton *> qButtons = findChildren<QPushButton *>(); 0698 for (auto &button : qButtons) 0699 button->setAutoDefault(false); 0700 0701 DarkLibrary::Instance()->setCaptureModule(this); 0702 0703 // display the capture status in the UI 0704 connect(this, &Capture::newStatus, captureStatusWidget, &LedStatusWidget::setCaptureState); 0705 0706 // react upon state changes 0707 connect(m_captureModuleState.data(), &CaptureModuleState::captureBusy, this, &Capture::setBusy); 0708 connect(m_captureModuleState.data(), &CaptureModuleState::startCapture, this, &Capture::start); 0709 connect(m_captureModuleState.data(), &CaptureModuleState::abortCapture, this, &Capture::abort); 0710 connect(m_captureModuleState.data(), &CaptureModuleState::suspendCapture, this, &Capture::suspend); 0711 connect(m_captureModuleState.data(), &CaptureModuleState::executeActiveJob, m_captureProcess.data(), 0712 &CaptureProcess::executeJob); 0713 connect(m_captureModuleState.data(), &CaptureModuleState::updatePrepareState, this, &Capture::updatePrepareState); 0714 // forward signals from capture module state 0715 connect(m_captureModuleState.data(), &CaptureModuleState::captureStarted, m_captureProcess.data(), 0716 &CaptureProcess::captureStarted); 0717 connect(m_captureModuleState.data(), &CaptureModuleState::newLog, this, &Capture::appendLogText); 0718 connect(m_captureModuleState.data(), &CaptureModuleState::newStatus, this, &Capture::newStatus); 0719 connect(m_captureModuleState.data(), &CaptureModuleState::sequenceChanged, this, &Capture::sequenceChanged); 0720 connect(m_captureModuleState.data(), &CaptureModuleState::checkFocus, this, &Capture::checkFocus); 0721 connect(m_captureModuleState.data(), &CaptureModuleState::runAutoFocus, this, &Capture::runAutoFocus); 0722 connect(m_captureModuleState.data(), &CaptureModuleState::resetFocus, this, &Capture::resetFocus); 0723 connect(m_captureModuleState.data(), &CaptureModuleState::adaptiveFocus, this, &Capture::adaptiveFocus); 0724 connect(m_captureModuleState.data(), &CaptureModuleState::guideAfterMeridianFlip, this, 0725 &Capture::guideAfterMeridianFlip); 0726 connect(m_captureModuleState.data(), &CaptureModuleState::newFocusStatus, this, &Capture::updateFocusStatus); 0727 connect(m_captureModuleState.data(), &CaptureModuleState::newMeridianFlipStage, this, &Capture::updateMeridianFlipStage); 0728 connect(m_captureModuleState.data(), &CaptureModuleState::meridianFlipStarted, this, &Capture::meridianFlipStarted); 0729 0730 // forward signals from capture process 0731 connect(m_captureProcess.data(), &CaptureProcess::cameraReady, this, &Capture::ready); 0732 connect(m_captureProcess.data(), &CaptureProcess::refreshCamera, this, &Capture::updateCamera); 0733 connect(m_captureProcess.data(), &CaptureProcess::refreshCameraSettings, this, &Capture::refreshCameraSettings); 0734 connect(m_captureProcess.data(), &CaptureProcess::refreshFilterSettings, this, &Capture::refreshFilterSettings); 0735 connect(m_captureProcess.data(), &CaptureProcess::newExposureProgress, this, &Capture::newExposureProgress); 0736 connect(m_captureProcess.data(), &CaptureProcess::newDownloadProgress, this, &Capture::updateDownloadProgress); 0737 connect(m_captureProcess.data(), &CaptureProcess::updateCaptureCountDown, this, &Capture::updateCaptureCountDown); 0738 connect(m_captureProcess.data(), &CaptureProcess::processingFITSfinished, this, &Capture::processingFITSfinished); 0739 connect(m_captureProcess.data(), &CaptureProcess::newImage, this, &Capture::newImage); 0740 connect(m_captureProcess.data(), &CaptureProcess::syncGUIToJob, this, &Capture::syncGUIToJob); 0741 connect(m_captureProcess.data(), &CaptureProcess::captureComplete, this, &Capture::captureComplete); 0742 connect(m_captureProcess.data(), &CaptureProcess::updateFrameProperties, this, &Capture::updateFrameProperties); 0743 connect(m_captureProcess.data(), &CaptureProcess::jobExecutionPreparationStarted, this, 0744 &Capture::jobExecutionPreparationStarted); 0745 connect(m_captureProcess.data(), &CaptureProcess::sequenceChanged, this, &Capture::sequenceChanged); 0746 connect(m_captureProcess.data(), &CaptureProcess::addJob, this, &Capture::addJob); 0747 connect(m_captureProcess.data(), &CaptureProcess::createJob, [this](SequenceJob::SequenceJobType jobType) 0748 { 0749 // report the result back to the process 0750 process()->jobCreated(createJob(jobType)); 0751 }); 0752 connect(m_captureProcess.data(), &CaptureProcess::jobPrepared, this, &Capture::jobPrepared); 0753 connect(m_captureProcess.data(), &CaptureProcess::captureImageStarted, this, &Capture::captureImageStarted); 0754 connect(m_captureProcess.data(), &CaptureProcess::captureTarget, this, &Capture::setTargetName); 0755 connect(m_captureProcess.data(), &CaptureProcess::downloadingFrame, this, [this]() 0756 { 0757 captureStatusWidget->setStatus(i18n("Downloading..."), Qt::yellow); 0758 }); 0759 connect(m_captureProcess.data(), &CaptureProcess::captureAborted, this, &Capture::captureAborted); 0760 connect(m_captureProcess.data(), &CaptureProcess::captureStopped, this, &Capture::captureStopped); 0761 connect(m_captureProcess.data(), &CaptureProcess::updateJobTable, this, &Capture::updateJobTable); 0762 connect(m_captureProcess.data(), &CaptureProcess::abortFocus, this, &Capture::abortFocus); 0763 connect(m_captureProcess.data(), &CaptureProcess::updateMeridianFlipStage, this, &Capture::updateMeridianFlipStage); 0764 connect(m_captureProcess.data(), &CaptureProcess::darkFrameCompleted, this, &Capture::imageCapturingCompleted); 0765 connect(m_captureProcess.data(), &CaptureProcess::newLog, this, &Capture::appendLogText); 0766 connect(m_captureProcess.data(), &CaptureProcess::jobStarting, this, &Capture::jobStarting); 0767 connect(m_captureProcess.data(), &CaptureProcess::captureRunning, this, &Capture::captureRunning); 0768 connect(m_captureProcess.data(), &CaptureProcess::stopCapture, this, &Capture::stop); 0769 connect(m_captureProcess.data(), &CaptureProcess::suspendGuiding, this, &Capture::suspendGuiding); 0770 connect(m_captureProcess.data(), &CaptureProcess::resumeGuiding, this, &Capture::resumeGuiding); 0771 connect(m_captureProcess.data(), &CaptureProcess::driverTimedout, this, &Capture::driverTimedout); 0772 connect(m_captureProcess.data(), &CaptureProcess::rotatorReverseToggled, this, &Capture::setRotatorReversed); 0773 // connections between state machine and device adaptor 0774 connect(m_captureModuleState.data(), &CaptureModuleState::newFilterPosition, 0775 m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::setFilterPosition); 0776 connect(m_captureModuleState.data(), &CaptureModuleState::abortFastExposure, 0777 m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::abortFastExposure); 0778 connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::pierSideChanged, 0779 m_captureModuleState.data(), &CaptureModuleState::setPierSide); 0780 connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::newFilterWheel, this, &Capture::setFilterWheel); 0781 connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::CameraConnected, this, [this](bool connected) 0782 { 0783 CCDFWGroup->setEnabled(connected); 0784 sequenceBox->setEnabled(connected); 0785 for (auto &oneChild : sequenceControlsButtonGroup->buttons()) 0786 oneChild->setEnabled(connected); 0787 0788 if (! connected) 0789 { 0790 opticalTrainCombo->setEnabled(true); 0791 trainLabel->setEnabled(true); 0792 } 0793 }); 0794 connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::FilterWheelConnected, this, [this](bool connected) 0795 { 0796 FilterPosLabel->setEnabled(connected); 0797 FilterPosCombo->setEnabled(connected); 0798 filterManagerB->setEnabled(connected); 0799 }); 0800 connect(m_captureDeviceAdaptor.data(), &CaptureDeviceAdaptor::newRotator, this, &Capture::setRotator); 0801 0802 setupOpticalTrainManager(); 0803 0804 // Generate Meridian Flip State 0805 getMeridianFlipState(); 0806 0807 //Update the filename preview 0808 placeholderFormatT->setText(Options::placeholderFormat()); 0809 connect(placeholderFormatT, &QLineEdit::textChanged, this, [this]() 0810 { 0811 Options::setPlaceholderFormat(placeholderFormatT->text()); 0812 generatePreviewFilename(); 0813 }); 0814 connect(formatSuffixN, QOverload<int>::of(&QSpinBox::valueChanged), this, &Capture::generatePreviewFilename); 0815 connect(captureExposureN, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, 0816 &Capture::generatePreviewFilename); 0817 connect(targetNameT, &QLineEdit::textEdited, this, [ = ]() 0818 { 0819 generatePreviewFilename(); 0820 qCDebug(KSTARS_EKOS_CAPTURE) << "Changed target to" << targetNameT->text() << "because of user edit"; 0821 }); 0822 connect(captureTypeS, &QComboBox::currentTextChanged, this, &Capture::generatePreviewFilename); 0823 0824 connect(exposureCalcB, &QPushButton::clicked, this, &Capture::openExposureCalculatorDialog); 0825 0826 } 0827 0828 Capture::~Capture() 0829 { 0830 qDeleteAll(state()->allJobs()); 0831 state()->allJobs().clear(); 0832 } 0833 0834 void Capture::updateHFRCheckAlgo() 0835 { 0836 // Threshold % is not relevant for FIXED HFR do disable the field 0837 const bool threshold = (m_LimitsUI->limitFocusHFRAlgorithm->currentIndex() != HFR_CHECK_FIXED); 0838 m_LimitsUI->limitFocusHFRThresholdPercentage->setEnabled(threshold); 0839 m_LimitsUI->limitFocusHFRThresholdLabel->setEnabled(threshold); 0840 m_LimitsUI->limitFocusHFRPercentLabel->setEnabled(threshold); 0841 state()->updateHFRThreshold(); 0842 } 0843 0844 bool Capture::updateCamera() 0845 { 0846 auto isConnected = activeCamera() && activeCamera()->isConnected(); 0847 CCDFWGroup->setEnabled(isConnected); 0848 sequenceBox->setEnabled(isConnected); 0849 for (auto &oneChild : sequenceControlsButtonGroup->buttons()) 0850 oneChild->setEnabled(isConnected); 0851 0852 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain); 0853 0854 if (activeCamera() && trainID.isValid()) 0855 { 0856 opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(activeCamera()->getDeviceName(), currentScope()["name"].toString())); 0857 0858 cameraLabel->setText(activeCamera()->getDeviceName()); 0859 } 0860 else 0861 { 0862 cameraLabel->clear(); 0863 return false; 0864 } 0865 0866 if (devices()->filterWheel()) 0867 process()->updateFilterInfo(); 0868 0869 process()->checkCamera(); 0870 0871 emit settingsUpdated(getPresetSettings()); 0872 0873 return true; 0874 } 0875 0876 0877 0878 void Capture::setFilterWheel(QString name) 0879 { 0880 // Should not happen 0881 if (m_standAlone) 0882 return; 0883 0884 if (devices()->filterWheel() && devices()->filterWheel()->getDeviceName() == name) 0885 { 0886 refreshFilterSettings(); 0887 return; 0888 } 0889 0890 auto isConnected = devices()->filterWheel() && devices()->filterWheel()->isConnected(); 0891 FilterPosLabel->setEnabled(isConnected); 0892 FilterPosCombo->setEnabled(isConnected); 0893 filterManagerB->setEnabled(isConnected); 0894 0895 refreshFilterSettings(); 0896 0897 if (devices()->filterWheel()) 0898 emit settingsUpdated(getPresetSettings()); 0899 } 0900 0901 bool Capture::setDome(ISD::Dome *device) 0902 { 0903 return m_captureProcess->setDome(device); 0904 } 0905 0906 void Capture::setRotator(QString name) 0907 { 0908 ISD::Rotator *Rotator = devices()->rotator(); 0909 // clear old rotator 0910 rotatorB->setEnabled(false); 0911 if (Rotator && !m_RotatorControlPanel.isNull()) 0912 m_RotatorControlPanel->close(); 0913 0914 // set new rotator 0915 if (!name.isEmpty()) // start real rotator 0916 { 0917 Manager::Instance()->getRotatorController(name, m_RotatorControlPanel); 0918 m_RotatorControlPanel->initRotator(opticalTrainCombo->currentText(), m_captureDeviceAdaptor.data(), Rotator); 0919 connect(rotatorB, &QPushButton::clicked, this, [this]() 0920 { 0921 m_RotatorControlPanel->show(); 0922 m_RotatorControlPanel->raise(); 0923 }); 0924 rotatorB->setEnabled(true); 0925 } 0926 else if (Options::astrometryUseRotator()) // start at least rotatorutils for "manual rotator" 0927 { 0928 RotatorUtils::Instance()->initRotatorUtils(opticalTrainCombo->currentText()); 0929 } 0930 } 0931 0932 void Capture::pause() 0933 { 0934 process()->pauseCapturing(); 0935 updateStartButtons(false, true); 0936 } 0937 0938 void Capture::toggleSequence() 0939 { 0940 const CaptureState capturestate = state()->getCaptureState(); 0941 if (capturestate == CAPTURE_PAUSE_PLANNED || capturestate == CAPTURE_PAUSED) 0942 updateStartButtons(true, false); 0943 0944 process()->toggleSequence(); 0945 } 0946 0947 void Capture::jobStarting() 0948 { 0949 if (m_LimitsUI->limitFocusHFRS->isChecked() && state()->getRefocusState()->isAutoFocusReady() == false) 0950 appendLogText(i18n("Warning: in-sequence focusing is selected but autofocus process was not started.")); 0951 if (m_LimitsUI->limitFocusDeltaTS->isChecked() && state()->getRefocusState()->isAutoFocusReady() == false) 0952 appendLogText(i18n("Warning: temperature delta check is selected but autofocus process was not started.")); 0953 0954 updateStartButtons(true, false); 0955 } 0956 0957 void Capture::registerNewModule(const QString &name) 0958 { 0959 if (m_standAlone) 0960 return; 0961 if (name == "Mount" && mountInterface == nullptr) 0962 { 0963 qCDebug(KSTARS_EKOS_CAPTURE) << "Registering new Module (" << name << ")"; 0964 mountInterface = new QDBusInterface("org.kde.kstars", "/KStars/Ekos/Mount", 0965 "org.kde.kstars.Ekos.Mount", QDBusConnection::sessionBus(), this); 0966 0967 } 0968 } 0969 0970 QString Capture::camera() 0971 { 0972 if (devices()->getActiveCamera()) 0973 return devices()->getActiveCamera()->getDeviceName(); 0974 0975 return QString(); 0976 } 0977 0978 void Capture::refreshCameraSettings() 0979 { 0980 // Make sure we have a valid chip and valid base device. 0981 // Make sure we are not in capture process. 0982 auto camera = activeCamera(); 0983 auto targetChip = devices()->getActiveChip(); 0984 // If camera is restarted, try again in one second 0985 if (!m_standAlone && (!camera || !targetChip || !targetChip->getCCD() || targetChip->isCapturing())) 0986 { 0987 QTimer::singleShot(1000, this, &Capture::refreshCameraSettings); 0988 return; 0989 } 0990 0991 if (camera->hasCoolerControl()) 0992 { 0993 coolerOnB->setEnabled(true); 0994 coolerOffB->setEnabled(true); 0995 coolerOnB->setChecked(camera->isCoolerOn()); 0996 coolerOffB->setChecked(!camera->isCoolerOn()); 0997 } 0998 else 0999 { 1000 coolerOnB->setEnabled(false); 1001 coolerOnB->setChecked(false); 1002 coolerOffB->setEnabled(false); 1003 coolerOffB->setChecked(false); 1004 } 1005 1006 updateFrameProperties(); 1007 1008 updateCaptureFormats(); 1009 1010 customPropertiesDialog->setCCD(camera); 1011 1012 liveVideoB->setEnabled(camera->hasVideoStream()); 1013 if (camera->hasVideoStream()) 1014 setVideoStreamEnabled(camera->isStreamingEnabled()); 1015 else 1016 liveVideoB->setIcon(QIcon::fromTheme("camera-off")); 1017 1018 connect(camera, &ISD::Camera::propertyUpdated, this, &Capture::processCameraNumber, Qt::UniqueConnection); 1019 connect(camera, &ISD::Camera::coolerToggled, this, &Capture::setCoolerToggled, Qt::UniqueConnection); 1020 connect(camera, &ISD::Camera::videoStreamToggled, this, &Capture::setVideoStreamEnabled, Qt::UniqueConnection); 1021 connect(camera, &ISD::Camera::ready, this, &Capture::ready, Qt::UniqueConnection); 1022 connect(camera, &ISD::Camera::error, m_captureProcess.data(), &CaptureProcess::processCaptureError, 1023 Qt::UniqueConnection); 1024 1025 syncCameraInfo(); 1026 1027 // update values received by the device adaptor 1028 // connect(activeCamera(), &ISD::Camera::newTemperatureValue, this, &Capture::updateCCDTemperature, Qt::UniqueConnection); 1029 1030 DarkLibrary::Instance()->checkCamera(); 1031 } 1032 1033 void Capture::updateCaptureFormats() 1034 { 1035 QStringList frameTypes = process()->frameTypes(); 1036 1037 captureTypeS->clear(); 1038 1039 if (frameTypes.isEmpty()) 1040 captureTypeS->setEnabled(false); 1041 else 1042 { 1043 captureTypeS->setEnabled(true); 1044 captureTypeS->addItems(frameTypes); 1045 Options::setCaptureStandAloneTypes(standAloneEncode(frameTypes)); 1046 captureTypeS->setCurrentIndex(devices()->getActiveChip()->getFrameType()); 1047 } 1048 1049 // Capture Format 1050 captureFormatS->blockSignals(true); 1051 captureFormatS->clear(); 1052 captureFormatS->addItems(activeCamera()->getCaptureFormats()); 1053 Options::setCaptureStandAloneFormats(standAloneEncode(activeCamera()->getCaptureFormats())); 1054 captureFormatS->setCurrentText(activeCamera()->getCaptureFormat()); 1055 captureFormatS->blockSignals(false); 1056 1057 // Encoding format 1058 captureEncodingS->blockSignals(true); 1059 captureEncodingS->clear(); 1060 captureEncodingS->addItems(activeCamera()->getEncodingFormats()); 1061 captureEncodingS->setCurrentText(activeCamera()->getEncodingFormat()); 1062 Options::setCaptureStandAloneEncodings(standAloneEncode(activeCamera()->getEncodingFormats())); 1063 captureEncodingS->blockSignals(false); 1064 1065 Options::setCaptureStandAloneTimestamp(KStarsData::Instance()->lt().toString("yyyy-MM-dd hh:mm")); 1066 } 1067 1068 void Capture::syncCameraInfo() 1069 { 1070 if (!activeCamera()) 1071 return; 1072 1073 if (activeCamera()->hasCooler()) 1074 { 1075 cameraTemperatureS->setEnabled(true); 1076 cameraTemperatureN->setEnabled(true); 1077 1078 if (activeCamera()->getPermission("CCD_TEMPERATURE") != IP_RO) 1079 { 1080 double min, max, step; 1081 setTemperatureB->setEnabled(true); 1082 cameraTemperatureN->setReadOnly(false); 1083 cameraTemperatureS->setEnabled(true); 1084 temperatureRegulationB->setEnabled(true); 1085 activeCamera()->getMinMaxStep("CCD_TEMPERATURE", "CCD_TEMPERATURE_VALUE", &min, &max, &step); 1086 cameraTemperatureN->setMinimum(min); 1087 cameraTemperatureN->setMaximum(max); 1088 cameraTemperatureN->setSingleStep(1); 1089 bool isChecked = activeCamera()->getDriverInfo()->getAuxInfo().value(QString("%1_TC").arg(activeCamera()->getDeviceName()), 1090 false).toBool(); 1091 cameraTemperatureS->setChecked(isChecked); 1092 } 1093 else 1094 { 1095 setTemperatureB->setEnabled(false); 1096 cameraTemperatureN->setReadOnly(true); 1097 cameraTemperatureS->setEnabled(false); 1098 cameraTemperatureS->setChecked(false); 1099 temperatureRegulationB->setEnabled(false); 1100 } 1101 1102 double temperature = 0; 1103 if (activeCamera()->getTemperature(&temperature)) 1104 { 1105 temperatureOUT->setText(QString("%L1").arg(temperature, 0, 'f', 2)); 1106 if (cameraTemperatureN->cleanText().isEmpty()) 1107 cameraTemperatureN->setValue(temperature); 1108 } 1109 } 1110 else 1111 { 1112 cameraTemperatureS->setEnabled(false); 1113 cameraTemperatureN->setEnabled(false); 1114 temperatureRegulationB->setEnabled(false); 1115 cameraTemperatureN->clear(); 1116 temperatureOUT->clear(); 1117 setTemperatureB->setEnabled(false); 1118 } 1119 1120 auto isoList = devices()->getActiveChip()->getISOList(); 1121 captureISOS->blockSignals(true); 1122 captureISOS->setEnabled(false); 1123 captureISOS->clear(); 1124 1125 // No ISO range available 1126 if (isoList.isEmpty()) 1127 { 1128 captureISOS->setEnabled(false); 1129 Options::setCaptureStandAloneISOs(""); 1130 } 1131 else 1132 { 1133 captureISOS->setEnabled(true); 1134 captureISOS->addItems(isoList); 1135 captureISOS->setCurrentIndex(devices()->getActiveChip()->getISOIndex()); 1136 Options::setCaptureStandAloneISOs(standAloneEncode(isoList)); 1137 Options::setCaptureStandAloneISOIndex(devices()->getActiveChip()->getISOIndex()); 1138 1139 uint16_t w, h; 1140 uint8_t bbp {8}; 1141 double pixelX = 0, pixelY = 0; 1142 bool rc = devices()->getActiveChip()->getImageInfo(w, h, pixelX, pixelY, bbp); 1143 bool isModelInDB = state()->isModelinDSLRInfo(QString(activeCamera()->getDeviceName())); 1144 // If rc == true, then the property has been defined by the driver already 1145 // Only then we check if the pixels are zero 1146 if (rc == true && (pixelX == 0.0 || pixelY == 0.0 || isModelInDB == false)) 1147 { 1148 // If model is already in database, no need to show dialog 1149 // The zeros above are the initial packets so we can safely ignore them 1150 if (isModelInDB == false) 1151 { 1152 createDSLRDialog(); 1153 } 1154 else 1155 { 1156 QString model = QString(activeCamera()->getDeviceName()); 1157 process()->syncDSLRToTargetChip(model); 1158 } 1159 } 1160 } 1161 captureISOS->blockSignals(false); 1162 1163 // Gain Check 1164 if (activeCamera()->hasGain()) 1165 { 1166 double min, max, step, value, targetCustomGain; 1167 activeCamera()->getGainMinMaxStep(&min, &max, &step); 1168 1169 // Allow the possibility of no gain value at all. 1170 GainSpinSpecialValue = min - step; 1171 captureGainN->setRange(GainSpinSpecialValue, max); 1172 captureGainN->setSpecialValueText(i18n("--")); 1173 captureGainN->setEnabled(true); 1174 captureGainN->setSingleStep(step); 1175 activeCamera()->getGain(&value); 1176 currentGainLabel->setText(QString::number(value, 'f', 0)); 1177 1178 targetCustomGain = getGain(); 1179 1180 // Set the custom gain if we have one 1181 // otherwise it will not have an effect. 1182 if (targetCustomGain > 0) 1183 captureGainN->setValue(targetCustomGain); 1184 else 1185 captureGainN->setValue(GainSpinSpecialValue); 1186 1187 captureGainN->setReadOnly(activeCamera()->getGainPermission() == IP_RO); 1188 1189 connect(captureGainN, &QDoubleSpinBox::editingFinished, this, [this]() 1190 { 1191 if (captureGainN->value() != GainSpinSpecialValue) 1192 setGain(captureGainN->value()); 1193 else 1194 setGain(-1); 1195 }); 1196 } 1197 else 1198 { 1199 captureGainN->setEnabled(false); 1200 currentGainLabel->clear(); 1201 } 1202 1203 // Offset checks 1204 if (activeCamera()->hasOffset()) 1205 { 1206 double min, max, step, value, targetCustomOffset; 1207 activeCamera()->getOffsetMinMaxStep(&min, &max, &step); 1208 1209 // Allow the possibility of no Offset value at all. 1210 OffsetSpinSpecialValue = min - step; 1211 captureOffsetN->setRange(OffsetSpinSpecialValue, max); 1212 captureOffsetN->setSpecialValueText(i18n("--")); 1213 captureOffsetN->setEnabled(true); 1214 captureOffsetN->setSingleStep(step); 1215 activeCamera()->getOffset(&value); 1216 currentOffsetLabel->setText(QString::number(value, 'f', 0)); 1217 1218 targetCustomOffset = getOffset(); 1219 1220 // Set the custom Offset if we have one 1221 // otherwise it will not have an effect. 1222 if (targetCustomOffset > 0) 1223 captureOffsetN->setValue(targetCustomOffset); 1224 else 1225 captureOffsetN->setValue(OffsetSpinSpecialValue); 1226 1227 captureOffsetN->setReadOnly(activeCamera()->getOffsetPermission() == IP_RO); 1228 1229 connect(captureOffsetN, &QDoubleSpinBox::editingFinished, this, [this]() 1230 { 1231 if (captureOffsetN->value() != OffsetSpinSpecialValue) 1232 setOffset(captureOffsetN->value()); 1233 else 1234 setOffset(-1); 1235 }); 1236 } 1237 else 1238 { 1239 captureOffsetN->setEnabled(false); 1240 currentOffsetLabel->clear(); 1241 } 1242 } 1243 1244 void Capture::setGuideChip(ISD::CameraChip * guideChip) 1245 { 1246 // We should suspend guide in two scenarios: 1247 // 1. If guide chip is within the primary CCD, then we cannot download any data from guide chip while primary CCD is downloading. 1248 // 2. If we have two CCDs running from ONE driver (Multiple-Devices-Per-Driver mpdp is true). Same issue as above, only one download 1249 // at a time. 1250 // After primary CCD download is complete, we resume guiding. 1251 if (!devices()->getActiveCamera()) 1252 return; 1253 1254 state()->setSuspendGuidingOnDownload((devices()->getActiveCamera()->getChip( 1255 ISD::CameraChip::GUIDE_CCD) == guideChip) || 1256 (guideChip->getCCD() == devices()->getActiveCamera() && 1257 devices()->getActiveCamera()->getDriverInfo()->getAuxInfo().value("mdpd", false).toBool())); 1258 } 1259 1260 void Capture::resetFrameToZero() 1261 { 1262 captureFrameXN->setMinimum(0); 1263 captureFrameXN->setMaximum(0); 1264 captureFrameXN->setValue(0); 1265 1266 captureFrameYN->setMinimum(0); 1267 captureFrameYN->setMaximum(0); 1268 captureFrameYN->setValue(0); 1269 1270 captureFrameWN->setMinimum(0); 1271 captureFrameWN->setMaximum(0); 1272 captureFrameWN->setValue(0); 1273 1274 captureFrameHN->setMinimum(0); 1275 captureFrameHN->setMaximum(0); 1276 captureFrameHN->setValue(0); 1277 } 1278 1279 void Capture::updateFrameProperties(int reset) 1280 { 1281 if (!devices()->getActiveCamera()) 1282 return; 1283 1284 int binx = 1, biny = 1; 1285 double min, max, step; 1286 int xstep = 0, ystep = 0; 1287 1288 QString frameProp = state()->useGuideHead() ? QString("GUIDER_FRAME") : QString("CCD_FRAME"); 1289 QString exposureProp = state()->useGuideHead() ? QString("GUIDER_EXPOSURE") : QString("CCD_EXPOSURE"); 1290 QString exposureElem = state()->useGuideHead() ? QString("GUIDER_EXPOSURE_VALUE") : 1291 QString("CCD_EXPOSURE_VALUE"); 1292 devices()->setActiveChip(state()->useGuideHead() ? 1293 devices()->getActiveCamera()->getChip( 1294 ISD::CameraChip::GUIDE_CCD) : 1295 devices()->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD)); 1296 1297 captureFrameWN->setEnabled(devices()->getActiveChip()->canSubframe()); 1298 captureFrameHN->setEnabled(devices()->getActiveChip()->canSubframe()); 1299 captureFrameXN->setEnabled(devices()->getActiveChip()->canSubframe()); 1300 captureFrameYN->setEnabled(devices()->getActiveChip()->canSubframe()); 1301 1302 captureBinHN->setEnabled(devices()->getActiveChip()->canBin()); 1303 captureBinVN->setEnabled(devices()->getActiveChip()->canBin()); 1304 1305 QList<double> exposureValues; 1306 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 << 1307 10 << 20 << 30 << 40 << 50 << 60 << 120 << 180 << 300 << 600 << 900 << 1200 << 1800; 1308 1309 if (devices()->getActiveCamera()->getMinMaxStep(exposureProp, exposureElem, &min, &max, &step)) 1310 { 1311 if (min < 0.001) 1312 captureExposureN->setDecimals(6); 1313 else 1314 captureExposureN->setDecimals(3); 1315 for(int i = 0; i < exposureValues.count(); i++) 1316 { 1317 double value = exposureValues.at(i); 1318 if(value < min || value > max) 1319 { 1320 exposureValues.removeAt(i); 1321 i--; //So we don't skip one 1322 } 1323 } 1324 1325 exposureValues.prepend(min); 1326 exposureValues.append(max); 1327 } 1328 1329 captureExposureN->setRecommendedValues(exposureValues); 1330 state()->setExposureRange(exposureValues.first(), exposureValues.last()); 1331 1332 if (devices()->getActiveCamera()->getMinMaxStep(frameProp, "WIDTH", &min, &max, &step)) 1333 { 1334 if (min >= max) 1335 { 1336 resetFrameToZero(); 1337 return; 1338 } 1339 1340 if (step == 0.0) 1341 xstep = static_cast<int>(max * 0.05); 1342 else 1343 xstep = static_cast<int>(step); 1344 1345 if (min >= 0 && max > 0) 1346 { 1347 captureFrameWN->setMinimum(static_cast<int>(min)); 1348 captureFrameWN->setMaximum(static_cast<int>(max)); 1349 captureFrameWN->setSingleStep(xstep); 1350 } 1351 } 1352 else 1353 return; 1354 1355 if (devices()->getActiveCamera()->getMinMaxStep(frameProp, "HEIGHT", &min, &max, &step)) 1356 { 1357 if (min >= max) 1358 { 1359 resetFrameToZero(); 1360 return; 1361 } 1362 1363 if (step == 0.0) 1364 ystep = static_cast<int>(max * 0.05); 1365 else 1366 ystep = static_cast<int>(step); 1367 1368 if (min >= 0 && max > 0) 1369 { 1370 captureFrameHN->setMinimum(static_cast<int>(min)); 1371 captureFrameHN->setMaximum(static_cast<int>(max)); 1372 captureFrameHN->setSingleStep(ystep); 1373 } 1374 } 1375 else 1376 return; 1377 1378 if (devices()->getActiveCamera()->getMinMaxStep(frameProp, "X", &min, &max, &step)) 1379 { 1380 if (min >= max) 1381 { 1382 resetFrameToZero(); 1383 return; 1384 } 1385 1386 if (step == 0.0) 1387 step = xstep; 1388 1389 if (min >= 0 && max > 0) 1390 { 1391 captureFrameXN->setMinimum(static_cast<int>(min)); 1392 captureFrameXN->setMaximum(static_cast<int>(max)); 1393 captureFrameXN->setSingleStep(static_cast<int>(step)); 1394 } 1395 } 1396 else 1397 return; 1398 1399 if (devices()->getActiveCamera()->getMinMaxStep(frameProp, "Y", &min, &max, &step)) 1400 { 1401 if (min >= max) 1402 { 1403 resetFrameToZero(); 1404 return; 1405 } 1406 1407 if (step == 0.0) 1408 step = ystep; 1409 1410 if (min >= 0 && max > 0) 1411 { 1412 captureFrameYN->setMinimum(static_cast<int>(min)); 1413 captureFrameYN->setMaximum(static_cast<int>(max)); 1414 captureFrameYN->setSingleStep(static_cast<int>(step)); 1415 } 1416 } 1417 else 1418 return; 1419 1420 // cull to camera limits, if there are any 1421 if (state()->useGuideHead() == false) 1422 cullToDSLRLimits(); 1423 1424 // Save the sensor's width and height for the stand-alone editor. 1425 Options::setCaptureStandAloneWHGO( 1426 standAloneEncode( 1427 QStringList({QString("%1").arg(captureFrameWN->value()), 1428 QString("%1").arg(captureFrameHN->value()), 1429 QString("%1").arg(devices()->getActiveCamera()->getProperty("CCD_GAIN") ? "CCD_GAIN" : "CCD_CONTROLS"), 1430 QString("%1").arg(devices()->getActiveCamera()->getProperty("CCD_OFFSET") ? "CCD_OFFSET" : "CCD_CONTROLS")}))); 1431 1432 if (reset == 1 || state()->frameSettings().contains(devices()->getActiveChip()) == false) 1433 { 1434 QVariantMap settings; 1435 1436 settings["x"] = 0; 1437 settings["y"] = 0; 1438 settings["w"] = captureFrameWN->maximum(); 1439 settings["h"] = captureFrameHN->maximum(); 1440 settings["binx"] = captureBinHN->value(); 1441 settings["biny"] = captureBinVN->value(); 1442 1443 state()->frameSettings()[devices()->getActiveChip()] = settings; 1444 } 1445 else if (reset == 2 && state()->frameSettings().contains(devices()->getActiveChip())) 1446 { 1447 QVariantMap settings = state()->frameSettings()[devices()->getActiveChip()]; 1448 int x, y, w, h; 1449 1450 x = settings["x"].toInt(); 1451 y = settings["y"].toInt(); 1452 w = settings["w"].toInt(); 1453 h = settings["h"].toInt(); 1454 1455 // Bound them 1456 x = qBound(captureFrameXN->minimum(), x, captureFrameXN->maximum() - 1); 1457 y = qBound(captureFrameYN->minimum(), y, captureFrameYN->maximum() - 1); 1458 w = qBound(captureFrameWN->minimum(), w, captureFrameWN->maximum()); 1459 h = qBound(captureFrameHN->minimum(), h, captureFrameHN->maximum()); 1460 1461 settings["x"] = x; 1462 settings["y"] = y; 1463 settings["w"] = w; 1464 settings["h"] = h; 1465 settings["binx"] = captureBinHN->value(); 1466 settings["biny"] = captureBinVN->value(); 1467 1468 state()->frameSettings()[devices()->getActiveChip()] = settings; 1469 } 1470 1471 if (state()->frameSettings().contains(devices()->getActiveChip())) 1472 { 1473 QVariantMap settings = state()->frameSettings()[devices()->getActiveChip()]; 1474 int x = settings["x"].toInt(); 1475 int y = settings["y"].toInt(); 1476 int w = settings["w"].toInt(); 1477 int h = settings["h"].toInt(); 1478 1479 if (devices()->getActiveChip()->canBin()) 1480 { 1481 devices()->getActiveChip()->getMaxBin(&binx, &biny); 1482 captureBinHN->setMaximum(binx); 1483 captureBinVN->setMaximum(biny); 1484 1485 captureBinHN->setValue(settings["binx"].toInt()); 1486 captureBinVN->setValue(settings["biny"].toInt()); 1487 } 1488 else 1489 { 1490 captureBinHN->setValue(1); 1491 captureBinVN->setValue(1); 1492 } 1493 1494 if (x >= 0) 1495 captureFrameXN->setValue(x); 1496 if (y >= 0) 1497 captureFrameYN->setValue(y); 1498 if (w > 0) 1499 captureFrameWN->setValue(w); 1500 if (h > 0) 1501 captureFrameHN->setValue(h); 1502 } 1503 } 1504 1505 void Capture::processCameraNumber(INDI::Property prop) 1506 { 1507 if (devices()->getActiveCamera() == nullptr) 1508 return; 1509 1510 if ((prop.isNameMatch("CCD_FRAME") && state()->useGuideHead() == false) || 1511 (prop.isNameMatch("GUIDER_FRAME") && state()->useGuideHead())) 1512 updateFrameProperties(); 1513 else if ((prop.isNameMatch("CCD_INFO") && state()->useGuideHead() == false) || 1514 (prop.isNameMatch("GUIDER_INFO") && state()->useGuideHead())) 1515 updateFrameProperties(1); 1516 else if (prop.isNameMatch("CCD_TRANSFER_FORMAT") || prop.isNameMatch("CCD_CAPTURE_FORMAT")) 1517 updateCaptureFormats(); 1518 else if (prop.isNameMatch("CCD_CONTROLS")) 1519 { 1520 auto nvp = prop.getNumber(); 1521 auto gain = nvp->findWidgetByName("Gain"); 1522 if (gain) 1523 currentGainLabel->setText(QString::number(gain->value, 'f', 0)); 1524 auto offset = nvp->findWidgetByName("Offset"); 1525 if (offset) 1526 currentOffsetLabel->setText(QString::number(offset->value, 'f', 0)); 1527 } 1528 else if (prop.isNameMatch("CCD_GAIN")) 1529 { 1530 auto nvp = prop.getNumber(); 1531 currentGainLabel->setText(QString::number(nvp->at(0)->getValue(), 'f', 0)); 1532 } 1533 else if (prop.isNameMatch("CCD_OFFSET")) 1534 { 1535 auto nvp = prop.getNumber(); 1536 currentOffsetLabel->setText(QString::number(nvp->at(0)->getValue(), 'f', 0)); 1537 } 1538 } 1539 1540 void Capture::syncFrameType(const QString &name) 1541 { 1542 if (!activeCamera() || name != activeCamera()->getDeviceName()) 1543 return; 1544 1545 QStringList frameTypes = process()->frameTypes(); 1546 1547 captureTypeS->clear(); 1548 1549 if (frameTypes.isEmpty()) 1550 captureTypeS->setEnabled(false); 1551 else 1552 { 1553 captureTypeS->setEnabled(true); 1554 captureTypeS->addItems(frameTypes); 1555 ISD::CameraChip *tChip = devices()->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD); 1556 captureTypeS->setCurrentIndex(tChip->getFrameType()); 1557 } 1558 } 1559 1560 QString Capture::filterWheel() 1561 { 1562 if (devices()->filterWheel()) 1563 return devices()->filterWheel()->getDeviceName(); 1564 1565 return QString(); 1566 } 1567 1568 bool Capture::setFilter(const QString &filter) 1569 { 1570 if (devices()->filterWheel()) 1571 { 1572 FilterPosCombo->setCurrentText(filter); 1573 return true; 1574 } 1575 1576 return false; 1577 } 1578 1579 QString Capture::filter() 1580 { 1581 return FilterPosCombo->currentText(); 1582 } 1583 1584 void Capture::updateCurrentFilterPosition() 1585 { 1586 const QString currentFilterText = FilterPosCombo->itemText(m_FilterManager->getFilterPosition() - 1); 1587 state()->setCurrentFilterPosition(m_FilterManager->getFilterPosition(), 1588 currentFilterText, 1589 m_FilterManager->getFilterLock(currentFilterText)); 1590 } 1591 1592 void Capture::refreshFilterSettings() 1593 { 1594 FilterPosCombo->clear(); 1595 1596 if (!devices()->filterWheel()) 1597 { 1598 FilterPosLabel->setEnabled(false); 1599 FilterPosCombo->setEnabled(false); 1600 filterEditB->setEnabled(false); 1601 1602 devices()->setFilterManager(m_FilterManager); 1603 return; 1604 } 1605 1606 FilterPosLabel->setEnabled(true); 1607 FilterPosCombo->setEnabled(true); 1608 filterEditB->setEnabled(true); 1609 1610 setupFilterManager(); 1611 1612 process()->updateFilterInfo(); 1613 1614 FilterPosCombo->addItems(process()->filterLabels()); 1615 Options::setCaptureStandAloneFilters(standAloneEncode(process()->filterLabels())); 1616 1617 updateCurrentFilterPosition(); 1618 1619 filterEditB->setEnabled(state()->getCurrentFilterPosition() > 0); 1620 1621 FilterPosCombo->setCurrentIndex(state()->getCurrentFilterPosition() - 1); 1622 } 1623 1624 void Capture::processingFITSfinished(bool success) 1625 { 1626 // do nothing in case of failure 1627 if (success == false) 1628 return; 1629 1630 // If this is a preview job, make sure to enable preview button after 1631 if (devices()->getActiveCamera() 1632 && devices()->getActiveCamera()->getUploadMode() != ISD::Camera::UPLOAD_LOCAL) 1633 previewB->setEnabled(true); 1634 1635 imageCapturingCompleted(); 1636 } 1637 1638 void Capture::imageCapturingCompleted() 1639 { 1640 SequenceJob *thejob = activeJob(); 1641 1642 if (!thejob) 1643 return; 1644 1645 // In case we're framing, let's return quickly to continue the process. 1646 if (state()->isLooping()) 1647 { 1648 captureStatusWidget->setStatus(i18n("Framing..."), Qt::darkGreen); 1649 return; 1650 } 1651 1652 // If fast exposure is off, disconnect exposure progress 1653 // otherwise, keep it going since it fires off from driver continuous capture process. 1654 if (devices()->getActiveCamera()->isFastExposureEnabled() == false) 1655 DarkLibrary::Instance()->disconnect(this); 1656 1657 // Do not display notifications for very short captures 1658 if (thejob->getCoreProperty(SequenceJob::SJ_Exposure).toDouble() >= 1) 1659 KSNotification::event(QLatin1String("EkosCaptureImageReceived"), i18n("Captured image received"), 1660 KSNotification::Capture); 1661 1662 // If it was initially set as pure preview job and NOT as preview for calibration 1663 if (thejob->jobType() == SequenceJob::JOBTYPE_PREVIEW) 1664 return; 1665 1666 /* The image progress has now one more capture */ 1667 imgProgress->setValue(thejob->getCompleted()); 1668 } 1669 1670 void Capture::captureStopped() 1671 { 1672 imgProgress->reset(); 1673 imgProgress->setEnabled(false); 1674 1675 frameRemainingTime->setText("--:--:--"); 1676 jobRemainingTime->setText("--:--:--"); 1677 frameInfoLabel->setText(i18n("Expose (-/-):")); 1678 1679 // stopping to CAPTURE_IDLE means that capturing will continue automatically 1680 auto captureState = state()->getCaptureState(); 1681 if (captureState == CAPTURE_ABORTED || captureState == CAPTURE_SUSPENDED || captureState == CAPTURE_COMPLETE) 1682 updateStartButtons(false, false); 1683 } 1684 1685 void Capture::updateTargetDistance(double targetDiff) 1686 { 1687 // ensure that the drift is visible 1688 targetDriftLabel->setVisible(true); 1689 targetDrift->setVisible(true); 1690 targetDriftUnit->setVisible(true); 1691 // update the drift value 1692 targetDrift->setText(QString("%L1").arg(targetDiff, 0, 'd', 1)); 1693 } 1694 1695 void Capture::captureImageStarted() 1696 { 1697 if (devices()->filterWheel() != nullptr) 1698 { 1699 // JM 2021.08.23 Call filter info to set the active filter wheel in the camera driver 1700 // so that it may snoop on the active filter 1701 process()->updateFilterInfo(); 1702 updateCurrentFilterPosition(); 1703 } 1704 1705 // necessary since the status widget doesn't store the calibration stage 1706 if (activeJob()->getCalibrationStage() == SequenceJobState::CAL_CALIBRATION) 1707 captureStatusWidget->setStatus(i18n("Calibrating..."), Qt::yellow); 1708 } 1709 1710 namespace 1711 { 1712 QString frameLabel(CCDFrameType type, const QString &filter) 1713 { 1714 switch(type) 1715 { 1716 case FRAME_LIGHT: 1717 if (filter.size() == 0) 1718 return CCDFrameTypeNames[type]; 1719 else 1720 return filter; 1721 break; 1722 case FRAME_FLAT: 1723 if (filter.size() == 0) 1724 return CCDFrameTypeNames[type]; 1725 else 1726 return QString("%1 %2").arg(filter).arg(CCDFrameTypeNames[type]); 1727 break; 1728 case FRAME_BIAS: 1729 case FRAME_DARK: 1730 case FRAME_NONE: 1731 default: 1732 return CCDFrameTypeNames[type]; 1733 } 1734 } 1735 } 1736 1737 void Capture::captureRunning() 1738 { 1739 emit captureStarting(activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(), 1740 activeJob()->getCoreProperty(SequenceJob::SJ_Filter).toString()); 1741 appendLogText(i18n("Capturing %1-second %2 image...", 1742 QString("%L1").arg(activeJob()->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(), 0, 'f', 3), 1743 activeJob()->getCoreProperty(SequenceJob::SJ_Filter).toString())); 1744 frameInfoLabel->setText(QString("%1 (%L3/%L4):").arg(frameLabel(activeJob()->getFrameType(), 1745 activeJob()->getCoreProperty(SequenceJob::SJ_Filter).toString())) 1746 .arg(activeJob()->getCompleted()).arg(activeJob()->getCoreProperty( 1747 SequenceJob::SJ_Count).toInt())); 1748 // ensure that the download time label is visible 1749 avgDownloadTime->setVisible(true); 1750 avgDownloadLabel->setVisible(true); 1751 secLabel->setVisible(true); 1752 // show estimated download time 1753 avgDownloadTime->setText(QString("%L1").arg(state()->averageDownloadTime(), 0, 'd', 2)); 1754 } 1755 1756 void Capture::appendLogText(const QString &text) 1757 { 1758 m_LogText.insert(0, i18nc("log entry; %1 is the date, %2 is the text", "%1 %2", 1759 KStarsData::Instance()->lt().toString("yyyy-MM-ddThh:mm:ss"), text)); 1760 1761 qCInfo(KSTARS_EKOS_CAPTURE) << text; 1762 1763 emit newLog(text); 1764 } 1765 1766 void Capture::clearLog() 1767 { 1768 m_LogText.clear(); 1769 emit newLog(QString()); 1770 } 1771 1772 void Capture::updateDownloadProgress(double downloadTimeLeft) 1773 { 1774 frameRemainingTime->setText(state()->imageCountDown().toString("hh:mm:ss")); 1775 emit newDownloadProgress(downloadTimeLeft); 1776 } 1777 1778 void Capture::updateCaptureCountDown(int deltaMillis) 1779 { 1780 state()->imageCountDownAddMSecs(deltaMillis); 1781 state()->sequenceCountDownAddMSecs(deltaMillis); 1782 frameRemainingTime->setText(state()->imageCountDown().toString("hh:mm:ss")); 1783 jobRemainingTime->setText(state()->sequenceCountDown().toString("hh:mm:ss")); 1784 } 1785 1786 void Capture::updateCCDTemperature(double value) 1787 { 1788 if (cameraTemperatureS->isEnabled() == false && devices()->getActiveCamera()) 1789 { 1790 if (devices()->getActiveCamera()->getPermission("CCD_TEMPERATURE") != IP_RO) 1791 process()->checkCamera(); 1792 } 1793 1794 temperatureOUT->setText(QString("%L1").arg(value, 0, 'f', 2)); 1795 1796 if (cameraTemperatureN->cleanText().isEmpty()) 1797 cameraTemperatureN->setValue(value); 1798 } 1799 1800 void Capture::updateRotatorAngle(double value) 1801 { 1802 IPState RState = devices()->rotator()->absoluteAngleState(); 1803 if (RState == IPS_OK) 1804 m_RotatorControlPanel->updateRotator(value); 1805 else 1806 m_RotatorControlPanel->updateGauge(value); 1807 } 1808 1809 void Capture::addJob(SequenceJob *job) 1810 { 1811 // create a new row 1812 createNewJobTableRow(job); 1813 } 1814 1815 SequenceJob *Capture::createJob(SequenceJob::SequenceJobType jobtype, FilenamePreviewType filenamePreview) 1816 { 1817 SequenceJob *job = new SequenceJob(devices(), state(), jobtype); 1818 1819 updateJobFromUI(job, filenamePreview); 1820 1821 // Nothing more to do if preview or for placeholder calculations 1822 if (jobtype == SequenceJob::JOBTYPE_PREVIEW || filenamePreview != NOT_PREVIEW) 1823 return job; 1824 1825 // check if the upload paths are correct 1826 if (checkUploadPaths(filenamePreview) == false) 1827 return nullptr; 1828 1829 // all other jobs will be added to the job list 1830 state()->allJobs().append(job); 1831 1832 // create a new row 1833 createNewJobTableRow(job); 1834 1835 return job; 1836 } 1837 1838 void Ekos::Capture::createNewJobTableRow(SequenceJob *job) 1839 { 1840 int currentRow = queueTable->rowCount(); 1841 queueTable->insertRow(currentRow); 1842 1843 // create job table widgets 1844 QTableWidgetItem *status = new QTableWidgetItem(); 1845 status->setTextAlignment(Qt::AlignHCenter); 1846 status->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1847 1848 QTableWidgetItem *filter = new QTableWidgetItem(); 1849 filter->setTextAlignment(Qt::AlignHCenter); 1850 filter->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1851 1852 QTableWidgetItem *count = new QTableWidgetItem(); 1853 count->setTextAlignment(Qt::AlignHCenter); 1854 count->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1855 1856 QTableWidgetItem *exp = new QTableWidgetItem(); 1857 exp->setTextAlignment(Qt::AlignHCenter); 1858 exp->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1859 1860 QTableWidgetItem *type = new QTableWidgetItem(); 1861 type->setTextAlignment(Qt::AlignHCenter); 1862 type->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1863 1864 QTableWidgetItem *bin = new QTableWidgetItem(); 1865 bin->setTextAlignment(Qt::AlignHCenter); 1866 bin->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1867 1868 QTableWidgetItem *iso = new QTableWidgetItem(); 1869 iso->setTextAlignment(Qt::AlignHCenter); 1870 iso->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1871 1872 QTableWidgetItem *offset = new QTableWidgetItem(); 1873 offset->setTextAlignment(Qt::AlignHCenter); 1874 offset->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 1875 1876 // add the widgets to the table 1877 queueTable->setItem(currentRow, JOBTABLE_COL_STATUS, status); 1878 queueTable->setItem(currentRow, JOBTABLE_COL_FILTER, filter); 1879 queueTable->setItem(currentRow, JOBTABLE_COL_COUNTS, count); 1880 queueTable->setItem(currentRow, JOBTABLE_COL_EXP, exp); 1881 queueTable->setItem(currentRow, JOBTABLE_COL_TYPE, type); 1882 queueTable->setItem(currentRow, JOBTABLE_COL_BINNING, bin); 1883 queueTable->setItem(currentRow, JOBTABLE_COL_ISO, iso); 1884 queueTable->setItem(currentRow, JOBTABLE_COL_OFFSET, offset); 1885 1886 // full update to the job table row 1887 updateJobTable(job, true); 1888 1889 // Create a new JSON object. Needs to be called after the new row has been filled 1890 QJsonObject jsonJob = createJsonJob(job, currentRow); 1891 state()->getSequence().append(jsonJob); 1892 emit sequenceChanged(state()->getSequence()); 1893 1894 removeFromQueueB->setEnabled(true); 1895 } 1896 1897 1898 void Capture::editJobFinished() 1899 { 1900 if (queueTable->currentRow() < 0) 1901 qCWarning(KSTARS_EKOS_CAPTURE()) << "Editing finished, but no row selected!"; 1902 1903 int currentRow = queueTable->currentRow(); 1904 SequenceJob *job = state()->allJobs().at(currentRow); 1905 updateJobFromUI(job); 1906 1907 // full update to the job table row 1908 updateJobTable(job, true); 1909 1910 // Update the JSON object for the current row. Needs to be called after the new row has been filled 1911 QJsonObject jsonJob = createJsonJob(job, currentRow); 1912 state()->getSequence().replace(currentRow, jsonJob); 1913 emit sequenceChanged(state()->getSequence()); 1914 1915 resetJobEdit(); 1916 appendLogText(i18n("Job #%1 changes applied.", currentRow + 1)); 1917 } 1918 1919 void Capture::removeJobFromQueue() 1920 { 1921 int currentRow = queueTable->currentRow(); 1922 1923 if (currentRow < 0) 1924 currentRow = queueTable->rowCount() - 1; 1925 1926 removeJob(currentRow); 1927 1928 // update selection 1929 if (queueTable->rowCount() == 0) 1930 return; 1931 1932 if (currentRow > queueTable->rowCount()) 1933 queueTable->selectRow(queueTable->rowCount() - 1); 1934 else 1935 queueTable->selectRow(currentRow); 1936 } 1937 1938 bool Capture::removeJob(int index) 1939 { 1940 if (state()->getCaptureState() != CAPTURE_IDLE && state()->getCaptureState() != CAPTURE_ABORTED 1941 && state()->getCaptureState() != CAPTURE_COMPLETE) 1942 return false; 1943 1944 if (m_JobUnderEdit) 1945 { 1946 resetJobEdit(true); 1947 return false; 1948 } 1949 1950 if (index < 0 || index >= state()->allJobs().count()) 1951 return false; 1952 1953 queueTable->removeRow(index); 1954 QJsonArray seqArray = state()->getSequence(); 1955 seqArray.removeAt(index); 1956 state()->setSequence(seqArray); 1957 emit sequenceChanged(seqArray); 1958 1959 if (state()->allJobs().empty()) 1960 return true; 1961 1962 SequenceJob * job = state()->allJobs().at(index); 1963 // remove completed frame counts from frame count map 1964 state()->removeCapturedFrameCount(job->getSignature(), job->getCompleted()); 1965 // remove the job 1966 state()->allJobs().removeOne(job); 1967 if (job == activeJob()) 1968 state()->setActiveJob(nullptr); 1969 1970 delete job; 1971 1972 if (queueTable->rowCount() == 0) 1973 removeFromQueueB->setEnabled(false); 1974 1975 if (queueTable->rowCount() == 1) 1976 { 1977 queueUpB->setEnabled(false); 1978 queueDownB->setEnabled(false); 1979 } 1980 1981 if (index < queueTable->rowCount()) 1982 queueTable->selectRow(index); 1983 else if (queueTable->rowCount() > 0) 1984 queueTable->selectRow(queueTable->rowCount() - 1); 1985 1986 if (queueTable->rowCount() == 0) 1987 { 1988 queueSaveAsB->setEnabled(false); 1989 queueSaveB->setEnabled(false); 1990 resetB->setEnabled(false); 1991 } 1992 1993 state()->setDirty(true); 1994 1995 return true; 1996 } 1997 1998 void Capture::moveJob(bool up) 1999 { 2000 int currentRow = queueTable->currentRow(); 2001 int destinationRow = up ? currentRow - 1 : currentRow + 1; 2002 2003 int columnCount = queueTable->columnCount(); 2004 2005 if (currentRow < 0 || destinationRow < 0 || destinationRow >= queueTable->rowCount()) 2006 return; 2007 2008 for (int i = 0; i < columnCount; i++) 2009 { 2010 QTableWidgetItem * selectedLine = queueTable->takeItem(currentRow, i); 2011 QTableWidgetItem * counterpart = queueTable->takeItem(destinationRow, i); 2012 2013 queueTable->setItem(destinationRow, i, selectedLine); 2014 queueTable->setItem(currentRow, i, counterpart); 2015 } 2016 2017 SequenceJob * job = state()->allJobs().takeAt(currentRow); 2018 2019 state()->allJobs().removeOne(job); 2020 state()->allJobs().insert(destinationRow, job); 2021 2022 QJsonArray seqArray = state()->getSequence(); 2023 QJsonObject currentJob = seqArray[currentRow].toObject(); 2024 seqArray.replace(currentRow, seqArray[destinationRow]); 2025 seqArray.replace(destinationRow, currentJob); 2026 emit sequenceChanged(seqArray); 2027 2028 queueTable->selectRow(destinationRow); 2029 2030 state()->setDirty(true); 2031 } 2032 2033 void Capture::newTargetName(const QString &name) 2034 { 2035 targetNameT->setText(name); 2036 generatePreviewFilename(); 2037 } 2038 2039 void Capture::setBusy(bool enable) 2040 { 2041 previewB->setEnabled(!enable); 2042 loopB->setEnabled(!enable); 2043 opticalTrainCombo->setEnabled(!enable); 2044 trainB->setEnabled(!enable); 2045 2046 foreach (QAbstractButton * button, queueEditButtonGroup->buttons()) 2047 button->setEnabled(!enable); 2048 } 2049 2050 void Capture::jobPrepared(SequenceJob * job) 2051 { 2052 2053 int index = state()->allJobs().indexOf(job); 2054 if (index >= 0) 2055 queueTable->selectRow(index); 2056 2057 if (activeJob()->jobType() != SequenceJob::JOBTYPE_PREVIEW) 2058 { 2059 // set the progress info 2060 imgProgress->setEnabled(true); 2061 imgProgress->setMaximum(activeJob()->getCoreProperty(SequenceJob::SJ_Count).toInt()); 2062 imgProgress->setValue(activeJob()->getCompleted()); 2063 } 2064 } 2065 2066 void Capture::jobExecutionPreparationStarted() 2067 { 2068 if (activeJob() == nullptr) 2069 { 2070 // this should never happen 2071 qWarning(KSTARS_EKOS_CAPTURE) << "jobExecutionPreparationStarted with null state()->getActiveJob()."; 2072 return; 2073 } 2074 if (activeJob()->jobType() == SequenceJob::JOBTYPE_PREVIEW) 2075 updateStartButtons(true, false); 2076 } 2077 2078 void Capture::updatePrepareState(CaptureState prepareState) 2079 { 2080 state()->setCaptureState(prepareState); 2081 2082 if (activeJob() == nullptr) 2083 { 2084 qWarning(KSTARS_EKOS_CAPTURE) << "updatePrepareState with null activeJob()."; 2085 // Everything below depends on activeJob(). Just return. 2086 return; 2087 } 2088 2089 switch (prepareState) 2090 { 2091 case CAPTURE_SETTING_TEMPERATURE: 2092 appendLogText(i18n("Setting temperature to %1 °C...", activeJob()->getTargetTemperature())); 2093 captureStatusWidget->setStatus(i18n("Set Temp to %1 °C...", activeJob()->getTargetTemperature()), 2094 Qt::yellow); 2095 break; 2096 case CAPTURE_GUIDER_DRIFT: 2097 appendLogText(i18n("Waiting for guide drift below %1\"...", Options::startGuideDeviation())); 2098 captureStatusWidget->setStatus(i18n("Wait for Guider < %1\"...", Options::startGuideDeviation()), Qt::yellow); 2099 break; 2100 2101 case CAPTURE_SETTING_ROTATOR: 2102 appendLogText(i18n("Setting camera to %1 degrees E of N...", activeJob()->getTargetRotation())); 2103 captureStatusWidget->setStatus(i18n("Set Camera to %1 deg...", activeJob()->getTargetRotation()), 2104 Qt::yellow); 2105 break; 2106 2107 default: 2108 break; 2109 2110 } 2111 } 2112 2113 void Capture::setFocusTemperatureDelta(double focusTemperatureDelta, double absTemperture) 2114 { 2115 Q_UNUSED(absTemperture); 2116 // This produces too much log spam 2117 // Maybe add a threshold to report later? 2118 //qCDebug(KSTARS_EKOS_CAPTURE) << "setFocusTemperatureDelta: " << focusTemperatureDelta; 2119 state()->getRefocusState()->setFocusTemperatureDelta(focusTemperatureDelta); 2120 } 2121 2122 void Capture::setGuideDeviation(double delta_ra, double delta_dec) 2123 { 2124 const double deviation_rms = std::hypot(delta_ra, delta_dec); 2125 2126 // forward it to the state machine 2127 state()->setGuideDeviation(deviation_rms); 2128 2129 } 2130 2131 void Capture::setFocusStatus(FocusState newstate) 2132 { 2133 // directly forward it to the state machine 2134 state()->updateFocusState(newstate); 2135 } 2136 2137 void Capture::updateFocusStatus(FocusState newstate) 2138 { 2139 if ((state()->getRefocusState()->isRefocusing() 2140 || state()->getRefocusState()->isInSequenceFocus()) && activeJob() 2141 && activeJob()->getStatus() == JOB_BUSY) 2142 { 2143 switch (newstate) 2144 { 2145 case FOCUS_COMPLETE: 2146 appendLogText(i18n("Focus complete.")); 2147 captureStatusWidget->setStatus(i18n("Focus complete."), Qt::yellow); 2148 break; 2149 case FOCUS_FAILED: 2150 case FOCUS_ABORTED: 2151 captureStatusWidget->setStatus(i18n("Autofocus failed."), Qt::darkRed); 2152 break; 2153 default: 2154 // otherwise do nothing 2155 break; 2156 } 2157 } 2158 } 2159 2160 2161 2162 void Capture::updateMeridianFlipStage(MeridianFlipState::MFStage stage) 2163 { 2164 // update UI 2165 if (getMeridianFlipState()->getMeridianFlipStage() != stage) 2166 { 2167 switch (stage) 2168 { 2169 case MeridianFlipState::MF_READY: 2170 if (state()->getCaptureState() == CAPTURE_PAUSED) 2171 { 2172 // paused after meridian flip requested 2173 captureStatusWidget->setStatus(i18n("Paused..."), Qt::yellow); 2174 } 2175 break; 2176 2177 case MeridianFlipState::MF_INITIATED: 2178 captureStatusWidget->setStatus(i18n("Meridian Flip..."), Qt::yellow); 2179 KSNotification::event(QLatin1String("MeridianFlipStarted"), i18n("Meridian flip started"), KSNotification::Capture); 2180 break; 2181 2182 case MeridianFlipState::MF_COMPLETED: 2183 captureStatusWidget->setStatus(i18n("Flip complete."), Qt::yellow); 2184 break; 2185 2186 default: 2187 break; 2188 } 2189 } 2190 } 2191 2192 void Capture::setRotatorReversed(bool toggled) 2193 { 2194 m_RotatorControlPanel->reverseDirection->setEnabled(true); 2195 2196 m_RotatorControlPanel->reverseDirection->blockSignals(true); 2197 m_RotatorControlPanel->reverseDirection->setChecked(toggled); 2198 m_RotatorControlPanel->reverseDirection->blockSignals(false); 2199 } 2200 2201 void Capture::saveFITSDirectory() 2202 { 2203 QString dir = 2204 QFileDialog::getExistingDirectory(Manager::Instance(), i18nc("@title:window", "FITS Save Directory"), 2205 dirPath.toLocalFile()); 2206 if (dir.isEmpty()) 2207 return; 2208 2209 fileDirectoryT->setText(QDir::toNativeSeparators(dir)); 2210 } 2211 2212 void Capture::loadSequenceQueue() 2213 { 2214 QUrl fileURL = QFileDialog::getOpenFileUrl(Manager::Instance(), i18nc("@title:window", "Open Ekos Sequence Queue"), 2215 dirPath, 2216 "Ekos Sequence Queue (*.esq)"); 2217 if (fileURL.isEmpty()) 2218 return; 2219 2220 if (fileURL.isValid() == false) 2221 { 2222 QString message = i18n("Invalid URL: %1", fileURL.toLocalFile()); 2223 KSNotification::sorry(message, i18n("Invalid URL")); 2224 return; 2225 } 2226 2227 dirPath = QUrl(fileURL.url(QUrl::RemoveFilename)); 2228 2229 loadSequenceQueue(fileURL.toLocalFile()); 2230 } 2231 2232 bool Capture::loadSequenceQueue(const QString &fileURL, QString targetName) 2233 { 2234 QFile sFile(fileURL); 2235 if (!sFile.open(QIODevice::ReadOnly)) 2236 { 2237 QString message = i18n("Unable to open file %1", fileURL); 2238 KSNotification::sorry(message, i18n("Could Not Open File")); 2239 return false; 2240 } 2241 2242 state()->clearCapturedFramesMap(); 2243 clearSequenceQueue(); 2244 2245 // !m_standAlone so the stand-alone editor doesn't influence a live capture sesion. 2246 const bool result = process()->loadSequenceQueue(fileURL, targetName, !m_standAlone); 2247 // cancel if loading fails 2248 if (result == false) 2249 return result; 2250 2251 // update general settings 2252 setObserverName(state()->observerName()); 2253 syncGUIToGeneralSettings(); 2254 2255 // select the first one of the loaded jobs 2256 if (state()->allJobs().size() > 0) 2257 syncGUIToJob(state()->allJobs().first()); 2258 2259 // update save button tool tip 2260 queueSaveB->setToolTip("Save to " + sFile.fileName()); 2261 2262 return true; 2263 } 2264 2265 void Capture::saveSequenceQueue() 2266 { 2267 QUrl backupCurrent = state()->sequenceURL(); 2268 2269 if (state()->sequenceURL().toLocalFile().startsWith(QLatin1String("/tmp/")) 2270 || state()->sequenceURL().toLocalFile().contains("/Temp")) 2271 state()->setSequenceURL(QUrl("")); 2272 2273 // If no changes made, return. 2274 if (state()->dirty() == false && !state()->sequenceURL().isEmpty()) 2275 return; 2276 2277 if (state()->sequenceURL().isEmpty()) 2278 { 2279 state()->setSequenceURL(QFileDialog::getSaveFileUrl(Manager::Instance(), i18nc("@title:window", 2280 "Save Ekos Sequence Queue"), 2281 dirPath, 2282 "Ekos Sequence Queue (*.esq)")); 2283 // if user presses cancel 2284 if (state()->sequenceURL().isEmpty()) 2285 { 2286 state()->setSequenceURL(backupCurrent); 2287 return; 2288 } 2289 2290 dirPath = QUrl(state()->sequenceURL().url(QUrl::RemoveFilename)); 2291 2292 if (state()->sequenceURL().toLocalFile().endsWith(QLatin1String(".esq")) == false) 2293 state()->setSequenceURL(QUrl("file:" + state()->sequenceURL().toLocalFile() + ".esq")); 2294 2295 } 2296 2297 if (state()->sequenceURL().isValid()) 2298 { 2299 // !m_standAlone so the stand-alone editor doesn't influence a live capture sesion. 2300 if ((process()->saveSequenceQueue(state()->sequenceURL().toLocalFile(), !m_standAlone)) == false) 2301 { 2302 KSNotification::error(i18n("Failed to save sequence queue"), i18n("Save")); 2303 return; 2304 } 2305 2306 state()->setDirty(false); 2307 } 2308 else 2309 { 2310 QString message = i18n("Invalid URL: %1", state()->sequenceURL().url()); 2311 KSNotification::sorry(message, i18n("Invalid URL")); 2312 } 2313 } 2314 2315 void Capture::saveSequenceQueueAs() 2316 { 2317 state()->setSequenceURL(QUrl("")); 2318 saveSequenceQueue(); 2319 } 2320 2321 bool Capture::saveSequenceQueue(const QString &path) 2322 { 2323 // forward it to the process engine 2324 return process()->saveSequenceQueue(path); 2325 } 2326 2327 void Capture::resetJobs() 2328 { 2329 // Stop any running capture 2330 stop(); 2331 2332 // If a job is selected for edit, reset only that job 2333 if (m_JobUnderEdit == true) 2334 { 2335 SequenceJob * job = state()->allJobs().at(queueTable->currentRow()); 2336 if (nullptr != job) 2337 { 2338 job->resetStatus(); 2339 updateJobTable(job); 2340 } 2341 } 2342 else 2343 { 2344 if (KMessageBox::warningContinueCancel( 2345 nullptr, i18n("Are you sure you want to reset status of all jobs?"), i18n("Reset job status"), 2346 KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "reset_job_status_warning") != KMessageBox::Continue) 2347 { 2348 return; 2349 } 2350 2351 foreach (SequenceJob * job, state()->allJobs()) 2352 { 2353 job->resetStatus(); 2354 updateJobTable(job); 2355 } 2356 } 2357 2358 // Also reset the storage count for all jobs 2359 state()->clearCapturedFramesMap(); 2360 2361 // We're not controlled by the Scheduler, restore progress option 2362 state()->setIgnoreJobProgress(Options::alwaysResetSequenceWhenStarting()); 2363 2364 // enable start button 2365 startB->setEnabled(true); 2366 } 2367 2368 void Capture::ignoreSequenceHistory() 2369 { 2370 // This function is called independently from the Scheduler or the UI, so honor the change 2371 state()->setIgnoreJobProgress(true); 2372 } 2373 2374 void Capture::syncGUIToJob(SequenceJob * job) 2375 { 2376 if (job == nullptr) 2377 { 2378 qWarning(KSTARS_EKOS_CAPTURE) << "syncGuiToJob with null job."; 2379 // Everything below depends on job. Just return. 2380 return; 2381 } 2382 2383 const auto roi = job->getCoreProperty(SequenceJob::SJ_ROI).toRect(); 2384 2385 captureFormatS->setCurrentText(job->getCoreProperty(SequenceJob::SJ_Format).toString()); 2386 captureEncodingS->setCurrentText(job->getCoreProperty(SequenceJob::SJ_Encoding).toString()); 2387 captureExposureN->setValue(job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble()); 2388 captureBinHN->setValue(job->getCoreProperty(SequenceJob::SJ_Binning).toPoint().x()); 2389 captureBinVN->setValue(job->getCoreProperty(SequenceJob::SJ_Binning).toPoint().y()); 2390 captureFrameXN->setValue(roi.x()); 2391 captureFrameYN->setValue(roi.y()); 2392 captureFrameWN->setValue(roi.width()); 2393 captureFrameHN->setValue(roi.height()); 2394 FilterPosCombo->setCurrentIndex(job->getTargetFilter() - 1); 2395 captureTypeS->setCurrentIndex(job->getFrameType()); 2396 captureCountN->setValue(job->getCoreProperty(SequenceJob::SJ_Count).toInt()); 2397 captureDelayN->setValue(job->getCoreProperty(SequenceJob::SJ_Delay).toInt() / 1000); 2398 targetNameT->setText(job->getCoreProperty(SequenceJob::SJ_TargetName).toString()); 2399 fileDirectoryT->setText(job->getCoreProperty(SequenceJob::SJ_LocalDirectory).toString()); 2400 fileUploadModeS->setCurrentIndex(job->getUploadMode()); 2401 fileRemoteDirT->setEnabled(fileUploadModeS->currentIndex() != 0); 2402 fileRemoteDirT->setText(job->getCoreProperty(SequenceJob::SJ_RemoteDirectory).toString()); 2403 placeholderFormatT->setText(job->getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString()); 2404 formatSuffixN->setValue(job->getCoreProperty(SequenceJob::SJ_PlaceholderSuffix).toUInt()); 2405 m_LimitsUI->limitDitherFrequencyN->setValue(job->getCoreProperty(SequenceJob::SJ_DitherPerJobFrequency).toInt()); 2406 2407 // Temperature Options 2408 cameraTemperatureS->setChecked(job->getCoreProperty(SequenceJob::SJ_EnforceTemperature).toBool()); 2409 if (job->getCoreProperty(SequenceJob::SJ_EnforceTemperature).toBool()) 2410 cameraTemperatureN->setValue(job->getTargetTemperature()); 2411 2412 // Start guider drift options 2413 m_LimitsUI->startGuiderDriftS->setChecked(Options::enforceStartGuiderDrift()); 2414 if (Options::enforceStartGuiderDrift()) 2415 m_LimitsUI->startGuiderDriftN->setValue(Options::startGuideDeviation()); 2416 2417 // Flat field options 2418 calibrationB->setEnabled(job->getFrameType() != FRAME_LIGHT); 2419 generateDarkFlatsB->setEnabled(job->getFrameType() != FRAME_LIGHT); 2420 state()->setFlatFieldDuration(job->getFlatFieldDuration()); 2421 state()->setCalibrationPreAction(job->getCalibrationPreAction()); 2422 state()->setTargetADU(job->getCoreProperty(SequenceJob::SJ_TargetADU).toDouble()); 2423 state()->setTargetADUTolerance(job->getCoreProperty(SequenceJob::SJ_TargetADUTolerance).toDouble()); 2424 state()->setWallCoord(job->getWallCoord()); 2425 m_scriptsManager->setScripts(job->getScripts()); 2426 2427 // Custom Properties 2428 customPropertiesDialog->setCustomProperties(job->getCustomProperties()); 2429 2430 if (captureISOS) 2431 captureISOS->setCurrentIndex(job->getCoreProperty(SequenceJob::SJ_ISOIndex).toInt()); 2432 2433 double gain = getGain(); 2434 if (gain >= 0) 2435 captureGainN->setValue(gain); 2436 else 2437 captureGainN->setValue(GainSpinSpecialValue); 2438 2439 double offset = getOffset(); 2440 if (offset >= 0) 2441 captureOffsetN->setValue(offset); 2442 else 2443 captureOffsetN->setValue(OffsetSpinSpecialValue); 2444 2445 // update place holder typ 2446 generatePreviewFilename(); 2447 2448 if (m_RotatorControlPanel) // only if rotator is registered 2449 { 2450 if (job->getTargetRotation() != Ekos::INVALID_VALUE) 2451 { 2452 // remove enforceJobPA m_RotatorControlPanel->setRotationEnforced(true); 2453 m_RotatorControlPanel->setCameraPA(job->getTargetRotation()); 2454 } 2455 // remove enforceJobPA 2456 // else 2457 // m_RotatorControlPanel->setRotationEnforced(false); 2458 } 2459 2460 // hide target drift if align check frequency is == 0 2461 if (Options::alignCheckFrequency() == 0) 2462 { 2463 targetDriftLabel->setVisible(false); 2464 targetDrift->setVisible(false); 2465 targetDriftUnit->setVisible(false); 2466 } 2467 2468 emit settingsUpdated(getPresetSettings()); 2469 } 2470 2471 void Capture::syncGUIToGeneralSettings() 2472 { 2473 m_LimitsUI->startGuiderDriftS->setChecked(Options::enforceStartGuiderDrift()); 2474 m_LimitsUI->startGuiderDriftN->setValue(Options::startGuideDeviation()); 2475 m_LimitsUI->limitGuideDeviationS->setChecked(Options::enforceGuideDeviation()); 2476 m_LimitsUI->limitGuideDeviationN->setValue(Options::guideDeviation()); 2477 m_LimitsUI->limitGuideDeviationRepsN->setValue(static_cast<int>(Options::guideDeviationReps())); 2478 m_LimitsUI->limitFocusHFRS->setChecked(Options::enforceAutofocusHFR()); 2479 m_LimitsUI->limitFocusHFRThresholdPercentage->setValue(Options::hFRThresholdPercentage()); 2480 m_LimitsUI->limitFocusHFRN->setValue(Options::hFRDeviation()); 2481 m_LimitsUI->limitFocusHFRCheckFrames->setValue(Options::inSequenceCheckFrames()); 2482 m_LimitsUI->limitFocusHFRAlgorithm->setCurrentIndex(Options::hFRCheckAlgorithm()); 2483 m_LimitsUI->limitFocusDeltaTS->setChecked(Options::enforceAutofocusOnTemperature()); 2484 m_LimitsUI->limitFocusDeltaTN->setValue(Options::maxFocusTemperatureDelta()); 2485 m_LimitsUI->limitRefocusS->setChecked(Options::enforceRefocusEveryN()); 2486 m_LimitsUI->limitRefocusN->setValue(static_cast<int>(Options::refocusEveryN())); 2487 m_LimitsUI->meridianRefocusS->setChecked(Options::refocusAfterMeridianFlip()); 2488 } 2489 2490 QJsonObject Capture::getPresetSettings() 2491 { 2492 QJsonObject settings; 2493 2494 // Try to get settings value 2495 // if not found, fallback to camera value 2496 double gain = -1; 2497 if (GainSpinSpecialValue > INVALID_VALUE && captureGainN->value() > GainSpinSpecialValue) 2498 gain = captureGainN->value(); 2499 else if (devices()->getActiveCamera() && devices()->getActiveCamera()->hasGain()) 2500 devices()->getActiveCamera()->getGain(&gain); 2501 2502 double offset = -1; 2503 if (OffsetSpinSpecialValue > INVALID_VALUE && captureOffsetN->value() > OffsetSpinSpecialValue) 2504 offset = captureOffsetN->value(); 2505 else if (devices()->getActiveCamera() && devices()->getActiveCamera()->hasOffset()) 2506 devices()->getActiveCamera()->getOffset(&offset); 2507 2508 int iso = -1; 2509 if (captureISOS) 2510 iso = captureISOS->currentIndex(); 2511 else if (devices()->getActiveCamera()) 2512 iso = devices()->getActiveCamera()->getChip(ISD::CameraChip::PRIMARY_CCD)->getISOIndex(); 2513 2514 settings.insert("optical_train", opticalTrainCombo->currentText()); 2515 settings.insert("filter", FilterPosCombo->currentText()); 2516 settings.insert("dark", darkB->isChecked()); 2517 settings.insert("exp", captureExposureN->value()); 2518 settings.insert("bin", captureBinHN->value()); 2519 settings.insert("iso", iso); 2520 settings.insert("frameType", captureTypeS->currentIndex()); 2521 settings.insert("captureFormat", captureFormatS->currentIndex()); 2522 settings.insert("transferFormat", captureEncodingS->currentIndex()); 2523 settings.insert("gain", gain); 2524 settings.insert("offset", offset); 2525 settings.insert("temperature", cameraTemperatureN->value()); 2526 settings.insert("ditherPerJobFrequency", m_LimitsUI->limitDitherFrequencyN->value()); 2527 2528 return settings; 2529 } 2530 2531 void Capture::selectedJobChanged(QModelIndex current, QModelIndex previous) 2532 { 2533 Q_UNUSED(previous) 2534 selectJob(current); 2535 } 2536 2537 bool Capture::selectJob(QModelIndex i) 2538 { 2539 if (i.row() < 0 || (i.row() + 1) > state()->allJobs().size()) 2540 return false; 2541 2542 SequenceJob * job = state()->allJobs().at(i.row()); 2543 2544 if (job == nullptr || job->jobType() == SequenceJob::JOBTYPE_DARKFLAT) 2545 return false; 2546 2547 syncGUIToJob(job); 2548 2549 if (state()->isBusy()) 2550 return false; 2551 2552 if (state()->allJobs().size() >= 2) 2553 { 2554 queueUpB->setEnabled(i.row() > 0); 2555 queueDownB->setEnabled(i.row() + 1 < state()->allJobs().size()); 2556 } 2557 2558 return true; 2559 } 2560 2561 void Capture::editJob(QModelIndex i) 2562 { 2563 // Try to select a job. If job not found or not editable return. 2564 if (selectJob(i) == false) 2565 return; 2566 2567 appendLogText(i18n("Editing job #%1...", i.row() + 1)); 2568 2569 addToQueueB->setIcon(QIcon::fromTheme("dialog-ok-apply")); 2570 addToQueueB->setToolTip(i18n("Apply job changes.")); 2571 removeFromQueueB->setToolTip(i18n("Cancel job changes.")); 2572 2573 // Make it sure if user presses enter, the job is validated. 2574 previewB->setDefault(false); 2575 addToQueueB->setDefault(true); 2576 2577 m_JobUnderEdit = true; 2578 } 2579 2580 void Capture::resetJobEdit(bool cancelled) 2581 { 2582 if (cancelled == true) 2583 appendLogText(i18n("Editing job canceled.")); 2584 2585 m_JobUnderEdit = false; 2586 addToQueueB->setIcon(QIcon::fromTheme("list-add")); 2587 2588 addToQueueB->setToolTip(i18n("Add job to sequence queue")); 2589 removeFromQueueB->setToolTip(i18n("Remove job from sequence queue")); 2590 2591 addToQueueB->setDefault(false); 2592 previewB->setDefault(true); 2593 } 2594 2595 void Capture::setMaximumGuidingDeviation(bool enable, double value) 2596 { 2597 m_LimitsUI->limitGuideDeviationS->setChecked(enable); 2598 if (enable) 2599 m_LimitsUI->limitGuideDeviationN->setValue(value); 2600 } 2601 2602 void Capture::setInSequenceFocus(bool enable, double HFR) 2603 { 2604 m_LimitsUI->limitFocusHFRS->setChecked(enable); 2605 if (enable) 2606 m_LimitsUI->limitFocusHFRN->setValue(HFR); 2607 } 2608 2609 void Capture::clearSequenceQueue() 2610 { 2611 state()->setActiveJob(nullptr); 2612 while (queueTable->rowCount() > 0) 2613 queueTable->removeRow(0); 2614 qDeleteAll(state()->allJobs()); 2615 state()->allJobs().clear(); 2616 2617 while (state()->getSequence().count()) 2618 state()->getSequence().pop_back(); 2619 emit sequenceChanged(state()->getSequence()); 2620 } 2621 2622 void Capture::setAlignStatus(AlignState newstate) 2623 { 2624 // forward it directly to the state machine 2625 state()->setAlignState(newstate); 2626 } 2627 2628 void Capture::setGuideStatus(GuideState newstate) 2629 { 2630 // forward it directly to the state machine 2631 state()->setGuideState(newstate); 2632 } 2633 2634 void Capture::checkFrameType(int index) 2635 { 2636 calibrationB->setEnabled(index != FRAME_LIGHT); 2637 generateDarkFlatsB->setEnabled(index != FRAME_LIGHT); 2638 } 2639 2640 void Capture::clearAutoFocusHFR() 2641 { 2642 if (Options::hFRCheckAlgorithm() == HFR_CHECK_FIXED) 2643 return; 2644 2645 m_LimitsUI->limitFocusHFRN->setValue(0); 2646 //firstAutoFocus = true; 2647 } 2648 2649 void Capture::openCalibrationDialog() 2650 { 2651 QDialog calibrationDialog(this); 2652 2653 Ui_calibrationOptions calibrationOptions; 2654 calibrationOptions.setupUi(&calibrationDialog); 2655 2656 calibrationOptions.parkMountC->setEnabled(devices()->mount() && devices()->mount()->canPark()); 2657 calibrationOptions.parkDomeC->setEnabled(devices()->dome() && devices()->dome()->canPark()); 2658 2659 calibrationOptions.parkMountC->setChecked(false); 2660 calibrationOptions.parkDomeC->setChecked(false); 2661 calibrationOptions.gotoWallC->setChecked(false); 2662 2663 calibrationOptions.parkMountC->setChecked(state()->calibrationPreAction() & ACTION_PARK_MOUNT); 2664 calibrationOptions.parkDomeC->setChecked(state()->calibrationPreAction() & ACTION_PARK_DOME); 2665 if (state()->calibrationPreAction() & ACTION_WALL) 2666 { 2667 calibrationOptions.gotoWallC->setChecked(true); 2668 calibrationOptions.azBox->setText(state()->wallCoord().az().toDMSString()); 2669 calibrationOptions.altBox->setText(state()->wallCoord().alt().toDMSString()); 2670 } 2671 2672 switch (state()->flatFieldDuration()) 2673 { 2674 case DURATION_MANUAL: 2675 calibrationOptions.manualDurationC->setChecked(true); 2676 break; 2677 2678 case DURATION_ADU: 2679 calibrationOptions.ADUC->setChecked(true); 2680 calibrationOptions.ADUValue->setValue(static_cast<int>(std::round(state()->targetADU()))); 2681 calibrationOptions.ADUTolerance->setValue(static_cast<int>(std::round(state()->targetADUTolerance()))); 2682 break; 2683 } 2684 2685 // avoid combination of ACTION_WALL and ACTION_PARK_MOUNT 2686 connect(calibrationOptions.gotoWallC, &QCheckBox::clicked, [&](bool checked) 2687 { 2688 if (checked) 2689 calibrationOptions.parkMountC->setChecked(false); 2690 }); 2691 connect(calibrationOptions.parkMountC, &QCheckBox::clicked, [&](bool checked) 2692 { 2693 if (checked) 2694 calibrationOptions.gotoWallC->setChecked(false); 2695 }); 2696 2697 if (calibrationDialog.exec() == QDialog::Accepted) 2698 { 2699 state()->setCalibrationPreAction(ACTION_NONE); 2700 if (calibrationOptions.parkMountC->isChecked()) 2701 state()->setCalibrationPreAction(state()->calibrationPreAction() | ACTION_PARK_MOUNT); 2702 if (calibrationOptions.parkDomeC->isChecked()) 2703 state()->setCalibrationPreAction(state()->calibrationPreAction() | ACTION_PARK_DOME); 2704 if (calibrationOptions.gotoWallC->isChecked()) 2705 { 2706 dms wallAz, wallAlt; 2707 bool azOk = false, altOk = false; 2708 2709 wallAz = calibrationOptions.azBox->createDms(&azOk); 2710 wallAlt = calibrationOptions.altBox->createDms(&altOk); 2711 2712 if (azOk && altOk) 2713 { 2714 state()->setCalibrationPreAction((state()->calibrationPreAction() & ~ACTION_PARK_MOUNT) | ACTION_WALL); 2715 state()->wallCoord().setAz(wallAz); 2716 state()->wallCoord().setAlt(wallAlt); 2717 } 2718 else 2719 { 2720 calibrationOptions.gotoWallC->setChecked(false); 2721 KSNotification::error(i18n("Wall coordinates are invalid.")); 2722 } 2723 } 2724 2725 if (calibrationOptions.manualDurationC->isChecked()) 2726 state()->setFlatFieldDuration(DURATION_MANUAL); 2727 else 2728 { 2729 state()->setFlatFieldDuration(DURATION_ADU); 2730 state()->setTargetADU(calibrationOptions.ADUValue->value()); 2731 state()->setTargetADUTolerance(calibrationOptions.ADUTolerance->value()); 2732 } 2733 2734 state()->setDirty(true); 2735 2736 if (!m_standAlone) 2737 { 2738 Options::setCalibrationPreActionIndex(state()->calibrationPreAction()); 2739 Options::setCalibrationFlatDurationIndex(state()->flatFieldDuration()); 2740 Options::setCalibrationWallAz(state()->wallCoord().az().Degrees()); 2741 Options::setCalibrationWallAlt(state()->wallCoord().alt().Degrees()); 2742 Options::setCalibrationADUValue(static_cast<uint>(std::round(state()->targetADU()))); 2743 Options::setCalibrationADUValueTolerance(static_cast<uint>(std::round(state()->targetADUTolerance()))); 2744 } 2745 } 2746 } 2747 2748 bool Capture::setVideoLimits(uint16_t maxBufferSize, uint16_t maxPreviewFPS) 2749 { 2750 if (devices()->getActiveCamera() == nullptr) 2751 return false; 2752 2753 return devices()->getActiveCamera()->setStreamLimits(maxBufferSize, maxPreviewFPS); 2754 } 2755 2756 void Capture::setVideoStreamEnabled(bool enabled) 2757 { 2758 if (enabled) 2759 { 2760 liveVideoB->setChecked(true); 2761 liveVideoB->setIcon(QIcon::fromTheme("camera-on")); 2762 } 2763 else 2764 { 2765 liveVideoB->setChecked(false); 2766 liveVideoB->setIcon(QIcon::fromTheme("camera-ready")); 2767 } 2768 } 2769 2770 void Capture::setMountStatus(ISD::Mount::Status newState) 2771 { 2772 switch (newState) 2773 { 2774 case ISD::Mount::MOUNT_PARKING: 2775 case ISD::Mount::MOUNT_SLEWING: 2776 case ISD::Mount::MOUNT_MOVING: 2777 previewB->setEnabled(false); 2778 liveVideoB->setEnabled(false); 2779 // Only disable when button is "Start", and not "Stopped" 2780 // If mount is in motion, Stopped button should always be enabled to terminate 2781 // the sequence 2782 if (state()->isBusy() == false) 2783 startB->setEnabled(false); 2784 break; 2785 2786 default: 2787 if (state()->isBusy() == false) 2788 { 2789 previewB->setEnabled(true); 2790 if (devices()->getActiveCamera()) 2791 liveVideoB->setEnabled(devices()->getActiveCamera()->hasVideoStream()); 2792 startB->setEnabled(true); 2793 } 2794 2795 break; 2796 } 2797 } 2798 2799 void Capture::showObserverDialog() 2800 { 2801 QList<OAL::Observer *> m_observerList; 2802 KStars::Instance()->data()->userdb()->GetAllObservers(m_observerList); 2803 QStringList observers; 2804 for (auto &o : m_observerList) 2805 observers << QString("%1 %2").arg(o->name(), o->surname()); 2806 2807 QDialog observersDialog(this); 2808 observersDialog.setWindowTitle(i18nc("@title:window", "Select Current Observer")); 2809 2810 QLabel label(i18n("Current Observer:")); 2811 2812 QComboBox observerCombo(&observersDialog); 2813 observerCombo.addItems(observers); 2814 observerCombo.setCurrentText(getObserverName()); 2815 observerCombo.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); 2816 2817 QPushButton manageObserver(&observersDialog); 2818 manageObserver.setFixedSize(QSize(32, 32)); 2819 manageObserver.setIcon(QIcon::fromTheme("document-edit")); 2820 manageObserver.setAttribute(Qt::WA_LayoutUsesWidgetRect); 2821 manageObserver.setToolTip(i18n("Manage Observers")); 2822 connect(&manageObserver, &QPushButton::clicked, this, [&]() 2823 { 2824 ObserverAdd add; 2825 add.exec(); 2826 2827 QList<OAL::Observer *> m_observerList; 2828 KStars::Instance()->data()->userdb()->GetAllObservers(m_observerList); 2829 QStringList observers; 2830 for (auto &o : m_observerList) 2831 observers << QString("%1 %2").arg(o->name(), o->surname()); 2832 2833 observerCombo.clear(); 2834 observerCombo.addItems(observers); 2835 observerCombo.setCurrentText(getObserverName()); 2836 2837 }); 2838 2839 QHBoxLayout * layout = new QHBoxLayout; 2840 layout->addWidget(&label); 2841 layout->addWidget(&observerCombo); 2842 layout->addWidget(&manageObserver); 2843 2844 observersDialog.setLayout(layout); 2845 2846 observersDialog.exec(); 2847 setObserverName(observerCombo.currentText()); 2848 } 2849 2850 void Capture::setAlignResults(double solverPA, double ra, double de, double pixscale) 2851 { 2852 Q_UNUSED(ra) 2853 Q_UNUSED(de) 2854 Q_UNUSED(pixscale) 2855 if (devices()->rotator() && m_RotatorControlPanel) 2856 m_RotatorControlPanel->refresh(solverPA); 2857 } 2858 2859 void Capture::setFilterStatus(FilterState filterState) 2860 { 2861 if (filterState != state()->getFilterManagerState()) 2862 qCDebug(KSTARS_EKOS_CAPTURE) << "Filter state changed from" << Ekos::getFilterStatusString( 2863 state()->getFilterManagerState()) << "to" << Ekos::getFilterStatusString(filterState); 2864 if (state()->getCaptureState() == CAPTURE_CHANGING_FILTER) 2865 { 2866 switch (filterState) 2867 { 2868 case FILTER_OFFSET: 2869 appendLogText(i18n("Changing focus offset by %1 steps...", 2870 m_FilterManager->getTargetFilterOffset())); 2871 break; 2872 2873 case FILTER_CHANGE: 2874 appendLogText(i18n("Changing filter to %1...", 2875 FilterPosCombo->itemText(m_FilterManager->getTargetFilterPosition() - 1))); 2876 break; 2877 2878 case FILTER_AUTOFOCUS: 2879 appendLogText(i18n("Auto focus on filter change...")); 2880 clearAutoFocusHFR(); 2881 break; 2882 2883 case FILTER_IDLE: 2884 if (state()->getFilterManagerState() == FILTER_CHANGE) 2885 { 2886 appendLogText(i18n("Filter set to %1.", 2887 FilterPosCombo->itemText(m_FilterManager->getTargetFilterPosition() - 1))); 2888 } 2889 break; 2890 2891 default: 2892 break; 2893 } 2894 } 2895 state()->setFilterManagerState(filterState); 2896 } 2897 2898 void Capture::setupFilterManager() 2899 { 2900 // Do we have an existing filter manager? 2901 if (m_FilterManager) 2902 m_FilterManager->disconnect(this); 2903 2904 // Create new or refresh device 2905 Manager::Instance()->createFilterManager(devices()->filterWheel()); 2906 2907 // Return global filter manager for this filter wheel. 2908 Manager::Instance()->getFilterManager(devices()->filterWheel()->getDeviceName(), m_FilterManager); 2909 2910 devices()->setFilterManager(m_FilterManager); 2911 2912 connect(m_FilterManager.get(), &FilterManager::updated, this, [this]() 2913 { 2914 emit filterManagerUpdated(devices()->filterWheel()); 2915 }); 2916 2917 // display capture status changes 2918 connect(m_FilterManager.get(), &FilterManager::newStatus, this, &Capture::newFilterStatus); 2919 2920 connect(filterManagerB, &QPushButton::clicked, this, [this]() 2921 { 2922 m_FilterManager->refreshFilterModel(); 2923 m_FilterManager->show(); 2924 m_FilterManager->raise(); 2925 }); 2926 2927 connect(m_FilterManager.get(), &FilterManager::ready, this, &Capture::updateCurrentFilterPosition); 2928 2929 connect(m_FilterManager.get(), &FilterManager::failed, this, [this]() 2930 { 2931 if (activeJob()) 2932 { 2933 appendLogText(i18n("Filter operation failed.")); 2934 abort(); 2935 } 2936 }); 2937 2938 // filter changes 2939 connect(m_FilterManager.get(), &FilterManager::newStatus, this, &Capture::setFilterStatus); 2940 2941 // display capture status changes 2942 connect(m_FilterManager.get(), &FilterManager::newStatus, captureStatusWidget, &LedStatusWidget::setFilterState); 2943 2944 connect(m_FilterManager.get(), &FilterManager::labelsChanged, this, [this]() 2945 { 2946 FilterPosCombo->clear(); 2947 FilterPosCombo->addItems(m_FilterManager->getFilterLabels()); 2948 FilterPosCombo->setCurrentIndex(m_FilterManager->getFilterPosition() - 1); 2949 updateCurrentFilterPosition(); 2950 }); 2951 2952 connect(m_FilterManager.get(), &FilterManager::positionChanged, this, [this]() 2953 { 2954 FilterPosCombo->setCurrentIndex(m_FilterManager->getFilterPosition() - 1); 2955 updateCurrentFilterPosition(); 2956 }); 2957 } 2958 2959 void Capture::addDSLRInfo(const QString &model, uint32_t maxW, uint32_t maxH, double pixelW, double pixelH) 2960 { 2961 // Check if model already exists 2962 auto pos = std::find_if(state()->DSLRInfos().begin(), state()->DSLRInfos().end(), [model](const auto & oneDSLRInfo) 2963 { 2964 return (oneDSLRInfo["Model"] == model); 2965 }); 2966 2967 if (pos != state()->DSLRInfos().end()) 2968 { 2969 KStarsData::Instance()->userdb()->DeleteDSLRInfo(model); 2970 state()->DSLRInfos().removeOne(*pos); 2971 } 2972 2973 QMap<QString, QVariant> oneDSLRInfo; 2974 oneDSLRInfo["Model"] = model; 2975 oneDSLRInfo["Width"] = maxW; 2976 oneDSLRInfo["Height"] = maxH; 2977 oneDSLRInfo["PixelW"] = pixelW; 2978 oneDSLRInfo["PixelH"] = pixelH; 2979 2980 KStarsData::Instance()->userdb()->AddDSLRInfo(oneDSLRInfo); 2981 KStarsData::Instance()->userdb()->GetAllDSLRInfos(state()->DSLRInfos()); 2982 2983 updateFrameProperties(); 2984 process()->resetFrame(); 2985 process()->syncDSLRToTargetChip(model); 2986 2987 // In case the dialog was opened, let's close it 2988 if (dslrInfoDialog) 2989 dslrInfoDialog.reset(); 2990 } 2991 2992 void Capture::cullToDSLRLimits() 2993 { 2994 QString model(devices()->getActiveCamera()->getDeviceName()); 2995 2996 // Check if model already exists 2997 auto pos = std::find_if(state()->DSLRInfos().begin(), 2998 state()->DSLRInfos().end(), [model](QMap<QString, QVariant> &oneDSLRInfo) 2999 { 3000 return (oneDSLRInfo["Model"] == model); 3001 }); 3002 3003 if (pos != state()->DSLRInfos().end()) 3004 { 3005 if (captureFrameWN->maximum() == 0 || captureFrameWN->maximum() > (*pos)["Width"].toInt()) 3006 { 3007 captureFrameWN->setValue((*pos)["Width"].toInt()); 3008 captureFrameWN->setMaximum((*pos)["Width"].toInt()); 3009 } 3010 3011 if (captureFrameHN->maximum() == 0 || captureFrameHN->maximum() > (*pos)["Height"].toInt()) 3012 { 3013 captureFrameHN->setValue((*pos)["Height"].toInt()); 3014 captureFrameHN->setMaximum((*pos)["Height"].toInt()); 3015 } 3016 } 3017 } 3018 3019 void Capture::setPresetSettings(const QJsonObject &settings) 3020 { 3021 auto opticalTrain = settings["optical_train"].toString(opticalTrainCombo->currentText()); 3022 auto targetFilter = settings["filter"].toString(FilterPosCombo->currentText()); 3023 3024 opticalTrainCombo->setCurrentText(opticalTrain); 3025 FilterPosCombo->setCurrentText(targetFilter); 3026 3027 captureExposureN->setValue(settings["exp"].toDouble(1)); 3028 3029 int bin = settings["bin"].toInt(1); 3030 setBinning(bin, bin); 3031 3032 if (settings["temperature"].isString() && settings["temperature"].toString() == "--") 3033 setForceTemperature(false); 3034 else 3035 { 3036 double temperature = settings["temperature"].toDouble(INVALID_VALUE); 3037 if (temperature > INVALID_VALUE && devices()->getActiveCamera() 3038 && devices()->getActiveCamera()->hasCoolerControl()) 3039 { 3040 setForceTemperature(true); 3041 setTargetTemperature(temperature); 3042 } 3043 else 3044 setForceTemperature(false); 3045 } 3046 3047 if (settings["gain"].isString() && settings["gain"].toString() == "--") 3048 captureGainN->setValue(GainSpinSpecialValue); 3049 else 3050 { 3051 double gain = settings["gain"].toDouble(GainSpinSpecialValue); 3052 if (devices()->getActiveCamera() && devices()->getActiveCamera()->hasGain()) 3053 { 3054 if (gain == GainSpinSpecialValue) 3055 captureGainN->setValue(GainSpinSpecialValue); 3056 else 3057 setGain(gain); 3058 } 3059 } 3060 3061 if (settings["offset"].isString() && settings["offset"].toString() == "--") 3062 captureOffsetN->setValue(OffsetSpinSpecialValue); 3063 else 3064 { 3065 double offset = settings["offset"].toDouble(OffsetSpinSpecialValue); 3066 if (devices()->getActiveCamera() && devices()->getActiveCamera()->hasOffset()) 3067 { 3068 if (offset == OffsetSpinSpecialValue) 3069 captureOffsetN->setValue(OffsetSpinSpecialValue); 3070 else 3071 setOffset(offset); 3072 } 3073 } 3074 3075 int transferFormat = settings["transferFormat"].toInt(-1); 3076 if (transferFormat >= 0) 3077 { 3078 captureEncodingS->setCurrentIndex(transferFormat); 3079 } 3080 3081 QString captureFormat = settings["captureFormat"].toString(captureFormatS->currentText()); 3082 if (captureFormat != captureFormatS->currentText()) 3083 captureFormatS->setCurrentText(captureFormat); 3084 3085 captureTypeS->setCurrentIndex(qMax(0, settings["frameType"].toInt(0))); 3086 3087 // ISO 3088 int isoIndex = settings["iso"].toInt(-1); 3089 if (isoIndex >= 0) 3090 setISO(isoIndex); 3091 3092 bool dark = settings["dark"].toBool(darkB->isChecked()); 3093 if (dark != darkB->isChecked()) 3094 darkB->setChecked(dark); 3095 3096 int ditherPerJobFrequency = settings["ditherPerJobFrequency"].toInt(0); 3097 m_LimitsUI->limitDitherFrequencyN->setValue(ditherPerJobFrequency); 3098 } 3099 3100 void Capture::setFileSettings(const QJsonObject &settings) 3101 { 3102 const auto prefix = settings["prefix"].toString(targetNameT->text()); 3103 const auto directory = settings["directory"].toString(fileDirectoryT->text()); 3104 const auto upload = settings["upload"].toInt(fileUploadModeS->currentIndex()); 3105 const auto remote = settings["remote"].toString(fileRemoteDirT->text()); 3106 const auto format = settings["format"].toString(placeholderFormatT->text()); 3107 const auto suffix = settings["suffix"].toInt(formatSuffixN->value()); 3108 3109 targetNameT->setText(prefix); 3110 fileDirectoryT->setText(directory); 3111 fileUploadModeS->setCurrentIndex(upload); 3112 fileRemoteDirT->setText(remote); 3113 placeholderFormatT->setText(format); 3114 formatSuffixN->setValue(suffix); 3115 } 3116 3117 QJsonObject Capture::getFileSettings() 3118 { 3119 QJsonObject settings = 3120 { 3121 {"prefix", targetNameT->text()}, 3122 {"directory", fileDirectoryT->text()}, 3123 {"format", placeholderFormatT->text()}, 3124 {"suffix", formatSuffixN->value()}, 3125 {"upload", fileUploadModeS->currentIndex()}, 3126 {"remote", fileRemoteDirT->text()} 3127 }; 3128 3129 return settings; 3130 } 3131 3132 void Capture::setLimitSettings(const QJsonObject &settings) 3133 { 3134 const bool deviationCheck = settings["deviationCheck"].toBool(Options::enforceGuideDeviation()); 3135 const double deviationValue = settings["deviationValue"].toDouble(Options::guideDeviation()); 3136 const bool focusHFRCheck = settings["focusHFRCheck"].toBool(m_LimitsUI->limitFocusHFRS->isChecked()); 3137 const double focusHFRThresholdPercentage = settings["hFRThresholdPercentage"].toDouble( 3138 m_LimitsUI->limitFocusHFRThresholdPercentage->value()); 3139 const double focusHFRValue = settings["focusHFRValue"].toDouble(m_LimitsUI->limitFocusHFRN->value()); 3140 const int focusHFRCheckFrames = settings["inSequenceCheckFrames"].toInt(m_LimitsUI->limitFocusHFRCheckFrames->value()); 3141 const int focusHFRAlgorithm = settings["hFRCheckAlgorithm"].toInt(m_LimitsUI->limitFocusHFRAlgorithm->currentIndex()); 3142 const bool focusDeltaTCheck = settings["focusDeltaTCheck"].toBool(m_LimitsUI->limitFocusDeltaTS->isChecked()); 3143 const double focusDeltaTValue = settings["focusDeltaTValue"].toDouble(m_LimitsUI->limitFocusDeltaTN->value()); 3144 const bool refocusNCheck = settings["refocusNCheck"].toBool(m_LimitsUI->limitRefocusS->isChecked()); 3145 const int refocusNValue = settings["refocusNValue"].toInt(m_LimitsUI->limitRefocusN->value()); 3146 const int ditherPerJobFrequency = settings["ditherPerJobFrequency"].toInt(m_LimitsUI->limitDitherFrequencyN->value()); 3147 3148 if (deviationCheck) 3149 { 3150 m_LimitsUI->limitGuideDeviationS->setChecked(true); 3151 m_LimitsUI->limitGuideDeviationN->setValue(deviationValue); 3152 } 3153 else 3154 m_LimitsUI->limitGuideDeviationS->setChecked(false); 3155 3156 if (focusHFRCheck) 3157 { 3158 m_LimitsUI->limitFocusHFRS->setChecked(true); 3159 m_LimitsUI->limitFocusHFRThresholdPercentage->setValue(focusHFRThresholdPercentage); 3160 m_LimitsUI->limitFocusHFRN->setValue(focusHFRValue); 3161 m_LimitsUI->limitFocusHFRCheckFrames->setValue(focusHFRCheckFrames); 3162 m_LimitsUI->limitFocusHFRAlgorithm->setCurrentIndex(focusHFRAlgorithm); 3163 } 3164 else 3165 m_LimitsUI->limitFocusHFRS->setChecked(false); 3166 3167 if (focusDeltaTCheck) 3168 { 3169 m_LimitsUI->limitFocusDeltaTS->setChecked(true); 3170 m_LimitsUI->limitFocusDeltaTN->setValue(focusDeltaTValue); 3171 } 3172 else 3173 m_LimitsUI->limitFocusDeltaTS->setChecked(false); 3174 3175 if (refocusNCheck) 3176 { 3177 m_LimitsUI->limitRefocusS->setChecked(true); 3178 m_LimitsUI->limitRefocusN->setValue(refocusNValue); 3179 } 3180 else 3181 m_LimitsUI->limitRefocusS->setChecked(false); 3182 3183 m_LimitsUI->limitDitherFrequencyN->setValue(ditherPerJobFrequency); 3184 3185 syncRefocusOptionsFromGUI(); 3186 } 3187 3188 QJsonObject Capture::getLimitSettings() 3189 { 3190 QJsonObject settings = 3191 { 3192 {"deviationCheck", Options::enforceGuideDeviation()}, 3193 {"deviationValue", Options::guideDeviation()}, 3194 {"ditherPerJobFrequency", m_LimitsUI->limitDitherFrequencyN->value()}, 3195 {"focusHFRCheck", m_LimitsUI->limitFocusHFRS->isChecked()}, 3196 {"hFRThresholdPercentage", m_LimitsUI->limitFocusHFRThresholdPercentage->value()}, 3197 {"focusHFRValue", m_LimitsUI->limitFocusHFRN->value()}, 3198 {"inSequenceCheckFrames", m_LimitsUI->limitFocusHFRCheckFrames->value()}, 3199 {"hFRCheckAlgorithm", m_LimitsUI->limitFocusHFRAlgorithm->currentIndex()}, 3200 {"focusDeltaTCheck", m_LimitsUI->limitFocusDeltaTS->isChecked()}, 3201 {"focusDeltaTValue", m_LimitsUI->limitFocusDeltaTN->value()}, 3202 {"refocusNCheck", m_LimitsUI->limitRefocusS->isChecked()}, 3203 {"refocusNValue", m_LimitsUI->limitRefocusN->value()}, 3204 }; 3205 3206 return settings; 3207 } 3208 3209 void Capture::clearCameraConfiguration() 3210 { 3211 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this]() 3212 { 3213 //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr); 3214 KSMessageBox::Instance()->disconnect(this); 3215 devices()->getActiveCamera()->setConfig(PURGE_CONFIG); 3216 KStarsData::Instance()->userdb()->DeleteDSLRInfo(devices()->getActiveCamera()->getDeviceName()); 3217 3218 QStringList shutterfulCCDs = Options::shutterfulCCDs(); 3219 QStringList shutterlessCCDs = Options::shutterlessCCDs(); 3220 3221 // Remove camera from shutterful and shutterless CCDs 3222 if (shutterfulCCDs.contains(devices()->getActiveCamera()->getDeviceName())) 3223 { 3224 shutterfulCCDs.removeOne(devices()->getActiveCamera()->getDeviceName()); 3225 Options::setShutterfulCCDs(shutterfulCCDs); 3226 } 3227 if (shutterlessCCDs.contains(devices()->getActiveCamera()->getDeviceName())) 3228 { 3229 shutterlessCCDs.removeOne(devices()->getActiveCamera()->getDeviceName()); 3230 Options::setShutterlessCCDs(shutterlessCCDs); 3231 } 3232 3233 // For DSLRs, immediately ask them to enter the values again. 3234 if (captureISOS && captureISOS->count() > 0) 3235 { 3236 createDSLRDialog(); 3237 } 3238 }); 3239 3240 KSMessageBox::Instance()->questionYesNo( i18n("Reset %1 configuration to default?", 3241 devices()->getActiveCamera()->getDeviceName()), 3242 i18n("Confirmation"), 30); 3243 } 3244 3245 void Capture::updateJobTable(SequenceJob *job, bool full) 3246 { 3247 if (job == nullptr) 3248 { 3249 QListIterator<SequenceJob *> iter(state()->allJobs()); 3250 while (iter.hasNext()) 3251 updateJobTable(iter.next(), full); 3252 } 3253 else 3254 { 3255 // find the job's row 3256 int row = state()->allJobs().indexOf(job); 3257 if (row >= 0 && row < queueTable->rowCount()) 3258 { 3259 updateRowStyle(job); 3260 QTableWidgetItem *status = queueTable->item(row, JOBTABLE_COL_STATUS); 3261 QTableWidgetItem *count = queueTable->item(row, JOBTABLE_COL_COUNTS); 3262 status->setText(job->getStatusString()); 3263 updateJobTableCountCell(job, count); 3264 3265 if (full) 3266 { 3267 bool isDarkFlat = job->jobType() == SequenceJob::JOBTYPE_DARKFLAT; 3268 3269 QTableWidgetItem *filter = queueTable->item(row, JOBTABLE_COL_FILTER); 3270 if (FilterPosCombo->findText(job->getCoreProperty(SequenceJob::SJ_Filter).toString()) >= 0 && 3271 (captureTypeS->currentIndex() == FRAME_LIGHT || captureTypeS->currentIndex() == FRAME_FLAT || isDarkFlat) ) 3272 filter->setText(job->getCoreProperty(SequenceJob::SJ_Filter).toString()); 3273 else 3274 filter->setText("--"); 3275 3276 QTableWidgetItem *exp = queueTable->item(row, JOBTABLE_COL_EXP); 3277 exp->setText(QString("%L1").arg(job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(), 0, 'f', 3278 captureExposureN->decimals())); 3279 3280 QTableWidgetItem *type = queueTable->item(row, JOBTABLE_COL_TYPE); 3281 type->setText(isDarkFlat ? i18n("Dark Flat") : CCDFrameTypeNames[job->getFrameType()]); 3282 3283 QTableWidgetItem *bin = queueTable->item(row, JOBTABLE_COL_BINNING); 3284 QPoint binning = job->getCoreProperty(SequenceJob::SJ_Binning).toPoint(); 3285 bin->setText(QString("%1x%2").arg(binning.x()).arg(binning.y())); 3286 3287 QTableWidgetItem *iso = queueTable->item(row, JOBTABLE_COL_ISO); 3288 if (job->getCoreProperty(SequenceJob::SJ_ISOIndex).toInt() != -1) 3289 iso->setText(captureISOS->itemText(job->getCoreProperty(SequenceJob::SJ_ISOIndex).toInt())); 3290 else if (job->getCoreProperty(SequenceJob::SJ_Gain).toDouble() >= 0) 3291 iso->setText(QString::number(job->getCoreProperty(SequenceJob::SJ_Gain).toDouble(), 'f', 1)); 3292 else 3293 iso->setText("--"); 3294 3295 QTableWidgetItem *offset = queueTable->item(row, JOBTABLE_COL_OFFSET); 3296 if (job->getCoreProperty(SequenceJob::SJ_Offset).toDouble() >= 0) 3297 offset->setText(QString::number(job->getCoreProperty(SequenceJob::SJ_Offset).toDouble(), 'f', 1)); 3298 else 3299 offset->setText("--"); 3300 } 3301 3302 // update button enablement 3303 if (queueTable->rowCount() > 0) 3304 { 3305 queueSaveAsB->setEnabled(true); 3306 queueSaveB->setEnabled(true); 3307 resetB->setEnabled(true); 3308 state()->setDirty(true); 3309 } 3310 3311 if (queueTable->rowCount() > 1) 3312 { 3313 queueUpB->setEnabled(true); 3314 queueDownB->setEnabled(true); 3315 } 3316 } 3317 } 3318 } 3319 3320 void Capture::updateRowStyle(SequenceJob *job) 3321 { 3322 if (job == nullptr) 3323 return; 3324 3325 // find the job's row 3326 int row = state()->allJobs().indexOf(job); 3327 if (row >= 0 && row < queueTable->rowCount()) 3328 { 3329 updateCellStyle(queueTable->item(row, JOBTABLE_COL_STATUS), job->getStatus() == JOB_BUSY); 3330 updateCellStyle(queueTable->item(row, JOBTABLE_COL_FILTER), job->getStatus() == JOB_BUSY); 3331 updateCellStyle(queueTable->item(row, JOBTABLE_COL_COUNTS), job->getStatus() == JOB_BUSY); 3332 updateCellStyle(queueTable->item(row, JOBTABLE_COL_EXP), job->getStatus() == JOB_BUSY); 3333 updateCellStyle(queueTable->item(row, JOBTABLE_COL_TYPE), job->getStatus() == JOB_BUSY); 3334 updateCellStyle(queueTable->item(row, JOBTABLE_COL_BINNING), job->getStatus() == JOB_BUSY); 3335 updateCellStyle(queueTable->item(row, JOBTABLE_COL_ISO), job->getStatus() == JOB_BUSY); 3336 updateCellStyle(queueTable->item(row, JOBTABLE_COL_OFFSET), job->getStatus() == JOB_BUSY); 3337 } 3338 } 3339 3340 void Capture::updateCellStyle(QTableWidgetItem *cell, bool active) 3341 { 3342 if (cell == nullptr) 3343 return; 3344 3345 QFont font(cell->font()); 3346 font.setBold(active); 3347 font.setItalic(active); 3348 cell->setFont(font); 3349 } 3350 3351 void Capture::updateJobTableCountCell(SequenceJob *job, QTableWidgetItem *countCell) 3352 { 3353 countCell->setText(QString("%L1/%L2").arg(job->getCompleted()).arg(job->getCoreProperty(SequenceJob::SJ_Count).toInt())); 3354 } 3355 3356 bool Capture::checkUploadPaths(FilenamePreviewType filenamePreview) 3357 { 3358 // only relevant if we do not generate file name previews 3359 if (filenamePreview != NOT_PREVIEW) 3360 return true; 3361 3362 if (fileUploadModeS->currentIndex() != ISD::Camera::UPLOAD_CLIENT && fileRemoteDirT->text().isEmpty()) 3363 { 3364 KSNotification::error(i18n("You must set remote directory for Local & Both modes.")); 3365 return false; 3366 } 3367 3368 if (fileUploadModeS->currentIndex() != ISD::Camera::UPLOAD_LOCAL && fileDirectoryT->text().isEmpty()) 3369 { 3370 KSNotification::error(i18n("You must set local directory for Client & Both modes.")); 3371 return false; 3372 } 3373 // everything OK 3374 return true; 3375 } 3376 3377 QJsonObject Capture::createJsonJob(SequenceJob *job, int currentRow) 3378 { 3379 if (job == nullptr) 3380 return QJsonObject(); 3381 3382 QJsonObject jsonJob = {{"Status", "Idle"}}; 3383 bool isDarkFlat = job->jobType() == SequenceJob::JOBTYPE_DARKFLAT; 3384 jsonJob.insert("Filter", FilterPosCombo->currentText()); 3385 jsonJob.insert("Count", queueTable->item(currentRow, JOBTABLE_COL_COUNTS)->text()); 3386 jsonJob.insert("Exp", queueTable->item(currentRow, JOBTABLE_COL_EXP)->text()); 3387 jsonJob.insert("Type", isDarkFlat ? i18n("Dark Flat") : queueTable->item(currentRow, JOBTABLE_COL_TYPE)->text()); 3388 jsonJob.insert("Bin", queueTable->item(currentRow, JOBTABLE_COL_BINNING)->text()); 3389 jsonJob.insert("ISO/Gain", queueTable->item(currentRow, JOBTABLE_COL_ISO)->text()); 3390 jsonJob.insert("Offset", queueTable->item(currentRow, JOBTABLE_COL_OFFSET)->text()); 3391 3392 return jsonJob; 3393 } 3394 3395 void Capture::setCoolerToggled(bool enabled) 3396 { 3397 auto isToggled = (!enabled && coolerOnB->isChecked()) || (enabled && coolerOffB->isChecked()); 3398 3399 coolerOnB->blockSignals(true); 3400 coolerOnB->setChecked(enabled); 3401 coolerOnB->blockSignals(false); 3402 3403 coolerOffB->blockSignals(true); 3404 coolerOffB->setChecked(!enabled); 3405 coolerOffB->blockSignals(false); 3406 3407 if (isToggled) 3408 appendLogText(enabled ? i18n("Cooler is on") : i18n("Cooler is off")); 3409 } 3410 3411 void Capture::createDSLRDialog() 3412 { 3413 dslrInfoDialog.reset(new DSLRInfo(this, devices()->getActiveCamera())); 3414 3415 connect(dslrInfoDialog.get(), &DSLRInfo::infoChanged, this, [this]() 3416 { 3417 if (devices()->getActiveCamera()) 3418 addDSLRInfo(QString(devices()->getActiveCamera()->getDeviceName()), 3419 dslrInfoDialog->sensorMaxWidth, 3420 dslrInfoDialog->sensorMaxHeight, 3421 dslrInfoDialog->sensorPixelW, 3422 dslrInfoDialog->sensorPixelH); 3423 }); 3424 3425 dslrInfoDialog->show(); 3426 3427 emit dslrInfoRequested(devices()->getActiveCamera()->getDeviceName()); 3428 } 3429 3430 void Capture::setStandAloneGain(double value) 3431 { 3432 QMap<QString, QMap<QString, QVariant> > propertyMap = customPropertiesDialog->getCustomProperties(); 3433 3434 if (m_standAloneUseCcdGain) 3435 { 3436 if (value >= 0) 3437 { 3438 QMap<QString, QVariant> ccdGain; 3439 ccdGain["GAIN"] = value; 3440 propertyMap["CCD_GAIN"] = ccdGain; 3441 } 3442 else 3443 { 3444 propertyMap["CCD_GAIN"].remove("GAIN"); 3445 if (propertyMap["CCD_GAIN"].size() == 0) 3446 propertyMap.remove("CCD_GAIN"); 3447 } 3448 } 3449 else 3450 { 3451 if (value >= 0) 3452 { 3453 QMap<QString, QVariant> ccdGain = propertyMap["CCD_CONTROLS"]; 3454 ccdGain["Gain"] = value; 3455 propertyMap["CCD_CONTROLS"] = ccdGain; 3456 } 3457 else 3458 { 3459 propertyMap["CCD_CONTROLS"].remove("Gain"); 3460 if (propertyMap["CCD_CONTROLS"].size() == 0) 3461 propertyMap.remove("CCD_CONTROLS"); 3462 } 3463 } 3464 3465 customPropertiesDialog->setCustomProperties(propertyMap); 3466 } 3467 3468 void Capture::setStandAloneOffset(double value) 3469 { 3470 QMap<QString, QMap<QString, QVariant> > propertyMap = customPropertiesDialog->getCustomProperties(); 3471 3472 if (m_standAloneUseCcdOffset) 3473 { 3474 if (value >= 0) 3475 { 3476 QMap<QString, QVariant> ccdOffset; 3477 ccdOffset["OFFSET"] = value; 3478 propertyMap["CCD_OFFSET"] = ccdOffset; 3479 } 3480 else 3481 { 3482 propertyMap["CCD_OFFSET"].remove("OFFSET"); 3483 if (propertyMap["CCD_OFFSET"].size() == 0) 3484 propertyMap.remove("CCD_OFFSET"); 3485 } 3486 } 3487 else 3488 { 3489 if (value >= 0) 3490 { 3491 QMap<QString, QVariant> ccdOffset = propertyMap["CCD_CONTROLS"]; 3492 ccdOffset["Offset"] = value; 3493 propertyMap["CCD_CONTROLS"] = ccdOffset; 3494 } 3495 else 3496 { 3497 propertyMap["CCD_CONTROLS"].remove("Offset"); 3498 if (propertyMap["CCD_CONTROLS"].size() == 0) 3499 propertyMap.remove("CCD_CONTROLS"); 3500 } 3501 } 3502 3503 customPropertiesDialog->setCustomProperties(propertyMap); 3504 } 3505 void Capture::setGain(double value) 3506 { 3507 if (m_standAlone) 3508 { 3509 setStandAloneGain(value); 3510 return; 3511 } 3512 if (!devices()->getActiveCamera()) 3513 return; 3514 3515 QMap<QString, QMap<QString, QVariant> > customProps = customPropertiesDialog->getCustomProperties(); 3516 process()->updateGain(value, customProps); 3517 customPropertiesDialog->setCustomProperties(customProps); 3518 } 3519 3520 void Capture::setOffset(double value) 3521 { 3522 if (m_standAlone) 3523 { 3524 setStandAloneOffset(value); 3525 return; 3526 } 3527 if (!devices()->getActiveCamera()) 3528 return; 3529 3530 QMap<QString, QMap<QString, QVariant> > customProps = customPropertiesDialog->getCustomProperties(); 3531 3532 process()->updateOffset(value, customProps); 3533 customPropertiesDialog->setCustomProperties(customProps); 3534 } 3535 3536 void Capture::editFilterName() 3537 { 3538 if (m_standAlone) 3539 { 3540 QStringList labels; 3541 for (int index = 0; index < FilterPosCombo->count(); index++) 3542 labels << FilterPosCombo->itemText(index); 3543 QStringList newLabels; 3544 if (editFilterNameInternal(labels, newLabels)) 3545 { 3546 FilterPosCombo->clear(); 3547 FilterPosCombo->addItems(newLabels); 3548 } 3549 } 3550 else 3551 { 3552 if (devices()->filterWheel() == nullptr || state()->getCurrentFilterPosition() < 1) 3553 return; 3554 3555 QStringList labels = m_FilterManager->getFilterLabels(); 3556 QStringList newLabels; 3557 if (editFilterNameInternal(labels, newLabels)) 3558 m_FilterManager->setFilterNames(newLabels); 3559 } 3560 } 3561 3562 bool Capture::editFilterNameInternal(const QStringList &labels, QStringList &newLabels) 3563 { 3564 QDialog filterDialog; 3565 3566 QFormLayout *formLayout = new QFormLayout(&filterDialog); 3567 QVector<QLineEdit *> newLabelEdits; 3568 3569 for (uint8_t i = 0; i < labels.count(); i++) 3570 { 3571 QLabel *existingLabel = new QLabel(QString("%1. <b>%2</b>").arg(i + 1).arg(labels[i]), &filterDialog); 3572 QLineEdit *newLabel = new QLineEdit(labels[i], &filterDialog); 3573 newLabelEdits.append(newLabel); 3574 formLayout->addRow(existingLabel, newLabel); 3575 } 3576 3577 QString title = m_standAlone ? 3578 "Edit Filter Names" : devices()->filterWheel()->getDeviceName(); 3579 filterDialog.setWindowTitle(title); 3580 filterDialog.setLayout(formLayout); 3581 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &filterDialog); 3582 connect(buttonBox, &QDialogButtonBox::accepted, &filterDialog, &QDialog::accept); 3583 connect(buttonBox, &QDialogButtonBox::rejected, &filterDialog, &QDialog::reject); 3584 filterDialog.layout()->addWidget(buttonBox); 3585 3586 if (filterDialog.exec() == QDialog::Accepted) 3587 { 3588 QStringList results; 3589 for (uint8_t i = 0; i < labels.count(); i++) 3590 results << newLabelEdits[i]->text(); 3591 newLabels = results; 3592 return true; 3593 } 3594 return false; 3595 } 3596 3597 void Capture::handleScriptsManager() 3598 { 3599 QMap<ScriptTypes, QString> old_scripts = m_scriptsManager->getScripts(); 3600 3601 if (m_scriptsManager->exec() != QDialog::Accepted) 3602 // reset to old value 3603 m_scriptsManager->setScripts(old_scripts); 3604 } 3605 3606 void Capture::showTemperatureRegulation() 3607 { 3608 if (!devices()->getActiveCamera()) 3609 return; 3610 3611 double currentRamp, currentThreshold; 3612 if (!devices()->getActiveCamera()->getTemperatureRegulation(currentRamp, currentThreshold)) 3613 return; 3614 3615 3616 double rMin, rMax, rStep, tMin, tMax, tStep; 3617 3618 devices()->getActiveCamera()->getMinMaxStep("CCD_TEMP_RAMP", "RAMP_SLOPE", &rMin, &rMax, &rStep); 3619 devices()->getActiveCamera()->getMinMaxStep("CCD_TEMP_RAMP", "RAMP_THRESHOLD", &tMin, &tMax, &tStep); 3620 3621 QLabel rampLabel(i18nc("Maximum temperature variation over time when regulating.", "Ramp (°C/min):")); 3622 QDoubleSpinBox rampSpin; 3623 rampSpin.setMinimum(rMin); 3624 rampSpin.setMaximum(rMax); 3625 rampSpin.setSingleStep(rStep); 3626 rampSpin.setValue(currentRamp); 3627 rampSpin.setToolTip(i18n("<html><body>" 3628 "<p>Maximum temperature change per minute when cooling or warming the camera. Set zero to disable." 3629 "<p>This setting is read from and stored in the INDI camera driver configuration." 3630 "</body></html>")); 3631 3632 QLabel thresholdLabel(i18nc("Temperature threshold above which regulation triggers.", "Threshold (°C):")); 3633 QDoubleSpinBox thresholdSpin; 3634 thresholdSpin.setMinimum(tMin); 3635 thresholdSpin.setMaximum(tMax); 3636 thresholdSpin.setSingleStep(tStep); 3637 thresholdSpin.setValue(currentThreshold); 3638 thresholdSpin.setToolTip(i18n("<html><body>" 3639 "<p>Maximum difference between camera and target temperatures triggering regulation." 3640 "<p>This setting is read from and stored in the INDI camera driver configuration." 3641 "</body></html>")); 3642 3643 QFormLayout layout; 3644 layout.addRow(&rampLabel, &rampSpin); 3645 layout.addRow(&thresholdLabel, &thresholdSpin); 3646 3647 QPointer<QDialog> dialog = new QDialog(this); 3648 QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog); 3649 connect(&buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); 3650 connect(&buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); 3651 dialog->setWindowTitle(i18nc("@title:window", "Set Temperature Regulation")); 3652 layout.addWidget(&buttonBox); 3653 dialog->setLayout(&layout); 3654 dialog->setMinimumWidth(300); 3655 3656 if (dialog->exec() == QDialog::Accepted) 3657 { 3658 if (devices()->getActiveCamera()) 3659 devices()->getActiveCamera()->setTemperatureRegulation(rampSpin.value(), thresholdSpin.value()); 3660 } 3661 } 3662 3663 void Capture::updateStartButtons(bool start, bool pause) 3664 { 3665 if (start) 3666 { 3667 // start capturing, therefore next possible action is stopping 3668 startB->setIcon(QIcon::fromTheme("media-playback-stop")); 3669 startB->setToolTip(i18n("Stop Sequence")); 3670 } 3671 else 3672 { 3673 // stop capturing, therefore next possible action is starting 3674 startB->setIcon(QIcon::fromTheme("media-playback-start")); 3675 startB->setToolTip(i18n(pause ? "Resume Sequence" : "Start Sequence")); 3676 } 3677 pauseB->setEnabled(start && !pause); 3678 3679 } 3680 3681 void Capture::generateDarkFlats() 3682 { 3683 const auto existingJobs = state()->allJobs().size(); 3684 uint8_t jobsAdded = 0; 3685 3686 for (int i = 0; i < existingJobs; i++) 3687 { 3688 if (state()->allJobs().at(i)->getFrameType() != FRAME_FLAT) 3689 continue; 3690 3691 syncGUIToJob(state()->allJobs().at(i)); 3692 3693 captureTypeS->setCurrentIndex(FRAME_DARK); 3694 createJob(SequenceJob::JOBTYPE_DARKFLAT); 3695 jobsAdded++; 3696 } 3697 3698 if (jobsAdded > 0) 3699 { 3700 appendLogText(i18np("One dark flats job was created.", "%1 dark flats jobs were created.", jobsAdded)); 3701 } 3702 } 3703 3704 void Capture::updateJobFromUI(SequenceJob * job, FilenamePreviewType filenamePreview) 3705 { 3706 job->setCoreProperty(SequenceJob::SJ_Format, captureFormatS->currentText()); 3707 job->setCoreProperty(SequenceJob::SJ_Encoding, captureEncodingS->currentText()); 3708 3709 if (captureISOS) 3710 job->setISO(captureISOS->currentIndex()); 3711 3712 job->setCoreProperty(SequenceJob::SJ_Gain, getGain()); 3713 job->setCoreProperty(SequenceJob::SJ_Offset, getOffset()); 3714 3715 if (cameraTemperatureN->isEnabled()) 3716 { 3717 job->setCoreProperty(SequenceJob::SJ_EnforceTemperature, cameraTemperatureS->isChecked()); 3718 job->setTargetTemperature(cameraTemperatureN->value()); 3719 } 3720 3721 job->setScripts(m_scriptsManager->getScripts()); 3722 job->setUploadMode(static_cast<ISD::Camera::UploadMode>(fileUploadModeS->currentIndex())); 3723 job->setFlatFieldDuration(state()->flatFieldDuration()); 3724 job->setCalibrationPreAction(state()->calibrationPreAction()); 3725 job->setWallCoord(state()->wallCoord()); 3726 job->setCoreProperty(SequenceJob::SJ_TargetADU, state()->targetADU()); 3727 job->setCoreProperty(SequenceJob::SJ_TargetADUTolerance, state()->targetADUTolerance()); 3728 job->setFrameType(static_cast<CCDFrameType>(qMax(0, captureTypeS->currentIndex()))); 3729 3730 if (FilterPosCombo->currentIndex() != -1 && (m_standAlone || devices()->filterWheel() != nullptr)) 3731 job->setTargetFilter(FilterPosCombo->currentIndex() + 1, FilterPosCombo->currentText()); 3732 3733 job->setCoreProperty(SequenceJob::SJ_Exposure, captureExposureN->value()); 3734 3735 job->setCoreProperty(SequenceJob::SJ_Count, captureCountN->value()); 3736 3737 job->setCoreProperty(SequenceJob::SJ_Binning, QPoint(captureBinHN->value(), captureBinVN->value())); 3738 3739 /* in ms */ 3740 job->setCoreProperty(SequenceJob::SJ_Delay, captureDelayN->value() * 1000); 3741 3742 // Custom Properties 3743 job->setCustomProperties(customPropertiesDialog->getCustomProperties()); 3744 3745 job->setCoreProperty(SequenceJob::SJ_ROI, QRect(captureFrameXN->value(), captureFrameYN->value(), captureFrameWN->value(), 3746 captureFrameHN->value())); 3747 job->setCoreProperty(SequenceJob::SJ_RemoteDirectory, fileRemoteDirT->text()); 3748 job->setCoreProperty(SequenceJob::SJ_LocalDirectory, fileDirectoryT->text()); 3749 job->setCoreProperty(SequenceJob::SJ_TargetName, targetNameT->text()); 3750 job->setCoreProperty(SequenceJob::SJ_PlaceholderFormat, placeholderFormatT->text()); 3751 job->setCoreProperty(SequenceJob::SJ_PlaceholderSuffix, formatSuffixN->value()); 3752 3753 job->setCoreProperty(SequenceJob::SJ_DitherPerJobFrequency, m_LimitsUI->limitDitherFrequencyN->value()); 3754 3755 auto placeholderPath = PlaceholderPath(); 3756 placeholderPath.addJob(job, placeholderFormatT->text()); 3757 3758 QString signature = placeholderPath.generateSequenceFilename(*job, 3759 filenamePreview != REMOTE_PREVIEW, true, 1, 3760 ".fits", "", false, true); 3761 job->setCoreProperty(SequenceJob::SJ_Signature, signature); 3762 3763 auto remoteUpload = placeholderPath.generateSequenceFilename(*job, 3764 false, 3765 true, 3766 1, 3767 ".fits", 3768 "", 3769 false, 3770 true); 3771 3772 auto lastSeparator = remoteUpload.lastIndexOf(QDir::separator()); 3773 auto remoteDirectory = remoteUpload.mid(0, lastSeparator); 3774 auto remoteFilename = QString("%1_XXX").arg(remoteUpload.mid(lastSeparator + 1)); 3775 job->setCoreProperty(SequenceJob::SJ_RemoteFormatDirectory, remoteDirectory); 3776 job->setCoreProperty(SequenceJob::SJ_RemoteFormatFilename, remoteFilename); 3777 } 3778 3779 void Capture::setMeridianFlipState(QSharedPointer<MeridianFlipState> newstate) 3780 { 3781 state()->setMeridianFlipState(newstate); 3782 connect(state()->getMeridianFlipState().get(), &MeridianFlipState::newLog, this, &Capture::appendLogText); 3783 } 3784 3785 void Capture::syncRefocusOptionsFromGUI() 3786 { 3787 Options::setEnforceAutofocusHFR(m_LimitsUI->limitFocusHFRS->isChecked()); 3788 Options::setHFRThresholdPercentage(m_LimitsUI->limitFocusHFRThresholdPercentage->value()); 3789 Options::setHFRDeviation(m_LimitsUI->limitFocusHFRN->value()); 3790 Options::setInSequenceCheckFrames(m_LimitsUI->limitFocusHFRCheckFrames->value()); 3791 Options::setHFRCheckAlgorithm(m_LimitsUI->limitFocusHFRAlgorithm->currentIndex()); 3792 Options::setEnforceAutofocusOnTemperature(m_LimitsUI->limitFocusDeltaTS->isChecked()); 3793 Options::setMaxFocusTemperatureDelta(m_LimitsUI->limitFocusDeltaTN->value()); 3794 Options::setEnforceRefocusEveryN(m_LimitsUI->limitRefocusS->isChecked()); 3795 Options::setRefocusEveryN(static_cast<uint>(m_LimitsUI->limitRefocusN->value())); 3796 Options::setRefocusAfterMeridianFlip(m_LimitsUI->meridianRefocusS->isChecked()); 3797 } 3798 3799 QJsonObject Capture::currentScope() 3800 { 3801 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain); 3802 if (activeCamera() && trainID.isValid()) 3803 { 3804 auto id = trainID.toUInt(); 3805 auto name = OpticalTrainManager::Instance()->name(id); 3806 return OpticalTrainManager::Instance()->getScope(name); 3807 } 3808 // return empty JSON object 3809 return QJsonObject(); 3810 } 3811 3812 double Capture::currentReducer() 3813 { 3814 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain); 3815 if (activeCamera() && trainID.isValid()) 3816 { 3817 auto id = trainID.toUInt(); 3818 auto name = OpticalTrainManager::Instance()->name(id); 3819 return OpticalTrainManager::Instance()->getReducer(name); 3820 } 3821 // no reducer available 3822 return 1.0; 3823 } 3824 3825 double Capture::currentAperture() 3826 { 3827 auto scope = currentScope(); 3828 3829 double focalLength = scope["focal_length"].toDouble(-1); 3830 double aperture = scope["aperture"].toDouble(-1); 3831 double focalRatio = scope["focal_ratio"].toDouble(-1); 3832 3833 // DSLR Lens Aperture 3834 if (aperture < 0 && focalRatio > 0) 3835 aperture = focalLength * focalRatio; 3836 3837 return aperture; 3838 } 3839 3840 void Capture::setupOpticalTrainManager() 3841 { 3842 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &Capture::refreshOpticalTrain); 3843 connect(trainB, &QPushButton::clicked, this, [this]() 3844 { 3845 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText()); 3846 }); 3847 connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) 3848 { 3849 ProfileSettings::Instance()->setOneSetting(ProfileSettings::CaptureOpticalTrain, 3850 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index))); 3851 refreshOpticalTrain(); 3852 emit trainChanged(); 3853 }); 3854 } 3855 3856 void Capture::refreshOpticalTrain() 3857 { 3858 opticalTrainCombo->blockSignals(true); 3859 opticalTrainCombo->clear(); 3860 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames()); 3861 trainB->setEnabled(true); 3862 3863 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::CaptureOpticalTrain); 3864 3865 if (trainID.isValid()) 3866 { 3867 auto id = trainID.toUInt(); 3868 3869 // If train not found, select the first one available. 3870 if (OpticalTrainManager::Instance()->exists(id) == false) 3871 { 3872 qCWarning(KSTARS_EKOS_CAPTURE) << "Optical train doesn't exist for id" << id; 3873 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0)); 3874 } 3875 3876 auto name = OpticalTrainManager::Instance()->name(id); 3877 3878 opticalTrainCombo->setCurrentText(name); 3879 process()->refreshOpticalTrain(name); 3880 } 3881 3882 opticalTrainCombo->blockSignals(false); 3883 } 3884 3885 void Capture::generatePreviewFilename() 3886 { 3887 if (state()->isCaptureRunning() == false) 3888 { 3889 placeholderFormatT->setToolTip(previewFilename( LOCAL_PREVIEW )); 3890 emit newLocalPreview(placeholderFormatT->toolTip()); 3891 3892 if (fileUploadModeS->currentIndex() != 0) 3893 fileRemoteDirT->setToolTip(previewFilename( REMOTE_PREVIEW )); 3894 } 3895 } 3896 3897 QString Capture::previewFilename(FilenamePreviewType previewType) 3898 { 3899 QString previewText; 3900 QString m_format; 3901 auto separator = QDir::separator(); 3902 3903 if (previewType == LOCAL_PREVIEW) 3904 { 3905 if(!fileDirectoryT->text().endsWith(separator) && !placeholderFormatT->text().startsWith(separator)) 3906 placeholderFormatT->setText(separator + placeholderFormatT->text()); 3907 m_format = fileDirectoryT->text() + placeholderFormatT->text() + formatSuffixN->prefix() + formatSuffixN->cleanText(); 3908 } 3909 else if (previewType == REMOTE_PREVIEW) 3910 m_format = fileRemoteDirT->text(); 3911 3912 //Guard against an empty format to avoid the empty directory warning pop-up in addjob 3913 if (m_format.isEmpty()) 3914 return previewText; 3915 // Tags %d & %p disable for now for simplicity 3916 // else if (state()->sequenceURL().toLocalFile().isEmpty() && (m_format.contains("%d") || m_format.contains("%p") 3917 // || m_format.contains("%f"))) 3918 else if (state()->sequenceURL().toLocalFile().isEmpty() && m_format.contains("%f")) 3919 previewText = ("Save the sequence file to show filename preview"); 3920 else 3921 { 3922 // create temporarily a sequence job 3923 SequenceJob *m_job = createJob(SequenceJob::JOBTYPE_PREVIEW, previewType); 3924 if (m_job == nullptr) 3925 return previewText; 3926 3927 QString previewSeq; 3928 if (state()->sequenceURL().toLocalFile().isEmpty()) 3929 { 3930 if (m_format.startsWith(separator)) 3931 previewSeq = m_format.left(m_format.lastIndexOf(separator)); 3932 } 3933 else 3934 previewSeq = state()->sequenceURL().toLocalFile(); 3935 auto m_placeholderPath = PlaceholderPath(previewSeq); 3936 3937 QString extension; 3938 if (captureEncodingS->currentText() == "FITS") 3939 extension = ".fits"; 3940 else if (captureEncodingS->currentText() == "XISF") 3941 extension = ".xisf"; 3942 else 3943 extension = ".[NATIVE]"; 3944 previewText = m_placeholderPath.generateSequenceFilename(*m_job, previewType == LOCAL_PREVIEW, true, 1, 3945 extension, "", false); 3946 previewText = QDir::toNativeSeparators(previewText); 3947 // we do not use it any more 3948 m_job->deleteLater(); 3949 } 3950 3951 // Must change directory separate to UNIX style for remote 3952 if (previewType == REMOTE_PREVIEW) 3953 previewText.replace(separator, "/"); 3954 3955 return previewText; 3956 } 3957 3958 void Capture::openExposureCalculatorDialog() 3959 { 3960 qCInfo(KSTARS_EKOS_CAPTURE) << "Instantiating an Exposure Calculator"; 3961 3962 // Learn how to read these from indi 3963 double preferredSkyQuality = 20.5; 3964 3965 auto scope = currentScope(); 3966 double focalRatio = scope["focal_ratio"].toDouble(-1); 3967 3968 auto reducedFocalLength = currentReducer() * scope["focal_length"].toDouble(-1); 3969 auto aperture = currentAperture(); 3970 auto reducedFocalRatio = (focalRatio > 0 || aperture == 0) ? focalRatio : reducedFocalLength / aperture; 3971 3972 if (devices()->getActiveCamera() != nullptr) 3973 { 3974 qCInfo(KSTARS_EKOS_CAPTURE) << "set ExposureCalculator preferred camera to active camera id: " 3975 << devices()->getActiveCamera()->getDeviceName(); 3976 } 3977 3978 QPointer<ExposureCalculatorDialog> anExposureCalculatorDialog(new ExposureCalculatorDialog(KStars::Instance(), 3979 preferredSkyQuality, 3980 reducedFocalRatio, 3981 devices()->getActiveCamera()->getDeviceName())); 3982 anExposureCalculatorDialog->setAttribute(Qt::WA_DeleteOnClose); 3983 anExposureCalculatorDialog->show(); 3984 } 3985 3986 bool Capture::hasCoolerControl() 3987 { 3988 return process()->hasCoolerControl(); 3989 } 3990 3991 bool Capture::setCoolerControl(bool enable) 3992 { 3993 return process()->setCoolerControl(enable); 3994 } 3995 3996 void Capture::removeDevice(const QSharedPointer<ISD::GenericDevice> &device) 3997 { 3998 process()->removeDevice(device); 3999 } 4000 4001 void Capture::start() 4002 { 4003 process()->startNextPendingJob(); 4004 } 4005 4006 void Capture::stop(CaptureState targetState) 4007 { 4008 process()->stopCapturing(targetState); 4009 } 4010 4011 void Capture::toggleVideo(bool enabled) 4012 { 4013 process()->toggleVideo(enabled); 4014 } 4015 4016 void Capture::setTargetName(const QString &newTargetName) 4017 { 4018 // target is changed only if no job is running 4019 if (activeJob() == nullptr) 4020 { 4021 // set the target name in the currently selected job 4022 targetNameT->setText(newTargetName); 4023 auto rows = queueTable->selectionModel()->selectedRows(); 4024 if(rows.count() > 0) 4025 { 4026 // take the first one, since we are in single selection mode 4027 int pos = rows.constFirst().row(); 4028 4029 if (state()->allJobs().size() > pos) 4030 state()->allJobs().at(pos)->setCoreProperty(SequenceJob::SJ_TargetName, newTargetName); 4031 } 4032 4033 emit captureTarget(newTargetName); 4034 } 4035 } 4036 4037 QString Capture::getTargetName() 4038 { 4039 if (activeJob()) 4040 return activeJob()->getCoreProperty(SequenceJob::SJ_TargetName).toString(); 4041 else 4042 return ""; 4043 } 4044 4045 void Capture::restartCamera(const QString &name) 4046 { 4047 process()->restartCamera(name); 4048 } 4049 4050 void Capture::capturePreview() 4051 { 4052 process()->capturePreview(); 4053 } 4054 4055 void Capture::startFraming() 4056 { 4057 process()->capturePreview(true); 4058 } 4059 4060 double Capture::getGain() 4061 { 4062 return devices()->cameraGain(customPropertiesDialog->getCustomProperties()); 4063 } 4064 4065 double Capture::getOffset() 4066 { 4067 return devices()->cameraOffset(customPropertiesDialog->getCustomProperties()); 4068 } 4069 4070 void Capture::setHFR(double newHFR, int, bool inAutofocus) 4071 { 4072 state()->getRefocusState()->setFocusHFR(newHFR, inAutofocus); 4073 } 4074 4075 ISD::Camera *Capture::activeCamera() 4076 { 4077 return m_captureDeviceAdaptor->getActiveCamera(); 4078 } 4079 }