DeskCalc: make input Locale-aware
Accepts input with separators based on user's Locale. For example, with a European locale, "1.234,56" is valid input. With a US locale, "1,234.56" is accepted. The grouping separator is ignored and removed, and the decimal separator is kept. Supports multi-byte decimal separator and grouping separators. The keypad localization is based on the user's Language setting, but the separators come from the Formatting. Thus if the Language is set to English, but the Formatting is set to, for example, German, the keypad will show '.', but when pressed it will emit ',' to match the number Formatting. Otherwise the keypad breaks the localized formatting. Fixes #8503 Change-Id: I0d112bdca67a4e4898e37062102343194ed47f8f Reviewed-on: https://review.haiku-os.org/c/haiku/+/4965 Reviewed-by: Jérôme Duval <[email protected]> Reviewed-by: waddlesplash <[email protected]> Tested-by: Commit checker robot <[email protected]>
This commit is contained in:
committed by
waddlesplash
parent
cda13c638d
commit
d39b5bfd0c
@@ -50,6 +50,8 @@ class ExpressionParser {
|
|||||||
int64 EvaluateToInt64(const char* expressionString);
|
int64 EvaluateToInt64(const char* expressionString);
|
||||||
double EvaluateToDouble(const char* expressionString);
|
double EvaluateToDouble(const char* expressionString);
|
||||||
|
|
||||||
|
status_t SetSeparators(BString decimal, BString group);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Token;
|
struct Token;
|
||||||
class Tokenizer;
|
class Tokenizer;
|
||||||
|
|||||||
@@ -31,9 +31,11 @@
|
|||||||
#include <Clipboard.h>
|
#include <Clipboard.h>
|
||||||
#include <File.h>
|
#include <File.h>
|
||||||
#include <Font.h>
|
#include <Font.h>
|
||||||
|
#include <Locale.h>
|
||||||
#include <MenuItem.h>
|
#include <MenuItem.h>
|
||||||
#include <Message.h>
|
#include <Message.h>
|
||||||
#include <MessageRunner.h>
|
#include <MessageRunner.h>
|
||||||
|
#include <NumberFormat.h>
|
||||||
#include <Point.h>
|
#include <Point.h>
|
||||||
#include <PopUpMenu.h>
|
#include <PopUpMenu.h>
|
||||||
#include <Region.h>
|
#include <Region.h>
|
||||||
@@ -1100,8 +1102,14 @@ CalcView::_EvaluateThread(void* data)
|
|||||||
BString result;
|
BString result;
|
||||||
status_t status = acquire_sem(calcView->fEvaluateSemaphore);
|
status_t status = acquire_sem(calcView->fEvaluateSemaphore);
|
||||||
if (status == B_OK) {
|
if (status == B_OK) {
|
||||||
|
BLocale locale;
|
||||||
|
BNumberFormat format(&locale);
|
||||||
|
|
||||||
ExpressionParser parser;
|
ExpressionParser parser;
|
||||||
parser.SetDegreeMode(calcView->fOptions->degree_mode);
|
parser.SetDegreeMode(calcView->fOptions->degree_mode);
|
||||||
|
parser.SetSeparators(format.GetSeparator(B_DECIMAL_SEPARATOR),
|
||||||
|
format.GetSeparator(B_GROUPING_SEPARATOR));
|
||||||
|
|
||||||
BString expression(calcView->fExpressionTextView->Text());
|
BString expression(calcView->fExpressionTextView->Text());
|
||||||
try {
|
try {
|
||||||
result = parser.Evaluate(expression.String());
|
result = parser.Evaluate(expression.String());
|
||||||
@@ -1301,6 +1309,11 @@ CalcView::_PressKey(int key)
|
|||||||
static_cast<BTextView*>(fExpressionTextView)->Select(
|
static_cast<BTextView*>(fExpressionTextView)->Select(
|
||||||
endSelection + labelLen + 1, endSelection + labelLen + 1);
|
endSelection + labelLen + 1, endSelection + labelLen + 1);
|
||||||
}
|
}
|
||||||
|
} else if (strcmp(fKeypad[key].code, ".") == 0) {
|
||||||
|
BLocale locale;
|
||||||
|
BNumberFormat format(&locale);
|
||||||
|
|
||||||
|
fExpressionTextView->Insert(format.GetSeparator(B_DECIMAL_SEPARATOR));
|
||||||
} else {
|
} else {
|
||||||
// check for evaluation order
|
// check for evaluation order
|
||||||
if (fKeypad[key].code[0] == '\n') {
|
if (fKeypad[key].code[0] == '\n') {
|
||||||
|
|||||||
@@ -97,7 +97,9 @@ class ExpressionParser::Tokenizer {
|
|||||||
fCurrentChar(NULL),
|
fCurrentChar(NULL),
|
||||||
fCurrentToken(),
|
fCurrentToken(),
|
||||||
fReuseToken(false),
|
fReuseToken(false),
|
||||||
fHexSupport(false)
|
fHexSupport(false),
|
||||||
|
fDecimalSeparator("."),
|
||||||
|
fGroupSeparator(",")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,12 +130,13 @@ class ExpressionParser::Tokenizer {
|
|||||||
while (*fCurrentChar != 0 && isspace(*fCurrentChar))
|
while (*fCurrentChar != 0 && isspace(*fCurrentChar))
|
||||||
fCurrentChar++;
|
fCurrentChar++;
|
||||||
|
|
||||||
if (*fCurrentChar == 0)
|
int32 decimalLen = fDecimalSeparator.Length();
|
||||||
|
int32 groupLen = fGroupSeparator.Length();
|
||||||
|
|
||||||
|
if (*fCurrentChar == 0 || decimalLen == 0)
|
||||||
return fCurrentToken = Token("", 0, _CurrentPos(), TOKEN_END_OF_LINE);
|
return fCurrentToken = Token("", 0, _CurrentPos(), TOKEN_END_OF_LINE);
|
||||||
|
|
||||||
bool decimal = *fCurrentChar == '.' || *fCurrentChar == ',';
|
if (*fCurrentChar == fDecimalSeparator[0] || isdigit(*fCurrentChar)) {
|
||||||
|
|
||||||
if (decimal || isdigit(*fCurrentChar)) {
|
|
||||||
if (fHexSupport && *fCurrentChar == '0' && fCurrentChar[1] == 'x')
|
if (fHexSupport && *fCurrentChar == '0' && fCurrentChar[1] == 'x')
|
||||||
return _ParseHexNumber();
|
return _ParseHexNumber();
|
||||||
|
|
||||||
@@ -142,16 +145,30 @@ class ExpressionParser::Tokenizer {
|
|||||||
const char* begin = fCurrentChar;
|
const char* begin = fCurrentChar;
|
||||||
|
|
||||||
// optional digits before the comma
|
// optional digits before the comma
|
||||||
while (isdigit(*fCurrentChar)) {
|
while (isdigit(*fCurrentChar) ||
|
||||||
temp << *fCurrentChar;
|
(groupLen > 0 && *fCurrentChar == fGroupSeparator[0])) {
|
||||||
fCurrentChar++;
|
if (groupLen > 0 && *fCurrentChar == fGroupSeparator[0]) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < groupLen && *fCurrentChar == fGroupSeparator[i]) {
|
||||||
|
fCurrentChar++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
temp << *fCurrentChar;
|
||||||
|
fCurrentChar++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional post comma part
|
// optional post comma part
|
||||||
// (required if there are no digits before the comma)
|
// (required if there are no digits before the comma)
|
||||||
if (*fCurrentChar == '.' || *fCurrentChar == ',') {
|
if (*fCurrentChar == fDecimalSeparator[0]) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < decimalLen && *fCurrentChar == fDecimalSeparator[i]) {
|
||||||
|
fCurrentChar++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
temp << '.';
|
temp << '.';
|
||||||
fCurrentChar++;
|
|
||||||
|
|
||||||
// optional post comma digits
|
// optional post comma digits
|
||||||
while (isdigit(*fCurrentChar)) {
|
while (isdigit(*fCurrentChar)) {
|
||||||
@@ -258,6 +275,22 @@ class ExpressionParser::Tokenizer {
|
|||||||
fReuseToken = true;
|
fReuseToken = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BString DecimalSeparator()
|
||||||
|
{
|
||||||
|
return fDecimalSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
BString GroupSeparator()
|
||||||
|
{
|
||||||
|
return fGroupSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetSeparators(BString decimal, BString group)
|
||||||
|
{
|
||||||
|
fDecimalSeparator = decimal;
|
||||||
|
fGroupSeparator = group;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool _IsHexDigit(char c)
|
static bool _IsHexDigit(char c)
|
||||||
{
|
{
|
||||||
@@ -306,6 +339,8 @@ class ExpressionParser::Tokenizer {
|
|||||||
Token fCurrentToken;
|
Token fCurrentToken;
|
||||||
bool fReuseToken;
|
bool fReuseToken;
|
||||||
bool fHexSupport;
|
bool fHexSupport;
|
||||||
|
BString fDecimalSeparator;
|
||||||
|
BString fGroupSeparator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -356,7 +391,9 @@ ExpressionParser::Evaluate(const char* expressionString)
|
|||||||
if (value == 0)
|
if (value == 0)
|
||||||
return BString("0");
|
return BString("0");
|
||||||
|
|
||||||
char* buffer = value.toFixPtStringExp(kMaxDecimalPlaces, '.', 0, 0);
|
char* buffer = value.toFixPtStringExp(kMaxDecimalPlaces,
|
||||||
|
'.', 0, 0);
|
||||||
|
|
||||||
if (buffer == NULL)
|
if (buffer == NULL)
|
||||||
throw ParseException("out of memory", 0);
|
throw ParseException("out of memory", 0);
|
||||||
|
|
||||||
@@ -365,11 +402,13 @@ ExpressionParser::Evaluate(const char* expressionString)
|
|||||||
if (strchr(buffer, '.')) {
|
if (strchr(buffer, '.')) {
|
||||||
while (buffer[lastChar] == '0')
|
while (buffer[lastChar] == '0')
|
||||||
lastChar--;
|
lastChar--;
|
||||||
|
|
||||||
if (buffer[lastChar] == '.')
|
if (buffer[lastChar] == '.')
|
||||||
lastChar--;
|
lastChar--;
|
||||||
}
|
}
|
||||||
|
|
||||||
BString result(buffer, lastChar + 1);
|
BString result(buffer, lastChar + 1);
|
||||||
|
result.Replace(".", fTokenizer->DecimalSeparator(), 1);
|
||||||
free(buffer);
|
free(buffer);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -792,3 +831,15 @@ ExpressionParser::_EatToken(int32 type)
|
|||||||
throw ParseException(temp.String(), token.position);
|
throw ParseException(temp.String(), token.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
ExpressionParser::SetSeparators(BString decimal, BString group)
|
||||||
|
{
|
||||||
|
if (decimal == group)
|
||||||
|
return B_ERROR;
|
||||||
|
|
||||||
|
fTokenizer->SetSeparators(decimal, group);
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user