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

KDE's Doxygen guidelines are available online.