calculator/src/Calculator/Views/GraphingCalculator/GraphingCalculator.xaml.cpp
Rudy Huyn 534139d67d GraphControl: refactoring and optimizations (#831)
* GraphControl cleaning

* replace textbox value after submission

* rebase

* rebase

* rebase

* Add filters

* rebase!

* rebase
2019-12-13 16:33:08 -08:00

497 lines
18 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
#include "GraphingCalculator.xaml.h"
#include "CalcViewModel/Common/AppResourceProvider.h"
#include "CalcViewModel/Common/TraceLogger.h"
#include "CalcViewModel/Common/LocalizationSettings.h"
#include "CalcViewModel/Common/LocalizationStringUtil.h"
#include "CalcViewModel/Common/KeyboardShortcutManager.h"
#include "CalcViewModel/Common/Automation/NarratorAnnouncement.h"
#include "CalcViewModel/Common/Automation/NarratorNotifier.h"
#include "Controls/CalculationResult.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::Common::Automation;
using namespace CalculatorApp::Controls;
using namespace CalculatorApp::ViewModel;
using namespace CalcManager::NumberFormattingUtils;
using namespace concurrency;
using namespace GraphControl;
using namespace Platform;
using namespace Platform::Collections;
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;
using namespace Windows::System;
using namespace Windows::UI::Core;
using namespace Windows::UI::Input;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Automation;
using namespace Windows::UI::Xaml::Automation::Peers;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Media::Imaging;
using namespace Windows::UI::Popups;
constexpr auto sc_ViewModelPropertyName = L"ViewModel";
DEPENDENCY_PROPERTY_INITIALIZATION(GraphingCalculator, IsSmallState);
GraphingCalculator::GraphingCalculator()
{
InitializeComponent();
DataTransferManager ^ dataTransferManager = DataTransferManager::GetForCurrentView();
// Register the current control as a share source.
m_dataRequestedToken = dataTransferManager->DataRequested +=
ref new TypedEventHandler<DataTransferManager ^, DataRequestedEventArgs ^>(this, &GraphingCalculator::OnDataRequested);
// Request notifications when we should be showing the trace values
GraphingControl->TracingChangedEvent += ref new TracingChangedEventHandler(this, &GraphingCalculator::OnShowTracePopupChanged);
// And when the actual trace value changes
GraphingControl->TracingValueChangedEvent += ref new TracingValueChangedEventHandler(this, &GraphingCalculator::OnTracePointChanged);
}
void GraphingCalculator::OnShowTracePopupChanged(bool newValue)
{
if ((TraceValuePopup->Visibility == ::Visibility::Visible) != newValue)
{
TraceValuePopup->Visibility = newValue ? ::Visibility::Visible : ::Visibility::Collapsed;
}
}
void GraphingCalculator::GraphingCalculator_DataContextChanged(FrameworkElement ^ sender, DataContextChangedEventArgs ^ args)
{
if (ViewModel != nullptr)
{
if (m_vectorChangedToken.Value != 0)
{
ViewModel->Equations->VectorChanged -= m_vectorChangedToken;
m_vectorChangedToken.Value = 0;
}
if (m_variableUpdatedToken.Value != 0)
{
ViewModel->VariableUpdated -= m_variableUpdatedToken;
m_variableUpdatedToken.Value = 0;
}
}
ViewModel = dynamic_cast<GraphingCalculatorViewModel ^>(args->NewValue);
m_vectorChangedToken = ViewModel->Equations->VectorChanged +=
ref new VectorChangedEventHandler<EquationViewModel ^>(this, &GraphingCalculator::OnEquationsVectorChanged);
m_variableUpdatedToken = ViewModel->VariableUpdated +=
ref new EventHandler<VariableChangedEventArgs>(this, &CalculatorApp::GraphingCalculator::OnVariableChanged);
}
void GraphingCalculator::OnEquationsVectorChanged(IObservableVector<EquationViewModel ^> ^ sender, IVectorChangedEventArgs ^ event)
{
// If an item is already added to the graph, changing it should automatically trigger a graph update
if (event->CollectionChange == ::CollectionChange::ItemChanged)
{
return;
}
// Do not plot the graph if we are removing an empty equation, just remove it
if (event->CollectionChange == ::CollectionChange::ItemRemoved)
{
auto itemToRemove = GraphingControl->Equations->GetAt(event->Index);
if (itemToRemove->Expression->IsEmpty())
{
GraphingControl->Equations->RemoveAt(event->Index);
return;
}
}
// Do not plot the graph if we are adding an empty equation, just add it
if (event->CollectionChange == ::CollectionChange::ItemInserted)
{
auto itemToAdd = sender->GetAt(event->Index);
if (itemToAdd->Expression->IsEmpty())
{
GraphingControl->Equations->Append(itemToAdd->GraphEquation);
return;
}
}
// We are either adding or removing a valid equation, or resetting the collection. We will need to plot the graph
GraphingControl->Equations->Clear();
for (auto equationViewModel : ViewModel->Equations)
{
GraphingControl->Equations->Append(equationViewModel->GraphEquation);
}
GraphingControl->PlotGraph();
}
void GraphingCalculator::OnTracePointChanged(Windows::Foundation::Point newPoint)
{
wstringstream traceValueString;
// TODO: The below precision should ideally be dynamic based on the current scale of the graph.
traceValueString << "x=" << fixed << setprecision(1) << newPoint.X << ", y=" << fixed << setprecision(1) << newPoint.Y;
TraceValue->Text = ref new String(traceValueString.str().c_str());
auto peer = FrameworkElementAutomationPeer::FromElement(TraceValue);
if (peer != nullptr)
{
peer->RaiseAutomationEvent(AutomationEvents::LiveRegionChanged);
}
PositionGraphPopup();
}
GraphingCalculatorViewModel ^ GraphingCalculator::ViewModel::get()
{
return m_viewModel;
}
void GraphingCalculator::ViewModel::set(GraphingCalculatorViewModel ^ vm)
{
if (m_viewModel != vm)
{
m_viewModel = vm;
RaisePropertyChanged(StringReference(sc_ViewModelPropertyName));
}
}
void CalculatorApp::GraphingCalculator::OnShareClick(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e)
{
// Ask the OS to start a share action.
DataTransferManager::ShowShareUI();
}
// When share is invoked (by the user or programmatically) the event handler we registered will be called to populate the data package with the
// 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 = ResourceLoader::GetForCurrentView();
try
{
std::wstringstream rawHtml;
std::wstringstream equationHtml;
rawHtml << L"<p><img src='graph.png' width='600' alt='" << resourceLoader->GetString(L"GraphImageAltText")->Data() << "'></p>";
auto equations = ViewModel->Equations;
bool hasEquations = false;
if (equations->Size > 0)
{
equationHtml << L"<span style=\"color: rgb(68, 114, 196); font-style: bold; font-size : 13pt;\">";
equationHtml << resourceLoader->GetString(L"EquationsShareHeader")->Data();
equationHtml << L"</span>";
equationHtml << L"<table cellpadding=\"0\">";
for (auto equation : equations)
{
auto expression = equation->Expression;
if (expression->IsEmpty())
{
continue;
}
auto color = equation->LineColor;
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"<tr><td><span style=\"line-height: 0; font-size: 24pt;" << equationColorHtml.str()
<< L"\">&#x25A0;</span></td><td><div style=\"margin: 4pt 0pt 0pt 0pt; \">";
equationHtml << EscapeHtmlSpecialCharacters(expression)->Data();
equationHtml << L"</div></td>";
}
equationHtml << L"</table>";
}
if (hasEquations)
{
rawHtml << equationHtml.str();
}
auto variables = ViewModel->Variables;
if (variables->Size > 0)
{
auto localizedSeperator = LocalizationSettings::GetInstance().GetListSeparator() + L" ";
rawHtml << L"<span style=\"color: rgb(68, 114, 196); font-style: bold; font-size: 13pt;\">";
rawHtml << resourceLoader->GetString(L"VariablesShareHeader")->Data();
rawHtml << L"</span><br><span>";
for (unsigned i = 0; i < variables->Size; i++)
{
auto name = variables->GetAt(i)->Name;
auto value = variables->GetAt(i)->Value;
rawHtml << name->Data();
rawHtml << L"=";
auto formattedValue = to_wstring(value);
TrimTrailingZeros(formattedValue);
rawHtml << formattedValue;
if (variables->Size - 1 != i)
{
rawHtml << localizedSeperator;
}
}
rawHtml << L"</span>";
}
rawHtml << L"<br><br>";
// Shortcut to the request data
auto requestData = args->Request->Data;
DataPackage ^ dataPackage = ref new DataPackage();
auto html = HtmlFormatHelper::CreateHtmlFormat(ref new String(rawHtml.str().c_str()));
requestData->Properties->Title = resourceLoader->GetString(L"ShareActionTitle");
requestData->SetHtmlFormat(html);
auto bitmapStream = GraphingControl->GetGraphBitmapStream();
requestData->ResourceMap->Insert(ref new String(L"graph.png"), bitmapStream);
// Set the thumbnail image (in case the share target can't handle HTML)
requestData->Properties->Thumbnail = bitmapStream;
}
catch (Exception ^ ex)
{
TraceLogger::GetInstance()->LogPlatformException(ViewMode::Graphing, __FUNCTIONW__, ex);
// Something went wrong, notify the user.
auto errDialog = ref new ContentDialog();
errDialog->Content = resourceLoader->GetString(L"ShareActionErrorMessage");
errDialog->CloseButtonText = resourceLoader->GetString(L"ShareActionErrorOk");
errDialog->ShowAsync();
}
}
void GraphingCalculator::GraphingControl_VariablesUpdated(Object ^, Object ^)
{
m_viewModel->UpdateVariables(GraphingControl->Variables);
}
void GraphingCalculator::OnVariableChanged(Platform::Object ^ sender, VariableChangedEventArgs args)
{
GraphingControl->SetVariable(args.variableName, args.newValue);
}
void GraphingCalculator::OnZoomInCommand(Object ^ /* parameter */)
{
GraphingControl->ZoomFromCenter(zoomInScale);
}
void GraphingCalculator::OnZoomOutCommand(Object ^ /* parameter */)
{
GraphingControl->ZoomFromCenter(zoomOutScale);
}
void GraphingCalculator::OnZoomResetCommand(Object ^ /* parameter */)
{
GraphingControl->ResetGrid();
}
String ^ GraphingCalculator::GetTracingLegend(Platform::IBox<bool> ^ isTracing)
{
auto resProvider = AppResourceProvider::GetInstance();
return isTracing != nullptr && isTracing->Value ? resProvider->GetResourceString(L"disableTracingButtonToolTip")
: resProvider->GetResourceString(L"enableTracingButtonToolTip");
}
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)
{
if (ActiveTracing->Equals(FocusManager::GetFocusedElement()) && ActiveTracing->IsPressed)
{
m_ActiveTracingPointerCaptureLost = ActiveTracing->PointerCaptureLost +=
ref new Windows::UI::Xaml::Input::PointerEventHandler(this, &CalculatorApp::GraphingCalculator::ActiveTracing_PointerCaptureLost);
}
else
{
GraphingControl->ActiveTracing = false;
OnShowTracePopupChanged(false);
}
}
}
void CalculatorApp::GraphingCalculator::ActiveTracing_PointerCaptureLost(Platform::Object ^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs ^ e)
{
if (m_ActiveTracingPointerCaptureLost.Value != 0)
{
ActiveTracing->PointerCaptureLost -= m_ActiveTracingPointerCaptureLost;
m_ActiveTracingPointerCaptureLost.Value = 0;
}
if (GraphingControl->ActiveTracing)
{
GraphingControl->ActiveTracing = false;
OnShowTracePopupChanged(false);
}
}
void GraphingCalculator::GraphingControl_LosingFocus(UIElement ^ sender, LosingFocusEventArgs ^ args)
{
auto newFocusElement = dynamic_cast<FrameworkElement ^>(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
// we will get a null destination. So we are going to try and cancel that request.
// If the destination is not in our application we will also get a null destination but the cancel will fail so it doesn't hurt to try.
args->TryCancel();
}
}
void GraphingCalculator::OnEquationKeyGraphFeaturesRequested(Object ^ sender, EquationViewModel ^ equationViewModel)
{
ViewModel->SetSelectedEquation(equationViewModel);
if (equationViewModel != nullptr)
{
auto keyGraphFeatureInfo = GraphingControl->AnalyzeEquation(equationViewModel->GraphEquation);
equationViewModel->PopulateKeyGraphFeatures(keyGraphFeatureInfo);
IsKeyGraphFeaturesVisible = true;
}
}
void GraphingCalculator::OnKeyGraphFeaturesClosed(Object ^ sender, RoutedEventArgs ^ e)
{
IsKeyGraphFeaturesVisible = false;
}
Visibility GraphingCalculator::ShouldDisplayPanel(bool isSmallState, bool isEquationModeActivated, bool isGraphPanel)
{
return (!isSmallState || isEquationModeActivated ^ isGraphPanel) ? ::Visibility::Visible : ::Visibility::Collapsed;
}
Platform::String ^ GraphingCalculator::GetInfoForSwitchModeToggleButton(bool isChecked)
{
if (isChecked)
{
return AppResourceProvider::GetInstance()->GetResourceString(L"GraphSwitchToGraphMode");
}
else
{
return AppResourceProvider::GetInstance()->GetResourceString(L"GraphSwitchToEquationMode");
}
}
void GraphingCalculator::SwitchModeToggleButton_Checked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e)
{
auto narratorNotifier = ref new NarratorNotifier();
String ^ announcementText;
if (SwitchModeToggleButton->IsChecked->Value)
{
announcementText = AppResourceProvider::GetInstance()->GetResourceString(L"GraphSwitchedToEquationModeAnnouncement");
}
else
{
announcementText = AppResourceProvider::GetInstance()->GetResourceString(L"GraphSwitchedToGraphModeAnnouncement");
}
auto announcement = CalculatorAnnouncement::GetGraphModeChangedAnnouncement(announcementText);
narratorNotifier->Announce(announcement);
}
void GraphingCalculator::PositionGraphPopup()
{
if (GraphingControl->TraceLocation.X + 15 + TraceValuePopup->ActualWidth >= GraphingControl->ActualWidth)
{
TraceValuePopupTransform->X = (int)GraphingControl->TraceLocation.X - 15 - TraceValuePopup->ActualWidth;
}
else
{
TraceValuePopupTransform->X = (int)GraphingControl->TraceLocation.X + 15;
}
if (GraphingControl->TraceLocation.Y >= 30)
{
TraceValuePopupTransform->Y = (int)GraphingControl->TraceLocation.Y - 30;
}
else
{
TraceValuePopupTransform->Y = (int)GraphingControl->TraceLocation.Y;
}
}
void GraphingCalculator::TraceValuePopup_SizeChanged(Object ^ sender, SizeChangedEventArgs ^ e)
{
PositionGraphPopup();
}
::Visibility GraphingCalculator::ManageEditVariablesButtonVisibility(unsigned int numberOfVariables)
{
return numberOfVariables == 0 ? ::Visibility::Collapsed : ::Visibility::Visible;
}
void CalculatorApp::GraphingCalculator::ActiveTracing_Checked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e)
{
GraphingControl->Focus(::FocusState::Programmatic);
m_activeTracingKeyUpToken = Window::Current->CoreWindow->KeyUp +=
ref new Windows::Foundation::TypedEventHandler<Windows::UI::Core::CoreWindow ^, Windows::UI::Core::KeyEventArgs ^>(
this, &CalculatorApp::GraphingCalculator::ActiveTracing_KeyUp);
KeyboardShortcutManager::IgnoreEscape(false);
}
void CalculatorApp::GraphingCalculator::ActiveTracing_Unchecked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e)
{
if (m_ActiveTracingPointerCaptureLost.Value != 0)
{
ActiveTracing->PointerCaptureLost -= m_ActiveTracingPointerCaptureLost;
m_ActiveTracingPointerCaptureLost.Value = 0;
}
if (m_activeTracingKeyUpToken.Value != 0)
{
Window::Current->CoreWindow->KeyUp -= m_activeTracingKeyUpToken;
m_activeTracingKeyUpToken.Value = 0;
}
KeyboardShortcutManager::HonorEscape();
}
void CalculatorApp::GraphingCalculator::ActiveTracing_KeyUp(Windows::UI::Core::CoreWindow ^ sender, Windows::UI::Core::KeyEventArgs ^ args)
{
if (args->VirtualKey == VirtualKey::Escape)
{
GraphingControl->ActiveTracing = false;
args->Handled = true;
}
}