diff --git a/src/CalcViewModel/Common/Utils.cpp b/src/CalcViewModel/Common/Utils.cpp index 846d630c..65f5c96d 100644 --- a/src/CalcViewModel/Common/Utils.cpp +++ b/src/CalcViewModel/Common/Utils.cpp @@ -264,6 +264,71 @@ void Utils::TrimBack(wstring& value) }).base(), value.end()); } +String^ Utils::EscapeHtmlSpecialCharacters(String^ originalString, shared_ptr> specialCharacters) +{ + // Construct a default special characters if not provided. + if (specialCharacters == nullptr) + { + specialCharacters = make_shared>(); + specialCharacters->push_back(L'&'); + specialCharacters->push_back(L'\"'); + specialCharacters->push_back(L'\''); + specialCharacters->push_back(L'<'); + specialCharacters->push_back(L'>'); + } + + bool replaceCharacters = false; + const wchar_t* pCh; + String^ replacementString = nullptr; + + // First step is scanning the string for special characters. + // If there isn't any special character, we simply return the original string + for (pCh = originalString->Data(); *pCh; pCh++) + { + if (std::find(specialCharacters->begin(), specialCharacters->end(), *pCh) != specialCharacters->end()) + { + replaceCharacters = true; + break; + } + } + + if (replaceCharacters) + { + // If we indeed find a special character, we step back one character (the special + // character), and we create a new string where we replace those characters one by one + pCh--; + wstringstream buffer; + buffer << wstring(originalString->Data(), pCh); + + for (; *pCh; pCh++) + { + switch (*pCh) + { + case L'&': + buffer << L"&"; + break; + case L'\"': + buffer << L"""; + break; + case L'\'': + buffer << L"'"; + break; + case L'<': + buffer << L"<"; + break; + case L'>': + buffer << L">"; + break; + default: + buffer << *pCh; + } + } + replacementString = ref new String(buffer.str().c_str()); + } + + return replaceCharacters ? replacementString : originalString; +} + bool operator==(const Color& color1, const Color& color2) { return equal_to()(color1, color2); diff --git a/src/CalcViewModel/Common/Utils.h b/src/CalcViewModel/Common/Utils.h index df2788c1..48261aef 100644 --- a/src/CalcViewModel/Common/Utils.h +++ b/src/CalcViewModel/Common/Utils.h @@ -430,6 +430,7 @@ namespace Utils void TrimFront(std::wstring& value); void TrimBack(std::wstring& value); + Platform::String ^ EscapeHtmlSpecialCharacters(Platform::String ^ originalString, std::shared_ptr> specialCharacters = nullptr); } diff --git a/src/Calculator/Resources/en-US/Resources.resw b/src/Calculator/Resources/en-US/Resources.resw index bc19b61b..e0ab5dee 100644 --- a/src/Calculator/Resources/en-US/Resources.resw +++ b/src/Calculator/Resources/en-US/Resources.resw @@ -3507,9 +3507,17 @@ Look what I graphed. Sent as part of the shared content. The title for the share. - - Empty graph equation - When sharing and one of the equations has no content this will be shown in the graph key for that equation. + + Equations: + Header that appears over the equations section when sharing + + + Variables: + Header that appears over the variables section when sharing + + + Image of a graph with equations + Alt text for the graph image when output via Share Variables diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml index 73c99993..cb16f4d3 100644 --- a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml +++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml @@ -50,10 +50,10 @@ + VariablesUpdated="GraphingControl_VariablesUpdated"> diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp index aef10540..da4377b2 100644 --- a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp +++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp @@ -2,17 +2,21 @@ // Licensed under the MIT License. #include "pch.h" -#include "CalcViewModel/Common/TraceLogger.h" #include "GraphingCalculator.xaml.h" +#include "CalcViewModel/Common/TraceLogger.h" +#include "CalcViewModel/Common/LocalizationSettings.h" #include "CalcViewModel/Common/KeyboardShortcutManager.h" #include "Controls/CalculationResult.h" -#include "Calculator\Controls\EquationTextBox.h" -#include "Calculator\Views\GraphingCalculator\EquationInputArea.xaml.h" +#include "CalcManager/NumberFormattingUtils.h" +#include "Calculator/Controls/EquationTextBox.h" +#include "Calculator/Views/GraphingCalculator/EquationInputArea.xaml.h" +#include "CalcViewModel/Common/Utils.h" using namespace CalculatorApp; using namespace CalculatorApp::Common; using namespace CalculatorApp::Controls; using namespace CalculatorApp::ViewModel; +using namespace CalcManager::NumberFormattingUtils; using namespace concurrency; using namespace GraphControl; using namespace Platform; @@ -21,6 +25,7 @@ using namespace std; using namespace std::chrono; using namespace Utils; using namespace Windows::ApplicationModel::DataTransfer; +using namespace Windows::ApplicationModel::Resources; using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; using namespace Windows::Storage::Streams; @@ -109,79 +114,94 @@ void CalculatorApp::GraphingCalculator::OnShareClick(Platform::Object ^ sender, // data to be shared. We will request the current graph image from the grapher as a stream that will pass to the share request. void GraphingCalculator::OnDataRequested(DataTransferManager ^ sender, DataRequestedEventArgs ^ args) { - auto resourceLoader = Windows::ApplicationModel::Resources::ResourceLoader::GetForCurrentView(); + auto resourceLoader = ResourceLoader::GetForCurrentView(); + try { - // Get our title from the localized resources - auto EmptyEquationString = resourceLoader->GetString(L"EmptyEquationString"); + std::wstringstream rawHtml; + std::wstringstream equationHtml; - std::wstring rawHtml = L"

"; + rawHtml << L"

" << resourceLoader->GetString(L"GraphImageAltText")->Data() << "

"; auto equations = ViewModel->Equations; - rawHtml += L"

"; - rawHtml += L""; - rawHtml += L""; - for (unsigned i = 0; i < equations->Size; i++) + bool hasEquations = false; + + if (equations->Size > 0) { - auto expression = equations->GetAt(i)->Expression->Data(); - auto color = equations->GetAt(i)->LineColor->Color; + equationHtml << L""; + equationHtml << resourceLoader->GetString(L"EquationsShareHeader")->Data(); + equationHtml << L""; + equationHtml << L"
"; - if (equations->GetAt(i)->Expression->Length() == 0) + for (auto equation : equations) { - expression = EmptyEquationString->Data(); + auto expression = equation->Expression; + if (expression->IsEmpty()) + { + continue; + } + + auto color = equation->LineColor->Color; + hasEquations = true; + + expression = GraphingControl->ConvertToLinear(expression); + + std::wstringstream equationColorHtml; + equationColorHtml << L"color:rgb(" << color.R.ToString()->Data() << L"," << color.G.ToString()->Data() << L"," << color.B.ToString()->Data() + << L");"; + + equationHtml << L""; } - - rawHtml += L""; - - rawHtml += L""; - rawHtml += L""; - - rawHtml += L""; + equationHtml << L"
"; + equationHtml << EscapeHtmlSpecialCharacters(expression)->Data(); + equationHtml << L"
Data(); - rawHtml += L","; - rawHtml += color.G.ToString()->Data(); - rawHtml += L","; - rawHtml += color.B.ToString()->Data(); - rawHtml += L"); \">"; - rawHtml += L""; - rawHtml += expression; - rawHtml += L"
"; + } + + if (hasEquations) + { + rawHtml << equationHtml.str(); } - rawHtml += L"

"; auto variables = ViewModel->Variables; - rawHtml += L"

"; - rawHtml += L""; - rawHtml += L""; - for (unsigned i = 0; i < variables->Size; i++) + + if (variables->Size > 0) { - auto name = variables->GetAt(i)->Name; - auto value = variables->GetAt(i)->Value; + auto localizedSeperator = LocalizationSettings::GetInstance().GetListSeparator() + L" "; - if (name->Length() >= 0) + rawHtml << L""; + rawHtml << resourceLoader->GetString(L"VariablesShareHeader")->Data(); + rawHtml << L"
"; + + for (unsigned i = 0; i < variables->Size; i++) { - rawHtml += L"
"; + auto name = variables->GetAt(i)->Name; + auto value = variables->GetAt(i)->Value; - rawHtml += L""; - rawHtml += L""; + rawHtml << name->Data(); + rawHtml << L"="; + auto formattedValue = to_wstring(value); + TrimTrailingZeros(formattedValue); + rawHtml << formattedValue; - rawHtml += L""; + if (variables->Size - 1 != i) + { + rawHtml << localizedSeperator; + } } + + rawHtml << L""; } - rawHtml += L"
"; - rawHtml += name->Data(); - rawHtml += L""; - rawHtml += std::to_wstring(value); - rawHtml += L"

"; + + rawHtml << L"

"; // Shortcut to the request data auto requestData = args->Request->Data; DataPackage ^ dataPackage = ref new DataPackage(); - auto html = HtmlFormatHelper::CreateHtmlFormat(ref new String(rawHtml.c_str())); + auto html = HtmlFormatHelper::CreateHtmlFormat(ref new String(rawHtml.str().c_str())); - auto titleString = resourceLoader->GetString(L"ShareActionTitle"); - requestData->Properties->Title = titleString; + requestData->Properties->Title = resourceLoader->GetString(L"ShareActionTitle"); requestData->SetHtmlFormat(html); @@ -191,26 +211,21 @@ void GraphingCalculator::OnDataRequested(DataTransferManager ^ sender, DataReque // Set the thumbnail image (in case the share target can't handle HTML) requestData->Properties->Thumbnail = bitmapStream; - - // And the bitmap (in case the share target can't handle HTML) - requestData->SetBitmap(bitmapStream); } catch (Exception ^ ex) { TraceLogger::GetInstance().LogPlatformException(ViewMode::Graphing, __FUNCTIONW__, ex); // Something went wrong, notify the user. - auto errorTitleString = resourceLoader->GetString(L"ShareActionErrorMessage"); - auto errorOkString = resourceLoader->GetString(L"ShareActionErrorOk"); - auto errDialog = ref new Windows::UI::Xaml::Controls::ContentDialog(); - errDialog->Content = errorTitleString; - errDialog->CloseButtonText = errorOkString; + auto errDialog = ref new ContentDialog(); + errDialog->Content = resourceLoader->GetString(L"ShareActionErrorMessage"); + errDialog->CloseButtonText = resourceLoader->GetString(L"ShareActionErrorOk"); errDialog->ShowAsync(); } } -void GraphingCalculator::GraphVariablesUpdated(Object ^, Object ^) +void GraphingCalculator::GraphingControl_VariablesUpdated(Object ^, Object ^) { m_viewModel->UpdateVariables(GraphingControl->Variables); } @@ -287,14 +302,14 @@ void GraphingCalculator::OnZoomResetCommand(Object ^ /* parameter */) GraphingControl->ResetGrid(); } -void GraphingCalculator::OnActiveTracingClick(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e) +void GraphingCalculator::OnActiveTracingClick(Object ^ sender, RoutedEventArgs ^ e) { // The focus change to this button will have turned off the tracing if it was on ActiveTracingOn = !ActiveTracingOn; GraphingControl->ActiveTracing = ActiveTracingOn; } -void CalculatorApp::GraphingCalculator::OnGraphLostFocus(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e) +void GraphingCalculator::GraphingControl_LostFocus(Object ^ sender, RoutedEventArgs ^ e) { // If the graph is losing focus while we are in active tracing we need to turn it off so we don't try to eat keys in other controls. if (GraphingControl->ActiveTracing) @@ -304,9 +319,9 @@ void CalculatorApp::GraphingCalculator::OnGraphLostFocus(Platform::Object ^ send } } -void CalculatorApp::GraphingCalculator::OnLoosingFocus(Windows::UI::Xaml::UIElement ^ sender, Windows::UI::Xaml::Input::LosingFocusEventArgs ^ args) +void GraphingCalculator::GraphingControl_LosingFocus(UIElement ^ sender, LosingFocusEventArgs ^ args) { - FrameworkElement ^ newFocusElement = (FrameworkElement ^) args->NewFocusedElement; + auto newFocusElement = dynamic_cast(args->NewFocusedElement); if (newFocusElement == nullptr || newFocusElement->Name == nullptr) { // Because clicking on the swap chain panel will try to move focus to a control that can't actually take focus diff --git a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h index b30008a4..45fdf2af 100644 --- a/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h +++ b/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.h @@ -33,7 +33,6 @@ namespace CalculatorApp private: void GraphingCalculator_DataContextChanged(Windows::UI::Xaml::FrameworkElement^ sender, Windows::UI::Xaml::DataContextChangedEventArgs^ args); - void GraphVariablesUpdated(Platform::Object^ sender, Object^ args); void OnVariableChanged(Platform::Object^ sender, CalculatorApp::ViewModel::VariableChangedEventArgs args); void TextBoxLosingFocus(Windows::UI::Xaml::Controls::TextBox^ textbox, Windows::UI::Xaml::Input::LosingFocusEventArgs^ args); @@ -60,8 +59,9 @@ namespace CalculatorApp void TextBoxGotFocus(Windows::UI::Xaml::Controls::TextBox^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); void OnActiveTracingClick(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e); - void OnGraphLostFocus(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e); - void OnLoosingFocus(Windows::UI::Xaml::UIElement ^ sender, Windows::UI::Xaml::Input::LosingFocusEventArgs ^ args); + void GraphingControl_LostFocus(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e); + void GraphingControl_LosingFocus(Windows::UI::Xaml::UIElement ^ sender, Windows::UI::Xaml::Input::LosingFocusEventArgs ^ args); + void GraphingControl_VariablesUpdated(Platform::Object ^ sender, Object ^ args); void OnEquationKeyGraphFeaturesVisibilityChanged(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e); void OnKeyGraphFeaturesClosed(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e); bool ActiveTracingOn; diff --git a/src/GraphControl/Control/Grapher.cpp b/src/GraphControl/Control/Grapher.cpp index 74d959f2..e30108bc 100644 --- a/src/GraphControl/Control/Grapher.cpp +++ b/src/GraphControl/Control/Grapher.cpp @@ -38,7 +38,7 @@ namespace constexpr auto s_X = L"x"; constexpr auto s_Y = L"y"; - + constexpr auto s_defaultFormatType = FormatType::MathML; constexpr auto s_getGraphOpeningTags = L"show2d"; constexpr auto s_getGraphClosingTags = L""; @@ -65,7 +65,7 @@ namespace GraphControl , m_graph{ m_solver->CreateGrapher() } , m_Moving{ false } { - m_solver->ParsingOptions().SetFormatType(FormatType::MathML); + m_solver->ParsingOptions().SetFormatType(s_defaultFormatType); m_solver->FormatOptions().SetFormatType(FormatType::MathML); m_solver->FormatOptions().SetMathMLPrefix(wstring(L"mml")); @@ -1009,3 +1009,15 @@ void Grapher::HandleTracingMovementTick(Object ^ sender, Object ^ e) ActiveTraceCursorPosition = curPos; } } + +String ^ Grapher::ConvertToLinear(String ^ mmlString) +{ + m_solver->FormatOptions().SetFormatType(FormatType::LinearInput); + + auto expression = m_solver->ParseInput(mmlString->Data()); + auto linearExpression = m_solver->Serialize(expression.get()); + + m_solver->FormatOptions().SetFormatType(s_defaultFormatType); + + return ref new String(linearExpression.c_str()); +} diff --git a/src/GraphControl/Control/Grapher.h b/src/GraphControl/Control/Grapher.h index 9bf8ce4d..5c8d7977 100644 --- a/src/GraphControl/Control/Grapher.h +++ b/src/GraphControl/Control/Grapher.h @@ -190,6 +190,7 @@ public event Windows::Foundation::EventHandler ^> ^ VariablesUpdated; void SetVariable(Platform::String ^ variableName, double newValue); + Platform::String ^ ConvertToLinear(Platform::String ^ mmlString); protected: #pragma region Control Overrides