384 lines
13 KiB
C++
384 lines
13 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
// UnitConverter.xaml.cpp
|
|
// Implementation of the UnitConverter class
|
|
|
|
#include "pch.h"
|
|
#include "UnitConverter.xaml.h"
|
|
#include "CalcViewModel/Common/TraceLogger.h"
|
|
#include "CalcViewModel/UnitConverterViewModel.h"
|
|
#include "Controls/CalculationResult.h"
|
|
#include "Controls/CalculatorButton.h"
|
|
#include "CalcViewModel/Common/CopyPasteManager.h"
|
|
#include "CalcViewModel/Common/LocalizationService.h"
|
|
#include "CalcViewModel/Common/LocalizationSettings.h"
|
|
#include "Common/KeyboardShortcutManager.h"
|
|
|
|
using namespace std;
|
|
using namespace CalculatorApp;
|
|
using namespace CalculatorApp::Common;
|
|
using namespace CalculatorApp::Controls;
|
|
using namespace concurrency;
|
|
using namespace Windows::System;
|
|
using namespace Platform;
|
|
using namespace ViewModel;
|
|
using namespace Windows::Foundation;
|
|
using namespace Windows::Foundation::Collections;
|
|
using namespace Windows::UI::Core;
|
|
using namespace Windows::UI::Text;
|
|
using namespace Windows::UI::Xaml;
|
|
using namespace Windows::UI::Xaml::Automation;
|
|
using namespace Windows::UI::Xaml::Automation::Peers;
|
|
using namespace Windows::UI::Xaml::Controls;
|
|
using namespace Windows::UI::Xaml::Controls::Primitives;
|
|
using namespace Windows::UI::Xaml::Data;
|
|
using namespace Windows::UI::Xaml::Documents;
|
|
using namespace Windows::UI::Xaml::Input;
|
|
using namespace Windows::UI::Xaml::Interop;
|
|
using namespace Windows::UI::Xaml::Media;
|
|
using namespace Windows::UI::Xaml::Navigation;
|
|
using namespace Windows::UI::ViewManagement;
|
|
|
|
// Calculate number of 100-nanosecond intervals in 500 milliseconds.
|
|
// There are 10,000 intervals in 1 ms.
|
|
static const long long DURATION_500_MS = 10000 * 500;
|
|
|
|
UnitConverter::UnitConverter()
|
|
: m_meteredConnectionOverride(false)
|
|
{
|
|
m_layoutDirection = LocalizationService::GetInstance()->GetFlowDirection();
|
|
m_FlowDirectionHorizontalAlignment = m_layoutDirection == ::FlowDirection::RightToLeft ? ::HorizontalAlignment::Right : ::HorizontalAlignment::Left;
|
|
|
|
InitializeComponent();
|
|
|
|
// adding ESC key shortcut binding to clear button
|
|
ClearEntryButtonPos0->SetValue(Common::KeyboardShortcutManager::VirtualKeyProperty, Common::MyVirtualKey::Escape);
|
|
|
|
// Is currency symbol preference set to right side
|
|
bool preferRight = LocalizationSettings::GetInstance().GetCurrencySymbolPrecedence() == 0;
|
|
VisualStateManager::GoToState(this, preferRight ? "CurrencySymbolRightState" : "CurrencySymbolLeftState", false);
|
|
|
|
auto resLoader = AppResourceProvider::GetInstance();
|
|
m_chargesMayApplyText = resLoader->GetResourceString(L"DataChargesMayApply");
|
|
m_failedToRefreshText = resLoader->GetResourceString(L"FailedToRefresh");
|
|
|
|
InitializeOfflineStatusTextBlock();
|
|
|
|
m_resultsFlyout = static_cast<MenuFlyout ^>(Resources->Lookup(L"CalculationResultContextMenu"));
|
|
CopyMenuItem->Text = resLoader->GetResourceString(L"copyMenuItem");
|
|
PasteMenuItem->Text = resLoader->GetResourceString(L"pasteMenuItem");
|
|
}
|
|
|
|
void UnitConverter::OnPropertyChanged(_In_ Object ^ sender, _In_ PropertyChangedEventArgs ^ e)
|
|
{
|
|
String ^ propertyName = e->PropertyName;
|
|
if (propertyName == UnitConverterViewModel::NetworkBehaviorPropertyName || propertyName == UnitConverterViewModel::CurrencyDataLoadFailedPropertyName)
|
|
{
|
|
OnNetworkBehaviorChanged();
|
|
}
|
|
else if (propertyName == UnitConverterViewModel::CurrencyDataIsWeekOldPropertyName)
|
|
{
|
|
SetCurrencyTimestampFontWeight();
|
|
}
|
|
else if (
|
|
propertyName == UnitConverterViewModel::IsCurrencyLoadingVisiblePropertyName
|
|
|| propertyName == UnitConverterViewModel::IsCurrencyCurrentCategoryPropertyName)
|
|
{
|
|
OnIsDisplayVisibleChanged();
|
|
}
|
|
}
|
|
|
|
void UnitConverter::OnNetworkBehaviorChanged()
|
|
{
|
|
switch (Model->NetworkBehavior)
|
|
{
|
|
case NetworkAccessBehavior::Normal:
|
|
OnNormalNetworkAccess();
|
|
break;
|
|
case NetworkAccessBehavior::OptIn:
|
|
OnOptInNetworkAccess();
|
|
break;
|
|
case NetworkAccessBehavior::Offline:
|
|
OnOfflineNetworkAccess();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void UnitConverter::OnNormalNetworkAccess()
|
|
{
|
|
CurrencyRefreshBlockControl->Visibility = ::Visibility::Visible;
|
|
OfflineBlock->Visibility = ::Visibility::Collapsed;
|
|
|
|
if (Model->CurrencyDataLoadFailed)
|
|
{
|
|
SetFailedToRefreshStatus();
|
|
}
|
|
else
|
|
{
|
|
SetNormalCurrencyStatus();
|
|
}
|
|
}
|
|
|
|
void UnitConverter::OnOptInNetworkAccess()
|
|
{
|
|
CurrencyRefreshBlockControl->Visibility = ::Visibility::Visible;
|
|
OfflineBlock->Visibility = ::Visibility::Collapsed;
|
|
|
|
if (m_meteredConnectionOverride && Model->CurrencyDataLoadFailed)
|
|
{
|
|
SetFailedToRefreshStatus();
|
|
}
|
|
else
|
|
{
|
|
SetChargesMayApplyStatus();
|
|
}
|
|
}
|
|
|
|
void UnitConverter::OnOfflineNetworkAccess()
|
|
{
|
|
CurrencyRefreshBlockControl->Visibility = ::Visibility::Collapsed;
|
|
OfflineBlock->Visibility = ::Visibility::Visible;
|
|
}
|
|
|
|
void UnitConverter::SetNormalCurrencyStatus()
|
|
{
|
|
CurrencySecondaryStatus->Text = L"";
|
|
}
|
|
|
|
void UnitConverter::SetChargesMayApplyStatus()
|
|
{
|
|
VisualStateManager::GoToState(this, L"ChargesMayApplyCurrencyStatus", false);
|
|
CurrencySecondaryStatus->Text = m_chargesMayApplyText;
|
|
}
|
|
|
|
void UnitConverter::SetFailedToRefreshStatus()
|
|
{
|
|
VisualStateManager::GoToState(this, L"FailedCurrencyStatus", false);
|
|
CurrencySecondaryStatus->Text = m_failedToRefreshText;
|
|
}
|
|
|
|
void UnitConverter::InitializeOfflineStatusTextBlock()
|
|
{
|
|
auto resProvider = AppResourceProvider::GetInstance();
|
|
std::wstring offlineStatusHyperlinkText = resProvider->GetResourceString(L"OfflineStatusHyperlinkText")->Data();
|
|
|
|
// The resource string has the 'NetworkSettings' hyperlink wrapped with '%HL%'.
|
|
// Break the string and assign pieces appropriately.
|
|
static const std::wstring delimiter{ L"%HL%" };
|
|
static const size_t delimiterLength{ delimiter.length() };
|
|
|
|
// Find the delimiters.
|
|
size_t firstSplitPosition = offlineStatusHyperlinkText.find(delimiter, 0);
|
|
assert(firstSplitPosition != std::wstring::npos);
|
|
size_t secondSplitPosition = offlineStatusHyperlinkText.find(delimiter, firstSplitPosition + 1);
|
|
assert(secondSplitPosition != std::wstring::npos);
|
|
size_t hyperlinkTextLength = secondSplitPosition - (firstSplitPosition + delimiterLength);
|
|
|
|
// Assign pieces.
|
|
auto offlineStatusTextBeforeHyperlink = ref new String(offlineStatusHyperlinkText.substr(0, firstSplitPosition).c_str());
|
|
auto offlineStatusTextLink = ref new String(offlineStatusHyperlinkText.substr(firstSplitPosition + delimiterLength, hyperlinkTextLength).c_str());
|
|
auto offlineStatusTextAfterHyperlink = ref new String(offlineStatusHyperlinkText.substr(secondSplitPosition + delimiterLength).c_str());
|
|
|
|
OfflineRunBeforeLink->Text = offlineStatusTextBeforeHyperlink;
|
|
OfflineRunLink->Text = offlineStatusTextLink;
|
|
OfflineRunAfterLink->Text = offlineStatusTextAfterHyperlink;
|
|
|
|
AutomationProperties::SetName(OfflineBlock, offlineStatusTextBeforeHyperlink + L" " + offlineStatusTextLink + L" " + offlineStatusTextAfterHyperlink);
|
|
}
|
|
|
|
void UnitConverter::SetCurrencyTimestampFontWeight()
|
|
{
|
|
if (Model->CurrencyDataIsWeekOld)
|
|
{
|
|
VisualStateManager::GoToState(this, L"WeekOldTimestamp", false);
|
|
}
|
|
else
|
|
{
|
|
VisualStateManager::GoToState(this, L"DefaultTimestamp", false);
|
|
}
|
|
}
|
|
|
|
void UnitConverter::OnValueKeyDown(Platform::Object ^ sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs ^ e)
|
|
{
|
|
if (e->Key == VirtualKey::Space)
|
|
{
|
|
OnValueSelected(sender);
|
|
}
|
|
}
|
|
|
|
void UnitConverter::OnContextRequested(UIElement ^ sender, ContextRequestedEventArgs ^ e)
|
|
{
|
|
OnValueSelected(sender);
|
|
auto requestedElement = safe_cast<FrameworkElement ^>(sender);
|
|
|
|
PasteMenuItem->IsEnabled = CopyPasteManager::HasStringToPaste();
|
|
|
|
Point point;
|
|
if (e->TryGetPosition(requestedElement, &point))
|
|
{
|
|
m_resultsFlyout->ShowAt(requestedElement, point);
|
|
}
|
|
else
|
|
{
|
|
// Not invoked via pointer, so let XAML choose a default location.
|
|
m_resultsFlyout->ShowAt(requestedElement);
|
|
}
|
|
|
|
e->Handled = true;
|
|
}
|
|
|
|
void UnitConverter::OnContextCanceled(UIElement ^ sender, RoutedEventArgs ^ e)
|
|
{
|
|
m_resultsFlyout->Hide();
|
|
}
|
|
|
|
void UnitConverter::OnCopyMenuItemClicked(_In_ Object ^ sender, _In_ RoutedEventArgs ^ e)
|
|
{
|
|
auto calcResult = safe_cast<CalculationResult ^>(m_resultsFlyout->Target);
|
|
CopyPasteManager::CopyToClipboard(calcResult->GetRawDisplayValue());
|
|
}
|
|
|
|
void UnitConverter::OnPasteMenuItemClicked(_In_ Object ^ sender, _In_ RoutedEventArgs ^ e)
|
|
{
|
|
auto that(this);
|
|
create_task(CopyPasteManager::GetStringToPaste(Model->Mode, CategoryGroupType::Converter, NumberBase::Unknown, BitLength::BitLengthUnknown))
|
|
.then([that](String ^ pastedString) { that->Model->OnPaste(pastedString); });
|
|
}
|
|
|
|
void UnitConverter::AnimateConverter()
|
|
{
|
|
static auto uiSettings = ref new UISettings();
|
|
if (uiSettings->AnimationsEnabled)
|
|
{
|
|
AnimationStory->Begin();
|
|
}
|
|
}
|
|
|
|
void UnitConverter::OnValueSelected(_In_ Platform::Object ^ sender)
|
|
{
|
|
auto value = safe_cast<CalculationResult ^>(sender);
|
|
// update the font size since the font is changed to bold
|
|
value->UpdateTextState();
|
|
safe_cast<UnitConverterViewModel ^>(this->DataContext)->OnValueActivated(AsActivatable(value));
|
|
}
|
|
|
|
void UnitConverter::UpdateDropDownState(_In_ Platform::Object ^ sender, _In_ Platform::Object ^ e)
|
|
{
|
|
safe_cast<UnitConverterViewModel ^>(this->DataContext)->IsDropDownOpen = (Units1->IsDropDownOpen) || (Units2->IsDropDownOpen);
|
|
KeyboardShortcutManager::UpdateDropDownState((Units1->IsDropDownOpen) || (Units2->IsDropDownOpen));
|
|
}
|
|
|
|
void UnitConverter::OnLoaded(_In_ Object ^, _In_ RoutedEventArgs ^)
|
|
{
|
|
}
|
|
|
|
void UnitConverter::SetDefaultFocus()
|
|
{
|
|
const std::vector<Control ^> focusPrecedence = { Value1, CurrencyRefreshBlockControl, OfflineBlock, ClearEntryButtonPos0 };
|
|
|
|
for (Control ^ control : focusPrecedence)
|
|
{
|
|
if (control->Focus(::FocusState::Programmatic))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UnitConverter::CurrencyRefreshButton_Click(_In_ Object ^ /*sender*/, _In_ RoutedEventArgs ^ /*e*/)
|
|
{
|
|
// If IsCurrencyLoadingVisible is true that means CurrencyRefreshButton_Click was recently called
|
|
// and is still executing. In this case there is no reason to process the click.
|
|
if (!Model->IsCurrencyLoadingVisible)
|
|
{
|
|
if (Model->NetworkBehavior == NetworkAccessBehavior::OptIn)
|
|
{
|
|
m_meteredConnectionOverride = true;
|
|
}
|
|
|
|
Model->RefreshCurrencyRatios();
|
|
}
|
|
}
|
|
|
|
void UnitConverter::OnDataContextChanged(_In_ FrameworkElement ^ sender, _In_ DataContextChangedEventArgs ^ args)
|
|
{
|
|
Model->PropertyChanged -= m_propertyChangedToken;
|
|
|
|
m_propertyChangedToken = Model->PropertyChanged += ref new PropertyChangedEventHandler(this, &UnitConverter::OnPropertyChanged);
|
|
|
|
OnNetworkBehaviorChanged();
|
|
}
|
|
|
|
void UnitConverter::Units1_IsEnabledChanged(Object ^ sender, DependencyPropertyChangedEventArgs ^ e)
|
|
{
|
|
if ((Units1->Visibility == ::Visibility::Visible) && Units1->IsEnabled)
|
|
{
|
|
SetDefaultFocus();
|
|
}
|
|
}
|
|
|
|
void UnitConverter::OnIsDisplayVisibleChanged()
|
|
{
|
|
if (!Model->IsCurrencyCurrentCategory)
|
|
{
|
|
VisualStateManager::GoToState(this, UnitLoadedState->Name, false);
|
|
}
|
|
else
|
|
{
|
|
if (Model->IsCurrencyLoadingVisible)
|
|
{
|
|
VisualStateManager::GoToState(this, UnitNotLoadedState->Name, false);
|
|
StartProgressRingWithDelay();
|
|
}
|
|
else
|
|
{
|
|
HideProgressRing();
|
|
VisualStateManager::GoToState(this, !Model->CurrencyTimestamp->IsEmpty() ? UnitLoadedState->Name : UnitNotLoadedState->Name, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UnitConverter::StartProgressRingWithDelay()
|
|
{
|
|
HideProgressRing();
|
|
|
|
TimeSpan delay{};
|
|
delay.Duration = DURATION_500_MS;
|
|
|
|
m_delayTimer = ref new DispatcherTimer();
|
|
m_delayTimer->Interval = delay;
|
|
m_delayTimer->Tick += ref new EventHandler<Object ^>(this, &UnitConverter::OnDelayTimerTick);
|
|
|
|
m_delayTimer->Start();
|
|
}
|
|
|
|
void UnitConverter::OnDelayTimerTick(Object ^ /*sender*/, Object ^ /*e*/)
|
|
{
|
|
CurrencyLoadingProgressRing->IsActive = true;
|
|
m_delayTimer->Stop();
|
|
}
|
|
|
|
void UnitConverter::HideProgressRing()
|
|
{
|
|
if (m_delayTimer != nullptr)
|
|
{
|
|
m_delayTimer->Stop();
|
|
}
|
|
|
|
CurrencyLoadingProgressRing->IsActive = false;
|
|
}
|
|
|
|
// The function will make sure the UI will have enough space to display supplementary results and currency information
|
|
void CalculatorApp::UnitConverter::SupplementaryResultsPanelInGrid_SizeChanged(Platform::Object ^ sender, Windows::UI::Xaml::SizeChangedEventArgs ^ e)
|
|
{
|
|
// We add 0.01 to be sure to not create an infinite loop with SizeChanged events cascading due to float approximation
|
|
RowDltrUnits->MinHeight = max(48.0, e->NewSize.Height + 0.01);
|
|
}
|
|
|
|
void CalculatorApp::UnitConverter::OnVisualStateChanged(Platform::Object ^ sender, Windows::UI::Xaml::VisualStateChangedEventArgs ^ e)
|
|
{
|
|
auto mode = NavCategory::Deserialize(Model->CurrentCategory->GetModelCategory().id);
|
|
TraceLogger::GetInstance()->LogVisualStateChanged(mode, e->NewState->Name, false);
|
|
}
|