From 503831895d325a5bf9800f13e5e1ed2025978157 Mon Sep 17 00:00:00 2001 From: Rudy Huyn Date: Fri, 6 Dec 2019 15:03:07 -0800 Subject: [PATCH] Hide or Disable graphing calculator menu item when necessary (#848) --- src/CalcViewModel/Common/NavCategory.cpp | 446 +++++++++++------- src/CalcViewModel/Common/NavCategory.h | 84 +--- src/CalcViewModel/pch.h | 1 + src/Calculator/Resources/en-US/Resources.resw | 18 +- .../KeyGraphFeaturesPanel.xaml | 37 +- src/Calculator/Views/MainPage.xaml.cpp | 1 + .../CurrencyConverterUnitTests.cpp | 10 +- .../NavCategoryUnitTests.cpp | 69 +-- 8 files changed, 369 insertions(+), 297 deletions(-) diff --git a/src/CalcViewModel/Common/NavCategory.cpp b/src/CalcViewModel/Common/NavCategory.cpp index a15ee7dd..a3028713 100644 --- a/src/CalcViewModel/Common/NavCategory.cpp +++ b/src/CalcViewModel/Common/NavCategory.cpp @@ -5,6 +5,7 @@ #include "NavCategory.h" #include "AppResourceProvider.h" #include "Common/LocalizationStringUtil.h" +#include using namespace CalculatorApp; using namespace CalculatorApp::Common; @@ -22,12 +23,6 @@ static constexpr bool SUPPORTS_ALL = true; 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 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; @@ -49,151 +44,263 @@ static constexpr int CURRENCY_ID = 16; static constexpr int GRAPHING_ID = 17; // ^^^ THESE CONSTANTS SHOULD NEVER CHANGE ^^^ +wchar_t* towchar_t(int number) +{ + auto wstr = to_wstring(number); + return _wcsdup(wstr.c_str()); +} + +extern "C" +{ + WINADVAPI LSTATUS APIENTRY RegGetValueW( + _In_ HKEY hkey, + _In_opt_ LPCWSTR lpSubKey, + _In_opt_ LPCWSTR lpValue, + _In_ DWORD dwFlags, + _Out_opt_ LPDWORD pdwType, + _When_( + (dwFlags & 0x7F) == RRF_RT_REG_SZ || (dwFlags & 0x7F) == RRF_RT_REG_EXPAND_SZ || (dwFlags & 0x7F) == (RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ) + || *pdwType == REG_SZ || *pdwType == REG_EXPAND_SZ, + _Post_z_) _When_((dwFlags & 0x7F) == RRF_RT_REG_MULTI_SZ || *pdwType == REG_MULTI_SZ, _Post_ _NullNull_terminated_) + _Out_writes_bytes_to_opt_(*pcbData, *pcbData) PVOID pvData, + _Inout_opt_ LPDWORD pcbData); +} + +bool IsGraphingModeAvailable() +{ + static bool supportGraph = Windows::Foundation::Metadata::ApiInformation::IsMethodPresent("Windows.UI.Text.RichEditTextDocument", "GetMath"); + return supportGraph; +} + +Box ^ _isGraphingModeEnabledCached = nullptr; +bool IsGraphingModeEnabled() +{ + if (!IsGraphingModeAvailable()) + { + return false; + } + + if (_isGraphingModeEnabledCached != nullptr) + { + return _isGraphingModeEnabledCached->Value; + } + + DWORD allowGraphingCalculator{ 0 }; + DWORD bufferSize{ sizeof(allowGraphingCalculator) }; + // Make sure to call RegGetValueW only on Windows 10 1903+ + if (RegGetValueW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Calculator", + L"AllowGraphingCalculator", + RRF_RT_REG_DWORD | RRF_RT_REG_BINARY, + nullptr, + reinterpret_cast(&allowGraphingCalculator), + &bufferSize) + == ERROR_SUCCESS) + { + _isGraphingModeEnabledCached = allowGraphingCalculator != 0; + } + else + { + _isGraphingModeEnabledCached = true; + } + return _isGraphingModeEnabledCached->Value; +} + // The order of items in this list determines the order of items in the menu. -static constexpr array 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::Graphing, - GRAPHING_ID, - L"Graphing", - L"GraphingCalculatorMode", - L"\uF770", - CategoryGroupType::Calculator, - MyVirtualKey::Number5, - 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, - SUPPORTS_NEGATIVE }, - 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, - SUPPORTS_NEGATIVE } }; +static const list s_categoryManifest = [] { + auto res = list{ NavCategoryInitializer{ ViewMode::Standard, + STANDARD_ID, + L"Standard", + L"StandardMode", + L"\uE8EF", + CategoryGroupType::Calculator, + MyVirtualKey::Number1, + L"1", + SUPPORTS_ALL, + true }, + NavCategoryInitializer{ ViewMode::Scientific, + SCIENTIFIC_ID, + L"Scientific", + L"ScientificMode", + L"\uF196", + CategoryGroupType::Calculator, + MyVirtualKey::Number2, + L"2", + SUPPORTS_ALL, + true } }; + + int currentIndex = 3; + bool supportGraphingCalculator = IsGraphingModeAvailable(); + if (supportGraphingCalculator) + { + const bool isEnabled = IsGraphingModeEnabled(); + res.push_back(NavCategoryInitializer{ ViewMode::Graphing, + GRAPHING_ID, + L"Graphing", + L"GraphingCalculatorMode", + L"\uF770", + CategoryGroupType::Calculator, + MyVirtualKey::Number3, + L"3", + SUPPORTS_ALL, + isEnabled }); + ++currentIndex; + } + res.insert( + res.end(), + { NavCategoryInitializer{ ViewMode::Programmer, + PROGRAMMER_ID, + L"Programmer", + L"ProgrammerMode", + L"\uECCE", + CategoryGroupType::Calculator, + supportGraphingCalculator ? MyVirtualKey::Number4 : MyVirtualKey::Number3, + towchar_t(currentIndex++), + SUPPORTS_ALL, + true }, + NavCategoryInitializer{ ViewMode::Date, + DATE_ID, + L"Date", + L"DateCalculationMode", + L"\uE787", + CategoryGroupType::Calculator, + supportGraphingCalculator ? MyVirtualKey::Number5 : MyVirtualKey::Number4, + towchar_t(currentIndex++), + SUPPORTS_ALL, + true }, + NavCategoryInitializer{ ViewMode::Currency, + CURRENCY_ID, + L"Currency", + L"CategoryName_Currency", + L"\uEB0D", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Volume, + VOLUME_ID, + L"Volume", + L"CategoryName_Volume", + L"\uF1AA", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Length, + LENGTH_ID, + L"Length", + L"CategoryName_Length", + L"\uECC6", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Weight, + WEIGHT_ID, + L"Weight and Mass", + L"CategoryName_Weight", + L"\uF4C1", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Temperature, + TEMPERATURE_ID, + L"Temperature", + L"CategoryName_Temperature", + L"\uE7A3", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + SUPPORTS_NEGATIVE, + true }, + NavCategoryInitializer{ ViewMode::Energy, + ENERGY_ID, + L"Energy", + L"CategoryName_Energy", + L"\uECAD", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Area, + AREA_ID, + L"Area", + L"CategoryName_Area", + L"\uE809", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Speed, + SPEED_ID, + L"Speed", + L"CategoryName_Speed", + L"\uEADA", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Time, + TIME_ID, + L"Time", + L"CategoryName_Time", + L"\uE917", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Power, + POWER_ID, + L"Power", + L"CategoryName_Power", + L"\uE945", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + SUPPORTS_NEGATIVE, + true }, + NavCategoryInitializer{ ViewMode::Data, + DATA_ID, + L"Data", + L"CategoryName_Data", + L"\uF20F", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Pressure, + PRESSURE_ID, + L"Pressure", + L"CategoryName_Pressure", + L"\uEC4A", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + POSITIVE_ONLY, + true }, + NavCategoryInitializer{ ViewMode::Angle, + ANGLE_ID, + L"Angle", + L"CategoryName_Angle", + L"\uF515", + CategoryGroupType::Converter, + MyVirtualKey::None, + nullptr, + SUPPORTS_NEGATIVE, + true } }); + return res; +}(); // This function should only be used when storing the mode to app data. int NavCategory::Serialize(ViewMode mode) @@ -220,6 +327,14 @@ ViewMode NavCategory::Deserialize(Platform::Object ^ obj) if (iter != s_categoryManifest.end()) { + if (iter->viewMode == ViewMode::Graphing) + { + // check if the user is allowed to use this feature + if (!IsGraphingModeEnabled()) + { + return ViewMode::None; + } + } return iter->viewMode; } } @@ -402,10 +517,12 @@ NavCategoryGroup::NavCategoryGroup(const NavCategoryGroupInitializer& groupIniti categoryName, categoryAutomationName, StringReference(categoryInitializer.glyph), - resProvider->GetResourceString(nameResourceKey + "AccessKey"), + categoryInitializer.accessKey != nullptr ? ref new String(categoryInitializer.accessKey) + : resProvider->GetResourceString(nameResourceKey + "AccessKey"), groupMode, categoryInitializer.viewMode, - categoryInitializer.supportsNegative)); + categoryInitializer.supportsNegative, + categoryInitializer.isEnabled)); } } } @@ -420,29 +537,12 @@ IObservableVector ^ NavCategoryGroup::CreateMenuOptions() NavCategoryGroup ^ NavCategoryGroup::CreateCalculatorCategory() { - return ref new NavCategoryGroup(s_categoryGroupManifest.at(0)); + return ref new NavCategoryGroup( + NavCategoryGroupInitializer{ CategoryGroupType::Calculator, L"CalculatorModeTextCaps", L"CalculatorModeText", L"CalculatorModePluralText" }); } NavCategoryGroup ^ NavCategoryGroup::CreateConverterCategory() { - return ref new NavCategoryGroup(s_categoryGroupManifest.at(1)); -} - -vector NavCategoryGroup::GetInitializerCategoryGroup(CategoryGroupType groupType) -{ - vector 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; + return ref new NavCategoryGroup( + NavCategoryGroupInitializer{ CategoryGroupType::Converter, L"ConverterModeTextCaps", L"ConverterModeText", L"ConverterModePluralText" }); } diff --git a/src/CalcViewModel/Common/NavCategory.h b/src/CalcViewModel/Common/NavCategory.h index c94c553f..a2edc995 100644 --- a/src/CalcViewModel/Common/NavCategory.h +++ b/src/CalcViewModel/Common/NavCategory.h @@ -67,7 +67,9 @@ namespace CalculatorApp wchar_t const* glyph, CategoryGroupType group, MyVirtualKey vKey, - bool categorySupportsNegative) + wchar_t const* aKey, + bool categorySupportsNegative, + bool enabled) : viewMode(mode) , serializationId(id) , friendlyName(name) @@ -75,7 +77,9 @@ namespace CalculatorApp , glyph(glyph) , groupType(group) , virtualKey(vKey) + , accessKey(aKey) , supportsNegative(categorySupportsNegative) + , isEnabled(enabled) { } @@ -86,7 +90,9 @@ namespace CalculatorApp const wchar_t* const glyph; const CategoryGroupType groupType; const MyVirtualKey virtualKey; + const wchar_t* const accessKey; const bool supportsNegative; + const bool isEnabled; }; private @@ -110,45 +116,17 @@ namespace CalculatorApp { public: OBSERVABLE_OBJECT(); + PROPERTY_R(Platform::String ^, Name); + PROPERTY_R(Platform::String ^, AutomationName); + PROPERTY_R(Platform::String ^, Glyph); + PROPERTY_R(ViewMode, Mode); + PROPERTY_R(Platform::String ^, AccessKey); + PROPERTY_R(bool, SupportsNegative); + PROPERTY_R(bool, IsEnabled); property Platform::String - ^ Name { Platform::String ^ get() { return m_name; } } + ^ AutomationId { Platform::String ^ get() { return m_Mode.ToString(); } } - 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); @@ -180,16 +158,17 @@ namespace CalculatorApp 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) + bool supportsNegative, + bool isEnabled) + : m_Name(name) + , m_AutomationName(automationName) + , m_Glyph(glyph) + , m_AccessKey(accessKey) + , m_modeString(mode) + , m_Mode(viewMode) + , m_SupportsNegative(supportsNegative) + , m_IsEnabled(isEnabled) { - m_position = NavCategory::GetPosition(m_viewMode); } static std::vector GetCategoryAcceleratorKeys(); @@ -197,14 +176,7 @@ namespace CalculatorApp 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; + Platform::String ^ m_modeString; }; [Windows::UI::Xaml::Data::Bindable] public ref class NavCategoryGroup sealed : public Windows::UI::Xaml::Data::INotifyPropertyChanged @@ -218,15 +190,11 @@ namespace CalculatorApp static Windows::Foundation::Collections::IObservableVector ^ CreateMenuOptions(); - static Platform::String ^ GetHeaderResourceKey(CategoryGroupType type); - internal : static NavCategoryGroup ^ CreateCalculatorCategory(); static NavCategoryGroup ^ CreateConverterCategory(); private: NavCategoryGroup(const NavCategoryGroupInitializer& groupInitializer); - - static std::vector GetInitializerCategoryGroup(CategoryGroupType groupType); }; } } diff --git a/src/CalcViewModel/pch.h b/src/CalcViewModel/pch.h index 77e1094b..40a440aa 100644 --- a/src/CalcViewModel/pch.h +++ b/src/CalcViewModel/pch.h @@ -40,6 +40,7 @@ #include "winrt/Windows.Globalization.DateTimeFormatting.h" #include "winrt/Windows.System.UserProfile.h" #include "winrt/Windows.UI.Xaml.h" +#include "winrt/Windows.Foundation.Metadata.h" // The following namespaces exist as a convenience to resolve // ambiguity for Windows types in the Windows::UI::Xaml::Automation::Peers diff --git a/src/Calculator/Resources/en-US/Resources.resw b/src/Calculator/Resources/en-US/Resources.resw index f1b6744f..58e17573 100644 --- a/src/Calculator/Resources/en-US/Resources.resw +++ b/src/Calculator/Resources/en-US/Resources.resw @@ -3163,10 +3163,6 @@ AB AccessKey for the About button. {StringCategory="Accelerator"} - - 4 - AccessKey for the Date Calculation mode navbar item. {StringCategory="Accelerator"} - I Access key for the History button. {StringCategory="Accelerator"} @@ -3179,18 +3175,6 @@ H Access key for the Hamburger button. {StringCategory="Accelerator"} - - 3 - AccessKey for the Programmer mode navbar item. {StringCategory="Accelerator"} - - - 2 - AccessKey for the Scientific mode navbar item. {StringCategory="Accelerator"} - - - 1 - AccessKey for the Standard mode navbar item. {StringCategory="Accelerator"} - AN AccessKey for the angle converter navbar item. {StringCategory="Accelerator"} @@ -4218,4 +4202,4 @@ Current mode is graph mode Announcement used in Graphing Calculator when switching to the graph mode - \ No newline at end of file + diff --git a/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml b/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml index e85cd875..0c43f609 100644 --- a/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml +++ b/src/Calculator/Views/GraphingCalculator/KeyGraphFeaturesPanel.xaml @@ -38,28 +38,29 @@