calculator/src/Calculator/Views/GraphingCalculator/EquationInputArea.xaml.cpp
Stephanie Anderl 18a1f82035
Graphing Calculator Diagnostics Instrumentation (#1041)
* Add telemetry for keyboard button usage in graphing mode

* Added the diagnostics for EquationAdded and FunctionAnalysis

* Added remaining diagnostics events for graphing calculator

* Fix proj files to include the IsStoreBuild condition. Move the Delayer class to the Calculator/Utils folder

* Ensure the variable textbox has focus before logging diagnostics

* Move maxVariableCount check into the tracelogger class

* Created enums and updated the slider value changed method to remove the variable from the map after the log method is called

* Re-enable hidden lines when the expression is updated

* Fixed extra line in grapher.h and removed the conditional logging for variable count

* Updated logging per PR feedback

* Updated variable logging and fixed issues in the IsEquationLineDisabled binding the EditTextBox control.

* Update per PR feedback

* Added TraceLogging project to contain shared logging logic.

* Updated TraceLogging project and updated tracelogger classes to use the TraceLogging project methods

* Updated VariableLogging to log variable name. And updated per PR comments

* Updated Variables logging to log count changed instead of variable added and fixed issue with variableSliders not being initialized

* Remove outdated tracelogging call caused by rebase

* Updated Delayer class to DispatcherTimerDelayer and fixed some small formatting issues

* Fixed missing Dalyer class name updates

* Removed extra line in traceloger.h
2020-03-12 14:05:47 -07:00

485 lines
15 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
#include "EquationInputArea.xaml.h"
#include "Utils/VisualTree.h"
using namespace CalculatorApp;
using namespace CalculatorApp::Common;
using namespace GraphControl;
using namespace CalculatorApp::ViewModel;
using namespace CalculatorApp::Controls;
using namespace Platform;
using namespace Platform::Collections;
using namespace std;
using namespace Windows::Foundation;
using namespace Windows::System;
using namespace Windows::UI;
using namespace Windows::UI::ViewManagement;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Controls::Primitives;
using namespace Windows::UI::Xaml::Input;
using namespace GraphControl;
using namespace Calculator::Utils;
namespace
{
inline constexpr auto maxEquationSize = 14;
inline constexpr std::array<int, 14> colorAssignmentMapping = { 0, 3, 7, 10, 1, 4, 8, 11, 2, 5, 9, 12, 6, 13 };
StringReference EquationsPropertyName(L"Equations");
}
EquationInputArea::EquationInputArea()
: m_lastLineColorIndex{ -1 }
, m_AvailableColors{ ref new Vector<SolidColorBrush ^>() }
, m_accessibilitySettings{ ref new AccessibilitySettings() }
, m_equationToFocus{ nullptr }
{
m_accessibilitySettings->HighContrastChanged +=
ref new TypedEventHandler<AccessibilitySettings ^, Object ^>(this, &EquationInputArea::OnHighContrastChanged);
ReloadAvailableColors(m_accessibilitySettings->HighContrast);
InitializeComponent();
}
void EquationInputArea::OnPropertyChanged(String ^ propertyName)
{
if (propertyName == EquationsPropertyName)
{
OnEquationsPropertyChanged();
}
}
void EquationInputArea::OnEquationsPropertyChanged()
{
if (Equations != nullptr && Equations->Size == 0)
{
AddNewEquation();
}
}
void EquationInputArea::AddNewEquation()
{
if (Equations->Size > 0)
{
Equations->GetAt(Equations->Size - 1)->IsLastItemInList = false;
}
// Cap equations at 14
if (Equations->Size >= maxEquationSize)
{
return;
}
m_lastLineColorIndex = (m_lastLineColorIndex + 1) % AvailableColors->Size;
int colorIndex;
if (m_accessibilitySettings->HighContrast)
{
colorIndex = m_lastLineColorIndex;
}
else
{
colorIndex = colorAssignmentMapping[m_lastLineColorIndex];
}
auto eq = ref new EquationViewModel(ref new Equation(), ++m_lastFunctionLabelIndex, AvailableColors->GetAt(colorIndex)->Color);
eq->IsLastItemInList = true;
m_equationToFocus = eq;
Equations->Append(eq);
}
void EquationInputArea::EquationTextBox_GotFocus(Object ^ sender, RoutedEventArgs ^ e)
{
KeyboardShortcutManager::HonorShortcuts(false);
auto eq = GetViewModelFromEquationTextBox(sender);
if (eq != nullptr)
{
eq->GraphEquation->IsSelected = true;
}
}
void EquationInputArea::EquationTextBox_LostFocus(Object ^ sender, RoutedEventArgs ^ e)
{
KeyboardShortcutManager::HonorShortcuts(true);
auto eq = GetViewModelFromEquationTextBox(sender);
if (eq != nullptr)
{
eq->GraphEquation->IsSelected = false;
}
}
void EquationInputArea::EquationTextBox_Submitted(Object ^ sender, MathRichEditBoxSubmission ^ submission)
{
auto eq = GetViewModelFromEquationTextBox(sender);
if (eq == nullptr)
{
return;
}
if (submission->Source == EquationSubmissionSource::ENTER_KEY
|| (submission->Source == EquationSubmissionSource::FOCUS_LOST && submission->HasTextChanged && eq->Expression != nullptr
&& eq->Expression->Length() > 0))
{
eq->IsLineEnabled = true;
unsigned int index = 0;
if (Equations->IndexOf(eq, &index))
{
if (index == Equations->Size - 1)
{
// If it's the last equation of the list
AddNewEquation();
}
else
{
if (submission->Source == EquationSubmissionSource::ENTER_KEY)
{
auto nextEquation = Equations->GetAt(index + 1);
FocusEquationTextBox(nextEquation);
}
}
}
}
}
void EquationInputArea::FocusEquationTextBox(EquationViewModel ^ equation)
{
unsigned int index;
if (!Equations->IndexOf(equation, &index) || index < 0)
{
return;
}
auto container = EquationInputList->ContainerFromIndex(index);
if (container == nullptr)
{
return;
}
auto equationTextBox = dynamic_cast<EquationTextBox ^>(container);
if (equationTextBox != nullptr)
{
equationTextBox->FocusTextBox();
}
else
{
auto equationInput = VisualTree::FindDescendantByName(container, "EquationInputButton");
if (equationInput == nullptr)
{
return;
}
equationTextBox = dynamic_cast<EquationTextBox ^>(equationInput);
if (equationTextBox != nullptr)
{
equationTextBox->FocusTextBox();
}
}
}
void EquationInputArea::EquationTextBox_RemoveButtonClicked(Object ^ sender, RoutedEventArgs ^ e)
{
auto eq = GetViewModelFromEquationTextBox(sender);
unsigned int index;
if (Equations->IndexOf(eq, &index))
{
// Prevent removing the last equation
if (index == Equations->Size - 1)
{
return;
}
Equations->RemoveAt(index);
int lastIndex = Equations->Size - 1;
if (Equations->Size <= 1)
{
m_lastFunctionLabelIndex = 1;
}
else
{
m_lastFunctionLabelIndex = Equations->GetAt(lastIndex - 1)->FunctionLabelIndex + 1;
}
Equations->GetAt(lastIndex)->FunctionLabelIndex = m_lastFunctionLabelIndex;
}
}
void EquationInputArea::EquationTextBox_KeyGraphFeaturesButtonClicked(Object ^ sender, RoutedEventArgs ^ e)
{
KeyGraphFeaturesRequested(this, GetViewModelFromEquationTextBox(sender));
}
void EquationInputArea::EquationTextBox_EquationButtonClicked(Object ^ sender, RoutedEventArgs ^ e)
{
auto eq = GetViewModelFromEquationTextBox(sender);
eq->IsLineEnabled = !eq->IsLineEnabled;
TraceLogger::GetInstance()->LogShowHideButtonClicked(eq->IsLineEnabled ? false : true);
}
void EquationInputArea::EquationTextBox_Loaded(Object ^ sender, RoutedEventArgs ^ e)
{
auto tb = static_cast<EquationTextBox ^>(sender);
auto colorChooser = static_cast<EquationStylePanelControl ^>(tb->ColorChooserFlyout->Content);
colorChooser->AvailableColors = AvailableColors;
if (m_equationToFocus != nullptr && tb->DataContext == m_equationToFocus)
{
auto copyEquationToFocus = m_equationToFocus;
m_equationToFocus = nullptr;
tb->FocusTextBox();
unsigned int index;
if (Equations->IndexOf(copyEquationToFocus, &index))
{
auto container = static_cast<UIElement^>(EquationInputList->ContainerFromIndex(index));
if (container != nullptr)
{
container->StartBringIntoView();
}
}
}
}
void EquationInputArea::EquationTextBox_DataContextChanged(Windows::UI::Xaml::FrameworkElement ^ sender, Windows::UI::Xaml::DataContextChangedEventArgs ^ args)
{
auto tb = static_cast<EquationTextBox ^>(sender);
if (!tb->IsLoaded)
{
return;
}
FocusEquationIfNecessary(tb);
}
void EquationInputArea::FocusEquationIfNecessary(CalculatorApp::Controls::EquationTextBox ^ textBox)
{
if (m_equationToFocus != nullptr && textBox->DataContext == m_equationToFocus)
{
m_equationToFocus = nullptr;
textBox->FocusTextBox();
unsigned int index;
if (Equations->IndexOf(m_equationToFocus, &index))
{
auto container = static_cast<UIElement ^>(EquationInputList->ContainerFromIndex(index));
if (container != nullptr)
{
container->StartBringIntoView();
}
}
}
}
void EquationInputArea::OnHighContrastChanged(AccessibilitySettings ^ sender, Object ^ args)
{
ReloadAvailableColors(sender->HighContrast);
}
void EquationInputArea::ReloadAvailableColors(bool isHighContrast)
{
m_AvailableColors->Clear();
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush1")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush2")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush3")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush4")));
// If this is not high contrast, we have all 16 colors, otherwise we will restrict this to a subset of high contrast colors
if (!isHighContrast)
{
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush5")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush6")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush7")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush8")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush9")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush10")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush11")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush12")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush13")));
m_AvailableColors->Append(safe_cast<SolidColorBrush ^>(Application::Current->Resources->Lookup(L"EquationBrush14")));
}
// If there are no equations to reload, quit early
if (Equations == nullptr || Equations->Size == 0)
{
return;
}
// Reassign colors for each equation
m_lastLineColorIndex = -1;
for (auto equationViewModel : Equations)
{
m_lastLineColorIndex = (m_lastLineColorIndex + 1) % AvailableColors->Size;
equationViewModel->LineColor = AvailableColors->GetAt(m_lastLineColorIndex)->Color;
}
}
void EquationInputArea::TextBoxGotFocus(TextBox ^ sender, RoutedEventArgs ^ e)
{
sender->SelectAll();
}
void EquationInputArea::SubmitTextbox(TextBox ^ sender)
{
auto variableViewModel = static_cast<VariableViewModel ^>(sender->DataContext);
double val;
if (sender->Name == "ValueTextBox")
{
val = validateDouble(sender->Text, variableViewModel->Value);
variableViewModel->Value = val;
TraceLogger::GetInstance()->LogVariableChanged(L"ValueTextBox", variableViewModel->Name);
}
else if (sender->Name == "MinTextBox")
{
val = validateDouble(sender->Text, variableViewModel->Min);
variableViewModel->Min = val;
TraceLogger::GetInstance()->LogVariableSettingsChanged(L"MinTextBox");
}
else if (sender->Name == "MaxTextBox")
{
val = validateDouble(sender->Text, variableViewModel->Max);
variableViewModel->Max = val;
TraceLogger::GetInstance()->LogVariableSettingsChanged(L"MaxTextBox");
}
else if (sender->Name == "StepTextBox")
{
val = validateDouble(sender->Text, variableViewModel->Step);
variableViewModel->Step = val;
TraceLogger::GetInstance()->LogVariableSettingsChanged(L"StepTextBox");
}
else
{
return;
}
wostringstream oss;
oss << std::noshowpoint << val;
sender->Text = ref new String(oss.str().c_str());
}
void EquationInputArea::TextBoxLosingFocus(TextBox ^ sender, LosingFocusEventArgs ^)
{
SubmitTextbox(sender);
}
void EquationInputArea::TextBoxKeyDown(TextBox ^ sender, KeyRoutedEventArgs ^ e)
{
if (e->Key == ::VirtualKey::Enter)
{
SubmitTextbox(sender);
}
}
double EquationInputArea::validateDouble(String ^ value, double defaultValue)
{
try
{
return stod(value->Data());
}
catch (...)
{
return defaultValue;
}
}
::Visibility EquationInputArea::ManageEditVariablesButtonVisibility(unsigned int numberOfVariables)
{
return numberOfVariables == 0 ? ::Visibility::Collapsed : ::Visibility::Visible;
}
bool EquationInputArea::ManageEditVariablesButtonLoaded(unsigned int numberOfVariables)
{
return numberOfVariables != 0;
}
String ^ EquationInputArea::GetChevronIcon(bool isCollapsed)
{
return isCollapsed ? L"\uE70E" : L"\uE70D";
}
void EquationInputArea::VariableAreaTapped(Object ^ sender, TappedRoutedEventArgs ^ e)
{
auto selectedVariableViewModel = static_cast<VariableViewModel ^>(static_cast<Grid ^>(sender)->DataContext);
selectedVariableViewModel->SliderSettingsVisible = !selectedVariableViewModel->SliderSettingsVisible;
// Collapse all other slider settings that are open
for (auto variableViewModel : Variables)
{
if (variableViewModel != selectedVariableViewModel)
{
variableViewModel->SliderSettingsVisible = false;
}
}
}
void EquationInputArea::EquationTextBox_EquationFormatRequested(Object ^ sender, MathRichEditBoxFormatRequest ^ e)
{
EquationFormatRequested(sender, e);
}
void EquationInputArea::Slider_ValueChanged(Object ^ sender, RangeBaseValueChangedEventArgs ^ e)
{
if (variableSliders == nullptr)
{
variableSliders = ref new Map<String ^, DispatcherTimerDelayer ^>();
}
auto slider = static_cast<Slider ^>(sender);
// The slider value updates when the user uses the TextBox to change the variable value.
// Check the focus state so that we don't trigger the event when the user used the textbox to change the variable value.
if (slider->FocusState == Windows::UI::Xaml::FocusState::Unfocused)
{
return;
}
auto variableVM = static_cast<VariableViewModel ^>(slider->DataContext);
if (variableVM == nullptr)
{
return;
}
auto name = variableVM->Name;
if (!variableSliders->HasKey(name))
{
TimeSpan timeSpan;
timeSpan.Duration = 10000000; // The duration is 1 second. TimeSpan durations are expressed in 100 nanosecond units.
DispatcherTimerDelayer ^ delayer = ref new DispatcherTimerDelayer(timeSpan);
delayer->Action += ref new EventHandler<Platform::Object ^>([this, name](Platform::Object ^ sender, Platform::Object ^ e) {
TraceLogger::GetInstance()->LogVariableChanged("Slider", name);
variableSliders->Remove(name);
});
delayer->Start();
variableSliders->Insert(name, delayer);
}
else
{
auto delayer = variableSliders->Lookup(name);
delayer->ResetAndStart();
}
}
EquationViewModel ^ EquationInputArea::GetViewModelFromEquationTextBox(Object ^ sender)
{
auto tb = static_cast<EquationTextBox ^>(sender);
if (tb == nullptr)
{
return nullptr;
}
auto eq = static_cast<EquationViewModel ^>(tb->DataContext);
return eq;
}