Kstars

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

KDE's Doxygen guidelines are available online.