mirror of
https://github.com/Microsoft/calculator.git
synced 2025-07-16 02:02:51 -07:00
Convert CopyPasteManager to runtime class (#766)
* Convert CopyPasteManager to runtime class * merge AssertUtils and Helpers.h * update onpastemanager
This commit is contained in:
parent
a217e0534b
commit
b9b0e068cd
15 changed files with 604 additions and 360 deletions
|
@ -11,9 +11,11 @@ using namespace concurrency;
|
|||
using namespace CalculatorApp;
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace Platform;
|
||||
using namespace Platform::Collections;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::System;
|
||||
using namespace Windows::ApplicationModel::DataTransfer;
|
||||
using namespace Windows::Foundation::Collections;
|
||||
|
||||
StringReference PasteErrorString(L"NoOp");
|
||||
|
||||
|
@ -58,6 +60,7 @@ static const array<array<wregex, 5>, 4> programmerModePatterns = {
|
|||
wregex(c_wspcLParens + c_binProgrammerChars + L"[bB]?" + c_wspcRParens) } }
|
||||
};
|
||||
static const array<wregex, 1> unitConverterPatterns = { wregex(c_wspc + c_signedDecFloat + c_wspc) };
|
||||
|
||||
void CopyPasteManager::CopyToClipboard(String ^ stringToCopy)
|
||||
{
|
||||
// Copy the string to the clipboard
|
||||
|
@ -66,7 +69,7 @@ void CopyPasteManager::CopyToClipboard(String ^ stringToCopy)
|
|||
Clipboard::SetContent(dataPackage);
|
||||
}
|
||||
|
||||
IAsyncOperation<String ^> ^ CopyPasteManager::GetStringToPaste(ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, BitLength bitLengthType)
|
||||
IAsyncOperation<String ^> ^ CopyPasteManager::GetStringToPaste(ViewMode mode, CategoryGroupType modeType, NumberBase programmerNumberBase, BitLength bitLengthType)
|
||||
{
|
||||
// Retrieve the text in the clipboard
|
||||
auto dataPackageView = Clipboard::GetContent();
|
||||
|
@ -91,7 +94,7 @@ bool CopyPasteManager::HasStringToPaste()
|
|||
return Clipboard::GetContent()->Contains(StandardDataFormats::Text);
|
||||
}
|
||||
|
||||
String ^ CopyPasteManager::ValidatePasteExpression(String ^ pastedText, ViewMode mode, int programmerNumberBase, BitLength bitLengthType)
|
||||
String ^ CopyPasteManager::ValidatePasteExpression(String ^ pastedText, ViewMode mode, NumberBase programmerNumberBase, BitLength bitLengthType)
|
||||
{
|
||||
return ValidatePasteExpression(pastedText, mode, NavCategory::GetGroupType(mode), programmerNumberBase, bitLengthType);
|
||||
}
|
||||
|
@ -102,7 +105,7 @@ String
|
|||
String ^ pastedText,
|
||||
ViewMode mode,
|
||||
CategoryGroupType modeType,
|
||||
int programmerNumberBase,
|
||||
NumberBase programmerNumberBase,
|
||||
BitLength bitLengthType)
|
||||
{
|
||||
if (pastedText->Length() > MaxPasteableLength)
|
||||
|
@ -116,7 +119,7 @@ String
|
|||
String ^ englishString = LocalizationSettings::GetInstance().GetEnglishValueFromLocalizedDigits(pastedText);
|
||||
|
||||
// Removing the spaces, comma separator from the pasteExpression to allow pasting of expressions like 1 + 2+1,333
|
||||
wstring pasteExpression = RemoveUnwantedCharsFromWstring(englishString->Data());
|
||||
auto pasteExpression = wstring(RemoveUnwantedCharsFromString(englishString)->Data());
|
||||
|
||||
// If the last character is an = sign, remove it from the pasteExpression to allow evaluating the result on paste.
|
||||
if (!pasteExpression.empty() && pasteExpression.back() == L'=')
|
||||
|
@ -126,8 +129,8 @@ String
|
|||
|
||||
// Extract operands from the expression to make regex comparison easy and quick. For whole expression it was taking too much of time.
|
||||
// Operands vector will have the list of operands in the pasteExpression
|
||||
vector<wstring> operands = ExtractOperands(pasteExpression, mode);
|
||||
if (operands.empty())
|
||||
auto operands = ExtractOperands(StringReference(pasteExpression.c_str()), mode);
|
||||
if (operands->Size == 0)
|
||||
{
|
||||
// return NoOp to indicate don't paste anything.
|
||||
return PasteErrorString;
|
||||
|
@ -135,7 +138,8 @@ String
|
|||
|
||||
if (modeType == CategoryGroupType::Converter)
|
||||
{
|
||||
operands = { pasteExpression };
|
||||
operands->Clear();
|
||||
operands->Append(ref new String(pasteExpression.c_str()));
|
||||
}
|
||||
|
||||
// validate each operand with patterns for different modes
|
||||
|
@ -148,9 +152,9 @@ String
|
|||
return pastedText;
|
||||
}
|
||||
|
||||
vector<wstring> CopyPasteManager::ExtractOperands(const wstring& pasteExpression, ViewMode mode)
|
||||
IVector<Platform::String ^> ^ CopyPasteManager::ExtractOperands(Platform::String ^ pasteExpression, ViewMode mode)
|
||||
{
|
||||
vector<wstring> operands{};
|
||||
auto operands = ref new Vector<Platform::String ^>();
|
||||
size_t lastIndex = 0;
|
||||
bool haveOperator = false;
|
||||
bool startExpCounting = false;
|
||||
|
@ -176,19 +180,20 @@ vector<wstring> CopyPasteManager::ExtractOperands(const wstring& pasteExpression
|
|||
|
||||
// This will have the exponent length
|
||||
size_t expLength = 0;
|
||||
for (size_t i = 0; i < pasteExpression.length(); i++)
|
||||
int i = -1;
|
||||
for (auto currentChar : pasteExpression)
|
||||
{
|
||||
wchar_t currentChar = pasteExpression.at(i);
|
||||
++i;
|
||||
// if the current character is not a valid one don't process it
|
||||
if (validCharacterSet.find(currentChar) == wstring_view::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operands.size() >= MaxOperandCount)
|
||||
if (operands->Size >= MaxOperandCount)
|
||||
{
|
||||
TraceLogger::GetInstance()->LogError(mode, L"CopyPasteManager::ExtractOperands", L"OperandCountGreaterThanMaxCount");
|
||||
operands.clear();
|
||||
operands->Clear();
|
||||
return operands;
|
||||
}
|
||||
|
||||
|
@ -202,7 +207,7 @@ vector<wstring> CopyPasteManager::ExtractOperands(const wstring& pasteExpression
|
|||
if (expLength > MaxExponentLength)
|
||||
{
|
||||
TraceLogger::GetInstance()->LogError(mode, L"CopyPasteManager::ExtractOperands", L"ExponentLengthGreaterThanMaxLength");
|
||||
operands.clear();
|
||||
operands->Clear();
|
||||
return operands;
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +227,7 @@ vector<wstring> CopyPasteManager::ExtractOperands(const wstring& pasteExpression
|
|||
{
|
||||
// don't break the expression into operands if the encountered character corresponds to sign command(+-)
|
||||
if (isPreviousOpenParen || startOfExpression || isPreviousOperator
|
||||
|| ((mode != ViewMode::Programmer) && !((i != 0) && (pasteExpression.at(i - 1) != L'e'))))
|
||||
|| ((mode != ViewMode::Programmer) && !((i != 0) && pasteExpression->Data()[i - 1] != L'e')))
|
||||
{
|
||||
isPreviousOperator = false;
|
||||
continue;
|
||||
|
@ -233,7 +238,7 @@ vector<wstring> CopyPasteManager::ExtractOperands(const wstring& pasteExpression
|
|||
expLength = 0;
|
||||
haveOperator = true;
|
||||
isPreviousOperator = true;
|
||||
operands.push_back(pasteExpression.substr(lastIndex, i - lastIndex));
|
||||
operands->Append(ref new String(wstring(pasteExpression->Data()).substr(lastIndex, i - lastIndex).c_str()));
|
||||
lastIndex = i + 1;
|
||||
}
|
||||
else
|
||||
|
@ -241,31 +246,31 @@ vector<wstring> CopyPasteManager::ExtractOperands(const wstring& pasteExpression
|
|||
isPreviousOperator = false;
|
||||
}
|
||||
|
||||
isPreviousOpenParen = (pasteExpression.at(i) == L'(');
|
||||
isPreviousOpenParen = (currentChar == L'(');
|
||||
startOfExpression = false;
|
||||
}
|
||||
|
||||
if (!haveOperator)
|
||||
{
|
||||
operands.clear();
|
||||
operands.push_back(pasteExpression);
|
||||
operands->Clear();
|
||||
operands->Append(pasteExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
operands.push_back(pasteExpression.substr(lastIndex, pasteExpression.length() - 1));
|
||||
operands->Append(ref new String(wstring(pasteExpression->Data()).substr(lastIndex, pasteExpression->Length() - 1).c_str()));
|
||||
}
|
||||
|
||||
return operands;
|
||||
}
|
||||
|
||||
bool CopyPasteManager::ExpressionRegExMatch(
|
||||
vector<wstring> operands,
|
||||
IVector<String ^> ^ operands,
|
||||
ViewMode mode,
|
||||
CategoryGroupType modeType,
|
||||
int programmerNumberBase,
|
||||
NumberBase programmerNumberBase,
|
||||
BitLength bitLengthType)
|
||||
{
|
||||
if (operands.empty())
|
||||
if (operands->Size == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -282,14 +287,16 @@ bool CopyPasteManager::ExpressionRegExMatch(
|
|||
}
|
||||
else if (mode == ViewMode::Programmer)
|
||||
{
|
||||
patterns.assign(programmerModePatterns[programmerNumberBase - HexBase].begin(), programmerModePatterns[programmerNumberBase - HexBase].end());
|
||||
patterns.assign(
|
||||
programmerModePatterns[(int)programmerNumberBase - (int)NumberBase::HexBase].begin(),
|
||||
programmerModePatterns[(int)programmerNumberBase - (int)NumberBase::HexBase].end());
|
||||
}
|
||||
else if (modeType == CategoryGroupType::Converter)
|
||||
{
|
||||
patterns.assign(unitConverterPatterns.begin(), unitConverterPatterns.end());
|
||||
}
|
||||
|
||||
const auto [maxOperandLength, maxOperandValue] = GetMaxOperandLengthAndValue(mode, modeType, programmerNumberBase, bitLengthType);
|
||||
auto maxOperandLengthAndValue = GetMaxOperandLengthAndValue(mode, modeType, programmerNumberBase, bitLengthType);
|
||||
bool expMatched = true;
|
||||
|
||||
for (const auto& operand : operands)
|
||||
|
@ -298,34 +305,34 @@ bool CopyPasteManager::ExpressionRegExMatch(
|
|||
bool operandMatched = false;
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
operandMatched = operandMatched || regex_match(operand, pattern);
|
||||
operandMatched = operandMatched || regex_match(operand->Data(), pattern);
|
||||
}
|
||||
|
||||
if (operandMatched)
|
||||
{
|
||||
// Remove characters that are valid in the expression but we do not want to include in length calculations
|
||||
// or which will break conversion from string-to-ULL.
|
||||
const wstring operandValue = SanitizeOperand(operand);
|
||||
auto operandValue = SanitizeOperand(operand);
|
||||
|
||||
// If an operand exceeds the maximum length allowed, break and return.
|
||||
if (OperandLength(operandValue, mode, modeType, programmerNumberBase) > maxOperandLength)
|
||||
if (OperandLength(operandValue, mode, modeType, programmerNumberBase) > maxOperandLengthAndValue.maxLength)
|
||||
{
|
||||
expMatched = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// If maxOperandValue is set and the operandValue exceeds it, break and return.
|
||||
if (maxOperandValue != 0)
|
||||
if (maxOperandLengthAndValue.maxValue != 0)
|
||||
{
|
||||
unsigned long long int operandAsULL = 0;
|
||||
if (!TryOperandToULL(operandValue, programmerNumberBase, operandAsULL))
|
||||
auto operandAsULL = TryOperandToULL(operandValue, programmerNumberBase);
|
||||
if (operandAsULL == nullptr)
|
||||
{
|
||||
// Operand was empty, received invalid_argument, or received out_of_range. Input is invalid.
|
||||
expMatched = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (operandAsULL > maxOperandValue)
|
||||
if (operandAsULL->Value > maxOperandLengthAndValue.maxValue)
|
||||
{
|
||||
expMatched = false;
|
||||
break;
|
||||
|
@ -339,19 +346,23 @@ bool CopyPasteManager::ExpressionRegExMatch(
|
|||
return expMatched;
|
||||
}
|
||||
|
||||
pair<size_t, uint64_t>
|
||||
CopyPasteManager::GetMaxOperandLengthAndValue(ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, BitLength bitLengthType)
|
||||
CopyPasteMaxOperandLengthAndValue
|
||||
CopyPasteManager::GetMaxOperandLengthAndValue(ViewMode mode, CategoryGroupType modeType, NumberBase programmerNumberBase, BitLength bitLengthType)
|
||||
{
|
||||
constexpr size_t defaultMaxOperandLength = 0;
|
||||
constexpr uint64_t defaultMaxValue = 0;
|
||||
|
||||
CopyPasteMaxOperandLengthAndValue res;
|
||||
if (mode == ViewMode::Standard)
|
||||
{
|
||||
return make_pair(MaxStandardOperandLength, defaultMaxValue);
|
||||
res.maxLength = MaxStandardOperandLength;
|
||||
res.maxValue = defaultMaxValue;
|
||||
return res;
|
||||
}
|
||||
else if (mode == ViewMode::Scientific)
|
||||
{
|
||||
return make_pair(MaxScientificOperandLength, defaultMaxValue);
|
||||
res.maxLength = MaxScientificOperandLength;
|
||||
res.maxValue = defaultMaxValue;
|
||||
return res;
|
||||
}
|
||||
else if (mode == ViewMode::Programmer)
|
||||
{
|
||||
|
@ -375,65 +386,69 @@ CopyPasteManager::GetMaxOperandLengthAndValue(ViewMode mode, CategoryGroupType m
|
|||
double bitsPerDigit = 0;
|
||||
switch (programmerNumberBase)
|
||||
{
|
||||
case BinBase:
|
||||
case NumberBase::BinBase:
|
||||
bitsPerDigit = log2(2);
|
||||
break;
|
||||
case OctBase:
|
||||
case NumberBase::OctBase:
|
||||
bitsPerDigit = log2(8);
|
||||
break;
|
||||
case DecBase:
|
||||
case NumberBase::DecBase:
|
||||
bitsPerDigit = log2(10);
|
||||
break;
|
||||
case HexBase:
|
||||
case NumberBase::HexBase:
|
||||
bitsPerDigit = log2(16);
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned int signBit = (programmerNumberBase == DecBase) ? 1 : 0;
|
||||
unsigned int signBit = (programmerNumberBase == NumberBase::DecBase) ? 1 : 0;
|
||||
|
||||
const auto maxLength = static_cast<size_t>(ceil((bitLength - signBit) / bitsPerDigit));
|
||||
const auto maxLength = static_cast<unsigned int>(ceil((bitLength - signBit) / bitsPerDigit));
|
||||
const uint64_t maxValue = UINT64_MAX >> (MaxProgrammerBitLength - (bitLength - signBit));
|
||||
|
||||
return make_pair(maxLength, maxValue);
|
||||
res.maxLength = maxLength;
|
||||
res.maxValue = maxValue;
|
||||
return res;
|
||||
}
|
||||
else if (modeType == CategoryGroupType::Converter)
|
||||
{
|
||||
return make_pair(MaxConverterInputLength, defaultMaxValue);
|
||||
res.maxLength = MaxConverterInputLength;
|
||||
res.maxValue = defaultMaxValue;
|
||||
return res;
|
||||
}
|
||||
|
||||
return make_pair(defaultMaxOperandLength, defaultMaxValue);
|
||||
res.maxLength = defaultMaxOperandLength;
|
||||
res.maxValue = defaultMaxValue;
|
||||
return res;
|
||||
}
|
||||
|
||||
wstring CopyPasteManager::SanitizeOperand(const wstring& operand)
|
||||
Platform::String ^ CopyPasteManager::SanitizeOperand(Platform::String ^ operand)
|
||||
{
|
||||
wchar_t unWantedChars[] = { L'\'', L'_', L'`', L'(', L')', L'-', L'+' };
|
||||
|
||||
return Utils::RemoveUnwantedCharsFromWstring(operand, unWantedChars, static_cast<int>(size(unWantedChars)));
|
||||
return ref new String(Utils::RemoveUnwantedCharsFromString(operand->Data(), unWantedChars, static_cast<int>(size(unWantedChars))).c_str());
|
||||
}
|
||||
|
||||
bool CopyPasteManager::TryOperandToULL(const wstring& operand, int numberBase, unsigned long long int& result)
|
||||
IBox<unsigned long long int> ^ CopyPasteManager::TryOperandToULL(String ^ operand, NumberBase numberBase)
|
||||
{
|
||||
result = 0;
|
||||
|
||||
if (operand.length() == 0 || operand.front() == L'-')
|
||||
if (operand->Length() == 0 || operand->Data()[0] == L'-')
|
||||
{
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int intBase;
|
||||
switch (numberBase)
|
||||
{
|
||||
case HexBase:
|
||||
case NumberBase::HexBase:
|
||||
intBase = 16;
|
||||
break;
|
||||
case OctBase:
|
||||
case NumberBase::OctBase:
|
||||
intBase = 8;
|
||||
break;
|
||||
case BinBase:
|
||||
case NumberBase::BinBase:
|
||||
intBase = 2;
|
||||
break;
|
||||
default:
|
||||
case DecBase:
|
||||
case NumberBase::DecBase:
|
||||
intBase = 10;
|
||||
break;
|
||||
}
|
||||
|
@ -441,8 +456,7 @@ bool CopyPasteManager::TryOperandToULL(const wstring& operand, int numberBase, u
|
|||
wstring::size_type size = 0;
|
||||
try
|
||||
{
|
||||
result = stoull(operand, &size, intBase);
|
||||
return true;
|
||||
return stoull(operand->Data(), &size, intBase);
|
||||
}
|
||||
catch (const invalid_argument&)
|
||||
{
|
||||
|
@ -453,14 +467,14 @@ bool CopyPasteManager::TryOperandToULL(const wstring& operand, int numberBase, u
|
|||
// Do nothing
|
||||
}
|
||||
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t CopyPasteManager::OperandLength(const wstring& operand, ViewMode mode, CategoryGroupType modeType, int programmerNumberBase)
|
||||
ULONG32 CopyPasteManager::OperandLength(Platform::String ^ operand, ViewMode mode, CategoryGroupType modeType, NumberBase programmerNumberBase)
|
||||
{
|
||||
if (modeType == CategoryGroupType::Converter)
|
||||
{
|
||||
return operand.length();
|
||||
return operand->Length();
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
|
@ -477,45 +491,46 @@ size_t CopyPasteManager::OperandLength(const wstring& operand, ViewMode mode, Ca
|
|||
}
|
||||
}
|
||||
|
||||
size_t CopyPasteManager::StandardScientificOperandLength(const wstring& operand)
|
||||
ULONG32 CopyPasteManager::StandardScientificOperandLength(Platform::String ^ operand)
|
||||
{
|
||||
const bool hasDecimal = operand.find('.') != wstring::npos;
|
||||
auto operandWstring = wstring(operand->Data());
|
||||
const bool hasDecimal = operandWstring.find('.') != wstring::npos;
|
||||
|
||||
if (hasDecimal)
|
||||
{
|
||||
if (operand.length() >= 2)
|
||||
if (operandWstring.length() >= 2)
|
||||
{
|
||||
if ((operand[0] == L'0') && (operand[1] == L'.'))
|
||||
if ((operandWstring[0] == L'0') && (operandWstring[1] == L'.'))
|
||||
{
|
||||
return operand.length() - 2;
|
||||
return static_cast<ULONG32>(operandWstring.length() - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
return operand.length() - 1;
|
||||
return static_cast<ULONG32>(operandWstring.length() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return operand.length();
|
||||
return static_cast<ULONG32>(operandWstring.length());
|
||||
}
|
||||
|
||||
size_t CopyPasteManager::ProgrammerOperandLength(const wstring& operand, int numberBase)
|
||||
ULONG32 CopyPasteManager::ProgrammerOperandLength(Platform::String ^ operand, NumberBase numberBase)
|
||||
{
|
||||
vector<wstring> prefixes{};
|
||||
vector<wstring> suffixes{};
|
||||
switch (numberBase)
|
||||
{
|
||||
case BinBase:
|
||||
case NumberBase::BinBase:
|
||||
prefixes = { L"0B", L"0Y" };
|
||||
suffixes = { L"B" };
|
||||
break;
|
||||
case DecBase:
|
||||
case NumberBase::DecBase:
|
||||
prefixes = { L"-", L"0N" };
|
||||
break;
|
||||
case OctBase:
|
||||
case NumberBase::OctBase:
|
||||
prefixes = { L"0T", L"0O" };
|
||||
break;
|
||||
case HexBase:
|
||||
case NumberBase::HexBase:
|
||||
prefixes = { L"0X" };
|
||||
suffixes = { L"H" };
|
||||
break;
|
||||
|
@ -528,10 +543,10 @@ size_t CopyPasteManager::ProgrammerOperandLength(const wstring& operand, int num
|
|||
const array<wstring, 5> uintSuffixes = { L"ULL", L"UL", L"LL", L"U", L"L" };
|
||||
suffixes.insert(suffixes.end(), uintSuffixes.begin(), uintSuffixes.end());
|
||||
|
||||
wstring operandUpper = operand;
|
||||
wstring operandUpper = wstring(operand->Data());
|
||||
transform(operandUpper.begin(), operandUpper.end(), operandUpper.begin(), towupper);
|
||||
|
||||
size_t len = operand.length();
|
||||
size_t len = operand->Length();
|
||||
|
||||
// Detect if there is a suffix and subtract its length
|
||||
// Check suffixes first to allow e.g. "0b" to result in length 1 (value 0), rather than length 0 (no value).
|
||||
|
@ -564,7 +579,7 @@ size_t CopyPasteManager::ProgrammerOperandLength(const wstring& operand, int num
|
|||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
return static_cast<ULONG32>(len);
|
||||
}
|
||||
|
||||
// return wstring after removing characters like space, comma, double quotes, and monetary prefix currency symbols supported by the Windows keyboard:
|
||||
|
@ -579,10 +594,10 @@ size_t CopyPasteManager::ProgrammerOperandLength(const wstring& operand, int num
|
|||
// Indian rupee(₹) - 8377
|
||||
// pound(£) - 163
|
||||
// euro(€) - 8364
|
||||
wstring CopyPasteManager::RemoveUnwantedCharsFromWstring(const wstring& input)
|
||||
Platform::String ^ CopyPasteManager::RemoveUnwantedCharsFromString(Platform::String ^ input)
|
||||
{
|
||||
wchar_t unWantedChars[] = { L' ', L',', L'"', 165, 164, 8373, 36, 8353, 8361, 8362, 8358, 8377, 163, 8364, 8234, 8235, 8236, 8237 };
|
||||
return Utils::RemoveUnwantedCharsFromWstring(input, unWantedChars, 18);
|
||||
return ref new String(Utils::RemoveUnwantedCharsFromString(input->Data(), unWantedChars, 18).c_str());
|
||||
}
|
||||
|
||||
bool CopyPasteManager::IsErrorMessage(Platform::String ^ message)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue