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

KDE's Doxygen guidelines are available online.