Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monetary columns and locale sensitive number display #5794

Open
wants to merge 21 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
63eebd6
some rudimentary support for monetary columns
JorisGoosen Feb 11, 2025
5814d79
add support for formatting via locale
JorisGoosen Feb 12, 2025
318f956
numbers are translated through qt now, which avoids problems with str…
JorisGoosen Feb 12, 2025
c2f48a8
update double labels after language change
JorisGoosen Feb 12, 2025
74449bd
mention something about restarting jasp
JorisGoosen Feb 12, 2025
9e8db8a
fighting with some qml components...
JorisGoosen Feb 12, 2025
378d7cc
attempt to make all qml use localized doubles etc
JorisGoosen Feb 18, 2025
902b005
remove integer array thing from textinputbase and make it handle perc…
JorisGoosen Feb 19, 2025
fa56c01
rewrite the html js formatting code a bit
JorisGoosen Feb 19, 2025
b820dca
ok money now seems to respect locale
JorisGoosen Feb 19, 2025
6ecedcd
now the currency and some numbers are formatted correctly even on sta…
JorisGoosen Feb 19, 2025
39a0c22
all numbers in the table now seem to be localized
JorisGoosen Feb 19, 2025
6ff9056
latenight problems get latenight solutions
JorisGoosen Feb 19, 2025
fa6dd66
make a setting to switch back to the US setting
JorisGoosen Feb 19, 2025
9fa82f6
make sure comparing results uses english, us locale and doesnt ask fo…
JorisGoosen Feb 20, 2025
7501ae3
remove debug print from js
JorisGoosen Feb 20, 2025
d84f855
add #include <functional>
JorisGoosen Feb 20, 2025
13065b3
make sure all string -> double/int also uses locale
JorisGoosen Feb 20, 2025
72ca4dc
add support for choosing the locale you want instead of the one belon…
JorisGoosen Feb 26, 2025
2ef0ccd
sort on toLower to find things a bit easier
JorisGoosen Feb 26, 2025
f56b0ec
ah and also maybe store the changed settings...
JorisGoosen Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CommonData/column.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions CommonData/column.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
56 changes: 44 additions & 12 deletions CommonData/columnutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,22 @@ using namespace std;
using namespace boost::posix_time;
using namespace boost;

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<int>(value);
Expand All @@ -30,20 +44,14 @@ bool ColumnUtils::getIntValue(const string &value, int &intValue)

bool ColumnUtils::isIntValue(const string &value)
{
try
{
boost::lexical_cast<int>(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;
Expand All @@ -65,19 +73,32 @@ 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<double>::infinity() * (value == "-∞" ? -1 : 1);
return true;
}

if(_extraStringToDouble && _extraStringToDouble(value, doubleValue))
return true;

try
{
doubleValue = boost::lexical_cast<double>(deEuropeaniseForImport(value));
doubleValue = boost::lexical_cast<double>((value));
return true;
}
catch (...) {}
catch (...) // If it failed try to "deEuropeanise it"
{
try
{
doubleValue = boost::lexical_cast<double>(deEuropeaniseForImport(value));
return true;
}
catch (...)
{
}
}

return false;
}
Expand Down Expand Up @@ -216,14 +237,25 @@ 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);

if (dbl > std::numeric_limits<double>::max()) return "∞";
if (dbl < std::numeric_limits<double>::lowest()) return "-∞";

if(_alternativeDoubleToString)
return _alternativeDoubleToString(dbl, precision); //Use QString for translations

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;
return conv.str();
Expand Down
48 changes: 34 additions & 14 deletions CommonData/columnutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,50 @@

Copy link
Contributor

@shun2wang shun2wang Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <functional>

I cannot compiling it without this with MSVC, and buildBot on Windows also failed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ill have a look

#include <string>
#include "utils.h"
#include <locale>
#include <functional>

class ColumnUtils
{
public:
typedef std::function<std::string(double, int)> doubleF;
typedef std::function<bool(std::string, double&)> toDoubleF;
typedef std::function<bool(std::string, int&)> toIntF;

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 void setExtraStringToNumber( toDoubleF newDoubleFunc, toIntF newIntFunc);
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;}

static bool convertVecToInt( const stringvec & values, intvec & intValues, intset & uniqueValues);
static bool convertVecToDouble( const stringvec & values, doublevec & doubleValues);
private:
static std::string _convertEscapedUnicodeToUTF8( std::string hex);
private:
static std::string _convertEscapedUnicodeToUTF8( std::string hex);
static doubleF _alternativeDoubleToString;
static toDoubleF _extraStringToDouble;
static toIntF _extraStringToInt;
static std::string _decimalPoint,
_currentQLocaleId;
};

#endif // COLUMNUTILS_H
6 changes: 6 additions & 0 deletions CommonData/dataset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
3 changes: 2 additions & 1 deletion CommonData/dataset.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions CommonData/label.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "column.h"
#include <cassert>
#include "timers.h"
#include "columnutils.h"
#include "databaseinterface.h"

const int Label::DOUBLE_LABEL_VALUE = -1;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions CommonData/label.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
42 changes: 40 additions & 2 deletions Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,50 @@ ScrollView
DropDown
{
id: languages
label: qsTr("Choose language ")
label: qsTr("Choose language")
source: languageModel
startValue: languageModel.currentLanguage
onValueChanged: languageModel.currentLanguage = value

KeyNavigation.tab: altnavcheckbox
KeyNavigation.tab: useAlternativeLocale

}

CheckBox
{
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
}

}

Expand Down
19 changes: 18 additions & 1 deletion Desktop/data/datasetpackage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -1248,6 +1248,23 @@ 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

if(_dataSet)
_dataSet->updateLabelsPostLocaleChange();

refresh();
}



void DataSetPackage::resetAllFilters()
{
for(Column * col : _dataSet->columns())
Expand Down
5 changes: 4 additions & 1 deletion Desktop/data/datasetpackage.h
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ public slots:
void checkDataSetForUpdates();
void delayedRefresh();
void resetFilterCounters();
void prepareForLanguageChange();
void languageChangeDone();

private:
bool isThisTheSameThreadAsEngineSync();
Expand Down Expand Up @@ -366,7 +368,8 @@ public slots:
_analysesHTMLReady = false,
_filterShouldRunInit = false,
_dataMode = false,
_manualEdits = false;
_manualEdits = false,
_waitingForLanguageChange = false;

Json::Value _analysesData,
_database = Json::nullValue;
Expand Down
8 changes: 3 additions & 5 deletions Desktop/html/index-jasp.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@
<link rel="stylesheet" href="css/highlight-default.min.css" type="text/css" />

<script type="text/javascript" > var insideJASP = true; </script>


<script type="text/javascript" src="js/jquery-3.7.0.js"> </script>
<script type="text/javascript" src="js/jquery-ui-1.13.2.custom.js"> </script>
<script type="text/javascript" src="js/underscore-min.js"> </script>
<script type="text/javascript" src="js/backbone-1.5.0.js"> </script>
<script type="text/javascript" src="js/etch.js"> </script>
<script type="text/javascript" src="js/mathjax-config.js"> </script>
<script type="text/javascript" src="js/mathjax-config.js"> </script>
<script type="text/javascript" src="js/mathjax-tex-svg-full.js"> </script>
<script type="text/javascript" src="js/highlight.min.js"> </script>
<script type="text/javascript" src="js/highlight-r.min.js"> </script>
<script type="text/javascript" src="js/quill-blot-formatter.min.js"> </script>
<script type="text/javascript" src="js/highlight.min.js"> </script>
<script type="text/javascript" src="js/highlight-r.min.js"> </script>
<script type="text/javascript" src="js/quill.js"> </script>
<script type="text/javascript" src="js/mrkdwn.js"> </script>
<script type="text/javascript" src="js/jaspwidgets.js"> </script>
Expand Down
Loading
Loading