From 63eebd6e6e5dfe8b1be04617f5cf3822ff7aaf5a Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Tue, 11 Feb 2025 18:23:04 +0100 Subject: [PATCH 01/21] some rudimentary support for monetary columns --- Desktop/html/js/utils.js | 12 +++++++++++- Engine/jaspBase | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Desktop/html/js/utils.js b/Desktop/html/js/utils.js index 1e3ee1a096..45b126f013 100644 --- a/Desktop/html/js/utils.js +++ b/Desktop/html/js/utils.js @@ -129,7 +129,17 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote } } - if (isFinite(sf)) { + moneyStr = "monetary" + if(f.startsWith(moneyStr)) { + for (let rowNo = 0; rowNo < column.length; rowNo++) { + + var cell = column[rowNo] + var _content = cell.content + var _symbol = f.size() > moneyStr.size() ? f.substr(moneyStr.size()) : "€" + columnCells[rowNo] = { content: `${_symbol} ${_content}`, class: "monetary" } //should probably do better formatting then this though + } + } + else if (isFinite(sf)) { var upperLimit = 1e6 var minLSD = Infinity // right most position of the least significant digit diff --git a/Engine/jaspBase b/Engine/jaspBase index cf7c84b877..ebaef59530 160000 --- a/Engine/jaspBase +++ b/Engine/jaspBase @@ -1 +1 @@ -Subproject commit cf7c84b877e1af958dd0d5b228d9a81831002bdd +Subproject commit ebaef59530146c4f3cb1bf691c0dc89db7282ab9 From 5814d79a44364dab2dbe5fa136d624072cb3c21d Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 12 Feb 2025 16:57:43 +0100 Subject: [PATCH 02/21] add support for formatting via locale --- CommonData/columnutils.cpp | 16 +++ CommonData/columnutils.h | 8 ++ .../JASP/Widgets/FileMenu/PrefsUI.qml | 9 ++ Desktop/html/js/utils.js | 36 ++++-- Desktop/main.cpp | 3 +- Desktop/po/jaspDesktop-de.po | 2 +- Desktop/utilities/languagemodel.cpp | 59 ++++++++-- Desktop/utilities/languagemodel.h | 108 ++++++++++-------- Desktop/utilities/settings.cpp | 1 + Desktop/utilities/settings.h | 1 + 10 files changed, 178 insertions(+), 65 deletions(-) diff --git a/CommonData/columnutils.cpp b/CommonData/columnutils.cpp index b923589868..7cb3a6c7cb 100644 --- a/CommonData/columnutils.cpp +++ b/CommonData/columnutils.cpp @@ -16,6 +16,9 @@ using namespace std; using namespace boost::posix_time; using namespace boost; +std::locale ColumnUtils::_currentLocale = std::locale("en_US"); + + bool ColumnUtils::getIntValue(const string &value, int &intValue) { try @@ -161,6 +164,16 @@ bool ColumnUtils::convertVecToDouble(const stringvec & values, doublevec & doubl return true; } +locale ColumnUtils::currentLocale() +{ + return _currentLocale; +} + +void ColumnUtils::setCurrentLocale(const std::string &localeString) +{ + _currentLocale = std::locale(localeString); +} + std::string ColumnUtils::deEuropeaniseForImport(std::string value) { int dots = 0, @@ -224,6 +237,9 @@ std::string ColumnUtils::doubleToString(double dbl, int precision) if (dbl < std::numeric_limits::lowest()) return "-∞"; std::stringstream conv; //Use this instead of std::to_string to make sure there are no trailing zeroes (and to get full precision) + + conv.imbue(_currentLocale); + conv << std::setprecision(precision); conv << dbl; return conv.str(); diff --git a/CommonData/columnutils.h b/CommonData/columnutils.h index b32ae75fe5..dd2bb1980f 100644 --- a/CommonData/columnutils.h +++ b/CommonData/columnutils.h @@ -3,6 +3,7 @@ #include #include "utils.h" +#include class ColumnUtils { @@ -25,7 +26,14 @@ class ColumnUtils static bool convertVecToInt( const stringvec & values, intvec & intValues, intset & uniqueValues); static bool convertVecToDouble( const stringvec & values, doublevec & doubleValues); + + static std::locale currentLocale(); + static void setCurrentLocale(const std::string & localeString); + + private: + static std::locale _currentLocale; + static std::string _convertEscapedUnicodeToUTF8( std::string hex); }; diff --git a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml index bfa8e464ee..8673bf663e 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml @@ -194,6 +194,15 @@ ScrollView } + CheckBox + { + id: useSystemLocale + label: qsTr("Use system locale") + checked: languageModel.useSystemLocale + onCheckedChanged: languageModel.useSystemLocale = checked + toolTip: qsTr("Use system locale for display of numbers, dates, times and currency") + } + Text { diff --git a/Desktop/html/js/utils.js b/Desktop/html/js/utils.js index 45b126f013..542d17c55f 100644 --- a/Desktop/html/js/utils.js +++ b/Desktop/html/js/utils.js @@ -1,3 +1,13 @@ +function formatMoney(_currency='EUR', amount) { + const formatter = new Intl.NumberFormat(undefined, { + style: 'currency', + currency: _currency, + trailingZeroDisplay: 'stripIfInteger' + }); + + return formatter.format(amount) +} + function formatColumn(column, type, format, alignNumbers, combine, modelFootnotes, html = true, errorOnMixed = false) { /** * Prepares the columns of a table to the required format @@ -88,7 +98,10 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote let log10 = false; dp = parseInt(window.globSet.decimals); // NaN if not specified by user let fixDecimals = (typeof dp === 'number') && (dp % 1 === 0); + let currency = "" + var moneyStr = "monetary" + for (let i = 0; i < formats.length; i++) { let f = formats[i]; @@ -100,6 +113,19 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote p = f.substring(2); } } + + if(f.startsWith(moneyStr)) + { + if(f.length > moneyStr.length) + { + currency = f.substr(moneyStr.length) + if(currency.startsWith(":")) + currency = currency.substring(1) + } + + if(currency == "") + currency = "EUR" + } if (f.indexOf("dp:") != -1 && !fixDecimals) dp = f.substring(3); @@ -129,14 +155,10 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote } } - moneyStr = "monetary" - if(f.startsWith(moneyStr)) { + + if(currency != "") { for (let rowNo = 0; rowNo < column.length; rowNo++) { - - var cell = column[rowNo] - var _content = cell.content - var _symbol = f.size() > moneyStr.size() ? f.substr(moneyStr.size()) : "€" - columnCells[rowNo] = { content: `${_symbol} ${_content}`, class: "monetary" } //should probably do better formatting then this though + columnCells[rowNo] = { content: `${formatMoney(_currency=currency, column[rowNo].content)}`, class: "monetary" } //should probably do better formatting then this though } } else if (isFinite(sf)) { diff --git a/Desktop/main.cpp b/Desktop/main.cpp index 6ab2a84578..a61b2b1ce1 100644 --- a/Desktop/main.cpp +++ b/Desktop/main.cpp @@ -461,7 +461,8 @@ int main(int argc, char *argv[]) QmlUtils::configureQMLCacheDir(); #endif - QLocale::setDefault(QLocale(QLocale::English)); // make decimal points == . + if(!Settings::value(Settings::USE_SYSTEM_LOCALE).toBool()) + QLocale::setDefault(QLocale(QLocale::English)); // make decimal points == . //Now we convert all these strings in args back to an int and a char * array. //But to keep things easy, we are going to copy the old argv to avoid duplication (or messing up the executable name) diff --git a/Desktop/po/jaspDesktop-de.po b/Desktop/po/jaspDesktop-de.po index d4663d8e59..f550d24c68 100644 --- a/Desktop/po/jaspDesktop-de.po +++ b/Desktop/po/jaspDesktop-de.po @@ -4,7 +4,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Language: en_US\n" +"X-Language: de_DE\n" "X-Source-Language: American English\n" "X-Qt-Contexts: true\n" diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 2e96855af9..84601245b5 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -8,10 +8,12 @@ #include #include "results/resultsjsinterface.h" #include "modules/dynamicmodule.h" +#include "columnutils.h" LanguageModel * LanguageModel::_singleton = nullptr; -QLocale LanguageModel::_defaultLocale = QLocale(QLocale::English, QLocale::World); +QLocale LanguageModel::_defaultLocale = QLocale(QLocale::English, QLocale::UnitedStates); +// the bool indicates whether the language is considered "complete" or not QMap LanguageModel::LanguageInfo::_allowedLanguages = { { "en" , true }, @@ -62,19 +64,29 @@ LanguageModel::LanguageModel(QApplication *app, QQmlApplicationEngine *qml, QObj void LanguageModel::initialize() { - QString defaultLanguageCode = LanguageInfo::getLanguageCode(_defaultLocale); - LanguageInfo defaultLanguageInfo(_defaultLocale); + + QString defaultLanguageCode = LanguageInfo::getLanguageCode(_defaultLocale); + LanguageInfo defaultLanguageInfo = _defaultLocale; + _languages[defaultLanguageCode] = defaultLanguageInfo; findQmFiles(); - _currentLanguageCode = Settings::value(Settings::PREFERRED_LANGUAGE).toString(); - if (!LanguageInfo::isLanguageAllowed(_currentLanguageCode)) _currentLanguageCode = defaultLanguageCode; + _currentLanguageCode = Settings::value(Settings::PREFERRED_LANGUAGE).toString(); + _useSystemLocale = Settings::value(Settings::USE_SYSTEM_LOCALE).toBool(); + + + if (!LanguageInfo::isLanguageAllowed(_currentLanguageCode)) + _currentLanguageCode = defaultLanguageCode; if (_currentLanguageCode != defaultLanguageCode) { // Load all translated language files for specific language loadQmFilesForLanguage(_currentLanguageCode); + + if(!useSystemLocale()) + setDefaultLocaleFromCurrent(); + _qml->retranslate(); } @@ -128,9 +140,24 @@ void LanguageModel::setCurrentLanguage(QString language) if (_currentLanguageCode == LanguageInfo::getLanguageCode(_defaultLocale)) removeTranslators(); else loadQmFilesForLanguage(_currentLanguageCode); + refreshAll(); +} + +void LanguageModel::setDefaultLocaleFromCurrent() +{ + QLocale::setDefault(currentLocale()); + QString curLocStr = currentLocale().name(); + ColumnUtils::setCurrentLocale(fq(curLocStr)); +} + +void LanguageModel::refreshAll() +{ //prepare for language change emit aboutToChangeLanguage(); //asks all analyses to abort and to block refresh ResultsJsInterface::singleton()->setResultsLoaded(false); //So that javascript starts queueing any Js (such as title changed of an analysis) until the page is reloaded + + if(!useSystemLocale()) + setDefaultLocaleFromCurrent(); //On linux it somehow ignores the newer settings, so instead of pausing we kill the engines... https://github.com/jasp-stats/jasp-test-release/issues/1046 //But I do not know if it necessary, because the modules-translations aren't working. @@ -145,8 +172,8 @@ void LanguageModel::setCurrentLanguage(QString language) emit stopEngines(); _qml->retranslate(); - Settings::setValue(Settings::PREFERRED_LANGUAGE , _currentLanguageCode); - Settings::setValue(Settings::PREFERRED_COUNTRY, _languages[_currentLanguageCode].locale.country()); + Settings::setValue(Settings::PREFERRED_LANGUAGE , currentLanguageCode()); + Settings::setValue(Settings::PREFERRED_COUNTRY, currentLocale().country()); _shouldEmitLanguageChanged = true; ResultsJsInterface::singleton()->resetResults(); @@ -154,6 +181,19 @@ void LanguageModel::setCurrentLanguage(QString language) //resumeEngines() will be emitted in resultsPageLoaded } +void LanguageModel::setUseSystemLocale(bool useIt) +{ + if(_useSystemLocale == useIt) + return; + + _useSystemLocale = useIt; + + Settings::setValue(Settings::USE_SYSTEM_LOCALE , _useSystemLocale); + + emit useSystemLocaleChanged(); + refreshAll(); +} + void LanguageModel::resultsPageLoaded() { if(!_shouldEmitLanguageChanged) @@ -322,6 +362,11 @@ QString LanguageModel::currentLanguage() const return li.entryName; } +QLocale LanguageModel::currentLocale() const +{ + return _languages[_currentLanguageCode].locale; +} + bool LanguageModel::hasDefaultLanguage() const { const LanguageInfo & li = _languages[_currentLanguageCode]; diff --git a/Desktop/utilities/languagemodel.h b/Desktop/utilities/languagemodel.h index 543a44c7f1..82e1d5d56f 100644 --- a/Desktop/utilities/languagemodel.h +++ b/Desktop/utilities/languagemodel.h @@ -17,22 +17,24 @@ namespace Modules { class DynamicModule; } class LanguageModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(QString currentLanguage READ currentLanguage WRITE setCurrentLanguage NOTIFY currentLanguageChanged) + Q_PROPERTY(QString currentLanguage READ currentLanguage WRITE setCurrentLanguage NOTIFY currentLanguageChanged ) + Q_PROPERTY(QLocale currentLocale READ currentLocale NOTIFY currentLocaleChanged ) + Q_PROPERTY(bool useSystemLocale READ useSystemLocale WRITE setUseSystemLocale NOTIFY useSystemLocaleChanged ) struct LanguageInfo { - static QString getLanguageCode(const QLocale& locale); - static bool isLanguageAllowed(const QString& language) { return _allowedLanguages.contains(language); } - LanguageInfo(const QLocale& _locale = LanguageModel::_defaultLocale, const QString& _code = getLanguageCode(LanguageModel::_defaultLocale), const QString& _qmFilename = ""); - - static QMap _allowedLanguages; + + + static QString getLanguageCode(const QLocale& locale); + static bool isLanguageAllowed(const QString& language) { return _allowedLanguages.contains(language); } + static QMap _allowedLanguages; static QString _incompleteFlag; - QString code; // This is not necessary the same as locale.name(), especially with zh_Hans or zh_Hant - QString entryName; // Name used in the language dropdown - QLocale locale; - QVector qmFilenames; + QString code, // This is not necessary the same as locale.name(), especially with zh_Hans or zh_Hant + entryName; // Name used in the language dropdown + QLocale locale; + QVector qmFilenames; }; public: @@ -46,56 +48,64 @@ class LanguageModel : public QAbstractListModel LocalNameRole }; - explicit LanguageModel(QApplication *app = nullptr, QQmlApplicationEngine *qml = nullptr, QObject *parent = nullptr) ; - ~LanguageModel() override { _singleton = nullptr; } + explicit LanguageModel(QApplication *app = nullptr, QQmlApplicationEngine *qml = nullptr, QObject *parent = nullptr) ; + ~LanguageModel() override { _singleton = nullptr; } - int rowCount(const QModelIndex & = QModelIndex()) const override { return _languages.size(); } - int columnCount(const QModelIndex & = QModelIndex()) const override { return 1; } - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + void initialize(); + void refreshAll(); + + int rowCount(const QModelIndex & = QModelIndex()) const override { return _languages.size(); } + int columnCount(const QModelIndex & = QModelIndex()) const override { return 1; } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QHash roleNames() const override; + QHash roleNames() const override; - QString currentLanguageCode() const { return _currentLanguageCode; } - QString currentLanguage() const; - bool hasDefaultLanguage() const; + QString currentLanguageCode() const { return _currentLanguageCode; } + QString currentLanguage() const; + QLocale currentLocale() const; + bool hasDefaultLanguage() const; + bool useSystemLocale() const { return _useSystemLocale; } //This function (currentTranslationSuffix) should be made obsolete through the abolishment of all the _nl etc files: - static QString currentTranslationSuffix() { return _singleton->hasDefaultLanguage() ? "" : ("_" + _singleton->currentLanguageCode()); } - - void setApplicationEngine(QQmlApplicationEngine * ae) { _qml = ae; } - void initialize(); - + static QString currentTranslationSuffix() { return _singleton->hasDefaultLanguage() ? "" : ("_" + _singleton->currentLanguageCode()); } + void setApplicationEngine(QQmlApplicationEngine * ae) { _qml = ae; } + void setDefaultLocaleFromCurrent(); + public slots: - void setCurrentLanguage(QString language); - void loadModuleTranslationFiles(Modules::DynamicModule *dyn); - void resultsPageLoaded(); + void setCurrentLanguage(QString language); + void setUseSystemLocale(bool useIt); + void loadModuleTranslationFiles(Modules::DynamicModule *dyn); + void resultsPageLoaded(); signals: - void currentLanguageChanged(); - void aboutToChangeLanguage(); - void pauseEngines(bool unloadData = false); - void stopEngines(); - void resumeEngines(); + void currentLanguageChanged(); + void currentLocaleChanged(); + void useSystemLocaleChanged(); + void aboutToChangeLanguage(); + void pauseEngines(bool unloadData = false); + void stopEngines(); + void resumeEngines(); private: - static LanguageModel * _singleton; - static QLocale _defaultLocale; - - void findQmFiles(); - void loadQmFilesForLanguage(const QString& languageCode); - void loadQmFile(const QString& filename); - void removeTranslators(); - bool isValidLocaleName(const QString& filename, QLocale & loc, QString & languageCode); - - QApplication * _mApp = nullptr; - QTranslator * _mTranslator = nullptr; - QQmlApplicationEngine * _qml = nullptr; - QString _currentLanguageCode, - _qmLocation; - QMap _languages; - QVector _translators; - bool _shouldEmitLanguageChanged = false; + static LanguageModel * _singleton; + static QLocale _defaultLocale; + + void findQmFiles(); + void loadQmFilesForLanguage(const QString& languageCode); + void loadQmFile(const QString& filename); + void removeTranslators(); + bool isValidLocaleName(const QString& filename, QLocale & loc, QString & languageCode); + + QApplication * _mApp = nullptr; + QTranslator * _mTranslator = nullptr; + QQmlApplicationEngine * _qml = nullptr; + QString _currentLanguageCode, + _qmLocation; + QMap _languages; + QVector _translators; + bool _shouldEmitLanguageChanged = false, + _useSystemLocale; }; diff --git a/Desktop/utilities/settings.cpp b/Desktop/utilities/settings.cpp index 8846cedd66..e77eeacc93 100644 --- a/Desktop/utilities/settings.cpp +++ b/Desktop/utilities/settings.cpp @@ -39,6 +39,7 @@ const Settings::Setting Settings::Values[] = { {"userHasGitHubAccount", false}, {"preferredLanguage", "en"}, {"preferredCountry", QLocale::World}, + {"useSystemLocale", true}, {"themeName", "lightTheme"}, {"useNativeFileDialog", true}, {"disableAnimations", false}, diff --git a/Desktop/utilities/settings.h b/Desktop/utilities/settings.h index bc03f67eff..645930b39f 100644 --- a/Desktop/utilities/settings.h +++ b/Desktop/utilities/settings.h @@ -43,6 +43,7 @@ class Settings { USER_HAS_GITHUB_ACCOUNT, PREFERRED_LANGUAGE, PREFERRED_COUNTRY, + USE_SYSTEM_LOCALE, THEME_NAME, USE_NATIVE_FILE_DIALOG, DISABLE_ANIMATIONS, From 318f9560a7b7af12260ef5c78bf4354b859f8271 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 12 Feb 2025 17:44:55 +0100 Subject: [PATCH 03/21] numbers are translated through qt now, which avoids problems with strange language+territory codes however, once a Label is created it keeps the particular punctuation etc from the locale when it changed... --- CommonData/columnutils.cpp | 38 ++++++++++++++++++----------- CommonData/columnutils.h | 12 ++++----- Desktop/data/datasetpackage.cpp | 16 +++++++++++- Desktop/data/datasetpackage.h | 5 +++- Desktop/mainwindow.cpp | 2 ++ Desktop/utilities/languagemodel.cpp | 11 +++++++-- Desktop/utilities/languagemodel.h | 1 + 7 files changed, 61 insertions(+), 24 deletions(-) diff --git a/CommonData/columnutils.cpp b/CommonData/columnutils.cpp index 7cb3a6c7cb..0a500e7b9d 100644 --- a/CommonData/columnutils.cpp +++ b/CommonData/columnutils.cpp @@ -16,8 +16,6 @@ using namespace std; using namespace boost::posix_time; using namespace boost; -std::locale ColumnUtils::_currentLocale = std::locale("en_US"); - bool ColumnUtils::getIntValue(const string &value, int &intValue) { @@ -77,10 +75,21 @@ bool ColumnUtils::getDoubleValue(const string &value, double &doubleValue) try { - doubleValue = boost::lexical_cast(deEuropeaniseForImport(value)); + doubleValue = boost::lexical_cast((value)); return true; } - catch (...) {} + catch (...) // If it failed try to "deEuropeanise it" + { + try + { + doubleValue = boost::lexical_cast(deEuropeaniseForImport(value)); + return true; + } + catch (...) + { + + } + } return false; } @@ -164,15 +173,7 @@ bool ColumnUtils::convertVecToDouble(const stringvec & values, doublevec & doubl return true; } -locale ColumnUtils::currentLocale() -{ - return _currentLocale; -} -void ColumnUtils::setCurrentLocale(const std::string &localeString) -{ - _currentLocale = std::locale(localeString); -} std::string ColumnUtils::deEuropeaniseForImport(std::string value) { @@ -229,6 +230,14 @@ std::string ColumnUtils::doubleToStringMaxPrec(double dbl) return doubleToString(dbl, max_precision); } +ColumnUtils::doubleF ColumnUtils::_alternativeDoubleToString; + +void ColumnUtils::setAlternativeDoubleToString(doubleF newDoubleFunc) +{ + _alternativeDoubleToString = newDoubleFunc; +} + + std::string ColumnUtils::doubleToString(double dbl, int precision) { JASPTIMER_SCOPE(ColumnUtils::doubleToString); @@ -236,9 +245,10 @@ std::string ColumnUtils::doubleToString(double dbl, int precision) if (dbl > std::numeric_limits::max()) return "∞"; if (dbl < std::numeric_limits::lowest()) return "-∞"; - std::stringstream conv; //Use this instead of std::to_string to make sure there are no trailing zeroes (and to get full precision) + if(_alternativeDoubleToString) + return _alternativeDoubleToString(dbl, precision); //Use QString for translations - conv.imbue(_currentLocale); + std::stringstream conv; //Use this instead of std::to_string to make sure there are no trailing zeroes (and to get full precision) conv << std::setprecision(precision); conv << dbl; diff --git a/CommonData/columnutils.h b/CommonData/columnutils.h index dd2bb1980f..a276ddb0d5 100644 --- a/CommonData/columnutils.h +++ b/CommonData/columnutils.h @@ -8,6 +8,8 @@ class ColumnUtils { public: + typedef std::function doubleF; + friend class PreferencesModel; static bool getIntValue( const std::string & value, int & intValue); @@ -27,14 +29,12 @@ class ColumnUtils static bool convertVecToInt( const stringvec & values, intvec & intValues, intset & uniqueValues); static bool convertVecToDouble( const stringvec & values, doublevec & doubleValues); - static std::locale currentLocale(); - static void setCurrentLocale(const std::string & localeString); - + static void setAlternativeDoubleToString(doubleF newDoubleFunc); -private: - static std::locale _currentLocale; - static std::string _convertEscapedUnicodeToUTF8( std::string hex); +private: + static std::string _convertEscapedUnicodeToUTF8( std::string hex); + static doubleF _alternativeDoubleToString; }; #endif // COLUMNUTILS_H diff --git a/Desktop/data/datasetpackage.cpp b/Desktop/data/datasetpackage.cpp index 8ad20bfa67..06dc748919 100644 --- a/Desktop/data/datasetpackage.cpp +++ b/Desktop/data/datasetpackage.cpp @@ -672,7 +672,7 @@ bool DataSetPackage::setData(const QModelIndex &index, const QVariant &value, in { JASPTIMER_SCOPE(DataSetPackage::setData); - if(!index.isValid() || !_dataSet) return false; + if(_waitingForLanguageChange || !index.isValid() || !_dataSet) return false; DataSetBaseNode * node = indexPointerToNode(index); @@ -1248,6 +1248,20 @@ void DataSetPackage::resetFilterCounters() col->nonFilteredCountersReset(); } +void DataSetPackage::prepareForLanguageChange() +{ + _waitingForLanguageChange = true; //Dont accept changes while the interface changes +} + +void DataSetPackage::languageChangeDone() +{ + _waitingForLanguageChange = false; //Dont accept changes while the interface changes + + refresh(); +} + + + void DataSetPackage::resetAllFilters() { for(Column * col : _dataSet->columns()) diff --git a/Desktop/data/datasetpackage.h b/Desktop/data/datasetpackage.h index 7a4aad782b..4f99031de6 100644 --- a/Desktop/data/datasetpackage.h +++ b/Desktop/data/datasetpackage.h @@ -333,6 +333,8 @@ public slots: void checkDataSetForUpdates(); void delayedRefresh(); void resetFilterCounters(); + void prepareForLanguageChange(); + void languageChangeDone(); private: bool isThisTheSameThreadAsEngineSync(); @@ -366,7 +368,8 @@ public slots: _analysesHTMLReady = false, _filterShouldRunInit = false, _dataMode = false, - _manualEdits = false; + _manualEdits = false, + _waitingForLanguageChange = false; Json::Value _analysesData, _database = Json::nullValue; diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 3fffb5cafe..65f0a16faa 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -524,6 +524,8 @@ void MainWindow::makeConnections() connect(_languageModel, &LanguageModel::currentLanguageChanged, _fileMenu, &FileMenu::refresh ); connect(_languageModel, &LanguageModel::aboutToChangeLanguage, _analyses, &Analyses::prepareForLanguageChange ); + connect(_languageModel, &LanguageModel::aboutToChangeLanguage, _package, &DataSetPackage::prepareForLanguageChange ); + connect(_languageModel, &LanguageModel::languageChangeDone, _package, &DataSetPackage::languageChangeDone ); connect(_languageModel, &LanguageModel::currentLanguageChanged, _analyses, &Analyses::languageChangedHandler, Qt::QueuedConnection); connect(_languageModel, &LanguageModel::currentLanguageChanged, _helpModel, &HelpModel::generateJavascript, Qt::QueuedConnection); connect(_languageModel, &LanguageModel::currentLanguageChanged, this, &MainWindow::contactTextChanged, Qt::QueuedConnection); //Probably not necessary but we can check once there actually are translations diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 84601245b5..14c07eba98 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -146,8 +146,13 @@ void LanguageModel::setCurrentLanguage(QString language) void LanguageModel::setDefaultLocaleFromCurrent() { QLocale::setDefault(currentLocale()); - QString curLocStr = currentLocale().name(); - ColumnUtils::setCurrentLocale(fq(curLocStr)); + + static ColumnUtils::doubleF altFunc = [&](double dbl, int precision) + { + return fq(currentLocale().toString(dbl, 'g', precision)); + }; + + ColumnUtils::setAlternativeDoubleToString(altFunc); } void LanguageModel::refreshAll() @@ -179,6 +184,8 @@ void LanguageModel::refreshAll() ResultsJsInterface::singleton()->resetResults(); //resumeEngines() will be emitted in resultsPageLoaded + + emit languageChangeDone(); } void LanguageModel::setUseSystemLocale(bool useIt) diff --git a/Desktop/utilities/languagemodel.h b/Desktop/utilities/languagemodel.h index 82e1d5d56f..44e1045b01 100644 --- a/Desktop/utilities/languagemodel.h +++ b/Desktop/utilities/languagemodel.h @@ -83,6 +83,7 @@ public slots: void currentLocaleChanged(); void useSystemLocaleChanged(); void aboutToChangeLanguage(); + void languageChangeDone(); void pauseEngines(bool unloadData = false); void stopEngines(); void resumeEngines(); From c2f48a832e895a6350895e6e1c80b2916f5f0020 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 12 Feb 2025 18:11:19 +0100 Subject: [PATCH 04/21] update double labels after language change --- CommonData/column.cpp | 12 ++++++++++++ CommonData/column.h | 5 +++-- CommonData/dataset.cpp | 6 ++++++ CommonData/dataset.h | 3 ++- CommonData/label.cpp | 15 +++++++++++++++ CommonData/label.h | 2 ++ Desktop/data/datasetpackage.cpp | 3 +++ 7 files changed, 43 insertions(+), 3 deletions(-) diff --git a/CommonData/column.cpp b/CommonData/column.cpp index b7ce5d22b5..3281fc97b4 100644 --- a/CommonData/column.cpp +++ b/CommonData/column.cpp @@ -460,6 +460,18 @@ stringset Column::mergeOldMissingDataMap(const Json::Value &missingData) return foundEmpty; } +void Column::updateLabelsPostLocaleChange() +{ + labelsTempReset(); + + beginBatchedLabelsDB(); + + for(Label * label : _labels) + label->updateDoubleLabelsPostLocaleChange(); + + endBatchedLabelsDB(true); +} + columnType Column::setValues(const stringvec & values, const stringvec & labels, int thresholdScale, bool * aChange) { JASPTIMER_SCOPE(Column::setValues); diff --git a/CommonData/column.h b/CommonData/column.h index 0991921b8b..3b0f516a6f 100644 --- a/CommonData/column.h +++ b/CommonData/column.h @@ -226,10 +226,11 @@ class Column : public DataSetBaseNode bool isEmptyValue( const std::string & val) const; bool isEmptyValue( const double val) const; - size_t getMaximumWidthInCharactersIncludingShadow(); - size_t getMaximumWidthInCharacters(bool shortenAndFancyEmptyValue, bool valuesPlease, size_t extraPad = 4); ///< Tries to take into consideration that utf-8 can have more characters than codepoints and compensates for it + size_t getMaximumWidthInCharactersIncludingShadow(); + size_t getMaximumWidthInCharacters(bool shortenAndFancyEmptyValue, bool valuesPlease, size_t extraPad = 4); ///< Tries to take into consideration that utf-8 can have more characters than codepoints and compensates for it columnType resetValues(int thresholdScale); ///< "Reimport" the values it already has with a possibly different threshold of values stringset mergeOldMissingDataMap(const Json::Value & missingData); ///< <0.19 JASP collected the removed empty values values in a map in a json object... We need to be able to read at least 0.18.3 so here this function that absorbs such a map and adds any required labels. It does not add the empty values itself though! + void updateLabelsPostLocaleChange(); static void setAutoSortByValuesByDefault(bool autoSort); static bool autoSortByValuesByDefault(); diff --git a/CommonData/dataset.cpp b/CommonData/dataset.cpp index 7ad3f7ed35..0ed8cb1af7 100644 --- a/CommonData/dataset.cpp +++ b/CommonData/dataset.cpp @@ -587,6 +587,12 @@ void DataSet::setDescription(const std::string &desc) dbUpdate(); } +void DataSet::updateLabelsPostLocaleChange() +{ + for(Column * column : _columns) + column->updateLabelsPostLocaleChange(); +} + DatabaseInterface &DataSet::db() { return *DatabaseInterface::singleton(); diff --git a/CommonData/dataset.h b/CommonData/dataset.h index 4c986dca89..47a17af128 100644 --- a/CommonData/dataset.h +++ b/CommonData/dataset.h @@ -85,7 +85,8 @@ class DataSet : public DataSetBaseNode void setWorkspaceEmptyValues( const stringset& values); const std::string & description() const { return _description; } void setDescription( const std::string& desc); - + void updateLabelsPostLocaleChange(); + private: void upgradeTo019(const Json::Value & emptyVals); void setEmptyValuesJsonOldStuff( const Json::Value & emptyValues); diff --git a/CommonData/label.cpp b/CommonData/label.cpp index 17c43145f7..346481a80c 100644 --- a/CommonData/label.cpp +++ b/CommonData/label.cpp @@ -2,6 +2,7 @@ #include "column.h" #include #include "timers.h" +#include "columnutils.h" #include "databaseinterface.h" const int Label::DOUBLE_LABEL_VALUE = -1; @@ -103,6 +104,20 @@ void Label::setInformation(Column * column, int id, int order, const std::string _originalValue = originalValue; } +void Label::updateDoubleLabelsPostLocaleChange() +{ + if(_originalValue.isDouble() && originalValueAsString() != _label) + { + //Maybe they really arent the same, but its also possible a languagechange just changed the way the decimal separator is written... + double labelDouble; + if(ColumnUtils::getDoubleValue(_label, labelDouble) && ColumnUtils::doubleToString(labelDouble) == originalValueAsString()) + { + _label = originalValueAsString(); + dbUpdate(); + } + } +} + Json::Value Label::serialize() const { Json::Value json(Json::objectValue); diff --git a/CommonData/label.h b/CommonData/label.h index db334ca5bd..d7a38a565d 100644 --- a/CommonData/label.h +++ b/CommonData/label.h @@ -61,6 +61,8 @@ class Label : public DataSetBaseNode bool setDescription( const std::string & description); bool setFilterAllows( bool allowFilter); void setInformation(Column * column, int id, int order, const std::string &label, int value, bool filterAllows, const std::string & description, const Json::Value & originalValue); + + void updateDoubleLabelsPostLocaleChange(); Json::Value serialize() const; diff --git a/Desktop/data/datasetpackage.cpp b/Desktop/data/datasetpackage.cpp index 06dc748919..aecdb5172c 100644 --- a/Desktop/data/datasetpackage.cpp +++ b/Desktop/data/datasetpackage.cpp @@ -1257,6 +1257,9 @@ void DataSetPackage::languageChangeDone() { _waitingForLanguageChange = false; //Dont accept changes while the interface changes + if(_dataSet) + _dataSet->updateLabelsPostLocaleChange(); + refresh(); } From 74449bdfe3e831a22f5e1371b48d1abd729ef589 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 12 Feb 2025 18:13:00 +0100 Subject: [PATCH 05/21] mention something about restarting jasp --- Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml index 8673bf663e..15810788d6 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml @@ -185,7 +185,7 @@ ScrollView DropDown { id: languages - label: qsTr("Choose language ") + label: qsTr("Choose language") source: languageModel startValue: languageModel.currentLanguage onValueChanged: languageModel.currentLanguage = value @@ -200,7 +200,7 @@ ScrollView label: qsTr("Use system locale") checked: languageModel.useSystemLocale onCheckedChanged: languageModel.useSystemLocale = checked - toolTip: qsTr("Use system locale for display of numbers, dates, times and currency") + toolTip: qsTr("Use system locale for display of numbers, dates, times and currency. Might require a restart of JASP to be effective.") } From 9e8db8ad9397e7db1c7c2f0c47f9e23af3314c62 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 12 Feb 2025 19:15:56 +0100 Subject: [PATCH 06/21] fighting with some qml components... --- CommonData/columnutils.cpp | 1 + CommonData/columnutils.h | 30 +++++---- Desktop/results/ploteditoraxismodel.cpp | 8 ++- Desktop/utilities/languagemodel.cpp | 1 + .../controls/jaspdoublevalidator.cpp | 11 +++- QMLComponents/controls/textinputbase.cpp | 64 +++++++++++++++++-- QMLComponents/controls/textinputbase.h | 9 +-- 7 files changed, 94 insertions(+), 30 deletions(-) diff --git a/CommonData/columnutils.cpp b/CommonData/columnutils.cpp index 0a500e7b9d..519a2230ce 100644 --- a/CommonData/columnutils.cpp +++ b/CommonData/columnutils.cpp @@ -16,6 +16,7 @@ using namespace std; using namespace boost::posix_time; using namespace boost; +std::string ColumnUtils::_decimalPoint = "."; bool ColumnUtils::getIntValue(const string &value, int &intValue) { diff --git a/CommonData/columnutils.h b/CommonData/columnutils.h index a276ddb0d5..8e17de1cb5 100644 --- a/CommonData/columnutils.h +++ b/CommonData/columnutils.h @@ -12,29 +12,31 @@ class ColumnUtils friend class PreferencesModel; - static bool getIntValue( const std::string & value, int & intValue); - static bool getIntValue( const double & value, int & intValue); - static bool getDoubleValue( const std::string & value, double & doubleValue); - static doubleset getDoubleValues(const stringset & values, bool stripNAN = true); + static bool getIntValue( const std::string & value, int & intValue); + static bool getIntValue( const double & value, int & intValue); + static bool getDoubleValue( const std::string & value, double & doubleValue); + static doubleset getDoubleValues(const stringset & values, bool stripNAN = true); - static bool isIntValue( const std::string & value); - static bool isDoubleValue( const std::string & value); + static bool isIntValue( const std::string & value); + static bool isDoubleValue( const std::string & value); - static void convertEscapedUnicodeToUTF8( std::string & inputStr); - static std::string deEuropeaniseForImport( std::string value); //Convert a string to a double with a dot for a separator + static void convertEscapedUnicodeToUTF8( std::string & inputStr); + static std::string deEuropeaniseForImport( std::string value); //Convert a string to a double with a dot for a separator - static std::string doubleToString( double dbl, int precision = 10); - static std::string doubleToStringMaxPrec( double dbl); + static std::string doubleToString( double dbl, int precision = 10); + static std::string doubleToStringMaxPrec( double dbl); - static bool convertVecToInt( const stringvec & values, intvec & intValues, intset & uniqueValues); - static bool convertVecToDouble( const stringvec & values, doublevec & doubleValues); - - static void setAlternativeDoubleToString(doubleF newDoubleFunc); + static bool convertVecToInt( const stringvec & values, intvec & intValues, intset & uniqueValues); + static bool convertVecToDouble( const stringvec & values, doublevec & doubleValues); + static void setAlternativeDoubleToString(doubleF newDoubleFunc); + static const std::string & decimalPoint() { return _decimalPoint; } + static void setDecimalPoint(const std::string & p) { _decimalPoint = p;} private: static std::string _convertEscapedUnicodeToUTF8( std::string hex); static doubleF _alternativeDoubleToString; + static std::string _decimalPoint; }; #endif // COLUMNUTILS_H diff --git a/Desktop/results/ploteditoraxismodel.cpp b/Desktop/results/ploteditoraxismodel.cpp index 67f708992f..eebb489f1e 100644 --- a/Desktop/results/ploteditoraxismodel.cpp +++ b/Desktop/results/ploteditoraxismodel.cpp @@ -3,6 +3,7 @@ #include "utilities/qutils.h" #include "jsonutilities.h" #include "log.h" +#include "columnutils.h" #include "utilities/messageforwarder.h" #include "utils.h" @@ -212,9 +213,10 @@ bool AxisModel::setData(const QModelIndex &index, const QVariant &value, int) return false; } - QString label = _labels[entry]; - bool labelIsDouble = false; - double labelDouble = label.toDouble(&labelIsDouble); + QString label = _labels[entry]; + double labelDouble; + bool labelIsDouble = ColumnUtils::getDoubleValue(fq(label), labelDouble); + QModelIndex index2 = index; if (labelIsDouble && (Utils::isEqual(labelDouble, oldBreak))) diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 14c07eba98..98398bb84d 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -153,6 +153,7 @@ void LanguageModel::setDefaultLocaleFromCurrent() }; ColumnUtils::setAlternativeDoubleToString(altFunc); + ColumnUtils::setDecimalPoint(fq(currentLocale().decimalPoint())); } void LanguageModel::refreshAll() diff --git a/QMLComponents/controls/jaspdoublevalidator.cpp b/QMLComponents/controls/jaspdoublevalidator.cpp index b821bf98de..681812723a 100644 --- a/QMLComponents/controls/jaspdoublevalidator.cpp +++ b/QMLComponents/controls/jaspdoublevalidator.cpp @@ -18,6 +18,7 @@ #include "jaspdoublevalidator.h" #include +#include "columnutils.h" QValidator::State JASPDoubleValidator::validate(QString& s, int& pos) const @@ -31,8 +32,9 @@ QValidator::State JASPDoubleValidator::validate(QString& s, int& pos) const if (s.startsWith("-") && bottom() >= 0) return QValidator::Invalid; + // check length of decimal places - QString point = locale().decimalPoint(); + QString point = tq(ColumnUtils::decimalPoint()); int indexPoint = s.indexOf(point); if (indexPoint != -1) @@ -43,9 +45,12 @@ QValidator::State JASPDoubleValidator::validate(QString& s, int& pos) const if (lengthDecimals > decimals()) return QValidator::Invalid; } + // check range of value - bool isNumber; - double value = locale().toDouble(s, &isNumber); + double value; + bool isNumber = ColumnUtils::getDoubleValue(fq(s), value); + + if (!isNumber) { if (s.length() == 1 && s[0] == point) diff --git a/QMLComponents/controls/textinputbase.cpp b/QMLComponents/controls/textinputbase.cpp index 6b6cda622f..0e9177f362 100644 --- a/QMLComponents/controls/textinputbase.cpp +++ b/QMLComponents/controls/textinputbase.cpp @@ -18,6 +18,7 @@ #include "textinputbase.h" #include "analysisform.h" +#include "columnutils.h" using namespace std; @@ -212,8 +213,12 @@ void TextInputBase::setUp() void TextInputBase::setDisplayValue() { - if(property("displayValue") != _value) - setProperty("displayValue", _value); + QString showThis = _value.toString(); + if(_value.typeId() == QMetaType::Type::Double) + showThis = tq(ColumnUtils::doubleToString(_value.toDouble())); + + if(property("displayValue") != showThis) + setProperty("displayValue", showThis); } void TextInputBase::rScriptDoneHandler(const QString &result) @@ -229,7 +234,8 @@ void TextInputBase::rScriptDoneHandler(const QString &result) bool succes = true; for (const QString& valStr : results) { - double val = valStr.toDouble(&succes); + double val; + succes = ColumnUtils::getDoubleValue(fq(valStr), val); if (!succes) { @@ -277,6 +283,33 @@ QString TextInputBase::friendlyName() const } } +QVariant TextInputBase::defaultValue() const +{ + switch(_defaultValue.typeId()) + { + case QMetaType::Double: + return tq(ColumnUtils::doubleToString(_defaultValue.toDouble())); + + default: + return _defaultValue; + } +} + +QVariant TextInputBase::value() const +{ + QVariant showThis = _value.isNull() ? _defaultValue : _value; + + switch(showThis.typeId()) + { + case QMetaType::Double: + return tq(ColumnUtils::doubleToString(showThis.toDouble())); + + default: + return showThis; + } + +} + void TextInputBase::checkIfColumnIsFreeOrMine() { QString val = _value.toString(); @@ -352,8 +385,10 @@ Json::Value TextInputBase::_getJsonValue(const QVariant& value) const } else { - double value = chunk.toDouble(&ok); - if (ok) values.append(value); + double valueDbl; + ok = ColumnUtils::getDoubleValue(fq(chunk), valueDbl); + if (ok) + values.append(valueDbl); } } return values; @@ -367,8 +402,12 @@ void TextInputBase::valueChangedSlot() setValue(property("displayValue")); } -void TextInputBase::setValue(const QVariant &value) +void TextInputBase::setValue(QVariant value) { + double valueDbl; + if(ColumnUtils::getDoubleValue(fq(value.toString()), valueDbl)) + value = valueDbl; + bool hasChanged = _value != value; _value = value; @@ -386,6 +425,19 @@ void TextInputBase::setValue(const QVariant &value) } } +void TextInputBase::setDefaultValue(QVariant value) +{ + double valueDbl; + if(ColumnUtils::getDoubleValue(fq(value.toString()), valueDbl)) + value = valueDbl; + + bool hasChanged = _defaultValue != value; + _defaultValue = value; + + if(hasChanged) + emit defaultValueChanged(); +} + void TextInputBase::_setBoundValue() { if (_inputType == TextInputType::FormulaType || _inputType == TextInputType::FormulaArrayType) diff --git a/QMLComponents/controls/textinputbase.h b/QMLComponents/controls/textinputbase.h index 8e5bc18b81..79a9f96989 100644 --- a/QMLComponents/controls/textinputbase.h +++ b/QMLComponents/controls/textinputbase.h @@ -48,8 +48,8 @@ class TextInputBase : public JASPControl, public BoundControlBase TextInputType inputType() { return _inputType; } QString friendlyName() const override; bool hasScriptError() const { return _hasScriptError; } - QVariant defaultValue() const { return _defaultValue; } - QVariant value() const { return _value.isNull() ? _defaultValue : _value; } // Sometimes the value is asked before the control is setup, so in this case give the default value + QVariant defaultValue() const; + QVariant value() const; // Sometimes the value is asked before the control is setup, so in this case give the default value const QString &label() const { return _label; } const QString &afterLabel() const { return _afterLabel; } @@ -67,10 +67,11 @@ class TextInputBase : public JASPControl, public BoundControlBase public slots: GENERIC_SET_FUNCTION(HasScriptError, _hasScriptError, hasScriptErrorChanged, bool ) - GENERIC_SET_FUNCTION(DefaultValue, _defaultValue, defaultValueChanged, QVariant ) + GENERIC_SET_FUNCTION(Label, _label, labelChanged, QString ) GENERIC_SET_FUNCTION(AfterLabel, _afterLabel, afterLabelChanged, QString ) - void setValue(const QVariant &value); + void setValue( QVariant value); + void setDefaultValue( QVariant value); private slots: void valueChangedSlot(); From 378d7ccade679f33737967222035408173f7608e Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Tue, 18 Feb 2025 19:01:32 +0100 Subject: [PATCH 07/21] attempt to make all qml use localized doubles etc --- CommonData/columnutils.cpp | 4 +- CommonData/columnutils.h | 6 +- Desktop/results/ploteditoraxismodel.cpp | 2 +- Desktop/utilities/languagemodel.cpp | 1 + QMLComponents/analysisform.cpp | 1 + .../components/JASP/Controls/DoubleField.qml | 9 -- .../components/JASP/Controls/IntegerField.qml | 9 -- .../controls/jaspdoublevalidator.cpp | 15 ++- QMLComponents/controls/textinputbase.cpp | 126 +++++++++++++----- QMLComponents/controls/textinputbase.h | 2 +- .../models/listmodelfiltereddataentry.cpp | 2 +- QMLComponents/utilities/qutils.cpp | 70 +++++++++- QMLComponents/utilities/qutils.h | 23 +++- 13 files changed, 204 insertions(+), 66 deletions(-) diff --git a/CommonData/columnutils.cpp b/CommonData/columnutils.cpp index 519a2230ce..734d65bc06 100644 --- a/CommonData/columnutils.cpp +++ b/CommonData/columnutils.cpp @@ -17,6 +17,8 @@ using namespace boost::posix_time; using namespace boost; std::string ColumnUtils::_decimalPoint = "."; +std::string ColumnUtils::_currentQLocaleId = "C"; + bool ColumnUtils::getIntValue(const string &value, int &intValue) { @@ -67,7 +69,7 @@ bool ColumnUtils::getIntValue(const double &value, int &intValue) bool ColumnUtils::getDoubleValue(const string &value, double &doubleValue) { doubleValue = EmptyValues::missingValueDouble; - + if(value == "∞" || value == "-∞") { doubleValue = std::numeric_limits::infinity() * (value == "-∞" ? -1 : 1); diff --git a/CommonData/columnutils.h b/CommonData/columnutils.h index 8e17de1cb5..bb2b47f3e6 100644 --- a/CommonData/columnutils.h +++ b/CommonData/columnutils.h @@ -33,10 +33,14 @@ class ColumnUtils static const std::string & decimalPoint() { return _decimalPoint; } static void setDecimalPoint(const std::string & p) { _decimalPoint = p;} + static const std::string & currentQLocaleId() { return _currentQLocaleId; } + static void setCurrentQLocaleId(const std::string & p) { _currentQLocaleId = p;} + private: static std::string _convertEscapedUnicodeToUTF8( std::string hex); static doubleF _alternativeDoubleToString; - static std::string _decimalPoint; + static std::string _decimalPoint, + _currentQLocaleId; }; #endif // COLUMNUTILS_H diff --git a/Desktop/results/ploteditoraxismodel.cpp b/Desktop/results/ploteditoraxismodel.cpp index eebb489f1e..95c50ce3c1 100644 --- a/Desktop/results/ploteditoraxismodel.cpp +++ b/Desktop/results/ploteditoraxismodel.cpp @@ -215,7 +215,7 @@ bool AxisModel::setData(const QModelIndex &index, const QVariant &value, int) QString label = _labels[entry]; double labelDouble; - bool labelIsDouble = ColumnUtils::getDoubleValue(fq(label), labelDouble); + bool labelIsDouble = QColumnUtils::getDoubleValue((label), labelDouble); QModelIndex index2 = index; diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 98398bb84d..16b951c788 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -154,6 +154,7 @@ void LanguageModel::setDefaultLocaleFromCurrent() ColumnUtils::setAlternativeDoubleToString(altFunc); ColumnUtils::setDecimalPoint(fq(currentLocale().decimalPoint())); + ColumnUtils::setCurrentQLocaleId(fq(currentLocale().bcp47Name())); } void LanguageModel::refreshAll() diff --git a/QMLComponents/analysisform.cpp b/QMLComponents/analysisform.cpp index e6377bbb4c..88a657a755 100644 --- a/QMLComponents/analysisform.cpp +++ b/QMLComponents/analysisform.cpp @@ -386,6 +386,7 @@ void AnalysisForm::bindTo(const Json::Value & defaultOptions) if (boundControl) { std::string name = control->name().toStdString(); + if (defaultOptions.isMember(name)) optionValue = defaultOptions[name]; diff --git a/QMLComponents/components/JASP/Controls/DoubleField.qml b/QMLComponents/components/JASP/Controls/DoubleField.qml index 4b997e50f5..f621ec2ac3 100644 --- a/QMLComponents/components/JASP/Controls/DoubleField.qml +++ b/QMLComponents/components/JASP/Controls/DoubleField.qml @@ -23,7 +23,6 @@ TextField { id: doubleField defaultValue: 0 - property var _prevDefaultValue: 0 property alias doubleValidator: doubleValidator property bool negativeValues: false property double min: negativeValues ? -Infinity : 0 @@ -34,12 +33,4 @@ TextField inputType: "number" validator: JASPDoubleValidator { id: doubleValidator; bottom: min; top: max ; decimals: doubleField.decimals; notation: DoubleValidator.StandardNotation } fieldWidth: jaspTheme.numericFieldWidth - - onDefaultValueChanged: - { - if (_prevDefaultValue == value) - value = defaultValue - - _prevDefaultValue = defaultValue; - } } diff --git a/QMLComponents/components/JASP/Controls/IntegerField.qml b/QMLComponents/components/JASP/Controls/IntegerField.qml index 9c32d7269d..2f4bb04db6 100644 --- a/QMLComponents/components/JASP/Controls/IntegerField.qml +++ b/QMLComponents/components/JASP/Controls/IntegerField.qml @@ -23,7 +23,6 @@ TextField { id: textField defaultValue: 0 - property var _prevDefaultValue: 0 property bool negativeValues: false property int min: negativeValues ? -2147483647 : 0 // 2^32 - 1 property int max: 2147483647 @@ -34,12 +33,4 @@ TextField validator: JASPDoubleValidator { id: intValidator; bottom: min; top: max; decimals: 0 } cursorShape: Qt.IBeamCursor fieldWidth: jaspTheme.numericFieldWidth - - onDefaultValueChanged: - { - if (_prevDefaultValue == value) - value = defaultValue - - _prevDefaultValue = defaultValue; - } } diff --git a/QMLComponents/controls/jaspdoublevalidator.cpp b/QMLComponents/controls/jaspdoublevalidator.cpp index 681812723a..987ad12870 100644 --- a/QMLComponents/controls/jaspdoublevalidator.cpp +++ b/QMLComponents/controls/jaspdoublevalidator.cpp @@ -48,18 +48,21 @@ QValidator::State JASPDoubleValidator::validate(QString& s, int& pos) const // check range of value double value; - bool isNumber = ColumnUtils::getDoubleValue(fq(s), value); + bool isNumber = QColumnUtils::getDoubleValue(s, value); if (!isNumber) { - if (s.length() == 1 && s[0] == point) + if(!isNumber) { - isNumber = true; - value = 0; + if (s.length() == 1 && s[0] == point) + { + isNumber = true; + value = 0; + } + else + return QValidator::Invalid; } - else - return QValidator::Invalid; } bool isMaxExclusive = _inclusive == JASPControl::Inclusive::None || _inclusive == JASPControl::Inclusive::MinOnly; diff --git a/QMLComponents/controls/textinputbase.cpp b/QMLComponents/controls/textinputbase.cpp index 0e9177f362..31ea0524f5 100644 --- a/QMLComponents/controls/textinputbase.cpp +++ b/QMLComponents/controls/textinputbase.cpp @@ -72,19 +72,26 @@ void TextInputBase::bindTo(const Json::Value& value) switch (_inputType) { case TextInputType::IntegerInputType: - if (value.isNumeric()) _value = value.asInt(); - else if (value.isString()) _value = std::stoi(value.asString()); + int intVal; + if (value.isNumeric()) + _value = value.asInt(); + + else if (value.isString() && QColumnUtils::getIntValue(tq(value.asString()), intVal)) + _value = intVal; + break; + case TextInputType::NumberInputType: case TextInputType::PercentIntputType: { double dblVal = 0; - if (value.isNumeric()) dblVal = value.asDouble(); - else if (value.isString()) dblVal = std::stod(value.asString()); - if (_inputType == TextInputType::PercentIntputType) - _value = _getPercentValue(dblVal); - else - _value = dblVal; + if (value.isNumeric()) + dblVal = value.asDouble(); + else if (value.isString() && QColumnUtils::getDoubleValue(tq(value.asString()), dblVal)) + + + + _value = _inputType == TextInputType::PercentIntputType ? _getPercentValue(dblVal): dblVal; break; } @@ -94,7 +101,8 @@ void TextInputBase::bindTo(const Json::Value& value) if (value.isArray()) { for (const Json::Value& oneValue : value) - if (oneValue.isNumeric()) arrayVal.push_back(oneValue.asInt()); + if (oneValue.isNumeric()) + arrayVal.push_back(oneValue.asInt()); } _value = _getIntegerArrayValue(arrayVal); break; @@ -105,7 +113,8 @@ void TextInputBase::bindTo(const Json::Value& value) if (value.isArray()) { for (const Json::Value& oneValue : value) - if (oneValue.isNumeric()) arrayVal.push_back(oneValue.asDouble()); + if (oneValue.isNumeric()) + arrayVal.push_back(oneValue.asDouble()); } _value = _getDoubleArrayValue(arrayVal); break; @@ -114,7 +123,8 @@ void TextInputBase::bindTo(const Json::Value& value) case TextInputType::FormulaArrayType: { QString strValue; - if (value.isString()) strValue = tq(value.asString()); + if (value.isString()) + strValue = tq(value.asString()); _value = strValue; setIsRCode(); @@ -127,31 +137,37 @@ void TextInputBase::bindTo(const Json::Value& value) } case TextInputType::ComputedColumnType: { - if (value.isString()) _value = tq(value.asString()); + if (value.isString()) + _value = tq(value.asString()); + setIsColumn(true); checkIfColumnIsFreeOrMine(); break; } case TextInputType::CheckColumnFreeOrMineType: { - if (value.isString()) _value = tq(value.asString()); + if (value.isString()) + _value = tq(value.asString()); + checkIfColumnIsFreeOrMine(); break; } case TextInputType::AddColumnType: { - if (value.isString()) _value = tq(value.asString()); + if (value.isString()) + _value = tq(value.asString()); + columnType colType = static_cast(property("columnType").toInt()); setIsColumn(false, colType); checkIfColumnIsFreeOrMine(); break; } + default: - { - if (value.isString()) _value = tq(value.asString()); + if (value.isString()) + _value = tq(value.asString()); break; } - } setDisplayValue(); emit valueChanged(); @@ -213,9 +229,24 @@ void TextInputBase::setUp() void TextInputBase::setDisplayValue() { + Json::Value valueJson = _getJsonValue(_value); + QString showThis = _value.toString(); - if(_value.typeId() == QMetaType::Type::Double) - showThis = tq(ColumnUtils::doubleToString(_value.toDouble())); + switch(valueJson.type()) + { + case Json::intValue: + showThis = QColumnUtils::currentQLocale().toString(valueJson.asInt());; + break; + + case Json::realValue: + showThis = QColumnUtils::doubleToString(valueJson.asDouble()); + break; + } + + + + if(showThis == "95.0") + 1+2; if(property("displayValue") != showThis) setProperty("displayValue", showThis); @@ -235,7 +266,7 @@ void TextInputBase::rScriptDoneHandler(const QString &result) for (const QString& valStr : results) { double val; - succes = ColumnUtils::getDoubleValue(fq(valStr), val); + succes = QColumnUtils::getDoubleValue(valStr, val); if (!succes) { @@ -302,7 +333,7 @@ QVariant TextInputBase::value() const switch(showThis.typeId()) { case QMetaType::Double: - return tq(ColumnUtils::doubleToString(showThis.toDouble())); + return QColumnUtils::doubleToString(showThis.toDouble()); default: return showThis; @@ -360,52 +391,72 @@ bool TextInputBase::_formulaResultInBounds(double result) return inBounds; } -Json::Value TextInputBase::_getJsonValue(const QVariant& value) const +Json::Value TextInputBase::_getJsonValue(QVariant value) const { + int valueInt; + double valueDbl; + bool isInt, + isDbl; + + isInt = QColumnUtils::getIntValue( value.toString(), valueInt); + isDbl = QColumnUtils::getDoubleValue( value.toString(), valueDbl); + switch (_inputType) { - case TextInputType::IntegerInputType: return (value.toInt()); - case TextInputType::NumberInputType: return value.toDouble(); - case TextInputType::PercentIntputType: return std::min(std::max(value.toDouble(), 0.0), 100.0) / 100; + case TextInputType::IntegerInputType: return isInt ? valueInt : 0; + case TextInputType::NumberInputType: return isDbl ? valueDbl : 0; + case TextInputType::PercentIntputType: return std::min(std::max(isDbl ? valueDbl : 0, 0.0), 100.0) / 100; case TextInputType::IntegerArrayInputType: case TextInputType::DoubleArrayInputType: { QString str = value.toString(); - str.replace(QString(" "), QString(",")); + str.replace(QString(" "), QString(":")); Json::Value values(Json::arrayValue); - QStringList chunks = str.split(QChar(','), Qt::SkipEmptyParts); + QStringList chunks = str.split(QChar(':'), Qt::SkipEmptyParts); for (QString &chunk: chunks) { bool ok; if (_inputType == TextInputType::IntegerInputType) { - int value = chunk.toInt(&ok); - if (ok) values.append(value); + int value; + ok = QColumnUtils::getIntValue(chunk, value); + + if (ok) + values.append(value); } else { double valueDbl; - ok = ColumnUtils::getDoubleValue(fq(chunk), valueDbl); + ok = QColumnUtils::getDoubleValue(chunk, valueDbl); + if (ok) values.append(valueDbl); } } return values; } - default: return fq(value.toString()); + default: + return isInt ? Json::Value(valueInt) + : isDbl ? Json::Value(valueDbl) + : fq(value.toString()); } } void TextInputBase::valueChangedSlot() { - setValue(property("displayValue")); + QVariant prop = property("displayValue"); + + if(prop.toString() == "95.0") + 1+2; + + setValue(prop); } void TextInputBase::setValue(QVariant value) { double valueDbl; - if(ColumnUtils::getDoubleValue(fq(value.toString()), valueDbl)) + if(QColumnUtils::getDoubleValue(value.toString(), valueDbl)) value = valueDbl; bool hasChanged = _value != value; @@ -428,14 +479,19 @@ void TextInputBase::setValue(QVariant value) void TextInputBase::setDefaultValue(QVariant value) { double valueDbl; - if(ColumnUtils::getDoubleValue(fq(value.toString()), valueDbl)) + if(QColumnUtils::getDoubleValue(value.toString(), valueDbl)) value = valueDbl; - bool hasChanged = _defaultValue != value; + bool hasChanged = _defaultValue != value, + curValIsDef = _defaultValue == _value; + _defaultValue = value; if(hasChanged) emit defaultValueChanged(); + + if(curValIsDef) + setValue(_defaultValue); } void TextInputBase::_setBoundValue() diff --git a/QMLComponents/controls/textinputbase.h b/QMLComponents/controls/textinputbase.h index 79a9f96989..16c87a2f02 100644 --- a/QMLComponents/controls/textinputbase.h +++ b/QMLComponents/controls/textinputbase.h @@ -78,7 +78,7 @@ private slots: void setDisplayValue(); private: - Json::Value _getJsonValue(const QVariant& value) const; + Json::Value _getJsonValue(QVariant value) const; bool _formulaResultInBounds(double result); QString _getPercentValue(double val); diff --git a/QMLComponents/models/listmodelfiltereddataentry.cpp b/QMLComponents/models/listmodelfiltereddataentry.cpp index 6646b9e84b..49859e29a9 100644 --- a/QMLComponents/models/listmodelfiltereddataentry.cpp +++ b/QMLComponents/models/listmodelfiltereddataentry.cpp @@ -365,7 +365,7 @@ void ListModelFilteredDataEntry::informDataSetOfInitialValues() QVariantList vals; for(size_t i=0; i<_initialValues.size(); i++) { - vals.append(_acceptedRows[i] ? tq(ColumnUtils::doubleToString(_initialValues[i])) : ""); + vals.append(_acceptedRows[i] ? QColumnUtils::doubleToString(_initialValues[i]) : ""); if(_acceptedRows[i]) somethingFilled = true; } diff --git a/QMLComponents/utilities/qutils.cpp b/QMLComponents/utilities/qutils.cpp index 47ff95f4f5..b28d61c604 100644 --- a/QMLComponents/utilities/qutils.cpp +++ b/QMLComponents/utilities/qutils.cpp @@ -26,7 +26,7 @@ #include "appinfo.h" #include "simplecrypt.h" #include "log.h" -#include "utils.h" +#include "columnutils.h" using namespace std; @@ -324,3 +324,71 @@ QPoint maxQModelIndex(const QItemSelection &list) return QPoint(c, r); } + +QLocale QColumnUtils::_lastQLocale = QLocale(); +QString QColumnUtils::_lastQLocaleId = "C"; + +bool QColumnUtils::getIntValue(const QString &value, int &intValue) +{ + bool isInt = false; + intValue = currentQLocale().toInt(value, &isInt); + + return isInt || ColumnUtils::getIntValue(fq(value), intValue); +} + +bool QColumnUtils::getDoubleValue(const QString &value, double &doubleValue) +{ + bool isDouble = false; + doubleValue = currentQLocale().toDouble(value, &isDouble); + + return isDouble || ColumnUtils::getDoubleValue(fq(value), doubleValue); +} + +doubleset QColumnUtils::getDoubleValues(const QStringList &values, bool stripNAN) +{ + doubleset result; + for (const QString & val : values) + { + double doubleValue; + if (getDoubleValue(val, doubleValue) && !std::isnan(doubleValue)) + result.insert(doubleValue); + } + + return result; +} + +bool QColumnUtils::isIntValue(const QString &value) +{ + static int last; + return getIntValue(value, last); +} + +bool QColumnUtils::isDoubleValue(const QString &value) +{ + static double last; + return getDoubleValue(value, last); +} + +QLocale QColumnUtils::currentQLocale() +{ + QString newId = tq(ColumnUtils::currentQLocaleId()); + + if(newId != _lastQLocaleId) + { + _lastQLocaleId = newId; + _lastQLocale = QLocale(_lastQLocaleId); + } + + return _lastQLocale; +} + +QString QColumnUtils::doubleToString(double dbl, int precision) +{ + return currentQLocale().toString(dbl, 'g', precision); +} + +QString QColumnUtils::doubleToStringMaxPrec(double dbl) +{ + constexpr auto max_precision{std::numeric_limits::digits10 + 1}; + return doubleToString(dbl, max_precision); +} diff --git a/QMLComponents/utilities/qutils.h b/QMLComponents/utilities/qutils.h index b376fea35a..c159fcd2e4 100644 --- a/QMLComponents/utilities/qutils.h +++ b/QMLComponents/utilities/qutils.h @@ -31,6 +31,7 @@ #include #include #include +#include "utils.h" #include #include #include @@ -76,6 +77,7 @@ QString decrypt(const QString &input); QString getSortableTimestamp(); QString QJSErrorToString(QJSValue::ErrorType errorType); + void copyQDirRecursively(QDir copyThis, QDir toHere); QString shortenWinPaths(QString); @@ -96,6 +98,25 @@ void set##WHAT_TO_SET(TYPE new##WHAT_TO_SET) \ } \ } - +class QColumnUtils +{ +public: + static bool getIntValue( const QString & value, int & intValue); + static bool getDoubleValue( const QString & value, double & doubleValue); + static doubleset getDoubleValues(const QStringList & values, bool stripNAN = true); + + static bool isIntValue( const QString & value); + static bool isDoubleValue( const QString & value); + + static QLocale currentQLocale(); + + static QString doubleToString( double dbl, int precision = 10); + static QString doubleToStringMaxPrec( double dbl); + + +private: + static QString _lastQLocaleId; + static QLocale _lastQLocale; +}; #endif // QUTILS_H From 902b005f2be2b2f153e4813a4573182a704c56db Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 19 Feb 2025 14:06:52 +0100 Subject: [PATCH 08/21] remove integer array thing from textinputbase and make it handle percentages well also make sure the doubles in the double array are not sperated by a comma :s --- QMLComponents/controls/textinputbase.cpp | 92 ++++++++---------------- QMLComponents/controls/textinputbase.h | 3 +- 2 files changed, 32 insertions(+), 63 deletions(-) diff --git a/QMLComponents/controls/textinputbase.cpp b/QMLComponents/controls/textinputbase.cpp index 31ea0524f5..49ef36ac4f 100644 --- a/QMLComponents/controls/textinputbase.cpp +++ b/QMLComponents/controls/textinputbase.cpp @@ -34,22 +34,7 @@ QString TextInputBase::_getPercentValue(double dblVal) doubleValue = std::max(0., std::min(100., doubleValue)); int decimals = property("decimals").toInt(); - return QString::number(doubleValue, 'f', decimals); -} - -QString TextInputBase::_getIntegerArrayValue(const std::vector& intValues) -{ - QString value; - bool first = true; - for (int intValue : intValues) - { - if (!first) - value += ","; - first = false; - value += QString::number(intValue); - } - - return value; + return QColumnUtils::currentQLocale().toString(doubleValue, 'f', decimals); } QString TextInputBase::_getDoubleArrayValue(const std::vector& doubleValues) @@ -59,9 +44,9 @@ QString TextInputBase::_getDoubleArrayValue(const std::vector& doubleVal for (double doubleValue : doubleValues) { if (!first) - value += ","; + value += ";"; first = false; - value += QString::number(doubleValue); + value += QColumnUtils::doubleToString(doubleValue); } return value; @@ -87,26 +72,17 @@ void TextInputBase::bindTo(const Json::Value& value) double dblVal = 0; if (value.isNumeric()) dblVal = value.asDouble(); - else if (value.isString() && QColumnUtils::getDoubleValue(tq(value.asString()), dblVal)) - + else if (value.isString() && !QColumnUtils::getDoubleValue(tq(value.asString()), dblVal)) + dblVal = NAN; - _value = _inputType == TextInputType::PercentIntputType ? _getPercentValue(dblVal): dblVal; + _value = dblVal; //Stored as the user enters (so 0-100), but sent in json / 100 through _getPercentValue + //This mean the "bound value" is 0...1 so: + if(_inputType == TextInputType::PercentIntputType) + _value = dblVal * 100.0; break; } - case TextInputType::IntegerArrayInputType: - { - std::vector arrayVal; - if (value.isArray()) - { - for (const Json::Value& oneValue : value) - if (oneValue.isNumeric()) - arrayVal.push_back(oneValue.asInt()); - } - _value = _getIntegerArrayValue(arrayVal); - break; - } case TextInputType::DoubleArrayInputType: { std::vector arrayVal; @@ -188,10 +164,9 @@ bool TextInputBase::isJsonValid(const Json::Value &value) const bool valid = false; switch (_inputType) { - case TextInputType::IntegerArrayInputType: case TextInputType::DoubleArrayInputType: - case TextInputType::FormulaArrayType: valid = value.isArray(); break; - default: valid = value.isNumeric() || value.isString(); break; + case TextInputType::FormulaArrayType: valid = value.isArray(); break; + default: valid = value.isNumeric() || value.isString(); break; } return valid; } @@ -203,7 +178,6 @@ void TextInputBase::setUp() if (type == "integer") _inputType = TextInputType::IntegerInputType; else if (type == "number") _inputType = TextInputType::NumberInputType; else if (type == "percent") _inputType = TextInputType::PercentIntputType; - else if (type == "integerArray") _inputType = TextInputType::IntegerArrayInputType; else if (type == "doubleArray") _inputType = TextInputType::DoubleArrayInputType; else if (type == "computedColumn") _inputType = TextInputType::ComputedColumnType; else if (type == "checkColumn") _inputType = TextInputType::CheckColumnFreeOrMineType; @@ -229,25 +203,26 @@ void TextInputBase::setUp() void TextInputBase::setDisplayValue() { - Json::Value valueJson = _getJsonValue(_value); - - QString showThis = _value.toString(); - switch(valueJson.type()) - { - case Json::intValue: - showThis = QColumnUtils::currentQLocale().toString(valueJson.asInt());; - break; - case Json::realValue: - showThis = QColumnUtils::doubleToString(valueJson.asDouble()); - break; - } + int valueInt; + double valueDbl; + bool isInt, + isDbl; + isInt = QColumnUtils::getIntValue( _value.toString(), valueInt); + isDbl = QColumnUtils::getDoubleValue( _value.toString(), valueDbl); + QString showThis = _value.toString(); - if(showThis == "95.0") - 1+2; + if(isInt) + showThis = QColumnUtils::currentQLocale().toString(valueInt); + + else if(isDbl) + showThis = QColumnUtils::doubleToString(valueDbl); + if(showThis == "0,95") + 1+2; + if(property("displayValue") != showThis) setProperty("displayValue", showThis); } @@ -302,7 +277,6 @@ QString TextInputBase::friendlyName() const case TextInputType::IntegerInputType: return tr("Integer Field"); case TextInputType::NumberInputType: return tr("Double Field"); case TextInputType::PercentIntputType: return tr("Percentage Field"); - case TextInputType::IntegerArrayInputType: return tr("Integers Field"); case TextInputType::DoubleArrayInputType: return tr("Doubles Field"); case TextInputType::AddColumnType: return tr("Add Column Field"); case TextInputType::ComputedColumnType: return tr("Add Computed Column Field"); @@ -406,13 +380,12 @@ Json::Value TextInputBase::_getJsonValue(QVariant value) const case TextInputType::IntegerInputType: return isInt ? valueInt : 0; case TextInputType::NumberInputType: return isDbl ? valueDbl : 0; case TextInputType::PercentIntputType: return std::min(std::max(isDbl ? valueDbl : 0, 0.0), 100.0) / 100; - case TextInputType::IntegerArrayInputType: case TextInputType::DoubleArrayInputType: { QString str = value.toString(); - str.replace(QString(" "), QString(":")); + str.replace(QString(" "), QString(";")); Json::Value values(Json::arrayValue); - QStringList chunks = str.split(QChar(':'), Qt::SkipEmptyParts); + QStringList chunks = str.split(QChar(';'), Qt::SkipEmptyParts); for (QString &chunk: chunks) { @@ -446,10 +419,7 @@ Json::Value TextInputBase::_getJsonValue(QVariant value) const void TextInputBase::valueChangedSlot() { QVariant prop = property("displayValue"); - - if(prop.toString() == "95.0") - 1+2; - + setValue(prop); } @@ -480,8 +450,8 @@ void TextInputBase::setDefaultValue(QVariant value) { double valueDbl; if(QColumnUtils::getDoubleValue(value.toString(), valueDbl)) - value = valueDbl; - + value = valueDbl; + bool hasChanged = _defaultValue != value, curValIsDef = _defaultValue == _value; diff --git a/QMLComponents/controls/textinputbase.h b/QMLComponents/controls/textinputbase.h index 16c87a2f02..c16841174e 100644 --- a/QMLComponents/controls/textinputbase.h +++ b/QMLComponents/controls/textinputbase.h @@ -34,7 +34,7 @@ class TextInputBase : public JASPControl, public BoundControlBase Q_PROPERTY( QVariant value READ value WRITE setValue NOTIFY valueChanged ) public: - enum TextInputType { IntegerInputType = 0, StringInputType, NumberInputType, PercentIntputType, IntegerArrayInputType, DoubleArrayInputType, ComputedColumnType, AddColumnType, CheckColumnFreeOrMineType, FormulaType, FormulaArrayType}; + enum TextInputType { IntegerInputType = 0, StringInputType, NumberInputType, PercentIntputType, DoubleArrayInputType, ComputedColumnType, AddColumnType, CheckColumnFreeOrMineType, FormulaType, FormulaArrayType}; TextInputBase(QQuickItem* parent = nullptr); @@ -82,7 +82,6 @@ private slots: bool _formulaResultInBounds(double result); QString _getPercentValue(double val); - QString _getIntegerArrayValue(const std::vector& intValues); QString _getDoubleArrayValue(const std::vector& dblValues); void _setBoundValue(); From fa56c015b5d88a44c719b6c5e35ef6fb0406fd17 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 19 Feb 2025 18:52:52 +0100 Subject: [PATCH 09/21] rewrite the html js formatting code a bit and tweaking the doublevalidator a bit more just convert to english/default locale thing when checking for decimals also add setLocal thing to resultsJsInterface, now to think of where to put that information --- Desktop/html/js/main.js | 9 + Desktop/html/js/utils.js | 606 +++++++----------- Desktop/results/resultsjsinterface.cpp | 5 + Desktop/results/resultsjsinterface.h | 1 + Desktop/utilities/languagemodel.cpp | 8 +- .../controls/jaspdoublevalidator.cpp | 35 +- QMLComponents/rsyntax/rsyntax.cpp | 2 +- 7 files changed, 256 insertions(+), 410 deletions(-) diff --git a/Desktop/html/js/main.js b/Desktop/html/js/main.js index 0af10ece67..892d8d1fda 100644 --- a/Desktop/html/js/main.js +++ b/Desktop/html/js/main.js @@ -631,3 +631,12 @@ window.setFontFamily = function(fontFamily) { document.body.style.fontFamily = fontFamily; } + +window.setFontFamily = function(fontFamily) { + document.body.style.fontFamily = fontFamily; +} + +window.setLocale = function(localeId) { + jasp.setLocale(localeId); +} + diff --git a/Desktop/html/js/utils.js b/Desktop/html/js/utils.js index 542d17c55f..bab79ce54d 100644 --- a/Desktop/html/js/utils.js +++ b/Desktop/html/js/utils.js @@ -89,21 +89,23 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote return columnCells } - let formats = format.split(";"); - let p = NaN; - let dp = NaN; - let sf = NaN; - let pc = false; - let approx = false; - let log10 = false; - dp = parseInt(window.globSet.decimals); // NaN if not specified by user + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////// First collect the formats and their respective settings //////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + let formats = format.split(";"); + let p = NaN; + let dp = parseInt(window.globSet.decimals); + let sf = NaN; + let pc = false; + let approx = false; + let log10 = false; let fixDecimals = (typeof dp === 'number') && (dp % 1 === 0); - let currency = "" - - var moneyStr = "monetary" + let currency = "" + let moneyFmt = "monetary" - for (let i = 0; i < formats.length; i++) { - + for (let i = 0; i < formats.length; i++) + { let f = formats[i]; if (f.match(/^p:/) !== null) { // override APA style if exact p-values wanted @@ -114,11 +116,11 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote } } - if(f.startsWith(moneyStr)) + if(f.startsWith(moneyFmt)) { - if(f.length > moneyStr.length) + if(f.length > moneyFmt.length) { - currency = f.substr(moneyStr.length) + currency = f.substr(moneyFmt.length) if(currency.startsWith(":")) currency = currency.substring(1) } @@ -143,402 +145,230 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote log10 = true; } + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////// Then try to convert all the cells to float if they werent numbers //////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// for (var rowNo = 0; rowNo < column.length; rowNo++) { - var cell = column[rowNo] - var content = cell.content + let cell = column[rowNo] + let content = cell.content if (typeof(content) !== "number" || typeof(content) !== "Number") { if (isNaN(parseFloat(content))) // isn't a number continue - //SHUT UP console.warn("You are delivering a result that should be a number as a string, We will do our best :("); cell.content = content = parseFloat(content) } } + // some vars we need for sf: + var upperLimit = 1e6 + var minLSD = Infinity // right most position of the least significant digit + var maxFSDOE = -Infinity // left most position of the least significant digit of the exponent in scientific notation + + if (isFinite(sf)) { + //prepare some stuff we need to run "sf" + + for (var rowNo = 0; rowNo < column.length; rowNo++) + { + let cell = column[rowNo] + let content = cell.content - if(currency != "") { - for (let rowNo = 0; rowNo < column.length; rowNo++) { - columnCells[rowNo] = { content: `${formatMoney(_currency=currency, column[rowNo].content)}`, class: "monetary" } //should probably do better formatting then this though - } - } - else if (isFinite(sf)) { - - var upperLimit = 1e6 - var minLSD = Infinity // right most position of the least significant digit - var maxFSDOE = -Infinity // left most position of the least significant digit of the exponent in scientific notation - - for (var rowNo = 0; rowNo < column.length; rowNo++) { - - var cell = column[rowNo] - var content = cell.content - if (isNaN(parseFloat(content))) // isn't a number continue - - var fsd // position of first significant digit - - if (log10) - fsd = content - else - fsd = fSD(content) - - var lsd = fsd - sf - - if (log10) { - - if (content >= 6 || content <= -dp) { - + + let fsd = log10 ? content : fSD(content) // position of first significant digit + let lsd = fsd - sf + let fsdoe + + if (log10) + { + if (content >= 6 || content <= -dp) fsdoe = fSD(content) - - if (fsdoe > maxFSDOE) - maxFSDOE = fsdoe - } - - } else if (Math.abs(content) >= upperLimit || Math.abs(content) <= Math.pow(10, -dp)) { - - var fsdoe // first significant digit of exponent - - fsdoe = fSDOE(content) - - if (fsdoe > maxFSDOE) - maxFSDOE = fsdoe - } - - if (lsd < minLSD) { - + } + else if (Math.abs(content) >= upperLimit || Math.abs(content) <= Math.pow(10, -dp)) + fsdoe = fSDOE(content) // first significant digit of exponent + + if (fsdoe > maxFSDOE) + maxFSDOE = fsdoe + + if (lsd < minLSD) minLSD = lsd - } - } - - if (fixDecimals) { - minLSD = -dp - } else { - if (minLSD < -dp) - minLSD = -dp - if (minLSD > 0) - minLSD = 0 } - - if (minLSD < -20) - minLSD = -20 - - for (var rowNo = 0; rowNo < column.length; rowNo++) { - - var cell = column[rowNo] - var content = cell.content - var formatted - var isNumber = false - - if (typeof content == "undefined") { - - formatted = { content: "." } - } - else if (typeof content === "") { - let content = (html) ? " " : " "; - formatted = { content: content, "class": "number" } - } - else if (combine && rowNo > 0 && column[rowNo - 1].content == content) { - let content = (html) ? " " : " "; - formatted = { content: content, "class": "number" } - } - else if (isNaN(parseFloat(content))) { // isn't a number - formatted = { content: content, "class": "number" } - } - else if (content < p) { - let content = (html) ? "< " : "< "; - formatted = { content: content + p, "class": "p-value" } - } - else if (content == 0) { - - var number = 0 - - if (log10) - number = 1 - - if (isFinite(dp)) - formatted = { content: number.toFixed(dp), "class": "number" } - else - formatted = { content: number.toPrecision(sf), "class": "number" } - - isNumber = true - } - else if (log10) { - - if (content < (Math.log(upperLimit) / Math.log(10)) && content > -dp) { - - if (alignNumbers || fixDecimals) { - let _sign = (html) ? "−" : "-"; - formatted = { content: Math.pow(10, content).toFixed(-minLSD).replace(/-/g, _sign), "class": "number" } - } - else { - let _sign = (html) ? "−" : "-"; - formatted = { content: Math.pow(10, content).toPrecision(sf).replace(/-/g, _sign), "class": "number" } - } - - isNumber = true + + minLSD = fixDecimals ? -dp : Math.min(0, Math.max(-dp, minLSD)) + minLSD = Math.max(-20, minLSD) + } + + format = currency != "" ? "monetary" : pc ? "percentage" : isFinite(sf) ? "significance" : isFinite(dp) ? "decimalPoints" : "other" + + //Now that thats been determined we can format our cells + for (var rowNo = 0; rowNo < column.length; rowNo++) + { + //Get the cell and set up content var everything will read + //and the formatted var for writing to the new cells at the end. + let cell = column[rowNo] + let content = cell.content + let formatted = { content: content, class: ""} + let isNumber = format == "significance" || format == "decimalPoints" || format == "percentage" //Will set class number + let isPercent = format == "percentage" // will set class percentage + + if (typeof content == "undefined") + formatted["content"] = "." + else if (content === "" || (combine && rowNo > 0 && column[rowNo - 1].content == content)) + formatted["content"] = html ? " " : " " + else + switch(format) + { + case "other": + break; //Wouldnt know what to do with it + + case "monetary": + { + formatted["content"] = `${formatMoney(_currency=currency, column[rowNo].content)}` + formatted["class"] = "monetary" + break; } - else { - - // var paddingNeeded = Math.max(maxFSDOE - fSD(content), 0) - var paddingNeeded = 0 - - var exponent = Math.abs(Math.floor(content)) - - var exp = "" - - while (exponent > 0) { - - var digit = exponent % 10 - exponent = Math.floor(exponent / 10) - exp = "" + digit + exp + + case "percentage": + { + if (!isNaN(parseFloat(content))) + formatted["content"] = "" + (100 * content.toFixed(0)) + (html ? " %" : "%") + break; + } + + case "significance": + { + if (isNaN(parseFloat(content))) // isn't a number but we'll still give it that class + { + formatted["class"] = "number" + isNumber = false } - - if (exp.length === 0) - exp = "1" - - exponent = exp - - var mantissa - if (content > 0) - mantissa = Math.pow(10, content % 1) - else - mantissa = Math.pow(10, 1 + (content % 1)) - - if (mantissa > 9.99999999) { - - mantissa = 1 - exponent-- + else if (content < p) + { + formatted["content"] = (html ? "< " : "< ") + p + formatted["class"] = "p-value" + isNumber = false } - - var sign = content >= 0 ? "+" : "-" - - mantissa = fixDecimals ? mantissa.toFixed(dp) : mantissa.toPrecision(sf) - - var padding - - if (paddingNeeded) - padding = '' - else - padding = '' - - let reassembled; - - if (html) { - if (window.globSet.normalizedNotation) { - reassembled = mantissa + "×10" + "" + padding + sign + exponent + ""; - } else { - reassembled = mantissa + "e" + padding + sign + exponent; + else if (content == 0) + { + let number = log10 ? 0 : 1 + formatted["content"] = isFinite(dp) ? number.toFixed(dp) : number.toPrecision(sf) + } + else if (log10) + { + if (content < (Math.log(upperLimit) / Math.log(10)) && content > -dp) + { + let pow = Math.pow(10, content) + formatted["content"] = alignNumbers || fixDecimals ? pow.toFixed(-minLSD) : pow.toPrecision(sf) + if(html) + formatted["content"] = formatted["content"].replace(/-/g, "−") } - } else { - if (window.globSet.normalizedNotation) { - reassembled = mantissa + "×10" + "" + sign + exponent + ""; - } else { - reassembled = mantissa + "e" + sign + exponent; + else + { + // var paddingNeeded = Math.max(maxFSDOE - fSD(content), 0) + let paddingNeeded = 0 + let exponent = Math.abs(Math.floor(content)) + let exp = "" + + while (exponent > 0) { + var digit = exponent % 10 + exponent = Math.floor(exponent / 10) + exp = "" + digit + exp + } + + exponent = Number(exp == "" ? 1 : exp) + + let mantissa = Math.pow(10, (content % 1) + (content > 0 ? 0 : 1)) + if (mantissa > 9.99999999) + { + mantissa = 1 + exponent-- + } + mantissa = fixDecimals ? mantissa.toFixed(dp) : mantissa.toPrecision(sf) + + let sign = content >= 0 ? "+" : "-" + let padding = !paddingNeeded ? '' : '' + + let reassembled = mantissa; + reassembled += !window.globSet.normalizedNotation ? "e" : (html ? "×10" : "×10") + "" + reassembled += html ? padding : "" + reassembled += sign + exponent + reassembled += !window.globSet.normalizedNotation ? "" : "" + + formatted["content"] = reassembled } } - - formatted = { content: reassembled, "class": "number" } - - isNumber = true - } - } - else if (Math.abs(content) >= upperLimit || Math.abs(content) < Math.pow(10, -dp)) { - - var decimalsExpon = fixDecimals ? dp : sf - 1; - // var paddingNeeded = Math.max(maxFSDOE - fSDOE(content), 0) - var paddingNeeded = 0 - - let reassembled = toExponential(content, decimalsExpon, paddingNeeded, html) - formatted = { content: reassembled, "class": "number" } - - isNumber = true - } - else { - - if (alignNumbers || fixDecimals) { - let _sign = (html) ? "−" : "-"; - formatted = { content: content.toFixed(-minLSD).replace(/-/g, _sign), "class": "number" } - } - else { - let _sign = (html) ? "−" : "-"; - formatted = { content: content.toPrecision(sf).replace(/-/g, _sign), "class": "number" } + else if (Math.abs(content) >= upperLimit || Math.abs(content) < Math.pow(10, -dp)) + { + let decimalsExpon = fixDecimals ? dp : sf - 1; + let paddingNeeded = 0 // var paddingNeeded = Math.max(maxFSDOE - fSDOE(content), 0) + formatted["content"] = toExponential(content, decimalsExpon, paddingNeeded, html) + } + else + { + formatted["content"] = alignNumbers || fixDecimals ? content.toFixed(-minLSD) : content.toPrecision(sf) + if(html) + formatted["content"] = formatted["content"].replace(/-/g, "−") + } + + break; } - - isNumber = true - } - - if (typeof cell.footnotes != "undefined") - formatted.footnotes = getFootnotes(modelFootnotes, cell.footnotes) - - if (cell.isStartOfGroup) - formatted["class"] += " new-group-row" - - if (cell.isStartOfSubGroup) - formatted["class"] += " new-sub-group-row" - - if (cell.isEndOfGroup) - formatted["class"] += " last-group-row" - - if (isNumber && approx) { - let _content = (html) ? "~ " : "~"; - formatted.content = _content + formatted.content - } - - columnCells[rowNo] = formatted - } - } - else if (isFinite(dp)) { - - for (var rowNo = 0; rowNo < column.length; rowNo++) { - - var cell = column[rowNo] - var content = cell.content - var formatted - - var isNumber = false - - if (typeof content == "undefined") { - - formatted = { content: "." } - } - else if (content === "") { - let _content = (html) ? " " : " "; - formatted = { content: _content } - } - else if (combine && rowNo > 0 && column[rowNo - 1].content == content) { - let _content = (html) ? " " : " "; - formatted = { content: _content, "class": "number" } - } - else if (isNaN(parseFloat(content))) { // isn't a number - formatted = { content: content, "class": "number" } - } - else if (content < p) { - let _content = (html) ? "< " : "< "; - formatted = { content: _content + p, "class": "p-value" } - } - else if (pc) { - let _content = (html) ? " %" : "%"; - formatted = { content: "" + (100 * content).toFixed(dp) + _content, "class": "percentage" } - isNumber = true - } - else { - var strContent; - - if (p && content != 0 && Math.abs(content) < 1/(Math.pow(10,dp))) { - strContent = toExponential(content, dp, 0, html) - } else { - let _content = (html) ? "−" : "-"; - strContent = content.toFixed(dp).replace(/-/g, _content) + + case "decimalPoints": + { + if (isNaN(parseFloat(content))) // isn't a number but we'll give it that class anyway + { + formatted["class"] = "number" + isNumber = false + } + else if (content < p) + { + formatted["content"] = (html ? "< " : "< ") + p + formatted["class"] = "p-value" + isNumber = false + } + else + { + var strContent = ""; + + if (p && content != 0 && Math.abs(content) < 1/(Math.pow(10,dp))) + strContent = toExponential(content, dp, 0, html) + else + { + strContent = content.toFixed(dp) + if(html) + strContent = strContent.replace(/-/g, "−") + } + formatted["content"] = strContent + } + break; } - formatted = { content: strContent, "class": "number" } - isNumber = true - } - - if (typeof cell.footnotes != "undefined") - formatted.footnotes = getFootnotes(modelFootnotes, cell.footnotes) - - if (cell.isStartOfGroup) - formatted["class"] += " new-group-row" - - if (cell.isStartOfSubGroup) - formatted["class"] += " new-sub-group-row" - - if (cell.isEndOfGroup) - formatted["class"] += " last-group-row" - - if (isNumber && approx) { - let _content = (html) ? "~ " : "~"; - formatted.content = _content + formatted.content - } - - columnCells[rowNo] = formatted - } - } - else if (pc) { - - for (var rowNo = 0; rowNo < column.length; rowNo++) { - - var cell = column[rowNo] - var content = cell.content - var formatted - - var isNumber = false - - if (typeof content == "undefined") { - - formatted = { content: "." } - } - else if (content === "") { - let _content = (html) ? " " : " "; - formatted = { content: _content } - } - else if (isNaN(parseFloat(content))) { // isn't a number - formatted = { content: content, "class": "percentage" } - } - else { - let _content = (html) ? " %" : "%"; - formatted = { content: "" + (100 * content.toFixed(0)) + _content, "class": "percentage" } - isNumber = true - } - - if (typeof cell.footnotes != "undefined") - formatted.footnotes = getFootnotes(modelFootnotes, cell.footnotes) - - if (cell.isStartOfGroup) - formatted["class"] += " new-group-row" - - if (cell.isStartOfSubGroup) - formatted["class"] += " new-sub-group-row" - - if (cell.isEndOfGroup) - formatted["class"] += " last-group-row" - - if (isNumber && approx) { - let _content = (html) ? "~ " : "~"; - formatted.content = _content + formatted.content - } - - columnCells[rowNo] = formatted - } - } - else { - - for (let rowNo = 0; rowNo < column.length; rowNo++) { - - var cell = column[rowNo] - var content = cell.content - var formatted - - if (typeof content == "undefined") { - - formatted = { content: "." } - } - else if (content === "") { - let _content = (html) ? " " : " "; - formatted = { content: _content } - } - else if (combine && rowNo > 0 && column[rowNo - 1].content == content) { - let _content = (html) ? " " : " "; - formatted = { content: _content } - } - else { - formatted = { content: content } + } - - if (typeof cell.footnotes != "undefined") - formatted.footnotes = getFootnotes(modelFootnotes, cell.footnotes) - - if (cell.isStartOfGroup) - formatted["class"] += " new-group-row" - - if (cell.isStartOfSubGroup) - formatted["class"] += " new-sub-group-row" - - if (cell.isEndOfGroup) - formatted["class"] += " last-group-row" - - columnCells[rowNo] = formatted - } + + if(isNumber) + formatted["class"] = "number" + + if(isPercent) + formatted["class"] = "percentage" + + if (isNumber && approx) + formatted.content = (html ? "~ " : "~") + formatted.content + + //Finishing up: + if (typeof cell.footnotes != "undefined") + formatted["footnotes"] = getFootnotes(modelFootnotes, cell.footnotes) + + if (cell.isStartOfGroup) + formatted["class"] += " new-group-row" + + if (cell.isStartOfSubGroup) + formatted["class"] += " new-sub-group-row" + + if (cell.isEndOfGroup) + formatted["class"] += " last-group-row" + + //And at last assign the formatted string to its cell: + columnCells[rowNo] = formatted } return columnCells diff --git a/Desktop/results/resultsjsinterface.cpp b/Desktop/results/resultsjsinterface.cpp index 66392b8768..ae8b1a2202 100644 --- a/Desktop/results/resultsjsinterface.cpp +++ b/Desktop/results/resultsjsinterface.cpp @@ -434,6 +434,11 @@ void ResultsJsInterface::setFontFamily() } } +void ResultsJsInterface::setLocale(QString localeId) +{ + runJavaScript("window.setLocale(\"" + escapeJavascriptString(localeId) + "\");"); +} + void ResultsJsInterface::runJavaScript(const QString & js) { if(_resultsLoaded) emit runJavaScriptSignal(js); diff --git a/Desktop/results/resultsjsinterface.h b/Desktop/results/resultsjsinterface.h index ea41398219..dd683ca0c0 100644 --- a/Desktop/results/resultsjsinterface.h +++ b/Desktop/results/resultsjsinterface.h @@ -108,6 +108,7 @@ public slots: void moveAnalyses( quint64 fromId, quint64 toId); void setThemeCss( QString themeName); void setFontFamily(); + void setLocale( QString localeId); //end callables diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 16b951c788..2e813e8af0 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -89,6 +89,8 @@ void LanguageModel::initialize() _qml->retranslate(); } + + setDefaultLocaleFromCurrent(); } @@ -152,9 +154,9 @@ void LanguageModel::setDefaultLocaleFromCurrent() return fq(currentLocale().toString(dbl, 'g', precision)); }; - ColumnUtils::setAlternativeDoubleToString(altFunc); - ColumnUtils::setDecimalPoint(fq(currentLocale().decimalPoint())); - ColumnUtils::setCurrentQLocaleId(fq(currentLocale().bcp47Name())); + ColumnUtils::setAlternativeDoubleToString( altFunc ); + ColumnUtils::setCurrentQLocaleId( fq(currentLocale().bcp47Name()) ); + ColumnUtils::setDecimalPoint( fq(currentLocale().decimalPoint()) ); } void LanguageModel::refreshAll() diff --git a/QMLComponents/controls/jaspdoublevalidator.cpp b/QMLComponents/controls/jaspdoublevalidator.cpp index 987ad12870..9c05f6c46a 100644 --- a/QMLComponents/controls/jaspdoublevalidator.cpp +++ b/QMLComponents/controls/jaspdoublevalidator.cpp @@ -32,37 +32,36 @@ QValidator::State JASPDoubleValidator::validate(QString& s, int& pos) const if (s.startsWith("-") && bottom() >= 0) return QValidator::Invalid; - + // check range of value + double value; + bool isNumber = QColumnUtils::getDoubleValue( s, value); + //int intVal; + //bool isInt = QColumnUtils::getIntValue( s, intVal); + + //Maybe the number is formatted in some crazy way, because of locales + QString toEnglish = isNumber ? QString::number(value) : s; + // check length of decimal places - QString point = tq(ColumnUtils::decimalPoint()); - int indexPoint = s.indexOf(point); + QString point = "."; + int indexPoint = toEnglish.indexOf(point); if (indexPoint != -1) { if (decimals() == 0) return QValidator::Invalid; - int lengthDecimals = s.length() - indexPoint - 1; + + int lengthDecimals = toEnglish.length() - indexPoint - 1; if (lengthDecimals > decimals()) return QValidator::Invalid; } - // check range of value - double value; - bool isNumber = QColumnUtils::getDoubleValue(s, value); - if (!isNumber) { - if(!isNumber) - { - if (s.length() == 1 && s[0] == point) - { - isNumber = true; - value = 0; - } - else - return QValidator::Invalid; - } + if (s == point) + value = 0; + else + return QValidator::Invalid; } bool isMaxExclusive = _inclusive == JASPControl::Inclusive::None || _inclusive == JASPControl::Inclusive::MinOnly; diff --git a/QMLComponents/rsyntax/rsyntax.cpp b/QMLComponents/rsyntax/rsyntax.cpp index c338fd7487..f99732ae4f 100644 --- a/QMLComponents/rsyntax/rsyntax.cpp +++ b/QMLComponents/rsyntax/rsyntax.cpp @@ -379,7 +379,7 @@ QString RSyntax::transformJsonToR(const Json::Value &json) result = QString::number(json.asUInt()); break; case Json::realValue: - result = QString::number(json.asDouble()); + result = QString::number(json.asDouble()); //This is not taking locale into account, but as its going to R this is ok I guess? break; case Json::arrayValue: if (json.size() == 0) result = "list()"; From b820dca5cf036acde90160f3c5a53e1e2867c20c Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 19 Feb 2025 21:23:33 +0100 Subject: [PATCH 10/21] ok money now seems to respect locale --- Desktop/html/index-jasp.html | 8 +++----- Desktop/html/index.html | 26 +++++++++++++++----------- Desktop/html/js/main.js | 2 +- Desktop/html/js/utils.js | 19 ++++++++++++++++++- Desktop/mainwindow.cpp | 1 + Desktop/utilities/languagemodel.cpp | 1 + Desktop/utilities/languagemodel.h | 2 +- 7 files changed, 40 insertions(+), 19 deletions(-) diff --git a/Desktop/html/index-jasp.html b/Desktop/html/index-jasp.html index b5b2eec3cd..e3ee3387e1 100644 --- a/Desktop/html/index-jasp.html +++ b/Desktop/html/index-jasp.html @@ -15,18 +15,16 @@ - - - + - - + + diff --git a/Desktop/html/index.html b/Desktop/html/index.html index 0484b0a0dd..1ffaf73773 100644 --- a/Desktop/html/index.html +++ b/Desktop/html/index.html @@ -6,23 +6,26 @@ JASP - + - - - - + + + + - - - + + - - - + + + + + + + @@ -31,6 +34,7 @@ + diff --git a/Desktop/html/js/main.js b/Desktop/html/js/main.js index 892d8d1fda..0b55f0090a 100644 --- a/Desktop/html/js/main.js +++ b/Desktop/html/js/main.js @@ -637,6 +637,6 @@ window.setFontFamily = function(fontFamily) { } window.setLocale = function(localeId) { - jasp.setLocale(localeId); + setCurrentLocaleID(localeId); //In utils.js } diff --git a/Desktop/html/js/utils.js b/Desktop/html/js/utils.js index bab79ce54d..bbb4d8cc12 100644 --- a/Desktop/html/js/utils.js +++ b/Desktop/html/js/utils.js @@ -1,5 +1,22 @@ +var currentLocale = new Intl.Locale("en"); +var currentLocaleId = "en" + +function setCurrentLocaleID(id) +{ + console.log(`Setting current locale to id "${id}"`) + currentLocale = new Intl.Locale(id) + currentLocaleId = id +} + +/* +function formatFixed(value, fixIt) +{ + return currentLocale.to +}*/ + + function formatMoney(_currency='EUR', amount) { - const formatter = new Intl.NumberFormat(undefined, { + const formatter = new Intl.NumberFormat(currentLocaleId, { style: 'currency', currency: _currency, trailingZeroDisplay: 'stripIfInteger' diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 65f0a16faa..43aa927b9d 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -414,6 +414,7 @@ void MainWindow::makeConnections() connect(_computedColumnsModel, &ComputedColumnModel::chooseColumn, _columnModel, &ColumnModel::setChosenColumnByName, Qt::QueuedConnection); connect(_languageModel, &LanguageModel::currentLanguageChanged, _columnModel, &ColumnModel::languageChangedHandler, Qt::QueuedConnection); + connect(_languageModel, &LanguageModel::currentLocaleChanged, _resultsJsInterface, &ResultsJsInterface::setLocale ); connect(_resultsJsInterface, &ResultsJsInterface::packageModified, this, &MainWindow::setPackageModified ); connect(_resultsJsInterface, &ResultsJsInterface::analysisChangedDownstream, this, &MainWindow::analysisChangedDownstreamHandler ); diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 2e813e8af0..590a58ae82 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -212,6 +212,7 @@ void LanguageModel::resultsPageLoaded() _shouldEmitLanguageChanged = false; emit currentLanguageChanged(); + emit currentLocaleChanged(currentLocale().bcp47Name()); emit resumeEngines(); } diff --git a/Desktop/utilities/languagemodel.h b/Desktop/utilities/languagemodel.h index 44e1045b01..d4fb7ea381 100644 --- a/Desktop/utilities/languagemodel.h +++ b/Desktop/utilities/languagemodel.h @@ -80,7 +80,7 @@ public slots: signals: void currentLanguageChanged(); - void currentLocaleChanged(); + void currentLocaleChanged(QString); void useSystemLocaleChanged(); void aboutToChangeLanguage(); void languageChangeDone(); From 6ecedcd3a33a917eb2648fadec1b4fd46a94d7f0 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 19 Feb 2025 21:50:40 +0100 Subject: [PATCH 11/21] now the currency and some numbers are formatted correctly even on startup --- Desktop/html/js/utils.js | 22 +++++++++++++++++----- Desktop/mainwindow.cpp | 6 ++++-- Desktop/utilities/languagemodel.cpp | 5 ++--- QMLComponents/controls/textinputbase.cpp | 5 +---- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Desktop/html/js/utils.js b/Desktop/html/js/utils.js index bbb4d8cc12..ee9a842c13 100644 --- a/Desktop/html/js/utils.js +++ b/Desktop/html/js/utils.js @@ -25,6 +25,18 @@ function formatMoney(_currency='EUR', amount) { return formatter.format(amount) } +function formatFixed(number, digitsFrac) { + const formatter = new Intl.NumberFormat(currentLocaleId, { minimumFractionDigits: digitsFrac }); + + return formatter.format(number) +} + +function formatPrecision(number, precision) { + const formatter = new Intl.NumberFormat(currentLocaleId, { minimumSignificantDigits: precision, maximumSignificantDigits: precision }); + + return formatter.format(number) +} + function formatColumn(column, type, format, alignNumbers, combine, modelFootnotes, html = true, errorOnMixed = false) { /** * Prepares the columns of a table to the required format @@ -268,14 +280,14 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote else if (content == 0) { let number = log10 ? 0 : 1 - formatted["content"] = isFinite(dp) ? number.toFixed(dp) : number.toPrecision(sf) + formatted["content"] = isFinite(dp) ? formatFixed(number, dp) : formatPrecision(number, sf) } else if (log10) { if (content < (Math.log(upperLimit) / Math.log(10)) && content > -dp) { let pow = Math.pow(10, content) - formatted["content"] = alignNumbers || fixDecimals ? pow.toFixed(-minLSD) : pow.toPrecision(sf) + formatted["content"] = alignNumbers || fixDecimals ? formatFixed(pow, -minLSD) : formatPrecision(pow, sf) if(html) formatted["content"] = formatted["content"].replace(/-/g, "−") } @@ -300,7 +312,7 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote mantissa = 1 exponent-- } - mantissa = fixDecimals ? mantissa.toFixed(dp) : mantissa.toPrecision(sf) + mantissa = fixDecimals ? formatFixed(mantissa, dp) : formatPrecision(mantissa, sf) let sign = content >= 0 ? "+" : "-" let padding = !paddingNeeded ? '' : '' @@ -322,7 +334,7 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote } else { - formatted["content"] = alignNumbers || fixDecimals ? content.toFixed(-minLSD) : content.toPrecision(sf) + formatted["content"] = alignNumbers || fixDecimals ? formatFixed(content, -minLSD) : formatPrecision(content, sf) if(html) formatted["content"] = formatted["content"].replace(/-/g, "−") } @@ -351,7 +363,7 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote strContent = toExponential(content, dp, 0, html) else { - strContent = content.toFixed(dp) + strContent = formatFixed(content, dp) if(html) strContent = strContent.replace(/-/g, "−") } diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 43aa927b9d..682e2a8c99 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -160,6 +160,8 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl _engineSync->start(_preferences->plotPPI()); checkForUpdates(); + + _languageModel->setDefaultLocaleFromCurrent(); //Make sure (Q)ColumnUtils knows whats up Log::log() << "JASP Desktop started and Engines initalized." << std::endl; @@ -414,7 +416,7 @@ void MainWindow::makeConnections() connect(_computedColumnsModel, &ComputedColumnModel::chooseColumn, _columnModel, &ColumnModel::setChosenColumnByName, Qt::QueuedConnection); connect(_languageModel, &LanguageModel::currentLanguageChanged, _columnModel, &ColumnModel::languageChangedHandler, Qt::QueuedConnection); - connect(_languageModel, &LanguageModel::currentLocaleChanged, _resultsJsInterface, &ResultsJsInterface::setLocale ); + connect(_languageModel, &LanguageModel::currentLocaleChanged, _resultsJsInterface, &ResultsJsInterface::setLocale, Qt::QueuedConnection); connect(_resultsJsInterface, &ResultsJsInterface::packageModified, this, &MainWindow::setPackageModified ); connect(_resultsJsInterface, &ResultsJsInterface::analysisChangedDownstream, this, &MainWindow::analysisChangedDownstreamHandler ); @@ -434,7 +436,7 @@ void MainWindow::makeConnections() connect(_resultsJsInterface, &ResultsJsInterface::showPlotEditor, _plotEditorModel, &PlotEditorModel::showPlotEditor ); connect(_resultsJsInterface, &ResultsJsInterface::resultsMetaChanged, _analyses, &Analyses::resultsMetaChanged ); connect(_resultsJsInterface, &ResultsJsInterface::allUserDataChanged, _analyses, &Analyses::allUserDataChanged ); - connect(_resultsJsInterface, &ResultsJsInterface::resultsPageLoadedSignal, _languageModel, &LanguageModel::resultsPageLoaded ); + connect(_resultsJsInterface, &ResultsJsInterface::resultsPageLoadedSignal, _languageModel, &LanguageModel::resultsPageLoaded, Qt::QueuedConnection); connect(_resultsJsInterface, &ResultsJsInterface::showRSyntaxInResults, _analyses, &Analyses::showRSyntaxInResults ); connect(_analyses, &Analyses::countChanged, this, &MainWindow::analysesCountChangedHandler ); diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 590a58ae82..32f4fa71d8 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -89,9 +89,6 @@ void LanguageModel::initialize() _qml->retranslate(); } - - setDefaultLocaleFromCurrent(); - } QVariant LanguageModel::data(const QModelIndex &index, int role) const @@ -157,6 +154,8 @@ void LanguageModel::setDefaultLocaleFromCurrent() ColumnUtils::setAlternativeDoubleToString( altFunc ); ColumnUtils::setCurrentQLocaleId( fq(currentLocale().bcp47Name()) ); ColumnUtils::setDecimalPoint( fq(currentLocale().decimalPoint()) ); + + emit currentLocaleChanged(currentLocale().bcp47Name()); } void LanguageModel::refreshAll() diff --git a/QMLComponents/controls/textinputbase.cpp b/QMLComponents/controls/textinputbase.cpp index 49ef36ac4f..e4b6c0c8e1 100644 --- a/QMLComponents/controls/textinputbase.cpp +++ b/QMLComponents/controls/textinputbase.cpp @@ -219,10 +219,7 @@ void TextInputBase::setDisplayValue() else if(isDbl) showThis = QColumnUtils::doubleToString(valueDbl); - - if(showThis == "0,95") - 1+2; - + if(property("displayValue") != showThis) setProperty("displayValue", showThis); } From 39a0c224ba1ae9c80de931459a7ba01f2a0975a6 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 19 Feb 2025 23:03:45 +0100 Subject: [PATCH 12/21] all numbers in the table now seem to be localized --- Desktop/html/js/utils.js | 114 ++++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 54 deletions(-) diff --git a/Desktop/html/js/utils.js b/Desktop/html/js/utils.js index ee9a842c13..c4d6e918ce 100644 --- a/Desktop/html/js/utils.js +++ b/Desktop/html/js/utils.js @@ -37,6 +37,12 @@ function formatPrecision(number, precision) { return formatter.format(number) } +function formatNumber(number) { + const formatter = new Intl.NumberFormat(currentLocaleId, { }); + + return formatter.format(number) +} + function formatColumn(column, type, format, alignNumbers, combine, modelFootnotes, html = true, errorOnMixed = false) { /** * Prepares the columns of a table to the required format @@ -73,32 +79,28 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote for (let rowNo = 0; rowNo < column.length; rowNo++) { - let clazz = (type === "string" && !errorOnMixed) ? "text" : "number"; - let cell = column[rowNo]; - let content = cell.content; - let formatted; - let combined = false; + let clazz = (type === "string" && !errorOnMixed) ? "text" : "number"; + let cell = column[rowNo]; + let content = cell.content; + let contentNum = parseFloat(content) + let isNumber = !isNaN(contentNum) + let formatted = { content: (isNumber ? formatNumber(contentNum) : content) } + let combined = false; + + formatted["class"] = isNumber ? "number" : clazz if (typeof content == "undefined") { - formatted = { content: "." } + formatted["content"] = "." } else if (combine && rowNo > 0 && column[rowNo - 1].content == content) { - clazz += " combined"; - let content = " "; - if (!html) { - content = " "; - } - formatted = { content: content, class: clazz }; + formatted["class"] += " combined"; + formatted["content"] = html ? " " : " " combined = true; - - } else { - if (typeof content === "string") { - if (html) { - content = content.replace(/\u273B/g, "\u273B"); - //content = content.replace(/<(\w)/gi, function(p1, p2) { return '< '+p2; }) //Makes sure there is a space between a< b // breaks any tag used in cells... like the above - } - } - formatted = { content: content, "class": clazz }; + } else if(!isNumber) { + if (typeof content === "string" && html) + content = content.replace(/\u273B/g, "\u273B"); + + formatted["content"] = content } if (combined == false && cell.isStartOfGroup) @@ -247,12 +249,21 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote else switch(format) { - case "other": - break; //Wouldnt know what to do with it - + case "other": //Lets try to format it as a number I guess + { + let aNumberPerhaps = parseFloat(content) + + if(!isNaN(aNumberPerhaps)) + { + formatted["content"] = formatNumber(aNumberPerhaps) + isNumber = true + } + + break; + } case "monetary": { - formatted["content"] = `${formatMoney(_currency=currency, column[rowNo].content)}` + formatted["content"] = formatMoney(_currency=currency, content) formatted["class"] = "monetary" break; } @@ -260,7 +271,7 @@ function formatColumn(column, type, format, alignNumbers, combine, modelFootnote case "percentage": { if (!isNaN(parseFloat(content))) - formatted["content"] = "" + (100 * content.toFixed(0)) + (html ? " %" : "%") + formatted["content"] = "" + (100 * formatFixed(content, 0)) + (html ? " %" : "%") break; } @@ -804,38 +815,33 @@ function camelize (str) { }); } -function toExponential(number, decimalsExpon, paddingNeeded, html) { - let _sign = (html) ? "−" : "-"; - var exponentiated = number.toExponential(decimalsExpon).replace(/-/g, _sign) - - var split = exponentiated.split("e") - var mantissa = split[0] - var exponent = split[1] - var exponentSign = exponent.substr(0, 1) - var exponentNum = exponent.substr(1) - var padding - if (paddingNeeded) - padding = '' - else - padding = '' +// It is kind of sad the below is the way to go: +function getDecimalSeparator() +{ + const numberWithDecimalSeparator = 1.1; + + return Intl.NumberFormat(currentLocaleId) + .formatToParts(numberWithDecimalSeparator) + .find(part => part.type === 'decimal') + .value; +} +function toExponential(number, decimalsExpon, paddingNeeded, html) { + let _sign = (html) ? "−" : "-"; + let exponentiated = number.toExponential(decimalsExpon).replace(/-/g, _sign).replace(".", getDecimalSeparator()) + let split = exponentiated.split("e") + let mantissa = split[0] + let exponent = split[1] + let padding = !paddingNeeded ? '' : '' + + let reassembled = mantissa; + reassembled += !window.globSet.normalizedNotation ? "e" : (html ? "×10" : "×10") + "" + reassembled += html ? padding : "" + reassembled += exponent + reassembled += !window.globSet.normalizedNotation ? "" : "" - let reassembled; - if (html) { - if (window.globSet.normalizedNotation) { - reassembled = mantissa + "×10" + "" + padding + exponentSign + exponentNum + ""; - } else { - reassembled = mantissa + "e" + padding + exponentSign + exponentNum; - } - } else { - if (window.globSet.normalizedNotation) { - reassembled = mantissa + "×10" + "" + exponentSign + exponentNum + ""; - } else { - reassembled = mantissa + "e" + exponentSign + exponentNum; - } - } return reassembled; } From 6ff9056f0919e8d1e97ba85dd0b6125c91872043 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 19 Feb 2025 23:27:46 +0100 Subject: [PATCH 13/21] latenight problems get latenight solutions --- Desktop/mainwindow.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index 682e2a8c99..f37705b984 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -1007,6 +1007,7 @@ void MainWindow::analysisResultsChangedHandler(Analysis *analysis) showInstructions = false; } + _resultsJsInterface->setLocale(_languageModel->currentLocale().bcp47Name()); _resultsJsInterface->analysisChanged(analysis); setPackageModified(); @@ -1248,7 +1249,7 @@ void MainWindow::closeVariablesPage() void MainWindow::dataSetIOCompleted(FileEvent *event) { hideProgress(); - + if (event->operation() == FileEvent::FileNew) { } @@ -1257,7 +1258,7 @@ void MainWindow::dataSetIOCompleted(FileEvent *event) if (event->isSuccessful()) { populateUIfromDataSet(); - + _package->setCurrentFile(event->path()); if(event->osfPath() != "") @@ -1379,7 +1380,7 @@ void MainWindow::populateUIfromDataSet() JASPTIMER_SCOPE(MainWindow::populateUIfromDataSet); bool errorFound = false; stringstream errorMsg; - + _resultsJsInterface->setScrollAtAll(false); _analyses->loadAnalysesFromDatasetPackage(errorFound, errorMsg, _ribbonModel); From fa6dd66ac07b6b98a3ea2021b511944dec85ebdf Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 19 Feb 2025 23:41:11 +0100 Subject: [PATCH 14/21] make a setting to switch back to the US setting Sort of backwards compatible? --- .../JASP/Widgets/FileMenu/PrefsUI.qml | 10 ++++----- Desktop/main.cpp | 2 +- Desktop/utilities/languagemodel.cpp | 22 +++++++++---------- Desktop/utilities/languagemodel.h | 10 ++++----- Desktop/utilities/settings.cpp | 2 +- Desktop/utilities/settings.h | 2 +- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml index 15810788d6..3ea5141879 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml @@ -196,11 +196,11 @@ ScrollView CheckBox { - id: useSystemLocale - label: qsTr("Use system locale") - checked: languageModel.useSystemLocale - onCheckedChanged: languageModel.useSystemLocale = checked - toolTip: qsTr("Use system locale for display of numbers, dates, times and currency. Might require a restart of JASP to be effective.") + id: useUsLocale + label: qsTr("Use American English locale for numbers") + checked: languageModel.useUsLocale + onCheckedChanged: languageModel.useUsLocale = checked + toolTip: qsTr("Use US english locale for display of numbers, dates, times and currency. Decimal separator will be '.' for instance.") } diff --git a/Desktop/main.cpp b/Desktop/main.cpp index a61b2b1ce1..6046f53673 100644 --- a/Desktop/main.cpp +++ b/Desktop/main.cpp @@ -461,7 +461,7 @@ int main(int argc, char *argv[]) QmlUtils::configureQMLCacheDir(); #endif - if(!Settings::value(Settings::USE_SYSTEM_LOCALE).toBool()) + if(Settings::value(Settings::USE_USA_LOCALE).toBool()) QLocale::setDefault(QLocale(QLocale::English)); // make decimal points == . //Now we convert all these strings in args back to an int and a char * array. diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 32f4fa71d8..51a3ee467d 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -73,7 +73,7 @@ void LanguageModel::initialize() findQmFiles(); _currentLanguageCode = Settings::value(Settings::PREFERRED_LANGUAGE).toString(); - _useSystemLocale = Settings::value(Settings::USE_SYSTEM_LOCALE).toBool(); + _useUsLocale = Settings::value(Settings::USE_USA_LOCALE).toBool(); if (!LanguageInfo::isLanguageAllowed(_currentLanguageCode)) @@ -84,8 +84,7 @@ void LanguageModel::initialize() // Load all translated language files for specific language loadQmFilesForLanguage(_currentLanguageCode); - if(!useSystemLocale()) - setDefaultLocaleFromCurrent(); + setDefaultLocaleFromCurrent(); _qml->retranslate(); } @@ -164,7 +163,7 @@ void LanguageModel::refreshAll() emit aboutToChangeLanguage(); //asks all analyses to abort and to block refresh ResultsJsInterface::singleton()->setResultsLoaded(false); //So that javascript starts queueing any Js (such as title changed of an analysis) until the page is reloaded - if(!useSystemLocale()) + if(!useUsLocale()) setDefaultLocaleFromCurrent(); //On linux it somehow ignores the newer settings, so instead of pausing we kill the engines... https://github.com/jasp-stats/jasp-test-release/issues/1046 @@ -191,16 +190,16 @@ void LanguageModel::refreshAll() emit languageChangeDone(); } -void LanguageModel::setUseSystemLocale(bool useIt) +void LanguageModel::setUseUsLocale(bool useIt) { - if(_useSystemLocale == useIt) + if(_useUsLocale == useIt) return; - _useSystemLocale = useIt; + _useUsLocale = useIt; - Settings::setValue(Settings::USE_SYSTEM_LOCALE , _useSystemLocale); + Settings::setValue(Settings::USE_USA_LOCALE , _useUsLocale); - emit useSystemLocaleChanged(); + emit useUsLocaleChanged(); refreshAll(); } @@ -329,7 +328,7 @@ void LanguageModel::loadQmFilesForLanguage(const QString& languageCode) { LanguageInfo & li = _languages[languageCode]; - for (QString qmfilename: li.qmFilenames) + for (const QString & qmfilename: li.qmFilenames) loadQmFile(qmfilename); } @@ -375,7 +374,8 @@ QString LanguageModel::currentLanguage() const QLocale LanguageModel::currentLocale() const { - return _languages[_currentLanguageCode].locale; + + return useUsLocale() ? _defaultLocale : _languages[_currentLanguageCode].locale; } bool LanguageModel::hasDefaultLanguage() const diff --git a/Desktop/utilities/languagemodel.h b/Desktop/utilities/languagemodel.h index d4fb7ea381..aa305318d6 100644 --- a/Desktop/utilities/languagemodel.h +++ b/Desktop/utilities/languagemodel.h @@ -19,7 +19,7 @@ class LanguageModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(QString currentLanguage READ currentLanguage WRITE setCurrentLanguage NOTIFY currentLanguageChanged ) Q_PROPERTY(QLocale currentLocale READ currentLocale NOTIFY currentLocaleChanged ) - Q_PROPERTY(bool useSystemLocale READ useSystemLocale WRITE setUseSystemLocale NOTIFY useSystemLocaleChanged ) + Q_PROPERTY(bool useUsLocale READ useUsLocale WRITE setUseUsLocale NOTIFY useUsLocaleChanged ) struct LanguageInfo { @@ -64,7 +64,7 @@ class LanguageModel : public QAbstractListModel QString currentLanguage() const; QLocale currentLocale() const; bool hasDefaultLanguage() const; - bool useSystemLocale() const { return _useSystemLocale; } + bool useUsLocale() const { return _useUsLocale; } //This function (currentTranslationSuffix) should be made obsolete through the abolishment of all the _nl etc files: static QString currentTranslationSuffix() { return _singleton->hasDefaultLanguage() ? "" : ("_" + _singleton->currentLanguageCode()); } @@ -74,14 +74,14 @@ class LanguageModel : public QAbstractListModel public slots: void setCurrentLanguage(QString language); - void setUseSystemLocale(bool useIt); + void setUseUsLocale(bool useIt); void loadModuleTranslationFiles(Modules::DynamicModule *dyn); void resultsPageLoaded(); signals: void currentLanguageChanged(); void currentLocaleChanged(QString); - void useSystemLocaleChanged(); + void useUsLocaleChanged(); void aboutToChangeLanguage(); void languageChangeDone(); void pauseEngines(bool unloadData = false); @@ -106,7 +106,7 @@ public slots: QMap _languages; QVector _translators; bool _shouldEmitLanguageChanged = false, - _useSystemLocale; + _useUsLocale; }; diff --git a/Desktop/utilities/settings.cpp b/Desktop/utilities/settings.cpp index e77eeacc93..038958c3ff 100644 --- a/Desktop/utilities/settings.cpp +++ b/Desktop/utilities/settings.cpp @@ -39,7 +39,7 @@ const Settings::Setting Settings::Values[] = { {"userHasGitHubAccount", false}, {"preferredLanguage", "en"}, {"preferredCountry", QLocale::World}, - {"useSystemLocale", true}, + {"useUsLocale", false}, {"themeName", "lightTheme"}, {"useNativeFileDialog", true}, {"disableAnimations", false}, diff --git a/Desktop/utilities/settings.h b/Desktop/utilities/settings.h index 645930b39f..d03d7879b1 100644 --- a/Desktop/utilities/settings.h +++ b/Desktop/utilities/settings.h @@ -43,7 +43,7 @@ class Settings { USER_HAS_GITHUB_ACCOUNT, PREFERRED_LANGUAGE, PREFERRED_COUNTRY, - USE_SYSTEM_LOCALE, + USE_USA_LOCALE, THEME_NAME, USE_NATIVE_FILE_DIALOG, DISABLE_ANIMATIONS, From 9fa82f6b2f882f0f85065a6c3198a11e27557b3c Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Thu, 20 Feb 2025 01:07:01 +0100 Subject: [PATCH 15/21] make sure comparing results uses english, us locale and doesnt ask for updates etc --- Desktop/mainwindow.cpp | 3 +++ Desktop/utilities/application.cpp | 9 +++++--- Desktop/utilities/languagemodel.cpp | 35 +++++++++++++++++++---------- Modules/jaspTestModule | 2 +- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/Desktop/mainwindow.cpp b/Desktop/mainwindow.cpp index f37705b984..091502464b 100644 --- a/Desktop/mainwindow.cpp +++ b/Desktop/mainwindow.cpp @@ -171,6 +171,9 @@ MainWindow::MainWindow(QApplication * application) : QObject(application), _appl void MainWindow::checkForUpdates() { + if(resultXmlCompare::compareResults::theOne()->testMode()) + return; + if(PreferencesModel::prefs()->checkUpdatesAskUser()) { bool answer = MessageForwarder::showYesNo( diff --git a/Desktop/utilities/application.cpp b/Desktop/utilities/application.cpp index e1e3bb4e7c..7fc2641f3a 100644 --- a/Desktop/utilities/application.cpp +++ b/Desktop/utilities/application.cpp @@ -18,12 +18,12 @@ #include "application.h" +#include "resultstesting/compareresults.h" +#include "utilities/settings.h" #include +#include #include - #include "log.h" -#include "utilities/settings.h" -#include void Application::init(QString filePath, bool newData, bool unitTest, int timeOut, bool save, bool logToFile, const Json::Value & dbJson, QString reportingPath) { @@ -33,6 +33,9 @@ void Application::init(QString filePath, bool newData, bool unitTest, int timeOu Settings::setValue(Settings::LOG_TO_FILE, true); Dirs::setReportingDir(fq(reportingPath)); + + if(unitTest) + resultXmlCompare::compareResults::theOne()->enableTestMode(); //So languagemodel can be aware _mainWindow = new MainWindow(this); diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 51a3ee467d..1277304494 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -1,14 +1,15 @@ #include "languagemodel.h" -#include "log.h" -#include "utilities/settings.h" -#include -#include -#include "dirs.h" #include "utilities/qutils.h" -#include +#include "utilities/settings.h" +#include "resultstesting/compareresults.h" #include "results/resultsjsinterface.h" #include "modules/dynamicmodule.h" #include "columnutils.h" +#include +#include +#include "dirs.h" +#include +#include "log.h" LanguageModel * LanguageModel::_singleton = nullptr; QLocale LanguageModel::_defaultLocale = QLocale(QLocale::English, QLocale::UnitedStates); @@ -73,8 +74,13 @@ void LanguageModel::initialize() findQmFiles(); _currentLanguageCode = Settings::value(Settings::PREFERRED_LANGUAGE).toString(); - _useUsLocale = Settings::value(Settings::USE_USA_LOCALE).toBool(); + _useUsLocale = Settings::value(Settings::USE_USA_LOCALE).toBool(); + if(resultXmlCompare::compareResults::theOne()->testMode()) + { + _currentLanguageCode = defaultLanguageCode; + _useUsLocale = true; + } if (!LanguageInfo::isLanguageAllowed(_currentLanguageCode)) _currentLanguageCode = defaultLanguageCode; @@ -129,6 +135,9 @@ QHash LanguageModel::roleNames() const void LanguageModel::setCurrentLanguage(QString language) { + if(resultXmlCompare::compareResults::theOne()->testMode()) + return; + QString languageCode = language.split(" ")[0]; if (languageCode == _currentLanguageCode || languageCode.isEmpty() || !_languages.contains(languageCode)) return; @@ -163,8 +172,7 @@ void LanguageModel::refreshAll() emit aboutToChangeLanguage(); //asks all analyses to abort and to block refresh ResultsJsInterface::singleton()->setResultsLoaded(false); //So that javascript starts queueing any Js (such as title changed of an analysis) until the page is reloaded - if(!useUsLocale()) - setDefaultLocaleFromCurrent(); + setDefaultLocaleFromCurrent(); //On linux it somehow ignores the newer settings, so instead of pausing we kill the engines... https://github.com/jasp-stats/jasp-test-release/issues/1046 //But I do not know if it necessary, because the modules-translations aren't working. @@ -179,8 +187,11 @@ void LanguageModel::refreshAll() emit stopEngines(); _qml->retranslate(); - Settings::setValue(Settings::PREFERRED_LANGUAGE , currentLanguageCode()); - Settings::setValue(Settings::PREFERRED_COUNTRY, currentLocale().country()); + if(!resultXmlCompare::compareResults::theOne()->testMode()) + { + Settings::setValue(Settings::PREFERRED_LANGUAGE , currentLanguageCode()); + Settings::setValue(Settings::PREFERRED_COUNTRY, currentLocale().country()); + } _shouldEmitLanguageChanged = true; ResultsJsInterface::singleton()->resetResults(); @@ -192,7 +203,7 @@ void LanguageModel::refreshAll() void LanguageModel::setUseUsLocale(bool useIt) { - if(_useUsLocale == useIt) + if(_useUsLocale == useIt || resultXmlCompare::compareResults::theOne()->testMode()) return; _useUsLocale = useIt; diff --git a/Modules/jaspTestModule b/Modules/jaspTestModule index ef62b09935..06fc807ce9 160000 --- a/Modules/jaspTestModule +++ b/Modules/jaspTestModule @@ -1 +1 @@ -Subproject commit ef62b09935b1d74ecca00bf66d6a64c74d0bca5e +Subproject commit 06fc807ce90078d631827b98e008f46aca322778 From 7501ae3499ad43cbb28551db487bc1062f95f595 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Thu, 20 Feb 2025 11:10:39 +0100 Subject: [PATCH 16/21] remove debug print from js Co-authored-by: Shun Wang --- Desktop/html/js/utils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Desktop/html/js/utils.js b/Desktop/html/js/utils.js index c4d6e918ce..cda1389b35 100644 --- a/Desktop/html/js/utils.js +++ b/Desktop/html/js/utils.js @@ -3,7 +3,6 @@ var currentLocaleId = "en" function setCurrentLocaleID(id) { - console.log(`Setting current locale to id "${id}"`) currentLocale = new Intl.Locale(id) currentLocaleId = id } From d84f855b76d9a64716aba99f6852a7b9a3306950 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Thu, 20 Feb 2025 15:16:35 +0100 Subject: [PATCH 17/21] add #include --- CommonData/columnutils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/CommonData/columnutils.h b/CommonData/columnutils.h index bb2b47f3e6..3cff0ce19a 100644 --- a/CommonData/columnutils.h +++ b/CommonData/columnutils.h @@ -4,6 +4,7 @@ #include #include "utils.h" #include +#include class ColumnUtils { From 13065b3877b3789ea14cb171cc4634ae8777b705 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Thu, 20 Feb 2025 16:21:59 +0100 Subject: [PATCH 18/21] make sure all string -> double/int also uses locale fixes shuns crash --- CommonData/columnutils.cpp | 33 ++++++++++++++++------------- CommonData/columnutils.h | 9 ++++++-- Desktop/utilities/languagemodel.cpp | 21 ++++++++++++++++-- QMLComponents/utilities/qutils.cpp | 31 ++++++--------------------- 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/CommonData/columnutils.cpp b/CommonData/columnutils.cpp index 734d65bc06..74d9413db4 100644 --- a/CommonData/columnutils.cpp +++ b/CommonData/columnutils.cpp @@ -16,12 +16,22 @@ using namespace std; using namespace boost::posix_time; using namespace boost; -std::string ColumnUtils::_decimalPoint = "."; -std::string ColumnUtils::_currentQLocaleId = "C"; +std::string ColumnUtils::_decimalPoint = "."; +std::string ColumnUtils::_currentQLocaleId = "C"; +ColumnUtils::toDoubleF ColumnUtils::_extraStringToDouble; +ColumnUtils::toIntF ColumnUtils::_extraStringToInt; +void ColumnUtils::setExtraStringToNumber(toDoubleF newDoubleFunc, toIntF newIntFunc) +{ + _extraStringToDouble = newDoubleFunc; + _extraStringToInt = newIntFunc; +} bool ColumnUtils::getIntValue(const string &value, int &intValue) { + if(_extraStringToInt && _extraStringToInt(value, intValue)) + return true; + try { intValue = boost::lexical_cast(value); @@ -34,20 +44,14 @@ bool ColumnUtils::getIntValue(const string &value, int &intValue) bool ColumnUtils::isIntValue(const string &value) { - try - { - boost::lexical_cast(value); - return true; - } - catch (...) {} - - return false; + int dummy; + return getIntValue(value, dummy); } bool ColumnUtils::getIntValue(const double &value, int &intValue) { JASPTIMER_SCOPE(ColumnUtils::getIntValue); - + try { double intPart; @@ -75,6 +79,9 @@ bool ColumnUtils::getDoubleValue(const string &value, double &doubleValue) doubleValue = std::numeric_limits::infinity() * (value == "-∞" ? -1 : 1); return true; } + + if(_extraStringToDouble && _extraStringToDouble(value, doubleValue)) + return true; try { @@ -90,7 +97,6 @@ bool ColumnUtils::getDoubleValue(const string &value, double &doubleValue) } catch (...) { - } } @@ -176,8 +182,6 @@ bool ColumnUtils::convertVecToDouble(const stringvec & values, doublevec & doubl return true; } - - std::string ColumnUtils::deEuropeaniseForImport(std::string value) { int dots = 0, @@ -240,7 +244,6 @@ void ColumnUtils::setAlternativeDoubleToString(doubleF newDoubleFunc) _alternativeDoubleToString = newDoubleFunc; } - std::string ColumnUtils::doubleToString(double dbl, int precision) { JASPTIMER_SCOPE(ColumnUtils::doubleToString); diff --git a/CommonData/columnutils.h b/CommonData/columnutils.h index 3cff0ce19a..697b14b270 100644 --- a/CommonData/columnutils.h +++ b/CommonData/columnutils.h @@ -9,7 +9,9 @@ class ColumnUtils { public: - typedef std::function doubleF; + typedef std::function doubleF; + typedef std::function toDoubleF; + typedef std::function toIntF; friend class PreferencesModel; @@ -30,7 +32,8 @@ class ColumnUtils static bool convertVecToInt( const stringvec & values, intvec & intValues, intset & uniqueValues); static bool convertVecToDouble( const stringvec & values, doublevec & doubleValues); - static void setAlternativeDoubleToString(doubleF newDoubleFunc); + static void setAlternativeDoubleToString( doubleF newDoubleFunc); + static void setExtraStringToNumber( toDoubleF newDoubleFunc, toIntF newIntFunc); static const std::string & decimalPoint() { return _decimalPoint; } static void setDecimalPoint(const std::string & p) { _decimalPoint = p;} @@ -40,6 +43,8 @@ class ColumnUtils private: static std::string _convertEscapedUnicodeToUTF8( std::string hex); static doubleF _alternativeDoubleToString; + static toDoubleF _extraStringToDouble; + static toIntF _extraStringToInt; static std::string _decimalPoint, _currentQLocaleId; }; diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 1277304494..de84104a92 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -154,12 +154,29 @@ void LanguageModel::setDefaultLocaleFromCurrent() { QLocale::setDefault(currentLocale()); - static ColumnUtils::doubleF altFunc = [&](double dbl, int precision) + static ColumnUtils::doubleF altFuncToString = [&](double dbl, int precision) { return fq(currentLocale().toString(dbl, 'g', precision)); }; + + static ColumnUtils::toDoubleF altFuncToDouble = [&](const std::string & str, double & dbl) + { + bool isDouble = false; + dbl = currentLocale().toDouble(tq(str), &isDouble); + + return isDouble; + }; + + static ColumnUtils::toIntF altFuncToInt = [&](const std::string & str, int & intVal) + { + bool isInt = false; + intVal = currentLocale().toInt(tq(str), &isInt); + + return isInt; + }; - ColumnUtils::setAlternativeDoubleToString( altFunc ); + ColumnUtils::setAlternativeDoubleToString( altFuncToString ); + ColumnUtils::setExtraStringToNumber( altFuncToDouble, altFuncToInt ); ColumnUtils::setCurrentQLocaleId( fq(currentLocale().bcp47Name()) ); ColumnUtils::setDecimalPoint( fq(currentLocale().decimalPoint()) ); diff --git a/QMLComponents/utilities/qutils.cpp b/QMLComponents/utilities/qutils.cpp index b28d61c604..93bbb14e02 100644 --- a/QMLComponents/utilities/qutils.cpp +++ b/QMLComponents/utilities/qutils.cpp @@ -330,43 +330,27 @@ QString QColumnUtils::_lastQLocaleId = "C"; bool QColumnUtils::getIntValue(const QString &value, int &intValue) { - bool isInt = false; - intValue = currentQLocale().toInt(value, &isInt); - - return isInt || ColumnUtils::getIntValue(fq(value), intValue); + return ColumnUtils::getIntValue(fq(value), intValue); } bool QColumnUtils::getDoubleValue(const QString &value, double &doubleValue) { - bool isDouble = false; - doubleValue = currentQLocale().toDouble(value, &isDouble); - - return isDouble || ColumnUtils::getDoubleValue(fq(value), doubleValue); + return ColumnUtils::getDoubleValue(fq(value), doubleValue); } doubleset QColumnUtils::getDoubleValues(const QStringList &values, bool stripNAN) { - doubleset result; - for (const QString & val : values) - { - double doubleValue; - if (getDoubleValue(val, doubleValue) && !std::isnan(doubleValue)) - result.insert(doubleValue); - } - - return result; + return ColumnUtils::getDoubleValues(fql(values), stripNAN); } bool QColumnUtils::isIntValue(const QString &value) { - static int last; - return getIntValue(value, last); + return ColumnUtils::isIntValue(fq(value)); } bool QColumnUtils::isDoubleValue(const QString &value) { - static double last; - return getDoubleValue(value, last); + return ColumnUtils::isDoubleValue(fq(value)); } QLocale QColumnUtils::currentQLocale() @@ -384,11 +368,10 @@ QLocale QColumnUtils::currentQLocale() QString QColumnUtils::doubleToString(double dbl, int precision) { - return currentQLocale().toString(dbl, 'g', precision); + return tq(ColumnUtils::doubleToString(dbl, precision)); } QString QColumnUtils::doubleToStringMaxPrec(double dbl) { - constexpr auto max_precision{std::numeric_limits::digits10 + 1}; - return doubleToString(dbl, max_precision); + return tq(ColumnUtils::doubleToStringMaxPrec(dbl)); } From 72ca4dc03855b8d9a5c9c8d3141b2665e2848379 Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 26 Feb 2025 14:20:58 +0100 Subject: [PATCH 19/21] add support for choosing the locale you want instead of the one belonging to the selected language also fix popups --- .../JASP/Widgets/FileMenu/PrefsUI.qml | 41 +++++- Desktop/main.cpp | 2 +- Desktop/utilities/languagemodel.cpp | 127 +++++++++++++++-- Desktop/utilities/languagemodel.h | 131 +++++++++++------- Desktop/utilities/settings.cpp | 4 +- Desktop/utilities/settings.h | 4 +- .../components/JASP/Controls/DropDown.qml | 13 +- 7 files changed, 243 insertions(+), 79 deletions(-) diff --git a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml index 3ea5141879..ac8546c792 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml @@ -190,17 +190,46 @@ ScrollView startValue: languageModel.currentLanguage onValueChanged: languageModel.currentLanguage = value - KeyNavigation.tab: altnavcheckbox + KeyNavigation.tab: useAlternativeLocale } CheckBox { - id: useUsLocale - label: qsTr("Use American English locale for numbers") - checked: languageModel.useUsLocale - onCheckedChanged: languageModel.useUsLocale = checked - toolTip: qsTr("Use US english locale for display of numbers, dates, times and currency. Decimal separator will be '.' for instance.") + id: useAlternativeLocale + label: qsTr("Use an alternative locale for numbers") + checked: languageModel.useAlternativeLocale + onCheckedChanged: languageModel.useAlternativeLocale = checked + toolTip: qsTr("Use the locale specified below for display of numbers and currency.") //dates and times will have to be added later + + KeyNavigation.tab: alternativeLocaleTerritory + } + + Group + { + enabled: languageModel.useAlternativeLocale + DropDown + { + id: alternativeLocaleLanguage + label: qsTr("Alt. language") + values: languageModel.altLanguages + startValue: languageModel.currentAltLanguage + onValueChanged: if(value != "") languageModel.currentAltLanguage = value + addEmptyValue: false + KeyNavigation.tab: alternativeLocaleTerritory + } + + DropDown + { + id: alternativeLocaleTerritory + label: qsTr("Alt. territory") + values: languageModel.altTerritories + startValue: languageModel.currentAltTerritory + onValueChanged: if(value != "") languageModel.currentAltTerritory = value + addEmptyValue: false + KeyNavigation.tab: altnavcheckbox + } + } diff --git a/Desktop/main.cpp b/Desktop/main.cpp index 6046f53673..5ee3dd22c6 100644 --- a/Desktop/main.cpp +++ b/Desktop/main.cpp @@ -461,7 +461,7 @@ int main(int argc, char *argv[]) QmlUtils::configureQMLCacheDir(); #endif - if(Settings::value(Settings::USE_USA_LOCALE).toBool()) + if(Settings::value(Settings::USE_ALT_LOCALE).toBool()) QLocale::setDefault(QLocale(QLocale::English)); // make decimal points == . //Now we convert all these strings in args back to an int and a char * array. diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index de84104a92..5a677bd572 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -11,8 +11,9 @@ #include #include "log.h" -LanguageModel * LanguageModel::_singleton = nullptr; -QLocale LanguageModel::_defaultLocale = QLocale(QLocale::English, QLocale::UnitedStates); +LanguageModel * LanguageModel::_singleton = nullptr; +QLocale LanguageModel::_defaultLocale = QLocale(QLocale::English, QLocale::UnitedStates); +QLocale LanguageModel::_alternativeLocale = QLocale(QLocale::English, QLocale::UnitedStates); // the bool indicates whether the language is considered "complete" or not QMap LanguageModel::LanguageInfo::_allowedLanguages = @@ -73,13 +74,19 @@ void LanguageModel::initialize() findQmFiles(); - _currentLanguageCode = Settings::value(Settings::PREFERRED_LANGUAGE).toString(); - _useUsLocale = Settings::value(Settings::USE_USA_LOCALE).toBool(); + _currentLanguageCode = Settings::value(Settings::PREFERRED_LANGUAGE) .toString(); + _useAlternativeLocale = Settings::value(Settings::USE_ALT_LOCALE) .toBool(); + _currentAltLanguage = Settings::value(Settings::ALT_LOCALE_LANGUAGE).toString(); + _currentAltTerritory = Settings::value(Settings::ALT_LOCALE_REGION) .toString(); + + fillAltOptions(); if(resultXmlCompare::compareResults::theOne()->testMode()) { - _currentLanguageCode = defaultLanguageCode; - _useUsLocale = true; + _currentLanguageCode = defaultLanguageCode; + _useAlternativeLocale = true; + _currentAltLanguage = _defaultLocale.nativeLanguageName(); + _currentAltTerritory = _defaultLocale.nativeTerritoryName(); } if (!LanguageInfo::isLanguageAllowed(_currentLanguageCode)) @@ -96,6 +103,62 @@ void LanguageModel::initialize() } } +void LanguageModel::fillAltOptions() +{ + QList allLocales = QLocale::matchingLocales( + QLocale::AnyLanguage, + QLocale::AnyScript, + QLocale::AnyCountry); + + QSet nativeLanguageNames; + _nativeLanguageNameToEnum.clear(); + + for(QLocale & l : allLocales) + { + _nativeLanguageNameToEnum[l.nativeLanguageName()] = l.language(); + nativeLanguageNames.insert(l.nativeLanguageName()); + } + nativeLanguageNames.remove(""); + _altLanguages = QStringList(nativeLanguageNames.begin(), nativeLanguageNames.end()); + + std::sort(_altLanguages.begin(), _altLanguages.end()); + + fillAltTerritories(); +} + +void LanguageModel::fillAltTerritories() +{ + QLocale::Language languageChosen = _currentAltLanguage == "" ? _defaultLocale.language() : _nativeLanguageNameToEnum[_currentAltLanguage]; + + QList languageLocales = QLocale::matchingLocales( + languageChosen, + QLocale::AnyScript, + QLocale::AnyCountry); + + QSet nativeTerritoryNames; + _nativeTerritoryNameToEnum.clear(); + + bool previouslyChosenTerritoryFound = false; + for(QLocale & l : languageLocales) + { + QString territory = l.nativeTerritoryName(); + + nativeTerritoryNames.insert(territory); + _nativeTerritoryNameToEnum [territory] = l.territory(); + + if(_currentAltTerritory == territory) + previouslyChosenTerritoryFound = true; + } + nativeTerritoryNames.remove(""); + _altTerritories = QStringList(nativeTerritoryNames.begin(), nativeTerritoryNames.end()); + + std::sort(_altTerritories.begin(), _altTerritories.end()); + emit altTerritoriesChanged(); + + if(!previouslyChosenTerritoryFound || _currentAltTerritory == "") + setCurrentAltTerritory(QLocale(languageChosen).nativeTerritoryName()); +} + QVariant LanguageModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= rowCount()) @@ -152,6 +215,8 @@ void LanguageModel::setCurrentLanguage(QString language) void LanguageModel::setDefaultLocaleFromCurrent() { + setAlternativeLocaleStatic(); + QLocale::setDefault(currentLocale()); static ColumnUtils::doubleF altFuncToString = [&](double dbl, int precision) @@ -218,16 +283,16 @@ void LanguageModel::refreshAll() emit languageChangeDone(); } -void LanguageModel::setUseUsLocale(bool useIt) +void LanguageModel::setUseAlternativeLocale(bool useIt) { - if(_useUsLocale == useIt || resultXmlCompare::compareResults::theOne()->testMode()) + if(_useAlternativeLocale == useIt || resultXmlCompare::compareResults::theOne()->testMode()) return; - _useUsLocale = useIt; + _useAlternativeLocale = useIt; - Settings::setValue(Settings::USE_USA_LOCALE , _useUsLocale); + Settings::setValue(Settings::USE_ALT_LOCALE , _useAlternativeLocale); - emit useUsLocaleChanged(); + emit useAlternativeLocaleChanged(); refreshAll(); } @@ -403,7 +468,7 @@ QString LanguageModel::currentLanguage() const QLocale LanguageModel::currentLocale() const { - return useUsLocale() ? _defaultLocale : _languages[_currentLanguageCode].locale; + return useAlternativeLocale() ? _alternativeLocale : _languages[_currentLanguageCode].locale; } bool LanguageModel::hasDefaultLanguage() const @@ -411,3 +476,41 @@ bool LanguageModel::hasDefaultLanguage() const const LanguageInfo & li = _languages[_currentLanguageCode]; return li.locale == _defaultLocale; } + +QString LanguageModel::currentAltLanguage() const +{ + return _currentAltLanguage; +} + +void LanguageModel::setCurrentAltLanguage(const QString &newCurrentAltLanguage) +{ + if (_currentAltLanguage == newCurrentAltLanguage) + return; + + _currentAltLanguage = newCurrentAltLanguage; + emit currentAltLanguageChanged(); + + fillAltTerritories(); + + refreshAll(); +} + +void LanguageModel::setAlternativeLocaleStatic() +{ + _alternativeLocale = QLocale(_nativeLanguageNameToEnum[_currentAltLanguage], _nativeTerritoryNameToEnum[_currentAltTerritory]); +} + +QString LanguageModel::currentAltTerritory() const +{ + return _currentAltTerritory; +} + +void LanguageModel::setCurrentAltTerritory(const QString &newCurrentAltTerritory) +{ + if (_currentAltTerritory == newCurrentAltTerritory) + return; + _currentAltTerritory = newCurrentAltTerritory; + emit currentAltTerritoryChanged(); + + refreshAll(); +} diff --git a/Desktop/utilities/languagemodel.h b/Desktop/utilities/languagemodel.h index aa305318d6..1a98fcff84 100644 --- a/Desktop/utilities/languagemodel.h +++ b/Desktop/utilities/languagemodel.h @@ -17,9 +17,13 @@ namespace Modules { class DynamicModule; } class LanguageModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(QString currentLanguage READ currentLanguage WRITE setCurrentLanguage NOTIFY currentLanguageChanged ) - Q_PROPERTY(QLocale currentLocale READ currentLocale NOTIFY currentLocaleChanged ) - Q_PROPERTY(bool useUsLocale READ useUsLocale WRITE setUseUsLocale NOTIFY useUsLocaleChanged ) + Q_PROPERTY(QString currentLanguage READ currentLanguage WRITE setCurrentLanguage NOTIFY currentLanguageChanged ) + Q_PROPERTY(QLocale currentLocale READ currentLocale NOTIFY currentLocaleChanged ) + Q_PROPERTY(bool useAlternativeLocale READ useAlternativeLocale WRITE setUseAlternativeLocale NOTIFY useAlternativeLocaleChanged ) + Q_PROPERTY(QStringList altLanguages READ altLanguages CONSTANT ) + Q_PROPERTY(QStringList altTerritories READ altTerritories NOTIFY altTerritoriesChanged ) + Q_PROPERTY(QString currentAltLanguage READ currentAltLanguage WRITE setCurrentAltLanguage NOTIFY currentAltLanguageChanged ) + Q_PROPERTY(QString currentAltTerritory READ currentAltTerritory WRITE setCurrentAltTerritory NOTIFY currentAltTerritoryChanged ) struct LanguageInfo { @@ -48,65 +52,88 @@ class LanguageModel : public QAbstractListModel LocalNameRole }; - explicit LanguageModel(QApplication *app = nullptr, QQmlApplicationEngine *qml = nullptr, QObject *parent = nullptr) ; - ~LanguageModel() override { _singleton = nullptr; } + explicit LanguageModel(QApplication *app = nullptr, QQmlApplicationEngine *qml = nullptr, QObject *parent = nullptr) ; + ~LanguageModel() override { _singleton = nullptr; } - void initialize(); - void refreshAll(); + void initialize(); + void refreshAll(); + + int rowCount(const QModelIndex & = QModelIndex()) const override { return _languages.size(); } + int columnCount(const QModelIndex & = QModelIndex()) const override { return 1; } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QHash roleNames() const override; + + QString currentLanguageCode() const { return _currentLanguageCode; } + QString currentLanguage() const; + QLocale currentLocale() const; + bool hasDefaultLanguage() const; + bool useAlternativeLocale() const { return _useAlternativeLocale; } + QString currentAltLanguage() const; + QString currentAltTerritory() const; + QStringList altLanguages() const { return _altLanguages; } + QStringList altTerritories() const { return _altTerritories; } - int rowCount(const QModelIndex & = QModelIndex()) const override { return _languages.size(); } - int columnCount(const QModelIndex & = QModelIndex()) const override { return 1; } - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - QHash roleNames() const override; - - QString currentLanguageCode() const { return _currentLanguageCode; } - QString currentLanguage() const; - QLocale currentLocale() const; - bool hasDefaultLanguage() const; - bool useUsLocale() const { return _useUsLocale; } //This function (currentTranslationSuffix) should be made obsolete through the abolishment of all the _nl etc files: - static QString currentTranslationSuffix() { return _singleton->hasDefaultLanguage() ? "" : ("_" + _singleton->currentLanguageCode()); } + static QString currentTranslationSuffix() { return _singleton->hasDefaultLanguage() ? "" : ("_" + _singleton->currentLanguageCode()); } - void setApplicationEngine(QQmlApplicationEngine * ae) { _qml = ae; } - void setDefaultLocaleFromCurrent(); + void setApplicationEngine(QQmlApplicationEngine * ae) { _qml = ae; } + void setDefaultLocaleFromCurrent(); + public slots: - void setCurrentLanguage(QString language); - void setUseUsLocale(bool useIt); - void loadModuleTranslationFiles(Modules::DynamicModule *dyn); - void resultsPageLoaded(); + void setCurrentLanguage(QString language); + void setUseAlternativeLocale(bool useIt); + void setCurrentAltLanguage(const QString &newCurrentAltLanguage); + void setCurrentAltTerritory(const QString &newCurrentAltTerritory); + void loadModuleTranslationFiles(Modules::DynamicModule *dyn); + void resultsPageLoaded(); signals: - void currentLanguageChanged(); - void currentLocaleChanged(QString); - void useUsLocaleChanged(); - void aboutToChangeLanguage(); - void languageChangeDone(); - void pauseEngines(bool unloadData = false); - void stopEngines(); - void resumeEngines(); - + void currentLanguageChanged(); + void currentLocaleChanged(QString); + void currentAltLanguageChanged(); + void currentAltTerritoryChanged(); + void useAlternativeLocaleChanged(); + void altTerritoriesChanged(); + void aboutToChangeLanguage(); + void languageChangeDone(); + void pauseEngines(bool unloadData = false); + void stopEngines(); + void resumeEngines(); + private: - static LanguageModel * _singleton; - static QLocale _defaultLocale; - - void findQmFiles(); - void loadQmFilesForLanguage(const QString& languageCode); - void loadQmFile(const QString& filename); - void removeTranslators(); - bool isValidLocaleName(const QString& filename, QLocale & loc, QString & languageCode); - - QApplication * _mApp = nullptr; - QTranslator * _mTranslator = nullptr; - QQmlApplicationEngine * _qml = nullptr; - QString _currentLanguageCode, - _qmLocation; - QMap _languages; - QVector _translators; - bool _shouldEmitLanguageChanged = false, - _useUsLocale; + static LanguageModel * _singleton; + static QLocale _defaultLocale; + static QLocale _alternativeLocale; + + void findQmFiles(); + void loadQmFilesForLanguage(const QString& languageCode); + void loadQmFile(const QString& filename); + void removeTranslators(); + bool isValidLocaleName(const QString& filename, QLocale & loc, QString & languageCode); + void fillAltOptions(); + void fillAltTerritories(); + void setAlternativeLocaleStatic(); + + QApplication * _mApp = nullptr; + QTranslator * _mTranslator = nullptr; + QQmlApplicationEngine * _qml = nullptr; + QString _currentLanguageCode, + _qmLocation, + _currentAltLanguage, + _currentAltTerritory; + QMap _languages; + QVector _translators; + bool _shouldEmitLanguageChanged = false, + _useAlternativeLocale; + QStringList _altLanguages, + _altTerritories; + std::map _nativeLanguageNameToEnum; + std::map _nativeTerritoryNameToEnum; + + }; diff --git a/Desktop/utilities/settings.cpp b/Desktop/utilities/settings.cpp index 038958c3ff..17f3e65801 100644 --- a/Desktop/utilities/settings.cpp +++ b/Desktop/utilities/settings.cpp @@ -39,7 +39,9 @@ const Settings::Setting Settings::Values[] = { {"userHasGitHubAccount", false}, {"preferredLanguage", "en"}, {"preferredCountry", QLocale::World}, - {"useUsLocale", false}, + {"useAlternativeLocale", false}, + {"alternativeLocLanguage", QLocale(QLocale::English).nativeLanguageName() }, + {"alternativeLocRegion", QLocale(QLocale::English, QLocale::UnitedStates).nativeTerritoryName() }, {"themeName", "lightTheme"}, {"useNativeFileDialog", true}, {"disableAnimations", false}, diff --git a/Desktop/utilities/settings.h b/Desktop/utilities/settings.h index d03d7879b1..338100ec1e 100644 --- a/Desktop/utilities/settings.h +++ b/Desktop/utilities/settings.h @@ -43,7 +43,9 @@ class Settings { USER_HAS_GITHUB_ACCOUNT, PREFERRED_LANGUAGE, PREFERRED_COUNTRY, - USE_USA_LOCALE, + USE_ALT_LOCALE, + ALT_LOCALE_LANGUAGE, + ALT_LOCALE_REGION, THEME_NAME, USE_NATIVE_FILE_DIALOG, DISABLE_ANIMATIONS, diff --git a/QMLComponents/components/JASP/Controls/DropDown.qml b/QMLComponents/components/JASP/Controls/DropDown.qml index 971c0552a3..15f9e6255f 100644 --- a/QMLComponents/components/JASP/Controls/DropDown.qml +++ b/QMLComponents/components/JASP/Controls/DropDown.qml @@ -165,7 +165,7 @@ ComboBoxBase id: popupRoot padding: 1 implicitWidth: popupView.implicitWidth + scrollBar.width + 2*padding - implicitHeight: popupView.implicitHeight + 2 * padding + implicitHeight: Math.min(popupView.implicitHeight + 2 * padding, popupView.maxHeight) enter: Transition { NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 } enabled: preferencesModel.animationsOn } @@ -194,12 +194,13 @@ ComboBoxBase currentIndex: control.highlightedIndex clip: true - property real maxHeight: typeof mainWindowRoot !== 'undefined' ? mainWindowRoot.height // Case Dropdowns used in Desktop - : (typeof rcmdRoot !== 'undefined' ? rcmdRoot.height // Case Dropdown used in R Command - : (typeof backgroundForms !== 'undefined' ? backgroundForms.height // Case Dropdowns used in Analysis forms - : Infinity)) - + property real maxHeight: typeof mainWindowRoot !== 'undefined' ? mainWindowRoot.height // Case Dropdowns used in Desktop + : typeof rcmdRoot !== 'undefined' ? rcmdRoot.height // Case Dropdown used in R Command + : typeof backgroundForms !== 'undefined' ? backgroundForms.height // Case Dropdowns used in Analysis forms + : typeof scrollPrefs !== 'undefined' ? scrollPrefs.height // When its used in a Prefs* page ? + : Infinity + //onMaxHeightChanged: messages.log("maxHeight is now " + maxHeight + " for " + popupView); Rectangle { From 2ef0ccd9d28a40a9d778dee3976c11750da79bae Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 26 Feb 2025 14:30:58 +0100 Subject: [PATCH 20/21] sort on toLower to find things a bit easier --- Desktop/utilities/languagemodel.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 5a677bd572..0bdaea73d1 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -121,7 +121,10 @@ void LanguageModel::fillAltOptions() nativeLanguageNames.remove(""); _altLanguages = QStringList(nativeLanguageNames.begin(), nativeLanguageNames.end()); - std::sort(_altLanguages.begin(), _altLanguages.end()); + std::sort(_altLanguages.begin(), _altLanguages.end(), [](const QString & l, const QString & r) + { + return l.toLower() < r.toLower(); + }); fillAltTerritories(); } @@ -152,7 +155,11 @@ void LanguageModel::fillAltTerritories() nativeTerritoryNames.remove(""); _altTerritories = QStringList(nativeTerritoryNames.begin(), nativeTerritoryNames.end()); - std::sort(_altTerritories.begin(), _altTerritories.end()); + std::sort(_altTerritories.begin(), _altTerritories.end(), [](const QString & l, const QString & r) + { + return l.toLower() < r.toLower(); + }); + emit altTerritoriesChanged(); if(!previouslyChosenTerritoryFound || _currentAltTerritory == "") From f56b0eccc038dff975a8fd4b9385686b859bd2ed Mon Sep 17 00:00:00 2001 From: Joris Goosen Date: Wed, 26 Feb 2025 14:34:46 +0100 Subject: [PATCH 21/21] ah and also maybe store the changed settings... --- Desktop/utilities/languagemodel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Desktop/utilities/languagemodel.cpp b/Desktop/utilities/languagemodel.cpp index 0bdaea73d1..c308e93e96 100644 --- a/Desktop/utilities/languagemodel.cpp +++ b/Desktop/utilities/languagemodel.cpp @@ -497,6 +497,8 @@ void LanguageModel::setCurrentAltLanguage(const QString &newCurrentAltLanguage) _currentAltLanguage = newCurrentAltLanguage; emit currentAltLanguageChanged(); + Settings::setValue(Settings::ALT_LOCALE_LANGUAGE, _currentAltLanguage); + fillAltTerritories(); refreshAll(); @@ -519,5 +521,7 @@ void LanguageModel::setCurrentAltTerritory(const QString &newCurrentAltTerritory _currentAltTerritory = newCurrentAltTerritory; emit currentAltTerritoryChanged(); + Settings::setValue(Settings::ALT_LOCALE_REGION, _currentAltTerritory); + refreshAll(); }