Kstars

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

KDE's Doxygen guidelines are available online.