Kstars

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

KDE's Doxygen guidelines are available online.