From 8d2791ff21cc9536d9f1569c56cc590874ce17fa Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 20 Sep 2024 10:57:13 -0700 Subject: [PATCH 1/8] Add Profile.BellSound to Settings UI --- .../TerminalSettingsEditor/MainPage.cpp | 2 +- .../ProfileViewModel.cpp | 156 ++++++++++++++++++ .../TerminalSettingsEditor/ProfileViewModel.h | 20 +++ .../ProfileViewModel.idl | 11 ++ .../Profiles_Advanced.cpp | 52 +++++- .../Profiles_Advanced.h | 5 + .../Profiles_Advanced.xaml | 60 +++++++ .../Resources/en-US/Resources.resw | 32 ++++ 8 files changed, 336 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 76bbeacb248..b6ac9ea4c73 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -341,7 +341,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (currentPage == ProfileSubPage::Advanced) { - contentFrame().Navigate(xaml_typename(), profile); + contentFrame().Navigate(xaml_typename(), winrt::make(profile, *this)); const auto crumb = winrt::make(breadcrumbTag, RS_(L"Profile_Advanced/Header"), BreadcrumbSubPage::Profile_Advanced); _breadcrumbs.Append(crumb); SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0); diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index 66d8371c3ea..e30d17ff165 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -40,6 +40,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(CloseOnExitMode, CloseOnExitMode, winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode, L"Profile_CloseOnExit", L"Content"); INITIALIZE_BINDABLE_ENUM_SETTING(ScrollState, ScrollbarState, winrt::Microsoft::Terminal::Control::ScrollbarState, L"Profile_ScrollbarVisibility", L"Content"); + _InitializeCurrentBellSounds(); + // Add a property changed handler to our own property changed event. // This propagates changes from the settings model to anybody listening to our // unique view model members. @@ -76,6 +78,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _NotifyChanges(L"HideIcon"); } + else if (viewModelProperty == L"CurrentBellSounds") + { + // we already have infrastructure in place to + // propagate changes from the CurrentBellSounds + // to the model. Refer to... + // - _InitializeCurrentBellSounds() --> _CurrentBellSounds.VectorChanged() + // - RequestAddBellSound() + // - RequestDeleteBellSound() + + _NotifyChanges(L"BellSoundPreview", L"HasBellSound"); + } + else if (viewModelProperty == L"BellSound") + { + _InitializeCurrentBellSounds(); + } }); // Do the same for the starting directory @@ -374,6 +391,145 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation BellStyle(currentStyle); } + // Method Description: + // - Construct _CurrentBellSounds by importing the _inherited_ value from the model + // - Adds a PropertyChanged handler to each BellSoundViewModel to propagate changes to the model + void ProfileViewModel::_InitializeCurrentBellSounds() + { + _CurrentBellSounds = winrt::single_threaded_observable_vector(); + if (const auto soundList = _profile.BellSound()) + { + for (const auto&& bellSound : soundList) + { + auto vm = winrt::make(bellSound); + vm.PropertyChanged({ this, &ProfileViewModel::_BellSoundVMPropertyChanged }); + _CurrentBellSounds.Append(vm); + } + } + _CurrentBellSounds.VectorChanged([this](auto&&, const IVectorChangedEventArgs& args) { + switch (args.CollectionChange()) + { + case CollectionChange::ItemInserted: + { + const auto index = args.Index(); + const auto& newSound = _CurrentBellSounds.GetAt(index); + + if (!_profile.BellSound()) + { + _profile.BellSound(winrt::single_threaded_vector()); + } + _profile.BellSound().InsertAt(index, newSound.Path()); + break; + } + case CollectionChange::ItemRemoved: + { + _profile.BellSound().RemoveAt(args.Index()); + break; + } + case CollectionChange::ItemChanged: + { + // I've never been able to get this one to hit, + // but if it ever does, propagate change to model + const auto index = args.Index(); + const auto& newSound = _CurrentBellSounds.GetAt(index); + _profile.BellSound().SetAt(index, newSound.Path()); + break; + } + case CollectionChange::Reset: + default: + { + // propagate changes to model + auto list = winrt::single_threaded_vector(); + for (const auto& sound : _CurrentBellSounds) + { + list.Append(sound.Path()); + } + _profile.BellSound(list); + break; + } + } + }); + _NotifyChanges(L"CurrentBellSounds"); + } + + // Method Description: + // - If the current layer is inheriting the bell sound from its parent, + // we need to copy the _inherited_ bell sound list to the current layer + // so that we can then apply modifications to it + void ProfileViewModel::_PrepareModelForBellSoundModification() + { + if (const auto inheritedSounds = _profile.BellSound(); !_profile.HasBellSound() && inheritedSounds) + { + auto newSounds{ winrt::single_threaded_vector() }; + for (const auto sound : inheritedSounds) + { + newSounds.Append(sound); + } + _profile.BellSound(newSounds); + } + } + + hstring ProfileViewModel::BellSoundPreview() + { + const auto& currentSound = BellSound(); + if (!currentSound || currentSound.Size() == 0) + { + return RS_(L"Profile_BellSoundPreviewDefault"); + } + else if (currentSound.Size() == 1) + { + std::filesystem::path filePath{ std::wstring_view{ currentSound.GetAt(0) } }; + return hstring{ filePath.filename().wstring() }; + } + return RS_(L"Profile_BellSoundPreviewMultiple"); + } + + void ProfileViewModel::_BellSoundVMPropertyChanged(const IInspectable& sender, const PropertyChangedEventArgs& args) + { + if (args.PropertyName() == L"Path") + { + auto senderVM = sender.as(); + + // propagate changes to model + uint32_t index; + if (_CurrentBellSounds.IndexOf(senderVM, index)) + { + // if current layer is inheriting, + // we should copy the bell sound then apply changes + _PrepareModelForBellSoundModification(); + + _profile.BellSound().SetAt(index, senderVM.Path()); + _NotifyChanges(L"CurrentBellSounds"); + } + } + } + + void ProfileViewModel::RequestAddBellSound() + { + // If we were inheriting our bell sound, + // copy it over to the current layer and apply modifications + _PrepareModelForBellSoundModification(); + + auto vm = winrt::make(); + vm.PropertyChanged({ this, &ProfileViewModel::_BellSoundVMPropertyChanged }); + _CurrentBellSounds.Append(vm); + _NotifyChanges(L"CurrentBellSounds"); + } + + void ProfileViewModel::RequestDeleteBellSound(const Editor::BellSoundViewModel& vm) + { + uint32_t index; + if (_CurrentBellSounds.IndexOf(vm, index)) + { + // If we were inheriting our bell sound, + // copy it over to the current layer and apply modifications + _PrepareModelForBellSoundModification(); + + _CurrentBellSounds.RemoveAt(index); + _NotifyChanges(L"CurrentBellSounds"); + } + } + void ProfileViewModel::DeleteProfile() { auto deleteProfileArgs{ winrt::make_self(Guid()) }; diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index f0ce84f1f05..6fa620fa180 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -5,6 +5,7 @@ #include "DeleteProfileEventArgs.g.h" #include "NavigateToProfileArgs.g.h" +#include "BellSoundViewModel.g.h" #include "ProfileViewModel.g.h" #include "Utils.h" #include "ViewModelHelpers.h" @@ -26,6 +27,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Editor::ProfileViewModel _Profile{ nullptr }; }; + struct BellSoundViewModel : BellSoundViewModelT, ViewModelHelper + { + public: + BellSoundViewModel() = default; + BellSoundViewModel(hstring path) : + _Path{ path } {} + + VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, Path); + }; + struct ProfileViewModel : ProfileViewModelT, ViewModelHelper { public: @@ -46,6 +57,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void SetBellStyleWindow(winrt::Windows::Foundation::IReference on); void SetBellStyleTaskbar(winrt::Windows::Foundation::IReference on); + hstring BellSoundPreview(); + void RequestAddBellSound(); + void RequestDeleteBellSound(const Editor::BellSoundViewModel& vm); + void SetAcrylicOpacityPercentageValue(double value) { Opacity(static_cast(value) / 100.0f); @@ -88,6 +103,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation til::typed_event DeleteProfileRequested; VIEW_MODEL_OBSERVABLE_PROPERTY(ProfileSubPage, CurrentPage); + VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, CurrentBellSounds); PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid); PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, ConnectionType); @@ -114,6 +130,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_profile, SnapOnInput); OBSERVABLE_PROJECTED_SETTING(_profile, AltGrAliasing); OBSERVABLE_PROJECTED_SETTING(_profile, BellStyle); + OBSERVABLE_PROJECTED_SETTING(_profile, BellSound); OBSERVABLE_PROJECTED_SETTING(_profile, Elevate); OBSERVABLE_PROJECTED_SETTING(_profile, ReloadEnvironmentVariables); OBSERVABLE_PROJECTED_SETTING(_profile, RightClickContextMenu); @@ -135,6 +152,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation winrt::hstring _lastIcon; Editor::AppearanceViewModel _defaultAppearanceViewModel; + void _InitializeCurrentBellSounds(); + void _PrepareModelForBellSoundModification(); + void _BellSoundVMPropertyChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); static Windows::Foundation::Collections::IObservableVector _MonospaceFontList; static Windows::Foundation::Collections::IObservableVector _FontList; diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl index c0bc407c7ae..15d85cbf629 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -25,6 +25,11 @@ namespace Microsoft.Terminal.Settings.Editor Guid ProfileGuid { get; }; } + runtimeclass BellSoundViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + String Path; + } + enum ProfileSubPage { Base = 0, @@ -48,6 +53,11 @@ namespace Microsoft.Terminal.Settings.Editor void SetBellStyleWindow(Windows.Foundation.IReference on); void SetBellStyleTaskbar(Windows.Foundation.IReference on); + String BellSoundPreview { get; }; + Windows.Foundation.Collections.IObservableVector CurrentBellSounds { get; }; + void RequestAddBellSound(); + void RequestDeleteBellSound(BellSoundViewModel vm); + IInspectable CurrentAntiAliasingMode; Windows.Foundation.Collections.IObservableVector AntiAliasingModeList { get; }; @@ -105,6 +115,7 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SnapOnInput); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AltGrAliasing); OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.BellStyle, BellStyle); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.Collections.IVector, BellSound); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, Elevate); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ReloadEnvironmentVariables); OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RightClickContextMenu); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.cpp b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.cpp index 00bdff6d0b6..d5b5e439e60 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.cpp +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.cpp @@ -10,6 +10,9 @@ #include #include "..\WinRTUtils\inc\Utils.h" +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; using namespace winrt::Windows::UI::Xaml::Navigation; namespace winrt::Microsoft::Terminal::Settings::Editor::implementation @@ -21,11 +24,58 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void Profiles_Advanced::OnNavigatedTo(const NavigationEventArgs& e) { - _Profile = e.Parameter().as(); + const auto args = e.Parameter().as(); + _Profile = args.Profile(); + _windowRoot = args.WindowRoot(); + + //if (const auto& bellSounds = _Profile.CurrentBellSounds()) + //{ + // auto uiStack = BellSoundStack().Children(); + // const auto dataTemplate = Resources().Lookup(box_value(L"BellSoundEntryViewModelTemplate")).as(); + // for (const auto&& entry : bellSounds) + // { + // ContentControl ctrl; + // ctrl.Content(entry); + // ctrl.ContentTemplate(dataTemplate); + // + // uiStack.Append(ctrl); + // } + //} } void Profiles_Advanced::OnNavigatedFrom(const NavigationEventArgs& /*e*/) { _ViewModelChangedRevoker.revoke(); } + + safe_void_coroutine Profiles_Advanced::BellSoundBrowse_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/) + { + static constexpr COMDLG_FILTERSPEC supportedFileTypes[] = { + { L"Sound Files (*.wav;*.mp3;*.flac)", L"*.wav;*.mp3;*.flac" }, + { L"All Files (*.*)", L"*.*" } + }; + + const auto parentHwnd{ reinterpret_cast(WindowRoot().GetHostingWindow()) }; + auto file = co_await OpenFilePicker(parentHwnd, [](auto&& dialog) { + try + { + auto folderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_Music, KF_FLAG_DEFAULT, nullptr) }; + dialog->SetDefaultFolder(folderShellItem.get()); + } + CATCH_LOG(); // non-fatal + THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedFileTypes), supportedFileTypes)); + THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed + THROW_IF_FAILED(dialog->SetDefaultExtension(L"wav;mp3;flac")); + }); + if (!file.empty()) + { + sender.as + + + + + + + + Non-monospace fonts: This is a label that is followed by a list of proportional fonts. + + Default system sound + The value shown for the default value for bell sound. + + + Multiple sounds + The value shown for when the "bell sound" setting is set to multiple values. + + + Bell sound + Header for a control to determine what sound the app uses to notify the user. "Bell" is the common term in terminals for the BEL character (like the metal device used to chime). + + + Controls what sound is played when the application emits a BEL character. + A description for what the "bell sound" setting does. Presented near "Profile_BellSound".{Locked="BEL"} + + + Path to sound file + Placeholder text for a text box that is used to set the path to the sound file that will be used. Presented near the "Profile_BellSound" setting. + + + Add sound + Text label for a button that adds a sound to the bell sound list. Presented near "Profile_BellSound" + + + Browse... + Text label for a button that opens a file picker to select a sound file to use for the bell sound. Presented near "Profile_BellSound". + + + When multiple sounds are defined, one is selected at random when the BEL character is received. + {Locked="BEL"} Text block displayed near "Profile_BellSound". + \ No newline at end of file From bd038c3491300ca8caae9ddd458c1269dfd2b2b3 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 1 Oct 2024 11:50:39 -0700 Subject: [PATCH 2/8] Add preview button --- .../Profiles_Advanced.cpp | 52 ++++++++++++++----- .../Profiles_Advanced.h | 3 ++ .../Profiles_Advanced.xaml | 15 +++++- .../Resources/en-US/Resources.resw | 8 +++ src/cascadia/TerminalSettingsEditor/pch.h | 5 ++ 5 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.cpp b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.cpp index d5b5e439e60..c007c2bc95f 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.cpp +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Advanced.cpp @@ -27,20 +27,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation const auto args = e.Parameter().as(); _Profile = args.Profile(); _windowRoot = args.WindowRoot(); - - //if (const auto& bellSounds = _Profile.CurrentBellSounds()) - //{ - // auto uiStack = BellSoundStack().Children(); - // const auto dataTemplate = Resources().Lookup(box_value(L"BellSoundEntryViewModelTemplate")).as(); - // for (const auto&& entry : bellSounds) - // { - // ContentControl ctrl; - // ctrl.Content(entry); - // ctrl.ContentTemplate(dataTemplate); - // - // uiStack.Append(ctrl); - // } - //} } void Profiles_Advanced::OnNavigatedFrom(const NavigationEventArgs& /*e*/) @@ -48,6 +34,44 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _ViewModelChangedRevoker.revoke(); } + safe_void_coroutine Profiles_Advanced::BellSoundAudioPreview_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/) + try + { + const auto path = sender.as +