From cd7c266a6b06bccff05a1c62298cd6710e6cb484 Mon Sep 17 00:00:00 2001 From: Rudy Huyn Date: Mon, 18 Mar 2019 11:22:32 -0700 Subject: [PATCH 1/6] Fix issue with Date diff when it includes a Daylight Saving Time (#193) The application uses local time to calculate the number of days between 2 dates. If a Daylight Saving Time takes place during this period of time (only Clocks Forward 2am->3am), the application will miss 1 day and fail to calculate the number of days/weeks/months between the 2 dates. image Description of the changes: DateCalculationEngine uses local time to modify dates, however, AddDays, AddWeeks,... won't add 24 hours or 7 days if DST happens between the 2 dates, but instead add ~23.9/24.1 hours or ~6.9/7.1 days (depends if it's the DST clock backward or clock forward). When the DST is clock forward, DateCalculationEngine will miss one day. Solution use UTC dates to calculate date difference. Extra Fix: use calendar->FirstPeriodInThisDay and calendar->FirstHourInThisPeriod in ClipTime (else it will set the time to 12PM (noon) in some regions. replace OBSERVABLE_PROPERTY_RW by OBSERVABLE_PROPERTY_R when possible. remove the definition of CheckClipTimeSameDay (implementation missing) How changes were validated: Tested manually with different regions (FR, US, ES, JP). Fixes #178 --- src/CalcViewModel/Common/DateCalculator.cpp | 1 + src/CalcViewModel/DateCalculatorViewModel.cpp | 34 ++++------- src/CalcViewModel/DateCalculatorViewModel.h | 16 +++--- .../DateCalculatorUnitTests.cpp | 57 +++++++++++++++++++ 4 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/CalcViewModel/Common/DateCalculator.cpp b/src/CalcViewModel/Common/DateCalculator.cpp index f00ae3c1..63efcdd0 100644 --- a/src/CalcViewModel/Common/DateCalculator.cpp +++ b/src/CalcViewModel/Common/DateCalculator.cpp @@ -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); } diff --git a/src/CalcViewModel/DateCalculatorViewModel.cpp b/src/CalcViewModel/DateCalculatorViewModel.cpp index ab3e384b..e581a55a 100644 --- a/src/CalcViewModel/DateCalculatorViewModel.cpp +++ b/src/CalcViewModel/DateCalculatorViewModel.cpp @@ -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(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; diff --git a/src/CalcViewModel/DateCalculatorViewModel.h b/src/CalcViewModel/DateCalculatorViewModel.h index b7d8d0ef..2fffa1cc 100644 --- a/src/CalcViewModel/DateCalculatorViewModel.h +++ b/src/CalcViewModel/DateCalculatorViewModel.h @@ -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; } diff --git a/src/CalculatorUnitTests/DateCalculatorUnitTests.cpp b/src/CalculatorUnitTests/DateCalculatorUnitTests.cpp index 2516db27..9bb98fab 100644 --- a/src/CalculatorUnitTests/DateCalculatorUnitTests.cpp +++ b/src/CalculatorUnitTests/DateCalculatorUnitTests.cpp @@ -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 From b8b0fdf86bd6959d10bc388e95c039c266dcc946 Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 18 Mar 2019 20:31:04 +0100 Subject: [PATCH 2/6] Improving code style : verbosity, indentation levels (#200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixing some nested if() statements and reducing indentation levels. Making some sections less verbose, e.g: if (a == 1) { b = true; } else { b = false; } ↓ b = (a == 1) --- src/CalcManager/CEngine/History.cpp | 48 +- src/CalcManager/CalculatorManager.cpp | 138 +++--- src/CalcManager/UnitConverter.cpp | 609 +++++++++++++------------- 3 files changed, 406 insertions(+), 389 deletions(-) diff --git a/src/CalcManager/CEngine/History.cpp b/src/CalcManager/CEngine/History.cpp index 8dda170e..e5bb746b 100644 --- a/src/CalcManager/CEngine/History.cpp +++ b/src/CalcManager/CEngine/History.cpp @@ -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 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 token; + IFT(m_spTokens->GetAt(i, &token)); + int commandPosition = token.second; + if (commandPosition != -1) { - std::pair token; - IFT(m_spTokens->GetAt(i, &token)); - int commandPosition = token.second; - if (commandPosition != -1) + std::shared_ptr expCommand; + IFT(m_spCommands->GetAt(commandPosition, &expCommand)); + if (expCommand != nullptr && CalculationManager::CommandType::OperandCommand == expCommand->GetCommandType()) { - std::shared_ptr expCommand; - IFT(m_spCommands->GetAt(commandPosition, &expCommand)); - if (expCommand != nullptr && CalculationManager::CommandType::OperandCommand == expCommand->GetCommandType()) + std::shared_ptr opndCommand = std::static_pointer_cast(expCommand); + if (opndCommand != nullptr) { - std::shared_ptr opndCommand = std::static_pointer_cast(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) diff --git a/src/CalcManager/CalculatorManager.cpp b/src/CalcManager/CalculatorManager.cpp index 0f6ccd49..4d13c4a0 100644 --- a/src/CalcManager/CalculatorManager.cpp +++ b/src/CalcManager/CalculatorManager.cpp @@ -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(); } /// @@ -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); } /// @@ -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); } /// @@ -613,11 +625,13 @@ namespace CalculationManager /// Index of the target memory 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); } /// @@ -627,13 +641,15 @@ namespace CalculationManager /// Index of the target memory 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; } } diff --git a/src/CalcManager/UnitConverter.cpp b/src/CalcManager/UnitConverter.cpp index bb1df9d6..0548719a 100644 --- a/src/CalcManager/UnitConverter.cpp +++ b/src/CalcManager/UnitConverter.cpp @@ -109,22 +109,8 @@ CategorySelectionInitializer UnitConverter::SetCurrentCategory(const Category& i vector& 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() /// Unit struct we are converting to 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(); } /// @@ -181,22 +169,24 @@ void UnitConverter::SetCurrentUnitTypes(const Unit& fromType, const Unit& toType /// 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 currencyDataLoader = GetCurrencyConverterDataLoader(); - const pair 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 currencyDataLoader = GetCurrencyConverterDataLoader(); + const pair 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 * /// 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; } /// @@ -349,55 +337,58 @@ wstring UnitConverter::Serialize() void UnitConverter::DeSerialize(const wstring& serializedData) { Reset(); - if (!serializedData.empty()) + + if (serializedData.empty()) { - vector outerTokens = StringToVector(serializedData, L"|"); - assert(outerTokens.size() == EXPECTEDSERIALIZEDTOKENCOUNT); - m_fromType = StringToUnit(outerTokens[0]); - m_toType = StringToUnit(outerTokens[1]); - m_currentCategory = StringToCategory(outerTokens[2]); - vector 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 categoryListTokens = StringToVector(outerTokens[4], L","); - for (wstring token : categoryListTokens) - { - m_categories.push_back(StringToCategory(token)); - } - vector unitVectorTokens = StringToVector(outerTokens[5], L"]"); - for (wstring unitVector : unitVectorTokens) - { - vector mapcomponents = StringToVector(unitVector, L"["); - assert(mapcomponents.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT); - Category key = StringToCategory(mapcomponents[0]); - vector units = StringToVector(mapcomponents[1], L","); - for (wstring unit : units) - { - m_categoryToUnits[key].push_back(StringToUnit(unit)); - } - } - vector ratioMapTokens = StringToVector(outerTokens[6], L"]"); - for (wstring token : ratioMapTokens) - { - vector ratioMapComponentTokens = StringToVector(token, L"["); - assert(ratioMapComponentTokens.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT); - Unit key = StringToUnit(ratioMapComponentTokens[0]); - vector ratioMapList = StringToVector(ratioMapComponentTokens[1], L","); - for (wstring subtoken : ratioMapList) - { - vector 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 outerTokens = StringToVector(serializedData, L"|"); + assert(outerTokens.size() == EXPECTEDSERIALIZEDTOKENCOUNT); + m_fromType = StringToUnit(outerTokens[0]); + m_toType = StringToUnit(outerTokens[1]); + m_currentCategory = StringToCategory(outerTokens[2]); + vector 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 categoryListTokens = StringToVector(outerTokens[4], L","); + for (wstring token : categoryListTokens) + { + m_categories.push_back(StringToCategory(token)); + } + vector unitVectorTokens = StringToVector(outerTokens[5], L"]"); + for (wstring unitVector : unitVectorTokens) + { + vector mapcomponents = StringToVector(unitVector, L"["); + assert(mapcomponents.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT); + Category key = StringToCategory(mapcomponents[0]); + vector units = StringToVector(mapcomponents[1], L","); + for (wstring unit : units) + { + m_categoryToUnits[key].push_back(StringToUnit(unit)); + } + } + vector ratioMapTokens = StringToVector(outerTokens[6], L"]"); + for (wstring token : ratioMapTokens) + { + vector ratioMapComponentTokens = StringToVector(token, L"["); + assert(ratioMapComponentTokens.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT); + Unit key = StringToUnit(ratioMapComponentTokens[0]); + vector ratioMapList = StringToVector(ratioMapComponentTokens[1], L","); + for (wstring subtoken : ratioMapList) + { + vector ratioMapSubComponentTokens = StringToVector(subtoken, L":"); + assert(ratioMapSubComponentTokens.size() == EXPECTEDMAPCOMPONENTTOKENCOUNT); + Unit subkey = StringToUnit(ratioMapSubComponentTokens[0]); + ConversionData conversion = StringToConversionData(ratioMapSubComponentTokens[1]); + m_ratioMap[key][subkey] = conversion; + } + } + UpdateViewModel(); } /// @@ -406,15 +397,17 @@ void UnitConverter::DeSerialize(const wstring& serializedData) /// wstring holding the serialized data. If it does not have expected number of parameters, we will ignore it void UnitConverter::RestoreUserPreferences(const wstring& userPreferences) { - if (!userPreferences.empty()) + if (userPreferences.empty()) { - vector 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 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) /// Command enum representing the command that was entered 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(); } /// @@ -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 activeDataLoader = GetDataLoaderForCategory(category); + if (activeDataLoader == nullptr) { - shared_ptr 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 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 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(); } /// @@ -1029,22 +1026,24 @@ void UnitConverter::Calculate() /// wstring to trim 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()); + } } /// From 965e36cc5dac5a772d60f4dbadff9992b90c34d3 Mon Sep 17 00:00:00 2001 From: Shamkhal Maharramov <9804406+Maharramoff@users.noreply.github.com> Date: Tue, 19 Mar 2019 00:38:04 +0400 Subject: [PATCH 3/6] Propose minor code-cleanups (#241) Description of the changes: Remove unnecessary 'else', 'continue' statements How changes were validated: Manual. --- src/CalcViewModel/UnitConverterViewModel.cpp | 21 +++++++------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/CalcViewModel/UnitConverterViewModel.cpp b/src/CalcViewModel/UnitConverterViewModel.cpp index 7ae1eb2b..2d4df1c9 100644 --- a/src/CalcViewModel/UnitConverterViewModel.cpp +++ b/src/CalcViewModel/UnitConverterViewModel.cpp @@ -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" @@ -202,12 +202,10 @@ void UnitConverterViewModel::BuildUnitList(const vector& 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) @@ -643,10 +641,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 +875,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(m_currencyMaxFractionDigits) + 1 == currencyInput.length()); } - else - { - m_isInputBlocked = false; - } } NumbersAndOperatorsEnum UnitConverterViewModel::MapCharacterToButtonId( @@ -988,7 +981,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); From 3d18dd38c2da74f470663fcd25ddbbb9f4d4b6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Janiszewski?= Date: Mon, 18 Mar 2019 22:02:02 +0100 Subject: [PATCH 4/6] Add explicit [[fallthrough]] attributes in Ratpack (#323) --- src/CalcManager/Ratpack/conv.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CalcManager/Ratpack/conv.cpp b/src/CalcManager/Ratpack/conv.cpp index ff7b18d5..cfd2ec40 100644 --- a/src/CalcManager/Ratpack/conv.cpp +++ b/src/CalcManager/Ratpack/conv.cpp @@ -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); From 62b2fafdd0810110e0164c041c559b7ca10b79f2 Mon Sep 17 00:00:00 2001 From: Rudy Huyn Date: Mon, 18 Mar 2019 14:09:13 -0700 Subject: [PATCH 5/6] Fix formatting issues with CurrencyConverter and some locales (#242) The ViewModel wrongly assumed that non-breaking spaces were only used between the value and the symbol. It's not the case of all locales using non-breaking spaces as a thousand delimiter (French for example). When it was the case, the function only replaced the first thousand delimiter found and kept the extra space at the end of the string, generating 2 issues: Extra space at the end: #240 Bad formatting of the number: #232 Description of the changes: Replace currencyResult.find(L'\u00a0') by a regex only removing spaces at the end of the string. Fixes #240 and #232 --- src/CalcViewModel/UnitConverterViewModel.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/CalcViewModel/UnitConverterViewModel.cpp b/src/CalcViewModel/UnitConverterViewModel.cpp index 2d4df1c9..1cf478f0 100644 --- a/src/CalcViewModel/UnitConverterViewModel.cpp +++ b/src/CalcViewModel/UnitConverterViewModel.cpp @@ -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 { @@ -346,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()); } } From 21e15c426e35083f5f6d203b86cea597a47bcb57 Mon Sep 17 00:00:00 2001 From: Rudy Huyn Date: Mon, 18 Mar 2019 14:22:44 -0700 Subject: [PATCH 6/6] hide history button in programmer mode (#327) Description of the changes: Hide the History button when in Programmer mode via VisualState How changes were validated: Open Standard mode Switch to Programmer mode Verify that the History button isn't visible Fixes #326 --- src/Calculator/Views/Calculator.xaml | 1 + src/Calculator/Views/Calculator.xaml.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Calculator/Views/Calculator.xaml b/src/Calculator/Views/Calculator.xaml index b7db10eb..01164b83 100644 --- a/src/Calculator/Views/Calculator.xaml +++ b/src/Calculator/Views/Calculator.xaml @@ -515,6 +515,7 @@ + diff --git a/src/Calculator/Views/Calculator.xaml.cpp b/src/Calculator/Views/Calculator.xaml.cpp index 10858a30..65b3f45e 100644 --- a/src/Calculator/Views/Calculator.xaml.cpp +++ b/src/Calculator/Views/Calculator.xaml.cpp @@ -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); }