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

KDE's Doxygen guidelines are available online.