Merge remote-tracking branch 'upstream/master' into Fix245

This commit is contained in:
Rudy Huyn 2019-03-18 16:55:59 -07:00
commit 55c860361a
11 changed files with 504 additions and 441 deletions

View file

@ -136,7 +136,7 @@ void CHistoryCollector::AddBinOpToHistory(int nOpCode, bool fNoRepetition)
}
// This is expected to be called when a binary op in the last say 1+2+ is changing to another one say 1+2* (+ changed to *)
// It needs to know by this change a Precedence inversion happened. i.e. previous op was lower or equal to its previous op, but the new
// It needs to know by this change a Precedence inversion happened. i.e. previous op was lower or equal to its previous op, but the new
// one isn't. (Eg. 1*2* to 1*2^). It can add explicit brackets to ensure the precedence is inverted. (Eg. (1*2) ^)
void CHistoryCollector::ChangeLastBinOp(int nOpCode, bool fPrecInvToHigher)
{
@ -203,7 +203,7 @@ bool CHistoryCollector::FOpndAddedToHistory()
// AddUnaryOpToHistory
//
// This is does the postfix to prefix translation of the input and adds the text to the history. Eg. doing 2 + 4 (sqrt),
// This is does the postfix to prefix translation of the input and adds the text to the history. Eg. doing 2 + 4 (sqrt),
// this routine will ensure the last sqrt call unary operator, actually goes back in history and wraps 4 in sqrt(4)
//
void CHistoryCollector::AddUnaryOpToHistory(int nOpCode, bool fInv, ANGLE_TYPE angletype)
@ -297,7 +297,7 @@ void CHistoryCollector::AddUnaryOpToHistory(int nOpCode, bool fInv, ANGLE_TYPE a
}
// Called after = with the result of the equation
// Responsible for clearing the top line of current running history display, as well as adding yet another element to
// Responsible for clearing the top line of current running history display, as well as adding yet another element to
// history of equations
void CHistoryCollector::CompleteHistoryLine(wstring_view numStr)
{
@ -413,37 +413,39 @@ int CHistoryCollector::AddCommand(_In_ const std::shared_ptr<IExpressionCommand>
return nCommands - 1;
}
// To Update the operands in the Expression according to the current Radix
// To Update the operands in the Expression according to the current Radix
void CHistoryCollector::UpdateHistoryExpression(uint32_t radix, int32_t precision)
{
if (m_spTokens != nullptr)
if (m_spTokens == nullptr)
{
unsigned int size;
IFT(m_spTokens->GetSize(&size));
return;
}
for (unsigned int i = 0; i < size; ++i)
unsigned int size;
IFT(m_spTokens->GetSize(&size));
for (unsigned int i = 0; i < size; ++i)
{
std::pair<std::wstring, int> token;
IFT(m_spTokens->GetAt(i, &token));
int commandPosition = token.second;
if (commandPosition != -1)
{
std::pair<std::wstring, int> token;
IFT(m_spTokens->GetAt(i, &token));
int commandPosition = token.second;
if (commandPosition != -1)
std::shared_ptr<IExpressionCommand> expCommand;
IFT(m_spCommands->GetAt(commandPosition, &expCommand));
if (expCommand != nullptr && CalculationManager::CommandType::OperandCommand == expCommand->GetCommandType())
{
std::shared_ptr<IExpressionCommand> expCommand;
IFT(m_spCommands->GetAt(commandPosition, &expCommand));
if (expCommand != nullptr && CalculationManager::CommandType::OperandCommand == expCommand->GetCommandType())
std::shared_ptr<COpndCommand> opndCommand = std::static_pointer_cast<COpndCommand>(expCommand);
if (opndCommand != nullptr)
{
std::shared_ptr<COpndCommand> opndCommand = std::static_pointer_cast<COpndCommand>(expCommand);
if (opndCommand != nullptr)
{
token.first = opndCommand->GetString(radix, precision);
IFT(m_spTokens->SetAt(i, token));
opndCommand->SetCommands(GetOperandCommandsFromString(token.first));
}
token.first = opndCommand->GetString(radix, precision);
IFT(m_spTokens->SetAt(i, token));
opndCommand->SetCommands(GetOperandCommandsFromString(token.first));
}
}
}
SetExpressionDisplay();
}
SetExpressionDisplay();
}
void CHistoryCollector::SetDecimalSymbol(wchar_t decimalSymbol)

View file

@ -490,22 +490,25 @@ namespace CalculationManager
void CalculatorManager::MemorizeNumber()
{
m_savedCommands.push_back(MEMORY_COMMAND_TO_UNSIGNED_CHAR(MemoryCommand::MemorizeNumber));
if (!(m_currentCalculatorEngine->FInErrorState()))
if (m_currentCalculatorEngine->FInErrorState())
{
m_currentCalculatorEngine->ProcessCommand(IDC_STORE);
auto memoryObjectPtr = m_currentCalculatorEngine->PersistedMemObject();
if (memoryObjectPtr != nullptr)
{
m_memorizedNumbers.insert(m_memorizedNumbers.begin(), *memoryObjectPtr);
}
if (m_memorizedNumbers.size() > m_maximumMemorySize)
{
m_memorizedNumbers.resize(m_maximumMemorySize);
}
this->SetMemorizedNumbersString();
return;
}
m_currentCalculatorEngine->ProcessCommand(IDC_STORE);
auto memoryObjectPtr = m_currentCalculatorEngine->PersistedMemObject();
if (memoryObjectPtr != nullptr)
{
m_memorizedNumbers.insert(m_memorizedNumbers.begin(), *memoryObjectPtr);
}
if (m_memorizedNumbers.size() > m_maximumMemorySize)
{
m_memorizedNumbers.resize(m_maximumMemorySize);
}
this->SetMemorizedNumbersString();
}
/// <summary>
@ -516,11 +519,14 @@ namespace CalculationManager
void CalculatorManager::MemorizedNumberLoad(_In_ unsigned int indexOfMemory)
{
SaveMemoryCommand(MemoryCommand::MemorizedNumberLoad, indexOfMemory);
if (!(m_currentCalculatorEngine->FInErrorState()))
if (m_currentCalculatorEngine->FInErrorState())
{
this->MemorizedNumberSelect(indexOfMemory);
m_currentCalculatorEngine->ProcessCommand(IDC_RECALL);
return;
}
this->MemorizedNumberSelect(indexOfMemory);
m_currentCalculatorEngine->ProcessCommand(IDC_RECALL);
}
/// <summary>
@ -532,24 +538,27 @@ namespace CalculationManager
void CalculatorManager::MemorizedNumberAdd(_In_ unsigned int indexOfMemory)
{
SaveMemoryCommand(MemoryCommand::MemorizedNumberAdd, indexOfMemory);
if (!(m_currentCalculatorEngine->FInErrorState()))
if (m_currentCalculatorEngine->FInErrorState())
{
if (m_memorizedNumbers.empty())
{
this->MemorizeNumber();
}
else
{
this->MemorizedNumberSelect(indexOfMemory);
m_currentCalculatorEngine->ProcessCommand(IDC_MPLUS);
this->MemorizedNumberChanged(indexOfMemory);
this->SetMemorizedNumbersString();
}
m_displayCallback->MemoryItemChanged(indexOfMemory);
return;
}
if (m_memorizedNumbers.empty())
{
this->MemorizeNumber();
}
else
{
this->MemorizedNumberSelect(indexOfMemory);
m_currentCalculatorEngine->ProcessCommand(IDC_MPLUS);
this->MemorizedNumberChanged(indexOfMemory);
this->SetMemorizedNumbersString();
}
m_displayCallback->MemoryItemChanged(indexOfMemory);
}
void CalculatorManager::MemorizedNumberClear(_In_ unsigned int indexOfMemory)
@ -570,27 +579,30 @@ namespace CalculationManager
void CalculatorManager::MemorizedNumberSubtract(_In_ unsigned int indexOfMemory)
{
SaveMemoryCommand(MemoryCommand::MemorizedNumberSubtract, indexOfMemory);
if (!(m_currentCalculatorEngine->FInErrorState()))
if (m_currentCalculatorEngine->FInErrorState())
{
// To add negative of the number on display to the memory -x = x - 2x
if (m_memorizedNumbers.empty())
{
this->MemorizeNumber();
this->MemorizedNumberSubtract(0);
this->MemorizedNumberSubtract(0);
}
else
{
this->MemorizedNumberSelect(indexOfMemory);
m_currentCalculatorEngine->ProcessCommand(IDC_MMINUS);
this->MemorizedNumberChanged(indexOfMemory);
this->SetMemorizedNumbersString();
}
m_displayCallback->MemoryItemChanged(indexOfMemory);
return;
}
// To add negative of the number on display to the memory -x = x - 2x
if (m_memorizedNumbers.empty())
{
this->MemorizeNumber();
this->MemorizedNumberSubtract(0);
this->MemorizedNumberSubtract(0);
}
else
{
this->MemorizedNumberSelect(indexOfMemory);
m_currentCalculatorEngine->ProcessCommand(IDC_MMINUS);
this->MemorizedNumberChanged(indexOfMemory);
this->SetMemorizedNumbersString();
}
m_displayCallback->MemoryItemChanged(indexOfMemory);
}
/// <summary>
@ -613,11 +625,13 @@ namespace CalculationManager
/// <param name="indexOfMemory">Index of the target memory</param>
void CalculatorManager::MemorizedNumberSelect(_In_ unsigned int indexOfMemory)
{
if (!(m_currentCalculatorEngine->FInErrorState()))
if (m_currentCalculatorEngine->FInErrorState())
{
auto memoryObject = m_memorizedNumbers.at(indexOfMemory);
m_currentCalculatorEngine->PersistedMemObject(memoryObject);
return;
}
auto memoryObject = m_memorizedNumbers.at(indexOfMemory);
m_currentCalculatorEngine->PersistedMemObject(memoryObject);
}
/// <summary>
@ -627,13 +641,15 @@ namespace CalculationManager
/// <param name="indexOfMemory">Index of the target memory</param>
void CalculatorManager::MemorizedNumberChanged(_In_ unsigned int indexOfMemory)
{
if (!(m_currentCalculatorEngine->FInErrorState()))
if (m_currentCalculatorEngine->FInErrorState())
{
auto memoryObject = m_currentCalculatorEngine->PersistedMemObject();
if (memoryObject != nullptr)
{
m_memorizedNumbers.at(indexOfMemory) = *memoryObject;
}
return;
}
auto memoryObject = m_currentCalculatorEngine->PersistedMemObject();
if (memoryObject != nullptr)
{
m_memorizedNumbers.at(indexOfMemory) = *memoryObject;
}
}

View file

@ -612,6 +612,7 @@ PNUMBER StringToNumber(wstring_view numberString, uint32_t radix, int32_t precis
break;
}
// Drop through in the 'e'-as-a-digit case
[[fallthrough]];
default:
state = machine[state][NZ];
break;
@ -646,7 +647,7 @@ PNUMBER StringToNumber(wstring_view numberString, uint32_t radix, int32_t precis
break;
case LD:
pnumret->exp++;
// Fall through
[[fallthrough]];
case DD:
{
curChar = NormalizeCharDigit(curChar, radix);

View file

@ -109,22 +109,8 @@ CategorySelectionInitializer UnitConverter::SetCurrentCategory(const Category& i
vector<Unit>& unitVector = m_categoryToUnits[m_currentCategory];
for (unsigned int i = 0; i < unitVector.size(); i++)
{
if (unitVector[i].id == m_fromType.id)
{
unitVector[i].isConversionSource = true;
}
else
{
unitVector[i].isConversionSource = false;
}
if (unitVector[i].id == m_toType.id)
{
unitVector[i].isConversionTarget = true;
}
else
{
unitVector[i].isConversionTarget = false;
}
unitVector[i].isConversionSource = (unitVector[i].id == m_fromType.id);
unitVector[i].isConversionTarget = (unitVector[i].id == m_toType.id);
}
m_currentCategory = input;
if (!m_currentCategory.supportsNegative && m_currentDisplay.front() == L'-')
@ -156,15 +142,17 @@ Category UnitConverter::GetCurrentCategory()
/// <param name="toType">Unit struct we are converting to</param>
void UnitConverter::SetCurrentUnitTypes(const Unit& fromType, const Unit& toType)
{
if (CheckLoad())
if (!CheckLoad())
{
m_fromType = fromType;
m_toType = toType;
Calculate();
UpdateCurrencySymbols();
UpdateViewModel();
return;
}
m_fromType = fromType;
m_toType = toType;
Calculate();
UpdateCurrencySymbols();
UpdateViewModel();
}
/// <summary>
@ -181,22 +169,24 @@ void UnitConverter::SetCurrentUnitTypes(const Unit& fromType, const Unit& toType
/// </param>
void UnitConverter::SwitchActive(const wstring& newValue)
{
if (CheckLoad())
if (!CheckLoad())
{
swap(m_fromType, m_toType);
swap(m_currentHasDecimal, m_returnHasDecimal);
m_returnDisplay = m_currentDisplay;
m_currentDisplay = newValue;
m_currentHasDecimal = (m_currentDisplay.find(L'.') != m_currentDisplay.npos);
m_switchedActive = true;
return;
}
if (m_currencyDataLoader != nullptr && m_vmCurrencyCallback != nullptr)
{
shared_ptr<ICurrencyConverterDataLoader> currencyDataLoader = GetCurrencyConverterDataLoader();
const pair<wstring, wstring> currencyRatios = currencyDataLoader->GetCurrencyRatioEquality(m_fromType, m_toType);
swap(m_fromType, m_toType);
swap(m_currentHasDecimal, m_returnHasDecimal);
m_returnDisplay = m_currentDisplay;
m_currentDisplay = newValue;
m_currentHasDecimal = (m_currentDisplay.find(L'.') != m_currentDisplay.npos);
m_switchedActive = true;
m_vmCurrencyCallback->CurrencyRatiosCallback(currencyRatios.first, currencyRatios.second);
}
if (m_currencyDataLoader != nullptr && m_vmCurrencyCallback != nullptr)
{
shared_ptr<ICurrencyConverterDataLoader> currencyDataLoader = GetCurrencyConverterDataLoader();
const pair<wstring, wstring> currencyRatios = currencyDataLoader->GetCurrencyRatioEquality(m_fromType, m_toType);
m_vmCurrencyCallback->CurrencyRatiosCallback(currencyRatios.first, currencyRatios.second);
}
}
@ -291,55 +281,53 @@ wstring UnitConverter::ConversionDataToString(ConversionData d, const wchar_t *
/// </summary>
wstring UnitConverter::Serialize()
{
if (CheckLoad())
{
wstringstream out(wstringstream::out);
const wchar_t * delimiter = L";";
out << UnitToString(m_fromType, delimiter) << "|";
out << UnitToString(m_toType, delimiter) << "|";
out << CategoryToString(m_currentCategory, delimiter) << "|";
out << std::to_wstring(m_currentHasDecimal) << delimiter << std::to_wstring(m_returnHasDecimal) << delimiter << std::to_wstring(m_switchedActive) << delimiter;
out << m_currentDisplay << delimiter << m_returnDisplay << delimiter << "|";
wstringstream categoryString(wstringstream::out);
wstringstream categoryToUnitString(wstringstream::out);
wstringstream unitToUnitToDoubleString(wstringstream::out);
for (const Category& c : m_categories)
{
categoryString << CategoryToString(c, delimiter) << ",";
}
for (const auto& cur : m_categoryToUnits)
{
categoryToUnitString << CategoryToString(cur.first, delimiter) << "[";
for (const Unit& u : cur.second)
{
categoryToUnitString << UnitToString(u, delimiter) << ",";
}
categoryToUnitString << "[" << "]";
}
for (const auto& cur : m_ratioMap)
{
unitToUnitToDoubleString << UnitToString(cur.first, delimiter) << "[";
for (const auto& curConversion : cur.second)
{
unitToUnitToDoubleString << UnitToString(curConversion.first, delimiter) << ":";
unitToUnitToDoubleString << ConversionDataToString(curConversion.second, delimiter) << ":,";
}
unitToUnitToDoubleString << "[" << "]";
}
out << categoryString.str() << "|";
out << categoryToUnitString.str() << "|";
out << unitToUnitToDoubleString.str() << "|";
wstring test = out.str();
return test;
}
else
if (!CheckLoad())
{
return wstring();
}
wstringstream out(wstringstream::out);
const wchar_t * delimiter = L";";
out << UnitToString(m_fromType, delimiter) << "|";
out << UnitToString(m_toType, delimiter) << "|";
out << CategoryToString(m_currentCategory, delimiter) << "|";
out << std::to_wstring(m_currentHasDecimal) << delimiter << std::to_wstring(m_returnHasDecimal) << delimiter << std::to_wstring(m_switchedActive) << delimiter;
out << m_currentDisplay << delimiter << m_returnDisplay << delimiter << "|";
wstringstream categoryString(wstringstream::out);
wstringstream categoryToUnitString(wstringstream::out);
wstringstream unitToUnitToDoubleString(wstringstream::out);
for (const Category& c : m_categories)
{
categoryString << CategoryToString(c, delimiter) << ",";
}
for (const auto& cur : m_categoryToUnits)
{
categoryToUnitString << CategoryToString(cur.first, delimiter) << "[";
for (const Unit& u : cur.second)
{
categoryToUnitString << UnitToString(u, delimiter) << ",";
}
categoryToUnitString << "[" << "]";
}
for (const auto& cur : m_ratioMap)
{
unitToUnitToDoubleString << UnitToString(cur.first, delimiter) << "[";
for (const auto& curConversion : cur.second)
{
unitToUnitToDoubleString << UnitToString(curConversion.first, delimiter) << ":";
unitToUnitToDoubleString << ConversionDataToString(curConversion.second, delimiter) << ":,";
}
unitToUnitToDoubleString << "[" << "]";
}
out << categoryString.str() << "|";
out << categoryToUnitString.str() << "|";
out << unitToUnitToDoubleString.str() << "|";
wstring test = out.str();
return test;
}
/// <summary>
@ -349,55 +337,58 @@ wstring UnitConverter::Serialize()
void UnitConverter::DeSerialize(const wstring& serializedData)
{
Reset();
if (!serializedData.empty())
if (serializedData.empty())
{
vector<wstring> outerTokens = StringToVector(serializedData, L"|");
assert(outerTokens.size() == EXPECTEDSERIALIZEDTOKENCOUNT);
m_fromType = StringToUnit(outerTokens[0]);
m_toType = StringToUnit(outerTokens[1]);
m_currentCategory = StringToCategory(outerTokens[2]);
vector<wstring> stateDataTokens = StringToVector(outerTokens[3], L";");
assert(stateDataTokens.size() == EXPECTEDSTATEDATATOKENCOUNT);
m_currentHasDecimal = (stateDataTokens[0].compare(L"1") == 0);
m_returnHasDecimal = (stateDataTokens[1].compare(L"1") == 0);
m_switchedActive = (stateDataTokens[2].compare(L"1") == 0);
m_currentDisplay = stateDataTokens[3];
m_returnDisplay = stateDataTokens[4];
vector<wstring> categoryListTokens = StringToVector(outerTokens[4], L",");
for (wstring token : categoryListTokens)
{
m_categories.push_back(StringToCategory(token));
}
vector<wstring> unitVectorTokens = StringToVector(outerTokens[5], L"]");
for (wstring unitVector : unitVectorTokens)
{
vector<wstring> mapcomponents = StringToVector(unitVector, L"[");
assert(mapcomponents.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT);
Category key = StringToCategory(mapcomponents[0]);
vector<wstring> units = StringToVector(mapcomponents[1], L",");
for (wstring unit : units)
{
m_categoryToUnits[key].push_back(StringToUnit(unit));
}
}
vector<wstring> ratioMapTokens = StringToVector(outerTokens[6], L"]");
for (wstring token : ratioMapTokens)
{
vector<wstring> ratioMapComponentTokens = StringToVector(token, L"[");
assert(ratioMapComponentTokens.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT);
Unit key = StringToUnit(ratioMapComponentTokens[0]);
vector<wstring> ratioMapList = StringToVector(ratioMapComponentTokens[1], L",");
for (wstring subtoken : ratioMapList)
{
vector<wstring> ratioMapSubComponentTokens = StringToVector(subtoken, L":");
assert(ratioMapSubComponentTokens.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT);
Unit subkey = StringToUnit(ratioMapSubComponentTokens[0]);
ConversionData conversion = StringToConversionData(ratioMapSubComponentTokens[1]);
m_ratioMap[key][subkey] = conversion;
}
}
UpdateViewModel();
return;
}
vector<wstring> outerTokens = StringToVector(serializedData, L"|");
assert(outerTokens.size() == EXPECTEDSERIALIZEDTOKENCOUNT);
m_fromType = StringToUnit(outerTokens[0]);
m_toType = StringToUnit(outerTokens[1]);
m_currentCategory = StringToCategory(outerTokens[2]);
vector<wstring> stateDataTokens = StringToVector(outerTokens[3], L";");
assert(stateDataTokens.size() == EXPECTEDSTATEDATATOKENCOUNT);
m_currentHasDecimal = (stateDataTokens[0].compare(L"1") == 0);
m_returnHasDecimal = (stateDataTokens[1].compare(L"1") == 0);
m_switchedActive = (stateDataTokens[2].compare(L"1") == 0);
m_currentDisplay = stateDataTokens[3];
m_returnDisplay = stateDataTokens[4];
vector<wstring> categoryListTokens = StringToVector(outerTokens[4], L",");
for (wstring token : categoryListTokens)
{
m_categories.push_back(StringToCategory(token));
}
vector<wstring> unitVectorTokens = StringToVector(outerTokens[5], L"]");
for (wstring unitVector : unitVectorTokens)
{
vector<wstring> mapcomponents = StringToVector(unitVector, L"[");
assert(mapcomponents.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT);
Category key = StringToCategory(mapcomponents[0]);
vector<wstring> units = StringToVector(mapcomponents[1], L",");
for (wstring unit : units)
{
m_categoryToUnits[key].push_back(StringToUnit(unit));
}
}
vector<wstring> ratioMapTokens = StringToVector(outerTokens[6], L"]");
for (wstring token : ratioMapTokens)
{
vector<wstring> ratioMapComponentTokens = StringToVector(token, L"[");
assert(ratioMapComponentTokens.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT);
Unit key = StringToUnit(ratioMapComponentTokens[0]);
vector<wstring> ratioMapList = StringToVector(ratioMapComponentTokens[1], L",");
for (wstring subtoken : ratioMapList)
{
vector<wstring> ratioMapSubComponentTokens = StringToVector(subtoken, L":");
assert(ratioMapSubComponentTokens.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT);
Unit subkey = StringToUnit(ratioMapSubComponentTokens[0]);
ConversionData conversion = StringToConversionData(ratioMapSubComponentTokens[1]);
m_ratioMap[key][subkey] = conversion;
}
}
UpdateViewModel();
}
/// <summary>
@ -406,15 +397,17 @@ void UnitConverter::DeSerialize(const wstring& serializedData)
/// <param name="userPreferences">wstring holding the serialized data. If it does not have expected number of parameters, we will ignore it</param>
void UnitConverter::RestoreUserPreferences(const wstring& userPreferences)
{
if (!userPreferences.empty())
if (userPreferences.empty())
{
vector<wstring> outerTokens = StringToVector(userPreferences, L"|");
if (outerTokens.size() == 3)
{
m_fromType = StringToUnit(outerTokens[0]);
m_toType = StringToUnit(outerTokens[1]);
m_currentCategory = StringToCategory(outerTokens[2]);
}
return;
}
vector<wstring> outerTokens = StringToVector(userPreferences, L"|");
if (outerTokens.size() == 3)
{
m_fromType = StringToUnit(outerTokens[0]);
m_toType = StringToUnit(outerTokens[1]);
m_currentCategory = StringToCategory(outerTokens[2]);
}
}
@ -503,144 +496,146 @@ wstring UnitConverter::Unquote(const wstring& s)
/// <param name="command">Command enum representing the command that was entered</param>
void UnitConverter::SendCommand(Command command)
{
if (CheckLoad())
if (!CheckLoad())
{
// TODO: Localization of characters
bool clearFront = false;
if (m_currentDisplay == L"0")
return;
}
// TODO: Localization of characters
bool clearFront = false;
if (m_currentDisplay == L"0")
{
clearFront = true;
}
bool clearBack = false;
if ((m_currentHasDecimal && m_currentDisplay.size() - 1 >= MAXIMUMDIGITSALLOWED) || (!m_currentHasDecimal && m_currentDisplay.size() >= MAXIMUMDIGITSALLOWED))
{
clearBack = true;
}
if (command != Command::Negate && m_switchedActive)
{
ClearValues();
m_switchedActive = false;
clearFront = true;
clearBack = false;
}
switch (command)
{
case Command::Zero:
m_currentDisplay += L"0";
break;
case Command::One:
m_currentDisplay += L"1";
break;
case Command::Two:
m_currentDisplay += L"2";
break;
case Command::Three:
m_currentDisplay += L"3";
break;
case Command::Four:
m_currentDisplay += L"4";
break;
case Command::Five:
m_currentDisplay += L"5";
break;
case Command::Six:
m_currentDisplay += L"6";
break;
case Command::Seven:
m_currentDisplay += L"7";
break;
case Command::Eight:
m_currentDisplay += L"8";
break;
case Command::Nine:
m_currentDisplay += L"9";
break;
case Command::Decimal:
clearFront = false;
clearBack = false;
if (!m_currentHasDecimal)
{
clearFront = true;
m_currentDisplay += L".";
m_currentHasDecimal = true;
}
bool clearBack = false;
if ((m_currentHasDecimal && m_currentDisplay.size() - 1 >= MAXIMUMDIGITSALLOWED) || (!m_currentHasDecimal && m_currentDisplay.size() >= MAXIMUMDIGITSALLOWED))
break;
case Command::Backspace:
clearFront = false;
clearBack = false;
if ((m_currentDisplay.front() != '-' && m_currentDisplay.size() > 1) || m_currentDisplay.size() > 2)
{
clearBack = true;
}
if (command != Command::Negate && m_switchedActive)
{
ClearValues();
m_switchedActive = false;
clearFront = true;
clearBack = false;
}
switch (command)
{
case Command::Zero:
m_currentDisplay += L"0";
break;
case Command::One:
m_currentDisplay += L"1";
break;
case Command::Two:
m_currentDisplay += L"2";
break;
case Command::Three:
m_currentDisplay += L"3";
break;
case Command::Four:
m_currentDisplay += L"4";
break;
case Command::Five:
m_currentDisplay += L"5";
break;
case Command::Six:
m_currentDisplay += L"6";
break;
case Command::Seven:
m_currentDisplay += L"7";
break;
case Command::Eight:
m_currentDisplay += L"8";
break;
case Command::Nine:
m_currentDisplay += L"9";
break;
case Command::Decimal:
clearFront = false;
clearBack = false;
if (!m_currentHasDecimal)
if (m_currentDisplay.back() == '.')
{
m_currentDisplay += L".";
m_currentHasDecimal = true;
m_currentHasDecimal = false;
}
break;
m_currentDisplay.pop_back();
}
else
{
m_currentDisplay = L"0";
m_currentHasDecimal = false;
}
break;
case Command::Backspace:
clearFront = false;
clearBack = false;
if ((m_currentDisplay.front() != '-' && m_currentDisplay.size() > 1) || m_currentDisplay.size() > 2)
case Command::Negate:
clearFront = false;
clearBack = false;
if (m_currentCategory.supportsNegative)
{
if (m_currentDisplay.front() == '-')
{
if (m_currentDisplay.back() == '.')
{
m_currentHasDecimal = false;
}
m_currentDisplay.pop_back();
m_currentDisplay.erase(0, 1);
}
else
{
m_currentDisplay = L"0";
m_currentHasDecimal = false;
m_currentDisplay.insert(0, 1, '-');
}
break;
case Command::Negate:
clearFront = false;
clearBack = false;
if (m_currentCategory.supportsNegative)
{
if (m_currentDisplay.front() == '-')
{
m_currentDisplay.erase(0, 1);
}
else
{
m_currentDisplay.insert(0, 1, '-');
}
}
break;
case Command::Clear:
clearFront = false;
clearBack = false;
ClearValues();
break;
case Command::Reset:
clearFront = false;
clearBack = false;
ClearValues();
Reset();
break;
default:
break;
}
break;
case Command::Clear:
clearFront = false;
clearBack = false;
ClearValues();
break;
if (clearFront)
{
m_currentDisplay.erase(0, 1);
}
if (clearBack)
{
m_currentDisplay.erase(m_currentDisplay.size() - 1, 1);
m_vmCallback->MaxDigitsReached();
}
case Command::Reset:
clearFront = false;
clearBack = false;
ClearValues();
Reset();
break;
Calculate();
UpdateViewModel();
default:
break;
}
if (clearFront)
{
m_currentDisplay.erase(0, 1);
}
if (clearBack)
{
m_currentDisplay.erase(m_currentDisplay.size() - 1, 1);
m_vmCallback->MaxDigitsReached();
}
Calculate();
UpdateViewModel();
}
/// <summary>
@ -844,47 +839,49 @@ void UnitConverter::Reset()
ClearValues();
m_switchedActive = false;
if (!m_categories.empty())
if (m_categories.empty())
{
m_currentCategory = m_categories[0];
return;
}
m_categoryToUnits.clear();
m_ratioMap.clear();
bool readyCategoryFound = false;
for (const Category& category : m_categories)
m_currentCategory = m_categories[0];
m_categoryToUnits.clear();
m_ratioMap.clear();
bool readyCategoryFound = false;
for (const Category& category : m_categories)
{
shared_ptr<IConverterDataLoader> activeDataLoader = GetDataLoaderForCategory(category);
if (activeDataLoader == nullptr)
{
shared_ptr<IConverterDataLoader> activeDataLoader = GetDataLoaderForCategory(category);
if (activeDataLoader == nullptr)
{
// The data loader is different depending on the category, e.g. currency data loader
// is different from the static data loader.
// If there is no data loader for this category, continue.
continue;
}
vector<Unit> units = activeDataLoader->LoadOrderedUnits(category);
m_categoryToUnits[category] = units;
// Just because the units are empty, doesn't mean the user can't select this category,
// we just want to make sure we don't let an unready category be the default.
if (!units.empty())
{
for (Unit u : units)
{
m_ratioMap[u] = activeDataLoader->LoadOrderedRatios(u);
}
if (!readyCategoryFound)
{
m_currentCategory = category;
readyCategoryFound = true;
}
}
// The data loader is different depending on the category, e.g. currency data loader
// is different from the static data loader.
// If there is no data loader for this category, continue.
continue;
}
InitializeSelectedUnits();
Calculate();
vector<Unit> units = activeDataLoader->LoadOrderedUnits(category);
m_categoryToUnits[category] = units;
// Just because the units are empty, doesn't mean the user can't select this category,
// we just want to make sure we don't let an unready category be the default.
if (!units.empty())
{
for (Unit u : units)
{
m_ratioMap[u] = activeDataLoader->LoadOrderedRatios(u);
}
if (!readyCategoryFound)
{
m_currentCategory = category;
readyCategoryFound = true;
}
}
}
InitializeSelectedUnits();
Calculate();
}
/// <summary>
@ -1029,22 +1026,24 @@ void UnitConverter::Calculate()
/// <param name="input">wstring to trim</param>
void UnitConverter::TrimString(wstring& returnString)
{
if (returnString.find(L'.') != m_returnDisplay.npos)
if (returnString.find(L'.') == m_returnDisplay.npos)
{
wstring::iterator iter;
for (iter = returnString.end() - 1; ;iter--)
return;
}
wstring::iterator iter;
for (iter = returnString.end() - 1; ;iter--)
{
if (*iter != L'0')
{
if (*iter != L'0')
{
returnString.erase(iter + 1, returnString.end());
break;
}
}
if (*(returnString.end()-1) == L'.')
{
returnString.erase(returnString.end()-1, returnString.end());
returnString.erase(iter + 1, returnString.end());
break;
}
}
if (*(returnString.end()-1) == L'.')
{
returnString.erase(returnString.end()-1, returnString.end());
}
}
/// <summary>

View file

@ -12,6 +12,7 @@ using namespace CalculatorApp::Common::DateCalculation;
DateCalculationEngine::DateCalculationEngine(_In_ String^ calendarIdentifier)
{
m_calendar = ref new Calendar();
m_calendar->ChangeTimeZone("UTC");
m_calendar->ChangeCalendarSystem(calendarIdentifier);
}

View file

@ -43,11 +43,7 @@ DateCalculatorViewModel::DateCalculatorViewModel() :
m_StrDateDiffResultAutomationName(L""),
m_StrDateDiffResultInDays(L""),
m_StrDateResult(L""),
m_StrDateResultAutomationName(L""),
m_fromDate({ 0 }),
m_toDate({ 0 }),
m_startDate({ 0 }),
m_dateResult({ 0 })
m_StrDateResultAutomationName(L"")
{
const auto& localizationSettings = LocalizationSettings::GetInstance();
@ -56,19 +52,19 @@ DateCalculatorViewModel::DateCalculatorViewModel() :
// Initialize Date Calc engine
m_dateCalcEngine = make_shared<DateCalculationEngine>(localizationSettings.GetCalendarIdentifier());
// Initialize dates of DatePicker controls to today's date
auto calendar = ref new Calendar();
// We force the timezone to UTC, in order to avoid being affected by Daylight Saving Time
// when we calculate the difference between 2 dates.
calendar->ChangeTimeZone("UTC");
auto today = calendar->GetDateTime();
// FromDate and ToDate should be clipped (adjusted to a consistent hour in UTC)
m_fromDate = today;
m_toDate = today;
FromDate = ClipTime(today);
ToDate = ClipTime(today);
m_fromDate = ClipTime(today);
m_toDate = ClipTime(today);
// StartDate should not be clipped
StartDate = today;
m_startDate = today;
m_dateResult = today;
// Initialize the list separator delimiter appended with a space at the end, e.g. ", "
@ -86,15 +82,6 @@ DateCalculatorViewModel::DateCalculatorViewModel() :
m_offsetValues->Append(ref new String(numberStr.c_str()));
}
/* In the ClipTime function, we used to change timezone to UTC before clipping the time.
The comment from the previous developers said this was done to eliminate the effects of
Daylight Savings Time. We can't think of a good reason why this change in timezone is
necessary and did find bugs related to the change, therefore, we have removed the
change. Just in case, we will see if the clipped time is ever a different day from the
original day, which would hopefully indicate the change in timezone was actually
necessary. We will collect telemetry if we find this case. If we don't see any
telemetry events after the application has been used for some time, we will feel safe
and can remove this function. */
DayOfWeek trueDayOfWeek = calendar->DayOfWeek;
DateTime clippedTime = ClipTime(today);
@ -383,13 +370,14 @@ String^ DateCalculatorViewModel::GetLocalizedNumberString(int value) const
return ref new String(numberStr.c_str());
}
// Adjusts the given DateTime to 12AM of the same day
// Adjusts the given DateTime to 12AM (UTC) of the same day
DateTime DateCalculatorViewModel::ClipTime(DateTime dateTime)
{
auto calendar = ref new Calendar();
calendar->ChangeTimeZone("UTC");
calendar->SetDateTime(dateTime);
calendar->Period = 1;
calendar->Hour = 12;
calendar->Period = calendar->FirstPeriodInThisDay;
calendar->Hour = calendar->FirstHourInThisPeriod;
calendar->Minute = 0;
calendar->Second = 0;
calendar->Nanosecond = 0;

View file

@ -23,8 +23,8 @@ namespace CalculatorApp
// Input Properties
OBSERVABLE_PROPERTY_RW(bool, IsDateDiffMode);
OBSERVABLE_PROPERTY_RW(bool, IsAddMode);
OBSERVABLE_PROPERTY_RW(bool, IsDiffInDays); // If diff is only in days or the dates are the same,
// then show only one result and avoid redundancy
OBSERVABLE_PROPERTY_R(bool, IsDiffInDays); // If diff is only in days or the dates are the same,
// then show only one result and avoid redundancy
OBSERVABLE_PROPERTY_RW(int, DaysOffset);
OBSERVABLE_PROPERTY_RW(int, MonthsOffset);
@ -82,11 +82,11 @@ namespace CalculatorApp
}
// Output Properties
OBSERVABLE_PROPERTY_RW(Platform::String^, StrDateDiffResult);
OBSERVABLE_PROPERTY_RW(Platform::String^, StrDateDiffResultAutomationName);
OBSERVABLE_PROPERTY_RW(Platform::String^, StrDateDiffResultInDays);
OBSERVABLE_PROPERTY_RW(Platform::String^, StrDateResult);
OBSERVABLE_PROPERTY_RW(Platform::String^, StrDateResultAutomationName);
OBSERVABLE_PROPERTY_R(Platform::String^, StrDateDiffResult);
OBSERVABLE_PROPERTY_R(Platform::String^, StrDateDiffResultAutomationName);
OBSERVABLE_PROPERTY_R(Platform::String^, StrDateDiffResultInDays);
OBSERVABLE_PROPERTY_R(Platform::String^, StrDateResult);
OBSERVABLE_PROPERTY_R(Platform::String^, StrDateResultAutomationName);
COMMAND_FOR_METHOD(CopyCommand, DateCalculatorViewModel::OnCopyCommand);
@ -104,8 +104,6 @@ namespace CalculatorApp
Platform::String^ GetLocalizedNumberString(int value) const;
static Windows::Foundation::DateTime ClipTime(Windows::Foundation::DateTime dateTime);
static void CheckClipTimeSameDay(Windows::Globalization::Calendar^ reference);
property bool IsOutOfBound
{
bool get() { return m_isOutOfBound; }

View file

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
@ -53,6 +53,8 @@ constexpr size_t SELECTED_TARGET_UNIT = 2;
// x millisecond delay before we consider conversion to be final
constexpr unsigned int CONVERSION_FINALIZED_DELAY_IN_MS = 1000;
const wregex regexTrimSpacesStart = wregex(L"^\\s+");
const wregex regexTrimSpacesEnd = wregex(L"\\s+$");
namespace CalculatorApp::ViewModel
{
@ -202,12 +204,10 @@ void UnitConverterViewModel::BuildUnitList(const vector<UCM::Unit>& modelUnitLis
m_Units->Clear();
for (const UCM::Unit& modelUnit : modelUnitList)
{
if (modelUnit.isWhimsical)
if (!modelUnit.isWhimsical)
{
continue;
m_Units->Append(ref new Unit(modelUnit));
}
m_Units->Append(ref new Unit(modelUnit));
}
if (m_Units->Size == 0)
@ -348,10 +348,15 @@ String^ UnitConverterViewModel::ConvertToLocalizedString(const std::wstring& str
if (pos != wstring::npos)
{
currencyResult.erase(pos, currencyCode.length());
pos = currencyResult.find(L'\u00a0'); // non-breaking space
if (pos != wstring::npos)
std::wsmatch sm;
if (regex_search(currencyResult, sm, regexTrimSpacesStart))
{
currencyResult.erase(pos, 1);
currencyResult.erase(sm.prefix().length(), sm.length());
}
if (regex_search(currencyResult, sm, regexTrimSpacesEnd))
{
currencyResult.erase(sm.prefix().length(), sm.length());
}
}
@ -643,10 +648,8 @@ String^ UnitConverterViewModel::Serialize()
String^ serializedData = ref new String(wstring(out.str()).c_str());
return serializedData;
}
else
{
return nullptr;
}
return nullptr;
}
void UnitConverterViewModel::Deserialize(Platform::String^ state)
@ -879,14 +882,11 @@ void UnitConverterViewModel::UpdateInputBlocked(_In_ const wstring& currencyInpu
{
// currencyInput is in en-US and has the default decimal separator, so this is safe to do.
auto posOfDecimal = currencyInput.find(L'.');
m_isInputBlocked = false;
if (posOfDecimal != wstring::npos && IsCurrencyCurrentCategory)
{
m_isInputBlocked = (posOfDecimal + static_cast<size_t>(m_currencyMaxFractionDigits) + 1 == currencyInput.length());
}
else
{
m_isInputBlocked = false;
}
}
NumbersAndOperatorsEnum UnitConverterViewModel::MapCharacterToButtonId(
@ -988,7 +988,7 @@ void UnitConverterViewModel::OnPaste(String^ stringToPaste, ViewMode mode)
}
// Negate is only allowed if it's the first legal character, which is handled above.
if (NumbersAndOperatorsEnum::None != op && NumbersAndOperatorsEnum::Negate != op)
if (NumbersAndOperatorsEnum::Negate != op)
{
UCM::Command cmd = CommandFromButtonId(op);
m_model->SendCommand(cmd);

View file

@ -515,6 +515,7 @@
<Setter Target="M5.MaxWidth" Value="80"/>
<Setter Target="memButton.(Grid.Column)" Value="5"/>
<Setter Target="MemoryButton.(Grid.Column)" Value="6"/>
<Setter Target="HistoryButton.Visibility" Value="Collapsed"/>
</VisualState.Setters>
<Storyboard Completed="OnStoryboardCompleted"/>
</VisualState>

View file

@ -415,7 +415,7 @@ void Calculator::UpdateHistoryState()
SetChildAsHistory();
HistoryButton->Visibility = ::Visibility::Collapsed;
if (m_IsLastFlyoutHistory)
if (!IsProgrammer && m_IsLastFlyoutHistory)
{
DockPivot->SelectedIndex = 0;
}
@ -522,7 +522,7 @@ void Calculator::HistoryFlyout_Closed(_In_ Object ^sender, _In_ Object ^args)
AutomationProperties::SetName(HistoryButton, m_openHistoryFlyoutAutomationName);
m_fIsHistoryFlyoutOpen = false;
EnableControls(true);
if (HistoryButton->IsEnabled)
if (HistoryButton->IsEnabled && HistoryButton->Visibility == ::Visibility::Visible)
{
HistoryButton->Focus(::FocusState::Programmatic);
}

View file

@ -387,6 +387,63 @@ namespace DateCalculationUnitTests
VERIFY_IS_TRUE(StringReference(L"") != viewModel->StrDateResult);
}
TEST_METHOD(DateCalcViewModelDateDiffDaylightSavingTimeTest)
{
auto viewModel = ref new DateCalculatorViewModel();
viewModel->IsDateDiffMode = true;
VERIFY_IS_TRUE(viewModel->IsDateDiffMode);
// 29.02.2008
viewModel->FromDate = DateUtils::SystemTimeToDateTime(datetimeDifftest[5].startDate);
// 31.03.2008
viewModel->ToDate = DateUtils::SystemTimeToDateTime(datetimeDifftest[5].endDate);
//// Assert for the result
VERIFY_IS_FALSE(viewModel->IsDiffInDays);
VERIFY_ARE_EQUAL(StringReference(L"31 days"), viewModel->StrDateDiffResultInDays);
VERIFY_ARE_EQUAL(StringReference(L"1 month, 2 days"), viewModel->StrDateDiffResult);
// Daylight Saving Time - Clock Forward
// 10.03.2019
SYSTEMTIME startDate;
startDate.wYear = 2019;
startDate.wMonth = 03;
startDate.wDay = 10;
startDate.wDayOfWeek = 0;
startDate.wHour = startDate.wMinute = 0;
startDate.wSecond = startDate.wMilliseconds = 0;
viewModel->FromDate = DateUtils::SystemTimeToDateTime(startDate);
// 11.03.2019
SYSTEMTIME endDate;
endDate.wYear = 2019;
endDate.wMonth = 03;
endDate.wDay = 11;
endDate.wDayOfWeek = 0;
endDate.wHour = endDate.wMinute = 0;
endDate.wSecond = endDate.wMilliseconds = 0;
viewModel->ToDate = DateUtils::SystemTimeToDateTime(endDate);
VERIFY_IS_TRUE(viewModel->IsDiffInDays);
VERIFY_ARE_EQUAL(StringReference(L"1 day"), viewModel->StrDateDiffResult);
endDate.wDay += 6;
viewModel->ToDate = DateUtils::SystemTimeToDateTime(endDate);
VERIFY_IS_FALSE(viewModel->IsDiffInDays);
VERIFY_ARE_EQUAL(StringReference(L"1 week"), viewModel->StrDateDiffResult);
// Daylight Saving Time - Clock Backward
// 03.11.2019
startDate.wMonth = 11;
startDate.wDay = 03;
viewModel->FromDate = DateUtils::SystemTimeToDateTime(startDate);
// 04.11.2019
endDate.wMonth = 11;
endDate.wDay = 04;
viewModel->ToDate = DateUtils::SystemTimeToDateTime(endDate);
VERIFY_IS_TRUE(viewModel->IsDiffInDays);
VERIFY_ARE_EQUAL(StringReference(L"1 day"), viewModel->StrDateDiffResult);
}
TEST_METHOD(DateCalcViewModelAddTest)
{
// TODO - MSFT 10331900, fix this test