Kstars

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

KDE's Doxygen guidelines are available online.