diff --git a/Tests/capture/decimal_values.esq b/Tests/capture/decimal_values.esq
new file mode 100644
--- /dev/null
+++ b/Tests/capture/decimal_values.esq
@@ -0,0 +1,88 @@
+
+
+Eric TallFurryMan
+CCD Simulator
+CCD Simulator
+7.57
+0
+60
+0.03
+
+1
+
+1
+1
+
+
+0
+0
+1280
+1024
+
+0
+Luminance
+Light
+
+
+0
+0
+0
+
+1
+1
+/var/tmp/kstars_tests
+0
+0
+
+
+
+
+Manual
+
+
+Manual
+
+False
+False
+
+
+
+1.1
+
+1
+1
+
+
+0
+0
+1280
+1024
+
+0
+Luminance
+Light
+
+
+0
+0
+0
+
+1
+1
+/var/tmp/kstars_tests
+0
+0
+
+
+
+
+Manual
+
+
+Manual
+
+False
+False
+
+
+
diff --git a/kstars/ekos/capture/capture.cpp b/kstars/ekos/capture/capture.cpp
--- a/kstars/ekos/capture/capture.cpp
+++ b/kstars/ekos/capture/capture.cpp
@@ -667,7 +667,7 @@
double temperature = 0;
if (currentCCD->getTemperature(&temperature))
{
- temperatureOUT->setText(QString::number(temperature, 'f', 2));
+ temperatureOUT->setText(QString("%L1").arg(temperature, 0, 'f', 2));
if (temperatureIN->cleanText().isEmpty())
temperatureIN->setValue(temperature);
}
@@ -1300,7 +1300,7 @@
m_State = CAPTURE_IMAGE_RECEIVED;
emit newStatus(Ekos::CAPTURE_IMAGE_RECEIVED);
- currentImgCountOUT->setText(QString::number(activeJob->getCompleted()));
+ currentImgCountOUT->setText(QString("%L1").arg(activeJob->getCompleted()));
// Check if we need to execute post capture script first
if (activeJob->getPostCaptureScript().isEmpty() == false)
@@ -1667,7 +1667,7 @@
{
case SequenceJob::CAPTURE_OK:
{
- appendLogText(i18n("Capturing %1-second %2 image...", QString::number(activeJob->getExposure(),'g',3), activeJob->getFilterName()));
+ appendLogText(i18n("Capturing %1-second %2 image...", QString("%L1").arg(activeJob->getExposure(), 0, 'f', 3), activeJob->getFilterName()));
if (activeJob->isPreview() == false)
{
int index = jobs.indexOf(activeJob);
@@ -1831,7 +1831,7 @@
if (targetChip != tChip || targetChip->getCaptureMode() != FITS_NORMAL || meridianFlipStage >= MF_ALIGNING)
return;
- exposeOUT->setText(QString::number(value, 'd', 2));
+ exposeOUT->setText(QString("%L1").arg(value, 0, 'd', 2));
if (activeJob)
{
@@ -1904,7 +1904,7 @@
checkCCD();
}
- temperatureOUT->setText(QString::number(value, 'f', 2));
+ temperatureOUT->setText(QString("%L1").arg(value, 0, 'f', 2));
if (temperatureIN->cleanText().isEmpty())
temperatureIN->setValue(value);
@@ -2102,7 +2102,7 @@
jsonJob.insert("Bin", bin->text());
QTableWidgetItem *exp = m_JobUnderEdit ? queueTable->item(currentRow, 4) : new QTableWidgetItem();
- exp->setText(QString::number(exposureIN->value()));
+ exp->setText(QString("%L1").arg(exposureIN->value(), 0, 'f', exposureIN->decimals()));
exp->setTextAlignment(Qt::AlignHCenter);
exp->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
jsonJob.insert("Exp", exp->text());
@@ -2122,7 +2122,7 @@
iso->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
QTableWidgetItem *count = m_JobUnderEdit ? queueTable->item(currentRow, 6) : new QTableWidgetItem();
- count->setText(QString::number(countIN->value()));
+ count->setText(QString("%L1").arg(countIN->value()));
count->setTextAlignment(Qt::AlignHCenter);
count->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
jsonJob.insert("Count", count->text());
@@ -2337,8 +2337,8 @@
if (activeJob->isPreview() == false)
{
- fullImgCountOUT->setText(QString::number(activeJob->getCount()));
- currentImgCountOUT->setText(QString::number(activeJob->getCompleted()));
+ fullImgCountOUT->setText(QString("%L1").arg(activeJob->getCount()));
+ currentImgCountOUT->setText(QString("%L1").arg(activeJob->getCompleted()));
// set the progress info
imgProgress->setEnabled(true);
@@ -2407,7 +2407,7 @@
{
activeJob->setCompleted(activeJob->getCount());
appendLogText(i18n("Job requires %1-second %2 images, has already %3/%4 captures and does not need to run.",
- QString::number(job->getExposure(),'g',3), job->getFilterName(),
+ QString("%L1").arg(job->getExposure(), 0, 'f', 3), job->getFilterName(),
activeJob->getCompleted(), activeJob->getCount()));
processJobCompletion();
@@ -2417,9 +2417,9 @@
else
{
// There are captures to process
- currentImgCountOUT->setText(QString::number(activeJob->getCompleted()));
+ currentImgCountOUT->setText(QString("%L1").arg(activeJob->getCompleted()));
appendLogText(i18n("Job requires %1-second %2 images, has %3/%4 frames captured and will be processed.",
- QString::number(job->getExposure(),'g',3), job->getFilterName(),
+ QString("%L1").arg(job->getExposure(), 0, 'f', 3), job->getFilterName(),
activeJob->getCompleted(), activeJob->getCount()));
// Emit progress update - done a few lines below
@@ -2682,7 +2682,7 @@
double deviation_rms = sqrt(delta_ra * delta_ra + delta_dec * delta_dec);
- QString deviationText = QString("%1").arg(deviation_rms, 0, 'g', 3);
+ QString deviationText = QString("%1").arg(deviation_rms, 0, 'f', 3);
// If we have an active busy job, let's abort it if guiding deviation is exceeded.
// And we accounted for the spike
@@ -2700,7 +2700,7 @@
appendLogText(i18n("Guiding deviation %1 exceeded limit value of %2 arcsecs, "
"suspending exposure and waiting for guider up to %3 seconds.",
deviationText, guideDeviation->value(),
- QString::number(guideDeviationTimer.interval()/1000.0,'g',3)));
+ QString("%L1").arg(guideDeviationTimer.interval()/1000.0,0,'f',3)));
suspend();
@@ -3015,13 +3015,16 @@
XMLEle *ep = nullptr;
char c;
+ // We expect all data read from the XML to be in the C locale - QLocale::c().
+ QLocale cLocale = QLocale::c();
+
while (sFile.getChar(&c))
{
root = readXMLEle(xmlParser, c, errmsg);
if (root)
{
- double sqVersion = atof(findXMLAttValu(root, "version"));
+ double sqVersion = cLocale.toFloat(findXMLAttValu(root, "version"));
if (sqVersion < SQ_COMPAT_VERSION)
{
appendLogText(i18n("Deprecated sequence file format version %1. Please construct a new sequence file.",
@@ -3037,61 +3040,36 @@
}
else if (!strcmp(tagXMLEle(ep), "GuideDeviation"))
{
- if (!strcmp(findXMLAttValu(ep, "enabled"), "true"))
- {
- guideDeviationCheck->setChecked(true);
- guideDeviation->setValue(atof(pcdataXMLEle(ep)));
- }
- else
- guideDeviationCheck->setChecked(false);
+ guideDeviationCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true"));
+ guideDeviation->setValue(cLocale.toDouble(pcdataXMLEle(ep)));
}
else if (!strcmp(tagXMLEle(ep), "Autofocus"))
{
- if (!strcmp(findXMLAttValu(ep, "enabled"), "true"))
- {
- autofocusCheck->setChecked(true);
- float HFRValue = atof(pcdataXMLEle(ep));
- if (HFRValue > 0)
- {
- fileHFR = HFRValue;
- HFRPixels->setValue(HFRValue);
- }
- else
- fileHFR = 0;
- }
- else
- autofocusCheck->setChecked(false);
+ autofocusCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true"));
+ double const HFRValue = cLocale.toDouble(pcdataXMLEle(ep));
+ // Set the HFR value from XML, or reset it to zero, don't let another unrelated older HFR be used
+ // Note that HFR value will only be serialized to XML when option "Save Sequence HFR to File" is enabled
+ fileHFR = HFRValue > 0.0 ? HFRValue : 0.0;
+ HFRPixels->setValue(fileHFR);
}
else if (!strcmp(tagXMLEle(ep), "RefocusEveryN"))
{
- if (!strcmp(findXMLAttValu(ep, "enabled"), "true"))
- {
- refocusEveryNCheck->setChecked(true);
- int minutesValue = atof(pcdataXMLEle(ep));
- if (minutesValue > 0)
- {
- refocusEveryNMinutesValue = minutesValue;
- refocusEveryN->setValue(refocusEveryNMinutesValue);
- }
- else
- refocusEveryNMinutesValue = 0;
- }
- else
- refocusEveryNCheck->setChecked(false);
+ refocusEveryNCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true"));
+ int const minutesValue = cLocale.toInt(pcdataXMLEle(ep));
+ // Set the refocus period from XML, or reset it to zero, don't let another unrelated older refocus period be used.
+ refocusEveryNMinutesValue = minutesValue > 0 ? minutesValue : 0;
+ refocusEveryN->setValue(refocusEveryNMinutesValue);
}
else if (!strcmp(tagXMLEle(ep), "MeridianFlip"))
{
- if (!strcmp(findXMLAttValu(ep, "enabled"), "true"))
- {
- meridianCheck->setChecked(true);
- meridianHours->setValue(atof(pcdataXMLEle(ep)));
- }
- else
- meridianCheck->setChecked(false);
+ meridianCheck->setChecked(!strcmp(findXMLAttValu(ep, "enabled"), "true"));
+ meridianHours->setValue(cLocale.toDouble(pcdataXMLEle(ep)));
}
else if (!strcmp(tagXMLEle(ep), "CCD"))
{
CCDCaptureCombo->setCurrentText(pcdataXMLEle(ep));
+ // Signal "activated" of QComboBox does not fire when changing the text programmatically
+ setCCD(pcdataXMLEle(ep));
}
else if (!strcmp(tagXMLEle(ep), "FilterWheel"))
{
@@ -3128,38 +3106,40 @@
XMLEle *subEP;
rotatorSettings->setRotationEnforced(false);
+ QLocale cLocale = QLocale::c();
+
for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
{
if (!strcmp(tagXMLEle(ep), "Exposure"))
- exposureIN->setValue(atof(pcdataXMLEle(ep)));
+ exposureIN->setValue(cLocale.toDouble(pcdataXMLEle(ep)));
else if (!strcmp(tagXMLEle(ep), "Binning"))
{
subEP = findXMLEle(ep, "X");
if (subEP)
- binXIN->setValue(atoi(pcdataXMLEle(subEP)));
+ binXIN->setValue(cLocale.toInt(pcdataXMLEle(subEP)));
subEP = findXMLEle(ep, "Y");
if (subEP)
- binYIN->setValue(atoi(pcdataXMLEle(subEP)));
+ binYIN->setValue(cLocale.toInt(pcdataXMLEle(subEP)));
}
else if (!strcmp(tagXMLEle(ep), "Frame"))
{
subEP = findXMLEle(ep, "X");
if (subEP)
- frameXIN->setValue(atoi(pcdataXMLEle(subEP)));
+ frameXIN->setValue(cLocale.toInt(pcdataXMLEle(subEP)));
subEP = findXMLEle(ep, "Y");
if (subEP)
- frameYIN->setValue(atoi(pcdataXMLEle(subEP)));
+ frameYIN->setValue(cLocale.toInt(pcdataXMLEle(subEP)));
subEP = findXMLEle(ep, "W");
if (subEP)
- frameWIN->setValue(atoi(pcdataXMLEle(subEP)));
+ frameWIN->setValue(cLocale.toInt(pcdataXMLEle(subEP)));
subEP = findXMLEle(ep, "H");
if (subEP)
- frameHIN->setValue(atoi(pcdataXMLEle(subEP)));
+ frameHIN->setValue(cLocale.toInt(pcdataXMLEle(subEP)));
}
else if (!strcmp(tagXMLEle(ep), "Temperature"))
{
if (temperatureIN->isEnabled())
- temperatureIN->setValue(atof(pcdataXMLEle(ep)));
+ temperatureIN->setValue(cLocale.toDouble(pcdataXMLEle(ep)));
// If force attribute exist, we change temperatureCheck, otherwise do nothing.
if (!strcmp(findXMLAttValu(ep, "force"), "true"))
@@ -3193,11 +3173,11 @@
}
else if (!strcmp(tagXMLEle(ep), "Count"))
{
- countIN->setValue(atoi(pcdataXMLEle(ep)));
+ countIN->setValue(cLocale.toInt(pcdataXMLEle(ep)));
}
else if (!strcmp(tagXMLEle(ep), "Delay"))
{
- delayIN->setValue(atoi(pcdataXMLEle(ep)));
+ delayIN->setValue(cLocale.toInt(pcdataXMLEle(ep)));
}
else if (!strcmp(tagXMLEle(ep), "PostCaptureScript"))
{
@@ -3213,21 +3193,21 @@
}
else if (!strcmp(tagXMLEle(ep), "UploadMode"))
{
- uploadModeCombo->setCurrentIndex(atoi(pcdataXMLEle(ep)));
+ uploadModeCombo->setCurrentIndex(cLocale.toInt(pcdataXMLEle(ep)));
}
else if (!strcmp(tagXMLEle(ep), "ISOIndex"))
{
if (ISOCombo->isEnabled())
- ISOCombo->setCurrentIndex(atoi(pcdataXMLEle(ep)));
+ ISOCombo->setCurrentIndex(cLocale.toInt(pcdataXMLEle(ep)));
}
else if (!strcmp(tagXMLEle(ep), "FormatIndex"))
{
- transferFormatCombo->setCurrentIndex(atoi(pcdataXMLEle(ep)));
+ transferFormatCombo->setCurrentIndex(cLocale.toInt(pcdataXMLEle(ep)));
}
else if (!strcmp(tagXMLEle(ep), "Rotation"))
{
rotatorSettings->setRotationEnforced(true);
- rotatorSettings->setTargetRotationPA(atof(pcdataXMLEle(ep)));
+ rotatorSettings->setTargetRotationPA(cLocale.toDouble(pcdataXMLEle(ep)));
}
else if (!strcmp(tagXMLEle(ep), "Properties"))
{
@@ -3240,7 +3220,7 @@
for (oneNumber = nextXMLEle(subEP, 1); oneNumber != nullptr; oneNumber = nextXMLEle(subEP, 0))
{
const char *name = findXMLAttValu(oneNumber, "name");
- numbers[name] = atof(pcdataXMLEle(oneNumber));
+ numbers[name] = cLocale.toDouble(pcdataXMLEle(oneNumber));
}
const char *name = findXMLAttValu(subEP, "name");
@@ -3271,8 +3251,8 @@
if (azEP && altEP)
{
flatFieldSource = SOURCE_WALL;
- wallCoord.setAz(atof(pcdataXMLEle(azEP)));
- wallCoord.setAlt(atof(pcdataXMLEle(altEP)));
+ wallCoord.setAz(cLocale.toDouble(pcdataXMLEle(azEP)));
+ wallCoord.setAlt(cLocale.toDouble(pcdataXMLEle(altEP)));
}
}
else
@@ -3294,13 +3274,13 @@
if (aduEP)
{
flatFieldDuration = DURATION_ADU;
- targetADU = atof(pcdataXMLEle(aduEP));
+ targetADU = cLocale.toDouble(pcdataXMLEle(aduEP));
}
aduEP = findXMLEle(subEP, "Tolerance");
if (aduEP)
{
- targetADUTolerance = atof(pcdataXMLEle(aduEP));
+ targetADUTolerance = cLocale.toDouble(pcdataXMLEle(aduEP));
}
}
@@ -3411,40 +3391,48 @@
QTextStream outstream(&file);
+ // We serialize sequence data to XML using the C locale
+ QLocale cLocale = QLocale::c();
+
outstream << "" << endl;
outstream << "" << endl;
if (m_ObserverName.isEmpty() == false)
outstream << "" << m_ObserverName << "" << endl;
outstream << "" << CCDCaptureCombo->currentText() << "" << endl;
outstream << "" << FilterDevicesCombo->currentText() << "" << endl;
outstream << ""
- << guideDeviation->value() << "" << endl;
+ << cLocale.toString(guideDeviation->value()) << "" << endl;
+ // Issue a warning when autofocus is enabled but Ekos options prevent HFR value from being written
+ if (autofocusCheck->isChecked() && !Options::saveHFRToFile())
+ appendLogText(i18n(
+ "Warning: HFR-based autofocus is set but option \"Save Sequence HFR Value to File\" is not enabled. "
+ "Current HFR value will not be written to sequence file."));
outstream << ""
- << (Options::saveHFRToFile() ? HFRPixels->value() : 0) << "" << endl;
+ << cLocale.toString(Options::saveHFRToFile() ? HFRPixels->value() : 0) << "" << endl;
outstream << ""
- << refocusEveryN->value() << "" << endl;
+ << cLocale.toString(refocusEveryN->value()) << "" << endl;
outstream << ""
- << meridianHours->value() << "" << endl;
+ << cLocale.toString(meridianHours->value()) << "" << endl;
foreach (SequenceJob *job, jobs)
{
job->getPrefixSettings(rawPrefix, filterEnabled, expEnabled, tsEnabled);
outstream << "" << endl;
- outstream << "" << job->getExposure() << "" << endl;
+ outstream << "" << cLocale.toString(job->getExposure()) << "" << endl;
outstream << "" << endl;
- outstream << "" << job->getXBin() << "" << endl;
- outstream << "" << job->getXBin() << "" << endl;
+ outstream << "" << cLocale.toString(job->getXBin()) << "" << endl;
+ outstream << "" << cLocale.toString(job->getXBin()) << "" << endl;
outstream << "" << endl;
outstream << "" << endl;
- outstream << "" << job->getSubX() << "" << endl;
- outstream << "" << job->getSubY() << "" << endl;
- outstream << "" << job->getSubW() << "" << endl;
- outstream << "" << job->getSubH() << "" << endl;
+ outstream << "" << cLocale.toString(job->getSubX()) << "" << endl;
+ outstream << "" << cLocale.toString(job->getSubY()) << "" << endl;
+ outstream << "" << cLocale.toString(job->getSubW()) << "" << endl;
+ outstream << "" << cLocale.toString(job->getSubH()) << "" << endl;
outstream << "" << endl;
if (job->getTargetTemperature() != INVALID_VALUE)
outstream << ""
- << job->getTargetTemperature() << "" << endl;
+ << cLocale.toString(job->getTargetTemperature()) << "" << endl;
if (job->getTargetFilter() >= 0)
//outstream << "" << job->getTargetFilter() << "" << endl;
outstream << "" << job->getFilterName() << "" << endl;
@@ -3456,9 +3444,9 @@
outstream << "" << (expEnabled ? 1 : 0) << "" << endl;
outstream << "" << (tsEnabled ? 1 : 0) << "" << endl;
outstream << "" << endl;
- outstream << "" << job->getCount() << "" << endl;
+ outstream << "" << cLocale.toString(job->getCount()) << "" << endl;
// ms to seconds
- outstream << "" << job->getDelay() / 1000 << "" << endl;
+ outstream << "" << cLocale.toString(job->getDelay() / 1000.0) << "" << endl;
if (job->getPostCaptureScript().isEmpty() == false)
outstream << "" << job->getPostCaptureScript() << "" << endl;
outstream << "" << job->getLocalDir() << "" << endl;
@@ -3481,7 +3469,8 @@
while (numberIter.hasNext())
{
numberIter.next();
- outstream << "" << numberIter.value() << "" << endl;
+ outstream << "" << cLocale.toString(numberIter.value()) << "" << endl;
}
outstream << "" << endl;
}
@@ -3498,8 +3487,8 @@
else if (job->getFlatFieldSource() == SOURCE_WALL)
{
outstream << "Wall" << endl;
- outstream << "" << job->getWallCoord().az().Degrees() << "" << endl;
- outstream << "" << job->getWallCoord().alt().Degrees() << "" << endl;
+ outstream << "" << cLocale.toString(job->getWallCoord().az().Degrees()) << "" << endl;
+ outstream << "" << cLocale.toString(job->getWallCoord().alt().Degrees()) << "" << endl;
}
else
outstream << "DawnDust" << endl;
@@ -3511,8 +3500,8 @@
else
{
outstream << "ADU" << endl;
- outstream << "" << job->getTargetADU() << "" << endl;
- outstream << "" << job->getTargetADUTolerance() << "" << endl;
+ outstream << "" << cLocale.toString(job->getTargetADU()) << "" << endl;
+ outstream << "" << cLocale.toString(job->getTargetADUTolerance()) << "" << endl;
}
outstream << "" << endl;
@@ -3682,11 +3671,12 @@
double exposureValue = exposureIN->value();
+ // Don't use the locale for exposure value in the capture file name, so that we get a "." as decimal separator
if (exposureValue == static_cast(exposureValue))
// Whole number
imagePrefix += QString::number(exposureIN->value(), 'd', 0) + QString("_secs");
- // Decimal
else
+ // Decimal
imagePrefix += QString::number(exposureIN->value(), 'f', 3) + QString("_secs");
}
if (ISOCheck->isChecked())
@@ -4052,9 +4042,9 @@
if (currentHA > meridianHours->value())
{
//NOTE: DO NOT make the follow sentence PLURAL as the value is in double
- appendLogText(i18n(
- "Current hour angle %1 hours exceeds meridian flip limit of %2 hours. Auto meridian flip is initiated.",
- QString::number(currentHA, 'f', 2), meridianHours->value()));
+ appendLogText(
+ i18n("Current hour angle %1 hours exceeds meridian flip limit of %2 hours. Auto meridian flip is initiated.",
+ QString("%L1").arg(currentHA, 0, 'f', 2), QString("%L1").arg(meridianHours->value(), 0, 'f', 2)));
meridianFlipStage = MF_INITIATED;
//KNotification::event(QLatin1String("MeridianFlipStarted"), i18n("Meridian flip started"));
@@ -4943,7 +4933,7 @@
nextExposure = qBound(exposureIN->minimum(), nextExposure, exposureIN->maximum());
appendLogText(i18n("Current ADU is %1 Next exposure is %2 seconds.", QString::number(currentADU, 'f', 0),
- QString::number(nextExposure, 'f', 3)));
+ QString("%L1").arg(nextExposure, 0, 'f', 3)));
calibrationStage = CAL_CALIBRATION;
activeJob->setExposure(nextExposure);
@@ -5453,6 +5443,7 @@
void Capture::setSettings(const QJsonObject &settings)
{
+ // FIXME: QComboBox signal "activated" does not trigger when setting text programmatically.
CCDCaptureCombo->setCurrentText(settings["camera"].toString());
FilterDevicesCombo->setCurrentText(settings["fw"].toString());
FilterPosCombo->setCurrentText(settings["filter"].toString());