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());