// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #include "pch.h" #include "StandardCalculatorViewModel.h" #include "Common/CalculatorButtonPressedEventArgs.h" #include "Common/LocalizationStringUtil.h" #include "Common/LocalizationSettings.h" #include "Common/CopyPasteManager.h" #include "Common/TraceLogger.h" using namespace CalculatorApp; using namespace CalculatorApp::Common; using namespace CalculatorApp::Common::Automation; using namespace CalculatorApp::ViewModel; using namespace CalculationManager; using namespace concurrency; using namespace Platform; using namespace Platform::Collections; using namespace std; using namespace Windows::ApplicationModel::Resources; using namespace Windows::Foundation; using namespace Windows::System; using namespace Windows::UI::Core; using namespace Windows::UI::Popups; using namespace Windows::Storage::Streams; using namespace Windows::Foundation::Collections; using namespace Utils; using namespace concurrency; constexpr int StandardModePrecision = 16; constexpr int ScientificModePrecision = 32; constexpr int ProgrammerModePrecision = 64; namespace { StringReference IsStandardPropertyName(L"IsStandard"); StringReference IsScientificPropertyName(L"IsScientific"); StringReference IsProgrammerPropertyName(L"IsProgrammer"); StringReference IsAlwaysOnTopPropertyName(L"IsAlwaysOnTop"); StringReference DisplayValuePropertyName(L"DisplayValue"); StringReference CalculationResultAutomationNamePropertyName(L"CalculationResultAutomationName"); StringReference IsBitFlipCheckedPropertyName(L"IsBitFlipChecked"); } namespace CalculatorResourceKeys { StringReference CalculatorExpression(L"Format_CalculatorExpression"); StringReference CalculatorResults(L"Format_CalculatorResults"); StringReference CalculatorResults_DecimalSeparator_Announced(L"Format_CalculatorResults_Decimal"); StringReference HexButton(L"Format_HexButtonValue"); StringReference DecButton(L"Format_DecButtonValue"); StringReference OctButton(L"Format_OctButtonValue"); StringReference BinButton(L"Format_BinButtonValue"); StringReference OpenParenthesisCountAutomationFormat(L"Format_OpenParenthesisCountAutomationNamePrefix"); StringReference NoParenthesisAdded(L"NoRightParenthesisAdded_Announcement"); StringReference MaxDigitsReachedFormat(L"Format_MaxDigitsReached"); StringReference ButtonPressFeedbackFormat(L"Format_ButtonPressAuditoryFeedback"); StringReference MemorySave(L"Format_MemorySave"); StringReference MemoryItemChanged(L"Format_MemorySlotChanged"); StringReference MemoryItemCleared(L"Format_MemorySlotCleared"); StringReference MemoryCleared(L"Memory_Cleared"); StringReference DisplayCopied(L"Display_Copied"); } StandardCalculatorViewModel::StandardCalculatorViewModel() : m_DisplayValue(L"0") , m_DecimalDisplayValue(L"0") , m_HexDisplayValue(L"0") , m_BinaryDisplayValue(L"0") , m_OctalDisplayValue(L"0") , m_BinaryDigits(ref new Vector(64, false)) , m_standardCalculatorManager(&m_calculatorDisplay, &m_resourceProvider) , m_ExpressionTokens(ref new Vector()) , m_MemorizedNumbers(ref new Vector()) , m_IsMemoryEmpty(true) , m_IsFToEChecked(false) , m_IsShiftProgrammerChecked(false) , m_valueBitLength(BitLength::BitLengthQWord) , m_isBitFlipChecked(false) , m_IsBinaryBitFlippingEnabled(false) , m_CurrentRadixType(NumberBase::DecBase) , m_CurrentAngleType(NumbersAndOperatorsEnum::Degree) , m_Announcement(nullptr) , m_OpenParenthesisCount(0) , m_feedbackForButtonPress(nullptr) , m_isRtlLanguage(false) , m_localizedMaxDigitsReachedAutomationFormat(nullptr) , m_localizedButtonPressFeedbackAutomationFormat(nullptr) , m_localizedMemorySavedAutomationFormat(nullptr) , m_localizedMemoryItemChangedAutomationFormat(nullptr) , m_localizedMemoryItemClearedAutomationFormat(nullptr) , m_localizedMemoryCleared(nullptr) , m_localizedOpenParenthesisCountChangedAutomationFormat(nullptr) , m_localizedNoRightParenthesisAddedFormat(nullptr) , m_TokenPosition(-1) , m_isLastOperationHistoryLoad(false) { WeakReference calculatorViewModel(this); auto appResourceProvider = AppResourceProvider::GetInstance(); m_calculatorDisplay.SetCallback(calculatorViewModel); m_expressionAutomationNameFormat = appResourceProvider->GetResourceString(CalculatorResourceKeys::CalculatorExpression); m_localizedCalculationResultAutomationFormat = appResourceProvider->GetResourceString(CalculatorResourceKeys::CalculatorResults); m_localizedCalculationResultDecimalAutomationFormat = appResourceProvider->GetResourceString(CalculatorResourceKeys::CalculatorResults_DecimalSeparator_Announced); m_localizedHexaDecimalAutomationFormat = appResourceProvider->GetResourceString(CalculatorResourceKeys::HexButton); m_localizedDecimalAutomationFormat = appResourceProvider->GetResourceString(CalculatorResourceKeys::DecButton); m_localizedOctalAutomationFormat = appResourceProvider->GetResourceString(CalculatorResourceKeys::OctButton); m_localizedBinaryAutomationFormat = appResourceProvider->GetResourceString(CalculatorResourceKeys::BinButton); // Initialize the Automation Name CalculationResultAutomationName = GetLocalizedStringFormat(m_localizedCalculationResultAutomationFormat, m_DisplayValue); CalculationExpressionAutomationName = GetLocalizedStringFormat(m_expressionAutomationNameFormat, L""); // Initialize history view model m_HistoryVM = ref new HistoryViewModel(&m_standardCalculatorManager); m_HistoryVM->SetCalculatorDisplay(m_calculatorDisplay); m_decimalSeparator = LocalizationSettings::GetInstance().GetDecimalSeparator(); if (CoreWindow::GetForCurrentThread() != nullptr) { // Must have a CoreWindow to access the resource context. m_isRtlLanguage = LocalizationService::GetInstance()->IsRtlLayout(); } IsEditingEnabled = false; IsUnaryOperatorEnabled = true; IsBinaryOperatorEnabled = true; IsOperandEnabled = true; IsNegateEnabled = true; IsDecimalEnabled = true; AreHistoryShortcutsEnabled = true; AreProgrammerRadixOperatorsEnabled = false; } String ^ StandardCalculatorViewModel::LocalizeDisplayValue(_In_ wstring const& displayValue, _In_ bool isError) { wstring result(displayValue); LocalizationSettings::GetInstance().LocalizeDisplayValue(&result); // WINBLUE: 440747 - In BiDi languages, error messages need to be wrapped in LRE/PDF if (isError && m_isRtlLanguage) { result.insert(result.begin(), Utils::LRE); result.push_back(Utils::PDF); } return ref new Platform::String(result.c_str()); } String ^ StandardCalculatorViewModel::CalculateNarratorDisplayValue(_In_ wstring const& displayValue, _In_ String ^ localizedDisplayValue, _In_ bool isError) { String ^ localizedValue = localizedDisplayValue; String ^ automationFormat = m_localizedCalculationResultAutomationFormat; // The narrator doesn't read the decimalSeparator if it's the last character if (Utils::IsLastCharacterTarget(displayValue, m_decimalSeparator)) { // remove the decimal separator, to avoid a long pause between words localizedValue = LocalizeDisplayValue(displayValue.substr(0, displayValue.length() - 1), isError); // Use a format which has a word in the decimal separator's place // "The Display is 10 point" automationFormat = m_localizedCalculationResultDecimalAutomationFormat; } // In Programmer modes using non-base10, we want the strings to be read as literal digits. if (IsProgrammer && CurrentRadixType != NumberBase::DecBase) { localizedValue = GetNarratorStringReadRawNumbers(localizedValue); } return GetLocalizedStringFormat(automationFormat, localizedValue); } String ^ StandardCalculatorViewModel::GetNarratorStringReadRawNumbers(_In_ String ^ localizedDisplayValue) { wstringstream wss; auto& locSettings = LocalizationSettings::GetInstance(); // Insert a space after each digit in the string, to force Narrator to read them as separate numbers. wstring wstrValue(localizedDisplayValue->Data()); for (wchar_t& c : wstrValue) { wss << c; if (locSettings.IsLocalizedHexDigit(c)) { wss << L' '; } } return ref new String(wss.str().c_str()); } void StandardCalculatorViewModel::SetPrimaryDisplay(_In_ String ^ displayStringValue, _In_ bool isError) { String ^ localizedDisplayStringValue = LocalizeDisplayValue(displayStringValue->Data(), isError); // Set this variable before the DisplayValue is modified, Otherwise the DisplayValue will // not match what the narrator is saying m_CalculationResultAutomationName = CalculateNarratorDisplayValue(displayStringValue->Data(), localizedDisplayStringValue, isError); AreAlwaysOnTopResultsUpdated = false; if (DisplayValue != localizedDisplayStringValue) { DisplayValue = localizedDisplayStringValue; AreAlwaysOnTopResultsUpdated = true; } IsInError = isError; if (IsProgrammer) { UpdateProgrammerPanelDisplay(); } } void StandardCalculatorViewModel::DisplayPasteError() { m_standardCalculatorManager.DisplayPasteError(); } void StandardCalculatorViewModel::SetParenthesisCount(_In_ unsigned int parenthesisCount) { if (m_OpenParenthesisCount == parenthesisCount) { return; } OpenParenthesisCount = parenthesisCount; if (IsProgrammer || IsScientific) { SetOpenParenthesisCountNarratorAnnouncement(); } } void StandardCalculatorViewModel::SetOpenParenthesisCountNarratorAnnouncement() { wstring localizedParenthesisCount = to_wstring(m_OpenParenthesisCount).c_str(); LocalizationSettings::GetInstance().LocalizeDisplayValue(&localizedParenthesisCount); if (m_localizedOpenParenthesisCountChangedAutomationFormat == nullptr) { m_localizedOpenParenthesisCountChangedAutomationFormat = AppResourceProvider::GetInstance()->GetResourceString(CalculatorResourceKeys::OpenParenthesisCountAutomationFormat); } String ^ announcement = LocalizationStringUtil::GetLocalizedString(m_localizedOpenParenthesisCountChangedAutomationFormat, StringReference(localizedParenthesisCount.c_str())); Announcement = CalculatorAnnouncement::GetOpenParenthesisCountChangedAnnouncement(announcement); } void StandardCalculatorViewModel::OnNoRightParenAdded() { SetNoParenAddedNarratorAnnouncement(); } void StandardCalculatorViewModel::SetNoParenAddedNarratorAnnouncement() { if (m_localizedNoRightParenthesisAddedFormat == nullptr) { m_localizedNoRightParenthesisAddedFormat = AppResourceProvider::GetInstance()->GetResourceString(CalculatorResourceKeys::NoParenthesisAdded); } Announcement = CalculatorAnnouncement::GetNoRightParenthesisAddedAnnouncement(m_localizedNoRightParenthesisAddedFormat); } void StandardCalculatorViewModel::DisableButtons(CommandType selectedExpressionCommandType) { if (selectedExpressionCommandType == CommandType::OperandCommand) { IsBinaryOperatorEnabled = false; IsUnaryOperatorEnabled = false; IsOperandEnabled = true; IsNegateEnabled = true; IsDecimalEnabled = true; } if (selectedExpressionCommandType == CommandType::BinaryCommand) { IsBinaryOperatorEnabled = true; IsUnaryOperatorEnabled = false; IsOperandEnabled = false; IsNegateEnabled = false; IsDecimalEnabled = false; } if (selectedExpressionCommandType == CommandType::UnaryCommand) { IsBinaryOperatorEnabled = false; IsUnaryOperatorEnabled = true; IsOperandEnabled = false; IsNegateEnabled = true; IsDecimalEnabled = false; } } void StandardCalculatorViewModel::SetExpressionDisplay( _Inout_ shared_ptr>> const& tokens, _Inout_ shared_ptr>> const& commands) { m_tokens = tokens; m_commands = commands; if (!IsEditingEnabled) { SetTokens(tokens); } CalculationExpressionAutomationName = GetCalculatorExpressionAutomationName(); AreTokensUpdated = true; } void StandardCalculatorViewModel::SetHistoryExpressionDisplay( _Inout_ shared_ptr>> const& tokens, _Inout_ shared_ptr>> const& commands) { m_tokens = make_shared>>(*tokens); m_commands = make_shared>>(*commands); IsEditingEnabled = false; // Setting the History Item Load Mode so that UI does not get updated with recalculation of every token m_standardCalculatorManager.SetInHistoryItemLoadMode(true); Recalculate(true); m_standardCalculatorManager.SetInHistoryItemLoadMode(false); m_isLastOperationHistoryLoad = true; } void StandardCalculatorViewModel::SetTokens(_Inout_ shared_ptr>> const& tokens) { AreTokensUpdated = false; const size_t nTokens = tokens->size(); if (nTokens == 0) { m_ExpressionTokens->Clear(); return; } const auto& localizer = LocalizationSettings::GetInstance(); const wstring separator = L" "; for (unsigned int i = 0; i < nTokens; ++i) { auto currentToken = (*tokens)[i]; Common::TokenType type; bool isEditable = currentToken.second != -1; localizer.LocalizeDisplayValue(&(currentToken.first)); if (!isEditable) { type = currentToken.first == separator ? TokenType::Separator : TokenType::Operator; } else { const shared_ptr& command = m_commands->at(currentToken.second); type = command->GetCommandType() == CommandType::OperandCommand ? TokenType::Operand : TokenType::Operator; } auto currentTokenString = StringReference(currentToken.first.c_str()); if (i < m_ExpressionTokens->Size) { auto existingItem = m_ExpressionTokens->GetAt(i); if (type == existingItem->Type && existingItem->Token->Equals(currentTokenString)) { existingItem->TokenPosition = i; existingItem->IsTokenEditable = isEditable; existingItem->CommandIndex = 0; } else { auto expressionToken = ref new DisplayExpressionToken(currentTokenString, i, isEditable, type); m_ExpressionTokens->InsertAt(i, expressionToken); } } else { auto expressionToken = ref new DisplayExpressionToken(currentTokenString, i, isEditable, type); m_ExpressionTokens->Append(expressionToken); } } while (m_ExpressionTokens->Size != nTokens) { m_ExpressionTokens->RemoveAtEnd(); } } String ^ StandardCalculatorViewModel::GetCalculatorExpressionAutomationName() { String ^ expression = L""; for (auto&& token : m_ExpressionTokens) { expression += LocalizationService::GetNarratorReadableToken(token->Token); } return GetLocalizedStringFormat(m_expressionAutomationNameFormat, expression); } void StandardCalculatorViewModel::SetMemorizedNumbers(const vector& newMemorizedNumbers) { const auto& localizer = LocalizationSettings::GetInstance(); if (newMemorizedNumbers.size() == 0) // Memory has been cleared { MemorizedNumbers->Clear(); IsMemoryEmpty = true; } // A new value is added to the memory else if (newMemorizedNumbers.size() > MemorizedNumbers->Size) { while (newMemorizedNumbers.size() > MemorizedNumbers->Size) { size_t newValuePosition = newMemorizedNumbers.size() - MemorizedNumbers->Size - 1; auto stringValue = newMemorizedNumbers.at(newValuePosition); MemoryItemViewModel ^ memorySlot = ref new MemoryItemViewModel(this); memorySlot->Position = 0; localizer.LocalizeDisplayValue(&stringValue); memorySlot->Value = Utils::LRO + ref new String(stringValue.c_str()) + Utils::PDF; MemorizedNumbers->InsertAt(0, memorySlot); IsMemoryEmpty = IsAlwaysOnTop; // Update the slot position for the rest of the slots for (unsigned int i = 1; i < MemorizedNumbers->Size; i++) { MemorizedNumbers->GetAt(i)->Position++; } } } else if (newMemorizedNumbers.size() == MemorizedNumbers->Size) // Either M+ or M- { for (unsigned int i = 0; i < MemorizedNumbers->Size; i++) { auto newStringValue = newMemorizedNumbers.at(i); localizer.LocalizeDisplayValue(&newStringValue); // If the value is different, update the value if (MemorizedNumbers->GetAt(i)->Value != StringReference(newStringValue.c_str())) { MemorizedNumbers->GetAt(i)->Value = Utils::LRO + ref new String(newStringValue.c_str()) + Utils::PDF; } } } } void StandardCalculatorViewModel::FtoEButtonToggled() { OnButtonPressed(NumbersAndOperatorsEnum::FToE); } void StandardCalculatorViewModel::HandleUpdatedOperandData(Command cmdenum) { DisplayExpressionToken ^ displayExpressionToken = ExpressionTokens->GetAt(m_TokenPosition); if (displayExpressionToken == nullptr) { return; } if ((displayExpressionToken->Token == nullptr) || (displayExpressionToken->Token->Length() == 0)) { displayExpressionToken->CommandIndex = 0; } wchar_t ch = 0; if ((cmdenum >= Command::Command0) && (cmdenum <= Command::Command9)) { switch (cmdenum) { case Command::Command0: ch = L'0'; break; case Command::Command1: ch = L'1'; break; case Command::Command2: ch = L'2'; break; case Command::Command3: ch = L'3'; break; case Command::Command4: ch = L'4'; break; case Command::Command5: ch = L'5'; break; case Command::Command6: ch = L'6'; break; case Command::Command7: ch = L'7'; break; case Command::Command8: ch = L'8'; break; case Command::Command9: ch = L'9'; break; } } else if (cmdenum == Command::CommandPNT) { ch = L'.'; } else if (cmdenum == Command::CommandBACK) { ch = L'x'; } else { return; } int length = 0; wchar_t* temp = new wchar_t[100]; const wchar_t* data = m_selectedExpressionLastData->Data(); int i = 0, j = 0; int commandIndex = displayExpressionToken->CommandIndex; if (IsOperandTextCompletelySelected) { // Clear older text; m_selectedExpressionLastData = L""; if (ch == L'x') { temp[0] = L'\0'; commandIndex = 0; } else { temp[0] = ch; temp[1] = L'\0'; commandIndex = 1; } IsOperandTextCompletelySelected = false; } else { if (ch == L'x') { if (commandIndex == 0) { delete[] temp; return; } length = m_selectedExpressionLastData->Length(); for (; j < length; ++j) { if (j == commandIndex - 1) { continue; } temp[i++] = data[j]; } temp[i] = L'\0'; commandIndex -= 1; } else { length = m_selectedExpressionLastData->Length() + 1; if (length > 50) { delete[] temp; return; } for (; i < length; ++i) { if (i == commandIndex) { temp[i] = ch; continue; } temp[i] = data[j++]; } temp[i] = L'\0'; commandIndex += 1; } } String ^ updatedData = ref new String(temp); UpdateOperand(m_TokenPosition, updatedData); displayExpressionToken->Token = updatedData; IsOperandUpdatedUsingViewModel = true; displayExpressionToken->CommandIndex = commandIndex; } bool StandardCalculatorViewModel::IsOperator(Command cmdenum) { if ((cmdenum >= Command::Command0 && cmdenum <= Command::Command9) || (cmdenum == Command::CommandPNT) || (cmdenum == Command::CommandBACK) || (cmdenum == Command::CommandEXP) || (cmdenum == Command::CommandFE) || (cmdenum == Command::ModeBasic) || (cmdenum == Command::ModeProgrammer) || (cmdenum == Command::ModeScientific) || (cmdenum == Command::CommandINV) || (cmdenum == Command::CommandCENTR) || (cmdenum == Command::CommandDEG) || (cmdenum == Command::CommandRAD) || (cmdenum == Command::CommandGRAD) || ((cmdenum >= Command::CommandBINEDITSTART) && (cmdenum <= Command::CommandBINEDITEND))) { return false; } return true; } void StandardCalculatorViewModel::OnButtonPressed(Object ^ parameter) { m_feedbackForButtonPress = CalculatorButtonPressedEventArgs::GetAuditoryFeedbackFromCommandParameter(parameter); NumbersAndOperatorsEnum numOpEnum = CalculatorButtonPressedEventArgs::GetOperationFromCommandParameter(parameter); Command cmdenum = ConvertToOperatorsEnum(numOpEnum); if (IsInError) { m_standardCalculatorManager.SendCommand(Command::CommandCLEAR); if (!IsRecoverableCommand(static_cast(numOpEnum))) { return; } } if (IsEditingEnabled && numOpEnum != NumbersAndOperatorsEnum::IsScientificMode && numOpEnum != NumbersAndOperatorsEnum::IsStandardMode && numOpEnum != NumbersAndOperatorsEnum::IsProgrammerMode && numOpEnum != NumbersAndOperatorsEnum::FToE && (numOpEnum != NumbersAndOperatorsEnum::Degree) && (numOpEnum != NumbersAndOperatorsEnum::Radians) && (numOpEnum != NumbersAndOperatorsEnum::Grads)) { if (!m_KeyPressed) { SaveEditedCommand(m_selectedExpressionToken->TokenPosition, cmdenum); } } else { if (numOpEnum == NumbersAndOperatorsEnum::IsStandardMode || numOpEnum == NumbersAndOperatorsEnum::IsScientificMode || numOpEnum == NumbersAndOperatorsEnum::IsProgrammerMode) { IsEditingEnabled = false; } if (numOpEnum == NumbersAndOperatorsEnum::Memory) { OnMemoryButtonPressed(); } else { if (numOpEnum == NumbersAndOperatorsEnum::Clear || numOpEnum == NumbersAndOperatorsEnum::ClearEntry || numOpEnum == NumbersAndOperatorsEnum::IsStandardMode || numOpEnum == NumbersAndOperatorsEnum::IsProgrammerMode) { // On Clear('C') the F-E button needs to be UnChecked if it in Checked state. // Also, the Primary Display Value should not show in exponential format. // Hence the check below to ensure parity with Desktop Calculator. // Clear the FE mode if the switching to StandardMode, since 'C'/'CE' in StandardMode // doesn't honor the FE button. if (IsFToEChecked) { IsFToEChecked = false; } } if (numOpEnum == NumbersAndOperatorsEnum::Degree || numOpEnum == NumbersAndOperatorsEnum::Radians || numOpEnum == NumbersAndOperatorsEnum::Grads) { m_CurrentAngleType = numOpEnum; } if ((cmdenum >= Command::Command0 && cmdenum <= Command::Command9) || (cmdenum == Command::CommandPNT) || (cmdenum == Command::CommandBACK) || (cmdenum == Command::CommandEXP)) { IsOperatorCommand = false; } else { IsOperatorCommand = true; } if (m_isLastOperationHistoryLoad && ((numOpEnum != NumbersAndOperatorsEnum::Degree) && (numOpEnum != NumbersAndOperatorsEnum::Radians) && (numOpEnum != NumbersAndOperatorsEnum::Grads))) { IsFToEEnabled = true; m_isLastOperationHistoryLoad = false; } TraceLogger::GetInstance()->UpdateButtonUsage(numOpEnum, GetCalculatorMode()); m_standardCalculatorManager.SendCommand(cmdenum); } } } RADIX_TYPE StandardCalculatorViewModel::GetRadixTypeFromNumberBase(NumberBase base) { switch (base) { case NumberBase::BinBase: return RADIX_TYPE::BIN_RADIX; case NumberBase::HexBase: return RADIX_TYPE::HEX_RADIX; case NumberBase::OctBase: return RADIX_TYPE::OCT_RADIX; default: return RADIX_TYPE::DEC_RADIX; } } void StandardCalculatorViewModel::OnCopyCommand(Object ^ parameter) { CopyPasteManager::CopyToClipboard(GetRawDisplayValue()); String ^ announcement = AppResourceProvider::GetInstance()->GetResourceString(CalculatorResourceKeys::DisplayCopied); Announcement = CalculatorAnnouncement::GetDisplayCopiedAnnouncement(announcement); } void StandardCalculatorViewModel::OnPasteCommand(Object ^ parameter) { auto that(this); ViewMode mode; BitLength bitLengthType = BitLength::BitLengthUnknown; NumberBase numberBase = NumberBase::Unknown; if (IsScientific) { mode = ViewMode::Scientific; } else if (IsProgrammer) { mode = ViewMode::Programmer; bitLengthType = m_valueBitLength; numberBase = CurrentRadixType; } else { mode = ViewMode::Standard; } // if there's nothing to copy early out if (IsEditingEnabled || !CopyPasteManager::HasStringToPaste()) { return; } // Ensure that the paste happens on the UI thread create_task(CopyPasteManager::GetStringToPaste(mode, NavCategory::GetGroupType(mode), numberBase, bitLengthType)) .then([that, mode](String ^ pastedString) { that->OnPaste(pastedString); }, concurrency::task_continuation_context::use_current()); } CalculationManager::Command StandardCalculatorViewModel::ConvertToOperatorsEnum(NumbersAndOperatorsEnum operation) { return safe_cast(operation); } void StandardCalculatorViewModel::OnPaste(String ^ pastedString) { // If pastedString is invalid("NoOp") then display pasteError else process the string if (CopyPasteManager::IsErrorMessage(pastedString)) { this->DisplayPasteError(); return; } TraceLogger::GetInstance()->LogInputPasted(GetCalculatorMode()); bool isFirstLegalChar = true; m_standardCalculatorManager.SendCommand(Command::CommandCENTR); bool sendNegate = false; bool processedDigit = false; bool sentEquals = false; bool isPreviousOperator = false; vector negateStack; // Iterate through each character pasted, and if it's valid, send it to the model. auto it = pastedString->Begin(); while (it != pastedString->End()) { bool sendCommand = true; auto buttonInfo = MapCharacterToButtonId(*it); NumbersAndOperatorsEnum mappedNumOp = buttonInfo.buttonId; bool canSendNegate = buttonInfo.canSendNegate; if (mappedNumOp == NumbersAndOperatorsEnum::None) { ++it; continue; } if (isFirstLegalChar || isPreviousOperator) { isFirstLegalChar = false; isPreviousOperator = false; // If the character is a - sign, send negate // after sending the next legal character. Send nothing now, or // it will be ignored. if (NumbersAndOperatorsEnum::Subtract == mappedNumOp) { sendNegate = true; sendCommand = false; } // Support (+) sign prefix if (NumbersAndOperatorsEnum::Add == mappedNumOp) { sendCommand = false; } } switch (mappedNumOp) { // Opening parenthesis starts a new expression and pushes negation state onto the stack case NumbersAndOperatorsEnum::OpenParenthesis: negateStack.push_back(sendNegate); sendNegate = false; break; // Closing parenthesis pops the negation state off the stack and sends it down to the calc engine case NumbersAndOperatorsEnum::CloseParenthesis: if (!negateStack.empty()) { sendNegate = negateStack.back(); negateStack.pop_back(); canSendNegate = true; } else { // Don't send a closing parenthesis if a matching opening parenthesis hasn't been sent already sendCommand = false; } break; case NumbersAndOperatorsEnum::Zero: case NumbersAndOperatorsEnum::One: case NumbersAndOperatorsEnum::Two: case NumbersAndOperatorsEnum::Three: case NumbersAndOperatorsEnum::Four: case NumbersAndOperatorsEnum::Five: case NumbersAndOperatorsEnum::Six: case NumbersAndOperatorsEnum::Seven: case NumbersAndOperatorsEnum::Eight: case NumbersAndOperatorsEnum::Nine: processedDigit = true; break; case NumbersAndOperatorsEnum::Add: case NumbersAndOperatorsEnum::Subtract: case NumbersAndOperatorsEnum::Multiply: case NumbersAndOperatorsEnum::Divide: isPreviousOperator = true; break; } if (sendCommand) { sentEquals = (mappedNumOp == NumbersAndOperatorsEnum::Equals); Command cmdenum = ConvertToOperatorsEnum(mappedNumOp); m_standardCalculatorManager.SendCommand(cmdenum); // The CalcEngine state machine won't allow the negate command to be sent before any // other digits, so instead a flag is set and the command is sent after the first appropriate // command. if (sendNegate) { if (canSendNegate) { Command cmdNegate = ConvertToOperatorsEnum(NumbersAndOperatorsEnum::Negate); m_standardCalculatorManager.SendCommand(cmdNegate); } // Can't send negate on a leading zero, so wait until the appropriate time to send it. if (NumbersAndOperatorsEnum::Zero != mappedNumOp && NumbersAndOperatorsEnum::Decimal != mappedNumOp) { sendNegate = false; } } } // Handle exponent and exponent sign (...e+... or ...e-... or ...e...) if (mappedNumOp == NumbersAndOperatorsEnum::Exp) { // Check the following item switch (MapCharacterToButtonId(*(it + 1)).buttonId) { case NumbersAndOperatorsEnum::Subtract: { Command cmdNegate = ConvertToOperatorsEnum(NumbersAndOperatorsEnum::Negate); m_standardCalculatorManager.SendCommand(cmdNegate); ++it; } break; case NumbersAndOperatorsEnum::Add: { // Nothing to do, skip to the next item ++it; } break; } } ++it; } } void StandardCalculatorViewModel::OnClearMemoryCommand(Object ^ parameter) { m_standardCalculatorManager.MemorizedNumberClearAll(); TraceLogger::GetInstance()->UpdateButtonUsage(NumbersAndOperatorsEnum::MemoryClear, GetCalculatorMode()); if (m_localizedMemoryCleared == nullptr) { m_localizedMemoryCleared = AppResourceProvider::GetInstance()->GetResourceString(CalculatorResourceKeys::MemoryCleared); } Announcement = CalculatorAnnouncement::GetMemoryClearedAnnouncement(m_localizedMemoryCleared); } void StandardCalculatorViewModel::OnPinUnpinCommand(Object ^ parameter) { SetViewPinnedState(!IsViewPinned()); } bool StandardCalculatorViewModel::IsViewPinned() { return m_IsCurrentViewPinned; } void StandardCalculatorViewModel::SetViewPinnedState(bool pinned) { IsCurrentViewPinned = pinned; } ButtonInfo StandardCalculatorViewModel::MapCharacterToButtonId(char16 ch) { ButtonInfo result; result.buttonId = NumbersAndOperatorsEnum::None; result.canSendNegate = false; switch (ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': result.buttonId = NumbersAndOperatorsEnum::Zero + static_cast(ch - L'0'); result.canSendNegate = true; break; case '*': result.buttonId = NumbersAndOperatorsEnum::Multiply; break; case '+': result.buttonId = NumbersAndOperatorsEnum::Add; break; case '-': result.buttonId = NumbersAndOperatorsEnum::Subtract; break; case '/': result.buttonId = NumbersAndOperatorsEnum::Divide; break; case '^': if (IsScientific) { result.buttonId = NumbersAndOperatorsEnum::XPowerY; } break; case '%': if (IsScientific || IsProgrammer) { result.buttonId = NumbersAndOperatorsEnum::Mod; } break; case '=': result.buttonId = NumbersAndOperatorsEnum::Equals; break; case '(': result.buttonId = NumbersAndOperatorsEnum::OpenParenthesis; break; case ')': result.buttonId = NumbersAndOperatorsEnum::CloseParenthesis; break; case 'a': case 'A': result.buttonId = NumbersAndOperatorsEnum::A; break; case 'b': case 'B': result.buttonId = NumbersAndOperatorsEnum::B; break; case 'c': case 'C': result.buttonId = NumbersAndOperatorsEnum::C; break; case 'd': case 'D': result.buttonId = NumbersAndOperatorsEnum::D; break; case 'e': case 'E': // Only allow scientific notation in scientific mode if (IsProgrammer) { result.buttonId = NumbersAndOperatorsEnum::E; } else { result.buttonId = NumbersAndOperatorsEnum::Exp; } break; case 'f': case 'F': result.buttonId = NumbersAndOperatorsEnum::F; break; default: // For the decimalSeparator, we need to respect the user setting. if (ch == m_decimalSeparator) { result.buttonId = NumbersAndOperatorsEnum::Decimal; } break; } if (result.buttonId == NumbersAndOperatorsEnum::None) { if (LocalizationSettings::GetInstance().IsLocalizedDigit(ch)) { result.buttonId = NumbersAndOperatorsEnum::Zero + static_cast(ch - LocalizationSettings::GetInstance().GetDigitSymbolFromEnUsDigit('0')); result.canSendNegate = true; } } // Negate cannot be sent for leading zeroes if (NumbersAndOperatorsEnum::Zero == result.buttonId) { result.canSendNegate = false; } return result; } void StandardCalculatorViewModel::OnInputChanged() { IsInputEmpty = m_standardCalculatorManager.IsInputEmpty(); } void StandardCalculatorViewModel::OnMemoryButtonPressed() { m_standardCalculatorManager.MemorizeNumber(); TraceLogger::GetInstance()->UpdateButtonUsage(NumbersAndOperatorsEnum::Memory, GetCalculatorMode()); if (m_localizedMemorySavedAutomationFormat == nullptr) { m_localizedMemorySavedAutomationFormat = AppResourceProvider::GetInstance()->GetResourceString(CalculatorResourceKeys::MemorySave); } String ^ announcement = LocalizationStringUtil::GetLocalizedString(m_localizedMemorySavedAutomationFormat, m_DisplayValue); Announcement = CalculatorAnnouncement::GetMemoryItemAddedAnnouncement(announcement); } void StandardCalculatorViewModel::OnMemoryItemChanged(unsigned int indexOfMemory) { if (indexOfMemory < MemorizedNumbers->Size) { MemoryItemViewModel ^ memSlot = MemorizedNumbers->GetAt(indexOfMemory); String ^ localizedValue = memSlot->Value; wstring localizedIndex = to_wstring(indexOfMemory + 1); LocalizationSettings::GetInstance().LocalizeDisplayValue(&localizedIndex); if (m_localizedMemoryItemChangedAutomationFormat == nullptr) { m_localizedMemoryItemChangedAutomationFormat = AppResourceProvider::GetInstance()->GetResourceString(CalculatorResourceKeys::MemoryItemChanged); } String ^ announcement = LocalizationStringUtil::GetLocalizedString(m_localizedMemoryItemChangedAutomationFormat, StringReference(localizedIndex.c_str()), localizedValue); Announcement = CalculatorAnnouncement::GetMemoryItemChangedAnnouncement(announcement); } } void StandardCalculatorViewModel::OnMemoryItemPressed(Object ^ memoryItemPosition) { if (MemorizedNumbers && MemorizedNumbers->Size > 0) { auto boxedPosition = safe_cast ^>(memoryItemPosition); m_standardCalculatorManager.MemorizedNumberLoad(boxedPosition->Value); HideMemoryClicked(); auto mode = IsStandard ? ViewMode::Standard : IsScientific ? ViewMode::Scientific : ViewMode::Programmer; TraceLogger::GetInstance()->LogMemoryItemLoad(mode, MemorizedNumbers->Size, boxedPosition->Value); } } void StandardCalculatorViewModel::OnMemoryAdd(Object ^ memoryItemPosition) { // M+ will add display to memorylist if memory list is empty. if (MemorizedNumbers) { auto boxedPosition = safe_cast ^>(memoryItemPosition); TraceLogger::GetInstance()->UpdateButtonUsage(NumbersAndOperatorsEnum::MemoryAdd, GetCalculatorMode()); m_standardCalculatorManager.MemorizedNumberAdd(boxedPosition->Value); } } void StandardCalculatorViewModel::OnMemorySubtract(Object ^ memoryItemPosition) { // M- will add negative of displayed number to memorylist if memory list is empty. if (MemorizedNumbers) { auto boxedPosition = safe_cast ^>(memoryItemPosition); TraceLogger::GetInstance()->UpdateButtonUsage(NumbersAndOperatorsEnum::MemorySubtract, GetCalculatorMode()); m_standardCalculatorManager.MemorizedNumberSubtract(boxedPosition->Value); } } void StandardCalculatorViewModel::OnMemoryClear(_In_ Object ^ memoryItemPosition) { if (MemorizedNumbers && MemorizedNumbers->Size > 0) { auto boxedPosition = safe_cast ^>(memoryItemPosition); if (boxedPosition->Value >= 0) { unsigned int unsignedPosition = safe_cast(boxedPosition->Value); m_standardCalculatorManager.MemorizedNumberClear(unsignedPosition); MemorizedNumbers->RemoveAt(unsignedPosition); for (unsigned int i = 0; i < MemorizedNumbers->Size; i++) { MemorizedNumbers->GetAt(i)->Position = i; } if (MemorizedNumbers->Size == 0) { IsMemoryEmpty = true; } TraceLogger::GetInstance()->UpdateButtonUsage(NumbersAndOperatorsEnum::MemoryClear, GetCalculatorMode()); wstring localizedIndex = to_wstring(boxedPosition->Value + 1); LocalizationSettings::GetInstance().LocalizeDisplayValue(&localizedIndex); if (m_localizedMemoryItemClearedAutomationFormat == nullptr) { m_localizedMemoryItemClearedAutomationFormat = AppResourceProvider::GetInstance()->GetResourceString(CalculatorResourceKeys::MemoryItemCleared); } String ^ announcement = LocalizationStringUtil::GetLocalizedString(m_localizedMemoryItemClearedAutomationFormat, StringReference(localizedIndex.c_str())); Announcement = CalculatorAnnouncement::GetMemoryClearedAnnouncement(announcement); } } } void StandardCalculatorViewModel::OnPropertyChanged(String ^ propertyname) { if (propertyname == IsScientificPropertyName) { if (IsScientific) { OnButtonPressed(NumbersAndOperatorsEnum::IsScientificMode); } } else if (propertyname == IsProgrammerPropertyName) { if (IsProgrammer) { OnButtonPressed(NumbersAndOperatorsEnum::IsProgrammerMode); } } else if (propertyname == IsStandardPropertyName) { if (IsStandard) { OnButtonPressed(NumbersAndOperatorsEnum::IsStandardMode); } } else if (propertyname == DisplayValuePropertyName) { RaisePropertyChanged(CalculationResultAutomationNamePropertyName); Announcement = GetDisplayUpdatedNarratorAnnouncement(); } else if (propertyname == IsBitFlipCheckedPropertyName) { TraceLogger::GetInstance()->UpdateButtonUsage( IsBitFlipChecked ? NumbersAndOperatorsEnum::BitflipButton : NumbersAndOperatorsEnum::FullKeypadButton, ViewMode::Programmer); } } void StandardCalculatorViewModel::SetCalculatorType(ViewMode targetState) { // Reset error state so that commands caused by the mode change are still // sent if calc is currently in error state. IsInError = false; // Setting one of these properties to true will set the others to false. switch (targetState) { case ViewMode::Standard: IsStandard = true; ResetDisplay(); SetPrecision(StandardModePrecision); UpdateMaxIntDigits(); break; case ViewMode::Scientific: IsScientific = true; ResetDisplay(); SetPrecision(ScientificModePrecision); break; case ViewMode::Programmer: IsProgrammer = true; ResetDisplay(); SetPrecision(ProgrammerModePrecision); break; } } String ^ StandardCalculatorViewModel::GetRawDisplayValue() { if (IsInError) { return DisplayValue; } else { return LocalizationSettings::GetInstance().RemoveGroupSeparators(DisplayValue); } } // Given a format string, returns a string with the input display value inserted. // 'format' is a localized string containing a %1 formatting mark where the display value should be inserted. // 'displayValue' is a localized string containing a numerical value to be displayed to the user. String ^ StandardCalculatorViewModel::GetLocalizedStringFormat(String ^ format, String ^ displayValue) { return LocalizationStringUtil::GetLocalizedString(format, displayValue); } void StandardCalculatorViewModel::ResetDisplay() { AreHEXButtonsEnabled = false; CurrentRadixType = NumberBase::DecBase; m_standardCalculatorManager.SetRadix(DEC_RADIX); } void StandardCalculatorViewModel::SetPrecision(int32_t precision) { m_standardCalculatorManager.SetPrecision(precision); } void StandardCalculatorViewModel::SwitchProgrammerModeBase(NumberBase numberBase) { if (IsInError) { m_standardCalculatorManager.SendCommand(Command::CommandCLEAR); } AreHEXButtonsEnabled = numberBase == NumberBase::HexBase; CurrentRadixType = numberBase; m_standardCalculatorManager.SetRadix(GetRadixTypeFromNumberBase(numberBase)); } void StandardCalculatorViewModel::SetMemorizedNumbersString() { m_standardCalculatorManager.SetMemorizedNumbersString(); } ANGLE_TYPE GetAngleTypeFromCommand(Command command) { switch (command) { case Command::CommandDEG: return ANGLE_DEG; case Command::CommandRAD: return ANGLE_RAD; case Command::CommandGRAD: return ANGLE_GRAD; default: throw ref new Exception(E_FAIL, L"Invalid command type"); } } void StandardCalculatorViewModel::SaveEditedCommand(_In_ unsigned int tokenPosition, _In_ Command command) { bool handleOperand = false; wstring updatedToken; const pair& token = m_tokens->at(tokenPosition); const shared_ptr& tokenCommand = m_commands->at(token.second); if (IsUnaryOp(command) && command != Command::CommandSIGN) { int angleCmd = static_cast(m_standardCalculatorManager.GetCurrentDegreeMode()); ANGLE_TYPE angleType = GetAngleTypeFromCommand(static_cast(angleCmd)); if (IsTrigOp(command)) { shared_ptr spUnaryCommand = dynamic_pointer_cast(tokenCommand); spUnaryCommand->SetCommands(angleCmd, static_cast(command)); } else { shared_ptr spUnaryCommand = dynamic_pointer_cast(tokenCommand); spUnaryCommand->SetCommand(static_cast(command)); } switch (command) { case Command::CommandASIN: updatedToken = CCalcEngine::OpCodeToUnaryString(static_cast(Command::CommandSIN), true, angleType); break; case Command::CommandACOS: updatedToken = CCalcEngine::OpCodeToUnaryString(static_cast(Command::CommandCOS), true, angleType); break; case Command::CommandATAN: updatedToken = CCalcEngine::OpCodeToUnaryString(static_cast(Command::CommandTAN), true, angleType); break; case Command::CommandASINH: updatedToken = CCalcEngine::OpCodeToUnaryString(static_cast(Command::CommandSINH), true, angleType); break; case Command::CommandACOSH: updatedToken = CCalcEngine::OpCodeToUnaryString(static_cast(Command::CommandCOSH), true, angleType); break; case Command::CommandATANH: updatedToken = CCalcEngine::OpCodeToUnaryString(static_cast(Command::CommandTANH), true, angleType); break; case Command::CommandPOWE: updatedToken = CCalcEngine::OpCodeToUnaryString(static_cast(Command::CommandLN), true, angleType); break; default: updatedToken = CCalcEngine::OpCodeToUnaryString(static_cast(command), false, angleType); } if ((token.first.length() > 0) && (token.first[token.first.length() - 1] == L'(')) { updatedToken += L'('; } } else if (IsBinOp(command)) { shared_ptr spBinaryCommand = dynamic_pointer_cast(tokenCommand); spBinaryCommand->SetCommand(static_cast(command)); updatedToken = CCalcEngine::OpCodeToString(static_cast(command)); } else if (IsOpnd(command) || command == Command::CommandBACK) { HandleUpdatedOperandData(command); handleOperand = true; } else if (command == Command::CommandSIGN) { if (tokenCommand->GetCommandType() == CommandType::UnaryCommand) { shared_ptr spSignCommand = make_shared(static_cast(command)); m_commands->insert(m_commands->begin() + token.second + 1, spSignCommand); } else { shared_ptr spOpndCommand = dynamic_pointer_cast(tokenCommand); spOpndCommand->ToggleSign(); updatedToken = spOpndCommand->GetToken(m_standardCalculatorManager.DecimalSeparator()); } IsOperandUpdatedUsingViewModel = true; } if (!handleOperand) { (*m_commands)[token.second] = tokenCommand; (*m_tokens)[tokenPosition].first = updatedToken; DisplayExpressionToken ^ displayExpressionToken = ExpressionTokens->GetAt(tokenPosition); displayExpressionToken->Token = ref new Platform::String(updatedToken.c_str()); // Special casing if (command == Command::CommandSIGN && tokenCommand->GetCommandType() == CommandType::UnaryCommand) { IsEditingEnabled = false; Recalculate(); } } } void StandardCalculatorViewModel::Recalculate(bool fromHistory) { // Recalculate Command currentDegreeMode = m_standardCalculatorManager.GetCurrentDegreeMode(); shared_ptr>> savedCommands = make_shared>>(); vector currentCommands; for (const auto& command : *m_commands) { savedCommands->push_back(command); CommandType commandType = command->GetCommandType(); if (commandType == CommandType::UnaryCommand) { shared_ptr spCommand = dynamic_pointer_cast(command); const shared_ptr>& unaryCommands = spCommand->GetCommands(); for (int nUCode : *unaryCommands) { currentCommands.push_back(nUCode); } } if (commandType == CommandType::BinaryCommand) { shared_ptr spCommand = dynamic_pointer_cast(command); currentCommands.push_back(spCommand->GetCommand()); } if (commandType == CommandType::Parentheses) { shared_ptr spCommand = dynamic_pointer_cast(command); currentCommands.push_back(spCommand->GetCommand()); } if (commandType == CommandType::OperandCommand) { shared_ptr spCommand = dynamic_pointer_cast(command); const shared_ptr>& opndCommands = spCommand->GetCommands(); bool fNeedIDCSign = spCommand->IsNegative(); for (int nOCode : *opndCommands) { currentCommands.push_back(nOCode); if (fNeedIDCSign && nOCode != IDC_0) { currentCommands.push_back(static_cast(CalculationManager::Command::CommandSIGN)); fNeedIDCSign = false; } } } } shared_ptr>> savedTokens = make_shared>>(); for (const auto& currentToken : *m_tokens) { savedTokens->push_back(currentToken); } m_standardCalculatorManager.Reset(false); if (IsScientific) { m_standardCalculatorManager.SendCommand(Command::ModeScientific); } if (IsFToEChecked) { m_standardCalculatorManager.SendCommand(Command::CommandFE); } m_standardCalculatorManager.SendCommand(currentDegreeMode); for (int command : currentCommands) { m_standardCalculatorManager.SendCommand(static_cast(command)); } if (fromHistory) // This is for the cases where the expression is loaded from history { // To maintain F-E state of the engine, as the last operand hasn't reached engine by now m_standardCalculatorManager.SendCommand(Command::CommandFE); m_standardCalculatorManager.SendCommand(Command::CommandFE); } // After recalculation. If there is an error then // IsInError should be set synchronously. if (IsInError) { SetExpressionDisplay(savedTokens, savedCommands); } } CommandType StandardCalculatorViewModel::GetSelectedTokenType(_In_ unsigned int tokenPosition) { const pair& token = m_tokens->at(tokenPosition); unsigned int tokenCommandIndex = token.second; const shared_ptr& tokenCommand = m_commands->at(tokenCommandIndex); return tokenCommand->GetCommandType(); } bool StandardCalculatorViewModel::IsOpnd(Command command) { static constexpr Command opnd[] = { Command::Command0, Command::Command1, Command::Command2, Command::Command3, Command::Command4, Command::Command5, Command::Command6, Command::Command7, Command::Command8, Command::Command9, Command::CommandPNT }; return find(begin(opnd), end(opnd), command) != end(opnd); } bool StandardCalculatorViewModel::IsUnaryOp(Command command) { static constexpr Command unaryOp[] = { Command::CommandSQRT, Command::CommandFAC, Command::CommandSQR, Command::CommandLOG, Command::CommandPOW10, Command::CommandPOWE, Command::CommandLN, Command::CommandREC, Command::CommandSIGN, Command::CommandSINH, Command::CommandASINH, Command::CommandCOSH, Command::CommandACOSH, Command::CommandTANH, Command::CommandATANH, Command::CommandCUB }; if (find(begin(unaryOp), end(unaryOp), command) != end(unaryOp)) { return true; } if (IsTrigOp(command)) { return true; } return false; } bool StandardCalculatorViewModel::IsTrigOp(Command command) { static constexpr Command trigOp[] = { Command::CommandSIN, Command::CommandCOS, Command::CommandTAN, Command::CommandASIN, Command::CommandACOS, Command::CommandATAN }; return find(begin(trigOp), end(trigOp), command) != end(trigOp); } bool StandardCalculatorViewModel::IsBinOp(Command command) { static constexpr Command binOp[] = { Command::CommandADD, Command::CommandSUB, Command::CommandMUL, Command::CommandDIV, Command::CommandEXP, Command::CommandROOT, Command::CommandMOD, Command::CommandPWR }; return find(begin(binOp), end(binOp), command) != end(binOp); } bool StandardCalculatorViewModel::IsRecoverableCommand(Command command) { if (IsOpnd(command)) { return true; } // Programmer mode, bit flipping if (Command::CommandBINEDITSTART <= command && command <= Command::CommandBINEDITEND) { return true; } static constexpr Command recoverableCommands[] = { Command::CommandA, Command::CommandB, Command::CommandC, Command::CommandD, Command::CommandE, Command::CommandF }; return find(begin(recoverableCommands), end(recoverableCommands), command) != end(recoverableCommands); } size_t StandardCalculatorViewModel::LengthWithoutPadding(wstring str) { return str.length() - count(str.begin(), str.end(), L' '); } wstring StandardCalculatorViewModel::AddPadding(wstring binaryString) { if (LocalizationSettings::GetInstance().GetEnglishValueFromLocalizedDigits(StringReference(binaryString.c_str())) == L"0") { return binaryString; } size_t pad = 4 - LengthWithoutPadding(binaryString) % 4; if (pad == 4) { pad = 0; } return wstring(pad, L'0') + binaryString; } void StandardCalculatorViewModel::UpdateProgrammerPanelDisplay() { constexpr int32_t precision = 64; wstring hexDisplayString; wstring decimalDisplayString; wstring octalDisplayString; wstring binaryDisplayString; if (!IsInError) { // we want the precision to be set to maximum value so that the autoconversions result as desired if ((hexDisplayString = m_standardCalculatorManager.GetResultForRadix(16, precision, true)) == L"") { hexDisplayString = DisplayValue->Data(); decimalDisplayString = DisplayValue->Data(); octalDisplayString = DisplayValue->Data(); binaryDisplayString = DisplayValue->Data(); } else { decimalDisplayString = m_standardCalculatorManager.GetResultForRadix(10, precision, true); octalDisplayString = m_standardCalculatorManager.GetResultForRadix(8, precision, true); binaryDisplayString = m_standardCalculatorManager.GetResultForRadix(2, precision, true); } } const auto& localizer = LocalizationSettings::GetInstance(); binaryDisplayString = AddPadding(binaryDisplayString); localizer.LocalizeDisplayValue(&hexDisplayString); localizer.LocalizeDisplayValue(&decimalDisplayString); localizer.LocalizeDisplayValue(&octalDisplayString); localizer.LocalizeDisplayValue(&binaryDisplayString); HexDisplayValue = Utils::LRO + ref new Platform::String(hexDisplayString.c_str()) + Utils::PDF; DecimalDisplayValue = Utils::LRO + ref new Platform::String(decimalDisplayString.c_str()) + Utils::PDF; OctalDisplayValue = Utils::LRO + ref new Platform::String(octalDisplayString.c_str()) + Utils::PDF; BinaryDisplayValue = Utils::LRO + ref new Platform::String(binaryDisplayString.c_str()) + Utils::PDF; HexDisplayValue_AutomationName = GetLocalizedStringFormat(m_localizedHexaDecimalAutomationFormat, GetNarratorStringReadRawNumbers(HexDisplayValue)); DecDisplayValue_AutomationName = GetLocalizedStringFormat(m_localizedDecimalAutomationFormat, DecimalDisplayValue); OctDisplayValue_AutomationName = GetLocalizedStringFormat(m_localizedOctalAutomationFormat, GetNarratorStringReadRawNumbers(OctalDisplayValue)); BinDisplayValue_AutomationName = GetLocalizedStringFormat(m_localizedBinaryAutomationFormat, GetNarratorStringReadRawNumbers(BinaryDisplayValue)); auto binaryValueArray = ref new Vector(64, false); auto binaryValue = m_standardCalculatorManager.GetResultForRadix(2, precision, false); int i = 0; // To get bit 0, grab from opposite end of string. for (std::wstring::reverse_iterator it = binaryValue.rbegin(); it != binaryValue.rend(); ++it) { binaryValueArray->SetAt(i++, *it == L'1'); } BinaryDigits = binaryValueArray; } void StandardCalculatorViewModel::SwitchAngleType(NumbersAndOperatorsEnum num) { OnButtonPressed(num); } NumbersAndOperatorsEnum StandardCalculatorViewModel::ConvertIntegerToNumbersAndOperatorsEnum(unsigned int parameter) { NumbersAndOperatorsEnum angletype; switch (parameter) { case 321: angletype = NumbersAndOperatorsEnum::Degree; break; case 322: angletype = NumbersAndOperatorsEnum::Radians; break; case 323: angletype = NumbersAndOperatorsEnum::Grads; break; default: angletype = NumbersAndOperatorsEnum::Degree; }; return angletype; } void StandardCalculatorViewModel::UpdateOperand(int pos, String ^ text) { pair p = m_tokens->at(pos); String ^ englishString = LocalizationSettings::GetInstance().GetEnglishValueFromLocalizedDigits(text); p.first = englishString->Data(); int commandPos = p.second; const shared_ptr& exprCmd = m_commands->at(commandPos); auto operandCommand = std::dynamic_pointer_cast(exprCmd); if (operandCommand != nullptr) { shared_ptr> commands = make_shared>(); size_t length = p.first.length(); if (length > 0) { int num = 0; for (unsigned int i = 0; i < length; ++i) { if (p.first[i] == L'.') { num = static_cast(Command::CommandPNT); } else if (p.first[i] == L'e') { num = static_cast(Command::CommandEXP); } else if (p.first[i] == L'-') { num = static_cast(Command::CommandSIGN); if (i == 0) { shared_ptr spOpndCommand = dynamic_pointer_cast(exprCmd); if (!spOpndCommand->IsNegative()) { spOpndCommand->ToggleSign(); } continue; } } else { num = static_cast(p.first[i]) - ASCII_0; num += IDC_0; if (num == static_cast(Command::CommandMPLUS)) { continue; } } commands->push_back(num); } } else { commands->push_back(0); } operandCommand->SetCommands(commands); } } void StandardCalculatorViewModel::UpdateCommandsInRecordingMode() { shared_ptr> commands = make_shared>(); bool isDecimal = false; bool isNegative = false; bool isExpMode = false; bool ePlusMode = false; bool eMinusMode = false; for (const auto savedCommand : m_standardCalculatorManager.GetSavedCommands()) { const Command val = static_cast(savedCommand); if (val == Command::CommandSIGN) { isNegative = true; continue; } else if ((val >= Command::Command0 && val <= Command::Command9)) { } else if (val == Command::CommandPNT) { isDecimal = true; } else if (val == Command::CommandEXP) { isExpMode = true; } else if (isExpMode && !ePlusMode && (val == Command::CommandMPLUS)) { ePlusMode = true; continue; } else if (isExpMode && !eMinusMode && (val == Command::CommandMMINUS)) { eMinusMode = true; continue; } else { // Reset all vars isDecimal = false; isNegative = false; isExpMode = false; ePlusMode = false; eMinusMode = false; commands->clear(); continue; } commands->push_back(static_cast(val)); } if (!commands->empty()) { shared_ptr sp = make_shared(commands, isNegative, isDecimal, isExpMode); m_commands->push_back(sp); } Recalculate(); } void StandardCalculatorViewModel::OnMaxDigitsReached() { if (m_localizedMaxDigitsReachedAutomationFormat == nullptr) { m_localizedMaxDigitsReachedAutomationFormat = AppResourceProvider::GetInstance()->GetResourceString(CalculatorResourceKeys::MaxDigitsReachedFormat); } String ^ announcement = LocalizationStringUtil::GetLocalizedString(m_localizedMaxDigitsReachedAutomationFormat, m_CalculationResultAutomationName); Announcement = CalculatorAnnouncement::GetMaxDigitsReachedAnnouncement(announcement); } void StandardCalculatorViewModel::OnBinaryOperatorReceived() { Announcement = GetDisplayUpdatedNarratorAnnouncement(); } NarratorAnnouncement ^ StandardCalculatorViewModel::GetDisplayUpdatedNarratorAnnouncement() { String ^ announcement; if (m_feedbackForButtonPress == nullptr || m_feedbackForButtonPress->IsEmpty()) { announcement = m_CalculationResultAutomationName; } else { if (m_localizedButtonPressFeedbackAutomationFormat == nullptr) { m_localizedButtonPressFeedbackAutomationFormat = AppResourceProvider::GetInstance()->GetResourceString(CalculatorResourceKeys::ButtonPressFeedbackFormat); } announcement = LocalizationStringUtil::GetLocalizedString( m_localizedButtonPressFeedbackAutomationFormat, m_CalculationResultAutomationName, m_feedbackForButtonPress); } // Make sure we don't accidentally repeat an announcement. m_feedbackForButtonPress = nullptr; return CalculatorAnnouncement::GetDisplayUpdatedAnnouncement(announcement); } ViewMode StandardCalculatorViewModel::GetCalculatorMode() { if (IsStandard) { return ViewMode::Standard; } else if (IsScientific) { return ViewMode::Scientific; } return ViewMode::Programmer; } void StandardCalculatorViewModel::ValueBitLength::set(CalculatorApp::Common::BitLength value) { if (m_valueBitLength != value) { m_valueBitLength = value; RaisePropertyChanged(L"ValueBitLength"); switch (value) { case BitLength::BitLengthQWord: ButtonPressed->Execute(NumbersAndOperatorsEnum::Qword); break; case BitLength::BitLengthDWord: ButtonPressed->Execute(NumbersAndOperatorsEnum::Dword); break; case BitLength::BitLengthWord: ButtonPressed->Execute(NumbersAndOperatorsEnum::Word); break; case BitLength::BitLengthByte: ButtonPressed->Execute(NumbersAndOperatorsEnum::Byte); break; } // update memory list according to bit length SetMemorizedNumbersString(); } } void StandardCalculatorViewModel::SelectHistoryItem(HistoryItemViewModel ^ item) { SetHistoryExpressionDisplay(item->GetTokens(), item->GetCommands()); SetExpressionDisplay(item->GetTokens(), item->GetCommands()); SetPrimaryDisplay(item->Result, false); IsFToEEnabled = false; }