Hello GitHub
This commit is contained in:
287
src/CalcViewModel/Common/AlwaysSelectedCollectionView.h
Normal file
287
src/CalcViewModel/Common/AlwaysSelectedCollectionView.h
Normal file
@@ -0,0 +1,287 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp { namespace Common
|
||||
{
|
||||
ref class AlwaysSelectedCollectionView sealed:
|
||||
public Windows::UI::Xaml::DependencyObject,
|
||||
public Windows::UI::Xaml::Data::ICollectionView
|
||||
{
|
||||
internal:
|
||||
AlwaysSelectedCollectionView(Windows::UI::Xaml::Interop::IBindableVector^ source):
|
||||
m_currentPosition(-1)
|
||||
{
|
||||
m_source = source;
|
||||
|
||||
Windows::UI::Xaml::Interop::IBindableObservableVector^ observable = dynamic_cast<Windows::UI::Xaml::Interop::IBindableObservableVector^>(source);
|
||||
if (observable)
|
||||
{
|
||||
observable->VectorChanged +=
|
||||
ref new Windows::UI::Xaml::Interop::BindableVectorChangedEventHandler(this, &AlwaysSelectedCollectionView::OnSourceBindableVectorChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// ICollectionView
|
||||
// Not implemented methods
|
||||
virtual WF::IAsyncOperation<Windows::UI::Xaml::Data::LoadMoreItemsResult>^ LoadMoreItemsAsync(unsigned int) = Windows::UI::Xaml::Data::ICollectionView::LoadMoreItemsAsync
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual bool MoveCurrentToFirst() = Windows::UI::Xaml::Data::ICollectionView::MoveCurrentToFirst
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual bool MoveCurrentToLast() = Windows::UI::Xaml::Data::ICollectionView::MoveCurrentToLast
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual bool MoveCurrentToNext() = Windows::UI::Xaml::Data::ICollectionView::MoveCurrentToNext
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual bool MoveCurrentToPrevious() = Windows::UI::Xaml::Data::ICollectionView::MoveCurrentToPrevious
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
property Windows::Foundation::Collections::IObservableVector<Platform::Object^>^ CollectionGroups
|
||||
{
|
||||
virtual Windows::Foundation::Collections::IObservableVector<Platform::Object^>^ get() = Windows::UI::Xaml::Data::ICollectionView::CollectionGroups::get
|
||||
{
|
||||
return ref new Platform::Collections::Vector<Platform::Object^>();
|
||||
}
|
||||
}
|
||||
property bool HasMoreItems
|
||||
{
|
||||
virtual bool get() = Windows::UI::Xaml::Data::ICollectionView::HasMoreItems::get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Implementented methods
|
||||
virtual bool MoveCurrentTo(Platform::Object^ item) = Windows::UI::Xaml::Data::ICollectionView::MoveCurrentTo
|
||||
{
|
||||
if (item)
|
||||
{
|
||||
unsigned int newCurrentPosition = 0;
|
||||
bool result = m_source->IndexOf(item, &newCurrentPosition);
|
||||
if (result)
|
||||
{
|
||||
m_currentPosition = newCurrentPosition;
|
||||
m_currentChanged(this, nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The item is not in the collection
|
||||
// We're going to schedule a call back later so we
|
||||
// restore the selection to the way we wanted it to begin with
|
||||
if (m_currentPosition >= 0 && m_currentPosition < static_cast<int>(m_source->Size))
|
||||
{
|
||||
this->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
ref new Windows::UI::Core::DispatchedHandler(
|
||||
[this]()
|
||||
{
|
||||
m_currentChanged(this, nullptr);
|
||||
}));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool MoveCurrentToPosition(int index) = Windows::UI::Xaml::Data::ICollectionView::MoveCurrentToPosition
|
||||
{
|
||||
if (index < 0 || index >= static_cast<int>(m_source->Size))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_currentPosition = index;
|
||||
m_currentChanged(this, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
property Platform::Object^ CurrentItem
|
||||
{
|
||||
virtual Platform::Object^ get() = Windows::UI::Xaml::Data::ICollectionView::CurrentItem::get
|
||||
{
|
||||
if (m_currentPosition >= 0 && m_currentPosition < static_cast<int>(m_source->Size))
|
||||
{
|
||||
return m_source->GetAt(m_currentPosition);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
property int CurrentPosition
|
||||
{
|
||||
virtual int get() = Windows::UI::Xaml::Data::ICollectionView::CurrentPosition::get
|
||||
{
|
||||
return m_currentPosition;
|
||||
}
|
||||
}
|
||||
|
||||
property bool IsCurrentAfterLast
|
||||
{
|
||||
virtual bool get() = Windows::UI::Xaml::Data::ICollectionView::IsCurrentAfterLast::get
|
||||
{
|
||||
return m_currentPosition >= static_cast<int>(m_source->Size);
|
||||
}
|
||||
}
|
||||
|
||||
property bool IsCurrentBeforeFirst
|
||||
{
|
||||
virtual bool get() = Windows::UI::Xaml::Data::ICollectionView::IsCurrentBeforeFirst::get
|
||||
{
|
||||
return m_currentPosition < 0;
|
||||
}
|
||||
}
|
||||
|
||||
event WF::EventHandler<Platform::Object^>^ CurrentChanged
|
||||
{
|
||||
virtual WF::EventRegistrationToken add(WF::EventHandler<Platform::Object^>^ handler) = Windows::UI::Xaml::Data::ICollectionView::CurrentChanged::add
|
||||
{
|
||||
return m_currentChanged += handler;
|
||||
}
|
||||
virtual void remove(WF::EventRegistrationToken token) = Windows::UI::Xaml::Data::ICollectionView::CurrentChanged::remove
|
||||
{
|
||||
m_currentChanged -= token;
|
||||
}
|
||||
}
|
||||
event Windows::UI::Xaml::Data::CurrentChangingEventHandler^ CurrentChanging
|
||||
{
|
||||
virtual WF::EventRegistrationToken add(Windows::UI::Xaml::Data::CurrentChangingEventHandler^ handler) = Windows::UI::Xaml::Data::ICollectionView::CurrentChanging::add
|
||||
{
|
||||
return m_currentChanging += handler;
|
||||
}
|
||||
virtual void remove(WF::EventRegistrationToken token) = Windows::UI::Xaml::Data::ICollectionView::CurrentChanging::remove
|
||||
{
|
||||
m_currentChanging -= token;
|
||||
}
|
||||
}
|
||||
|
||||
// IVector<Object^>
|
||||
// Not implemented methods
|
||||
virtual void Append(Platform::Object^ /*item*/) = Windows::Foundation::Collections::IVector<Platform::Object^>::Append
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual void Clear() = Windows::Foundation::Collections::IVector<Platform::Object^>::Clear
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual unsigned int GetMany(unsigned int /*startIndex*/, Platform::WriteOnlyArray<Platform::Object^>^ /*items*/) = Windows::Foundation::Collections::IVector<Platform::Object^>::GetMany
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual Windows::Foundation::Collections::IVectorView<Platform::Object^>^ GetView() = Windows::Foundation::Collections::IVector<Platform::Object^>::GetView
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual void InsertAt(unsigned int /*index*/, Platform::Object^ /*item*/) = Windows::Foundation::Collections::IVector<Platform::Object^>::InsertAt
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual void RemoveAt(unsigned int /*index*/) = Windows::Foundation::Collections::IVector<Platform::Object^>::RemoveAt
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual void RemoveAtEnd() = Windows::Foundation::Collections::IVector<Platform::Object^>::RemoveAtEnd
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual void ReplaceAll(const Platform::Array<Platform::Object^>^ /*items*/) = Windows::Foundation::Collections::IVector<Platform::Object^>::ReplaceAll
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
virtual void SetAt(unsigned int /*index*/, Platform::Object^ /*item*/) = Windows::Foundation::Collections::IVector<Platform::Object^>::SetAt
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
|
||||
// Implemented methods
|
||||
virtual Platform::Object^ GetAt(unsigned int index) = Windows::Foundation::Collections::IVector<Platform::Object^>::GetAt
|
||||
{
|
||||
return m_source->GetAt(index);
|
||||
}
|
||||
|
||||
virtual bool IndexOf(Platform::Object^ item, unsigned int* index) = Windows::Foundation::Collections::IVector<Platform::Object^>::IndexOf
|
||||
{
|
||||
return m_source->IndexOf(item, index);
|
||||
}
|
||||
|
||||
property unsigned int Size
|
||||
{
|
||||
virtual unsigned int get() = Windows::Foundation::Collections::IVector<Platform::Object^>::Size::get
|
||||
{
|
||||
return m_source->Size;
|
||||
}
|
||||
}
|
||||
|
||||
// IObservableVector<Object^>
|
||||
event Windows::Foundation::Collections::VectorChangedEventHandler<Platform::Object^>^ VectorChanged
|
||||
{
|
||||
virtual WF::EventRegistrationToken add(Windows::Foundation::Collections::VectorChangedEventHandler<Platform::Object^>^ handler) = Windows::Foundation::Collections::IObservableVector<Platform::Object^>::VectorChanged::add
|
||||
{
|
||||
return m_vectorChanged += handler;
|
||||
}
|
||||
virtual void remove(WF::EventRegistrationToken token) = Windows::Foundation::Collections::IObservableVector<Platform::Object^>::VectorChanged::remove
|
||||
{
|
||||
m_vectorChanged -= token;
|
||||
}
|
||||
}
|
||||
|
||||
// IIterable<Object^>
|
||||
// Not implemented
|
||||
virtual Windows::Foundation::Collections::IIterator<Platform::Object^>^ First() = Windows::Foundation::Collections::IIterable<Platform::Object^>::First
|
||||
{
|
||||
throw ref new Platform::NotImplementedException();
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
void OnSourceBindableVectorChanged(Windows::UI::Xaml::Interop::IBindableObservableVector^ source, Platform::Object^ e)
|
||||
{
|
||||
Windows::Foundation::Collections::IVectorChangedEventArgs^ args = safe_cast<Windows::Foundation::Collections::IVectorChangedEventArgs^>(e);
|
||||
m_vectorChanged(this, args);
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::Interop::IBindableVector^ m_source;
|
||||
int m_currentPosition;
|
||||
event WF::EventHandler<Platform::Object^>^ m_currentChanged;
|
||||
event Windows::UI::Xaml::Data::CurrentChangingEventHandler^ m_currentChanging;
|
||||
event Windows::Foundation::Collections::VectorChangedEventHandler<Platform::Object^>^ m_vectorChanged;
|
||||
};
|
||||
|
||||
public ref class AlwaysSelectedCollectionViewConverter sealed: public Windows::UI::Xaml::Data::IValueConverter
|
||||
{
|
||||
public:
|
||||
AlwaysSelectedCollectionViewConverter()
|
||||
{ }
|
||||
|
||||
private:
|
||||
virtual Platform::Object^ Convert(
|
||||
Platform::Object^ value,
|
||||
Windows::UI::Xaml::Interop::TypeName /*targetType*/,
|
||||
Platform::Object^ /*parameter*/,
|
||||
Platform::String^ /*language*/) = Windows::UI::Xaml::Data::IValueConverter::Convert
|
||||
{
|
||||
auto result = dynamic_cast<Windows::UI::Xaml::Interop::IBindableVector^>(value);
|
||||
if (result)
|
||||
{
|
||||
return ref new AlwaysSelectedCollectionView(result);
|
||||
}
|
||||
return Windows::UI::Xaml::DependencyProperty::UnsetValue; // Can't convert
|
||||
}
|
||||
|
||||
virtual Platform::Object^ ConverBack(
|
||||
Platform::Object^ /*value*/,
|
||||
Windows::UI::Xaml::Interop::TypeName /*targetType*/,
|
||||
Platform::Object^ /*parameter*/,
|
||||
Platform::String^ /*language*/) = Windows::UI::Xaml::Data::IValueConverter::ConvertBack
|
||||
{
|
||||
return Windows::UI::Xaml::DependencyProperty::UnsetValue;
|
||||
}
|
||||
};
|
||||
}}
|
33
src/CalcViewModel/Common/AppResourceProvider.cpp
Normal file
33
src/CalcViewModel/Common/AppResourceProvider.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pch.h"
|
||||
#include "AppResourceProvider.h"
|
||||
|
||||
using namespace Platform;
|
||||
using namespace Windows::ApplicationModel::Resources;
|
||||
using namespace CalculatorApp;
|
||||
|
||||
AppResourceProvider::AppResourceProvider()
|
||||
{
|
||||
m_stringResLoader = ResourceLoader::GetForViewIndependentUse();
|
||||
m_cEngineStringResLoader = ResourceLoader::GetForViewIndependentUse(L"CEngineStrings");
|
||||
}
|
||||
|
||||
AppResourceProvider & AppResourceProvider::GetInstance()
|
||||
{
|
||||
static AppResourceProvider s_instance;
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
String^ AppResourceProvider::GetResourceString(_In_ String^ key)
|
||||
{
|
||||
return m_stringResLoader->GetString(key);
|
||||
}
|
||||
|
||||
String^ AppResourceProvider::GetCEngineString(_In_ String^ key)
|
||||
{
|
||||
return m_cEngineStringResLoader->GetString(key);
|
||||
}
|
20
src/CalcViewModel/Common/AppResourceProvider.h
Normal file
20
src/CalcViewModel/Common/AppResourceProvider.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
class AppResourceProvider
|
||||
{
|
||||
public:
|
||||
static AppResourceProvider & GetInstance();
|
||||
Platform::String^ GetResourceString(_In_ Platform::String^ key);
|
||||
Platform::String^ GetCEngineString(_In_ Platform::String^ key);
|
||||
|
||||
private:
|
||||
AppResourceProvider();
|
||||
Windows::ApplicationModel::Resources::ResourceLoader^ m_stringResLoader;
|
||||
Windows::ApplicationModel::Resources::ResourceLoader^ m_cEngineStringResLoader;
|
||||
};
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
#include "NarratorAnnouncement.h"
|
||||
|
||||
// Declaration of the INarratorAnnouncementHost interface.
|
||||
// This interface exists to hide the concrete announcement host
|
||||
// being used. Depending on the version of the OS the app is running on,
|
||||
// the app may need a host that uses LiveRegionChanged or RaiseNotification.
|
||||
|
||||
namespace CalculatorApp::Common::Automation
|
||||
{
|
||||
public interface class INarratorAnnouncementHost
|
||||
{
|
||||
public:
|
||||
// Is the host available on this OS.
|
||||
bool IsHostAvailable();
|
||||
|
||||
// Make a new instance of a concrete host.
|
||||
INarratorAnnouncementHost^ MakeHost();
|
||||
|
||||
// Make an announcement using the concrete host's preferred method.
|
||||
void Announce(NarratorAnnouncement^ announcement);
|
||||
};
|
||||
}
|
41
src/CalcViewModel/Common/Automation/LiveRegionHost.cpp
Normal file
41
src/CalcViewModel/Common/Automation/LiveRegionHost.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "LiveRegionHost.h"
|
||||
|
||||
using namespace CalculatorApp::Common::Automation;
|
||||
using namespace Windows::UI::Xaml::Automation;
|
||||
using namespace Windows::UI::Xaml::Automation::Peers;
|
||||
using namespace Windows::UI::Xaml::Controls;
|
||||
|
||||
LiveRegionHost::LiveRegionHost() :
|
||||
m_host(nullptr)
|
||||
{}
|
||||
|
||||
bool LiveRegionHost::IsHostAvailable()
|
||||
{
|
||||
// LiveRegion is always available.
|
||||
return true;
|
||||
}
|
||||
|
||||
INarratorAnnouncementHost^ LiveRegionHost::MakeHost()
|
||||
{
|
||||
return ref new LiveRegionHost();
|
||||
}
|
||||
|
||||
void LiveRegionHost::Announce(NarratorAnnouncement^ announcement)
|
||||
{
|
||||
if (m_host == nullptr)
|
||||
{
|
||||
m_host = ref new TextBlock();
|
||||
AutomationProperties::SetLiveSetting(m_host, AutomationLiveSetting::Assertive);
|
||||
}
|
||||
|
||||
AutomationProperties::SetName(m_host, announcement->Announcement);
|
||||
AutomationPeer^ peer = FrameworkElementAutomationPeer::FromElement(m_host);
|
||||
if (peer != nullptr)
|
||||
{
|
||||
peer->RaiseAutomationEvent(AutomationEvents::LiveRegionChanged);
|
||||
}
|
||||
}
|
32
src/CalcViewModel/Common/Automation/LiveRegionHost.h
Normal file
32
src/CalcViewModel/Common/Automation/LiveRegionHost.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
#include "INarratorAnnouncementHost.h"
|
||||
|
||||
// Declaration of the LiveRegionHost class.
|
||||
// This class announces NarratorAnnouncements using the LiveRegionChanged event.
|
||||
// This event is unreliable and should be deprecated in favor of the new
|
||||
// RaiseNotification API in RS3.
|
||||
|
||||
namespace CalculatorApp::Common::Automation
|
||||
{
|
||||
// This class exists so that the app can run on RS2 and use LiveRegions
|
||||
// to host notifiactions on those builds.
|
||||
// When the app switches to min version RS3, this class can be removed
|
||||
// and the app will switch to using the Notification API.
|
||||
// TODO - MSFT 12735088
|
||||
public ref class LiveRegionHost sealed : public INarratorAnnouncementHost
|
||||
{
|
||||
public:
|
||||
LiveRegionHost();
|
||||
|
||||
virtual bool IsHostAvailable();
|
||||
virtual INarratorAnnouncementHost^ MakeHost();
|
||||
|
||||
virtual void Announce(NarratorAnnouncement^ announcement);
|
||||
|
||||
private:
|
||||
Windows::UI::Xaml::UIElement^ m_host;
|
||||
};
|
||||
}
|
144
src/CalcViewModel/Common/Automation/NarratorAnnouncement.cpp
Normal file
144
src/CalcViewModel/Common/Automation/NarratorAnnouncement.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "NarratorAnnouncement.h"
|
||||
|
||||
using namespace CalculatorApp::Common::Automation;
|
||||
using namespace Platform;
|
||||
|
||||
namespace CalculatorApp::Common::Automation
|
||||
{
|
||||
namespace CalculatorActivityIds
|
||||
{
|
||||
StringReference DisplayUpdated(L"DisplayUpdated");
|
||||
StringReference MaxDigitsReached(L"MaxDigitsReached");
|
||||
StringReference MemoryCleared(L"MemoryCleared");
|
||||
StringReference MemoryItemChanged(L"MemorySlotChanged");
|
||||
StringReference MemoryItemAdded(L"MemorySlotAdded");
|
||||
StringReference HistoryCleared(L"HistoryCleared");
|
||||
StringReference CategoryNameChanged(L"CategoryNameChanged");
|
||||
StringReference UpdateCurrencyRates(L"UpdateCurrencyRates");
|
||||
StringReference DisplayCopied(L"DisplayCopied");
|
||||
}
|
||||
}
|
||||
|
||||
NarratorAnnouncement::NarratorAnnouncement(
|
||||
String^ announcement,
|
||||
String^ activityId,
|
||||
AutomationNotificationKind kind,
|
||||
AutomationNotificationProcessing processing)
|
||||
:
|
||||
m_announcement(announcement),
|
||||
m_activityId(activityId),
|
||||
m_kind(kind),
|
||||
m_processing(processing)
|
||||
{}
|
||||
|
||||
String^ NarratorAnnouncement::Announcement::get()
|
||||
{
|
||||
return m_announcement;
|
||||
}
|
||||
|
||||
String^ NarratorAnnouncement::ActivityId::get()
|
||||
{
|
||||
return m_activityId;
|
||||
}
|
||||
|
||||
AutomationNotificationKind NarratorAnnouncement::Kind::get()
|
||||
{
|
||||
return m_kind;
|
||||
}
|
||||
|
||||
AutomationNotificationProcessing NarratorAnnouncement::Processing::get()
|
||||
{
|
||||
return m_processing;
|
||||
}
|
||||
|
||||
bool NarratorAnnouncement::IsValid(NarratorAnnouncement^ announcement)
|
||||
{
|
||||
return announcement != nullptr
|
||||
&& announcement->Announcement != nullptr
|
||||
&& !announcement->Announcement->IsEmpty();
|
||||
}
|
||||
|
||||
NarratorAnnouncement^ CalculatorAnnouncement::GetDisplayUpdatedAnnouncement(String^ announcement)
|
||||
{
|
||||
return ref new NarratorAnnouncement(
|
||||
announcement,
|
||||
CalculatorActivityIds::DisplayUpdated,
|
||||
AutomationNotificationKind::Other,
|
||||
AutomationNotificationProcessing::ImportantMostRecent);
|
||||
}
|
||||
|
||||
NarratorAnnouncement^ CalculatorAnnouncement::GetMaxDigitsReachedAnnouncement(String^ announcement)
|
||||
{
|
||||
return ref new NarratorAnnouncement(
|
||||
announcement,
|
||||
CalculatorActivityIds::MaxDigitsReached,
|
||||
AutomationNotificationKind::Other,
|
||||
AutomationNotificationProcessing::ImportantMostRecent);
|
||||
}
|
||||
|
||||
NarratorAnnouncement^ CalculatorAnnouncement::GetMemoryClearedAnnouncement(String^ announcement)
|
||||
{
|
||||
return ref new NarratorAnnouncement(
|
||||
announcement,
|
||||
CalculatorActivityIds::MemoryCleared,
|
||||
AutomationNotificationKind::ItemRemoved,
|
||||
AutomationNotificationProcessing::ImportantMostRecent);
|
||||
}
|
||||
|
||||
NarratorAnnouncement^ CalculatorAnnouncement::GetMemoryItemChangedAnnouncement(String^ announcement)
|
||||
{
|
||||
return ref new NarratorAnnouncement(
|
||||
announcement,
|
||||
CalculatorActivityIds::MemoryItemChanged,
|
||||
AutomationNotificationKind::ActionCompleted,
|
||||
AutomationNotificationProcessing::MostRecent);
|
||||
}
|
||||
|
||||
NarratorAnnouncement^ CalculatorAnnouncement::GetMemoryItemAddedAnnouncement(String^ announcement)
|
||||
{
|
||||
return ref new NarratorAnnouncement(
|
||||
announcement,
|
||||
CalculatorActivityIds::MemoryItemAdded,
|
||||
AutomationNotificationKind::ItemAdded,
|
||||
AutomationNotificationProcessing::MostRecent);
|
||||
}
|
||||
|
||||
NarratorAnnouncement^ CalculatorAnnouncement::GetHistoryClearedAnnouncement(String^ announcement)
|
||||
{
|
||||
return ref new NarratorAnnouncement(
|
||||
announcement,
|
||||
CalculatorActivityIds::HistoryCleared,
|
||||
AutomationNotificationKind::ItemRemoved,
|
||||
AutomationNotificationProcessing::MostRecent);
|
||||
}
|
||||
|
||||
NarratorAnnouncement^ CalculatorAnnouncement::GetCategoryNameChangedAnnouncement(String^ announcement)
|
||||
{
|
||||
return ref new NarratorAnnouncement(
|
||||
announcement,
|
||||
CalculatorActivityIds::CategoryNameChanged,
|
||||
AutomationNotificationKind::ActionCompleted,
|
||||
AutomationNotificationProcessing::ImportantMostRecent);
|
||||
}
|
||||
|
||||
NarratorAnnouncement^ CalculatorAnnouncement::GetUpdateCurrencyRatesAnnouncement(String^ announcement)
|
||||
{
|
||||
return ref new NarratorAnnouncement(
|
||||
announcement,
|
||||
CalculatorActivityIds::UpdateCurrencyRates,
|
||||
AutomationNotificationKind::ActionCompleted,
|
||||
AutomationNotificationProcessing::ImportantMostRecent);
|
||||
}
|
||||
|
||||
NarratorAnnouncement^ CalculatorAnnouncement::GetDisplayCopiedAnnouncement(String^ announcement)
|
||||
{
|
||||
return ref new NarratorAnnouncement(
|
||||
announcement,
|
||||
CalculatorActivityIds::DisplayCopied,
|
||||
AutomationNotificationKind::ActionCompleted,
|
||||
AutomationNotificationProcessing::ImportantMostRecent);
|
||||
}
|
94
src/CalcViewModel/Common/Automation/NarratorAnnouncement.h
Normal file
94
src/CalcViewModel/Common/Automation/NarratorAnnouncement.h
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp::Common::Automation
|
||||
{
|
||||
// These enum types are copied from the types available in
|
||||
// Windows::UI::Xaml::Automation::Peers in the RS3 SDK.
|
||||
// When this app switches to min version RS3, these custom
|
||||
// enums should be removed and the Windows types should be used
|
||||
// instead.
|
||||
// TODO - MSFT 12735088
|
||||
public enum class AutomationNotificationKind
|
||||
{
|
||||
ItemAdded = 0,
|
||||
ItemRemoved = 1,
|
||||
ActionCompleted = 2,
|
||||
ActionAborted = 3,
|
||||
Other = 4
|
||||
};
|
||||
|
||||
public enum class AutomationNotificationProcessing
|
||||
{
|
||||
ImportantAll = 0,
|
||||
ImportantMostRecent = 1,
|
||||
All = 2,
|
||||
MostRecent = 3,
|
||||
CurrentThenMostRecent = 4
|
||||
};
|
||||
|
||||
public ref class NarratorAnnouncement sealed
|
||||
{
|
||||
public:
|
||||
property Platform::String^ Announcement
|
||||
{
|
||||
Platform::String^ get();
|
||||
}
|
||||
|
||||
property Platform::String^ ActivityId
|
||||
{
|
||||
Platform::String^ get();
|
||||
}
|
||||
|
||||
property AutomationNotificationKind Kind
|
||||
{
|
||||
AutomationNotificationKind get();
|
||||
}
|
||||
|
||||
property AutomationNotificationProcessing Processing
|
||||
{
|
||||
AutomationNotificationProcessing get();
|
||||
}
|
||||
|
||||
static bool IsValid(NarratorAnnouncement^ announcement);
|
||||
|
||||
private:
|
||||
// Make CalculatorAnnouncement a friend class so it is the only
|
||||
// class that can access the private constructor.
|
||||
friend class CalculatorAnnouncement;
|
||||
|
||||
NarratorAnnouncement(
|
||||
Platform::String^ announcement,
|
||||
Platform::String^ activityId,
|
||||
AutomationNotificationKind kind,
|
||||
AutomationNotificationProcessing processing);
|
||||
|
||||
Platform::String^ m_announcement;
|
||||
Platform::String^ m_activityId;
|
||||
AutomationNotificationKind m_kind;
|
||||
AutomationNotificationProcessing m_processing;
|
||||
};
|
||||
|
||||
// CalculatorAnnouncement is intended to contain only static methods
|
||||
// that return announcements made for the Calculator app.
|
||||
class CalculatorAnnouncement
|
||||
{
|
||||
public:
|
||||
static NarratorAnnouncement^ GetDisplayUpdatedAnnouncement(Platform::String^ announcement);
|
||||
static NarratorAnnouncement^ GetMaxDigitsReachedAnnouncement(Platform::String^ announcement);
|
||||
|
||||
static NarratorAnnouncement^ GetMemoryClearedAnnouncement(Platform::String^ announcement);
|
||||
static NarratorAnnouncement^ GetMemoryItemChangedAnnouncement(Platform::String^ announcement);
|
||||
static NarratorAnnouncement^ GetMemoryItemAddedAnnouncement(Platform::String^ announcement);
|
||||
|
||||
static NarratorAnnouncement^ GetHistoryClearedAnnouncement(Platform::String^ announcement);
|
||||
|
||||
static NarratorAnnouncement^ GetCategoryNameChangedAnnouncement(Platform::String^ announcement);
|
||||
|
||||
static NarratorAnnouncement^ GetUpdateCurrencyRatesAnnouncement(Platform::String^ announcement);
|
||||
|
||||
static NarratorAnnouncement^ GetDisplayCopiedAnnouncement(Platform::String^ announcement);
|
||||
};
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "NarratorAnnouncementHostFactory.h"
|
||||
#include "NotificationHost.h"
|
||||
#include "LiveRegionHost.h"
|
||||
|
||||
using namespace CalculatorApp::Common::Automation;
|
||||
using namespace std;
|
||||
|
||||
INarratorAnnouncementHost^ NarratorAnnouncementHostFactory::s_hostProducer;
|
||||
vector<INarratorAnnouncementHost^> NarratorAnnouncementHostFactory::s_hosts;
|
||||
|
||||
// This static variable is used only to call the initialization function, to initialize the other static variables.
|
||||
int NarratorAnnouncementHostFactory::s_init = NarratorAnnouncementHostFactory::Initialize();
|
||||
int NarratorAnnouncementHostFactory::Initialize()
|
||||
{
|
||||
RegisterHosts();
|
||||
NarratorAnnouncementHostFactory::s_hostProducer = GetHostProducer();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// For now, there are two type of announcement hosts.
|
||||
// We'd prefer to use Notification if it's available and fallback to LiveRegion
|
||||
// if not. The availabilty of the host depends on the version of the OS the app is running on.
|
||||
// When the app switches to min version RS3, the LiveRegionHost can be removed and we will always
|
||||
// use NotificationHost.
|
||||
// TODO - MSFT 12735088
|
||||
void NarratorAnnouncementHostFactory::RegisterHosts()
|
||||
{
|
||||
// The host that will be used is the first available host,
|
||||
// therefore, order of hosts is important here.
|
||||
NarratorAnnouncementHostFactory::s_hosts = {
|
||||
ref new NotificationHost(),
|
||||
ref new LiveRegionHost()
|
||||
};
|
||||
}
|
||||
|
||||
INarratorAnnouncementHost^ NarratorAnnouncementHostFactory::GetHostProducer()
|
||||
{
|
||||
for (INarratorAnnouncementHost^ host : NarratorAnnouncementHostFactory::s_hosts)
|
||||
{
|
||||
if (host->IsHostAvailable())
|
||||
{
|
||||
return host;
|
||||
}
|
||||
}
|
||||
|
||||
assert(false && L"No suitable AnnouncementHost was found.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
INarratorAnnouncementHost^ NarratorAnnouncementHostFactory::MakeHost()
|
||||
{
|
||||
if (NarratorAnnouncementHostFactory::s_hostProducer == nullptr)
|
||||
{
|
||||
assert(false && L"No host producer has been assigned.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return NarratorAnnouncementHostFactory::s_hostProducer->MakeHost();
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
#include "INarratorAnnouncementHost.h"
|
||||
|
||||
// Declaration of the NarratorAnnouncementHostFactory class.
|
||||
// This class exists to hide the construction of a concrete INarratorAnnouncementHost.
|
||||
// Depending on the version of the OS the app is running on, the factory will return
|
||||
// an announcement host appropriate for that version.
|
||||
|
||||
namespace CalculatorApp::Common::Automation
|
||||
{
|
||||
class NarratorAnnouncementHostFactory
|
||||
{
|
||||
public:
|
||||
static INarratorAnnouncementHost^ MakeHost();
|
||||
|
||||
private:
|
||||
NarratorAnnouncementHostFactory() {}
|
||||
|
||||
static int Initialize();
|
||||
static void RegisterHosts();
|
||||
static INarratorAnnouncementHost^ GetHostProducer();
|
||||
|
||||
private:
|
||||
static int s_init;
|
||||
static INarratorAnnouncementHost^ s_hostProducer;
|
||||
static std::vector<INarratorAnnouncementHost^> s_hosts;
|
||||
};
|
||||
}
|
50
src/CalcViewModel/Common/Automation/NarratorNotifier.cpp
Normal file
50
src/CalcViewModel/Common/Automation/NarratorNotifier.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// Implementation of the NarratorNotifier class.
|
||||
|
||||
#include "pch.h"
|
||||
#include "NarratorNotifier.h"
|
||||
#include "NarratorAnnouncementHostFactory.h"
|
||||
|
||||
using namespace CalculatorApp::Common::Automation;
|
||||
using namespace Platform;
|
||||
using namespace Windows::UI::Xaml;
|
||||
using namespace Windows::UI::Xaml::Automation;
|
||||
using namespace Windows::UI::Xaml::Automation::Peers;
|
||||
|
||||
DependencyProperty^ NarratorNotifier::s_announcementProperty;
|
||||
|
||||
NarratorNotifier::NarratorNotifier()
|
||||
{
|
||||
m_announcementHost = NarratorAnnouncementHostFactory::MakeHost();
|
||||
}
|
||||
|
||||
void NarratorNotifier::Announce(NarratorAnnouncement^ announcement)
|
||||
{
|
||||
if (NarratorAnnouncement::IsValid(announcement)
|
||||
&& m_announcementHost != nullptr)
|
||||
{
|
||||
m_announcementHost->Announce(announcement);
|
||||
}
|
||||
}
|
||||
|
||||
void NarratorNotifier::RegisterDependencyProperties()
|
||||
{
|
||||
s_announcementProperty = DependencyProperty::Register(
|
||||
L"Announcement", // The name of the dependency property.
|
||||
NarratorAnnouncement::typeid, // The type of the dependency property.
|
||||
NarratorNotifier::typeid, // The owner of the dependency property.
|
||||
ref new PropertyMetadata(
|
||||
nullptr, // Default value of the dependency property.
|
||||
ref new PropertyChangedCallback(OnAnnouncementChanged)));
|
||||
}
|
||||
|
||||
void NarratorNotifier::OnAnnouncementChanged(_In_ DependencyObject^ dependencyObject, _In_ DependencyPropertyChangedEventArgs^ e)
|
||||
{
|
||||
auto instance = safe_cast<NarratorNotifier^>(dependencyObject);
|
||||
if (instance != nullptr)
|
||||
{
|
||||
instance->Announce(safe_cast<NarratorAnnouncement^>(e->NewValue));
|
||||
}
|
||||
}
|
57
src/CalcViewModel/Common/Automation/NarratorNotifier.h
Normal file
57
src/CalcViewModel/Common/Automation/NarratorNotifier.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// Declaration of the NarratorNotifier class.
|
||||
|
||||
#pragma once
|
||||
#include "INarratorAnnouncementHost.h"
|
||||
|
||||
namespace CalculatorApp::Common::Automation
|
||||
{
|
||||
public ref class NarratorNotifier sealed : public Windows::UI::Xaml::DependencyObject
|
||||
{
|
||||
public:
|
||||
NarratorNotifier();
|
||||
|
||||
void Announce(NarratorAnnouncement^ announcement);
|
||||
|
||||
property NarratorAnnouncement^ Announcement
|
||||
{
|
||||
NarratorAnnouncement^ get() { return GetAnnouncement(this); }
|
||||
void set(NarratorAnnouncement^ value)
|
||||
{
|
||||
SetAnnouncement(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
static void RegisterDependencyProperties();
|
||||
|
||||
static property Windows::UI::Xaml::DependencyProperty^ AnnouncementProperty
|
||||
{
|
||||
Windows::UI::Xaml::DependencyProperty^ get()
|
||||
{
|
||||
return s_announcementProperty;
|
||||
}
|
||||
}
|
||||
|
||||
static NarratorAnnouncement^ GetAnnouncement(Windows::UI::Xaml::DependencyObject^ element)
|
||||
{
|
||||
return safe_cast<NarratorAnnouncement^>(element->GetValue(s_announcementProperty));
|
||||
}
|
||||
|
||||
static void SetAnnouncement(Windows::UI::Xaml::DependencyObject^ element, NarratorAnnouncement^ value)
|
||||
{
|
||||
element->SetValue(s_announcementProperty, value);
|
||||
}
|
||||
|
||||
private:
|
||||
static void OnAnnouncementChanged(
|
||||
_In_ Windows::UI::Xaml::DependencyObject^ dependencyObject,
|
||||
_In_ Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ eventArgs);
|
||||
|
||||
static Windows::UI::Xaml::DependencyProperty^ s_announcementProperty;
|
||||
|
||||
private:
|
||||
INarratorAnnouncementHost^ m_announcementHost;
|
||||
};
|
||||
}
|
99
src/CalcViewModel/Common/Automation/NotificationHost.cpp
Normal file
99
src/CalcViewModel/Common/Automation/NotificationHost.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "NotificationHost.h"
|
||||
|
||||
using namespace CalculatorApp::Common::Automation;
|
||||
using namespace Windows::Foundation::Metadata;
|
||||
using namespace Windows::UI::Xaml::Automation;
|
||||
using namespace Windows::UI::Xaml::Automation::Peers;
|
||||
using namespace Windows::UI::Xaml::Controls;
|
||||
|
||||
NotificationHost::NotificationHost() :
|
||||
m_host(nullptr)
|
||||
{}
|
||||
|
||||
bool NotificationHost::IsHostAvailable()
|
||||
{
|
||||
return ApiInformation::IsMethodPresent(
|
||||
L"Windows.UI.Xaml.Automation.Peers.AutomationPeer",
|
||||
L"RaiseNotificationEvent");
|
||||
}
|
||||
|
||||
INarratorAnnouncementHost^ NotificationHost::MakeHost()
|
||||
{
|
||||
return ref new NotificationHost();
|
||||
}
|
||||
|
||||
void NotificationHost::Announce(NarratorAnnouncement^ announcement)
|
||||
{
|
||||
if (m_host == nullptr)
|
||||
{
|
||||
m_host = ref new TextBlock();
|
||||
}
|
||||
|
||||
auto peer = FrameworkElementAutomationPeer::FromElement(m_host);
|
||||
if (peer != nullptr)
|
||||
{
|
||||
peer->RaiseNotificationEvent(
|
||||
GetWindowsNotificationKind(announcement->Kind),
|
||||
GetWindowsNotificationProcessing(announcement->Processing),
|
||||
announcement->Announcement,
|
||||
announcement->ActivityId);
|
||||
}
|
||||
}
|
||||
|
||||
StandardPeers::AutomationNotificationKind NotificationHost::GetWindowsNotificationKind(
|
||||
CustomPeers::AutomationNotificationKind customKindType)
|
||||
{
|
||||
switch (customKindType)
|
||||
{
|
||||
case CustomPeers::AutomationNotificationKind::ItemAdded:
|
||||
return StandardPeers::AutomationNotificationKind::ItemAdded;
|
||||
|
||||
case CustomPeers::AutomationNotificationKind::ItemRemoved:
|
||||
return StandardPeers::AutomationNotificationKind::ItemRemoved;
|
||||
|
||||
case CustomPeers::AutomationNotificationKind::ActionCompleted:
|
||||
return StandardPeers::AutomationNotificationKind::ActionCompleted;
|
||||
|
||||
case CustomPeers::AutomationNotificationKind::ActionAborted:
|
||||
return StandardPeers::AutomationNotificationKind::ActionAborted;
|
||||
|
||||
case CustomPeers::AutomationNotificationKind::Other:
|
||||
return StandardPeers::AutomationNotificationKind::Other;
|
||||
|
||||
default:
|
||||
assert(false && L"Unexpected AutomationNotificationKind");
|
||||
}
|
||||
|
||||
return StandardPeers::AutomationNotificationKind::Other;
|
||||
}
|
||||
|
||||
StandardPeers::AutomationNotificationProcessing NotificationHost::GetWindowsNotificationProcessing(
|
||||
CustomPeers::AutomationNotificationProcessing customProcessingType)
|
||||
{
|
||||
switch (customProcessingType)
|
||||
{
|
||||
case CustomPeers::AutomationNotificationProcessing::ImportantAll:
|
||||
return StandardPeers::AutomationNotificationProcessing::ImportantAll;
|
||||
|
||||
case CustomPeers::AutomationNotificationProcessing::ImportantMostRecent:
|
||||
return StandardPeers::AutomationNotificationProcessing::ImportantMostRecent;
|
||||
|
||||
case CustomPeers::AutomationNotificationProcessing::All:
|
||||
return StandardPeers::AutomationNotificationProcessing::All;
|
||||
|
||||
case CustomPeers::AutomationNotificationProcessing::MostRecent:
|
||||
return StandardPeers::AutomationNotificationProcessing::MostRecent;
|
||||
|
||||
case CustomPeers::AutomationNotificationProcessing::CurrentThenMostRecent:
|
||||
return StandardPeers::AutomationNotificationProcessing::CurrentThenMostRecent;
|
||||
|
||||
default:
|
||||
assert(false && L"Unexpected AutomationNotificationProcessing");
|
||||
}
|
||||
|
||||
return StandardPeers::AutomationNotificationProcessing::ImportantMostRecent;
|
||||
}
|
34
src/CalcViewModel/Common/Automation/NotificationHost.h
Normal file
34
src/CalcViewModel/Common/Automation/NotificationHost.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
#include "INarratorAnnouncementHost.h"
|
||||
|
||||
// Declaration of the NotificationHost class.
|
||||
// This class announces NarratorAnnouncements using the RaiseNotification API
|
||||
// available in RS3.
|
||||
|
||||
namespace CalculatorApp::Common::Automation
|
||||
{
|
||||
public ref class NotificationHost sealed : public INarratorAnnouncementHost
|
||||
{
|
||||
public:
|
||||
NotificationHost();
|
||||
|
||||
virtual bool IsHostAvailable();
|
||||
virtual INarratorAnnouncementHost^ MakeHost();
|
||||
|
||||
virtual void Announce(NarratorAnnouncement^ announcement);
|
||||
|
||||
private:
|
||||
static Windows::UI::Xaml::Automation::Peers::AutomationNotificationKind GetWindowsNotificationKind(
|
||||
CalculatorApp::Common::Automation::AutomationNotificationKind customKindType);
|
||||
|
||||
static Windows::UI::Xaml::Automation::Peers::AutomationNotificationProcessing GetWindowsNotificationProcessing(
|
||||
CalculatorApp::Common::Automation::AutomationNotificationProcessing customProcessingType);
|
||||
|
||||
private:
|
||||
Windows::UI::Xaml::UIElement^ m_host;
|
||||
};
|
||||
}
|
||||
|
34
src/CalcViewModel/Common/BindableBase.cpp
Normal file
34
src/CalcViewModel/Common/BindableBase.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "BindableBase.h"
|
||||
|
||||
using namespace CalculatorApp::Common;
|
||||
|
||||
using namespace Platform;
|
||||
using namespace Windows::UI::Xaml::Data;
|
||||
|
||||
/// <summary>
|
||||
/// Notifies listeners that a property value has changed.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property used to notify listeners.</param>
|
||||
void BindableBase::OnPropertyChanged(String^ propertyName)
|
||||
{
|
||||
PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::Data::ICustomProperty^ BindableBase::GetCustomProperty(Platform::String^ name)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::Data::ICustomProperty^ BindableBase::GetIndexedProperty(Platform::String^ name, Windows::UI::Xaml::Interop::TypeName type)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Platform::String^ BindableBase::GetStringRepresentation()
|
||||
{
|
||||
return this->ToString();
|
||||
}
|
35
src/CalcViewModel/Common/BindableBase.h
Normal file
35
src/CalcViewModel/Common/BindableBase.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="INotifyPropertyChanged"/> to simplify models.
|
||||
/// </summary>
|
||||
[Windows::Foundation::Metadata::WebHostHidden]
|
||||
public ref class BindableBase : Windows::UI::Xaml::DependencyObject, Windows::UI::Xaml::Data::INotifyPropertyChanged, Windows::UI::Xaml::Data::ICustomPropertyProvider
|
||||
{
|
||||
public:
|
||||
virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged;
|
||||
|
||||
public:
|
||||
// ICustomPropertyProvider
|
||||
virtual Windows::UI::Xaml::Data::ICustomProperty^ GetCustomProperty(Platform::String^ name);
|
||||
virtual Windows::UI::Xaml::Data::ICustomProperty^ GetIndexedProperty(Platform::String^ name, Windows::UI::Xaml::Interop::TypeName type);
|
||||
virtual Platform::String^ GetStringRepresentation();
|
||||
|
||||
property Windows::UI::Xaml::Interop::TypeName Type
|
||||
{
|
||||
virtual Windows::UI::Xaml::Interop::TypeName get() { return this->GetType(); }
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
virtual void OnPropertyChanged(Platform::String^ propertyName);
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "CalculatorButtonPressedEventArgs.h"
|
||||
|
||||
using namespace CalculatorApp;
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace Platform;
|
||||
|
||||
NumbersAndOperatorsEnum CalculatorButtonPressedEventArgs::GetOperationFromCommandParameter(_In_ Object^ commandParameter)
|
||||
{
|
||||
auto eventArgs = dynamic_cast<CalculatorButtonPressedEventArgs^>(commandParameter);
|
||||
if (eventArgs != nullptr)
|
||||
{
|
||||
return eventArgs->Operation;
|
||||
}
|
||||
else
|
||||
{
|
||||
return safe_cast<NumbersAndOperatorsEnum>(commandParameter);
|
||||
}
|
||||
}
|
||||
|
||||
String^ CalculatorButtonPressedEventArgs::GetAuditoryFeedbackFromCommandParameter(_In_ Object^ commandParameter)
|
||||
{
|
||||
auto eventArgs = dynamic_cast<CalculatorButtonPressedEventArgs^>(commandParameter);
|
||||
if (eventArgs != nullptr)
|
||||
{
|
||||
return eventArgs->AuditoryFeedback;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
24
src/CalcViewModel/Common/CalculatorButtonPressedEventArgs.h
Normal file
24
src/CalcViewModel/Common/CalculatorButtonPressedEventArgs.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
public ref class CalculatorButtonPressedEventArgs sealed
|
||||
{
|
||||
public:
|
||||
PROPERTY_R(Platform::String^, AuditoryFeedback);
|
||||
PROPERTY_R(CalculatorApp::NumbersAndOperatorsEnum, Operation);
|
||||
|
||||
CalculatorButtonPressedEventArgs(
|
||||
Platform::String^ feedback, CalculatorApp::NumbersAndOperatorsEnum operation) :
|
||||
m_AuditoryFeedback(feedback), m_Operation(operation) {}
|
||||
|
||||
static CalculatorApp::NumbersAndOperatorsEnum GetOperationFromCommandParameter(_In_ Platform::Object^ commandParameter);
|
||||
static Platform::String^ GetAuditoryFeedbackFromCommandParameter(_In_ Platform::Object^ commandParameter);
|
||||
};
|
||||
}
|
||||
}
|
211
src/CalcViewModel/Common/CalculatorButtonUser.h
Normal file
211
src/CalcViewModel/Common/CalculatorButtonUser.h
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace CM = CalculationManager;
|
||||
|
||||
public enum class NumbersAndOperatorsEnum
|
||||
{
|
||||
Zero = (int) CM::Command::Command0,
|
||||
One = (int) CM::Command::Command1,
|
||||
Two = (int) CM::Command::Command2,
|
||||
Three = (int) CM::Command::Command3,
|
||||
Four = (int) CM::Command::Command4,
|
||||
Five = (int) CM::Command::Command5,
|
||||
Six = (int) CM::Command::Command6,
|
||||
Seven = (int) CM::Command::Command7,
|
||||
Eight = (int) CM::Command::Command8,
|
||||
Nine = (int) CM::Command::Command9,
|
||||
Add = (int) CM::Command::CommandADD,
|
||||
Subtract = (int) CM::Command::CommandSUB,
|
||||
Multiply = (int) CM::Command::CommandMUL,
|
||||
Divide = (int) CM::Command::CommandDIV,
|
||||
Invert = (int) CM::Command::CommandREC,
|
||||
Equals = (int) CM::Command::CommandEQU,
|
||||
Decimal = (int) CM::Command::CommandPNT,
|
||||
Sqrt = (int) CM::Command::CommandSQRT,
|
||||
Percent = (int) CM::Command::CommandPERCENT,
|
||||
Negate = (int) CM::Command::CommandSIGN,
|
||||
Backspace = (int) CM::Command::CommandBACK,
|
||||
ClearEntry = (int) CM::Command::CommandCENTR,
|
||||
Clear = (int) CM::Command::CommandCLEAR,
|
||||
Degree = (int) CM::Command::CommandDEG,
|
||||
Radians = (int) CM::Command::CommandRAD,
|
||||
Grads = (int) CM::Command::CommandGRAD,
|
||||
Degrees = (int) CM::Command::CommandDegrees,
|
||||
OpenParenthesis = (int) CM::Command::CommandOPENP,
|
||||
CloseParenthesis = (int) CM::Command::CommandCLOSEP,
|
||||
Pi = (int) CM::Command::CommandPI,
|
||||
Sin = (int) CM::Command::CommandSIN,
|
||||
Cos = (int) CM::Command::CommandCOS,
|
||||
Tan = (int) CM::Command::CommandTAN,
|
||||
Factorial = (int) CM::Command::CommandFAC,
|
||||
XPower2 = (int) CM::Command::CommandSQR,
|
||||
Mod = (int) CM::Command::CommandMOD,
|
||||
FToE = (int) CM::Command::CommandFE,
|
||||
LogBaseE = (int) CM::Command::CommandLN,
|
||||
InvSin = (int) CM::Command::CommandASIN,
|
||||
InvCos = (int) CM::Command::CommandACOS,
|
||||
InvTan = (int) CM::Command::CommandATAN,
|
||||
LogBase10 = (int) CM::Command::CommandLOG,
|
||||
XPowerY = (int) CM::Command::CommandPWR,
|
||||
YRootX = (int) CM::Command::CommandROOT,
|
||||
TenPowerX = (int) CM::Command::CommandPOW10,
|
||||
EPowerX = (int) CM::Command::CommandPOWE,
|
||||
Exp = (int) CM::Command::CommandEXP,
|
||||
IsScientificMode = (int) CM::Command::ModeScientific,
|
||||
IsStandardMode = (int) CM::Command::ModeBasic,
|
||||
None = (int) CM::Command::CommandNULL,
|
||||
IsProgrammerMode = (int) CM::Command::ModeProgrammer,
|
||||
DecButton = (int) CM::Command::CommandDec,
|
||||
OctButton = (int) CM::Command::CommandOct,
|
||||
HexButton = (int) CM::Command::CommandHex,
|
||||
BinButton = (int) CM::Command::CommandBin,
|
||||
And = (int) CM::Command::CommandAnd,
|
||||
Ror = (int) CM::Command::CommandROR,
|
||||
Rol = (int) CM::Command::CommandROL,
|
||||
Or = (int) CM::Command::CommandOR,
|
||||
Lsh = (int) CM::Command::CommandLSHF,
|
||||
Rsh = (int) CM::Command::CommandRSHF,
|
||||
Xor = (int) CM::Command::CommandXor,
|
||||
Not = (int) CM::Command::CommandNot,
|
||||
A = (int) CM::Command::CommandA,
|
||||
B = (int) CM::Command::CommandB,
|
||||
C = (int) CM::Command::CommandC,
|
||||
D = (int) CM::Command::CommandD,
|
||||
E = (int) CM::Command::CommandE,
|
||||
F = (int) CM::Command::CommandF,
|
||||
Memory, // This is the memory button. Doesn't have a direct mapping to the CalcEngine.
|
||||
Sinh = (int) CM::Command::CommandSINH,
|
||||
Cosh = (int) CM::Command::CommandCOSH,
|
||||
Tanh = (int) CM::Command::CommandTANH,
|
||||
InvSinh = (int) CM::Command::CommandASINH,
|
||||
InvCosh = (int) CM::Command::CommandACOSH,
|
||||
InvTanh = (int) CM::Command::CommandATANH,
|
||||
Qword = (int) CM::Command::CommandQword,
|
||||
Dword = (int) CM::Command::CommandDword,
|
||||
Word = (int) CM::Command::CommandWord,
|
||||
Byte = (int) CM::Command::CommandByte,
|
||||
Cube = (int) CM::Command::CommandCUB,
|
||||
DMS = (int) CM::Command::CommandDMS,
|
||||
|
||||
BINSTART = (int) CM::Command::CommandBINEDITSTART,
|
||||
BINPOS0 = (int) CM::Command::CommandBINPOS0,
|
||||
BINPOS1 = (int) CM::Command::CommandBINPOS1,
|
||||
BINPOS2 = (int) CM::Command::CommandBINPOS2,
|
||||
BINPOS3 = (int) CM::Command::CommandBINPOS3,
|
||||
BINPOS4 = (int) CM::Command::CommandBINPOS4,
|
||||
BINPOS5 = (int) CM::Command::CommandBINPOS5,
|
||||
BINPOS6 = (int) CM::Command::CommandBINPOS6,
|
||||
BINPOS7 = (int) CM::Command::CommandBINPOS7,
|
||||
BINPOS8 = (int) CM::Command::CommandBINPOS8,
|
||||
BINPOS9 = (int) CM::Command::CommandBINPOS9,
|
||||
BINPOS10 = (int) CM::Command::CommandBINPOS10,
|
||||
BINPOS11 = (int) CM::Command::CommandBINPOS11,
|
||||
BINPOS12 = (int) CM::Command::CommandBINPOS12,
|
||||
BINPOS13 = (int) CM::Command::CommandBINPOS13,
|
||||
BINPOS14 = (int) CM::Command::CommandBINPOS14,
|
||||
BINPOS15 = (int) CM::Command::CommandBINPOS15,
|
||||
BINPOS16 = (int) CM::Command::CommandBINPOS16,
|
||||
BINPOS17 = (int) CM::Command::CommandBINPOS17,
|
||||
BINPOS18 = (int) CM::Command::CommandBINPOS18,
|
||||
BINPOS19 = (int) CM::Command::CommandBINPOS19,
|
||||
BINPOS20 = (int) CM::Command::CommandBINPOS20,
|
||||
BINPOS21 = (int) CM::Command::CommandBINPOS21,
|
||||
BINPOS22 = (int) CM::Command::CommandBINPOS22,
|
||||
BINPOS23 = (int) CM::Command::CommandBINPOS23,
|
||||
BINPOS24 = (int) CM::Command::CommandBINPOS24,
|
||||
BINPOS25 = (int) CM::Command::CommandBINPOS25,
|
||||
BINPOS26 = (int) CM::Command::CommandBINPOS26,
|
||||
BINPOS27 = (int) CM::Command::CommandBINPOS27,
|
||||
BINPOS28 = (int) CM::Command::CommandBINPOS28,
|
||||
BINPOS29 = (int) CM::Command::CommandBINPOS29,
|
||||
BINPOS30 = (int) CM::Command::CommandBINPOS30,
|
||||
BINPOS31 = (int) CM::Command::CommandBINPOS31,
|
||||
BINPOS32 = (int) CM::Command::CommandBINPOS32,
|
||||
BINPOS33 = (int) CM::Command::CommandBINPOS33,
|
||||
BINPOS34 = (int) CM::Command::CommandBINPOS34,
|
||||
BINPOS35 = (int) CM::Command::CommandBINPOS35,
|
||||
BINPOS36 = (int) CM::Command::CommandBINPOS36,
|
||||
BINPOS37 = (int) CM::Command::CommandBINPOS37,
|
||||
BINPOS38 = (int) CM::Command::CommandBINPOS38,
|
||||
BINPOS39 = (int) CM::Command::CommandBINPOS39,
|
||||
BINPOS40 = (int) CM::Command::CommandBINPOS40,
|
||||
BINPOS41 = (int) CM::Command::CommandBINPOS41,
|
||||
BINPOS42 = (int) CM::Command::CommandBINPOS42,
|
||||
BINPOS43 = (int) CM::Command::CommandBINPOS43,
|
||||
BINPOS44 = (int) CM::Command::CommandBINPOS44,
|
||||
BINPOS45 = (int) CM::Command::CommandBINPOS45,
|
||||
BINPOS46 = (int) CM::Command::CommandBINPOS46,
|
||||
BINPOS47 = (int) CM::Command::CommandBINPOS47,
|
||||
BINPOS48 = (int) CM::Command::CommandBINPOS48,
|
||||
BINPOS49 = (int) CM::Command::CommandBINPOS49,
|
||||
BINPOS50 = (int) CM::Command::CommandBINPOS50,
|
||||
BINPOS51 = (int) CM::Command::CommandBINPOS51,
|
||||
BINPOS52 = (int) CM::Command::CommandBINPOS52,
|
||||
BINPOS53 = (int) CM::Command::CommandBINPOS53,
|
||||
BINPOS54 = (int) CM::Command::CommandBINPOS54,
|
||||
BINPOS55 = (int) CM::Command::CommandBINPOS55,
|
||||
BINPOS56 = (int) CM::Command::CommandBINPOS56,
|
||||
BINPOS57 = (int) CM::Command::CommandBINPOS57,
|
||||
BINPOS58 = (int) CM::Command::CommandBINPOS58,
|
||||
BINPOS59 = (int) CM::Command::CommandBINPOS59,
|
||||
BINPOS60 = (int) CM::Command::CommandBINPOS60,
|
||||
BINPOS61 = (int) CM::Command::CommandBINPOS61,
|
||||
BINPOS62 = (int) CM::Command::CommandBINPOS62,
|
||||
BINPOS63 = (int) CM::Command::CommandBINPOS63,
|
||||
BINEND = (int) CM::Command::CommandBINEDITEND,
|
||||
Hyp = (int) CM::Command::CommandHYP
|
||||
};
|
||||
|
||||
// This contains list of functions whose usage we are tracelogging
|
||||
public enum class FunctionLogEnum
|
||||
{
|
||||
Invert = (int) CM::Command::CommandREC,
|
||||
Sqrt = (int) CM::Command::CommandSQRT,
|
||||
Percent = (int) CM::Command::CommandPERCENT,
|
||||
Negate = (int) CM::Command::CommandSIGN,
|
||||
Degrees = (int) CM::Command::CommandDegrees,
|
||||
Pi = (int) CM::Command::CommandPI,
|
||||
Sin = (int) CM::Command::CommandSIN,
|
||||
Cos = (int) CM::Command::CommandCOS,
|
||||
Tan = (int) CM::Command::CommandTAN,
|
||||
Factorial = (int) CM::Command::CommandFAC,
|
||||
XPower2 = (int) CM::Command::CommandSQR,
|
||||
Mod = (int) CM::Command::CommandMOD,
|
||||
FToE = (int) CM::Command::CommandFE,
|
||||
LogBaseE = (int) CM::Command::CommandLN,
|
||||
InvSin = (int) CM::Command::CommandASIN,
|
||||
InvCos = (int) CM::Command::CommandACOS,
|
||||
InvTan = (int) CM::Command::CommandATAN,
|
||||
LogBase10 = (int) CM::Command::CommandLOG,
|
||||
XPowerY = (int) CM::Command::CommandPWR,
|
||||
YRootX = (int) CM::Command::CommandROOT,
|
||||
TenPowerX = (int) CM::Command::CommandPOW10,
|
||||
EPowerX = (int) CM::Command::CommandPOWE,
|
||||
Exp = (int) CM::Command::CommandEXP,
|
||||
DecButton = (int) CM::Command::CommandDec,
|
||||
OctButton = (int) CM::Command::CommandOct,
|
||||
HexButton = (int) CM::Command::CommandHex,
|
||||
BinButton = (int) CM::Command::CommandBin,
|
||||
And = (int) CM::Command::CommandAnd,
|
||||
Ror = (int) CM::Command::CommandROR,
|
||||
Rol = (int) CM::Command::CommandROL,
|
||||
Or = (int) CM::Command::CommandOR,
|
||||
Lsh = (int) CM::Command::CommandLSHF,
|
||||
Rsh = (int) CM::Command::CommandRSHF,
|
||||
Xor = (int) CM::Command::CommandXor,
|
||||
Not = (int) CM::Command::CommandNot,
|
||||
Sinh = (int) CM::Command::CommandSINH,
|
||||
Cosh = (int) CM::Command::CommandCOSH,
|
||||
Tanh = (int) CM::Command::CommandTANH,
|
||||
InvSinh = (int) CM::Command::CommandASINH,
|
||||
InvCosh = (int) CM::Command::CommandACOSH,
|
||||
InvTanh = (int) CM::Command::CommandATANH,
|
||||
Cube = (int) CM::Command::CommandCUB,
|
||||
DMS = (int) CM::Command::CommandDMS,
|
||||
};
|
||||
}
|
134
src/CalcViewModel/Common/CalculatorDisplay.cpp
Normal file
134
src/CalcViewModel/Common/CalculatorDisplay.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// This class provides the concrete implemenation for the ICalcDisplay interface
|
||||
// that is declared in the Calculation Manager Library.
|
||||
#include "pch.h"
|
||||
#include "CalculatorDisplay.h"
|
||||
#include "StandardCalculatorViewModel.h"
|
||||
|
||||
using namespace CalculatorApp;
|
||||
using namespace CalculationManager;
|
||||
using namespace std;
|
||||
|
||||
CalculatorDisplay::CalculatorDisplay()
|
||||
{
|
||||
}
|
||||
|
||||
void CalculatorDisplay::SetCallback(Platform::WeakReference callbackReference)
|
||||
{
|
||||
m_callbackReference = callbackReference;
|
||||
}
|
||||
|
||||
void CalculatorDisplay::SetHistoryCallback(Platform::WeakReference callbackReference)
|
||||
{
|
||||
m_historyCallbackReference = callbackReference;
|
||||
}
|
||||
|
||||
void CalculatorDisplay::SetPrimaryDisplay(_In_ const wstring& displayStringValue, _In_ bool isError)
|
||||
{
|
||||
if (m_callbackReference)
|
||||
{
|
||||
auto calcVM = m_callbackReference.Resolve<ViewModel::StandardCalculatorViewModel>();
|
||||
if (calcVM)
|
||||
{
|
||||
calcVM->SetPrimaryDisplay(displayStringValue, isError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculatorDisplay::SetParenDisplayText(_In_ const std::wstring& parenthesisCount)
|
||||
{
|
||||
if (m_callbackReference != nullptr)
|
||||
{
|
||||
auto calcVM = m_callbackReference.Resolve<ViewModel::StandardCalculatorViewModel>();
|
||||
if (calcVM)
|
||||
{
|
||||
calcVM->SetParenthesisCount(parenthesisCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculatorDisplay::SetIsInError(bool isError)
|
||||
{
|
||||
if (m_callbackReference != nullptr)
|
||||
{
|
||||
auto calcVM = m_callbackReference.Resolve<ViewModel::StandardCalculatorViewModel>();
|
||||
if (calcVM)
|
||||
{
|
||||
calcVM->IsInError = isError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculatorDisplay::SetExpressionDisplay(_Inout_ std::shared_ptr<CalculatorVector<std::pair<std::wstring, int>>> const &tokens, _Inout_ std::shared_ptr<CalculatorVector <std::shared_ptr<IExpressionCommand>>> const &commands)
|
||||
{
|
||||
if (m_callbackReference != nullptr)
|
||||
{
|
||||
auto calcVM = m_callbackReference.Resolve<ViewModel::StandardCalculatorViewModel>();
|
||||
if (calcVM)
|
||||
{
|
||||
calcVM->SetExpressionDisplay(tokens, commands);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculatorDisplay::SetMemorizedNumbers(_In_ const vector<std::wstring>& newMemorizedNumbers)
|
||||
{
|
||||
if (m_callbackReference != nullptr)
|
||||
{
|
||||
auto calcVM = m_callbackReference.Resolve<ViewModel::StandardCalculatorViewModel>();
|
||||
if (calcVM)
|
||||
{
|
||||
calcVM->SetMemorizedNumbers(newMemorizedNumbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculatorDisplay::OnHistoryItemAdded(_In_ unsigned int addedItemIndex)
|
||||
{
|
||||
if (m_historyCallbackReference != nullptr)
|
||||
{
|
||||
auto historyVM = m_historyCallbackReference.Resolve<ViewModel::HistoryViewModel>();
|
||||
if (historyVM)
|
||||
{
|
||||
historyVM->OnHistoryItemAdded(addedItemIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculatorDisplay::MaxDigitsReached()
|
||||
{
|
||||
if (m_callbackReference != nullptr)
|
||||
{
|
||||
auto calcVM = m_callbackReference.Resolve<ViewModel::StandardCalculatorViewModel>();
|
||||
if (calcVM)
|
||||
{
|
||||
calcVM->OnMaxDigitsReached();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculatorDisplay::BinaryOperatorReceived()
|
||||
{
|
||||
if (m_callbackReference != nullptr)
|
||||
{
|
||||
auto calcVM = m_callbackReference.Resolve<ViewModel::StandardCalculatorViewModel>();
|
||||
if (calcVM)
|
||||
{
|
||||
calcVM->OnBinaryOperatorReceived();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculatorDisplay::MemoryItemChanged(unsigned int indexOfMemory)
|
||||
{
|
||||
if (m_callbackReference != nullptr)
|
||||
{
|
||||
auto calcVM = m_callbackReference.Resolve<ViewModel::StandardCalculatorViewModel>();
|
||||
if (calcVM)
|
||||
{
|
||||
calcVM->OnMemoryItemChanged(indexOfMemory);
|
||||
}
|
||||
}
|
||||
}
|
31
src/CalcViewModel/Common/CalculatorDisplay.h
Normal file
31
src/CalcViewModel/Common/CalculatorDisplay.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
// Callback interface to be implemented by the CalculatorManager
|
||||
class CalculatorDisplay: public ICalcDisplay
|
||||
{
|
||||
public:
|
||||
CalculatorDisplay();
|
||||
void SetCallback(Platform::WeakReference callbackReference);
|
||||
void SetHistoryCallback(Platform::WeakReference callbackReference);
|
||||
|
||||
private:
|
||||
void SetPrimaryDisplay(_In_ const std::wstring& displayString, _In_ bool isError) override;
|
||||
void SetIsInError(bool isError) override;
|
||||
void SetExpressionDisplay(_Inout_ std::shared_ptr<CalculatorVector<std::pair<std::wstring, int>>> const &tokens, _Inout_ std::shared_ptr<CalculatorVector<std::shared_ptr<IExpressionCommand>>> const &commands) override;
|
||||
void SetMemorizedNumbers(_In_ const std::vector<std::wstring>& memorizedNumbers) override;
|
||||
void OnHistoryItemAdded(_In_ unsigned int addedItemIndex) override;
|
||||
void SetParenDisplayText(_In_ const std::wstring& parenthesisCount) override;
|
||||
void MaxDigitsReached() override;
|
||||
void BinaryOperatorReceived() override;
|
||||
void MemoryItemChanged(unsigned int indexOfMemory) override;
|
||||
|
||||
private:
|
||||
Platform::WeakReference m_callbackReference;
|
||||
Platform::WeakReference m_historyCallbackReference;
|
||||
};
|
||||
}
|
40
src/CalcViewModel/Common/ConversionResultTaskHelper.cpp
Normal file
40
src/CalcViewModel/Common/ConversionResultTaskHelper.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ConversionResultTaskHelper.h"
|
||||
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace concurrency;
|
||||
using namespace std;
|
||||
|
||||
ConversionResultTaskHelper::ConversionResultTaskHelper(unsigned int delay, const function<void()> functionToRun) :
|
||||
m_delay{ delay },
|
||||
m_storedFunction{ functionToRun }
|
||||
{
|
||||
auto token = m_cts.get_token();
|
||||
auto delayTask = CompleteAfter(delay);
|
||||
delayTask.then([this, token]()
|
||||
{
|
||||
if (!token.is_canceled())
|
||||
{
|
||||
m_storedFunction();
|
||||
}
|
||||
}, task_continuation_context::use_current());
|
||||
}
|
||||
|
||||
ConversionResultTaskHelper::~ConversionResultTaskHelper()
|
||||
{
|
||||
m_cts.cancel();
|
||||
}
|
||||
|
||||
#pragma optimize("", off)
|
||||
// Creates a task that completes after the specified delay.
|
||||
//
|
||||
// Taken from: How to: Create a Task that Completes After a Delay
|
||||
// https://msdn.microsoft.com/en-us/library/hh873170.aspx
|
||||
task<void> ConversionResultTaskHelper::CompleteAfter(unsigned int timeout)
|
||||
{
|
||||
co_await winrt::resume_after(winrt::Windows::Foundation::TimeSpan{ std::chrono::duration<uint32_t, std::milli>(timeout) });
|
||||
};
|
||||
#pragma optimize("", on)
|
24
src/CalcViewModel/Common/ConversionResultTaskHelper.h
Normal file
24
src/CalcViewModel/Common/ConversionResultTaskHelper.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
class ConversionResultTaskHelper
|
||||
{
|
||||
public:
|
||||
ConversionResultTaskHelper(unsigned int delay, const std::function<void()> functionToRun);
|
||||
~ConversionResultTaskHelper();
|
||||
|
||||
private:
|
||||
concurrency::task<void> CompleteAfter(unsigned int timeout);
|
||||
|
||||
unsigned int m_delay;
|
||||
concurrency::cancellation_token_source m_cts;
|
||||
const std::function<void()> m_storedFunction;
|
||||
};
|
||||
}
|
||||
}
|
569
src/CalcViewModel/Common/CopyPasteManager.cpp
Normal file
569
src/CalcViewModel/Common/CopyPasteManager.cpp
Normal file
@@ -0,0 +1,569 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "CopyPasteManager.h"
|
||||
#include "Common\LocalizationSettings.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace concurrency;
|
||||
using namespace CalculatorApp;
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace Platform;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::System;
|
||||
using namespace Windows::ApplicationModel::DataTransfer;
|
||||
|
||||
size_t maxOperandLength;
|
||||
unsigned long long maxOperandNumber;
|
||||
|
||||
String^ CopyPasteManager::supportedFormats[] =
|
||||
{
|
||||
StandardDataFormats::Text
|
||||
};
|
||||
|
||||
constexpr wstring_view c_validCharacterSet{ L"0123456789()+-*/.abcdefABCDEF" };
|
||||
// [\s\x85] means white-space characters
|
||||
static const wstring c_wspc = L"[\\s\\x85]*";
|
||||
static const wstring c_wspcLParens = c_wspc + L"[(]*" + c_wspc;
|
||||
static const wstring c_wspcRParens = c_wspc + L"[)]*" + c_wspc;
|
||||
static const wstring c_signedDecFloat = L"[-+]?\\d*(\\d|[.])\\d*";
|
||||
|
||||
// Programmer Mode Integer patterns
|
||||
// Support digit separators ` (WinDbg/MASM), ' (C++), and _ (C# and other languages)
|
||||
static const wstring c_hexProgrammerChars = L"([a-f]|[A-F]|\\d)+((_|'|`)([a-f]|[A-F]|\\d)+)*";
|
||||
static const wstring c_decProgrammerChars = L"\\d+((_|'|`)\\d+)*";
|
||||
static const wstring c_octProgrammerChars = L"[0-7]+((_|'|`)[0-7]+)*";
|
||||
static const wstring c_binProgrammerChars = L"[0-1]+((_|'|`)[0-1]+)*";
|
||||
static const wstring c_uIntSuffixes = L"[uU]?[lL]{0,2}";
|
||||
|
||||
// RegEx Patterns used by various modes
|
||||
static const array<wregex, 1> standardModePatterns =
|
||||
{
|
||||
wregex(c_wspc + c_signedDecFloat + c_wspc)
|
||||
};
|
||||
static const array<wregex, 2> scientificModePatterns =
|
||||
{
|
||||
wregex(c_wspcLParens + c_signedDecFloat + c_wspcRParens),
|
||||
wregex(c_wspcLParens + c_signedDecFloat + L"[e]([+]|[-])+\\d+" + c_wspcRParens)
|
||||
};
|
||||
static const array<array<wregex, 5>, 4> programmerModePatterns =
|
||||
{ {
|
||||
// Hex numbers like 5F, 4A0C, 0xa9, 0xFFull, 47CDh
|
||||
{
|
||||
wregex(c_wspcLParens + L"(0[xX])?" + c_hexProgrammerChars + c_uIntSuffixes + c_wspcRParens),
|
||||
wregex(c_wspcLParens + c_hexProgrammerChars + L"[hH]?" + c_wspcRParens)
|
||||
},
|
||||
// Decimal numbers like -145, 145, 0n145, 123ull etc
|
||||
{
|
||||
wregex(c_wspcLParens + L"[-+]?" + c_decProgrammerChars + L"[lL]{0,2}" +c_wspcRParens),
|
||||
wregex(c_wspcLParens + L"(0[nN])?" + c_decProgrammerChars + c_uIntSuffixes + c_wspcRParens)
|
||||
},
|
||||
// Octal numbers like 06, 010, 0t77, 0o77, 077ull etc
|
||||
{
|
||||
wregex(c_wspcLParens + L"(0[otOT])?" + c_octProgrammerChars + c_uIntSuffixes + c_wspcRParens)
|
||||
},
|
||||
// Binary numbers like 011010110, 0010110, 10101001, 1001b, 0b1001, 0y1001, 0b1001ull
|
||||
{
|
||||
wregex(c_wspcLParens + L"(0[byBY])?" + c_binProgrammerChars + c_uIntSuffixes + c_wspcRParens),
|
||||
wregex(c_wspcLParens + c_binProgrammerChars + L"[bB]?" + c_wspcRParens)
|
||||
}
|
||||
} };
|
||||
static const array<wregex, 1> unitConverterPatterns =
|
||||
{
|
||||
wregex(c_wspc + L"[-+]?\\d*[.]?\\d*" + c_wspc)
|
||||
};
|
||||
|
||||
void CopyPasteManager::CopyToClipboard(String^ stringToCopy)
|
||||
{
|
||||
// Copy the string to the clipboard
|
||||
auto dataPackage = ref new DataPackage();
|
||||
dataPackage->SetText(stringToCopy);
|
||||
Clipboard::SetContent(dataPackage);
|
||||
}
|
||||
|
||||
task<String^> CopyPasteManager::GetStringToPaste(ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, int bitLengthType)
|
||||
{
|
||||
// Retrieve the text in the clipboard
|
||||
auto dataPackageView = Clipboard::GetContent();
|
||||
|
||||
// TODO: Suport all formats supported by ClipboardHasText
|
||||
//-- add support to avoid pasting of expressions like 12 34(as of now we allow 1234)
|
||||
//-- add support to allow pasting for expressions like .2 , -.2
|
||||
//-- add support to allow pasting for expressions like 1.3e12(as of now we allow 1.3e+12)
|
||||
|
||||
return create_task((dataPackageView->GetTextAsync(::StandardDataFormats::Text)))
|
||||
.then([mode, modeType, programmerNumberBase, bitLengthType](String^ pastedText)
|
||||
{
|
||||
return ValidatePasteExpression(pastedText, mode, modeType, programmerNumberBase, bitLengthType);
|
||||
}
|
||||
, task_continuation_context::use_arbitrary());
|
||||
}
|
||||
|
||||
int CopyPasteManager::ClipboardTextFormat()
|
||||
{
|
||||
int result = -1;
|
||||
|
||||
auto dataPackageView = Clipboard::GetContent();
|
||||
|
||||
for (int i = 0; i < RTL_NUMBER_OF(supportedFormats); i++)
|
||||
{
|
||||
if (dataPackageView->Contains(supportedFormats[i]))
|
||||
{
|
||||
result = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String^ CopyPasteManager::ValidatePasteExpression(String^ pastedText, ViewMode mode, int programmerNumberBase, int bitLengthType)
|
||||
{
|
||||
return CopyPasteManager::ValidatePasteExpression(pastedText, mode, NavCategory::GetGroupType(mode), programmerNumberBase, bitLengthType);
|
||||
}
|
||||
|
||||
// return "NoOp" if pastedText is invalid else return pastedText
|
||||
|
||||
String^ CopyPasteManager::ValidatePasteExpression(String^ pastedText, ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, int bitLengthType)
|
||||
{
|
||||
if (pastedText->Length() > MaxPasteableLength)
|
||||
{
|
||||
// return NoOp to indicate don't paste anything.
|
||||
TraceLogger::GetInstance().LogInvalidInputPasted(L"PastedExpressionSizeGreaterThanMaxAllowed", L"MoreThanMaxInput", mode, programmerNumberBase, bitLengthType);
|
||||
return StringReference(PasteErrorString);
|
||||
}
|
||||
|
||||
wstring pasteExpression = pastedText->Data();
|
||||
|
||||
// Get english translated expression
|
||||
String^ englishString = LocalizationSettings::GetInstance().GetEnglishValueFromLocalizedDigits(pasteExpression);
|
||||
|
||||
// Removing the spaces, comma separator from the pasteExpression to allow pasting of expressions like 1 + 2+1,333
|
||||
pasteExpression = Utils::RemoveUnwantedCharsFromWstring(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'=')
|
||||
{
|
||||
pasteExpression = pasteExpression.substr(0, pasteExpression.length() - 1);
|
||||
}
|
||||
|
||||
// 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, programmerNumberBase, bitLengthType);
|
||||
if (operands.empty())
|
||||
{
|
||||
// return NoOp to indicate don't paste anything.
|
||||
return StringReference(PasteErrorString);
|
||||
}
|
||||
|
||||
if (modeType == CategoryGroupType::Converter)
|
||||
{
|
||||
operands = { pasteExpression };
|
||||
}
|
||||
|
||||
// validate each operand with patterns for different modes
|
||||
if (!ExpressionRegExMatch(operands, mode, modeType, programmerNumberBase, bitLengthType))
|
||||
{
|
||||
TraceLogger::GetInstance().LogInvalidInputPasted(L"InvalidExpressionForPresentMode", pastedText->Data(), mode, programmerNumberBase, bitLengthType);
|
||||
return StringReference(PasteErrorString);
|
||||
}
|
||||
|
||||
return ref new String(pastedText->Data());
|
||||
}
|
||||
|
||||
vector<wstring> CopyPasteManager::ExtractOperands(const wstring& pasteExpression, ViewMode mode, int programmerNumberBase, int bitLengthType)
|
||||
{
|
||||
vector<wstring> operands{};
|
||||
size_t lastIndex = 0;
|
||||
bool haveOperator = false;
|
||||
bool startExpCounting = false;
|
||||
bool startOfExpression = true;
|
||||
bool isPreviousOpenParen = false;
|
||||
bool isPreviousOperator = false;
|
||||
|
||||
// This will have the exponent length
|
||||
size_t expLength = 0;
|
||||
for (size_t i = 0; i < pasteExpression.length(); i++)
|
||||
{
|
||||
// if the current character is not a valid one don't process it
|
||||
if (c_validCharacterSet.find(pasteExpression.at(i)) == wstring_view::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operands.size() >= MaxOperandCount)
|
||||
{
|
||||
TraceLogger::GetInstance().LogInvalidInputPasted(L"OperandCountGreaterThanMaxCount", pasteExpression.c_str(), mode, programmerNumberBase, bitLengthType);
|
||||
operands.clear();
|
||||
return operands;
|
||||
}
|
||||
|
||||
if (startExpCounting)
|
||||
{
|
||||
if ((pasteExpression.at(i) >= L'0') && (pasteExpression.at(i) <= L'9'))
|
||||
{
|
||||
expLength++;
|
||||
|
||||
// to disallow pasting of 1e+12345 as 1e+1234, max exponent that can be pasted is 9999.
|
||||
if (expLength > MaxExponentLength)
|
||||
{
|
||||
TraceLogger::GetInstance().LogInvalidInputPasted(L"ExponentLengthGreaterThanMaxLength", pasteExpression.c_str(), mode, programmerNumberBase, bitLengthType);
|
||||
operands.clear();
|
||||
return operands;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((mode != ViewMode::Programmer) && (pasteExpression.at(i) == L'e'))
|
||||
{
|
||||
startExpCounting = true;
|
||||
}
|
||||
|
||||
if (((pasteExpression.at(i) == L'+') || (pasteExpression.at(i) == L'-') || (pasteExpression.at(i) == L'*') || (pasteExpression.at(i) == L'/')))
|
||||
{
|
||||
if ((pasteExpression.at(i) == L'+') || (pasteExpression.at(i) == L'-'))
|
||||
{
|
||||
// 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'))))
|
||||
{
|
||||
isPreviousOperator = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
startExpCounting = false;
|
||||
expLength = 0;
|
||||
haveOperator = true;
|
||||
isPreviousOperator = true;
|
||||
operands.push_back(pasteExpression.substr(lastIndex, i - lastIndex));
|
||||
lastIndex = i + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
isPreviousOperator = false;
|
||||
}
|
||||
|
||||
isPreviousOpenParen = (pasteExpression.at(i) == L'(');
|
||||
startOfExpression = false;
|
||||
}
|
||||
|
||||
if (!haveOperator)
|
||||
{
|
||||
operands.clear();
|
||||
operands.push_back(pasteExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
operands.push_back(pasteExpression.substr(lastIndex, pasteExpression.length() - 1));
|
||||
}
|
||||
|
||||
return operands;
|
||||
}
|
||||
|
||||
bool CopyPasteManager::ExpressionRegExMatch(vector<wstring> operands, ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, int bitLengthType)
|
||||
{
|
||||
if (operands.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool expMatched = true;
|
||||
vector<wregex> patterns{};
|
||||
|
||||
pair<size_t, uint64_t> operandLimits = GetMaxOperandLengthAndValue(mode, modeType, programmerNumberBase, bitLengthType);
|
||||
size_t maxOperandLength = operandLimits.first;
|
||||
uint64_t maxOperandValue = operandLimits.second;
|
||||
|
||||
if (mode == ViewMode::Standard)
|
||||
{
|
||||
patterns.assign(standardModePatterns.begin(), standardModePatterns.end());
|
||||
}
|
||||
else if (mode == ViewMode::Scientific)
|
||||
{
|
||||
patterns.assign(scientificModePatterns.begin(), scientificModePatterns.end());
|
||||
}
|
||||
else if (mode == ViewMode::Programmer)
|
||||
{
|
||||
patterns.assign(programmerModePatterns[programmerNumberBase - HexBase].begin(), programmerModePatterns[programmerNumberBase - HexBase].end());
|
||||
}
|
||||
else if (modeType == CategoryGroupType::Converter)
|
||||
{
|
||||
patterns.assign(unitConverterPatterns.begin(), unitConverterPatterns.end());
|
||||
}
|
||||
|
||||
for (const wstring& operand : operands)
|
||||
{
|
||||
// Each operand only needs to match one of the available patterns.
|
||||
bool operandMatched = false;
|
||||
for (const wregex& pattern : patterns)
|
||||
{
|
||||
operandMatched = operandMatched || regex_match(operand, 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.
|
||||
wstring operandValue = SanitizeOperand(operand);
|
||||
|
||||
// If an operand exceeds the maximum length allowed, break and return.
|
||||
if (OperandLength(operandValue, mode, modeType, programmerNumberBase) > maxOperandLength)
|
||||
{
|
||||
expMatched = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// If maxOperandValue is set and the operandValue exceeds it, break and return.
|
||||
if (maxOperandValue != 0)
|
||||
{
|
||||
unsigned long long int operandAsULL = 0;
|
||||
if (!TryOperandToULL(operandValue, programmerNumberBase, operandAsULL))
|
||||
{
|
||||
// Operand was empty, received invalid_argument, or received out_of_range. Input is invalid.
|
||||
expMatched = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (operandAsULL > maxOperandValue)
|
||||
{
|
||||
expMatched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expMatched = expMatched && operandMatched;
|
||||
}
|
||||
|
||||
return expMatched;
|
||||
}
|
||||
|
||||
pair<size_t, uint64_t> CopyPasteManager::GetMaxOperandLengthAndValue(ViewMode mode, CategoryGroupType modeType, int programmerNumberBase, int bitLengthType)
|
||||
{
|
||||
size_t maxLength = 0;
|
||||
uint64_t maxValue = 0;
|
||||
|
||||
if (mode == ViewMode::Standard)
|
||||
{
|
||||
maxLength = MaxStandardOperandLength;
|
||||
}
|
||||
else if (mode == ViewMode::Scientific)
|
||||
{
|
||||
maxLength = MaxScientificOperandLength;
|
||||
}
|
||||
else if (mode == ViewMode::Programmer)
|
||||
{
|
||||
unsigned int bitLength = 0;
|
||||
switch (bitLengthType)
|
||||
{
|
||||
case QwordType:
|
||||
bitLength = 64;
|
||||
break;
|
||||
case DwordType:
|
||||
bitLength = 32;
|
||||
break;
|
||||
case WordType:
|
||||
bitLength = 16;
|
||||
break;
|
||||
case ByteType:
|
||||
bitLength = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
double bitsPerDigit = 0;
|
||||
switch (programmerNumberBase)
|
||||
{
|
||||
case BinBase:
|
||||
bitsPerDigit = log2(2);
|
||||
break;
|
||||
case OctBase:
|
||||
bitsPerDigit = log2(8);
|
||||
break;
|
||||
case DecBase:
|
||||
bitsPerDigit = log2(10);
|
||||
break;
|
||||
case HexBase:
|
||||
bitsPerDigit = log2(16);
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned int signBit = (programmerNumberBase == DecBase) ? 1 : 0;
|
||||
|
||||
maxLength = (size_t)ceil((bitLength - signBit) / bitsPerDigit);
|
||||
maxValue = UINT64_MAX >> (MaxProgrammerBitLength - (bitLength - signBit));
|
||||
}
|
||||
else if (modeType == CategoryGroupType::Converter)
|
||||
{
|
||||
maxLength = MaxConverterInputLength;
|
||||
}
|
||||
|
||||
return make_pair(maxLength, maxValue);
|
||||
}
|
||||
|
||||
wstring CopyPasteManager::SanitizeOperand(const wstring& operand)
|
||||
{
|
||||
wchar_t unWantedChars[] = { L'\'', L'_', L'`', L'(', L')', L'-' };
|
||||
|
||||
return Utils::RemoveUnwantedCharsFromWstring(operand, unWantedChars, ARRAYSIZE(unWantedChars));
|
||||
}
|
||||
|
||||
bool CopyPasteManager::TryOperandToULL(const wstring& operand, int numberBase, unsigned long long int& result)
|
||||
{
|
||||
result = 0;
|
||||
|
||||
if (operand.length() == 0 || operand.front() == L'-')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default to base10
|
||||
int intBase = 10;
|
||||
switch (numberBase)
|
||||
{
|
||||
case HexBase:
|
||||
intBase = 16;
|
||||
break;
|
||||
case OctBase:
|
||||
intBase = 8;
|
||||
break;
|
||||
case BinBase:
|
||||
intBase = 2;
|
||||
break;
|
||||
case DecBase:
|
||||
intBase = 10;
|
||||
break;
|
||||
}
|
||||
|
||||
wstring::size_type size = 0;
|
||||
try
|
||||
{
|
||||
result = stoull(operand, &size, intBase);
|
||||
return true;
|
||||
}
|
||||
catch (invalid_argument)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
catch (out_of_range)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t CopyPasteManager::OperandLength(wstring operand, ViewMode mode, CategoryGroupType modeType, int programmerNumberBase)
|
||||
{
|
||||
size_t len = 0;
|
||||
if (mode == ViewMode::Standard || mode == ViewMode::Scientific)
|
||||
{
|
||||
len = StandardScientificOperandLength(operand);
|
||||
}
|
||||
else if (mode == ViewMode::Programmer)
|
||||
{
|
||||
len = ProgrammerOperandLength(operand, programmerNumberBase);
|
||||
}
|
||||
else if (modeType == CategoryGroupType::Converter)
|
||||
{
|
||||
len = operand.length();
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t CopyPasteManager::StandardScientificOperandLength(wstring operand)
|
||||
{
|
||||
bool hasDecimal = false;
|
||||
for (size_t i = 0; i < operand.length(); i++)
|
||||
{
|
||||
if (operand[i] == L'.')
|
||||
{
|
||||
hasDecimal = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDecimal)
|
||||
{
|
||||
if (operand.length() >= 2)
|
||||
{
|
||||
if ((operand[0] == L'0') && (operand[1] == L'.'))
|
||||
{
|
||||
return operand.length() - 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return operand.length() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return operand.length();
|
||||
}
|
||||
|
||||
size_t CopyPasteManager::ProgrammerOperandLength(const wstring& operand, int numberBase)
|
||||
{
|
||||
size_t len = operand.length();
|
||||
|
||||
vector<wstring> prefixes{};
|
||||
vector<wstring> suffixes{};
|
||||
switch (numberBase)
|
||||
{
|
||||
case BinBase:
|
||||
prefixes = { L"0B", L"0Y" };
|
||||
suffixes = { L"B" };
|
||||
break;
|
||||
case DecBase:
|
||||
prefixes = { L"-", L"0N" };
|
||||
break;
|
||||
case OctBase:
|
||||
prefixes = { L"0T", L"0O" };
|
||||
break;
|
||||
case HexBase:
|
||||
prefixes = { L"0X" };
|
||||
suffixes = { L"H" };
|
||||
break;
|
||||
default:
|
||||
// No defined prefixes/suffixes
|
||||
break;
|
||||
}
|
||||
|
||||
// UInt suffixes are common across all modes
|
||||
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;
|
||||
transform(operandUpper.begin(), operandUpper.end(), operandUpper.begin(), toupper);
|
||||
|
||||
// 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).
|
||||
for (const wstring& suffix : suffixes)
|
||||
{
|
||||
if (len < suffix.length())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operandUpper.compare(operandUpper.length() - suffix.length(), suffix.length(), suffix) == 0)
|
||||
{
|
||||
len -= suffix.length();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect if there is a prefix and subtract its length
|
||||
for (const wstring& prefix : prefixes)
|
||||
{
|
||||
if (len < prefix.length())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (operandUpper.compare(0, prefix.length(), prefix) == 0)
|
||||
{
|
||||
len -= prefix.length();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
72
src/CalcViewModel/Common/CopyPasteManager.h
Normal file
72
src/CalcViewModel/Common/CopyPasteManager.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
#include "AppResourceProvider.h"
|
||||
|
||||
namespace CalculatorUnitTests
|
||||
{
|
||||
class CopyPasteManagerTest;
|
||||
}
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
|
||||
#define QwordType 1
|
||||
#define DwordType 2
|
||||
#define WordType 3
|
||||
#define ByteType 4
|
||||
#define HexBase 5
|
||||
#define DecBase 6
|
||||
#define OctBase 7
|
||||
#define BinBase 8
|
||||
|
||||
class CopyPasteManager
|
||||
{
|
||||
public:
|
||||
static void CopyToClipboard(Platform::String^ stringToCopy);
|
||||
static concurrency::task<Platform::String^> GetStringToPaste(CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType, int programmerNumberBase = -1, int bitLengthType = -1);
|
||||
static bool HasStringToPaste()
|
||||
{
|
||||
return ClipboardTextFormat() >= 0;
|
||||
}
|
||||
|
||||
static constexpr auto PasteErrorString = L"NoOp";
|
||||
|
||||
private:
|
||||
static int ClipboardTextFormat();
|
||||
static Platform::String^ ValidatePasteExpression(
|
||||
Platform::String^ pastedText,
|
||||
CalculatorApp::Common::ViewMode mode,
|
||||
int programmerNumberBase,
|
||||
int bitLengthType);
|
||||
static Platform::String^ ValidatePasteExpression(
|
||||
Platform::String^ pastedText,
|
||||
CalculatorApp::Common::ViewMode mode,
|
||||
CalculatorApp::Common::CategoryGroupType modeType,
|
||||
int programmerNumberBase,
|
||||
int bitLengthType);
|
||||
|
||||
static std::vector<std::wstring> ExtractOperands(const std::wstring& pasteExpression, CalculatorApp::Common::ViewMode mode, int programmerNumberBase = -1, int bitLengthType = -1);
|
||||
static bool ExpressionRegExMatch(std::vector<std::wstring> operands, CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType, int programmerNumberBase = -1, int bitLengthType = -1);
|
||||
|
||||
static std::pair<size_t, uint64_t> GetMaxOperandLengthAndValue(CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType, int programmerNumberBase = -1, int bitLengthType = -1);
|
||||
static std::wstring SanitizeOperand(const std::wstring& operand);
|
||||
static bool TryOperandToULL(const std::wstring& operand, int numberBase, unsigned long long int& result);
|
||||
static size_t OperandLength(std::wstring operand, CalculatorApp::Common::ViewMode mode, CalculatorApp::Common::CategoryGroupType modeType, int programmerNumberBase = -1);
|
||||
static size_t StandardScientificOperandLength(std::wstring operand);
|
||||
static size_t ProgrammerOperandLength(const std::wstring& operand, int numberBase);
|
||||
|
||||
static constexpr size_t MaxStandardOperandLength = 16;
|
||||
static constexpr size_t MaxScientificOperandLength = 32;
|
||||
static constexpr size_t MaxConverterInputLength = 16;
|
||||
static constexpr size_t MaxOperandCount = 100;
|
||||
static constexpr size_t MaxPasteableLength = 512;
|
||||
static constexpr size_t MaxExponentLength = 4;
|
||||
static constexpr size_t MaxProgrammerBitLength = 64;
|
||||
|
||||
static Platform::String^ supportedFormats[];
|
||||
|
||||
friend class CalculatorUnitTests::CopyPasteManagerTest;
|
||||
};
|
||||
}
|
292
src/CalcViewModel/Common/DateCalculator.cpp
Normal file
292
src/CalcViewModel/Common/DateCalculator.cpp
Normal file
@@ -0,0 +1,292 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "DateCalculator.h"
|
||||
|
||||
using namespace Platform;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Globalization;
|
||||
using namespace CalculatorApp::Common::DateCalculation;
|
||||
|
||||
DateCalculationEngine::DateCalculationEngine(_In_ String^ calendarIdentifier)
|
||||
{
|
||||
m_calendar = ref new Calendar();
|
||||
m_calendar->ChangeCalendarSystem(calendarIdentifier);
|
||||
}
|
||||
|
||||
// Adding Duration to a Date
|
||||
// Returns: True if function succeeds to calculate the date else returns False
|
||||
bool DateCalculationEngine::AddDuration(_In_ DateTime startDate, _In_ const DateDifference& duration, _Out_ DateTime *endDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_calendar->SetDateTime(startDate);
|
||||
|
||||
if (duration.year != 0)
|
||||
{
|
||||
m_calendar->AddYears(duration.year);
|
||||
}
|
||||
if (duration.month != 0)
|
||||
{
|
||||
m_calendar->AddMonths(duration.month);
|
||||
}
|
||||
if (duration.day != 0)
|
||||
{
|
||||
m_calendar->AddDays(duration.day);
|
||||
}
|
||||
|
||||
*endDate = m_calendar->GetDateTime();
|
||||
}
|
||||
catch (Platform::InvalidArgumentException^ ex)
|
||||
{
|
||||
// Do nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Subtracting Duration from a Date
|
||||
// Returns: True if function succeeds to calculate the date else returns False
|
||||
bool DateCalculationEngine::SubtractDuration(_In_ DateTime startDate, _In_ const DateDifference& duration, _Out_ DateTime *endDate)
|
||||
{
|
||||
// For Subtract the Algorithm is different than Add. Here the smaller units are subtracted first
|
||||
// and then the larger units.
|
||||
try
|
||||
{
|
||||
m_calendar->SetDateTime(startDate);
|
||||
|
||||
if (duration.day != 0)
|
||||
{
|
||||
m_calendar->AddDays(-duration.day);
|
||||
}
|
||||
if (duration.month != 0)
|
||||
{
|
||||
m_calendar->AddMonths(-duration.month);
|
||||
}
|
||||
if (duration.year != 0)
|
||||
{
|
||||
m_calendar->AddYears(-duration.year);
|
||||
}
|
||||
|
||||
*endDate = m_calendar->GetDateTime();
|
||||
}
|
||||
catch (Platform::InvalidArgumentException^ ex)
|
||||
{
|
||||
// Do nothing
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the UniversalTime value is not negative
|
||||
return (endDate->UniversalTime >= 0);
|
||||
}
|
||||
|
||||
// Calculate the difference between two dates
|
||||
void DateCalculationEngine::GetDateDifference(_In_ DateTime date1, _In_ DateTime date2, _In_ DateUnit outputFormat, _Out_ DateDifference *difference)
|
||||
{
|
||||
DateTime startDate;
|
||||
DateTime endDate;
|
||||
DateTime pivotDate;
|
||||
DateTime tempPivotDate;
|
||||
UINT daysDiff = 0;
|
||||
UINT differenceInDates[c_unitsOfDate] = { 0 };
|
||||
|
||||
if (date1.UniversalTime < date2.UniversalTime)
|
||||
{
|
||||
startDate = date1;
|
||||
endDate = date2;
|
||||
}
|
||||
else
|
||||
{
|
||||
startDate = date2;
|
||||
endDate = date1;
|
||||
}
|
||||
|
||||
pivotDate = startDate;
|
||||
|
||||
daysDiff = GetDifferenceInDays(startDate, endDate);
|
||||
|
||||
// If output has units other than days
|
||||
// 0th bit: Year, 1st bit: Month, 2nd bit: Week, 3rd bit: Day
|
||||
if (static_cast<int>(outputFormat) & 7)
|
||||
{
|
||||
UINT daysInMonth;
|
||||
UINT approximateDaysInYear;
|
||||
|
||||
// If we're unable to calculate the days-in-month or days-in-year, we'll leave the values at 0.
|
||||
if (TryGetCalendarDaysInMonth(startDate, daysInMonth)
|
||||
&& TryGetCalendarDaysInYear(endDate, approximateDaysInYear))
|
||||
{
|
||||
UINT daysIn[c_unitsOfDate] = { approximateDaysInYear, daysInMonth, c_daysInWeek, 1 };
|
||||
|
||||
for (int unitIndex = 0; unitIndex < c_unitsGreaterThanDays; unitIndex++)
|
||||
{
|
||||
tempPivotDate = pivotDate;
|
||||
|
||||
// Check if the bit flag is set for the date unit
|
||||
DateUnit dateUnit = static_cast<DateUnit>(1 << unitIndex);
|
||||
|
||||
if (static_cast<int>(outputFormat & dateUnit))
|
||||
{
|
||||
bool isEndDateHit = false;
|
||||
differenceInDates[unitIndex] = (daysDiff / daysIn[unitIndex]);
|
||||
|
||||
if (differenceInDates[unitIndex] != 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
pivotDate = AdjustCalendarDate(pivotDate, dateUnit, static_cast<int>(differenceInDates[unitIndex]));
|
||||
}
|
||||
catch (Platform::InvalidArgumentException^)
|
||||
{
|
||||
// Operation failed due to out of bound result
|
||||
// Do nothing
|
||||
differenceInDates[unitIndex] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int tempDaysDiff;
|
||||
|
||||
do
|
||||
{
|
||||
tempDaysDiff = GetDifferenceInDays(pivotDate, endDate);
|
||||
|
||||
if (tempDaysDiff < 0)
|
||||
{
|
||||
// pivotDate has gone over the end date; start from the begining of this unit
|
||||
differenceInDates[unitIndex] -= 1;
|
||||
pivotDate = tempPivotDate;
|
||||
pivotDate = AdjustCalendarDate(pivotDate, dateUnit, static_cast<int>(differenceInDates[unitIndex]));
|
||||
isEndDateHit = true;
|
||||
}
|
||||
else if (tempDaysDiff > 0)
|
||||
{
|
||||
if (isEndDateHit)
|
||||
{
|
||||
// This is the closest the pivot can get to the end date for this unit
|
||||
break;
|
||||
}
|
||||
|
||||
// pivotDate is still below the end date
|
||||
try
|
||||
{
|
||||
pivotDate = AdjustCalendarDate(pivotDate, dateUnit, 1);
|
||||
differenceInDates[unitIndex] += 1;
|
||||
}
|
||||
catch (Platform::InvalidArgumentException^)
|
||||
{
|
||||
// handling for 31st Dec, 9999 last valid date
|
||||
// Do nothing - break out
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (tempDaysDiff != 0); // dates are the same - exit the loop
|
||||
|
||||
tempPivotDate = AdjustCalendarDate(tempPivotDate, dateUnit, static_cast<int>(differenceInDates[unitIndex]));
|
||||
pivotDate = tempPivotDate;
|
||||
daysDiff = GetDifferenceInDays(pivotDate, endDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
differenceInDates[3] = daysDiff;
|
||||
|
||||
difference->year = differenceInDates[0];
|
||||
difference->month = differenceInDates[1];
|
||||
difference->week = differenceInDates[2];
|
||||
difference->day = differenceInDates[3];
|
||||
}
|
||||
|
||||
|
||||
// Private Methods
|
||||
|
||||
// Gets number of days between the two date time values
|
||||
int DateCalculationEngine::GetDifferenceInDays(DateTime date1, DateTime date2)
|
||||
{
|
||||
// A tick is defined as the number of 100 nanoseconds
|
||||
long long ticksDifference = date2.UniversalTime - date1.UniversalTime;
|
||||
return static_cast<int>(ticksDifference / static_cast<long long>(c_day));
|
||||
}
|
||||
|
||||
// Gets number of Calendar days in the month in which this date falls.
|
||||
// Returns true if successful, false otherwise.
|
||||
bool DateCalculationEngine::TryGetCalendarDaysInMonth(_In_ DateTime date, _Out_ UINT& daysInMonth)
|
||||
{
|
||||
bool result = false;
|
||||
m_calendar->SetDateTime(date);
|
||||
|
||||
// NumberOfDaysInThisMonth returns -1 if unknown.
|
||||
int daysInThisMonth = m_calendar->NumberOfDaysInThisMonth;
|
||||
if (daysInThisMonth != -1)
|
||||
{
|
||||
daysInMonth = static_cast<UINT>(daysInThisMonth);
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Gets number of Calendar days in the year in which this date falls.
|
||||
// Returns true if successful, false otherwise.
|
||||
bool DateCalculationEngine::TryGetCalendarDaysInYear(_In_ DateTime date, _Out_ UINT& daysInYear)
|
||||
{
|
||||
bool result = false;
|
||||
UINT days = 0;
|
||||
|
||||
m_calendar->SetDateTime(date);
|
||||
|
||||
// NumberOfMonthsInThisYear returns -1 if unknown.
|
||||
int monthsInYear = m_calendar->NumberOfMonthsInThisYear;
|
||||
if (monthsInYear != -1)
|
||||
{
|
||||
bool monthResult = true;
|
||||
|
||||
// Not all years begin with Month 1.
|
||||
int firstMonthThisYear = m_calendar->FirstMonthInThisYear;
|
||||
for (int month = 0; month < monthsInYear; month++)
|
||||
{
|
||||
m_calendar->Month = firstMonthThisYear + month;
|
||||
|
||||
// NumberOfDaysInThisMonth returns -1 if unknown.
|
||||
int daysInMonth = m_calendar->NumberOfDaysInThisMonth;
|
||||
if (daysInMonth == -1)
|
||||
{
|
||||
monthResult = false;
|
||||
break;
|
||||
}
|
||||
|
||||
days += daysInMonth;
|
||||
}
|
||||
|
||||
if (monthResult)
|
||||
{
|
||||
daysInYear = days;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Adds/Subtracts certain value for a particular date unit
|
||||
DateTime DateCalculationEngine::AdjustCalendarDate(Windows::Foundation::DateTime date, DateUnit dateUnit, int difference)
|
||||
{
|
||||
m_calendar->SetDateTime(date);
|
||||
|
||||
switch (dateUnit)
|
||||
{
|
||||
case DateUnit::Year:
|
||||
m_calendar->AddYears(difference);
|
||||
break;
|
||||
case DateUnit::Month:
|
||||
m_calendar->AddMonths(difference);
|
||||
break;
|
||||
case DateUnit::Week:
|
||||
m_calendar->AddWeeks(difference);
|
||||
break;
|
||||
}
|
||||
|
||||
return m_calendar->GetDateTime();
|
||||
}
|
62
src/CalcViewModel/Common/DateCalculator.h
Normal file
62
src/CalcViewModel/Common/DateCalculator.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
const ULONGLONG c_millisecond = 10000;
|
||||
const ULONGLONG c_second = 1000 * c_millisecond;
|
||||
const ULONGLONG c_minute = 60 * c_second;
|
||||
const ULONGLONG c_hour = 60 * c_minute;
|
||||
const ULONGLONG c_day = 24 * c_hour;
|
||||
|
||||
const int c_unitsOfDate = 4; // Units Year,Month,Week,Day
|
||||
const int c_unitsGreaterThanDays = 3; // Units Greater than Days (Year/Month/Week) 3
|
||||
const int c_daysInWeek = 7;
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
namespace DateCalculation
|
||||
{
|
||||
public enum class _Enum_is_bitflag_ DateUnit
|
||||
{
|
||||
Year = 0x01,
|
||||
Month = 0x02,
|
||||
Week = 0x04,
|
||||
Day = 0x08
|
||||
};
|
||||
|
||||
// Struct to store the difference between two Dates in the form of Years, Months , Weeks
|
||||
struct DateDifference
|
||||
{
|
||||
int year = 0;
|
||||
int month = 0;
|
||||
int week = 0;
|
||||
int day = 0;
|
||||
};
|
||||
|
||||
class DateCalculationEngine
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
DateCalculationEngine(_In_ Platform::String^ calendarIdentifier);
|
||||
|
||||
// Public Methods
|
||||
bool __nothrow AddDuration(_In_ Windows::Foundation::DateTime startDate, _In_ const DateDifference& duration, _Out_ Windows::Foundation::DateTime *endDate);
|
||||
bool __nothrow SubtractDuration(_In_ Windows::Foundation::DateTime startDate, _In_ const DateDifference& duration, _Out_ Windows::Foundation::DateTime *endDate);
|
||||
void __nothrow GetDateDifference(_In_ Windows::Foundation::DateTime date1, _In_ Windows::Foundation::DateTime date2, _In_ DateUnit outputFormat, _Out_ DateDifference *difference);
|
||||
|
||||
private:
|
||||
// Private Variables
|
||||
Windows::Globalization::Calendar^ m_calendar;
|
||||
|
||||
// Private Methods
|
||||
int GetDifferenceInDays(Windows::Foundation::DateTime date1, Windows::Foundation::DateTime date2);
|
||||
bool TryGetCalendarDaysInMonth(_In_ Windows::Foundation::DateTime date, _Out_ UINT& daysInMonth);
|
||||
bool TryGetCalendarDaysInYear(_In_ Windows::Foundation::DateTime date, _Out_ UINT& daysInYear);
|
||||
Windows::Foundation::DateTime AdjustCalendarDate(Windows::Foundation::DateTime date, DateUnit dateUnit, int difference);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
70
src/CalcViewModel/Common/DelegateCommand.h
Normal file
70
src/CalcViewModel/Common/DelegateCommand.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
template <typename TTarget>
|
||||
ref class DelegateCommand: public Windows::UI::Xaml::Input::ICommand
|
||||
{
|
||||
internal:
|
||||
|
||||
typedef void (TTarget::*CommandHandlerFunc)(Platform::Object^);
|
||||
|
||||
DelegateCommand(TTarget^ target, CommandHandlerFunc func):
|
||||
m_weakTarget(target),
|
||||
m_function(func)
|
||||
{ }
|
||||
|
||||
private:
|
||||
|
||||
// Explicit, and private, implementation of ICommand, this way of programming makes it so
|
||||
// the ICommand methods will only be available if the ICommand interface is requested via a dynamic_cast
|
||||
// The ICommand interface is meant to be consumed by Xaml and not by the app, this is a defensive measure against
|
||||
// code in the app calling Execute.
|
||||
virtual void ExecuteImpl(Platform::Object^ parameter) sealed = Windows::UI::Xaml::Input::ICommand::Execute
|
||||
{
|
||||
TTarget^ target = m_weakTarget.Resolve<TTarget>();
|
||||
if (target)
|
||||
{
|
||||
(target->*m_function)(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool CanExecuteImpl(Platform::Object^ parameter) sealed = Windows::UI::Xaml::Input::ICommand::CanExecute
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual event Windows::Foundation::EventHandler<Platform::Object^>^ CanExecuteChangedImpl
|
||||
{
|
||||
virtual Windows::Foundation::EventRegistrationToken add(Windows::Foundation::EventHandler<Platform::Object^>^ handler) sealed = Windows::UI::Xaml::Input::ICommand::CanExecuteChanged::add
|
||||
{
|
||||
return m_canExecuteChanged += handler;
|
||||
}
|
||||
virtual void remove(Windows::Foundation::EventRegistrationToken token) sealed = Windows::UI::Xaml::Input::ICommand::CanExecuteChanged::remove
|
||||
{
|
||||
m_canExecuteChanged -= token;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
event Windows::Foundation::EventHandler<Platform::Object^>^ m_canExecuteChanged;
|
||||
|
||||
CommandHandlerFunc m_function;
|
||||
Platform::WeakReference m_weakTarget;
|
||||
|
||||
};
|
||||
|
||||
template <typename TTarget, typename TFuncPtr>
|
||||
DelegateCommand<TTarget>^ MakeDelegate(TTarget^ target, TFuncPtr&& function)
|
||||
{
|
||||
return ref new DelegateCommand<TTarget>(target, std::forward<TFuncPtr>(function));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
46
src/CalcViewModel/Common/DisplayExpressionToken.h
Normal file
46
src/CalcViewModel/Common/DisplayExpressionToken.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp::Common
|
||||
{
|
||||
public enum class TokenType
|
||||
{
|
||||
Operator,
|
||||
Operand,
|
||||
Separator
|
||||
};
|
||||
|
||||
[Windows::UI::Xaml::Data::Bindable]
|
||||
public ref class DisplayExpressionToken sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged
|
||||
{
|
||||
internal:
|
||||
DisplayExpressionToken(Platform::String^ token, int tokenPosition, bool fEditable, TokenType type) :
|
||||
m_Token(token), m_TokenPosition(tokenPosition), m_IsTokenEditable(fEditable), m_Type(type), m_OriginalToken(token), m_InEditMode(false)
|
||||
{}
|
||||
public:
|
||||
OBSERVABLE_OBJECT();
|
||||
OBSERVABLE_PROPERTY_RW(Platform::String^, Token);
|
||||
OBSERVABLE_PROPERTY_RW(int, TokenPosition);
|
||||
OBSERVABLE_PROPERTY_RW(bool, IsTokenEditable);
|
||||
OBSERVABLE_PROPERTY_RW(int, CommandIndex);
|
||||
OBSERVABLE_PROPERTY_R(Platform::String^, OriginalToken);
|
||||
|
||||
property bool IsTokenInEditMode {
|
||||
bool get() { return m_InEditMode; }
|
||||
void set(bool val)
|
||||
{
|
||||
if (!val)
|
||||
{
|
||||
m_OriginalToken = ref new Platform::String(m_Token->Data());
|
||||
}
|
||||
m_InEditMode = val;
|
||||
}
|
||||
}
|
||||
internal:
|
||||
OBSERVABLE_PROPERTY_RW(TokenType, Type);
|
||||
private:
|
||||
bool m_InEditMode;
|
||||
};
|
||||
}
|
50
src/CalcViewModel/Common/EngineResourceProvider.cpp
Normal file
50
src/CalcViewModel/Common/EngineResourceProvider.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "EngineResourceProvider.h"
|
||||
#include "Common/LocalizationSettings.h"
|
||||
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace Platform;
|
||||
using namespace Windows::ApplicationModel::Resources;
|
||||
using namespace std;
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
EngineResourceProvider::EngineResourceProvider()
|
||||
{
|
||||
m_resLoader = ResourceLoader::GetForViewIndependentUse("CEngineStrings");
|
||||
}
|
||||
|
||||
wstring EngineResourceProvider::GetCEngineString(const wstring& id)
|
||||
{
|
||||
const auto& localizationSettings = LocalizationSettings::GetInstance();
|
||||
|
||||
if (id.compare(L"sDecimal") == 0)
|
||||
{
|
||||
return localizationSettings.GetDecimalSeparatorStr();
|
||||
}
|
||||
|
||||
if (id.compare(L"sThousand") == 0)
|
||||
{
|
||||
return localizationSettings.GetNumberGroupingSeparatorStr();
|
||||
}
|
||||
|
||||
if (id.compare(L"sGrouping") == 0)
|
||||
{
|
||||
// The following groupings are the onces that CalcEngine supports.
|
||||
// 0;0 0x000 - no grouping
|
||||
// 3;0 0x003 - group every 3 digits
|
||||
// 3;2;0 0x023 - group 1st 3 and then every 2 digits
|
||||
// 4;0 0x004 - group every 4 digits
|
||||
// 5;3;2;0 0x235 - group 5, then 3, then every 2
|
||||
wstring numberGroupingString = localizationSettings.GetNumberGroupingStr();
|
||||
return numberGroupingString;
|
||||
}
|
||||
|
||||
StringReference idRef(id.c_str());
|
||||
String^ str = m_resLoader->GetString(idRef);
|
||||
return str->Begin();
|
||||
}
|
||||
}
|
17
src/CalcViewModel/Common/EngineResourceProvider.h
Normal file
17
src/CalcViewModel/Common/EngineResourceProvider.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
class EngineResourceProvider : public CalculationManager::IResourceProvider
|
||||
{
|
||||
public:
|
||||
EngineResourceProvider();
|
||||
virtual std::wstring GetCEngineString(const std::wstring& id) override;
|
||||
|
||||
private:
|
||||
Windows::ApplicationModel::Resources::ResourceLoader^ m_resLoader;
|
||||
};
|
||||
}
|
87
src/CalcViewModel/Common/ExpressionCommandDeserializer.cpp
Normal file
87
src/CalcViewModel/Common/ExpressionCommandDeserializer.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ExpressionCommandDeserializer.h"
|
||||
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace Windows::Storage::Streams;
|
||||
|
||||
CommandDeserializer::CommandDeserializer(_In_ DataReader^ dataReader) :m_dataReader(dataReader){}
|
||||
|
||||
std::shared_ptr<IExpressionCommand> CommandDeserializer::Deserialize(_In_ CalculationManager::CommandType cmdType)
|
||||
{
|
||||
switch (cmdType)
|
||||
{
|
||||
case CalculationManager::CommandType::OperandCommand:
|
||||
|
||||
return std::make_shared<COpndCommand>(DeserializeOperand());
|
||||
break;
|
||||
|
||||
case CalculationManager::CommandType::Parentheses:
|
||||
|
||||
return std::make_shared<CParentheses>(DeserializeParentheses());
|
||||
break;
|
||||
|
||||
case CalculationManager::CommandType::UnaryCommand:
|
||||
|
||||
return std::make_shared<CUnaryCommand>(DeserializeUnary());
|
||||
break;
|
||||
|
||||
case CalculationManager::CommandType::BinaryCommand:
|
||||
|
||||
return std::make_shared<CBinaryCommand>(DeserializeBinary());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ref new Platform::Exception(E_INVALIDARG, ref new Platform::String(L"Unknown command type"));
|
||||
}
|
||||
}
|
||||
|
||||
COpndCommand CommandDeserializer::DeserializeOperand()
|
||||
{
|
||||
bool fNegative = m_dataReader->ReadBoolean();
|
||||
bool fDecimal = m_dataReader->ReadBoolean();
|
||||
bool fSciFmt = m_dataReader->ReadBoolean();
|
||||
|
||||
std::shared_ptr<CalculatorVector<int>> cmdVector = std::make_shared<CalculatorVector<int>>();
|
||||
auto cmdVectorSize = m_dataReader->ReadUInt32();
|
||||
|
||||
for (unsigned int j = 0; j < cmdVectorSize; ++j)
|
||||
{
|
||||
int eachOpndcmd = m_dataReader->ReadInt32();
|
||||
cmdVector->Append(eachOpndcmd);
|
||||
}
|
||||
|
||||
return COpndCommand(cmdVector, fNegative, fDecimal, fSciFmt);
|
||||
}
|
||||
|
||||
CParentheses CommandDeserializer::DeserializeParentheses()
|
||||
{
|
||||
int parenthesisCmd = m_dataReader->ReadInt32();
|
||||
return CParentheses(parenthesisCmd);
|
||||
}
|
||||
|
||||
CUnaryCommand CommandDeserializer::DeserializeUnary()
|
||||
{
|
||||
auto cmdSize = m_dataReader->ReadUInt32();
|
||||
std::shared_ptr<CalculatorVector<int>> cmdVector = std::make_shared<CalculatorVector<int>>();
|
||||
|
||||
if (cmdSize == 1)
|
||||
{
|
||||
int eachOpndcmd = m_dataReader->ReadInt32();
|
||||
return CUnaryCommand(eachOpndcmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
int eachOpndcmd1 = m_dataReader->ReadInt32();
|
||||
int eachOpndcmd2 = m_dataReader->ReadInt32();
|
||||
return CUnaryCommand(eachOpndcmd1, eachOpndcmd2);
|
||||
}
|
||||
}
|
||||
|
||||
CBinaryCommand CommandDeserializer::DeserializeBinary()
|
||||
{
|
||||
int cmd = m_dataReader->ReadInt32();
|
||||
return CBinaryCommand(cmd);
|
||||
}
|
24
src/CalcViewModel/Common/ExpressionCommandDeserializer.h
Normal file
24
src/CalcViewModel/Common/ExpressionCommandDeserializer.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
class CommandDeserializer
|
||||
{
|
||||
public:
|
||||
CommandDeserializer(_In_ Windows::Storage::Streams::DataReader^ dataReader);
|
||||
std::shared_ptr<IExpressionCommand> Deserialize(_In_ CalculationManager::CommandType cmdType);
|
||||
|
||||
private:
|
||||
Windows::Storage::Streams::DataReader^ m_dataReader;
|
||||
COpndCommand DeserializeOperand();
|
||||
CParentheses DeserializeParentheses();
|
||||
CUnaryCommand DeserializeUnary();
|
||||
CBinaryCommand DeserializeBinary();
|
||||
};
|
||||
}
|
||||
}
|
56
src/CalcViewModel/Common/ExpressionCommandSerializer.cpp
Normal file
56
src/CalcViewModel/Common/ExpressionCommandSerializer.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Common\ExpressionCommandSerializer.h"
|
||||
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace Windows::Storage::Streams;
|
||||
|
||||
SerializeCommandVisitor::SerializeCommandVisitor(_In_ DataWriter^ dataWriter) :m_dataWriter(dataWriter)
|
||||
{
|
||||
}
|
||||
|
||||
void SerializeCommandVisitor::Visit(_In_ COpndCommand &opndCmd)
|
||||
{
|
||||
m_dataWriter->WriteBoolean(opndCmd.IsNegative());
|
||||
m_dataWriter->WriteBoolean(opndCmd.IsDecimalPresent());
|
||||
m_dataWriter->WriteBoolean(opndCmd.IsSciFmt());
|
||||
|
||||
auto opndCmds = opndCmd.GetCommands();
|
||||
unsigned int opndCmdSize;
|
||||
opndCmds->GetSize(&opndCmdSize);
|
||||
m_dataWriter->WriteUInt32(opndCmdSize);
|
||||
for (unsigned int j = 0; j < opndCmdSize; ++j)
|
||||
{
|
||||
int eachOpndcmd;
|
||||
opndCmds->GetAt(j, &eachOpndcmd);
|
||||
m_dataWriter->WriteInt32(eachOpndcmd);
|
||||
}
|
||||
}
|
||||
|
||||
void SerializeCommandVisitor::Visit(_In_ CUnaryCommand &unaryCmd)
|
||||
{
|
||||
auto cmds = unaryCmd.GetCommands();
|
||||
unsigned int cmdSize;
|
||||
cmds->GetSize(&cmdSize);
|
||||
m_dataWriter->WriteUInt32(cmdSize);
|
||||
for (unsigned int j = 0; j < cmdSize; ++j)
|
||||
{
|
||||
int eachOpndcmd;
|
||||
cmds->GetAt(j, &eachOpndcmd);
|
||||
m_dataWriter->WriteInt32(eachOpndcmd);
|
||||
}
|
||||
}
|
||||
|
||||
void SerializeCommandVisitor::Visit(_In_ CBinaryCommand &binaryCmd)
|
||||
{
|
||||
int cmd = binaryCmd.GetCommand();
|
||||
m_dataWriter->WriteInt32(cmd);
|
||||
}
|
||||
|
||||
void SerializeCommandVisitor::Visit(_In_ CParentheses ¶Cmd)
|
||||
{
|
||||
int parenthesisCmd = paraCmd.GetCommand();
|
||||
m_dataWriter->WriteInt32(parenthesisCmd);
|
||||
}
|
24
src/CalcViewModel/Common/ExpressionCommandSerializer.h
Normal file
24
src/CalcViewModel/Common/ExpressionCommandSerializer.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
class SerializeCommandVisitor : public ISerializeCommandVisitor
|
||||
{
|
||||
public:
|
||||
SerializeCommandVisitor(_In_ Windows::Storage::Streams::DataWriter^ dataWriter);
|
||||
|
||||
void Visit(_In_ COpndCommand &opndCmd);
|
||||
void Visit(_In_ CUnaryCommand &unaryCmd);
|
||||
void Visit(_In_ CBinaryCommand &binaryCmd);
|
||||
void Visit(_In_ CParentheses ¶Cmd);
|
||||
|
||||
private:
|
||||
Windows::Storage::Streams::DataWriter^ m_dataWriter;
|
||||
};
|
||||
}
|
||||
}
|
876
src/CalcViewModel/Common/KeyboardShortcutManager.cpp
Normal file
876
src/CalcViewModel/Common/KeyboardShortcutManager.cpp
Normal file
@@ -0,0 +1,876 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "KeyboardShortcutManager.h"
|
||||
#include "AppResourceProvider.h"
|
||||
#include "ApplicationViewModel.h"
|
||||
#include "LocalizationSettings.h"
|
||||
|
||||
using namespace Concurrency;
|
||||
using namespace Platform;
|
||||
using namespace std;
|
||||
using namespace Windows::ApplicationModel::Resources;
|
||||
using namespace Windows::UI::Xaml;
|
||||
using namespace Windows::UI::Xaml::Controls;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Collections;
|
||||
using namespace Windows::UI::Core;
|
||||
using namespace Windows::UI::Xaml::Controls::Primitives;
|
||||
using namespace Windows::System;
|
||||
using namespace Utils;
|
||||
using namespace CalculatorApp;
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace CalculatorApp::ViewModel;
|
||||
|
||||
namespace MUXC = Microsoft::UI::Xaml::Controls;
|
||||
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(KeyboardShortcutManager, Character);
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(KeyboardShortcutManager, VirtualKey);
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(KeyboardShortcutManager, VirtualKeyControlChord);
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(KeyboardShortcutManager, VirtualKeyShiftChord);
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(KeyboardShortcutManager, VirtualKeyAltChord);
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(KeyboardShortcutManager, VirtualKeyControlShiftChord);
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(KeyboardShortcutManager, VirtualKeyInverseChord);
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(KeyboardShortcutManager, VirtualKeyControlInverseChord);
|
||||
|
||||
static multimap<int, multimap<wchar_t, WeakReference>> s_CharacterForButtons;
|
||||
static multimap<int, multimap<MyVirtualKey, WeakReference>> s_VirtualKeysForButtons;
|
||||
static multimap<int, multimap<MyVirtualKey, WeakReference>> s_VirtualKeyControlChordsForButtons;
|
||||
static multimap<int, multimap<MyVirtualKey, WeakReference>> s_VirtualKeyShiftChordsForButtons;
|
||||
static multimap<int, multimap<MyVirtualKey, WeakReference>> s_VirtualKeyAltChordsForButtons;
|
||||
static multimap<int, multimap<MyVirtualKey, WeakReference>> s_VirtualKeyControlShiftChordsForButtons;
|
||||
static multimap<int, multimap<MyVirtualKey, WeakReference>> s_VirtualKeyInverseChordsForButtons;
|
||||
static multimap<int, multimap<MyVirtualKey, WeakReference>> s_VirtualKeyControlInverseChordsForButtons;
|
||||
|
||||
static const TimeSpan c_lightUpTime = { 500000 }; // Quarter of a second
|
||||
static multimap<int, bool> s_ShiftKeyPressed;
|
||||
static multimap<int, bool> s_ControlKeyPressed;
|
||||
static multimap<int, bool> s_ShiftButtonChecked;
|
||||
static multimap<int, bool> s_IsDropDownOpen;
|
||||
|
||||
static reader_writer_lock s_keyboardShortcutMapLock;
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
// Lights up all of the buttons in the given range
|
||||
// The range is defined by a pair of iterators
|
||||
template <typename T>
|
||||
void LightUpButtons(const T& buttons)
|
||||
{
|
||||
auto iterator = buttons.first;
|
||||
for (; iterator != buttons.second; ++iterator)
|
||||
{
|
||||
auto button = iterator->second.Resolve<ButtonBase>();
|
||||
if (button && button->IsEnabled)
|
||||
{
|
||||
LightUpButton(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LightUpButton(ButtonBase^ button)
|
||||
{
|
||||
// If the button is a toggle button then we don't need
|
||||
// to change the UI of the button
|
||||
if (dynamic_cast<ToggleButton^>(button))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The button will go into the visual Pressed state with this call
|
||||
VisualStateManager::GoToState(button, "Pressed", true);
|
||||
|
||||
// This timer will fire after c_lightUpTime and make the button
|
||||
// go back to the normal state.
|
||||
// This timer will only fire once after which it will be destroyed
|
||||
auto timer = ref new DispatcherTimer();
|
||||
timer->Interval = c_lightUpTime;
|
||||
|
||||
WeakReference timerWeakReference(timer);
|
||||
WeakReference buttonWeakReference(button);
|
||||
timer->Tick += ref new EventHandler<Object^>(
|
||||
[buttonWeakReference, timerWeakReference](Object^, Object^)
|
||||
{
|
||||
auto button = buttonWeakReference.Resolve<ButtonBase>();
|
||||
if (button)
|
||||
{
|
||||
VisualStateManager::GoToState(button, "Normal", true);
|
||||
}
|
||||
|
||||
// Cancel the timer after we're done so it only fires once
|
||||
auto timer = timerWeakReference.Resolve<DispatcherTimer>();
|
||||
if (timer)
|
||||
{
|
||||
timer->Stop();
|
||||
}
|
||||
});
|
||||
timer->Start();
|
||||
}
|
||||
|
||||
// Looks for the first button reference that it can resolve
|
||||
// and execute its command.
|
||||
// NOTE: It is assumed that all buttons associated with a particular
|
||||
// key have the same command
|
||||
template <typename T>
|
||||
void RunFirstEnabledButtonCommand(const T& buttons)
|
||||
{
|
||||
auto buttonIterator = buttons.first;
|
||||
for (; buttonIterator != buttons.second; ++buttonIterator)
|
||||
{
|
||||
auto button = buttonIterator->second.Resolve<ButtonBase>();
|
||||
if (button && button->IsEnabled)
|
||||
{
|
||||
RunButtonCommand(button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RunButtonCommand(ButtonBase^ button)
|
||||
{
|
||||
if (button->IsEnabled)
|
||||
{
|
||||
auto command = button->Command;
|
||||
auto parameter = button->CommandParameter;
|
||||
if (command && command->CanExecute(parameter))
|
||||
{
|
||||
command->Execute(parameter);
|
||||
}
|
||||
|
||||
auto radio = dynamic_cast<RadioButton^>(button);
|
||||
if (radio)
|
||||
{
|
||||
radio->IsChecked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto toggle = dynamic_cast<ToggleButton^>(button);
|
||||
if (toggle)
|
||||
{
|
||||
toggle->IsChecked = !toggle->IsChecked->Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static multimap<int, bool> s_ignoreNextEscape;
|
||||
static multimap<int, bool> s_keepIgnoringEscape;
|
||||
static multimap<int, bool> s_fHonorShortcuts;
|
||||
static multimap<int, bool> s_fHandledEnter;
|
||||
static multimap<int, Flyout^> s_AboutFlyout;
|
||||
|
||||
void KeyboardShortcutManager::IgnoreEscape(bool onlyOnce)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
|
||||
if (s_ignoreNextEscape.find(viewId) != s_ignoreNextEscape.end())
|
||||
{
|
||||
s_ignoreNextEscape.erase(viewId);
|
||||
s_ignoreNextEscape.insert(std::make_pair(viewId, true));
|
||||
}
|
||||
|
||||
if (s_keepIgnoringEscape.find(viewId) != s_keepIgnoringEscape.end())
|
||||
{
|
||||
s_keepIgnoringEscape.erase(viewId);
|
||||
s_keepIgnoringEscape.insert(std::make_pair(viewId, !onlyOnce));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::HonorEscape()
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
|
||||
if (s_ignoreNextEscape.find(viewId) != s_ignoreNextEscape.end())
|
||||
{
|
||||
s_ignoreNextEscape.erase(viewId);
|
||||
s_ignoreNextEscape.insert(std::make_pair(viewId, false));
|
||||
}
|
||||
|
||||
if (s_keepIgnoringEscape.find(viewId) != s_keepIgnoringEscape.end())
|
||||
{
|
||||
s_keepIgnoringEscape.erase(viewId);
|
||||
s_keepIgnoringEscape.insert(std::make_pair(viewId, false));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnCharacterPropertyChanged(
|
||||
DependencyObject^ target,
|
||||
String^ oldValue,
|
||||
String^ newValue)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto button = safe_cast<ButtonBase^>(target);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto iterViewMap = s_CharacterForButtons.find(viewId);
|
||||
|
||||
if (iterViewMap != s_CharacterForButtons.end())
|
||||
{
|
||||
if (oldValue)
|
||||
{
|
||||
iterViewMap->second.erase(oldValue->Data()[0]);
|
||||
}
|
||||
|
||||
if (newValue)
|
||||
{
|
||||
if (newValue == L".")
|
||||
{
|
||||
wchar_t decSep = LocalizationSettings::GetInstance().GetDecimalSeparator();
|
||||
iterViewMap->second.insert(std::make_pair(decSep, WeakReference(button)));
|
||||
}
|
||||
else
|
||||
{
|
||||
iterViewMap->second.insert(std::make_pair(newValue->Data()[0], WeakReference(button)));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s_CharacterForButtons.insert(std::make_pair(viewId, std::multimap<wchar_t, WeakReference>()));
|
||||
|
||||
if (newValue == L".")
|
||||
{
|
||||
wchar_t decSep = LocalizationSettings::GetInstance().GetDecimalSeparator();
|
||||
s_CharacterForButtons.find(viewId)->second.insert(std::make_pair(decSep, WeakReference(button)));
|
||||
}
|
||||
else
|
||||
{
|
||||
s_CharacterForButtons.find(viewId)->second.insert(std::make_pair(newValue->Data()[0], WeakReference(button)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnVirtualKeyPropertyChanged(
|
||||
DependencyObject^ target,
|
||||
MyVirtualKey /*oldValue*/,
|
||||
MyVirtualKey newValue)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto button = static_cast<ButtonBase^>(target);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto iterViewMap = s_VirtualKeysForButtons.find(viewId);
|
||||
|
||||
// Check if the View Id has already been registered
|
||||
if (iterViewMap != s_VirtualKeysForButtons.end())
|
||||
{
|
||||
iterViewMap->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the View Id is not already registered, then register it and make the entry
|
||||
s_VirtualKeysForButtons.insert(std::make_pair(viewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
s_VirtualKeysForButtons.find(viewId)->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnVirtualKeyControlChordPropertyChanged(
|
||||
DependencyObject^ target,
|
||||
MyVirtualKey /*oldValue*/,
|
||||
MyVirtualKey newValue)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
Control^ control = dynamic_cast<ButtonBase^>(target);
|
||||
|
||||
if (control == nullptr)
|
||||
{
|
||||
// Handling Ctrl+E shortcut for Date Calc, target would be NavigationView^ in that case
|
||||
control = safe_cast<MUXC::NavigationView^>(target);
|
||||
}
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto iterViewMap = s_VirtualKeyControlChordsForButtons.find(viewId);
|
||||
|
||||
// Check if the View Id has already been registered
|
||||
if (iterViewMap != s_VirtualKeyControlChordsForButtons.end())
|
||||
{
|
||||
iterViewMap->second.insert(std::make_pair(newValue, WeakReference(control)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the View Id is not already registered, then register it and make the entry
|
||||
s_VirtualKeyControlChordsForButtons.insert(std::make_pair(viewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
s_VirtualKeyControlChordsForButtons.find(viewId)->second.insert(std::make_pair(newValue, WeakReference(control)));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnVirtualKeyShiftChordPropertyChanged(
|
||||
DependencyObject^ target,
|
||||
MyVirtualKey /*oldValue*/,
|
||||
MyVirtualKey newValue)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto button = safe_cast<ButtonBase^>(target);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto iterViewMap = s_VirtualKeyShiftChordsForButtons.find(viewId);
|
||||
|
||||
// Check if the View Id has already been registered
|
||||
if (iterViewMap != s_VirtualKeyShiftChordsForButtons.end())
|
||||
{
|
||||
iterViewMap->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the View Id is not already registered, then register it and make the entry
|
||||
s_VirtualKeyShiftChordsForButtons.insert(std::make_pair(viewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
s_VirtualKeyShiftChordsForButtons.find(viewId)->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnVirtualKeyAltChordPropertyChanged(
|
||||
DependencyObject^ target,
|
||||
MyVirtualKey /*oldValue*/,
|
||||
MyVirtualKey newValue)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
MUXC::NavigationView^ navView = safe_cast<MUXC::NavigationView^>(target);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto iterViewMap = s_VirtualKeyAltChordsForButtons.find(viewId);
|
||||
|
||||
// Check if the View Id has already been registered
|
||||
if (iterViewMap != s_VirtualKeyAltChordsForButtons.end())
|
||||
{
|
||||
iterViewMap->second.insert(std::make_pair(newValue, WeakReference(navView)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the View Id is not already registered, then register it and make the entry
|
||||
s_VirtualKeyAltChordsForButtons.insert(std::make_pair(viewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
s_VirtualKeyAltChordsForButtons.find(viewId)->second.insert(std::make_pair(newValue, WeakReference(navView)));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnVirtualKeyControlShiftChordPropertyChanged(
|
||||
DependencyObject^ target,
|
||||
MyVirtualKey /*oldValue*/,
|
||||
MyVirtualKey newValue)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto button = safe_cast<ButtonBase^>(target);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto iterViewMap = s_VirtualKeyControlShiftChordsForButtons.find(viewId);
|
||||
|
||||
// Check if the View Id has already been registered
|
||||
if (iterViewMap != s_VirtualKeyControlShiftChordsForButtons.end())
|
||||
{
|
||||
iterViewMap->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the View Id is not already registered, then register it and make the entry
|
||||
s_VirtualKeyControlShiftChordsForButtons.insert(std::make_pair(viewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
s_VirtualKeyControlShiftChordsForButtons.find(viewId)->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnVirtualKeyInverseChordPropertyChanged(
|
||||
DependencyObject^ target,
|
||||
MyVirtualKey /*oldValue*/,
|
||||
MyVirtualKey newValue)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto button = safe_cast<ButtonBase^>(target);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto iterViewMap = s_VirtualKeyInverseChordsForButtons.find(viewId);
|
||||
|
||||
// Check if the View Id has already been registered
|
||||
if (iterViewMap != s_VirtualKeyInverseChordsForButtons.end())
|
||||
{
|
||||
iterViewMap->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the View Id is not already registered, then register it and make the entry
|
||||
s_VirtualKeyInverseChordsForButtons.insert(std::make_pair(viewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
s_VirtualKeyInverseChordsForButtons.find(viewId)->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnVirtualKeyControlInverseChordPropertyChanged(
|
||||
DependencyObject^ target,
|
||||
MyVirtualKey /*oldValue*/,
|
||||
MyVirtualKey newValue)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto button = safe_cast<ButtonBase^>(target);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto iterViewMap = s_VirtualKeyControlInverseChordsForButtons.find(viewId);
|
||||
|
||||
// Check if the View Id has already been registered
|
||||
if (iterViewMap != s_VirtualKeyControlInverseChordsForButtons.end())
|
||||
{
|
||||
iterViewMap->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the View Id is not already registered, then register it and make the entry
|
||||
s_VirtualKeyControlInverseChordsForButtons.insert(std::make_pair(viewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
s_VirtualKeyControlInverseChordsForButtons.find(viewId)->second.insert(std::make_pair(newValue, WeakReference(button)));
|
||||
}
|
||||
}
|
||||
|
||||
// In the three event handlers bellow we will not mark the event as handled
|
||||
// because this is a sumplemental operation and we don't want to interfere with
|
||||
// the normal keyboard handling.
|
||||
void KeyboardShortcutManager::OnCharacterReceivedHandler(CoreWindow^ sender, CharacterReceivedEventArgs^ args)
|
||||
{
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto currentHonorShortcuts = s_fHonorShortcuts.find(viewId);
|
||||
|
||||
if (currentHonorShortcuts != s_fHonorShortcuts.end())
|
||||
{
|
||||
if (currentHonorShortcuts->second)
|
||||
{
|
||||
wchar_t character = static_cast<wchar_t>(args->KeyCode);
|
||||
auto buttons = s_CharacterForButtons.find(viewId)->second.equal_range(character);
|
||||
|
||||
RunFirstEnabledButtonCommand(buttons);
|
||||
|
||||
LightUpButtons(buttons);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::multimap<MyVirtualKey, WeakReference>& GetCurrentKeyDictionary(MyVirtualKey key, bool altPressed = false)
|
||||
{
|
||||
int viewId = Utils::GetWindowId();
|
||||
|
||||
if (altPressed)
|
||||
{
|
||||
return s_VirtualKeyAltChordsForButtons.find(viewId)->second;
|
||||
}
|
||||
else if ((s_ShiftKeyPressed.find(viewId)->second) && ((Window::Current->CoreWindow->GetKeyState(VirtualKey::Control) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down))
|
||||
{
|
||||
return s_VirtualKeyControlShiftChordsForButtons.find(viewId)->second;
|
||||
}
|
||||
else if (s_ShiftKeyPressed.find(viewId)->second)
|
||||
{
|
||||
return s_VirtualKeyShiftChordsForButtons.find(viewId)->second;
|
||||
}
|
||||
else if (s_ShiftButtonChecked.find(viewId)->second)
|
||||
{
|
||||
if ((Window::Current->CoreWindow->GetKeyState(VirtualKey::Control) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down)
|
||||
{
|
||||
auto iterViewMap = s_VirtualKeyControlInverseChordsForButtons.find(viewId);
|
||||
if (iterViewMap != s_VirtualKeyControlInverseChordsForButtons.end())
|
||||
{
|
||||
for (auto iterator = iterViewMap->second.begin(); iterator != iterViewMap->second.end(); ++iterator)
|
||||
{
|
||||
if (key == iterator->first)
|
||||
{
|
||||
return s_VirtualKeyControlInverseChordsForButtons.find(viewId)->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto iterViewMap = s_VirtualKeyControlInverseChordsForButtons.find(viewId);
|
||||
if (iterViewMap != s_VirtualKeyControlInverseChordsForButtons.end())
|
||||
{
|
||||
for (auto iterator = iterViewMap->second.begin(); iterator != iterViewMap->second.end(); ++iterator)
|
||||
{
|
||||
if (key == iterator->first)
|
||||
{
|
||||
return s_VirtualKeyInverseChordsForButtons.find(viewId)->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((Window::Current->CoreWindow->GetKeyState(VirtualKey::Control) & CoreVirtualKeyStates::Down) == CoreVirtualKeyStates::Down)
|
||||
{
|
||||
return s_VirtualKeyControlChordsForButtons.find(viewId)->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
return s_VirtualKeysForButtons.find(viewId)->second;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnKeyDownHandler(CoreWindow^ sender, KeyEventArgs^ args)
|
||||
{
|
||||
// If keyboard shortcuts like Ctrl+C or Ctrl+V are not handled
|
||||
if (!args->Handled)
|
||||
{
|
||||
auto key = args->VirtualKey;
|
||||
int viewId = Utils::GetWindowId();
|
||||
|
||||
auto currentControlKeyPressed = s_ControlKeyPressed.find(viewId);
|
||||
auto currentShiftKeyPressed = s_ShiftKeyPressed.find(viewId);
|
||||
|
||||
bool isControlKeyPressed = (currentControlKeyPressed != s_ControlKeyPressed.end()) && (currentControlKeyPressed->second);
|
||||
bool isShiftKeyPressed = (currentShiftKeyPressed != s_ShiftKeyPressed.end()) && (currentShiftKeyPressed->second);
|
||||
|
||||
// Handle Ctrl + E for DateCalculator
|
||||
if ((key == VirtualKey::E) &&
|
||||
isControlKeyPressed &&
|
||||
!isShiftKeyPressed)
|
||||
{
|
||||
const auto& lookupMap = GetCurrentKeyDictionary(static_cast<MyVirtualKey>(key));
|
||||
auto buttons = lookupMap.equal_range(static_cast<MyVirtualKey>(key));
|
||||
auto navView = buttons.first->second.Resolve<MUXC::NavigationView>();
|
||||
auto appViewModel = safe_cast<ApplicationViewModel^>(navView->DataContext);
|
||||
appViewModel->Mode = ViewMode::Date;
|
||||
auto categoryName = AppResourceProvider::GetInstance().GetResourceString(L"DateCalculationModeText");
|
||||
appViewModel->CategoryName = categoryName;
|
||||
|
||||
auto menuItems = static_cast<IObservableVector<Object^>^>(navView->MenuItemsSource);
|
||||
auto flatIndex = NavCategory::GetFlatIndex(ViewMode::Date);
|
||||
navView->SelectedItem = menuItems->GetAt(flatIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
auto currentHonorShortcuts = s_fHonorShortcuts.find(viewId);
|
||||
|
||||
auto currentIgnoreNextEscape = s_ignoreNextEscape.find(viewId);
|
||||
|
||||
if (currentIgnoreNextEscape != s_ignoreNextEscape.end())
|
||||
{
|
||||
if (currentIgnoreNextEscape->second && key == VirtualKey::Escape)
|
||||
{
|
||||
auto currentKeepIgnoringEscape = s_keepIgnoringEscape.find(viewId);
|
||||
|
||||
if (currentKeepIgnoringEscape != s_keepIgnoringEscape.end())
|
||||
{
|
||||
if (!currentKeepIgnoringEscape->second)
|
||||
{
|
||||
HonorEscape();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key == VirtualKey::Control)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto currentControlKeyPressed = s_ControlKeyPressed.find(viewId);
|
||||
|
||||
if (currentControlKeyPressed != s_ControlKeyPressed.end())
|
||||
{
|
||||
s_ControlKeyPressed.erase(viewId);
|
||||
s_ControlKeyPressed.insert(std::make_pair(viewId, true));
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (key == VirtualKey::Shift)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto currentShiftKeyPressed = s_ShiftKeyPressed.find(viewId);
|
||||
|
||||
if (currentShiftKeyPressed != s_ShiftKeyPressed.end())
|
||||
{
|
||||
s_ShiftKeyPressed.erase(viewId);
|
||||
s_ShiftKeyPressed.insert(std::make_pair(viewId, true));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& lookupMap = GetCurrentKeyDictionary(static_cast<MyVirtualKey>(key));
|
||||
auto buttons = lookupMap.equal_range(static_cast<MyVirtualKey>(key));
|
||||
|
||||
auto currentIsDropDownOpen = s_IsDropDownOpen.find(viewId);
|
||||
|
||||
if (currentHonorShortcuts != s_fHonorShortcuts.end())
|
||||
{
|
||||
if (currentHonorShortcuts->second)
|
||||
{
|
||||
RunFirstEnabledButtonCommand(buttons);
|
||||
|
||||
// Ctrl+C and Ctrl+V shifts focus to some button because of which enter doesn't work after copy/paste. So don't shift focus if Ctrl+C or Ctrl+V is pressed.
|
||||
// When drop down is open, pressing escape shifts focus to clear button. So dont's shift focus if drop down is open.
|
||||
// Ctrl+Insert is equivalent to Ctrl+C and Shift+Insert is equivalent to Ctrl+V
|
||||
if (currentIsDropDownOpen != s_IsDropDownOpen.end() && !currentIsDropDownOpen->second)
|
||||
{
|
||||
// Do not Light Up Buttons when Ctrl+C, Ctrl+V, Ctrl+Insert or Shift+Insert is pressed
|
||||
if (!(isControlKeyPressed && (key == VirtualKey::C || key == VirtualKey::V || key == VirtualKey::Insert))
|
||||
&& !(isShiftKeyPressed && (key == VirtualKey::Insert)))
|
||||
{
|
||||
LightUpButtons(buttons);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnKeyUpHandler(CoreWindow^ sender, KeyEventArgs^ args)
|
||||
{
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto key = args->VirtualKey;
|
||||
|
||||
if (args->VirtualKey == VirtualKey::Shift)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto currentShiftKeyPressed = s_ShiftKeyPressed.find(viewId);
|
||||
|
||||
if (currentShiftKeyPressed != s_ShiftKeyPressed.end())
|
||||
{
|
||||
s_ShiftKeyPressed.erase(viewId);
|
||||
s_ShiftKeyPressed.insert(std::make_pair(viewId, false));
|
||||
}
|
||||
}
|
||||
else if (args->VirtualKey == VirtualKey::Control)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
auto currentControlKeyPressed = s_ControlKeyPressed.find(viewId);
|
||||
|
||||
if (currentControlKeyPressed != s_ControlKeyPressed.end())
|
||||
{
|
||||
s_ControlKeyPressed.erase(viewId);
|
||||
s_ControlKeyPressed.insert(std::make_pair(viewId, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnAcceleratorKeyActivated(CoreDispatcher^, AcceleratorKeyEventArgs^ args)
|
||||
{
|
||||
if (args->KeyStatus.IsKeyReleased)
|
||||
{
|
||||
auto key = args->VirtualKey;
|
||||
bool altPressed = args->KeyStatus.IsMenuKeyDown;
|
||||
|
||||
// If the Alt/Menu key is not pressed then we don't care about the key anymore
|
||||
if (!altPressed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& lookupMap = GetCurrentKeyDictionary(static_cast<MyVirtualKey>(key), altPressed);
|
||||
auto listItems = lookupMap.equal_range(static_cast<MyVirtualKey>(key));
|
||||
for (auto listIterator = listItems.first; listIterator != listItems.second; ++listIterator)
|
||||
{
|
||||
auto item = listIterator->second.Resolve<MUXC::NavigationView>();
|
||||
if (item != nullptr)
|
||||
{
|
||||
auto navView = safe_cast<MUXC::NavigationView^> (item);
|
||||
|
||||
auto menuItems = static_cast<IObservableVector<Object^>^>(navView->MenuItemsSource);
|
||||
if (menuItems != nullptr)
|
||||
{
|
||||
auto vm = safe_cast<ApplicationViewModel^>(navView->DataContext);
|
||||
if (nullptr != vm)
|
||||
{
|
||||
ViewMode toMode = NavCategory::GetViewModeForVirtualKey(static_cast<MyVirtualKey>(key));
|
||||
if (NavCategory::IsValidViewMode(toMode))
|
||||
{
|
||||
vm->Mode = toMode;
|
||||
navView->SelectedItem = menuItems->GetAt(NavCategory::GetFlatIndex(toMode));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (args->VirtualKey == VirtualKey::Escape)
|
||||
{
|
||||
int viewId = Utils::GetWindowId();
|
||||
auto iterViewMap = s_AboutFlyout.find(viewId);
|
||||
|
||||
if ((iterViewMap != s_AboutFlyout.end()) && (iterViewMap->second != nullptr))
|
||||
{
|
||||
iterViewMap->second->Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::Initialize()
|
||||
{
|
||||
auto coreWindow = Window::Current->CoreWindow;
|
||||
coreWindow->CharacterReceived +=
|
||||
ref new TypedEventHandler<CoreWindow^, CharacterReceivedEventArgs^>(&KeyboardShortcutManager::OnCharacterReceivedHandler);
|
||||
coreWindow->KeyDown +=
|
||||
ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(&KeyboardShortcutManager::OnKeyDownHandler);
|
||||
coreWindow->KeyUp +=
|
||||
ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(&KeyboardShortcutManager::OnKeyUpHandler);
|
||||
coreWindow->Dispatcher->AcceleratorKeyActivated +=
|
||||
ref new TypedEventHandler<CoreDispatcher^, AcceleratorKeyEventArgs^>(&KeyboardShortcutManager::OnAcceleratorKeyActivated);
|
||||
|
||||
KeyboardShortcutManager::RegisterNewAppViewId();
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::ShiftButtonChecked(bool checked)
|
||||
{
|
||||
int viewId = Utils::GetWindowId();
|
||||
|
||||
if (s_ShiftButtonChecked.find(viewId) != s_ShiftButtonChecked.end())
|
||||
{
|
||||
s_ShiftButtonChecked.erase(viewId);
|
||||
s_ShiftButtonChecked.insert(std::make_pair(viewId, checked));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::UpdateDropDownState(bool isOpen)
|
||||
{
|
||||
int viewId = Utils::GetWindowId();
|
||||
|
||||
if (s_IsDropDownOpen.find(viewId) != s_IsDropDownOpen.end())
|
||||
{
|
||||
s_IsDropDownOpen.erase(viewId);
|
||||
s_IsDropDownOpen.insert(std::make_pair(viewId, isOpen));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::UpdateDropDownState(Flyout^ aboutPageFlyout)
|
||||
{
|
||||
int viewId = Utils::GetWindowId();
|
||||
|
||||
if (s_AboutFlyout.find(viewId) != s_AboutFlyout.end())
|
||||
{
|
||||
s_AboutFlyout.erase(viewId);
|
||||
s_AboutFlyout.insert(std::make_pair(viewId, aboutPageFlyout));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::HonorShortcuts(bool allow)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
int viewId = Utils::GetWindowId();
|
||||
|
||||
if (s_fHonorShortcuts.find(viewId) != s_fHonorShortcuts.end())
|
||||
{
|
||||
s_fHonorShortcuts.erase(viewId);
|
||||
s_fHonorShortcuts.insert(std::make_pair(viewId, allow));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::HandledEnter(bool ishandled)
|
||||
{
|
||||
int viewId = Utils::GetWindowId();
|
||||
|
||||
if (s_fHandledEnter.find(viewId) != s_fHandledEnter.end())
|
||||
{
|
||||
s_fHandledEnter.erase(viewId);
|
||||
s_fHandledEnter.insert(std::make_pair(viewId, ishandled));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::RegisterNewAppViewId()
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
int appViewId = Utils::GetWindowId();
|
||||
|
||||
// Check if the View Id has already been registered
|
||||
if (s_CharacterForButtons.find(appViewId) == s_CharacterForButtons.end())
|
||||
{
|
||||
s_CharacterForButtons.insert(std::make_pair(appViewId, std::multimap<wchar_t, WeakReference>()));
|
||||
}
|
||||
|
||||
if (s_VirtualKeysForButtons.find(appViewId) == s_VirtualKeysForButtons.end())
|
||||
{
|
||||
s_VirtualKeysForButtons.insert(std::make_pair(appViewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
}
|
||||
|
||||
if (s_VirtualKeyControlChordsForButtons.find(appViewId) == s_VirtualKeyControlChordsForButtons.end())
|
||||
{
|
||||
s_VirtualKeyControlChordsForButtons.insert(std::make_pair(appViewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
}
|
||||
|
||||
if (s_VirtualKeyShiftChordsForButtons.find(appViewId) == s_VirtualKeyShiftChordsForButtons.end())
|
||||
{
|
||||
s_VirtualKeyShiftChordsForButtons.insert(std::make_pair(appViewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
}
|
||||
|
||||
if (s_VirtualKeyAltChordsForButtons.find(appViewId) == s_VirtualKeyAltChordsForButtons.end())
|
||||
{
|
||||
s_VirtualKeyAltChordsForButtons.insert(std::make_pair(appViewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
}
|
||||
|
||||
if (s_VirtualKeyControlShiftChordsForButtons.find(appViewId) == s_VirtualKeyControlShiftChordsForButtons.end())
|
||||
{
|
||||
s_VirtualKeyControlShiftChordsForButtons.insert(std::make_pair(appViewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
}
|
||||
|
||||
if (s_VirtualKeyInverseChordsForButtons.find(appViewId) == s_VirtualKeyInverseChordsForButtons.end())
|
||||
{
|
||||
s_VirtualKeyInverseChordsForButtons.insert(std::make_pair(appViewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
}
|
||||
|
||||
if (s_VirtualKeyControlInverseChordsForButtons.find(appViewId) == s_VirtualKeyControlInverseChordsForButtons.end())
|
||||
{
|
||||
s_VirtualKeyControlInverseChordsForButtons.insert(std::make_pair(appViewId, std::multimap<MyVirtualKey, WeakReference>()));
|
||||
}
|
||||
|
||||
s_ShiftKeyPressed.insert(std::make_pair(appViewId, false));
|
||||
s_ControlKeyPressed.insert(std::make_pair(appViewId, false));
|
||||
s_ShiftButtonChecked.insert(std::make_pair(appViewId, false));
|
||||
s_IsDropDownOpen.insert(std::make_pair(appViewId, false));
|
||||
s_ignoreNextEscape.insert(std::make_pair(appViewId, false));
|
||||
s_keepIgnoringEscape.insert(std::make_pair(appViewId, false));
|
||||
s_fHonorShortcuts.insert(std::make_pair(appViewId, true));
|
||||
s_fHandledEnter.insert(std::make_pair(appViewId, true));
|
||||
s_AboutFlyout.insert(std::make_pair(appViewId, nullptr));
|
||||
}
|
||||
|
||||
void KeyboardShortcutManager::OnWindowClosed(int viewId)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_keyboardShortcutMapLock);
|
||||
|
||||
s_CharacterForButtons.erase(viewId);
|
||||
|
||||
s_VirtualKeysForButtons.erase(viewId);
|
||||
s_VirtualKeyControlChordsForButtons.erase(viewId);
|
||||
s_VirtualKeyShiftChordsForButtons.erase(viewId);
|
||||
s_VirtualKeyAltChordsForButtons.erase(viewId);
|
||||
s_VirtualKeyControlShiftChordsForButtons.erase(viewId);
|
||||
s_VirtualKeyInverseChordsForButtons.erase(viewId);
|
||||
s_VirtualKeyControlInverseChordsForButtons.erase(viewId);
|
||||
|
||||
s_ShiftKeyPressed.erase(viewId);
|
||||
s_ControlKeyPressed.erase(viewId);
|
||||
s_ShiftButtonChecked.erase(viewId);
|
||||
s_IsDropDownOpen.erase(viewId);
|
||||
s_ignoreNextEscape.erase(viewId);
|
||||
s_keepIgnoringEscape.erase(viewId);
|
||||
s_fHonorShortcuts.erase(viewId);
|
||||
s_fHandledEnter.erase(viewId);
|
||||
s_AboutFlyout.erase(viewId);
|
||||
}
|
95
src/CalcViewModel/Common/KeyboardShortcutManager.h
Normal file
95
src/CalcViewModel/Common/KeyboardShortcutManager.h
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
public ref class KeyboardShortcutManager sealed
|
||||
{
|
||||
public:
|
||||
KeyboardShortcutManager() {}
|
||||
|
||||
DEPENDENCY_PROPERTY_OWNER(KeyboardShortcutManager);
|
||||
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(Platform::String^, Character);
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(MyVirtualKey, VirtualKey);
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(MyVirtualKey, VirtualKeyControlChord);
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(MyVirtualKey, VirtualKeyShiftChord);
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(MyVirtualKey, VirtualKeyAltChord);
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(MyVirtualKey, VirtualKeyControlShiftChord);
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(MyVirtualKey, VirtualKeyInverseChord);
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(MyVirtualKey, VirtualKeyControlInverseChord);
|
||||
|
||||
internal:
|
||||
|
||||
static void Initialize();
|
||||
|
||||
// Sometimes, like with popups, escape is treated as special and even
|
||||
// though it is handled we get it passed through to us. In those cases
|
||||
// we need to be able to ignore it (looking at e->Hanlded isn't sufficient
|
||||
// because that always returns true).
|
||||
// The onlyOnce flag is used to indicate whether we should only ignore the
|
||||
// next escape, or keep ignoring until you explicitly HonorEscape.
|
||||
static void IgnoreEscape(bool onlyOnce);
|
||||
static void HonorEscape();
|
||||
static void HonorShortcuts(bool allow);
|
||||
static void HandledEnter(bool ishandled);
|
||||
static void UpdateDropDownState(bool);
|
||||
static void ShiftButtonChecked(bool checked);
|
||||
static void UpdateDropDownState(Windows::UI::Xaml::Controls::Flyout^ aboutPageFlyout);
|
||||
|
||||
static void RegisterNewAppViewId();
|
||||
static void OnWindowClosed(int viewId);
|
||||
|
||||
private:
|
||||
|
||||
static void OnCharacterPropertyChanged(
|
||||
Windows::UI::Xaml::DependencyObject^ target,
|
||||
Platform::String^ oldValue,
|
||||
Platform::String^ newValue);
|
||||
|
||||
static void OnVirtualKeyPropertyChanged(
|
||||
Windows::UI::Xaml::DependencyObject^ target,
|
||||
MyVirtualKey oldValue,
|
||||
MyVirtualKey newValue);
|
||||
|
||||
static void OnVirtualKeyControlChordPropertyChanged(
|
||||
Windows::UI::Xaml::DependencyObject^ target,
|
||||
MyVirtualKey oldValue,
|
||||
MyVirtualKey newValue);
|
||||
|
||||
static void OnVirtualKeyShiftChordPropertyChanged(
|
||||
Windows::UI::Xaml::DependencyObject^ target,
|
||||
MyVirtualKey oldValue,
|
||||
MyVirtualKey newValue);
|
||||
|
||||
static void OnVirtualKeyInverseChordPropertyChanged(
|
||||
Windows::UI::Xaml::DependencyObject^ target,
|
||||
MyVirtualKey oldValue,
|
||||
MyVirtualKey newValue);
|
||||
|
||||
static void OnVirtualKeyControlInverseChordPropertyChanged(
|
||||
Windows::UI::Xaml::DependencyObject^ target,
|
||||
MyVirtualKey oldValue,
|
||||
MyVirtualKey newValue);
|
||||
|
||||
static void OnVirtualKeyAltChordPropertyChanged(
|
||||
Windows::UI::Xaml::DependencyObject^ target,
|
||||
MyVirtualKey oldValue,
|
||||
MyVirtualKey newValue);
|
||||
|
||||
static void OnVirtualKeyControlShiftChordPropertyChanged(
|
||||
Windows::UI::Xaml::DependencyObject^ target,
|
||||
MyVirtualKey oldValue,
|
||||
MyVirtualKey newValue);
|
||||
|
||||
static void OnCharacterReceivedHandler(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::CharacterReceivedEventArgs^ args);
|
||||
static void OnKeyDownHandler(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args);
|
||||
static void OnKeyUpHandler(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::KeyEventArgs^ args);
|
||||
static void OnAcceleratorKeyActivated(Windows::UI::Core::CoreDispatcher^, Windows::UI::Core::AcceleratorKeyEventArgs^ args);
|
||||
};
|
||||
}
|
||||
}
|
554
src/CalcViewModel/Common/LocalizationService.cpp
Normal file
554
src/CalcViewModel/Common/LocalizationService.cpp
Normal file
@@ -0,0 +1,554 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "LocalizationService.h"
|
||||
#include "LocalizationSettings.h"
|
||||
#include "AppResourceProvider.h"
|
||||
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace CalculatorApp::Common::LocalizationServiceProperties;
|
||||
using namespace Concurrency;
|
||||
using namespace Platform;
|
||||
using namespace Platform::Collections;
|
||||
using namespace std;
|
||||
using namespace Windows::ApplicationModel::Resources;
|
||||
using namespace Windows::ApplicationModel::Resources::Core;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Foundation::Collections;
|
||||
using namespace Windows::Globalization;
|
||||
using namespace Windows::Globalization::DateTimeFormatting;
|
||||
using namespace Windows::Globalization::Fonts;
|
||||
using namespace Windows::Globalization::NumberFormatting;
|
||||
using namespace Windows::System::UserProfile;
|
||||
using namespace Windows::UI::Text;
|
||||
using namespace Windows::UI::Xaml;
|
||||
using namespace Windows::UI::Xaml::Controls;
|
||||
using namespace Windows::UI::Xaml::Controls::Primitives;
|
||||
using namespace Windows::UI::Xaml::Documents;
|
||||
using namespace Windows::UI::Xaml::Media;
|
||||
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(LocalizationService, FontType);
|
||||
DEPENDENCY_PROPERTY_INITIALIZATION(LocalizationService, FontSize);
|
||||
|
||||
static reader_writer_lock s_locServiceInstanceLock;
|
||||
|
||||
LocalizationService^ LocalizationService::s_singletonInstance = nullptr;
|
||||
|
||||
// Resources for the engine use numbers as keys. It's inconvenient, but also difficult to
|
||||
// change given that the engine heavily relies on perfect ordering of certain elements.
|
||||
// The key for open parenthesis, '(', is "48".
|
||||
static constexpr auto s_openParenResourceKey = L"48";
|
||||
|
||||
LocalizationService^ LocalizationService::GetInstance()
|
||||
{
|
||||
|
||||
if (s_singletonInstance == nullptr)
|
||||
{
|
||||
// Writer lock for the static maps
|
||||
reader_writer_lock::scoped_lock lock(s_locServiceInstanceLock);
|
||||
|
||||
if (s_singletonInstance == nullptr)
|
||||
{
|
||||
s_singletonInstance = ref new LocalizationService();
|
||||
}
|
||||
}
|
||||
return s_singletonInstance;
|
||||
}
|
||||
|
||||
LocalizationService::LocalizationService()
|
||||
{
|
||||
m_language = ApplicationLanguages::Languages->GetAt(0);
|
||||
m_flowDirection = ResourceContext::GetForCurrentView()->QualifierValues->Lookup(L"LayoutDirection")
|
||||
!= L"LTR" ? FlowDirection::RightToLeft : FlowDirection::LeftToRight;
|
||||
|
||||
auto resourceLoader = AppResourceProvider::GetInstance();
|
||||
m_fontFamilyOverride = resourceLoader.GetResourceString(L"LocalizedFontFamilyOverride");
|
||||
|
||||
String^ reserved = L"RESERVED_FOR_FONTLOC";
|
||||
|
||||
m_overrideFontApiValues = ((m_fontFamilyOverride != nullptr) && (m_fontFamilyOverride != reserved));
|
||||
if (m_overrideFontApiValues)
|
||||
{
|
||||
String^ localizedUICaptionFontSizeFactorOverride = resourceLoader.GetResourceString(L"LocalizedUICaptionFontSizeFactorOverride");
|
||||
String^ localizedUITextFontSizeFactorOverride = resourceLoader.GetResourceString(L"LocalizedUITextFontSizeFactorOverride");
|
||||
String^ localizedFontWeightOverride = resourceLoader.GetResourceString(L"LocalizedFontWeightOverride");
|
||||
|
||||
// If any of the font overrides are modified then all of them need to be modified
|
||||
assert(localizedFontWeightOverride != reserved);
|
||||
assert(localizedUITextFontSizeFactorOverride != reserved);
|
||||
assert(localizedUICaptionFontSizeFactorOverride != reserved);
|
||||
|
||||
m_fontWeightOverride = ParseFontWeight(localizedFontWeightOverride);
|
||||
m_uiTextFontScaleFactorOverride = _wtof(localizedUITextFontSizeFactorOverride->Data());
|
||||
m_uiCaptionFontScaleFactorOverride = _wtof(localizedUICaptionFontSizeFactorOverride->Data());
|
||||
}
|
||||
|
||||
m_fontGroup = ref new LanguageFontGroup(m_language);
|
||||
}
|
||||
|
||||
FontWeight LocalizationService::ParseFontWeight(String^ fontWeight)
|
||||
{
|
||||
wstring weight = fontWeight->Data();
|
||||
transform(weight.begin(), weight.end(), weight.begin(), towlower);
|
||||
fontWeight = ref new String(weight.c_str());
|
||||
|
||||
if (fontWeight == "black")
|
||||
{
|
||||
return FontWeights::Black;
|
||||
}
|
||||
else if (fontWeight == "bold")
|
||||
{
|
||||
return FontWeights::Bold;
|
||||
}
|
||||
else if (fontWeight == "extrablack")
|
||||
{
|
||||
return FontWeights::ExtraBlack;
|
||||
}
|
||||
else if (fontWeight == "extrabold")
|
||||
{
|
||||
return FontWeights::ExtraBold;
|
||||
}
|
||||
else if (fontWeight == "extralight")
|
||||
{
|
||||
return FontWeights::ExtraLight;
|
||||
}
|
||||
else if (fontWeight == "light")
|
||||
{
|
||||
return FontWeights::Light;
|
||||
}
|
||||
else if (fontWeight == "medium")
|
||||
{
|
||||
return FontWeights::Medium;
|
||||
}
|
||||
else if (fontWeight == "normal")
|
||||
{
|
||||
return FontWeights::Normal;
|
||||
}
|
||||
else if (fontWeight == "semibold")
|
||||
{
|
||||
return FontWeights::SemiBold;
|
||||
}
|
||||
else if (fontWeight == "semilight")
|
||||
{
|
||||
return FontWeights::SemiLight;
|
||||
}
|
||||
else if (fontWeight == "thin")
|
||||
{
|
||||
return FontWeights::Thin;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw invalid_argument("Invalid argument: fontWeight");
|
||||
}
|
||||
}
|
||||
|
||||
FlowDirection LocalizationService::GetFlowDirection()
|
||||
{
|
||||
return m_flowDirection;
|
||||
}
|
||||
|
||||
bool LocalizationService::IsRtlLayout()
|
||||
{
|
||||
return m_flowDirection == FlowDirection::RightToLeft;
|
||||
}
|
||||
|
||||
String^ LocalizationService::GetLanguage()
|
||||
{
|
||||
return m_language;
|
||||
}
|
||||
|
||||
bool LocalizationService::GetOverrideFontApiValues()
|
||||
{
|
||||
return m_overrideFontApiValues;
|
||||
}
|
||||
|
||||
FontFamily^ LocalizationService::GetLanguageFontFamilyForType(LanguageFontType fontType)
|
||||
{
|
||||
if (m_overrideFontApiValues)
|
||||
{
|
||||
return ref new FontFamily(m_fontFamilyOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ref new FontFamily(GetLanguageFont(fontType)->FontFamily);
|
||||
}
|
||||
}
|
||||
|
||||
LanguageFont^ LocalizationService::GetLanguageFont(LanguageFontType fontType)
|
||||
{
|
||||
assert(!m_overrideFontApiValues);
|
||||
assert(m_fontGroup);
|
||||
|
||||
switch (fontType)
|
||||
{
|
||||
case LanguageFontType::UIText:
|
||||
return m_fontGroup->UITextFont;
|
||||
case LanguageFontType::UICaption:
|
||||
return m_fontGroup->UICaptionFont;
|
||||
default:
|
||||
throw std::invalid_argument("fontType");
|
||||
}
|
||||
}
|
||||
|
||||
String^ LocalizationService::GetFontFamilyOverride()
|
||||
{
|
||||
assert(m_overrideFontApiValues);
|
||||
return m_fontFamilyOverride;
|
||||
}
|
||||
|
||||
FontWeight LocalizationService::GetFontWeightOverride()
|
||||
{
|
||||
assert(m_overrideFontApiValues);
|
||||
return m_fontWeightOverride;
|
||||
}
|
||||
|
||||
double LocalizationService::GetFontScaleFactorOverride(LanguageFontType fontType)
|
||||
{
|
||||
assert(m_overrideFontApiValues);
|
||||
|
||||
switch (fontType)
|
||||
{
|
||||
case LanguageFontType::UIText:
|
||||
return m_uiTextFontScaleFactorOverride;
|
||||
case LanguageFontType::UICaption:
|
||||
return m_uiCaptionFontScaleFactorOverride;
|
||||
default:
|
||||
throw invalid_argument("Invalid argument: fontType");
|
||||
}
|
||||
}
|
||||
|
||||
void LocalizationService::OnFontTypePropertyChanged(DependencyObject^ target, LanguageFontType /*oldValue*/, LanguageFontType /*newValue*/)
|
||||
{
|
||||
UpdateFontFamilyAndSize(target);
|
||||
}
|
||||
|
||||
void LocalizationService::OnFontWeightPropertyChanged(DependencyObject^ target, FontWeight /*oldValue*/, FontWeight /*newValue*/)
|
||||
{
|
||||
UpdateFontFamilyAndSize(target);
|
||||
}
|
||||
|
||||
void LocalizationService::OnFontSizePropertyChanged(DependencyObject^ target, double /*oldValue*/, double /*newValue*/)
|
||||
{
|
||||
UpdateFontFamilyAndSize(target);
|
||||
}
|
||||
|
||||
void LocalizationService::UpdateFontFamilyAndSize(DependencyObject^ target)
|
||||
{
|
||||
FontFamily^ fontFamily;
|
||||
FontWeight fontWeight;
|
||||
bool fOverrideFontWeight = false;
|
||||
double scaleFactor;
|
||||
|
||||
auto service = LocalizationService::GetInstance();
|
||||
auto fontType = LocalizationService::GetFontType(target);
|
||||
|
||||
if (service->GetOverrideFontApiValues())
|
||||
{
|
||||
fontFamily = ref new FontFamily(service->GetFontFamilyOverride());
|
||||
scaleFactor = service->GetFontScaleFactorOverride(fontType) / 100.0;
|
||||
fontWeight = service->GetFontWeightOverride();
|
||||
fOverrideFontWeight = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto languageFont = service->GetLanguageFont(fontType);
|
||||
fontFamily = ref new FontFamily(languageFont->FontFamily);
|
||||
scaleFactor = languageFont->ScaleFactor / 100.0;
|
||||
}
|
||||
|
||||
double sizeToUse = LocalizationService::GetFontSize(target) * scaleFactor;
|
||||
|
||||
auto control = dynamic_cast<Control^>(target);
|
||||
if (control)
|
||||
{
|
||||
control->FontFamily = fontFamily;
|
||||
if (fOverrideFontWeight)
|
||||
{
|
||||
control->FontWeight = fontWeight;
|
||||
}
|
||||
if (sizeToUse != 0.0)
|
||||
{
|
||||
control->FontSize = sizeToUse;
|
||||
}
|
||||
else
|
||||
{
|
||||
control->ClearValue(Control::FontSizeProperty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto textBlock = dynamic_cast<TextBlock^>(target);
|
||||
if (textBlock)
|
||||
{
|
||||
textBlock->FontFamily = fontFamily;
|
||||
if (fOverrideFontWeight)
|
||||
{
|
||||
textBlock->FontWeight = fontWeight;
|
||||
}
|
||||
if (sizeToUse != 0.0)
|
||||
{
|
||||
textBlock->FontSize = sizeToUse;
|
||||
}
|
||||
else
|
||||
{
|
||||
textBlock->ClearValue(TextBlock::FontSizeProperty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RichTextBlock^ richTextBlock = dynamic_cast<RichTextBlock^>(target);
|
||||
if (richTextBlock)
|
||||
{
|
||||
richTextBlock->FontFamily = fontFamily;
|
||||
if (fOverrideFontWeight)
|
||||
{
|
||||
richTextBlock->FontWeight = fontWeight;
|
||||
}
|
||||
if (sizeToUse != 0.0)
|
||||
{
|
||||
richTextBlock->FontSize = sizeToUse;
|
||||
}
|
||||
else
|
||||
{
|
||||
richTextBlock->ClearValue(RichTextBlock::FontSizeProperty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TextElement^ textElement = dynamic_cast<TextElement^>(target);
|
||||
if (textElement)
|
||||
{
|
||||
textElement->FontFamily = fontFamily;
|
||||
if (fOverrideFontWeight)
|
||||
{
|
||||
textElement->FontWeight = fontWeight;
|
||||
}
|
||||
if (sizeToUse != 0.0)
|
||||
{
|
||||
textElement->FontSize = sizeToUse;
|
||||
}
|
||||
else
|
||||
{
|
||||
textElement->ClearValue(TextElement::FontSizeProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If successful, returns a formatter that respects the user's regional format settings,
|
||||
// as configured by running intl.cpl.
|
||||
DecimalFormatter^ LocalizationService::GetRegionalSettingsAwareDecimalFormatter()
|
||||
{
|
||||
IIterable<String^>^ languageIdentifiers = LocalizationService::GetLanguageIdentifiers();
|
||||
if (languageIdentifiers != nullptr)
|
||||
{
|
||||
return ref new DecimalFormatter(languageIdentifiers, GlobalizationPreferences::HomeGeographicRegion);
|
||||
}
|
||||
|
||||
return ref new DecimalFormatter();
|
||||
}
|
||||
|
||||
// If successful, returns a formatter that respects the user's regional format settings,
|
||||
// as configured by running intl.cpl.
|
||||
//
|
||||
// This helper function creates a DateTimeFormatter with a TwentyFour hour clock
|
||||
DateTimeFormatter^ LocalizationService::GetRegionalSettingsAwareDateTimeFormatter(_In_ String^ format)
|
||||
{
|
||||
IIterable<String^>^ languageIdentifiers = LocalizationService::GetLanguageIdentifiers();
|
||||
if (languageIdentifiers == nullptr)
|
||||
{
|
||||
languageIdentifiers = ApplicationLanguages::Languages;
|
||||
}
|
||||
|
||||
return ref new DateTimeFormatter(format, languageIdentifiers);
|
||||
}
|
||||
|
||||
// If successful, returns a formatter that respects the user's regional format settings,
|
||||
// as configured by running intl.cpl.
|
||||
DateTimeFormatter^ LocalizationService::GetRegionalSettingsAwareDateTimeFormatter(
|
||||
_In_ String^ format,
|
||||
_In_ String^ calendarIdentifier,
|
||||
_In_ String^ clockIdentifier)
|
||||
{
|
||||
IIterable<String^>^ languageIdentifiers = LocalizationService::GetLanguageIdentifiers();
|
||||
if (languageIdentifiers == nullptr)
|
||||
{
|
||||
languageIdentifiers = ApplicationLanguages::Languages;
|
||||
}
|
||||
|
||||
return ref new DateTimeFormatter(
|
||||
format,
|
||||
languageIdentifiers,
|
||||
GlobalizationPreferences::HomeGeographicRegion,
|
||||
calendarIdentifier,
|
||||
clockIdentifier);
|
||||
}
|
||||
|
||||
CurrencyFormatter^ LocalizationService::GetRegionalSettingsAwareCurrencyFormatter()
|
||||
{
|
||||
String^ userCurrency = (GlobalizationPreferences::Currencies->Size > 0)
|
||||
? GlobalizationPreferences::Currencies->GetAt(0)
|
||||
: StringReference(DefaultCurrencyCode.data());
|
||||
|
||||
IIterable<String^>^ languageIdentifiers = LocalizationService::GetLanguageIdentifiers();
|
||||
if (languageIdentifiers == nullptr)
|
||||
{
|
||||
languageIdentifiers = ApplicationLanguages::Languages;
|
||||
}
|
||||
|
||||
auto currencyFormatter = ref new CurrencyFormatter(
|
||||
userCurrency,
|
||||
languageIdentifiers,
|
||||
GlobalizationPreferences::HomeGeographicRegion);
|
||||
|
||||
int fractionDigits = LocalizationSettings::GetInstance().GetCurrencyTrailingDigits();
|
||||
currencyFormatter->FractionDigits = fractionDigits;
|
||||
|
||||
return currencyFormatter;
|
||||
}
|
||||
|
||||
IIterable<String^>^ LocalizationService::GetLanguageIdentifiers()
|
||||
{
|
||||
WCHAR currentLocale[LOCALE_NAME_MAX_LENGTH] = {};
|
||||
int result = GetUserDefaultLocaleName(currentLocale, LOCALE_NAME_MAX_LENGTH);
|
||||
if (result != 0)
|
||||
{
|
||||
// GetUserDefaultLocaleName may return an invalid bcp47 language tag with trailing non-BCP47 friendly characters,
|
||||
// which if present would start with an underscore, for example sort order
|
||||
// (see https://msdn.microsoft.com/en-us/library/windows/desktop/dd373814(v=vs.85).aspx).
|
||||
// Therefore, if there is an underscore in the locale name, trim all characters from the underscore onwards.
|
||||
WCHAR* underscore = wcschr(currentLocale, L'_');
|
||||
if (underscore != nullptr)
|
||||
{
|
||||
*underscore = L'\0';
|
||||
}
|
||||
|
||||
String^ localeString = ref new String(currentLocale);
|
||||
// validate if the locale we have is valid
|
||||
// otherwise we fallback to the default.
|
||||
if (Language::IsWellFormed(localeString))
|
||||
{
|
||||
auto languageList = ref new Vector<String^>();
|
||||
languageList->Append(localeString);
|
||||
return languageList;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unordered_map<wstring, wstring> LocalizationService::GetTokenToReadableNameMap()
|
||||
{
|
||||
// Resources for the engine use numbers as keys. It's inconvenient, but also difficult to
|
||||
// change given that the engine heavily relies on perfect ordering of certain elements.
|
||||
// To compromise, we'll declare a map from engine resource key to automation name from the
|
||||
// standard project resources.
|
||||
static vector<pair<wstring, wstring>> s_parenEngineKeyResourceMap = {
|
||||
// Sine permutations
|
||||
make_pair<wstring, wstring>(L"67", L"SineDegrees"),
|
||||
make_pair<wstring, wstring>(L"73", L"SineRadians"),
|
||||
make_pair<wstring, wstring>(L"79", L"SineGradians"),
|
||||
make_pair<wstring, wstring>(L"70", L"InverseSineDegrees"),
|
||||
make_pair<wstring, wstring>(L"76", L"InverseSineRadians"),
|
||||
make_pair<wstring, wstring>(L"82", L"InverseSineGradians"),
|
||||
make_pair<wstring, wstring>(L"25", L"HyperbolicSine"),
|
||||
make_pair<wstring, wstring>(L"85", L"InverseHyperbolicSine"),
|
||||
|
||||
// Cosine permutations
|
||||
make_pair<wstring, wstring>(L"68", L"CosineDegrees"),
|
||||
make_pair<wstring, wstring>(L"74", L"CosineRadians"),
|
||||
make_pair<wstring, wstring>(L"80", L"CosineGradians"),
|
||||
make_pair<wstring, wstring>(L"71", L"InverseCosineDegrees"),
|
||||
make_pair<wstring, wstring>(L"77", L"InverseCosineRadians"),
|
||||
make_pair<wstring, wstring>(L"83", L"InverseCosineGradians"),
|
||||
make_pair<wstring, wstring>(L"26", L"HyperbolicCosine"),
|
||||
make_pair<wstring, wstring>(L"86", L"InverseHyperbolicCosine"),
|
||||
|
||||
// Tangent permutations
|
||||
make_pair<wstring, wstring>(L"69", L"TangentDegrees"),
|
||||
make_pair<wstring, wstring>(L"75", L"TangentRadians"),
|
||||
make_pair<wstring, wstring>(L"81", L"TangentGradians"),
|
||||
make_pair<wstring, wstring>(L"72", L"InverseTangentDegrees"),
|
||||
make_pair<wstring, wstring>(L"78", L"InverseTangentRadians"),
|
||||
make_pair<wstring, wstring>(L"84", L"InverseTangentGradians"),
|
||||
make_pair<wstring, wstring>(L"27", L"HyperbolicTangent"),
|
||||
make_pair<wstring, wstring>(L"87", L"InverseHyperbolicTangent"),
|
||||
|
||||
// Miscellaneous Scientific functions
|
||||
make_pair<wstring, wstring>(L"94", L"Factorial"),
|
||||
make_pair<wstring, wstring>(L"35", L"DegreeMinuteSecond"),
|
||||
make_pair<wstring, wstring>(L"28", L"NaturalLog"),
|
||||
make_pair<wstring, wstring>(L"91", L"Square")
|
||||
};
|
||||
|
||||
static vector<pair<wstring, wstring>> s_noParenEngineKeyResourceMap = {
|
||||
// Programmer mode functions
|
||||
make_pair<wstring, wstring>(L"9", L"LeftShift"),
|
||||
make_pair<wstring, wstring>(L"10", L"RightShift"),
|
||||
|
||||
// Y Root scientific function
|
||||
make_pair<wstring, wstring>(L"16", L"YRoot")
|
||||
};
|
||||
|
||||
unordered_map<wstring, wstring> tokenToReadableNameMap{};
|
||||
auto resProvider = AppResourceProvider::GetInstance();
|
||||
|
||||
static const wstring openParen = resProvider.GetCEngineString(StringReference(s_openParenResourceKey))->Data();
|
||||
|
||||
for (const auto& keyPair : s_parenEngineKeyResourceMap)
|
||||
{
|
||||
wstring engineStr = resProvider.GetCEngineString(StringReference(keyPair.first.c_str()))->Data();
|
||||
wstring automationName = resProvider.GetResourceString(StringReference(keyPair.second.c_str()))->Data();
|
||||
|
||||
tokenToReadableNameMap.emplace(engineStr + openParen, automationName);
|
||||
}
|
||||
s_parenEngineKeyResourceMap.clear();
|
||||
|
||||
for (const auto& keyPair : s_noParenEngineKeyResourceMap)
|
||||
{
|
||||
wstring engineStr = resProvider.GetCEngineString(StringReference(keyPair.first.c_str()))->Data();
|
||||
wstring automationName = resProvider.GetResourceString(StringReference(keyPair.second.c_str()))->Data();
|
||||
|
||||
tokenToReadableNameMap.emplace(engineStr, automationName);
|
||||
}
|
||||
s_noParenEngineKeyResourceMap.clear();
|
||||
|
||||
// Also replace hyphens with "minus"
|
||||
wstring minusText = resProvider.GetResourceString(L"minus")->Data();
|
||||
tokenToReadableNameMap.emplace(L"-", minusText);
|
||||
|
||||
return tokenToReadableNameMap;
|
||||
}
|
||||
|
||||
String^ LocalizationService::GetNarratorReadableToken(String^ rawToken)
|
||||
{
|
||||
static unordered_map<wstring, wstring> s_tokenToReadableNameMap = GetTokenToReadableNameMap();
|
||||
|
||||
auto itr = s_tokenToReadableNameMap.find(rawToken->Data());
|
||||
if (itr == s_tokenToReadableNameMap.end())
|
||||
{
|
||||
return rawToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
static const String^ openParen = AppResourceProvider::GetInstance().GetCEngineString(StringReference(s_openParenResourceKey));
|
||||
return ref new String(itr->second.c_str()) + L" " + openParen;
|
||||
}
|
||||
}
|
||||
|
||||
String^ LocalizationService::GetNarratorReadableString(String^ rawString)
|
||||
{
|
||||
wstringstream readableString{};
|
||||
readableString << L"";
|
||||
|
||||
wstring asWstring = rawString->Data();
|
||||
for (const auto& c : asWstring)
|
||||
{
|
||||
readableString << LocalizationService::GetNarratorReadableToken(L"" + c)->Data();
|
||||
}
|
||||
|
||||
return ref new String(readableString.str().c_str());
|
||||
}
|
83
src/CalcViewModel/Common/LocalizationService.h
Normal file
83
src/CalcViewModel/Common/LocalizationService.h
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp { namespace Common
|
||||
{
|
||||
namespace LocalizationServiceProperties
|
||||
{
|
||||
static constexpr std::wstring_view DefaultCurrencyCode{ L"USD" };
|
||||
}
|
||||
|
||||
public enum class LanguageFontType
|
||||
{
|
||||
UIText,
|
||||
UICaption,
|
||||
};
|
||||
|
||||
public ref class LocalizationService sealed
|
||||
{
|
||||
public:
|
||||
|
||||
DEPENDENCY_PROPERTY_OWNER(LocalizationService);
|
||||
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_DEFAULT_AND_CALLBACK(LanguageFontType, FontType, LanguageFontType::UIText);
|
||||
DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(double, FontSize);
|
||||
|
||||
internal:
|
||||
static LocalizationService^ GetInstance();
|
||||
|
||||
Windows::UI::Xaml::FlowDirection GetFlowDirection();
|
||||
bool IsRtlLayout();
|
||||
bool GetOverrideFontApiValues();
|
||||
Platform::String^ GetLanguage();
|
||||
Windows::UI::Xaml::Media::FontFamily^ GetLanguageFontFamilyForType(LanguageFontType fontType);
|
||||
Platform::String^ GetFontFamilyOverride();
|
||||
Windows::UI::Text::FontWeight GetFontWeightOverride();
|
||||
double GetFontScaleFactorOverride(LanguageFontType fontType);
|
||||
|
||||
static Windows::Globalization::NumberFormatting::DecimalFormatter^ GetRegionalSettingsAwareDecimalFormatter();
|
||||
static Windows::Globalization::DateTimeFormatting::DateTimeFormatter^ GetRegionalSettingsAwareDateTimeFormatter(_In_ Platform::String^ format);
|
||||
static Windows::Globalization::DateTimeFormatting::DateTimeFormatter^ GetRegionalSettingsAwareDateTimeFormatter(
|
||||
_In_ Platform::String^ format,
|
||||
_In_ Platform::String^ calendarIdentifier,
|
||||
_In_ Platform::String^ clockIdentifier);
|
||||
|
||||
static Windows::Globalization::NumberFormatting::CurrencyFormatter^ GetRegionalSettingsAwareCurrencyFormatter();
|
||||
|
||||
static Platform::String^ GetNarratorReadableToken(Platform::String^ rawToken);
|
||||
static Platform::String^ GetNarratorReadableString(Platform::String^ rawString);
|
||||
|
||||
private:
|
||||
Windows::Globalization::Fonts::LanguageFont^ GetLanguageFont(LanguageFontType fontType);
|
||||
Windows::UI::Text::FontWeight ParseFontWeight(Platform::String^ fontWeight);
|
||||
|
||||
static Windows::Foundation::Collections::IIterable<Platform::String^>^ GetLanguageIdentifiers();
|
||||
|
||||
// Attached property callbacks
|
||||
static void OnFontTypePropertyChanged(Windows::UI::Xaml::DependencyObject^ target, LanguageFontType oldValue, LanguageFontType newValue);
|
||||
static void OnFontWeightPropertyChanged(Windows::UI::Xaml::DependencyObject^ target, Windows::UI::Text::FontWeight oldValue, Windows::UI::Text::FontWeight newValue);
|
||||
static void OnFontSizePropertyChanged(Windows::UI::Xaml::DependencyObject^ target, double oldValue, double newValue);
|
||||
|
||||
static void UpdateFontFamilyAndSize(Windows::UI::Xaml::DependencyObject^ target);
|
||||
|
||||
static std::unordered_map<std::wstring, std::wstring> GetTokenToReadableNameMap();
|
||||
|
||||
private:
|
||||
LocalizationService();
|
||||
|
||||
static LocalizationService^ s_singletonInstance;
|
||||
|
||||
Windows::Globalization::Fonts::LanguageFontGroup^ m_fontGroup;
|
||||
Platform::String^ m_language;
|
||||
Windows::UI::Xaml::FlowDirection m_flowDirection;
|
||||
bool m_overrideFontApiValues;
|
||||
Platform::String^ m_fontFamilyOverride;
|
||||
Windows::UI::Text::FontWeight m_fontWeightOverride;
|
||||
double m_uiTextFontScaleFactorOverride;
|
||||
double m_uiCaptionFontScaleFactorOverride;
|
||||
};
|
||||
|
||||
}}
|
||||
|
385
src/CalcViewModel/Common/LocalizationSettings.h
Normal file
385
src/CalcViewModel/Common/LocalizationSettings.h
Normal file
@@ -0,0 +1,385 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
#include "LocalizationService.h"
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
class LocalizationSettings
|
||||
{
|
||||
private:
|
||||
LocalizationSettings()
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
// Use DecimalFormatter as it respects the locale and the user setting
|
||||
Windows::Globalization::NumberFormatting::DecimalFormatter^ formatter;
|
||||
formatter = CalculatorApp::Common::LocalizationService::GetRegionalSettingsAwareDecimalFormatter();
|
||||
formatter->FractionDigits = 0;
|
||||
formatter->IsDecimalPointAlwaysDisplayed = false;
|
||||
|
||||
for (unsigned int i = 0; i < 10; i++)
|
||||
{
|
||||
m_digitSymbols.at(i) = formatter->FormatUInt(i)->Data()[0];
|
||||
}
|
||||
|
||||
result = ResolveLocaleName(formatter->ResolvedLanguage->Data(),
|
||||
m_resolvedName,
|
||||
LOCALE_NAME_MAX_LENGTH);
|
||||
if (result == 0)
|
||||
{
|
||||
throw std::runtime_error("Unexpected error resolving locale name");
|
||||
}
|
||||
else
|
||||
{
|
||||
wchar_t decimalString[LocaleSettingBufferSize] = L"";
|
||||
result = GetLocaleInfoEx(m_resolvedName,
|
||||
LOCALE_SDECIMAL,
|
||||
decimalString,
|
||||
ARRAYSIZE(decimalString));
|
||||
if (result == 0)
|
||||
{
|
||||
throw std::runtime_error("Unexpected error while getting locale info");
|
||||
}
|
||||
|
||||
wchar_t groupingSymbolString[LocaleSettingBufferSize] = L"";
|
||||
result = GetLocaleInfoEx(m_resolvedName,
|
||||
LOCALE_STHOUSAND,
|
||||
groupingSymbolString,
|
||||
ARRAYSIZE(groupingSymbolString));
|
||||
if (result == 0)
|
||||
{
|
||||
throw std::runtime_error("Unexpected error while getting locale info");
|
||||
}
|
||||
|
||||
wchar_t numberGroupingString[LocaleSettingBufferSize] = L"";
|
||||
result = GetLocaleInfoEx(m_resolvedName,
|
||||
LOCALE_SGROUPING,
|
||||
numberGroupingString,
|
||||
ARRAYSIZE(numberGroupingString));
|
||||
if (result == 0)
|
||||
{
|
||||
throw std::runtime_error("Unexpected error while getting locale info");
|
||||
}
|
||||
|
||||
// Get locale info for List Separator, eg. comma is used in many locales
|
||||
wchar_t listSeparatorString[4] = L"";
|
||||
result = ::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT,
|
||||
LOCALE_SLIST,
|
||||
listSeparatorString,
|
||||
ARRAYSIZE(listSeparatorString)); // Max length of the expected return value is 4
|
||||
if (result == 0)
|
||||
{
|
||||
throw std::runtime_error("Unexpected error while getting locale info");
|
||||
}
|
||||
|
||||
int currencyTrailingDigits = 0;
|
||||
result = GetLocaleInfoEx(m_resolvedName,
|
||||
LOCALE_ICURRDIGITS | LOCALE_RETURN_NUMBER,
|
||||
(LPWSTR)¤cyTrailingDigits,
|
||||
sizeof(currencyTrailingDigits) / sizeof(WCHAR));
|
||||
if (result == 0)
|
||||
{
|
||||
throw std::runtime_error("Unexpected error while getting locale info");
|
||||
}
|
||||
|
||||
// Currency symbol precedence is either 0 or 1.
|
||||
// A value of 0 indicates the symbol follows the currency value.
|
||||
int currencySymbolPrecedence = 1;
|
||||
result = GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT,
|
||||
LOCALE_IPOSSYMPRECEDES | LOCALE_RETURN_NUMBER,
|
||||
(LPWSTR)¤cySymbolPrecedence,
|
||||
sizeof(currencySymbolPrecedence) / sizeof(WCHAR));
|
||||
|
||||
// As CalcEngine only supports the first character of the decimal separator,
|
||||
// Only first character of the decimal separator string is supported.
|
||||
m_decimalSeparator = decimalString[0];
|
||||
m_numberGroupSeparator = groupingSymbolString[0];
|
||||
m_numberGrouping = numberGroupingString;
|
||||
m_listSeparator = listSeparatorString;
|
||||
m_currencyTrailingDigits = currencyTrailingDigits;
|
||||
m_currencySymbolPrecedence = currencySymbolPrecedence;
|
||||
}
|
||||
|
||||
// Get the system calendar type
|
||||
// Note: This function returns 0 on failure.
|
||||
// We'll ignore the failure in that case and the CalendarIdentifier would get set to GregorianCalendar.
|
||||
CALID calId;
|
||||
::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT,
|
||||
LOCALE_ICALENDARTYPE | LOCALE_RETURN_NUMBER,
|
||||
reinterpret_cast<PWSTR>(&calId),
|
||||
sizeof(calId));
|
||||
|
||||
m_calendarIdentifier = GetCalendarIdentifierFromCalid(calId);
|
||||
|
||||
// Get FirstDayOfWeek Date and Time setting
|
||||
wchar_t day[80] = L"";
|
||||
::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT,
|
||||
LOCALE_IFIRSTDAYOFWEEK, // The first day in a week
|
||||
reinterpret_cast<PWSTR>(day), // Argument is of type PWSTR
|
||||
ARRAYSIZE(day)); // Max return size are 80 characters
|
||||
|
||||
// The LOCALE_IFIRSTDAYOFWEEK integer value varies from 0, 1, .. 6 for Monday, Tuesday, ... Sunday
|
||||
// DayOfWeek enum value varies from 0, 1, .. 6 for Sunday, Monday, ... Saturday
|
||||
// Hence, DayOfWeek = (valueof(LOCALE_IFIRSTDAYOFWEEK) + 1) % 7
|
||||
m_firstDayOfWeek = static_cast<Windows::Globalization::DayOfWeek>((_wtoi(day) + 1) % 7); // static cast int to DayOfWeek enum
|
||||
}
|
||||
|
||||
public:
|
||||
// A LocalizationSettings object is not copyable.
|
||||
LocalizationSettings(const LocalizationSettings&) = delete;
|
||||
LocalizationSettings& operator=(const LocalizationSettings&) = delete;
|
||||
|
||||
// A LocalizationSettings object is not moveable.
|
||||
LocalizationSettings(LocalizationSettings&&) = delete;
|
||||
LocalizationSettings& operator=(LocalizationSettings&&) = delete;
|
||||
|
||||
// Provider of the singleton LocalizationSettings instance.
|
||||
static const LocalizationSettings& GetInstance()
|
||||
{
|
||||
static const LocalizationSettings localizationSettings;
|
||||
|
||||
return localizationSettings;
|
||||
}
|
||||
|
||||
Platform::String^ GetLocaleName() const
|
||||
{
|
||||
return ref new Platform::String(m_resolvedName);
|
||||
}
|
||||
|
||||
bool IsDigitEnUsSetting() const
|
||||
{
|
||||
if (this->GetDigitSymbolFromEnUsDigit('0') == L'0')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LocalizeDisplayValue(_Inout_ std::wstring* stringToLocalize) const
|
||||
{
|
||||
if (IsDigitEnUsSetting())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (wchar_t& ch : *stringToLocalize)
|
||||
{
|
||||
if (IsEnUsDigit(ch))
|
||||
{
|
||||
ch = GetDigitSymbolFromEnUsDigit(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform::String^ GetEnglishValueFromLocalizedDigits(const std::wstring& localizedString) const
|
||||
{
|
||||
if (m_resolvedName == L"en-US")
|
||||
{
|
||||
return ref new Platform::String(localizedString.c_str());
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
size_t length = localizedString.size();
|
||||
std::unique_ptr<wchar_t[]> englishString(new wchar_t[length + 1]); // +1 for the null termination
|
||||
|
||||
for (; i < length; ++i)
|
||||
{
|
||||
wchar_t ch = localizedString[i];
|
||||
if (!IsEnUsDigit(ch))
|
||||
{
|
||||
for (int j = 0; j < 10; ++j)
|
||||
{
|
||||
if (ch == m_digitSymbols[j])
|
||||
{
|
||||
ch = j.ToString()->Data()[0];
|
||||
break;
|
||||
//ch = val - L'0';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ch == m_decimalSeparator)
|
||||
{
|
||||
ch = L'.';
|
||||
}
|
||||
englishString[i] = ch;
|
||||
}
|
||||
englishString[i] = '\0';
|
||||
|
||||
return ref new Platform::String(englishString.get());
|
||||
}
|
||||
|
||||
bool IsEnUsDigit(const wchar_t digit) const
|
||||
{
|
||||
if (digit >= L'0' && digit <= L'9')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsLocalizedDigit(const wchar_t digit) const
|
||||
{
|
||||
for (auto dig : m_digitSymbols)
|
||||
{
|
||||
if (digit == dig)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsLocalizedHexDigit(const wchar_t digit) const
|
||||
{
|
||||
if (IsLocalizedDigit(digit))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto dig : s_hexSymbols)
|
||||
{
|
||||
if (digit == dig)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t GetDigitSymbolFromEnUsDigit(wchar_t digitSymbol) const
|
||||
{
|
||||
assert(digitSymbol >= L'0' && digitSymbol <= L'9');
|
||||
int digit = digitSymbol - L'0';
|
||||
return m_digitSymbols.at(digit); // throws on out of range
|
||||
}
|
||||
|
||||
wchar_t GetDecimalSeparator() const
|
||||
{
|
||||
return m_decimalSeparator;
|
||||
}
|
||||
|
||||
wchar_t GetNumberGroupSeparator() const
|
||||
{
|
||||
return m_numberGroupSeparator;
|
||||
}
|
||||
|
||||
std::wstring GetDecimalSeparatorStr() const
|
||||
{
|
||||
std::wstring result;
|
||||
result.push_back(m_decimalSeparator);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring GetNumberGroupingSeparatorStr() const
|
||||
{
|
||||
std::wstring result;
|
||||
result.push_back(m_numberGroupSeparator);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring GetNumberGroupingStr() const
|
||||
{
|
||||
return m_numberGrouping;
|
||||
}
|
||||
|
||||
void RemoveGroupSeparators(const wchar_t* value, const size_t length, std::wstring* rawValue) const
|
||||
{
|
||||
rawValue->clear();
|
||||
rawValue->reserve(length);
|
||||
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
if (value[i] != L' ' && value[i] != m_numberGroupSeparator)
|
||||
{
|
||||
rawValue->append(1, value[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform::String^ GetCalendarIdentifier() const
|
||||
{
|
||||
return m_calendarIdentifier;
|
||||
}
|
||||
|
||||
std::wstring GetListSeparator() const
|
||||
{
|
||||
return m_listSeparator;
|
||||
}
|
||||
|
||||
Windows::Globalization::DayOfWeek GetFirstDayOfWeek() const
|
||||
{
|
||||
return m_firstDayOfWeek;
|
||||
}
|
||||
|
||||
int GetCurrencyTrailingDigits() const
|
||||
{
|
||||
return m_currencyTrailingDigits;
|
||||
}
|
||||
|
||||
int GetCurrencySymbolPrecedence() const
|
||||
{
|
||||
return m_currencySymbolPrecedence;
|
||||
}
|
||||
|
||||
private:
|
||||
static Platform::String^ GetCalendarIdentifierFromCalid(CALID calId)
|
||||
{
|
||||
switch (calId)
|
||||
{
|
||||
case CAL_GREGORIAN:
|
||||
case CAL_GREGORIAN_ARABIC:
|
||||
case CAL_GREGORIAN_ME_FRENCH:
|
||||
case CAL_GREGORIAN_US:
|
||||
case CAL_GREGORIAN_XLIT_ENGLISH:
|
||||
case CAL_GREGORIAN_XLIT_FRENCH:
|
||||
return Windows::Globalization::CalendarIdentifiers::Gregorian;
|
||||
|
||||
case CAL_HEBREW:
|
||||
return Windows::Globalization::CalendarIdentifiers::Hebrew;
|
||||
|
||||
case CAL_HIJRI:
|
||||
case CAL_PERSIAN:
|
||||
return Windows::Globalization::CalendarIdentifiers::Hijri;
|
||||
|
||||
case CAL_JAPAN:
|
||||
return Windows::Globalization::CalendarIdentifiers::Japanese;
|
||||
|
||||
case CAL_KOREA:
|
||||
return Windows::Globalization::CalendarIdentifiers::Korean;
|
||||
|
||||
case CAL_TAIWAN:
|
||||
return Windows::Globalization::CalendarIdentifiers::Taiwan;
|
||||
|
||||
case CAL_THAI:
|
||||
return Windows::Globalization::CalendarIdentifiers::Thai;
|
||||
|
||||
case CAL_UMALQURA:
|
||||
return Windows::Globalization::CalendarIdentifiers::UmAlQura;
|
||||
|
||||
// Gregorian will be the default Calendar Type
|
||||
default:
|
||||
return Windows::Globalization::CalendarIdentifiers::Gregorian;
|
||||
}
|
||||
}
|
||||
|
||||
wchar_t m_decimalSeparator;
|
||||
wchar_t m_numberGroupSeparator;
|
||||
std::wstring m_numberGrouping;
|
||||
std::array<wchar_t, 10> m_digitSymbols;
|
||||
// Hexadecimal characters are not currently localized
|
||||
static constexpr std::array<wchar_t, 6> s_hexSymbols{ L'A', L'B', L'C', L'D', L'E', L'F' };
|
||||
std::wstring m_listSeparator;
|
||||
Platform::String^ m_calendarIdentifier;
|
||||
Windows::Globalization::DayOfWeek m_firstDayOfWeek;
|
||||
int m_currencySymbolPrecedence;
|
||||
wchar_t m_resolvedName[LOCALE_NAME_MAX_LENGTH];
|
||||
int m_currencyTrailingDigits;
|
||||
static const unsigned int LocaleSettingBufferSize = 16;
|
||||
};
|
||||
}
|
||||
}
|
64
src/CalcViewModel/Common/LocalizationStringUtil.h
Normal file
64
src/CalcViewModel/Common/LocalizationStringUtil.h
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AppResourceProvider.h"
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
class LocalizationStringUtil
|
||||
{
|
||||
public:
|
||||
static std::wstring GetLocalizedString(const wchar_t* pMessage, ...)
|
||||
{
|
||||
std::wstring returnString = L"";
|
||||
const UINT32 length = 1024;
|
||||
std::unique_ptr<wchar_t[]> spBuffer = std::unique_ptr<wchar_t[]>(new wchar_t[length]);
|
||||
va_list args = NULL;
|
||||
va_start(args, pMessage);
|
||||
DWORD fmtReturnVal = FormatMessage(FORMAT_MESSAGE_FROM_STRING,
|
||||
pMessage,
|
||||
0,
|
||||
0,
|
||||
spBuffer.get(),
|
||||
length,
|
||||
&args);
|
||||
va_end(args);
|
||||
|
||||
if (fmtReturnVal != 0)
|
||||
{
|
||||
returnString = spBuffer.get();
|
||||
}
|
||||
|
||||
return returnString;
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
static Platform::String^ GetLocalizedNarratorAnnouncement(Platform::String^ resourceKey, Platform::String^& formatVariable, T*... params)
|
||||
{
|
||||
EnsureInitialization(resourceKey, formatVariable);
|
||||
return StringReference(GetLocalizedString(formatVariable->Data(), params...).c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
static void EnsureInitialization(Platform::String^ resourceKey, Platform::String^& formatVariable)
|
||||
{
|
||||
if (resourceKey == nullptr || resourceKey->IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the formatVariable already has a value, we don't need to set it again. Simply return.
|
||||
if (formatVariable != nullptr && !formatVariable->IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
formatVariable = AppResourceProvider::GetInstance().GetResourceString(resourceKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
145
src/CalcViewModel/Common/MyVirtualKey.h
Normal file
145
src/CalcViewModel/Common/MyVirtualKey.h
Normal file
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
public enum class MyVirtualKey
|
||||
{
|
||||
None = 0,
|
||||
LeftButton = 1,
|
||||
RightButton = 2,
|
||||
Cancel = 3,
|
||||
MiddleButton = 4,
|
||||
XButton1 = 5,
|
||||
XButton2 = 6,
|
||||
Back = 8,
|
||||
Tab = 9,
|
||||
Clear = 12,
|
||||
Enter = 13,
|
||||
Shift = 16,
|
||||
Control = 17,
|
||||
Menu = 18,
|
||||
Pause = 19,
|
||||
CapitalLock = 20,
|
||||
Kana = 21,
|
||||
Hangul = 21,
|
||||
Junja = 23,
|
||||
Final = 24,
|
||||
Hanja = 25,
|
||||
Kanji = 25,
|
||||
Escape = 27,
|
||||
Convert = 28,
|
||||
NonConvert = 29,
|
||||
Accept = 30,
|
||||
ModeChange = 31,
|
||||
Space = 32,
|
||||
PageUp = 33,
|
||||
PageDown = 34,
|
||||
End = 35,
|
||||
Home = 36,
|
||||
Left = 37,
|
||||
Up = 38,
|
||||
Right = 39,
|
||||
Down = 40,
|
||||
Select = 41,
|
||||
Print = 42,
|
||||
Execute = 43,
|
||||
Snapshot = 44,
|
||||
Insert = 45,
|
||||
Delete = 46,
|
||||
Help = 47,
|
||||
Number0 = 48,
|
||||
Number1 = 49,
|
||||
Number2 = 50,
|
||||
Number3 = 51,
|
||||
Number4 = 52,
|
||||
Number5 = 53,
|
||||
Number6 = 54,
|
||||
Number7 = 55,
|
||||
Number8 = 56,
|
||||
Number9 = 57,
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
LeftWindows = 91,
|
||||
RightWindows = 92,
|
||||
Application = 93,
|
||||
Sleep = 95,
|
||||
NumberPad0 = 96,
|
||||
NumberPad1 = 97,
|
||||
NumberPad2 = 98,
|
||||
NumberPad3 = 99,
|
||||
NumberPad4 = 100,
|
||||
NumberPad5 = 101,
|
||||
NumberPad6 = 102,
|
||||
NumberPad7 = 103,
|
||||
NumberPad8 = 104,
|
||||
NumberPad9 = 105,
|
||||
Multiply = 106,
|
||||
Add = 107,
|
||||
Separator = 108,
|
||||
Subtract = 109,
|
||||
Decimal = 110,
|
||||
Divide = 111,
|
||||
F1 = 112,
|
||||
F2 = 113,
|
||||
F3 = 114,
|
||||
F4 = 115,
|
||||
F5 = 116,
|
||||
F6 = 117,
|
||||
F7 = 118,
|
||||
F8 = 119,
|
||||
F9 = 120,
|
||||
F10 = 121,
|
||||
F11 = 122,
|
||||
F12 = 123,
|
||||
F13 = 124,
|
||||
F14 = 125,
|
||||
F15 = 126,
|
||||
F16 = 127,
|
||||
F17 = 128,
|
||||
F18 = 129,
|
||||
F19 = 130,
|
||||
F20 = 131,
|
||||
F21 = 132,
|
||||
F22 = 133,
|
||||
F23 = 134,
|
||||
F24 = 135,
|
||||
NumberKeyLock = 144,
|
||||
Scroll = 145,
|
||||
LeftShift = 160,
|
||||
RightShift = 161,
|
||||
LeftControl = 162,
|
||||
RightControl = 163,
|
||||
LeftMenu = 164,
|
||||
RightMenu = 165
|
||||
};
|
||||
}
|
||||
}
|
379
src/CalcViewModel/Common/NavCategory.cpp
Normal file
379
src/CalcViewModel/Common/NavCategory.cpp
Normal file
@@ -0,0 +1,379 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "NavCategory.h"
|
||||
#include "AppResourceProvider.h"
|
||||
#include "Common\LocalizationStringUtil.h"
|
||||
|
||||
using namespace CalculatorApp;
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace Platform;
|
||||
using namespace Platform::Collections;
|
||||
using namespace std;
|
||||
using namespace Windows::Foundation::Collections;
|
||||
|
||||
namespace UCM = UnitConversionManager;
|
||||
|
||||
// Calculator categories always support negative and positive.
|
||||
static constexpr bool SUPPORTS_ALL = true;
|
||||
|
||||
// Converter categories usually only support positive.
|
||||
static constexpr bool SUPPORTS_NEGATIVE = true;
|
||||
static constexpr bool POSITIVE_ONLY = false;
|
||||
|
||||
// The order of items in this list determines the order of groups in the menu.
|
||||
static constexpr array<const NavCategoryGroupInitializer, 2> s_categoryGroupManifest = {
|
||||
NavCategoryGroupInitializer { CategoryGroupType::Calculator, L"CalculatorModeTextCaps", L"CalculatorModeText", L"CalculatorModePluralText"},
|
||||
NavCategoryGroupInitializer { CategoryGroupType::Converter, L"ConverterModeTextCaps", L"ConverterModeText", L"ConverterModePluralText" }
|
||||
};
|
||||
|
||||
// vvv THESE CONSTANTS SHOULD NEVER CHANGE vvv
|
||||
static constexpr int STANDARD_ID = 0;
|
||||
static constexpr int SCIENTIFIC_ID = 1;
|
||||
static constexpr int PROGRAMMER_ID = 2;
|
||||
static constexpr int DATE_ID = 3;
|
||||
static constexpr int VOLUME_ID = 4;
|
||||
static constexpr int LENGTH_ID = 5;
|
||||
static constexpr int WEIGHT_ID = 6;
|
||||
static constexpr int TEMPERATURE_ID = 7;
|
||||
static constexpr int ENERGY_ID = 8;
|
||||
static constexpr int AREA_ID = 9;
|
||||
static constexpr int SPEED_ID = 10;
|
||||
static constexpr int TIME_ID = 11;
|
||||
static constexpr int POWER_ID = 12;
|
||||
static constexpr int DATA_ID = 13;
|
||||
static constexpr int PRESSURE_ID = 14;
|
||||
static constexpr int ANGLE_ID = 15;
|
||||
static constexpr int CURRENCY_ID = 16;
|
||||
// ^^^ THESE CONSTANTS SHOULD NEVER CHANGE ^^^
|
||||
|
||||
// The order of items in this list determines the order of items in the menu.
|
||||
static constexpr array<const NavCategoryInitializer, 17> s_categoryManifest = {
|
||||
NavCategoryInitializer { ViewMode::Standard, STANDARD_ID, L"Standard", L"StandardMode", L"\uE8EF", CategoryGroupType::Calculator, MyVirtualKey::Number1, SUPPORTS_ALL },
|
||||
NavCategoryInitializer { ViewMode::Scientific, SCIENTIFIC_ID, L"Scientific", L"ScientificMode", L"\uF196", CategoryGroupType::Calculator, MyVirtualKey::Number2, SUPPORTS_ALL },
|
||||
NavCategoryInitializer { ViewMode::Programmer, PROGRAMMER_ID, L"Programmer", L"ProgrammerMode", L"\uECCE", CategoryGroupType::Calculator, MyVirtualKey::Number3, SUPPORTS_ALL },
|
||||
NavCategoryInitializer { ViewMode::Date, DATE_ID, L"Date", L"DateCalculationMode", L"\uE787", CategoryGroupType::Calculator, MyVirtualKey::Number4, SUPPORTS_ALL },
|
||||
NavCategoryInitializer { ViewMode::Currency, CURRENCY_ID, L"Currency", L"CategoryName_Currency", L"\uEB0D", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Volume, VOLUME_ID, L"Volume", L"CategoryName_Volume", L"\uF1AA", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Length, LENGTH_ID, L"Length", L"CategoryName_Length", L"\uECC6", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Weight, WEIGHT_ID, L"Weight and Mass", L"CategoryName_Weight", L"\uF4C1", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Temperature, TEMPERATURE_ID, L"Temperature", L"CategoryName_Temperature", L"\uE7A3", CategoryGroupType::Converter, MyVirtualKey::None, SUPPORTS_NEGATIVE },
|
||||
NavCategoryInitializer { ViewMode::Energy, ENERGY_ID, L"Energy", L"CategoryName_Energy", L"\uECAD", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Area, AREA_ID, L"Area", L"CategoryName_Area", L"\uE809", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Speed, SPEED_ID, L"Speed", L"CategoryName_Speed", L"\uEADA", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Time, TIME_ID, L"Time", L"CategoryName_Time", L"\uE917", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Power, POWER_ID, L"Power", L"CategoryName_Power", L"\uE945", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Data, DATA_ID, L"Data", L"CategoryName_Data", L"\uF20F", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Pressure, PRESSURE_ID, L"Pressure", L"CategoryName_Pressure", L"\uEC4A", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY },
|
||||
NavCategoryInitializer { ViewMode::Angle, ANGLE_ID, L"Angle", L"CategoryName_Angle", L"\uF515", CategoryGroupType::Converter, MyVirtualKey::None, POSITIVE_ONLY }
|
||||
};
|
||||
|
||||
// This function should only be used when storing the mode to app data.
|
||||
int NavCategory::Serialize(ViewMode mode)
|
||||
{
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[mode](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return initializer.viewMode == mode;
|
||||
});
|
||||
|
||||
return (iter != s_categoryManifest.end())
|
||||
? iter->serializationId
|
||||
: -1;
|
||||
}
|
||||
|
||||
// This function should only be used when restoring the mode from app data.
|
||||
ViewMode NavCategory::Deserialize(Platform::Object^ obj)
|
||||
{
|
||||
// If we cast directly to ViewMode we will fail
|
||||
// because we technically store an int.
|
||||
// Need to cast to int, then ViewMode.
|
||||
auto boxed = dynamic_cast<Box<int>^>(obj);
|
||||
if (boxed != nullptr)
|
||||
{
|
||||
int serializationId = boxed->Value;
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[serializationId](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return initializer.serializationId == serializationId;
|
||||
});
|
||||
|
||||
if (iter != s_categoryManifest.end())
|
||||
{
|
||||
return iter->viewMode;
|
||||
}
|
||||
}
|
||||
|
||||
return ViewMode::None;
|
||||
}
|
||||
|
||||
bool NavCategory::IsValidViewMode(ViewMode mode)
|
||||
{
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[mode](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return initializer.viewMode == mode;
|
||||
});
|
||||
|
||||
return iter != s_categoryManifest.end();
|
||||
}
|
||||
|
||||
bool NavCategory::IsCalculatorViewMode(ViewMode mode)
|
||||
{
|
||||
// Historically, Date Calculator is not a Calculator mode
|
||||
// even though it is in the Calculator category.
|
||||
return !IsDateCalculatorViewMode(mode) && IsModeInCategoryGroup(mode, CategoryGroupType::Calculator);
|
||||
}
|
||||
|
||||
bool NavCategory::IsDateCalculatorViewMode(ViewMode mode)
|
||||
{
|
||||
return mode == ViewMode::Date;
|
||||
}
|
||||
|
||||
bool NavCategory::IsConverterViewMode(ViewMode mode)
|
||||
{
|
||||
return IsModeInCategoryGroup(mode, CategoryGroupType::Converter);
|
||||
}
|
||||
|
||||
bool NavCategory::IsModeInCategoryGroup(ViewMode mode, CategoryGroupType type)
|
||||
{
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[mode, type](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return initializer.viewMode == mode && initializer.groupType == type;
|
||||
});
|
||||
|
||||
return iter != s_categoryManifest.end();
|
||||
}
|
||||
|
||||
String^ NavCategory::GetFriendlyName(ViewMode mode)
|
||||
{
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[mode](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return initializer.viewMode == mode;
|
||||
});
|
||||
|
||||
return (iter != s_categoryManifest.end())
|
||||
? StringReference(iter->friendlyName)
|
||||
: L"None";
|
||||
}
|
||||
|
||||
ViewMode NavCategory::GetViewModeForFriendlyName(String^ name)
|
||||
{
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[name](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return wcscmp(initializer.friendlyName, name->Data()) == 0;
|
||||
});
|
||||
|
||||
return (iter != s_categoryManifest.end())
|
||||
? iter->viewMode
|
||||
: ViewMode::None;
|
||||
}
|
||||
|
||||
String^ NavCategory::GetNameResourceKey(ViewMode mode)
|
||||
{
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[mode](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return initializer.viewMode == mode;
|
||||
});
|
||||
|
||||
return (iter != s_categoryManifest.end())
|
||||
? StringReference(iter->nameResourceKey) + "Text"
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
CategoryGroupType NavCategory::GetGroupType(ViewMode mode)
|
||||
{
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[mode](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return initializer.viewMode == mode;
|
||||
});
|
||||
|
||||
return (iter != s_categoryManifest.end())
|
||||
? iter->groupType
|
||||
: CategoryGroupType::None;
|
||||
}
|
||||
|
||||
// GetIndex is 0-based, GetPostion is 1-based
|
||||
int NavCategory::GetIndex(ViewMode mode)
|
||||
{
|
||||
int position = NavCategory::GetPosition(mode);
|
||||
return max(-1, position - 1);
|
||||
}
|
||||
|
||||
int NavCategory::GetFlatIndex(ViewMode mode)
|
||||
{
|
||||
int index = -1;
|
||||
CategoryGroupType type = CategoryGroupType::None;
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[mode, &type, &index](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
index++;
|
||||
if (initializer.groupType != type)
|
||||
{
|
||||
type = initializer.groupType;
|
||||
index++;
|
||||
}
|
||||
|
||||
return initializer.viewMode == mode;
|
||||
});
|
||||
|
||||
return (iter != s_categoryManifest.end())
|
||||
? index
|
||||
: -1;
|
||||
}
|
||||
|
||||
// GetIndex is 0-based, GetPostion is 1-based
|
||||
int NavCategory::GetIndexInGroup(ViewMode mode, CategoryGroupType type)
|
||||
{
|
||||
int index = -1;
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[mode, type, &index](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
if (initializer.groupType == type)
|
||||
{
|
||||
index++;
|
||||
return initializer.viewMode == mode;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return (iter != s_categoryManifest.end())
|
||||
? index
|
||||
: -1;
|
||||
}
|
||||
|
||||
// GetIndex is 0-based, GetPostion is 1-based
|
||||
int NavCategory::GetPosition(ViewMode mode)
|
||||
{
|
||||
int position = 0;
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[mode, &position](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
position++;
|
||||
return initializer.viewMode == mode;
|
||||
});
|
||||
|
||||
return (iter != s_categoryManifest.end())
|
||||
? position
|
||||
: -1;
|
||||
}
|
||||
|
||||
ViewMode NavCategory::GetViewModeForVirtualKey(MyVirtualKey virtualKey)
|
||||
{
|
||||
auto iter = find_if(begin(s_categoryManifest), end(s_categoryManifest),
|
||||
[virtualKey](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return initializer.virtualKey == virtualKey;
|
||||
});
|
||||
|
||||
return (iter != s_categoryManifest.end())
|
||||
? iter->viewMode
|
||||
: ViewMode::None;
|
||||
}
|
||||
|
||||
vector<MyVirtualKey> NavCategory::GetCategoryAcceleratorKeys()
|
||||
{
|
||||
vector<MyVirtualKey> accelerators{};
|
||||
for (auto category : s_categoryManifest)
|
||||
{
|
||||
if (category.virtualKey != MyVirtualKey::None)
|
||||
{
|
||||
accelerators.push_back(category.virtualKey);
|
||||
}
|
||||
}
|
||||
|
||||
return accelerators;
|
||||
}
|
||||
|
||||
NavCategoryGroup::NavCategoryGroup(const NavCategoryGroupInitializer& groupInitializer) :
|
||||
m_Categories(ref new Vector<NavCategory^>())
|
||||
{
|
||||
m_GroupType = groupInitializer.type;
|
||||
|
||||
auto resProvider = AppResourceProvider::GetInstance();
|
||||
String^ headerResourceKey = StringReference(groupInitializer.headerResourceKey);
|
||||
String^ modeResourceKey = StringReference(groupInitializer.modeResourceKey);
|
||||
String^ automationResourceKey = StringReference(groupInitializer.automationResourceKey);
|
||||
m_Name = resProvider.GetResourceString(headerResourceKey);
|
||||
String^ groupMode = resProvider.GetResourceString(modeResourceKey);
|
||||
String^ automationName = resProvider.GetResourceString(automationResourceKey);
|
||||
|
||||
String^ navCategoryHeaderAutomationNameFormat = resProvider.GetResourceString(L"NavCategoryHeader_AutomationNameFormat");
|
||||
m_AutomationName = ref new String(LocalizationStringUtil::GetLocalizedString(
|
||||
navCategoryHeaderAutomationNameFormat->Data(),
|
||||
automationName->Data()).c_str());
|
||||
|
||||
String^ navCategoryItemAutomationNameFormat = resProvider.GetResourceString(L"NavCategoryItem_AutomationNameFormat");
|
||||
|
||||
for (const NavCategoryInitializer& categoryInitializer : s_categoryManifest)
|
||||
{
|
||||
if (categoryInitializer.groupType == groupInitializer.type)
|
||||
{
|
||||
String^ nameResourceKey = StringReference(categoryInitializer.nameResourceKey);
|
||||
String^ categoryName = resProvider.GetResourceString(nameResourceKey + "Text");
|
||||
String^ categoryAutomationName = ref new String(LocalizationStringUtil::GetLocalizedString(
|
||||
navCategoryItemAutomationNameFormat->Data(),
|
||||
categoryName->Data(),
|
||||
m_Name->Data()).c_str());
|
||||
|
||||
m_Categories->Append(ref new NavCategory(
|
||||
categoryName,
|
||||
categoryAutomationName,
|
||||
StringReference(categoryInitializer.glyph),
|
||||
resProvider.GetResourceString(nameResourceKey + "AccessKey"),
|
||||
groupMode,
|
||||
categoryInitializer.viewMode,
|
||||
categoryInitializer.supportsNegative));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IObservableVector<NavCategoryGroup^>^ NavCategoryGroup::CreateMenuOptions()
|
||||
{
|
||||
auto menuOptions = ref new Vector<NavCategoryGroup^>();
|
||||
menuOptions->Append(CreateCalculatorCategory());
|
||||
menuOptions->Append(CreateConverterCategory());
|
||||
return menuOptions;
|
||||
}
|
||||
|
||||
NavCategoryGroup^ NavCategoryGroup::CreateCalculatorCategory()
|
||||
{
|
||||
return ref new NavCategoryGroup(s_categoryGroupManifest.at(0));
|
||||
}
|
||||
|
||||
NavCategoryGroup^ NavCategoryGroup::CreateConverterCategory()
|
||||
{
|
||||
return ref new NavCategoryGroup(s_categoryGroupManifest.at(1));
|
||||
}
|
||||
|
||||
vector<NavCategoryInitializer> NavCategoryGroup::GetInitializerCategoryGroup(CategoryGroupType groupType)
|
||||
{
|
||||
vector<NavCategoryInitializer> initializers{};
|
||||
copy_if(begin(s_categoryManifest), end(s_categoryManifest), back_inserter(initializers),
|
||||
[groupType](const NavCategoryInitializer& initializer)
|
||||
{
|
||||
return initializer.groupType == groupType;
|
||||
});
|
||||
|
||||
return initializers;
|
||||
}
|
||||
|
||||
String^ NavCategoryGroup::GetHeaderResourceKey(CategoryGroupType type)
|
||||
{
|
||||
auto iter = find_if(begin(s_categoryGroupManifest), end(s_categoryGroupManifest),
|
||||
[type](const NavCategoryGroupInitializer& initializer)
|
||||
{
|
||||
return initializer.type == type;
|
||||
});
|
||||
|
||||
return (iter != s_categoryGroupManifest.end())
|
||||
? StringReference(iter->headerResourceKey)
|
||||
: nullptr;
|
||||
}
|
231
src/CalcViewModel/Common/NavCategory.h
Normal file
231
src/CalcViewModel/Common/NavCategory.h
Normal file
@@ -0,0 +1,231 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/* The NavCategory group of classes and enumerations is intended to serve
|
||||
* as a single location for storing metadata about a navigation mode.
|
||||
*
|
||||
* These .h and .cpp files:
|
||||
* - Define the ViewMode enumeration which is used for setting the mode of the app.
|
||||
* - Define a list of metadata associated with each ViewMode.
|
||||
* - Define the order of groups and items in the navigation menu.
|
||||
* - Provide a static helper function for creating the navigation menu options.
|
||||
* - Provide static helper functions for querying information about a given ViewMode.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
// Don't change the order of these enums
|
||||
// and definitely don't use int arithmetic
|
||||
// to change modes.
|
||||
public enum class ViewMode
|
||||
{
|
||||
None = -1,
|
||||
Standard = 0,
|
||||
Scientific = 1,
|
||||
Programmer = 2,
|
||||
Date = 3,
|
||||
Volume = 4,
|
||||
Length = 5,
|
||||
Weight = 6,
|
||||
Temperature = 7,
|
||||
Energy = 8,
|
||||
Area = 9,
|
||||
Speed = 10,
|
||||
Time = 11,
|
||||
Power = 12,
|
||||
Data = 13,
|
||||
Pressure = 14,
|
||||
Angle = 15,
|
||||
Currency = 16
|
||||
};
|
||||
|
||||
public enum class CategoryGroupType
|
||||
{
|
||||
None = -1,
|
||||
Calculator = 0,
|
||||
Converter = 1
|
||||
};
|
||||
|
||||
private struct NavCategoryInitializer
|
||||
{
|
||||
constexpr NavCategoryInitializer(
|
||||
ViewMode mode,
|
||||
int id,
|
||||
wchar_t const * name,
|
||||
wchar_t const * nameKey,
|
||||
wchar_t const * glyph,
|
||||
CategoryGroupType group,
|
||||
CalculatorApp::Common::MyVirtualKey vKey,
|
||||
bool categorySupportsNegative)
|
||||
:
|
||||
viewMode(mode),
|
||||
serializationId(id),
|
||||
friendlyName(name),
|
||||
nameResourceKey(nameKey),
|
||||
glyph(glyph),
|
||||
groupType(group),
|
||||
virtualKey(vKey),
|
||||
supportsNegative(categorySupportsNegative)
|
||||
{}
|
||||
|
||||
const ViewMode viewMode;
|
||||
const int serializationId;
|
||||
const wchar_t * const friendlyName;
|
||||
const wchar_t * const nameResourceKey;
|
||||
const wchar_t * const glyph;
|
||||
const CategoryGroupType groupType;
|
||||
const CalculatorApp::Common::MyVirtualKey virtualKey;
|
||||
const bool supportsNegative;
|
||||
};
|
||||
|
||||
private struct NavCategoryGroupInitializer
|
||||
{
|
||||
constexpr NavCategoryGroupInitializer(CategoryGroupType t, wchar_t const * h, wchar_t const * n, wchar_t const * a) :
|
||||
type(t), headerResourceKey(h), modeResourceKey(n), automationResourceKey(a)
|
||||
{}
|
||||
|
||||
const CategoryGroupType type;
|
||||
const wchar_t *headerResourceKey;
|
||||
const wchar_t *modeResourceKey;
|
||||
const wchar_t *automationResourceKey;
|
||||
};
|
||||
|
||||
[Windows::UI::Xaml::Data::Bindable]
|
||||
public ref class NavCategory sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged
|
||||
{
|
||||
public:
|
||||
OBSERVABLE_OBJECT();
|
||||
|
||||
property Platform::String^ Name
|
||||
{
|
||||
Platform::String^ get() { return m_name; }
|
||||
}
|
||||
|
||||
property Platform::String^ AutomationName
|
||||
{
|
||||
Platform::String^ get() { return m_automationName; }
|
||||
}
|
||||
|
||||
property Platform::String^ Glyph
|
||||
{
|
||||
Platform::String^ get() { return m_glyph; }
|
||||
}
|
||||
|
||||
property int Position
|
||||
{
|
||||
int get() { return m_position; }
|
||||
}
|
||||
|
||||
property ViewMode Mode
|
||||
{
|
||||
ViewMode get()
|
||||
{
|
||||
return m_viewMode;
|
||||
}
|
||||
}
|
||||
|
||||
property Platform::String^ AutomationId
|
||||
{
|
||||
Platform::String^ get()
|
||||
{
|
||||
return m_viewMode.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
property Platform::String^ AccessKey
|
||||
{
|
||||
Platform::String^ get()
|
||||
{
|
||||
return m_accessKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
property bool SupportsNegative
|
||||
{
|
||||
bool get()
|
||||
{
|
||||
return m_supportsNegative;
|
||||
}
|
||||
}
|
||||
|
||||
// For saving/restoring last mode used.
|
||||
static int Serialize(ViewMode mode);
|
||||
static ViewMode Deserialize(Platform::Object^ obj);
|
||||
static ViewMode GetViewModeForFriendlyName(Platform::String^ name);
|
||||
|
||||
static bool IsValidViewMode(ViewMode mode);
|
||||
static bool IsCalculatorViewMode(ViewMode mode);
|
||||
static bool IsDateCalculatorViewMode(ViewMode mode);
|
||||
static bool IsConverterViewMode(ViewMode mode);
|
||||
|
||||
static Platform::String^ GetFriendlyName(ViewMode mode);
|
||||
static Platform::String^ GetNameResourceKey(ViewMode mode);
|
||||
static CategoryGroupType GetGroupType(ViewMode mode);
|
||||
|
||||
// GetIndex is 0-based, GetPostion is 1-based
|
||||
static int GetIndex(ViewMode mode);
|
||||
static int GetFlatIndex(ViewMode mode);
|
||||
static int GetIndexInGroup(ViewMode mode, CategoryGroupType type);
|
||||
static int GetPosition(ViewMode mode);
|
||||
|
||||
static ViewMode GetViewModeForVirtualKey(CalculatorApp::Common::MyVirtualKey virtualKey);
|
||||
|
||||
internal:
|
||||
NavCategory(Platform::String^ name, Platform::String^ automationName, Platform::String^ glyph, Platform::String^ accessKey, Platform::String^ mode, ViewMode viewMode, bool supportsNegative) :
|
||||
m_name(name),
|
||||
m_automationName(automationName),
|
||||
m_glyph(glyph),
|
||||
m_accessKey(accessKey),
|
||||
m_mode(mode),
|
||||
m_viewMode(viewMode),
|
||||
m_supportsNegative(supportsNegative)
|
||||
{
|
||||
m_position = NavCategory::GetPosition(m_viewMode);
|
||||
}
|
||||
|
||||
static std::vector<CalculatorApp::Common::MyVirtualKey> GetCategoryAcceleratorKeys();
|
||||
|
||||
private:
|
||||
static bool IsModeInCategoryGroup(ViewMode mode, CategoryGroupType groupType);
|
||||
|
||||
ViewMode m_viewMode;
|
||||
Platform::String^ m_name;
|
||||
Platform::String^ m_automationName;
|
||||
Platform::String^ m_glyph;
|
||||
Platform::String^ m_accessKey;
|
||||
Platform::String^ m_mode;
|
||||
int m_position;
|
||||
bool m_supportsNegative;
|
||||
};
|
||||
|
||||
[Windows::UI::Xaml::Data::Bindable]
|
||||
public ref class NavCategoryGroup sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged
|
||||
{
|
||||
|
||||
public:
|
||||
OBSERVABLE_OBJECT();
|
||||
OBSERVABLE_PROPERTY_R(Platform::String^, Name);
|
||||
OBSERVABLE_PROPERTY_R(Platform::String^, AutomationName);
|
||||
OBSERVABLE_PROPERTY_R(CategoryGroupType, GroupType);
|
||||
OBSERVABLE_PROPERTY_R(Windows::Foundation::Collections::IObservableVector<NavCategory^>^, Categories);
|
||||
|
||||
static Windows::Foundation::Collections::IObservableVector<NavCategoryGroup^>^ CreateMenuOptions();
|
||||
|
||||
static Platform::String^ GetHeaderResourceKey(CategoryGroupType type);
|
||||
|
||||
internal:
|
||||
static NavCategoryGroup^ CreateCalculatorCategory();
|
||||
static NavCategoryGroup^ CreateConverterCategory();
|
||||
|
||||
private:
|
||||
NavCategoryGroup(const NavCategoryGroupInitializer& groupInitializer);
|
||||
|
||||
static std::vector<NavCategoryInitializer> GetInitializerCategoryGroup(CategoryGroupType groupType);
|
||||
};
|
||||
}
|
||||
}
|
57
src/CalcViewModel/Common/NetworkManager.cpp
Normal file
57
src/CalcViewModel/Common/NetworkManager.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "pch.h"
|
||||
#include "NetworkManager.h"
|
||||
|
||||
using namespace CalculatorApp;
|
||||
using namespace Platform;
|
||||
using namespace Windows::Networking::Connectivity;
|
||||
|
||||
NetworkManager::NetworkManager()
|
||||
{
|
||||
m_NetworkStatusChangedToken =
|
||||
NetworkInformation::NetworkStatusChanged += ref new NetworkStatusChangedEventHandler(
|
||||
this, &NetworkManager::OnNetworkStatusChange, CallbackContext::Same);
|
||||
}
|
||||
|
||||
NetworkManager::~NetworkManager()
|
||||
{
|
||||
NetworkInformation::NetworkStatusChanged -= m_NetworkStatusChangedToken;
|
||||
}
|
||||
|
||||
NetworkAccessBehavior NetworkManager::GetNetworkAccessBehavior()
|
||||
{
|
||||
NetworkAccessBehavior behavior = NetworkAccessBehavior::Offline;
|
||||
ConnectionProfile^ connectionProfile = NetworkInformation::GetInternetConnectionProfile();
|
||||
if (connectionProfile != nullptr)
|
||||
{
|
||||
NetworkConnectivityLevel connectivityLevel = connectionProfile->GetNetworkConnectivityLevel();
|
||||
if (connectivityLevel == NetworkConnectivityLevel::InternetAccess
|
||||
|| connectivityLevel == NetworkConnectivityLevel::ConstrainedInternetAccess)
|
||||
{
|
||||
ConnectionCost^ connectionCost = connectionProfile->GetConnectionCost();
|
||||
behavior = ConvertCostInfoToBehavior(connectionCost);
|
||||
}
|
||||
}
|
||||
|
||||
return behavior;
|
||||
}
|
||||
|
||||
void NetworkManager::OnNetworkStatusChange(_In_ Object^ /*sender*/)
|
||||
{
|
||||
NetworkBehaviorChanged(GetNetworkAccessBehavior());
|
||||
}
|
||||
|
||||
// See app behavior guidelines at https://msdn.microsoft.com/en-us/library/windows/apps/xaml/jj835821(v=win.10).aspx
|
||||
NetworkAccessBehavior NetworkManager::ConvertCostInfoToBehavior(_In_ ConnectionCost^ connectionCost)
|
||||
{
|
||||
if (connectionCost->Roaming || connectionCost->OverDataLimit
|
||||
|| connectionCost->NetworkCostType == NetworkCostType::Variable
|
||||
|| connectionCost->NetworkCostType == NetworkCostType::Fixed)
|
||||
{
|
||||
return NetworkAccessBehavior::OptIn;
|
||||
}
|
||||
|
||||
return NetworkAccessBehavior::Normal;
|
||||
}
|
35
src/CalcViewModel/Common/NetworkManager.h
Normal file
35
src/CalcViewModel/Common/NetworkManager.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
public enum class NetworkAccessBehavior
|
||||
{
|
||||
Normal = 0,
|
||||
OptIn = 1,
|
||||
Offline = 2
|
||||
};
|
||||
|
||||
public delegate void NetworkBehaviorChangedHandler(NetworkAccessBehavior behavior);
|
||||
|
||||
public ref class NetworkManager sealed
|
||||
{
|
||||
public:
|
||||
NetworkManager();
|
||||
|
||||
static NetworkAccessBehavior GetNetworkAccessBehavior();
|
||||
|
||||
event NetworkBehaviorChangedHandler^ NetworkBehaviorChanged;
|
||||
|
||||
private:
|
||||
~NetworkManager();
|
||||
|
||||
void OnNetworkStatusChange(_In_ Platform::Object^ sender);
|
||||
static NetworkAccessBehavior ConvertCostInfoToBehavior(_In_ Windows::Networking::Connectivity::ConnectionCost^ connectionCost);
|
||||
|
||||
private:
|
||||
Windows::Foundation::EventRegistrationToken m_NetworkStatusChangedToken;
|
||||
};
|
||||
}
|
56
src/CalcViewModel/Common/TraceActivity.h
Normal file
56
src/CalcViewModel/Common/TraceActivity.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
constexpr int64_t WINEVENT_KEYWORD_RESPONSE_TIME = 0x1000000000000;
|
||||
|
||||
// RAII wrapper that automatically sends the Stop event when the class gets destructed.
|
||||
class TraceActivity
|
||||
{
|
||||
public:
|
||||
TraceActivity() :
|
||||
m_channel(nullptr),
|
||||
m_activity(nullptr),
|
||||
m_fields(nullptr)
|
||||
{ }
|
||||
|
||||
TraceActivity(
|
||||
winrt::Windows::Foundation::Diagnostics::LoggingChannel channel,
|
||||
std::wstring_view activityName,
|
||||
winrt::Windows::Foundation::Diagnostics::LoggingFields fields) :
|
||||
m_channel(channel),
|
||||
m_activityName(activityName),
|
||||
m_fields(fields),
|
||||
m_activity(nullptr)
|
||||
{
|
||||
// Write the activity's START event. Note that you must not specify keyword
|
||||
// or level for START and STOP events because they always use the activity's
|
||||
// keyword and level.
|
||||
m_activity = m_channel.StartActivity(
|
||||
m_activityName,
|
||||
m_fields,
|
||||
winrt::Windows::Foundation::Diagnostics::LoggingLevel::Verbose,
|
||||
winrt::Windows::Foundation::Diagnostics::LoggingOptions(WINEVENT_KEYWORD_RESPONSE_TIME)
|
||||
);
|
||||
}
|
||||
|
||||
~TraceActivity()
|
||||
{
|
||||
if (m_activity != nullptr)
|
||||
{
|
||||
// Write the activity's STOP event.
|
||||
m_activity.StopActivity(m_activityName, m_fields);
|
||||
m_activity = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::wstring m_activityName;
|
||||
winrt::Windows::Foundation::Diagnostics::LoggingChannel m_channel;
|
||||
winrt::Windows::Foundation::Diagnostics::LoggingFields m_fields;
|
||||
winrt::Windows::Foundation::Diagnostics::LoggingActivity m_activity;
|
||||
};
|
||||
}
|
985
src/CalcViewModel/Common/TraceLogger.cpp
Normal file
985
src/CalcViewModel/Common/TraceLogger.cpp
Normal file
@@ -0,0 +1,985 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pch.h"
|
||||
#include "TraceLogger.h"
|
||||
#include "NetworkManager.h"
|
||||
|
||||
using namespace CalculatorApp;
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace Concurrency;
|
||||
using namespace std;
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Foundation::Diagnostics;
|
||||
using namespace winrt::Windows::Globalization;
|
||||
using namespace winrt::Windows::Globalization::DateTimeFormatting;
|
||||
using namespace winrt::Windows::System::UserProfile;
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
static multimap<int, vector<wstring>> s_memoryMap;
|
||||
|
||||
static constexpr array<const wchar_t * const, 9> s_programmerType{
|
||||
L"N/A", L"QwordType", L"DwordType",
|
||||
L"WordType", L"ByteType", L"HexBase",
|
||||
L"DecBase", L"OctBase", L"BinBase" };
|
||||
static reader_writer_lock s_traceLoggerLock;
|
||||
|
||||
// Telemetry events. Uploaded to asimov.
|
||||
constexpr auto EVENT_NAME_DEBUG = L"Debug";
|
||||
constexpr auto EVENT_NAME_ERROR = L"ErrorMessage";
|
||||
constexpr auto EVENT_NAME_APP_PRELAUNCHED_BY_SYSTEM = L"AppPrelaunchedBySystem";
|
||||
constexpr auto EVENT_NAME_PRELAUNCHED_APP_ACTIVATED_BY_USER = L"PrelaunchedAppActivatedByUser";
|
||||
constexpr auto EVENT_NAME_APP_LAUNCH_BEGIN = L"AppLaunchBegin";
|
||||
constexpr auto EVENT_NAME_APP_LAUNCH_END = L"AppLaunchEnd";
|
||||
constexpr auto EVENT_NAME_APP_RESUME_END = L"AppResumeEnd";
|
||||
constexpr auto EVENT_NAME_PREVIOUS_STATE_WINDOW_ON_CREATION = L"PreviousStateOnWindowCreation";
|
||||
constexpr auto EVENT_NAME_SIZE_ON_SUSPENSION = L"CalculatorSizeOnSuspension";
|
||||
constexpr auto EVENT_NAME_CALCULATOR_VIEWED_IN_SESSION = L"CalculatorViewedInSession";
|
||||
constexpr auto EVENT_NAME_DATE_CALCULATOR_VIEWED_IN_SESSION = L"DateCalculatorViewedInSession";
|
||||
constexpr auto EVENT_NAME_CONVERTER_VIEWED_IN_SESSION = L"ConverterViewedInSession";
|
||||
constexpr auto EVENT_NAME_MODE_CHANGE_BEGIN = L"ModeChangeBegin";
|
||||
constexpr auto EVENT_NAME_MODE_CHANGE_END = L"ModeChangeEnd";
|
||||
constexpr auto EVENT_NAME_HISTORY_BODY_OPENED = L"HistoryBodyOpened";
|
||||
constexpr auto EVENT_NAME_HISTORY_ITEM_LOAD_BEGIN = L"HistoryItemLoadBegin";
|
||||
constexpr auto EVENT_NAME_HISTORY_ITEM_LOAD_END = L"HistoryItemLoadEnd";
|
||||
constexpr auto EVENT_NAME_HISTORY_FLYOUT_OPEN_BEGIN = L"HistoryFlyoutOpenBegin";
|
||||
constexpr auto EVENT_NAME_HISTORY_FLYOUT_OPEN_END = L"HistoryFlyoutOpenEnd";
|
||||
constexpr auto EVENT_NAME_NEW_WINDOW_CREATION_BEGIN = L"NewWindowCreationBegin";
|
||||
constexpr auto EVENT_NAME_NEW_WINDOW_CREATION_END = L"NewWindowCreationEnd";
|
||||
constexpr auto EVENT_NAME_HISTORY_CLEAR = L"HistoryClearBegin";
|
||||
constexpr auto EVENT_NAME_MULTIPLE_MEMORY_USED = L"MultipleMemoryUsed";
|
||||
constexpr auto EVENT_NAME_SINGLE_MEMORY_USED = L"SingleMemoryUsed";
|
||||
constexpr auto EVENT_NAME_SHARED_MEMORY_USED = L"SharedMemoryUsed";
|
||||
constexpr auto EVENT_NAME_MEMORY_BODY_OPENED = L"MemoryBodyOpened";
|
||||
constexpr auto EVENT_NAME_MEMORY_FLYOUT_OPEN_BEGIN = L"MemoryFlyoutOpenBegin";
|
||||
constexpr auto EVENT_NAME_MEMORY_FLYOUT_OPEN_END = L"MemoryFlyoutOpenEnd";
|
||||
constexpr auto EVENT_NAME_MEMORY_CLEAR_ALL = L"MemoryClearAll";
|
||||
constexpr auto EVENT_NAME_INVALID_INPUT_PASTED = L"InvalidInputPasted";
|
||||
constexpr auto EVENT_NAME_VALID_INPUT_PASTED = L"ValidInputPasted";
|
||||
constexpr auto EVENT_NAME_BITFLIP_PANE_CLICKED = L"BitFlipPaneClicked";
|
||||
constexpr auto EVENT_NAME_BITFLIP_BUTTONS_USED = L"BitFlipToggleButtonUsed";
|
||||
constexpr auto EVENT_NAME_ANGLE_BUTTONS_USED = L"AngleButtonUsedInSession";
|
||||
constexpr auto EVENT_NAME_HYP_BUTTON_USED = L"HypButtonUsedInSession";
|
||||
constexpr auto EVENT_NAME_FUNCTION_USAGE = L"FunctionUsageInSession";
|
||||
constexpr auto EVENT_NAME_BITLENGTH_BUTTON_USED = L"BitLengthButtonUsed";
|
||||
constexpr auto EVENT_NAME_RADIX_BUTTON_USED = L"RadixButtonUsed";
|
||||
constexpr auto EVENT_NAME_MAX_WINDOW_COUNT = L"MaxWindowCountInSession";
|
||||
constexpr auto EVENT_NAME_WINDOW_LAUNCHED_PROTOCOL = L"WindowActivatedThroughProtocol";
|
||||
constexpr auto EVENT_NAME_WINDOW_LAUNCHED_TILESEARCH = L"WindowLaunchedThroughTile";
|
||||
constexpr auto EVENT_NAME_DATE_DIFFERENCE_USED = L"DateDifferenceModeUsed";
|
||||
constexpr auto EVENT_NAME_DATE_ADD_SUBTRACT_USED = L"DateAddSubtractModeUsed";
|
||||
constexpr auto EVENT_NAME_DATE_DIFFERENCE_FOUND = L"DateDifferenceFound";
|
||||
constexpr auto EVENT_NAME_HIDE_IF_SHOWN = L"HideIfShown";
|
||||
constexpr auto EVENT_NAME_ABOUT_FLYOUT_OPENED = L"AboutFlyoutOpened";
|
||||
constexpr auto EVENT_NAME_NAV_BAR_OPENED = L"NavBarOpened";
|
||||
constexpr auto EVENT_NAME_CORE_WINDOW_WAS_NULL = L"CoreWindowWasNull";
|
||||
|
||||
constexpr auto EVENT_NAME_EXCEPTION = L"Exception";
|
||||
|
||||
#ifdef SEND_TELEMETRY
|
||||
// c.f. WINEVENT_KEYWORD_RESERVED_63-56 0xFF00000000000000 // Bits 63-56 - channel keywords
|
||||
// c.f. WINEVENT_KEYWORD_* 0x00FF000000000000 // Bits 55-48 - system-reserved keywords
|
||||
constexpr int64_t MICROSOFT_KEYWORD_CRITICAL_DATA = 0x0000800000000000; // Bit 47
|
||||
constexpr int64_t MICROSOFT_KEYWORD_MEASURES = 0x0000400000000000; // Bit 46
|
||||
constexpr int64_t MICROSOFT_KEYWORD_TELEMETRY = 0x0000200000000000; // Bit 45
|
||||
constexpr int64_t MICROSOFT_KEYWORD_RESERVED_44 = 0x0000100000000000; // Bit 44 (reserved for future assignment)
|
||||
#else
|
||||
// define all Keyword options as 0 when we do not want to upload app telemetry
|
||||
constexpr int64_t MICROSOFT_KEYWORD_CRITICAL_DATA = 0;
|
||||
constexpr int64_t MICROSOFT_KEYWORD_MEASURES = 0;
|
||||
constexpr int64_t MICROSOFT_KEYWORD_TELEMETRY = 0;
|
||||
constexpr int64_t MICROSOFT_KEYWORD_RESERVED_44 = 0;
|
||||
#endif
|
||||
|
||||
#pragma region TraceLogger setup and cleanup
|
||||
|
||||
TraceLogger::TraceLogger() :
|
||||
g_calculatorProvider(
|
||||
L"MicrosoftCalculator",
|
||||
LoggingChannelOptions(GUID{ 0x4f50731a, 0x89cf, 0x4782, 0xb3, 0xe0, 0xdc, 0xe8, 0xc9, 0x4, 0x76, 0xba }), // Microsoft Telemetry group
|
||||
GUID{ 0x905ca09, 0x610e, 0x401e, 0xb6, 0x50, 0x2f, 0x21, 0x29, 0x80, 0xb9, 0xe0 }), //Unique providerID {0905CA09-610E-401E-B650-2F212980B9E0}
|
||||
m_appLaunchActivity{ nullptr }
|
||||
{
|
||||
// initialize the function array
|
||||
InitFunctionLogArray();
|
||||
}
|
||||
|
||||
TraceLogger::~TraceLogger()
|
||||
{
|
||||
}
|
||||
|
||||
TraceLogger& TraceLogger::GetInstance()
|
||||
{
|
||||
static TraceLogger s_selfInstance;
|
||||
return s_selfInstance;
|
||||
}
|
||||
|
||||
bool TraceLogger::GetTraceLoggingProviderEnabled() const
|
||||
{
|
||||
return g_calculatorProvider.Enabled();
|
||||
}
|
||||
|
||||
#pragma region Tracing methods
|
||||
void TraceLogger::LogTelemetryEvent(wstring_view eventName, LoggingFields fields) const
|
||||
{
|
||||
g_calculatorProvider.LogEvent(eventName, fields, LoggingLevel::Verbose, LoggingOptions(MICROSOFT_KEYWORD_TELEMETRY));
|
||||
}
|
||||
|
||||
void TraceLogger::LogMeasureEvent(wstring_view eventName, LoggingFields fields) const
|
||||
{
|
||||
g_calculatorProvider.LogEvent(eventName, fields, LoggingLevel::Verbose, LoggingOptions(MICROSOFT_KEYWORD_MEASURES));
|
||||
}
|
||||
|
||||
void TraceLogger::LogCriticalDataEvent(wstring_view eventName, LoggingFields fields) const
|
||||
{
|
||||
g_calculatorProvider.LogEvent(eventName, fields, LoggingLevel::Verbose, LoggingOptions(MICROSOFT_KEYWORD_CRITICAL_DATA));
|
||||
}
|
||||
|
||||
void TraceLogger::LogPerformanceEvent(wstring_view eventName, LoggingFields fields) const
|
||||
{
|
||||
g_calculatorProvider.LogEvent(eventName, fields, LoggingLevel::Verbose, LoggingOptions(WINEVENT_KEYWORD_RESPONSE_TIME));
|
||||
}
|
||||
|
||||
void TraceLogger::LogInfoEvent(wstring_view eventName, LoggingFields fields) const
|
||||
{
|
||||
g_calculatorProvider.LogEvent(eventName, fields, LoggingLevel::Information);
|
||||
}
|
||||
|
||||
unique_ptr<TraceActivity> TraceLogger::CreateTraceActivity(wstring_view eventName, LoggingFields fields) const
|
||||
{
|
||||
return make_unique<TraceActivity>(g_calculatorProvider, eventName, fields);
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
void TraceLogger::InsertIntoMemoryMap(int windowId, bool isStandard, bool isScientific, bool isProgrammer)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
|
||||
auto iterMap = s_memoryMap.find(windowId);
|
||||
if (iterMap == s_memoryMap.end())
|
||||
{
|
||||
s_memoryMap.insert(std::make_pair(windowId, vector<wstring>()));
|
||||
iterMap = s_memoryMap.find(windowId);
|
||||
}
|
||||
|
||||
if (isScientific)
|
||||
{
|
||||
iterMap->second.insert(iterMap->second.begin(), L"Scientific");
|
||||
}
|
||||
else if (isProgrammer)
|
||||
{
|
||||
iterMap->second.insert(iterMap->second.begin(), L"Programmer");
|
||||
}
|
||||
else
|
||||
{
|
||||
iterMap->second.insert(iterMap->second.begin(), L"Standard");
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::UpdateMemoryMap(int windowId, int memoryPosition, bool isStandard, bool isScientific, bool isProgrammer)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
|
||||
auto iterMap = s_memoryMap.find(windowId);
|
||||
assert(iterMap != s_memoryMap.end());
|
||||
assert(iterMap->second.size() >= (unsigned int)memoryPosition);
|
||||
|
||||
if (isScientific)
|
||||
{
|
||||
iterMap->second[memoryPosition] = L"Scientific";
|
||||
}
|
||||
else if (isProgrammer)
|
||||
{
|
||||
iterMap->second[memoryPosition] = L"Programmer";
|
||||
}
|
||||
else
|
||||
{
|
||||
iterMap->second[memoryPosition] = L"Standard";
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::DeleteFromMemoryMap(int windowId, int memoryPosition)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
auto iterMap = s_memoryMap.find(windowId);
|
||||
assert(iterMap != s_memoryMap.end());
|
||||
|
||||
iterMap->second.erase(iterMap->second.begin() + memoryPosition);
|
||||
}
|
||||
|
||||
// return true if windowId is logged once else return false
|
||||
bool TraceLogger::UpdateWindowIdLog(int windowId)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
|
||||
if (windowIdLog.find(windowId) == windowIdLog.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (windowIdLog[windowId] == false)
|
||||
{
|
||||
windowIdLog[windowId] = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// call comes here at the time of ApplicationViewModel initialisation
|
||||
void TraceLogger::LogCalculatorModeViewed(ViewMode mode, int windowId)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
|
||||
// store windowId in windowIdLog which says we have logged mode for the present windowId.
|
||||
if (windowIdLog.find(windowId) == windowIdLog.end())
|
||||
{
|
||||
windowIdLog.insert(pair<int, bool>(windowId, false));
|
||||
}
|
||||
// if the event is not logged already for the present mode
|
||||
if (currentMode != mode)
|
||||
{
|
||||
currentMode = mode;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"CalculatorMode", NavCategory::GetFriendlyName(mode)->Data());
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_CALCULATOR_VIEWED_IN_SESSION, fields);
|
||||
}
|
||||
}
|
||||
|
||||
// call comes here at the time of ApplicationViewModel initialization
|
||||
void TraceLogger::LogDateCalculatorModeViewed(ViewMode mode, int windowId)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
|
||||
// store windowId in windowIdLog which says we have logged mode for the present windowId.
|
||||
if (windowIdLog.find(windowId) == windowIdLog.end())
|
||||
{
|
||||
windowIdLog.insert(pair<int, bool>(windowId, false));
|
||||
}
|
||||
// if the event is not logged already for the present mode
|
||||
if (currentMode != mode)
|
||||
{
|
||||
currentMode = mode;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"DateCalculatorMode", NavCategory::GetFriendlyName(mode)->Data());
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_DATE_CALCULATOR_VIEWED_IN_SESSION, fields);
|
||||
}
|
||||
}
|
||||
|
||||
// call comes here at the time of ApplicationViewModel initialization
|
||||
void TraceLogger::LogConverterModeViewed(ViewMode mode, int windowId)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
|
||||
if (windowIdLog.find(windowId) == windowIdLog.end())
|
||||
{
|
||||
windowIdLog.insert(pair<int, bool>(windowId, false));
|
||||
}
|
||||
// if the event is not logged already for the present mode
|
||||
if (currentMode != mode)
|
||||
{
|
||||
currentMode = mode;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"ConverterMode", NavCategory::GetFriendlyName(mode)->Data());
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_CONVERTER_VIEWED_IN_SESSION, fields);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogSharedMemoryUsed(wstring_view fromMode, wstring_view toMode, unsigned int memorySize) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"FromMode", fromMode);
|
||||
fields.AddString(L"ToMode", toMode);
|
||||
fields.AddUInt32(L"MemoryListSize", memorySize);
|
||||
LogTelemetryEvent(EVENT_NAME_SHARED_MEMORY_USED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogBitFlipPaneClicked() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_BITFLIP_PANE_CLICKED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogBitFlipUsed() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_BITFLIP_BUTTONS_USED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogAppLaunchStart()
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
|
||||
if (!isAppLaunchBeginLogged)
|
||||
{
|
||||
m_appLaunchActivity = g_calculatorProvider.StartActivity(
|
||||
EVENT_NAME_APP_LAUNCH_BEGIN,
|
||||
nullptr,
|
||||
LoggingLevel::Verbose,
|
||||
LoggingOptions(MICROSOFT_KEYWORD_TELEMETRY));
|
||||
|
||||
isAppLaunchBeginLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogAppLaunchComplete(/*Windows::ApplicationModel::Activation::ActivationKind activationKind, Windows::ApplicationModel::Activation::ApplicationExecutionState executionState*/)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
|
||||
if ((m_appLaunchActivity != nullptr) && (!isAppLaunchEndLogged))
|
||||
{
|
||||
m_appLaunchActivity.StopActivity(EVENT_NAME_APP_LAUNCH_END);
|
||||
|
||||
isAppLaunchEndLogged = true;
|
||||
}
|
||||
|
||||
m_appLaunchActivity = nullptr;
|
||||
}
|
||||
|
||||
void TraceLogger::LogAppResumeComplete()
|
||||
{
|
||||
if (m_appLaunchActivity != nullptr)
|
||||
{
|
||||
m_appLaunchActivity.StopActivity(EVENT_NAME_APP_RESUME_END);
|
||||
}
|
||||
|
||||
m_appLaunchActivity = nullptr;
|
||||
}
|
||||
|
||||
void TraceLogger::LogDebug(wstring_view debugData)
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"DebugData", debugData);
|
||||
LogTelemetryEvent(EVENT_NAME_DEBUG, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogOnAppLaunch(wstring_view previousExecutionState) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"PreviousExecutionState", previousExecutionState);
|
||||
LogTelemetryEvent(EVENT_NAME_PREVIOUS_STATE_WINDOW_ON_CREATION, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogAboutFlyoutOpened() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_ABOUT_FLYOUT_OPENED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogNavBarOpened() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_NAV_BAR_OPENED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogClearHistory() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_HISTORY_CLEAR, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogHistoryFlyoutOpenBegin(unsigned int historyItemCount) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
// we want to record the event only when history item count is atleast 20
|
||||
if (historyItemCount >= 20)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"HistoryItemCount", historyItemCount);
|
||||
LogTelemetryEvent(EVENT_NAME_HISTORY_FLYOUT_OPEN_BEGIN, fields);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogHistoryFlyoutOpenEnd(int historyItemCount) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
if (historyItemCount >= 20)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"HistoryItemCount", historyItemCount);
|
||||
LogTelemetryEvent(EVENT_NAME_HISTORY_FLYOUT_OPEN_END, fields);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogHistoryBodyOpened() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_HISTORY_BODY_OPENED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogMemoryFlyoutOpenBegin(unsigned int memoryItemCount) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
// we want to record the event only when memory item count is atleast 4
|
||||
if (memoryItemCount >= 4)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"MemoryItemCount", memoryItemCount);
|
||||
LogTelemetryEvent(EVENT_NAME_MEMORY_FLYOUT_OPEN_BEGIN, fields);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogMemoryFlyoutOpenEnd(unsigned int memoryItemCount) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
if (memoryItemCount >= 4)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"MemoryItemCount", memoryItemCount);
|
||||
LogTelemetryEvent(EVENT_NAME_MEMORY_FLYOUT_OPEN_END, fields);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogMemoryBodyOpened() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_MEMORY_BODY_OPENED, fields);
|
||||
}
|
||||
|
||||
//If calculator is launched in any mode other than standard then this call will come which is not intended. But there is no way to avoid it.
|
||||
//So don't use this function to analyze the count of mode change in session instead use CalculatorViewedInSession and ConverterViewedInSession to do that.
|
||||
//Use of this function is to analyze perf of mode change.
|
||||
void TraceLogger::LogModeChangeBegin(ViewMode fromMode, ViewMode toMode, int windowId)
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
if (NavCategory::IsValidViewMode(fromMode) && NavCategory::IsValidViewMode(toMode))
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"FromMode", NavCategory::GetFriendlyName(fromMode)->Data());
|
||||
fields.AddString(L"ToMode", NavCategory::GetFriendlyName(toMode)->Data());
|
||||
fields.AddInt32(L"WindowId", windowId);
|
||||
LogMeasureEvent(EVENT_NAME_MODE_CHANGE_BEGIN, fields);
|
||||
}
|
||||
}
|
||||
|
||||
//comment: same as LogModeChangeBegin
|
||||
void TraceLogger::LogModeChangeEnd(ViewMode toMode, int windowId) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
if (NavCategory::IsValidViewMode(toMode))
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"ToMode", NavCategory::GetFriendlyName(toMode)->Data());
|
||||
fields.AddInt32(L"WindowId", windowId);
|
||||
LogMeasureEvent(EVENT_NAME_MODE_CHANGE_END, fields);
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogHistoryItemLoadBegin() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_HISTORY_ITEM_LOAD_BEGIN, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogHistoryItemLoadEnd(unsigned int historyTokenCount) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"HistoryTokenCount", historyTokenCount);
|
||||
LogTelemetryEvent(EVENT_NAME_HISTORY_ITEM_LOAD_END, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogNewWindowCreationBegin(int windowId)
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_NEW_WINDOW_CREATION_BEGIN, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogNewWindowCreationEnd(int windowId)
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_NEW_WINDOW_CREATION_END, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogError(wstring_view errorString)
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"ErrorString", errorString);
|
||||
LogTelemetryEvent(EVENT_NAME_ERROR, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogPrelaunchedAppActivatedByUser()
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_PRELAUNCHED_APP_ACTIVATED_BY_USER, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogAppPrelaunchedBySystem()
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_APP_PRELAUNCHED_BY_SYSTEM, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogMemoryClearAll(int windowId)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
auto iterMap = s_memoryMap.find(windowId);
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_MEMORY_CLEAR_ALL, fields);
|
||||
|
||||
if (iterMap != s_memoryMap.end())
|
||||
{
|
||||
iterMap->second.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogMemoryUsed(int windowId, unsigned int slotPosition, bool isStandard, bool isScientific, bool isProgrammer, unsigned int memorySize) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
// Reader lock for the static resources
|
||||
reader_writer_lock::scoped_lock_read lock(s_traceLoggerLock);
|
||||
auto iterMap = s_memoryMap.find(windowId);
|
||||
|
||||
if (slotPosition == 0)
|
||||
{
|
||||
LogSingleMemoryUsed(memorySize);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMultipleMemoryUsed(slotPosition, memorySize);
|
||||
}
|
||||
|
||||
if (iterMap != s_memoryMap.end())
|
||||
{
|
||||
// if current mode is not equal to mode of memoryMap[slotPosition] then LogSharedMemoryUsed
|
||||
if (isStandard && (iterMap->second[slotPosition] != L"Standard"))
|
||||
{
|
||||
LogSharedMemoryUsed(iterMap->second[slotPosition], L"Standard", memorySize);
|
||||
}
|
||||
else if (isScientific && (iterMap->second[slotPosition] != L"Scientific"))
|
||||
{
|
||||
LogSharedMemoryUsed(iterMap->second[slotPosition], L"Scientific", memorySize);
|
||||
}
|
||||
else if (isProgrammer && (iterMap->second[slotPosition] != L"Programmer"))
|
||||
{
|
||||
LogSharedMemoryUsed(iterMap->second[slotPosition], L"Programmer", memorySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogMultipleMemoryUsed(unsigned int slotPosition, unsigned int memorySize) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"MemoryIndex", slotPosition);
|
||||
fields.AddUInt32(L"MemoryListSize", memorySize);
|
||||
LogTelemetryEvent(EVENT_NAME_MULTIPLE_MEMORY_USED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogSingleMemoryUsed(unsigned int memorySize) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"MemoryListSize", memorySize);
|
||||
LogTelemetryEvent(EVENT_NAME_SINGLE_MEMORY_USED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogInvalidInputPasted(wstring_view reason, wstring_view pastedExpression, ViewMode mode, int programmerNumberBase, int bitLengthType)
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"Mode", NavCategory::GetFriendlyName(mode)->Data());
|
||||
fields.AddString(L"Reason", reason);
|
||||
fields.AddString(L"PastedExpression", pastedExpression);
|
||||
fields.AddString(L"ProgrammerNumberBase", GetProgrammerType(programmerNumberBase).c_str());
|
||||
fields.AddString(L"BitLengthType", GetProgrammerType(bitLengthType).c_str());
|
||||
LogTelemetryEvent(EVENT_NAME_INVALID_INPUT_PASTED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogValidInputPasted(ViewMode mode) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"Mode", NavCategory::GetFriendlyName(mode)->Data());
|
||||
LogTelemetryEvent(EVENT_NAME_VALID_INPUT_PASTED, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogStandardException(wstring_view functionName, const exception& e) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"FunctionName", functionName);
|
||||
wstringstream exceptionMessage;
|
||||
exceptionMessage << e.what();
|
||||
fields.AddString(L"ExceptionMessage", exceptionMessage.str());
|
||||
LogMeasureEvent(EVENT_NAME_EXCEPTION, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogWinRTException(wstring_view functionName, hresult_error const& e) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"FunctionName", functionName);
|
||||
fields.AddInt32(L"HRESULT", e.code());
|
||||
fields.AddString(L"ExceptionMessage", e.message());
|
||||
LogMeasureEvent(EVENT_NAME_EXCEPTION, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogPlatformException(wstring_view functionName, Platform::Exception^ e) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"FunctionName", functionName);
|
||||
fields.AddInt32(L"HRESULT", e->HResult);
|
||||
fields.AddString(L"ExceptionMessage", e->Message->Data());
|
||||
LogMeasureEvent(EVENT_NAME_EXCEPTION, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::UpdateFunctionUsage(int funcIndex)
|
||||
{
|
||||
// Writer lock for the static resources
|
||||
reader_writer_lock::scoped_lock lock(s_traceLoggerLock);
|
||||
|
||||
if (GetIndex(funcIndex))
|
||||
{
|
||||
// funcIndex is passed by reference and will be having the returned index
|
||||
funcLog[funcIndex].count++;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::InitFunctionLogArray()
|
||||
{
|
||||
int i = -1;
|
||||
for (int funcIndex = 0; funcIndex != maxFunctionSize; funcIndex++)
|
||||
{
|
||||
FunctionLogEnum func = safe_cast<FunctionLogEnum>(funcIndex);
|
||||
wstring functionName = func.ToString()->Data();
|
||||
if (functionName.compare(L"CalculatorApp.FunctionLogEnum") != 0)
|
||||
{
|
||||
findIndex[funcIndex] = ++i;
|
||||
funcLog.push_back(FuncLog(functionName));
|
||||
}
|
||||
}
|
||||
// update the functionCount with total function count which we are tracking through tracelog.
|
||||
functionCount = i;
|
||||
}
|
||||
|
||||
wstring TraceLogger::GetProgrammerType(int index)
|
||||
{
|
||||
if (index >= 0)
|
||||
{
|
||||
return s_programmerType[index];
|
||||
}
|
||||
//return "N/A"
|
||||
return s_programmerType[0];
|
||||
}
|
||||
|
||||
bool TraceLogger::GetIndex(int &index)
|
||||
{
|
||||
if (findIndex[index] > 0)
|
||||
{
|
||||
index = findIndex[index];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TraceLogger::UpdateWindowCount(size_t windowCount)
|
||||
{
|
||||
maxWindowCount = (maxWindowCount > windowCount) ? maxWindowCount : windowCount;
|
||||
windowLaunchCount++;
|
||||
}
|
||||
|
||||
void TraceLogger::LogMaxWindowCount()
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"WindowCount", (unsigned int)maxWindowCount);
|
||||
LogTelemetryEvent(EVENT_NAME_MAX_WINDOW_COUNT, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogWindowActivated() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_WINDOW_LAUNCHED_PROTOCOL, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogWindowLaunched() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_WINDOW_LAUNCHED_TILESEARCH, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogBitLengthButtonUsed(int windowId)
|
||||
{
|
||||
if (bitLengthButtonUsage.find(windowId) == bitLengthButtonUsage.end())
|
||||
{
|
||||
bitLengthButtonUsage.insert(pair<int, int>(windowId, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
bitLengthButtonUsage[windowId]++;
|
||||
}
|
||||
if ((bitLengthButtonUsage[windowId] == 5) && !bitLengthButtonLoggedInSession)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_BITLENGTH_BUTTON_USED, fields);
|
||||
|
||||
bitLengthButtonLoggedInSession = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogRadixButtonUsed(int windowId)
|
||||
{
|
||||
if (radixButtonUsage.find(windowId) == radixButtonUsage.end())
|
||||
{
|
||||
radixButtonUsage.insert(pair<int, int>(windowId, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
radixButtonUsage[windowId]++;
|
||||
}
|
||||
if ((radixButtonUsage[windowId] == 2) && !radixButtonLoggedInSession)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_RADIX_BUTTON_USED, fields);
|
||||
|
||||
radixButtonLoggedInSession = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogAngleButtonUsed(int windowId)
|
||||
{
|
||||
if (angleButtonUsage.find(windowId) == angleButtonUsage.end())
|
||||
{
|
||||
angleButtonUsage.insert(pair<int, int>(windowId, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
angleButtonUsage[windowId]++;
|
||||
}
|
||||
if ((angleButtonUsage[windowId] == 2) && !angleButtonLoggedInSession)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_ANGLE_BUTTONS_USED, fields);
|
||||
|
||||
angleButtonLoggedInSession = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogFunctionUsage(int windowId)
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
for (int i = 0; i < functionCount; i++)
|
||||
{
|
||||
// log only those functions which are used
|
||||
if (funcLog[i].count > 0)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"FunctionName", funcLog[i].funcName.data());
|
||||
fields.AddUInt32(L"UsageCount", funcLog[i].count);
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_FUNCTION_USAGE, fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogHypButtonUsed(int windowId)
|
||||
{
|
||||
if (!isHypButtonLogged)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddUInt32(L"WindowId", windowId);
|
||||
LogTelemetryEvent(EVENT_NAME_HYP_BUTTON_USED, fields);
|
||||
|
||||
isHypButtonLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogDateDifferenceModeUsed(int windowId)
|
||||
{
|
||||
if (!m_dateDiffUsageLoggedInSession)
|
||||
{
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_DATE_DIFFERENCE_USED, fields);
|
||||
|
||||
m_dateDiffUsageLoggedInSession = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogDateAddSubtractModeUsed(int windowId, bool isAddMode)
|
||||
{
|
||||
auto usageMap = isAddMode ? &m_dateAddModeUsage : &m_dateSubtractModeUsage;
|
||||
auto isLoggedInSession = isAddMode ? &m_dateAddUsageLoggedInSession : &m_dateSubtractUsageLoggedInSession;
|
||||
|
||||
if (usageMap->find(windowId) == usageMap->end())
|
||||
{
|
||||
usageMap->insert(pair<int, int>(windowId, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
(*usageMap)[windowId]++;
|
||||
}
|
||||
|
||||
// Ignore first 3 calls during the initialization of the combo box selected items for Add mode
|
||||
int firstChangeEventCount = isAddMode ? 4 : 1;
|
||||
|
||||
if (((*usageMap)[windowId] == firstChangeEventCount)
|
||||
&& (!(*isLoggedInSession)))
|
||||
{
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"AddSubtractMode", isAddMode ? L"Add" : L"Subtract");
|
||||
LogTelemetryEvent(EVENT_NAME_DATE_ADD_SUBTRACT_USED, fields);
|
||||
|
||||
*isLoggedInSession = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TraceLogger::LogDateClippedTimeDifferenceFound(Calendar const& today, DateTime const& clippedTime) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
auto calendarSystem = today.GetCalendarSystem();
|
||||
auto clock = today.GetClock();
|
||||
DateTimeFormatter dtFormatter{
|
||||
L"longdate shorttime",
|
||||
{ L"en-US" },
|
||||
GlobalizationPreferences::HomeGeographicRegion(),
|
||||
calendarSystem,
|
||||
clock };
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"ResolvedCalendarLanguage", today.ResolvedLanguage());
|
||||
fields.AddString(L"Timezone", today.GetTimeZone());
|
||||
fields.AddString(L"CalendarSystem", calendarSystem);
|
||||
fields.AddString(L"Clock", clock);
|
||||
fields.AddBoolean(L"IsDaylightSavingTime", today.IsDaylightSavingTime());
|
||||
fields.AddString(L"TodayDate", dtFormatter.Format(today.GetDateTime()));
|
||||
fields.AddString(L"ClippedDate", dtFormatter.Format(clippedTime));
|
||||
LogTelemetryEvent(EVENT_NAME_DATE_DIFFERENCE_FOUND, fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogUserRequestedRefreshFailed() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(L"UserRequestedRefreshFailed", fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogConversionResult(wstring_view fromValue, wstring_view fromUnit, wstring_view toValue, wstring_view toUnit) const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
wstring behaviorString{};
|
||||
NetworkAccessBehavior behavior = NetworkManager::GetNetworkAccessBehavior();
|
||||
switch (behavior)
|
||||
{
|
||||
case NetworkAccessBehavior::Offline:
|
||||
behaviorString = L"Offline";
|
||||
break;
|
||||
|
||||
case NetworkAccessBehavior::OptIn:
|
||||
behaviorString = L"Metered";
|
||||
break;
|
||||
|
||||
case NetworkAccessBehavior::Normal:
|
||||
default:
|
||||
behaviorString = L"Online";
|
||||
break;
|
||||
}
|
||||
|
||||
LoggingFields fields{};
|
||||
fields.AddString(L"NetworkAccess", behaviorString);
|
||||
fields.AddString(L"FromValue", fromValue);
|
||||
fields.AddString(L"FromUnit", fromUnit);
|
||||
fields.AddString(L"ToValue", toValue);
|
||||
fields.AddString(L"ToUnit", toUnit);
|
||||
LogTelemetryEvent(L"CurrencyConverterInputReceived", fields);
|
||||
}
|
||||
|
||||
void TraceLogger::LogViewClosingTelemetry(int windowId)
|
||||
{
|
||||
LogFunctionUsage(windowId);
|
||||
LogMaxWindowCount();
|
||||
}
|
||||
|
||||
void TraceLogger::LogCoreWindowWasNull() const
|
||||
{
|
||||
if (!GetTraceLoggingProviderEnabled()) return;
|
||||
|
||||
LoggingFields fields{};
|
||||
LogTelemetryEvent(EVENT_NAME_CORE_WINDOW_WAS_NULL, fields);
|
||||
}
|
||||
}
|
||||
|
155
src/CalcViewModel/Common/TraceLogger.h
Normal file
155
src/CalcViewModel/Common/TraceLogger.h
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "TraceActivity.h"
|
||||
|
||||
static const int maxFunctionSize = (int)CalculationManager::Command::CommandBINEDITEND;
|
||||
|
||||
// A trace logging provider can only be instantiated and registered once per module.
|
||||
// This class implements a singleton model ensure that only one instance is created.
|
||||
namespace CalculatorApp
|
||||
{
|
||||
struct FuncLog
|
||||
{
|
||||
public:
|
||||
int count;
|
||||
std::wstring funcName;
|
||||
FuncLog() { count = 0; }
|
||||
FuncLog(std::wstring fName)
|
||||
{
|
||||
funcName = fName;
|
||||
count = 0;
|
||||
}
|
||||
};
|
||||
|
||||
class TraceLogger
|
||||
{
|
||||
public:
|
||||
TraceLogger(_In_ TraceLogger const&) = delete;
|
||||
TraceLogger const & operator= (_In_ TraceLogger const&) = delete;
|
||||
~TraceLogger();
|
||||
static TraceLogger& GetInstance();
|
||||
bool GetTraceLoggingProviderEnabled() const;
|
||||
|
||||
void LogAppLaunchStart();
|
||||
void LogAppLaunchComplete();
|
||||
void LogAppResumeComplete();
|
||||
void LogOnAppLaunch(std::wstring_view previousExecutionState) const;
|
||||
void LogMemoryClearAll(int);
|
||||
void LogBitFlipPaneClicked() const;
|
||||
void LogBitFlipUsed() const;
|
||||
void LogHistoryBodyOpened() const;
|
||||
void LogHistoryItemLoadBegin() const;
|
||||
void LogHistoryItemLoadEnd(unsigned int) const;
|
||||
void LogHistoryFlyoutOpenBegin(unsigned int) const;
|
||||
void LogHistoryFlyoutOpenEnd(int) const;
|
||||
void LogCalculatorModeViewed(CalculatorApp::Common::ViewMode, int);
|
||||
void LogDateCalculatorModeViewed(CalculatorApp::Common::ViewMode, int);
|
||||
void LogConverterModeViewed(CalculatorApp::Common::ViewMode, int);
|
||||
void LogModeChangeBegin(CalculatorApp::Common::ViewMode, CalculatorApp::Common::ViewMode, int);
|
||||
void LogModeChangeEnd(CalculatorApp::Common::ViewMode, int) const;
|
||||
void LogClearHistory() const;
|
||||
void InsertIntoMemoryMap(int, bool, bool, bool);
|
||||
void UpdateMemoryMap(int, int, bool, bool, bool);
|
||||
void DeleteFromMemoryMap(int, int);
|
||||
void LogMemoryUsed(int, unsigned int, bool, bool, bool, unsigned int) const;
|
||||
void LogMultipleMemoryUsed(unsigned int, unsigned int) const;
|
||||
void LogSingleMemoryUsed(unsigned int) const;
|
||||
void LogSharedMemoryUsed(std::wstring_view, std::wstring_view, unsigned int) const;
|
||||
void LogMemoryBodyOpened() const;
|
||||
void LogMemoryFlyoutOpenBegin(unsigned int) const;
|
||||
void LogDebug(std::wstring_view debugData);
|
||||
void LogMemoryFlyoutOpenEnd(unsigned int) const;
|
||||
void LogInvalidInputPasted(std::wstring_view reason, std::wstring_view pastedExpression, CalculatorApp::Common::ViewMode mode, int ProgrammerNumberBase, int bitLengthType);
|
||||
void LogValidInputPasted(CalculatorApp::Common::ViewMode mode) const;
|
||||
void UpdateFunctionUsage(int func);
|
||||
void LogFunctionUsage(int);
|
||||
void InitFunctionLogArray();
|
||||
void LogBitLengthButtonUsed(int windowId);
|
||||
void LogRadixButtonUsed(int windowId);
|
||||
void LogAngleButtonUsed(int windowId);
|
||||
void LogHypButtonUsed(int windowId);
|
||||
void LogNewWindowCreationBegin(int windowId);
|
||||
void LogNewWindowCreationEnd(int windowId);
|
||||
void LogError(std::wstring_view errorString);
|
||||
void LogPrelaunchedAppActivatedByUser();
|
||||
void LogAppPrelaunchedBySystem();
|
||||
void UpdateWindowCount(size_t windowCount);
|
||||
bool UpdateWindowIdLog(int windowId);
|
||||
void LogMaxWindowCount();
|
||||
void LogWindowActivated() const;
|
||||
void LogWindowLaunched() const;
|
||||
void LogUserRequestedRefreshFailed() const;
|
||||
void LogConversionResult(std::wstring_view fromValue, std::wstring_view fromUnit, std::wstring_view toValue, std::wstring_view toUnit) const;
|
||||
void LogAboutFlyoutOpened() const;
|
||||
void LogNavBarOpened() const;
|
||||
void LogViewClosingTelemetry(int);
|
||||
void LogCoreWindowWasNull() const;
|
||||
|
||||
// Trace methods for Date Calculator usage
|
||||
void LogDateDifferenceModeUsed(int windowId);
|
||||
void LogDateAddSubtractModeUsed(int windowId, bool isAddMode);
|
||||
void LogDateClippedTimeDifferenceFound(winrt::Windows::Globalization::Calendar const& today, winrt::Windows::Foundation::DateTime const& clippedTime) const;
|
||||
|
||||
void LogStandardException(std::wstring_view functionName, _In_ const std::exception& e) const;
|
||||
void LogWinRTException(std::wstring_view functionName, _In_ winrt::hresult_error const& e) const;
|
||||
void LogPlatformException(std::wstring_view functionName, _In_ Platform::Exception^ e) const;
|
||||
|
||||
private:
|
||||
// Create an instance of TraceLogger
|
||||
TraceLogger();
|
||||
|
||||
// Any new Log method should
|
||||
// a) decide the level of logging. This will help us in limiting recording of events only upto a certain level. See this link for guidance https://msdn.microsoft.com/en-us/library/windows/desktop/aa363742(v=vs.85).aspx
|
||||
// We're using Verbose level for events that are called frequently and needed only for debugging or capturing perf for specific scenarios
|
||||
// b) should decide whether or not to log to telemetry and pass TraceLoggingKeyword(MICROSOFT_KEYWORD_TELEMETRY) accordingly
|
||||
// c) Should accept a variable number of additional data arguments if needed
|
||||
void LogTelemetryEvent(std::wstring_view eventName, winrt::Windows::Foundation::Diagnostics::LoggingFields fields) const;
|
||||
void LogMeasureEvent(std::wstring_view eventName, winrt::Windows::Foundation::Diagnostics::LoggingFields fields) const;
|
||||
void LogCriticalDataEvent(std::wstring_view eventName, winrt::Windows::Foundation::Diagnostics::LoggingFields fields) const;
|
||||
void LogPerformanceEvent(std::wstring_view eventName, winrt::Windows::Foundation::Diagnostics::LoggingFields fields) const;
|
||||
void LogInfoEvent(std::wstring_view eventName, winrt::Windows::Foundation::Diagnostics::LoggingFields fields) const;
|
||||
|
||||
std::unique_ptr<TraceActivity> CreateTraceActivity(std::wstring_view activityName, winrt::Windows::Foundation::Diagnostics::LoggingFields fields) const;
|
||||
|
||||
winrt::Windows::Foundation::Diagnostics::LoggingChannel g_calculatorProvider;
|
||||
|
||||
bool isSizeChangeLogged = false;
|
||||
bool isHideIfShownLogged = false;
|
||||
bool isSizeChangedFirstTime = true; // to track the first size changed event which is fired on the launch of app
|
||||
bool isAutoConversionBeginLoggedInSession = false;
|
||||
bool isAutoConversionEndLoggedInSession = false;
|
||||
bool angleButtonLoggedInSession = false;
|
||||
bool radixButtonLoggedInSession = false;
|
||||
bool bitLengthButtonLoggedInSession = false;
|
||||
GUID sessionGuid;
|
||||
CalculatorApp::Common::ViewMode currentMode = CalculatorApp::Common::ViewMode::None;
|
||||
std::vector<FuncLog> funcLog;
|
||||
int functionCount = 0;
|
||||
bool isHypButtonLogged = false;
|
||||
bool isAngleButtonInitialized = false;
|
||||
unsigned int findIndex[maxFunctionSize] = { 0 };
|
||||
bool GetIndex(int &index);
|
||||
std::wstring GetProgrammerType(int index);
|
||||
size_t maxWindowCount = 0;
|
||||
bool isAppLaunchBeginLogged = false;
|
||||
bool isAppLaunchEndLogged = false;
|
||||
std::map<int, int> bitLengthButtonUsage;
|
||||
std::map<int, int> angleButtonUsage;
|
||||
std::map<int, int> radixButtonUsage;
|
||||
std::map<int, bool> windowIdLog;
|
||||
|
||||
// Private variables for Date Calculator usage
|
||||
bool m_dateDiffUsageLoggedInSession = false;
|
||||
bool m_dateAddUsageLoggedInSession = false;
|
||||
bool m_dateSubtractUsageLoggedInSession = false;
|
||||
std::map<int, int> m_dateAddModeUsage;
|
||||
std::map<int, int> m_dateSubtractModeUsage;
|
||||
|
||||
size_t windowLaunchCount = 0;
|
||||
|
||||
winrt::Windows::Foundation::Diagnostics::LoggingActivity m_appLaunchActivity;
|
||||
};
|
||||
}
|
234
src/CalcViewModel/Common/Utils.cpp
Normal file
234
src/CalcViewModel/Common/Utils.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//
|
||||
// Utils.cpp
|
||||
//
|
||||
|
||||
#include "pch.h"
|
||||
#include "Utils.h"
|
||||
#include "Common\AppResourceProvider.h"
|
||||
#include "Common\ExpressionCommandSerializer.h"
|
||||
#include "Common\ExpressionCommandDeserializer.h"
|
||||
#include "ViewState.h"
|
||||
|
||||
using namespace CalculatorApp;
|
||||
using namespace CalculatorApp::Common;
|
||||
using namespace concurrency;
|
||||
using namespace Platform;
|
||||
using namespace std;
|
||||
using namespace Utils;
|
||||
using namespace Windows::ApplicationModel::Resources;
|
||||
using namespace Windows::Storage::Streams;
|
||||
using namespace Windows::UI::Core;
|
||||
using namespace Windows::UI::ViewManagement;
|
||||
using namespace Windows::UI::Xaml;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Storage;
|
||||
|
||||
void Utils::IFTPlatformException(HRESULT hr)
|
||||
{
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Platform::Exception^ exception = ref new Platform::Exception(hr);
|
||||
throw(exception);
|
||||
}
|
||||
}
|
||||
|
||||
String^ Utils::GetStringValue(String^ input)
|
||||
{
|
||||
// Remove first and last " characters
|
||||
if (input->Length() >= 3)
|
||||
{
|
||||
wstring out(input->Begin() + 1, input->End() - 1);
|
||||
return ref new String(out.c_str());
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
double Utils::GetDoubleFromWstring(wstring input)
|
||||
{
|
||||
wchar_t unWantedChars[] = { L' ', L',', 8234, 8235, 8236, 8237 };
|
||||
wstring ws = RemoveUnwantedCharsFromWstring(input, unWantedChars, 6);
|
||||
string inputString(ws.begin(), ws.end());
|
||||
return ::atof(inputString.c_str());
|
||||
}
|
||||
|
||||
//returns windowId for the current view
|
||||
int Utils::GetWindowId()
|
||||
{
|
||||
int windowId = -1;
|
||||
|
||||
auto window = CoreWindow::GetForCurrentThread();
|
||||
if (window != nullptr)
|
||||
{
|
||||
windowId = ApplicationView::GetApplicationViewIdForWindow(window);
|
||||
}
|
||||
|
||||
return windowId;
|
||||
}
|
||||
|
||||
void Utils::RunOnUIThreadNonblocking(std::function<void()>&& function, _In_ CoreDispatcher^ currentDispatcher)
|
||||
{
|
||||
if (currentDispatcher != nullptr)
|
||||
{
|
||||
auto task = create_task(currentDispatcher->RunAsync(CoreDispatcherPriority::Normal,
|
||||
ref new DispatchedHandler([function]()
|
||||
{
|
||||
function();
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
// returns if the last character of a wstring is the target wchar_t
|
||||
bool Utils::IsLastCharacterTarget(_In_ wstring const &input, _In_ wchar_t target)
|
||||
{
|
||||
return !input.empty() && input.back() == target;
|
||||
}
|
||||
|
||||
//return wstring after removing characters like space, comma, and double quotes
|
||||
wstring Utils::RemoveUnwantedCharsFromWstring(wstring input)
|
||||
{
|
||||
wchar_t unWantedChars[] = { L' ', L',', L'"', 8234, 8235, 8236, 8237 };
|
||||
return RemoveUnwantedCharsFromWstring(input, unWantedChars, 6);
|
||||
}
|
||||
|
||||
//return wstring after removing characters specified by unwantedChars array
|
||||
wstring Utils::RemoveUnwantedCharsFromWstring(wstring input, wchar_t* unwantedChars, unsigned int size)
|
||||
{
|
||||
for (unsigned int i = 0; i < size; ++i)
|
||||
{
|
||||
input.erase(std::remove(input.begin(), input.end(), unwantedChars[i]), input.end());
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
void Utils::SerializeCommandsAndTokens(_In_ shared_ptr<CalculatorVector <pair<wstring, int>>> const &tokens,
|
||||
_In_ shared_ptr<CalculatorVector<shared_ptr<IExpressionCommand>>> const &commands,
|
||||
DataWriter^ writer)
|
||||
{
|
||||
unsigned int commandsSize;
|
||||
IFTPlatformException(commands->GetSize(&commandsSize));
|
||||
|
||||
// save the size of the commands vector
|
||||
writer->WriteUInt32(commandsSize);
|
||||
|
||||
SerializeCommandVisitor cmdVisitor(writer);
|
||||
for (unsigned int i = 0; i < commandsSize; ++i)
|
||||
{
|
||||
shared_ptr<IExpressionCommand> exprCmd;
|
||||
IFTPlatformException(commands->GetAt(i, &exprCmd));
|
||||
|
||||
CalculationManager::CommandType commandType = exprCmd->GetCommandType();
|
||||
writer->WriteInt32(static_cast<int>(commandType));
|
||||
exprCmd->Accept(cmdVisitor);
|
||||
}
|
||||
|
||||
unsigned int tokensSize;
|
||||
IFTPlatformException(tokens->GetSize(&tokensSize));
|
||||
writer->WriteUInt32(tokensSize);
|
||||
|
||||
for (unsigned int i = 0; i < tokensSize; ++i)
|
||||
{
|
||||
pair<wstring, int> eachToken;
|
||||
IFTPlatformException(tokens->GetAt(i, &eachToken));
|
||||
|
||||
auto stringData = ref new Platform::String(eachToken.first.c_str());
|
||||
auto intData = eachToken.second;
|
||||
writer->WriteUInt32(writer->MeasureString(stringData));
|
||||
writer->WriteString(stringData);
|
||||
writer->WriteInt32(intData);
|
||||
}
|
||||
}
|
||||
|
||||
const shared_ptr<CalculatorVector<shared_ptr<IExpressionCommand>>> Utils::DeserializeCommands(DataReader^ reader)
|
||||
{
|
||||
shared_ptr<CalculatorVector<shared_ptr<IExpressionCommand>>> commandVector = make_shared<CalculatorVector<shared_ptr<IExpressionCommand>>>();
|
||||
auto commandVectorSize = reader->ReadUInt32();
|
||||
|
||||
CommandDeserializer cmdDeserializer(reader);
|
||||
for (unsigned int i = 0; i < commandVectorSize; ++i)
|
||||
{
|
||||
auto commandTypeInt = reader->ReadInt32();
|
||||
CalculationManager::CommandType commandType = static_cast<CalculationManager::CommandType>(commandTypeInt);
|
||||
shared_ptr<IExpressionCommand> exprCmd = cmdDeserializer.Deserialize(commandType);
|
||||
commandVector->Append(exprCmd);
|
||||
}
|
||||
|
||||
return commandVector;
|
||||
}
|
||||
|
||||
const shared_ptr<CalculatorVector <pair<wstring, int>>> Utils::DeserializeTokens(DataReader^ reader)
|
||||
{
|
||||
shared_ptr<CalculatorVector <pair<wstring, int>>> tokenVector = make_shared<CalculatorVector<pair<wstring, int>>>();
|
||||
auto tokensSize = reader->ReadUInt32();
|
||||
|
||||
for (unsigned int i = 0; i < tokensSize; ++i)
|
||||
{
|
||||
pair<wstring, int> eachToken;
|
||||
auto stringDataLen = reader->ReadUInt32();
|
||||
auto stringData = reader->ReadString(stringDataLen);
|
||||
auto intData = reader->ReadInt32();
|
||||
eachToken.first = stringData->Data();
|
||||
eachToken.second = intData;
|
||||
tokenVector->Append(eachToken);
|
||||
}
|
||||
|
||||
return tokenVector;
|
||||
}
|
||||
|
||||
DateTime Utils::GetUniversalSystemTime()
|
||||
{
|
||||
SYSTEMTIME sysTime = {};
|
||||
GetSystemTime(&sysTime);
|
||||
|
||||
FILETIME sysTimeAsFileTime = {};
|
||||
SystemTimeToFileTime(&sysTime, &sysTimeAsFileTime);
|
||||
|
||||
ULARGE_INTEGER ularge;
|
||||
ularge.HighPart = sysTimeAsFileTime.dwHighDateTime;
|
||||
ularge.LowPart = sysTimeAsFileTime.dwLowDateTime;
|
||||
|
||||
DateTime result;
|
||||
result.UniversalTime = ularge.QuadPart;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Utils::IsDateTimeOlderThan(DateTime dateTime, const long long duration)
|
||||
{
|
||||
DateTime now = Utils::GetUniversalSystemTime();
|
||||
return dateTime.UniversalTime + duration < now.UniversalTime;
|
||||
}
|
||||
|
||||
task<void> Utils::WriteFileToFolder(IStorageFolder^ folder, String^ fileName, String^ contents, CreationCollisionOption collisionOption)
|
||||
{
|
||||
if (folder == nullptr)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
StorageFile^ file = co_await folder->CreateFileAsync(fileName, collisionOption);
|
||||
if (file == nullptr)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
co_await FileIO::WriteTextAsync(file, contents);
|
||||
}
|
||||
|
||||
task<String^> Utils::ReadFileFromFolder(IStorageFolder^ folder, String^ fileName)
|
||||
{
|
||||
if (folder == nullptr)
|
||||
{
|
||||
co_return nullptr;
|
||||
}
|
||||
|
||||
StorageFile^ file = co_await folder->GetFileAsync(fileName);
|
||||
if (file == nullptr)
|
||||
{
|
||||
co_return nullptr;
|
||||
}
|
||||
|
||||
String^ contents = co_await FileIO::ReadTextAsync(file);
|
||||
co_return contents;
|
||||
}
|
414
src/CalcViewModel/Common/Utils.h
Normal file
414
src/CalcViewModel/Common/Utils.h
Normal file
@@ -0,0 +1,414 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Utility macros to make Models easier to write
|
||||
// generates a member variable called m_<n>
|
||||
#define PROPERTY_R(t, n)\
|
||||
property t n {\
|
||||
t get() { return m_##n; }\
|
||||
private: void set(t value) { m_##n = value; }\
|
||||
} private: t m_##n; public:
|
||||
|
||||
#define PROPERTY_RW(t, n)\
|
||||
property t n {\
|
||||
t get() { return m_##n; }\
|
||||
void set(t value) { m_##n = value; }\
|
||||
} private: t m_##n; public:
|
||||
|
||||
#define OBSERVABLE_PROPERTY_R(t, n)\
|
||||
property t n {\
|
||||
t get() { return m_##n; }\
|
||||
private: void set(t value) {\
|
||||
if (m_##n != value) {\
|
||||
m_##n = value;\
|
||||
RaisePropertyChanged(L#n);\
|
||||
}}\
|
||||
} private: t m_##n; public:
|
||||
|
||||
#define OBSERVABLE_PROPERTY_RW(t, n)\
|
||||
property t n {\
|
||||
t get() { return m_##n; }\
|
||||
void set(t value) {\
|
||||
if (m_##n != value) {\
|
||||
m_##n = value;\
|
||||
RaisePropertyChanged(L#n);\
|
||||
}\
|
||||
}\
|
||||
} private: t m_##n; public:
|
||||
|
||||
#define NAMED_OBSERVABLE_PROPERTY_RW(t, n)\
|
||||
OBSERVABLE_PROPERTY_RW(t, n)\
|
||||
private: property Platform::StringReference n##_PropertyName {\
|
||||
Platform::StringReference get() { return Platform::StringReference(L#n); }\
|
||||
} public:
|
||||
|
||||
#define OBSERVABLE_PROPERTY_FIELD(n) m_##n
|
||||
|
||||
// This variant of the observable object is for objects that don't want to react to property changes
|
||||
#ifndef UNIT_TESTS
|
||||
#define OBSERVABLE_OBJECT() virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged;\
|
||||
internal: void RaisePropertyChanged(Platform::String^ p) {\
|
||||
PropertyChanged(this, ref new Windows::UI::Xaml::Data::PropertyChangedEventArgs(p)); } public:
|
||||
#else
|
||||
#define OBSERVABLE_OBJECT() virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged;\
|
||||
internal: void RaisePropertyChanged(Platform::String^ p) {\
|
||||
} public:
|
||||
#endif
|
||||
|
||||
// The callback specified in the macro is a method in the class that will be called every time the object changes
|
||||
// the callback is supposed to be have a single parameter of type Platform::String^
|
||||
#ifndef UNIT_TESTS
|
||||
#define OBSERVABLE_OBJECT_CALLBACK(c) virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged;\
|
||||
internal: void RaisePropertyChanged(Platform::String^ p) {\
|
||||
PropertyChanged(this, ref new Windows::UI::Xaml::Data::PropertyChangedEventArgs(p));\
|
||||
c(p);\
|
||||
} public:
|
||||
#else
|
||||
#define OBSERVABLE_OBJECT_CALLBACK(c) virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged;\
|
||||
internal: void RaisePropertyChanged(Platform::String^ p) {\
|
||||
c(p);\
|
||||
} public:
|
||||
#endif
|
||||
|
||||
// The variable member generated by this macro should not be used in the class code, use the
|
||||
// property getter instead.
|
||||
#define COMMAND_FOR_METHOD(p, m) property Windows::UI::Xaml::Input::ICommand^ p {\
|
||||
Windows::UI::Xaml::Input::ICommand^ get() {\
|
||||
if (!donotuse_##p) {\
|
||||
donotuse_##p = CalculatorApp::Common::MakeDelegate(this, &m);\
|
||||
} return donotuse_##p; }} private: Windows::UI::Xaml::Input::ICommand^ donotuse_##p; public:
|
||||
|
||||
#define DEPENDENCY_PROPERTY_DECLARATION(t, n)\
|
||||
property t n {\
|
||||
t get() { return safe_cast<t>(GetValue(s_##n##Property)); }\
|
||||
void set(t value) { SetValue(s_##n##Property, value); } }\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ s_##n##Property; public:
|
||||
|
||||
// Utilities for DependencyProperties
|
||||
namespace Utils
|
||||
{
|
||||
namespace Details
|
||||
{
|
||||
template <typename T>
|
||||
struct IsRefClass
|
||||
{
|
||||
static const bool value = __is_ref_class(T);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct RemoveHat
|
||||
{
|
||||
typedef T type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct RemoveHat<T^>
|
||||
{
|
||||
typedef T type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<IsRefClass<T>::value, T^>::type MakeDefault()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!IsRefClass<T>::value, T>::type MakeDefault()
|
||||
{
|
||||
return T();
|
||||
}
|
||||
|
||||
// There's a bug in Xaml in which custom enums are not recognized by the property system/binding
|
||||
// therefore this template will determine that for enums the type to use to register the
|
||||
// DependencyProperty is to be Object, for everything else it will use the type
|
||||
// NOTE: If we are to find more types in which this is broken this template
|
||||
// will be specialized for those types to return Object
|
||||
template <typename T>
|
||||
struct TypeToUseForDependencyProperty
|
||||
{
|
||||
typedef typename std::conditional<std::is_enum<T>::value, Platform::Object, T>::type type;
|
||||
};
|
||||
}
|
||||
|
||||
const wchar_t LRE = 0x202a; // Left-to-Right Embedding
|
||||
const wchar_t PDF = 0x202c; // Pop Directional Formatting
|
||||
const wchar_t LRO = 0x202d; // Left-to-Right Override
|
||||
|
||||
// Regular DependencyProperty
|
||||
template <typename TOwner, typename TType>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyProperty(
|
||||
_In_ const wchar_t* const name,
|
||||
_In_ Windows::UI::Xaml::PropertyMetadata^ metadata)
|
||||
{
|
||||
typedef typename Details::RemoveHat<TOwner>::type OwnerType;
|
||||
typedef typename Details::RemoveHat<TType>::type ThisPropertyType;
|
||||
typedef typename Details::TypeToUseForDependencyProperty<ThisPropertyType>::type ThisDependencyPropertyType;
|
||||
|
||||
static_assert(Details::IsRefClass<OwnerType>::value, "The owner of a DependencyProperty must be a ref class");
|
||||
|
||||
return Windows::UI::Xaml::DependencyProperty::Register(
|
||||
Platform::StringReference(name),
|
||||
ThisDependencyPropertyType::typeid, // Work around bugs in Xaml by using the filtered type
|
||||
OwnerType::typeid,
|
||||
metadata);
|
||||
}
|
||||
|
||||
template <typename TOwner, typename TType>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyProperty(_In_ const wchar_t* const name)
|
||||
{
|
||||
typedef typename Details::RemoveHat<TType>::type ThisPropertyType;
|
||||
|
||||
return RegisterDependencyProperty<TOwner, TType>(
|
||||
name,
|
||||
ref new Windows::UI::Xaml::PropertyMetadata(Details::MakeDefault<ThisPropertyType>()));
|
||||
}
|
||||
|
||||
template <typename TOwner, typename TType>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyProperty(_In_ const wchar_t* const name, TType defaultValue)
|
||||
{
|
||||
return RegisterDependencyProperty<TOwner, TType>(
|
||||
name,
|
||||
ref new Windows::UI::Xaml::PropertyMetadata(defaultValue));
|
||||
}
|
||||
|
||||
template <typename TOwner, typename TType, typename TCallback>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyPropertyWithCallback(
|
||||
_In_ wchar_t const * const name,
|
||||
TCallback callback)
|
||||
{
|
||||
typedef typename Details::RemoveHat<TType>::type ThisPropertyType;
|
||||
return RegisterDependencyProperty<TOwner, TType>(
|
||||
name,
|
||||
ref new Windows::UI::Xaml::PropertyMetadata(
|
||||
Details::MakeDefault<ThisPropertyType>(),
|
||||
ref new Windows::UI::Xaml::PropertyChangedCallback(callback)));
|
||||
}
|
||||
|
||||
template <typename TOwner, typename TType, typename TCallback>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyPropertyWithCallback(
|
||||
_In_ wchar_t const * const name,
|
||||
TType defaultValue,
|
||||
TCallback callback)
|
||||
{
|
||||
typedef typename Details::RemoveHat<TType>::type ThisPropertyType;
|
||||
return RegisterDependencyProperty<TOwner, TType>(
|
||||
name,
|
||||
ref new Windows::UI::Xaml::PropertyMetadata(
|
||||
defaultValue,
|
||||
ref new Windows::UI::Xaml::PropertyChangedCallback(callback)));
|
||||
}
|
||||
|
||||
// Attached DependencyProperty
|
||||
template <typename TOwner, typename TType>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyPropertyAttached(
|
||||
_In_ const wchar_t* const name,
|
||||
_In_ Windows::UI::Xaml::PropertyMetadata^ metadata)
|
||||
{
|
||||
typedef typename Details::RemoveHat<TOwner>::type OwnerType;
|
||||
typedef typename Details::RemoveHat<TType>::type ThisPropertyType;
|
||||
typedef typename Details::TypeToUseForDependencyProperty<ThisPropertyType>::type ThisDependencyPropertyType;
|
||||
|
||||
static_assert(Details::IsRefClass<OwnerType>::value, "The owner of a DependencyProperty must be a ref class");
|
||||
|
||||
return Windows::UI::Xaml::DependencyProperty::RegisterAttached(
|
||||
Platform::StringReference(name),
|
||||
ThisDependencyPropertyType::typeid, // Work around bugs in Xaml by using the filtered type
|
||||
OwnerType::typeid,
|
||||
metadata);
|
||||
}
|
||||
|
||||
template <typename TOwner, typename TType>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyPropertyAttached(_In_ const wchar_t* const name)
|
||||
{
|
||||
typedef typename Details::RemoveHat<TType>::type ThisPropertyType;
|
||||
return RegisterDependencyPropertyAttached<TOwner, TType>(
|
||||
name,
|
||||
ref new Windows::UI::Xaml::PropertyMetadata(Details::MakeDefault<ThisPropertyType>()));
|
||||
}
|
||||
|
||||
template <typename TOwner, typename TType>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyPropertyAttached(_In_ const wchar_t* const name, TType defaultValue)
|
||||
{
|
||||
return RegisterDependencyPropertyAttached<TOwner, TType>(
|
||||
name,
|
||||
ref new Windows::UI::Xaml::PropertyMetadata(defaultValue));
|
||||
}
|
||||
|
||||
template <typename TOwner, typename TType, typename TCallback>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyPropertyAttachedWithCallback(
|
||||
_In_ wchar_t const * const name,
|
||||
TCallback callback)
|
||||
{
|
||||
typedef typename Details::RemoveHat<TType>::type ThisPropertyType;
|
||||
return RegisterDependencyPropertyAttached<TOwner, TType>(
|
||||
name,
|
||||
ref new Windows::UI::Xaml::PropertyMetadata(
|
||||
Details::MakeDefault<ThisPropertyType>(),
|
||||
ref new Windows::UI::Xaml::PropertyChangedCallback(callback)));
|
||||
}
|
||||
|
||||
template <typename TOwner, typename TType, typename TCallback>
|
||||
Windows::UI::Xaml::DependencyProperty^ RegisterDependencyPropertyAttachedWithCallback(
|
||||
_In_ wchar_t const * const name,
|
||||
TType defaultValue,
|
||||
TCallback callback)
|
||||
{
|
||||
typedef typename Details::RemoveHat<TType>::type ThisPropertyType;
|
||||
return RegisterDependencyPropertyAttached<TOwner, TType>(
|
||||
name,
|
||||
ref new Windows::UI::Xaml::PropertyMetadata(
|
||||
defaultValue,
|
||||
ref new Windows::UI::Xaml::PropertyChangedCallback(callback)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Swap(T *ref1, T *ref2)
|
||||
{
|
||||
T temp = *ref1;
|
||||
*ref1 = *ref2;
|
||||
*ref2 = temp;
|
||||
}
|
||||
|
||||
void IFTPlatformException(HRESULT hr);
|
||||
Platform::String^ GetStringValue(Platform::String^ input);
|
||||
bool IsLastCharacterTarget(std::wstring const &input, wchar_t target);
|
||||
std::wstring RemoveUnwantedCharsFromWstring(std::wstring inputString, wchar_t* unwantedChars, unsigned int size);
|
||||
std::wstring RemoveUnwantedCharsFromWstring(std::wstring input);
|
||||
double GetDoubleFromWstring(std::wstring input);
|
||||
int GetWindowId();
|
||||
void RunOnUIThreadNonblocking(std::function<void()>&& function, _In_ Windows::UI::Core::CoreDispatcher^ currentDispatcher);
|
||||
void SerializeCommandsAndTokens(_In_ std::shared_ptr<CalculatorVector <std::pair<std::wstring, int>>> const &tokens,
|
||||
_In_ std::shared_ptr<CalculatorVector<std::shared_ptr<IExpressionCommand>>> const &commands,
|
||||
Windows::Storage::Streams::DataWriter^ writer);
|
||||
|
||||
const std::shared_ptr<CalculatorVector<std::shared_ptr<IExpressionCommand>>> DeserializeCommands(Windows::Storage::Streams::DataReader^ reader);
|
||||
const std::shared_ptr<CalculatorVector <std::pair<std::wstring, int>>> DeserializeTokens(Windows::Storage::Streams::DataReader^ reader);
|
||||
|
||||
Windows::Foundation::DateTime GetUniversalSystemTime();
|
||||
bool IsDateTimeOlderThan(Windows::Foundation::DateTime dateTime, const long long duration);
|
||||
|
||||
concurrency::task<void> WriteFileToFolder(Windows::Storage::IStorageFolder^ folder, Platform::String^ fileName, Platform::String^ contents, Windows::Storage::CreationCollisionOption collisionOption);
|
||||
concurrency::task<Platform::String^> ReadFileFromFolder(Windows::Storage::IStorageFolder^ folder, Platform::String^ fileName);
|
||||
}
|
||||
|
||||
// This goes into the header to define the property, in the public: section of the class
|
||||
#define DEPENDENCY_PROPERTY_OWNER(owner)\
|
||||
private: typedef owner DependencyPropertiesOwner; public:
|
||||
|
||||
// Normal DependencyProperty
|
||||
#define DEPENDENCY_PROPERTY(type, name)\
|
||||
property type name {\
|
||||
type get() { return safe_cast<type>(GetValue(s_##name##Property)); }\
|
||||
void set(type value) { SetValue(s_##name##Property, value); }\
|
||||
} private: static Windows::UI::Xaml::DependencyProperty^ s_##name##Property;\
|
||||
public: static property Windows::UI::Xaml::DependencyProperty^ name##Property {\
|
||||
Windows::UI::Xaml::DependencyProperty^ get() { assert(s_##name##Property); return s_##name##Property; }\
|
||||
}\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ Initialize##name##Property() {\
|
||||
return Utils::RegisterDependencyProperty<DependencyPropertiesOwner, type>(L#name); } public:
|
||||
|
||||
#define DEPENDENCY_PROPERTY_WITH_DEFAULT(type, name, defaultValue)\
|
||||
property type name {\
|
||||
type get() { return safe_cast<type>(GetValue(s_##name##Property)); }\
|
||||
void set(type value) { SetValue(s_##name##Property, value); }\
|
||||
} private: static Windows::UI::Xaml::DependencyProperty^ s_##name##Property;\
|
||||
public: static property Windows::UI::Xaml::DependencyProperty^ name##Property {\
|
||||
Windows::UI::Xaml::DependencyProperty^ get() { assert(s_##name##Property); return s_##name##Property; }\
|
||||
}\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ Initialize##name##Property() {\
|
||||
return Utils::RegisterDependencyProperty<DependencyPropertiesOwner, type>(L#name, defaultValue); } public:
|
||||
|
||||
#define DEPENDENCY_PROPERTY_WITH_CALLBACK(type, name)\
|
||||
property type name {\
|
||||
type get() { return safe_cast<type>(GetValue(s_##name##Property)); }\
|
||||
void set(type value) { SetValue(s_##name##Property, value); }\
|
||||
} private: static Windows::UI::Xaml::DependencyProperty^ s_##name##Property;\
|
||||
public: static property Windows::UI::Xaml::DependencyProperty^ name##Property {\
|
||||
Windows::UI::Xaml::DependencyProperty^ get() { assert(s_##name##Property); return s_##name##Property; }\
|
||||
}\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ Initialize##name##Property() {\
|
||||
return Utils::RegisterDependencyPropertyWithCallback<DependencyPropertiesOwner, type>(L#name, &On##name##PropertyChangedImpl); }\
|
||||
static void On##name##PropertyChangedImpl(Windows::UI::Xaml::DependencyObject^ sender, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args) {\
|
||||
auto self = safe_cast<DependencyPropertiesOwner^>(sender);\
|
||||
self->On##name##PropertyChanged(safe_cast<type>(args->OldValue), safe_cast<type>(args->NewValue)); } public:
|
||||
|
||||
#define DEPENDENCY_PROPERTY_WITH_DEFAULT_AND_CALLBACK(type, name, defaultValue)\
|
||||
property type name {\
|
||||
type get() { return safe_cast<type>(GetValue(s_##name##Property)); }\
|
||||
void set(type value) { SetValue(s_##name##Property, value); }\
|
||||
} private: static Windows::UI::Xaml::DependencyProperty^ s_##name##Property;\
|
||||
public: static property Windows::UI::Xaml::DependencyProperty^ name##Property {\
|
||||
Windows::UI::Xaml::DependencyProperty^ get() { assert(s_##name##Property); return s_##name##Property; }\
|
||||
}\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ Initialize##name##Property() {\
|
||||
return Utils::RegisterDependencyPropertyWithCallback<DependencyPropertiesOwner, type>(L#name, defaultValue, &On##name##PropertyChangedImpl); }\
|
||||
static void On##name##PropertyChangedImpl(Windows::UI::Xaml::DependencyObject^ sender, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args) {\
|
||||
auto self = safe_cast<DependencyPropertiesOwner^>(sender);\
|
||||
self->On##name##PropertyChanged(safe_cast<type>(args->OldValue), safe_cast<type>(args->NewValue)); } public:
|
||||
|
||||
// Attached DependencyProperty
|
||||
#define DEPENDENCY_PROPERTY_ATTACHED(type, name)\
|
||||
static type Get##name(Windows::UI::Xaml::DependencyObject^ target) { return safe_cast<type>(target->GetValue(s_##name##Property)); }\
|
||||
static void Set##name(Windows::UI::Xaml::DependencyObject^ target, type value) { target->SetValue(s_##name##Property, value); }\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ s_##name##Property;\
|
||||
public: static property Windows::UI::Xaml::DependencyProperty^ name##Property {\
|
||||
Windows::UI::Xaml::DependencyProperty^ get() { assert(s_##name##Property); return s_##name##Property; }\
|
||||
}\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ Initialize##name##Property() {\
|
||||
return Utils::RegisterDependencyPropertyAttached<DependencyPropertiesOwner, type>(L#name); } public:
|
||||
|
||||
#define DEPENDENCY_PROPERTY_ATTACHED_WITH_DEFAULT(type, name, defaultValue)\
|
||||
static type Get##name(Windows::UI::Xaml::DependencyObject^ target) { return safe_cast<type>(target->GetValue(s_##name##Property)); }\
|
||||
static void Set##name(Windows::UI::Xaml::DependencyObject^ target, type value) { target->SetValue(s_##name##Property, value); }\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ s_##name##Property;\
|
||||
public: static property Windows::UI::Xaml::DependencyProperty^ name##Property {\
|
||||
Windows::UI::Xaml::DependencyProperty^ get() { assert(s_##name##Property); return s_##name##Property; }\
|
||||
}\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ Initialize##name##Property() {\
|
||||
return Utils::RegisterDependencyPropertyAttached<DependencyPropertiesOwner, type>(L#name, defaultValue); } public:
|
||||
|
||||
#define DEPENDENCY_PROPERTY_ATTACHED_WITH_CALLBACK(type, name)\
|
||||
static type Get##name(Windows::UI::Xaml::DependencyObject^ target) { return safe_cast<type>(target->GetValue(s_##name##Property)); }\
|
||||
static void Set##name(Windows::UI::Xaml::DependencyObject^ target, type value) { target->SetValue(s_##name##Property, value); }\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ s_##name##Property;\
|
||||
public: static property Windows::UI::Xaml::DependencyProperty^ name##Property {\
|
||||
Windows::UI::Xaml::DependencyProperty^ get() { assert(s_##name##Property); return s_##name##Property; }\
|
||||
}\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ Initialize##name##Property() {\
|
||||
return Utils::RegisterDependencyPropertyAttachedWithCallback<DependencyPropertiesOwner, type>(L#name, &On##name##PropertyChangedImpl); }\
|
||||
static void On##name##PropertyChangedImpl(Windows::UI::Xaml::DependencyObject^ sender, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args) {\
|
||||
On##name##PropertyChanged(sender, safe_cast<type>(args->OldValue), safe_cast<type>(args->NewValue)); } public:
|
||||
|
||||
#define DEPENDENCY_PROPERTY_ATTACHED_WITH_DEFAULT_AND_CALLBACK(type, name, defaultValue)\
|
||||
static type Get##name(Windows::UI::Xaml::DependencyObject^ target) { return safe_cast<type>(target->GetValue(s_##name##Property)); }\
|
||||
static void Set##name(Windows::UI::Xaml::DependencyObject^ target, type value) { target->SetValue(s_##name##Property, value); }\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ s_##name##Property;\
|
||||
public: static property Windows::UI::Xaml::DependencyProperty^ name##Property {\
|
||||
Windows::UI::Xaml::DependencyProperty^ get() { assert(s_##name##Property); return s_##name##Property; }\
|
||||
}\
|
||||
private: static Windows::UI::Xaml::DependencyProperty^ Initialize##name##Property() {\
|
||||
return Utils::RegisterDependencyPropertyAttachedWithCallback<DependencyPropertiesOwner, type>(L#name, defaultValue, &On##name##PropertyChangedImpl); }\
|
||||
static void On##name##PropertyChangedImpl(Windows::UI::Xaml::DependencyObject^ sender, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ args) {\
|
||||
On##name##PropertyChanged(sender, safe_cast<type>(args->OldValue), safe_cast<type>(args->NewValue)); } public:
|
||||
|
||||
// This goes into the cpp to initalize the static variable
|
||||
#define DEPENDENCY_PROPERTY_INITIALIZATION(owner, name)\
|
||||
Windows::UI::Xaml::DependencyProperty^ owner::s_##name##Property =\
|
||||
owner::Initialize##name##Property();
|
||||
|
||||
namespace CalculatorApp
|
||||
{
|
||||
template <typename T>
|
||||
T from_cx(Platform::Object^ from)
|
||||
{
|
||||
T to{ nullptr };
|
||||
|
||||
winrt::check_hresult(reinterpret_cast<::IUnknown*>(from)->QueryInterface(winrt::guid_of<T>(),
|
||||
reinterpret_cast<void**>(winrt::put_abi(to))));
|
||||
|
||||
return to;
|
||||
}
|
||||
}
|
83
src/CalcViewModel/Common/ValidatingConverters.h
Normal file
83
src/CalcViewModel/Common/ValidatingConverters.h
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace CalculatorApp { namespace Common
|
||||
{
|
||||
public ref class ValidSelectedItemConverter sealed: public Windows::UI::Xaml::Data::IValueConverter
|
||||
{
|
||||
public:
|
||||
ValidSelectedItemConverter()
|
||||
{ }
|
||||
|
||||
private:
|
||||
|
||||
virtual Platform::Object^ Convert(
|
||||
Platform::Object^ value,
|
||||
Windows::UI::Xaml::Interop::TypeName /*targetType*/,
|
||||
Platform::Object^ /*parameter*/,
|
||||
Platform::String^ /*language*/) = Windows::UI::Xaml::Data::IValueConverter::Convert
|
||||
{
|
||||
// Pass through as we don't want to change the value from the source
|
||||
return value;
|
||||
}
|
||||
|
||||
virtual Platform::Object^ ConverBack(
|
||||
Platform::Object^ value,
|
||||
Windows::UI::Xaml::Interop::TypeName /*targetType*/,
|
||||
Platform::Object^ /*parameter*/,
|
||||
Platform::String^ /*language*/) = Windows::UI::Xaml::Data::IValueConverter::ConvertBack
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
// Stop the binding if the object is nullptr
|
||||
return Windows::UI::Xaml::DependencyProperty::UnsetValue;
|
||||
}
|
||||
};
|
||||
|
||||
public ref class ValidSelectedIndexConverter sealed: public Windows::UI::Xaml::Data::IValueConverter
|
||||
{
|
||||
public:
|
||||
ValidSelectedIndexConverter()
|
||||
{ }
|
||||
|
||||
private:
|
||||
|
||||
virtual Platform::Object^ Convert(
|
||||
Platform::Object^ value,
|
||||
Windows::UI::Xaml::Interop::TypeName /*targetType*/,
|
||||
Platform::Object^ /*parameter*/,
|
||||
Platform::String^ /*language*/) = Windows::UI::Xaml::Data::IValueConverter::Convert
|
||||
{
|
||||
// Pass through as we don't want to change the value from the source
|
||||
return value;
|
||||
}
|
||||
|
||||
virtual Platform::Object^ ConverBack(
|
||||
Platform::Object^ value,
|
||||
Windows::UI::Xaml::Interop::TypeName /*targetType*/,
|
||||
Platform::Object^ /*parameter*/,
|
||||
Platform::String^ /*language*/) = Windows::UI::Xaml::Data::IValueConverter::ConvertBack
|
||||
{
|
||||
// The value to be valid has to be a boxed int32 value
|
||||
// extract that value and ensure it is valid, ie >= 0
|
||||
if (value)
|
||||
{
|
||||
auto box = dynamic_cast<Windows::Foundation::IPropertyValue^>(value);
|
||||
if (box && box->Type == Windows::Foundation::PropertyType::Int32)
|
||||
{
|
||||
int index = box->GetInt32();
|
||||
if (index >= 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The value is not valid therefore stop the binding right here
|
||||
return Windows::UI::Xaml::DependencyProperty::UnsetValue;
|
||||
}
|
||||
};
|
||||
}}
|
Reference in New Issue
Block a user