Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Profile.BellSound to Settings UI #17983

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/cascadia/TerminalSettingsEditor/CommonResources.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@
<Setter Property="TextWrapping" Value="Wrap" />
</Style>

<!-- Text Block -->
<Style x:Key="TextBlockSettingStyle"
BasedOn="{StaticResource BaseTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="MinWidth" Value="{StaticResource StandardBoxMinWidth}" />
<Setter Property="MaxWidth" Value="400" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>

<Style x:Key="TextBlockSubHeaderStyle"
BasedOn="{StaticResource SubtitleTextBlockStyle}"
TargetType="TextBlock">
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalSettingsEditor/MainPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
else if (currentPage == ProfileSubPage::Advanced)
{
contentFrame().Navigate(xaml_typename<Editor::Profiles_Advanced>(), profile);
contentFrame().Navigate(xaml_typename<Editor::Profiles_Advanced>(), winrt::make<implementation::NavigateToProfileArgs>(profile, *this));
const auto crumb = winrt::make<Breadcrumb>(breadcrumbTag, RS_(L"Profile_Advanced/Header"), BreadcrumbSubPage::Profile_Advanced);
_breadcrumbs.Append(crumb);
SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0);
Expand Down
158 changes: 158 additions & 0 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -374,6 +391,147 @@ 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<Editor::BellSoundViewModel>();
if (const auto soundList = _profile.BellSound())
{
for (const auto&& bellSound : soundList)
{
auto vm = winrt::make<BellSoundViewModel>(bellSound);
vm.PropertyChanged({ this, &ProfileViewModel::_BellSoundVMPropertyChanged });
_CurrentBellSounds.Append(vm);
}
}
_CurrentBellSounds.VectorChanged([this](auto&&, const IVectorChangedEventArgs& args) {
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
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<winrt::hstring>());
}
_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<winrt::hstring>();
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<winrt::hstring>() };
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<Editor::BellSoundViewModel>();

// 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");
}
}
}

Editor::BellSoundViewModel ProfileViewModel::RequestAddBellSound()
{
// If we were inheriting our bell sound,
// copy it over to the current layer and apply modifications
_PrepareModelForBellSoundModification();

auto vm = winrt::make<BellSoundViewModel>();
vm.PropertyChanged({ this, &ProfileViewModel::_BellSoundVMPropertyChanged });
_CurrentBellSounds.Append(vm);
_NotifyChanges(L"CurrentBellSounds");

return vm;
}

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<DeleteProfileEventArgs>(Guid()) };
Expand Down
20 changes: 20 additions & 0 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -26,6 +27,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Editor::ProfileViewModel _Profile{ nullptr };
};

struct BellSoundViewModel : BellSoundViewModelT<BellSoundViewModel>, ViewModelHelper<BellSoundViewModel>
{
public:
BellSoundViewModel() = default;
BellSoundViewModel(hstring path) :
_Path{ path } {}

VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, Path);
};

struct ProfileViewModel : ProfileViewModelT<ProfileViewModel>, ViewModelHelper<ProfileViewModel>
{
public:
Expand All @@ -46,6 +57,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void SetBellStyleWindow(winrt::Windows::Foundation::IReference<bool> on);
void SetBellStyleTaskbar(winrt::Windows::Foundation::IReference<bool> on);

hstring BellSoundPreview();
Editor::BellSoundViewModel RequestAddBellSound();
void RequestDeleteBellSound(const Editor::BellSoundViewModel& vm);

void SetAcrylicOpacityPercentageValue(double value)
{
Opacity(static_cast<float>(value) / 100.0f);
Expand Down Expand Up @@ -88,6 +103,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
til::typed_event<Editor::ProfileViewModel, Editor::DeleteProfileEventArgs> DeleteProfileRequested;

VIEW_MODEL_OBSERVABLE_PROPERTY(ProfileSubPage, CurrentPage);
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::BellSoundViewModel>, CurrentBellSounds);

PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, ConnectionType);
Expand All @@ -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);
Expand All @@ -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<Editor::Font> _MonospaceFontList;
static Windows::Foundation::Collections::IObservableVector<Editor::Font> _FontList;

Expand Down
11 changes: 11 additions & 0 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -48,6 +53,11 @@ namespace Microsoft.Terminal.Settings.Editor
void SetBellStyleWindow(Windows.Foundation.IReference<Boolean> on);
void SetBellStyleTaskbar(Windows.Foundation.IReference<Boolean> on);

String BellSoundPreview { get; };
Windows.Foundation.Collections.IObservableVector<BellSoundViewModel> CurrentBellSounds { get; };
BellSoundViewModel RequestAddBellSound();
void RequestDeleteBellSound(BellSoundViewModel vm);

IInspectable CurrentAntiAliasingMode;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> AntiAliasingModeList { get; };

Expand Down Expand Up @@ -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<String>, BellSound);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, Elevate);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ReloadEnvironmentVariables);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RightClickContextMenu);
Expand Down
82 changes: 81 additions & 1 deletion src/cascadia/TerminalSettingsEditor/Profiles_Advanced.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
#include <LibraryResources.h>
#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
Expand All @@ -21,11 +24,88 @@

void Profiles_Advanced::OnNavigatedTo(const NavigationEventArgs& e)
{
_Profile = e.Parameter().as<Editor::ProfileViewModel>();
const auto args = e.Parameter().as<Editor::NavigateToProfileArgs>();
_Profile = args.Profile();
_windowRoot = args.WindowRoot();
}

void Profiles_Advanced::OnNavigatedFrom(const NavigationEventArgs& /*e*/)
{
_ViewModelChangedRevoker.revoke();
}

safe_void_coroutine Profiles_Advanced::BellSoundAudioPreview_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/)
try
{
const auto path = sender.as<Button>().Tag().as<Editor::BellSoundViewModel>().Path();
if (path.empty())
{
co_return;
}
winrt::hstring soundPath{ wil::ExpandEnvironmentStringsW<std::wstring>(path.c_str()) };
winrt::Windows::Foundation::Uri uri{ soundPath };

auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher());
if (auto strongThis{ weakThis.get() })
{
if (!strongThis->_bellPlayerCreated)
{
// The MediaPlayer might not exist on Windows N SKU.
try
{
strongThis->_bellPlayerCreated = true;
strongThis->_bellPlayer = winrt::Windows::Media::Playback::MediaPlayer();
// GH#12258: The media keys (like play/pause) should have no effect on our bell sound.
strongThis->_bellPlayer.CommandManager().IsEnabled(false);
}
CATCH_LOG();
}
if (strongThis->_bellPlayer)
{
const auto source{ winrt::Windows::Media::Core::MediaSource::CreateFromUri(uri) };
const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) };
strongThis->_bellPlayer.Source(item);
strongThis->_bellPlayer.Play();
}
}
}
CATCH_LOG();

void Profiles_Advanced::BellSoundDelete_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/)
{
auto bellSoundEntry = sender.as<Button>().Tag().as<Editor::BellSoundViewModel>();
_Profile.RequestDeleteBellSound(bellSoundEntry);
}

void Profiles_Advanced::BellSoundAdd_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
{
auto bellSoundVM = _Profile.RequestAddBellSound();
_PickFileForBellSound(bellSoundVM);
}

safe_void_coroutine Profiles_Advanced::_PickFileForBellSound(Editor::BellSoundViewModel& vm)
{
static constexpr COMDLG_FILTERSPEC supportedFileTypes[] = {
{ L"Sound Files (*.wav;*.mp3;*.flac)", L"*.wav;*.mp3;*.flac" },
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
{ L"All Files (*.*)", L"*.*" }
};

const auto parentHwnd{ reinterpret_cast<HWND>(WindowRoot().GetHostingWindow()) };
auto file = co_await OpenFilePicker(parentHwnd, [](auto&& dialog) {
try
{
auto folderShellItem{ winrt::capture<IShellItem>(&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"));
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
});
if (!file.empty())
{
vm.Path(file);
}
}
}
Loading
Loading