mirror of
https://github.com/microsoft/terminal.git
synced 2026-07-08 17:56:28 +00:00
849 lines
32 KiB
C++
849 lines
32 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "ProfileViewModel.h"
|
|
#include "ProfileViewModel.g.cpp"
|
|
#include "Appearances.h"
|
|
#include "EnumEntry.h"
|
|
#include "IconPicker.h"
|
|
|
|
#include "../WinRTUtils/inc/Utils.h"
|
|
#include "../../renderer/base/FontCache.h"
|
|
#include "../TerminalSettingsAppAdapterLib/TerminalSettings.h"
|
|
|
|
using namespace winrt::Windows::UI::Text;
|
|
using namespace winrt::Windows::UI::Xaml;
|
|
using namespace winrt::Windows::UI::Xaml::Controls;
|
|
using namespace winrt::Windows::UI::Xaml::Data;
|
|
using namespace winrt::Windows::UI::Xaml::Navigation;
|
|
using namespace winrt::Windows::Foundation;
|
|
using namespace winrt::Windows::Foundation::Collections;
|
|
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
|
|
|
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
|
{
|
|
static Editor::Font fontObjectForDWriteFont(IDWriteFontFamily* family, const wchar_t* locale);
|
|
|
|
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_MonospaceFontList{ nullptr };
|
|
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_FontList{ nullptr };
|
|
|
|
ProfileViewModel::ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& appSettings, const Windows::UI::Core::CoreDispatcher& dispatcher) :
|
|
_profile{ profile },
|
|
_defaultAppearanceViewModel{ nullptr },
|
|
_originalProfileGuid{ profile.Guid() },
|
|
_appSettings{ appSettings },
|
|
_unfocusedAppearanceViewModel{ nullptr },
|
|
_dispatcher{ dispatcher }
|
|
{
|
|
INITIALIZE_BINDABLE_ENUM_SETTING(AntiAliasingMode, TextAntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode, L"Profile_AntialiasingMode", L"Content");
|
|
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");
|
|
INITIALIZE_BINDABLE_ENUM_SETTING(PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle, L"Profile_PathTranslationStyle", L"Content");
|
|
|
|
_RefreshDefaultAppearanceViewModel();
|
|
_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.
|
|
PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
|
|
const auto viewModelProperty{ args.PropertyName() };
|
|
if (viewModelProperty == L"IsBaseLayer")
|
|
{
|
|
// we _always_ want to show the background image settings in base layer
|
|
_NotifyChanges(L"BackgroundImageSettingsEnabled");
|
|
}
|
|
else if (viewModelProperty == L"StartingDirectory")
|
|
{
|
|
// notify listener that all starting directory related values might have changed
|
|
// NOTE: this is similar to what is done with BackgroundImagePath above
|
|
_NotifyChanges(L"UseParentProcessDirectory", L"CurrentStartingDirectoryPreview");
|
|
}
|
|
else if (viewModelProperty == L"AntialiasingMode")
|
|
{
|
|
_NotifyChanges(L"CurrentAntiAliasingMode");
|
|
}
|
|
else if (viewModelProperty == L"CloseOnExit")
|
|
{
|
|
_NotifyChanges(L"CurrentCloseOnExitMode");
|
|
}
|
|
else if (viewModelProperty == L"BellStyle")
|
|
{
|
|
_NotifyChanges(L"IsBellStyleFlagSet", L"BellStylePreview");
|
|
}
|
|
else if (viewModelProperty == L"ScrollState")
|
|
{
|
|
_NotifyChanges(L"CurrentScrollState");
|
|
}
|
|
else if (viewModelProperty == L"Icon")
|
|
{
|
|
// The icon changed; let's re-evaluate it with its new context.
|
|
_appSettings.ResolveMediaResources();
|
|
|
|
// Propagate the rendered icon into a preview (i.e. nav view, container item)
|
|
_NotifyChanges(L"LocalizedIcon",
|
|
L"IconPreview",
|
|
L"IconPath",
|
|
L"EvaluatedIcon",
|
|
L"UsingNoIcon");
|
|
}
|
|
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()
|
|
_MarkDuplicateBellSoundDirectories();
|
|
_NotifyChanges(L"BellSoundPreview", L"HasBellSound");
|
|
}
|
|
else if (viewModelProperty == L"BellSound")
|
|
{
|
|
_InitializeCurrentBellSounds();
|
|
}
|
|
else if (viewModelProperty == L"PathTranslationStyle")
|
|
{
|
|
_NotifyChanges(L"CurrentPathTranslationStyle");
|
|
}
|
|
else if (viewModelProperty == L"Padding")
|
|
{
|
|
_parsedPadding = StringToXamlThickness(_profile.Padding());
|
|
_NotifyChanges(L"LeftPadding", L"TopPadding", L"RightPadding", L"BottomPadding");
|
|
}
|
|
else if (viewModelProperty == L"TabTitle")
|
|
{
|
|
_NotifyChanges(L"TabTitlePreview");
|
|
}
|
|
else if (viewModelProperty == L"AnswerbackMessage")
|
|
{
|
|
_NotifyChanges(L"AnswerbackMessagePreview");
|
|
}
|
|
else if (viewModelProperty == L"TabColor")
|
|
{
|
|
_NotifyChanges(L"TabColorPreview");
|
|
}
|
|
else if (viewModelProperty == L"TabThemeColorPreview")
|
|
{
|
|
_NotifyChanges(L"TabColorPreview");
|
|
}
|
|
});
|
|
|
|
// Do the same for the starting directory
|
|
if (!StartingDirectory().empty())
|
|
{
|
|
_lastStartingDirectoryPath = StartingDirectory();
|
|
}
|
|
|
|
// generate the font list, if we don't have one
|
|
if (!_FontList || !_MonospaceFontList)
|
|
{
|
|
UpdateFontList();
|
|
}
|
|
|
|
if (profile.HasUnfocusedAppearance())
|
|
{
|
|
_unfocusedAppearanceViewModel = winrt::make<implementation::AppearanceViewModel>(profile.UnfocusedAppearance().try_as<AppearanceConfig>());
|
|
}
|
|
|
|
_parsedPadding = StringToXamlThickness(_profile.Padding());
|
|
}
|
|
|
|
void ProfileViewModel::LeftPadding(double value) noexcept
|
|
{
|
|
if (std::abs(_parsedPadding.Left - value) >= .0001)
|
|
{
|
|
_parsedPadding.Left = value;
|
|
Padding(XamlThicknessToOptimalString(_parsedPadding));
|
|
}
|
|
}
|
|
|
|
double ProfileViewModel::LeftPadding() const noexcept
|
|
{
|
|
return _parsedPadding.Left;
|
|
}
|
|
|
|
void ProfileViewModel::TopPadding(double value) noexcept
|
|
{
|
|
if (std::abs(_parsedPadding.Top - value) >= .0001)
|
|
{
|
|
_parsedPadding.Top = value;
|
|
Padding(XamlThicknessToOptimalString(_parsedPadding));
|
|
}
|
|
}
|
|
|
|
double ProfileViewModel::TopPadding() const noexcept
|
|
{
|
|
return _parsedPadding.Top;
|
|
}
|
|
|
|
void ProfileViewModel::RightPadding(double value) noexcept
|
|
{
|
|
if (std::abs(_parsedPadding.Right - value) >= .0001)
|
|
{
|
|
_parsedPadding.Right = value;
|
|
Padding(XamlThicknessToOptimalString(_parsedPadding));
|
|
}
|
|
}
|
|
|
|
double ProfileViewModel::RightPadding() const noexcept
|
|
{
|
|
return _parsedPadding.Right;
|
|
}
|
|
|
|
void ProfileViewModel::BottomPadding(double value) noexcept
|
|
{
|
|
if (std::abs(_parsedPadding.Bottom - value) >= .0001)
|
|
{
|
|
_parsedPadding.Bottom = value;
|
|
Padding(XamlThicknessToOptimalString(_parsedPadding));
|
|
}
|
|
}
|
|
|
|
double ProfileViewModel::BottomPadding() const noexcept
|
|
{
|
|
return _parsedPadding.Bottom;
|
|
}
|
|
Control::IControlSettings ProfileViewModel::TermSettings() const
|
|
{
|
|
// This may look pricey, but it only resolves resources that have not been visited
|
|
// and the preview update is debounced.
|
|
_appSettings.ResolveMediaResources();
|
|
return *Settings::TerminalSettings::CreateForPreview(_appSettings, _profile);
|
|
}
|
|
|
|
// Method Description:
|
|
// - Updates the lists of fonts and sorts them alphabetically
|
|
void ProfileViewModel::UpdateFontList() noexcept
|
|
try
|
|
{
|
|
// initialize font list
|
|
std::vector<Editor::Font> fontList;
|
|
std::vector<Editor::Font> monospaceFontList;
|
|
|
|
// get the font collection; subscribe to updates
|
|
wil::com_ptr<IDWriteFactory> factory;
|
|
THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof())));
|
|
|
|
wil::com_ptr<IDWriteFontCollection> fontCollection;
|
|
THROW_IF_FAILED(factory->GetSystemFontCollection(fontCollection.addressof(), TRUE));
|
|
|
|
wchar_t localeName[LOCALE_NAME_MAX_LENGTH];
|
|
if (!GetUserDefaultLocaleName(&localeName[0], LOCALE_NAME_MAX_LENGTH))
|
|
{
|
|
memcpy(&localeName[0], L"en-US", 12);
|
|
}
|
|
|
|
for (UINT32 i = 0; i < fontCollection->GetFontFamilyCount(); ++i)
|
|
{
|
|
try
|
|
{
|
|
// get the font family
|
|
com_ptr<IDWriteFontFamily> fontFamily;
|
|
THROW_IF_FAILED(fontCollection->GetFontFamily(i, fontFamily.put()));
|
|
|
|
// construct a font entry for tracking
|
|
if (const auto fontEntry{ fontObjectForDWriteFont(fontFamily.get(), &localeName[0]) })
|
|
{
|
|
// check if the font is monospaced
|
|
try
|
|
{
|
|
com_ptr<IDWriteFont> font;
|
|
THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(DWRITE_FONT_WEIGHT::DWRITE_FONT_WEIGHT_NORMAL,
|
|
DWRITE_FONT_STRETCH::DWRITE_FONT_STRETCH_NORMAL,
|
|
DWRITE_FONT_STYLE::DWRITE_FONT_STYLE_NORMAL,
|
|
font.put()));
|
|
|
|
// add the font name to our list of monospace fonts
|
|
const auto castedFont{ font.try_as<IDWriteFont1>() };
|
|
if (castedFont && castedFont->IsMonospacedFont())
|
|
{
|
|
monospaceFontList.emplace_back(fontEntry);
|
|
}
|
|
}
|
|
CATCH_LOG();
|
|
|
|
// add the font name to our list of all fonts
|
|
fontList.emplace_back(std::move(fontEntry));
|
|
}
|
|
}
|
|
CATCH_LOG();
|
|
}
|
|
|
|
const auto comparator = [&](const Editor::Font& lhs, const Editor::Font& rhs) {
|
|
const auto a = lhs.LocalizedName();
|
|
const auto b = rhs.LocalizedName();
|
|
return til::compare_linguistic_insensitive(a, b) < 0;
|
|
};
|
|
|
|
// sort and save the lists
|
|
std::sort(begin(fontList), end(fontList), comparator);
|
|
_FontList = single_threaded_observable_vector<Editor::Font>(std::move(fontList));
|
|
|
|
std::sort(begin(monospaceFontList), end(monospaceFontList), comparator);
|
|
_MonospaceFontList = single_threaded_observable_vector<Editor::Font>(std::move(monospaceFontList));
|
|
}
|
|
CATCH_LOG();
|
|
|
|
static winrt::hstring getLocalizedStringByIndex(IDWriteLocalizedStrings* strings, UINT32 index)
|
|
{
|
|
UINT32 length = 0;
|
|
THROW_IF_FAILED(strings->GetStringLength(index, &length));
|
|
|
|
winrt::impl::hstring_builder builder{ length };
|
|
THROW_IF_FAILED(strings->GetString(index, builder.data(), length + 1));
|
|
|
|
return builder.to_hstring();
|
|
}
|
|
|
|
static UINT32 getLocalizedStringIndex(IDWriteLocalizedStrings* strings, const wchar_t* locale, UINT32 fallback)
|
|
{
|
|
UINT32 index;
|
|
BOOL exists;
|
|
if (FAILED(strings->FindLocaleName(locale, &index, &exists)) || !exists)
|
|
{
|
|
index = fallback;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
static Editor::Font fontObjectForDWriteFont(IDWriteFontFamily* family, const wchar_t* locale)
|
|
{
|
|
wil::com_ptr<IDWriteLocalizedStrings> familyNames;
|
|
THROW_IF_FAILED(family->GetFamilyNames(familyNames.addressof()));
|
|
|
|
// If en-us is missing we fall back to whatever is at index 0.
|
|
const auto ci = getLocalizedStringIndex(familyNames.get(), L"en-US", 0);
|
|
// If our locale is missing we fall back to en-us.
|
|
const auto li = getLocalizedStringIndex(familyNames.get(), locale, ci);
|
|
|
|
auto canonical = getLocalizedStringByIndex(familyNames.get(), ci);
|
|
// If the canonical/localized indices are the same, there's no need to get the other string.
|
|
auto localized = ci == li ? canonical : getLocalizedStringByIndex(familyNames.get(), li);
|
|
|
|
return make<Font>(std::move(canonical), std::move(localized));
|
|
}
|
|
|
|
winrt::guid ProfileViewModel::OriginalProfileGuid() const noexcept
|
|
{
|
|
return _originalProfileGuid;
|
|
}
|
|
|
|
bool ProfileViewModel::CanDeleteProfile() const
|
|
{
|
|
return !IsBaseLayer();
|
|
}
|
|
|
|
bool ProfileViewModel::Orphaned() const
|
|
{
|
|
return _profile.Orphaned();
|
|
}
|
|
|
|
hstring ProfileViewModel::TabTitlePreview() const
|
|
{
|
|
if (const auto tabTitle{ TabTitle() }; !tabTitle.empty())
|
|
{
|
|
return tabTitle;
|
|
}
|
|
return RS_(L"Profile_TabTitleNone");
|
|
}
|
|
|
|
hstring ProfileViewModel::AnswerbackMessagePreview() const
|
|
{
|
|
if (const auto answerbackMessage{ AnswerbackMessage() }; !answerbackMessage.empty())
|
|
{
|
|
return answerbackMessage;
|
|
}
|
|
return RS_(L"Profile_AnswerbackMessageNone");
|
|
}
|
|
|
|
Windows::UI::Color ProfileViewModel::TabColorPreview() const
|
|
{
|
|
if (const auto modelVal = _profile.TabColor())
|
|
{
|
|
const auto color = modelVal.Value();
|
|
// user defined an override value
|
|
return Windows::UI::Color{
|
|
.A = 255,
|
|
.R = color.R,
|
|
.G = color.G,
|
|
.B = color.B
|
|
};
|
|
}
|
|
// set to null --> deduce value from theme
|
|
return TabThemeColorPreview();
|
|
}
|
|
|
|
Windows::UI::Color ProfileViewModel::TabThemeColorPreview() const
|
|
{
|
|
const auto currentTheme = _appSettings.GlobalSettings().CurrentTheme();
|
|
if (const auto tabTheme = currentTheme.Tab())
|
|
{
|
|
// theme.tab.background: theme color must be evaluated
|
|
if (const auto tabBackground = tabTheme.Background())
|
|
{
|
|
const auto& tabBrush = tabBackground.Evaluate(Application::Current().Resources(),
|
|
Windows::UI::Xaml::Media::SolidColorBrush{ DefaultAppearance().CurrentColorScheme().BackgroundColor().Color() },
|
|
false);
|
|
if (const auto& tabColorBrush = tabBrush.try_as<Windows::UI::Xaml::Media::SolidColorBrush>())
|
|
{
|
|
const auto brushColor = tabColorBrush.Color();
|
|
return brushColor;
|
|
}
|
|
}
|
|
}
|
|
else if (const auto windowTheme = currentTheme.Window())
|
|
{
|
|
// theme.window.applicationTheme: evaluate light/dark to XAML default tab color
|
|
// Can also be "Default", in which case we fall through below
|
|
const auto appTheme = windowTheme.RequestedTheme();
|
|
if (appTheme == ElementTheme::Dark)
|
|
{
|
|
return Windows::UI::Color{
|
|
.A = 0xFF,
|
|
.R = 0x28,
|
|
.G = 0x28,
|
|
.B = 0x28
|
|
};
|
|
}
|
|
else if (appTheme == ElementTheme::Light)
|
|
{
|
|
return Windows::UI::Color{
|
|
.A = 0xFF,
|
|
.R = 0xF9,
|
|
.G = 0xF9,
|
|
.B = 0xF9
|
|
};
|
|
}
|
|
}
|
|
|
|
// XAML default tab color
|
|
if (Model::Theme::IsSystemInDarkTheme())
|
|
{
|
|
return Windows::UI::Color{
|
|
.A = 0xFF,
|
|
.R = 0x28,
|
|
.G = 0x28,
|
|
.B = 0x28
|
|
};
|
|
}
|
|
return Windows::UI::Color{
|
|
.A = 0xFF,
|
|
.R = 0xF9,
|
|
.G = 0xF9,
|
|
.B = 0xF9
|
|
};
|
|
}
|
|
|
|
Editor::AppearanceViewModel ProfileViewModel::DefaultAppearance() const
|
|
{
|
|
return _defaultAppearanceViewModel;
|
|
}
|
|
|
|
bool ProfileViewModel::HasUnfocusedAppearance()
|
|
{
|
|
return _profile.HasUnfocusedAppearance();
|
|
}
|
|
|
|
bool ProfileViewModel::EditableUnfocusedAppearance() const noexcept
|
|
{
|
|
return Feature_EditableUnfocusedAppearance::IsEnabled();
|
|
}
|
|
|
|
bool ProfileViewModel::ShowUnfocusedAppearance()
|
|
{
|
|
return EditableUnfocusedAppearance() && HasUnfocusedAppearance();
|
|
}
|
|
|
|
void ProfileViewModel::CreateUnfocusedAppearance()
|
|
{
|
|
_profile.CreateUnfocusedAppearance();
|
|
|
|
_unfocusedAppearanceViewModel = winrt::make<implementation::AppearanceViewModel>(_profile.UnfocusedAppearance().try_as<AppearanceConfig>());
|
|
_unfocusedAppearanceViewModel.SchemesList(DefaultAppearance().SchemesList());
|
|
|
|
_NotifyChanges(L"UnfocusedAppearance", L"HasUnfocusedAppearance", L"ShowUnfocusedAppearance");
|
|
}
|
|
|
|
void ProfileViewModel::DeleteUnfocusedAppearance()
|
|
{
|
|
_profile.DeleteUnfocusedAppearance();
|
|
|
|
_unfocusedAppearanceViewModel = nullptr;
|
|
|
|
_NotifyChanges(L"UnfocusedAppearance", L"HasUnfocusedAppearance", L"ShowUnfocusedAppearance");
|
|
}
|
|
|
|
Editor::AppearanceViewModel ProfileViewModel::UnfocusedAppearance() const
|
|
{
|
|
return _unfocusedAppearanceViewModel;
|
|
}
|
|
|
|
bool ProfileViewModel::ShowMarksAvailable() const noexcept
|
|
{
|
|
return Feature_ScrollbarMarks::IsEnabled();
|
|
}
|
|
bool ProfileViewModel::AutoMarkPromptsAvailable() const noexcept
|
|
{
|
|
return Feature_ScrollbarMarks::IsEnabled();
|
|
}
|
|
bool ProfileViewModel::RepositionCursorWithMouseAvailable() const noexcept
|
|
{
|
|
return Feature_ScrollbarMarks::IsEnabled();
|
|
}
|
|
|
|
hstring ProfileViewModel::CurrentStartingDirectoryPreview() const
|
|
{
|
|
if (UseParentProcessDirectory())
|
|
{
|
|
return RS_(L"Profile_StartingDirectoryUseParentCheckbox/Content");
|
|
}
|
|
return StartingDirectory();
|
|
}
|
|
|
|
bool ProfileViewModel::UseParentProcessDirectory() const
|
|
{
|
|
return StartingDirectory().empty();
|
|
}
|
|
|
|
void ProfileViewModel::UseParentProcessDirectory(const bool useParent)
|
|
{
|
|
if (useParent)
|
|
{
|
|
// Stash the current value of StartingDirectory. If the user
|
|
// checks and un-checks the "Use parent process directory" button, we want
|
|
// the path that we display in the text box to remain unchanged.
|
|
//
|
|
// Only stash this value if it's not empty
|
|
if (!StartingDirectory().empty())
|
|
{
|
|
_lastStartingDirectoryPath = StartingDirectory();
|
|
}
|
|
StartingDirectory(L"");
|
|
}
|
|
else
|
|
{
|
|
// Restore the path we had previously cached as long as it wasn't empty
|
|
// If it was empty, set the starting directory to %USERPROFILE%
|
|
// (we need to set it to something non-empty otherwise we will automatically
|
|
// disable the text box)
|
|
if (_lastStartingDirectoryPath.empty())
|
|
{
|
|
StartingDirectory(L"%USERPROFILE%");
|
|
}
|
|
else
|
|
{
|
|
StartingDirectory(_lastStartingDirectoryPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
winrt::hstring ProfileViewModel::LocalizedIcon() const
|
|
{
|
|
if (UsingNoIcon())
|
|
{
|
|
return RS_(L"IconPicker_IconTypeNone");
|
|
}
|
|
return IconPath(); // For display as a string
|
|
}
|
|
|
|
Windows::UI::Xaml::Controls::IconElement ProfileViewModel::IconPreview() const
|
|
{
|
|
// IconWUX sets the icon width/height to 32 by default
|
|
auto icon = Microsoft::Terminal::UI::IconPathConverter::IconWUX(EvaluatedIcon());
|
|
icon.Width(16);
|
|
icon.Height(16);
|
|
return icon;
|
|
}
|
|
|
|
bool ProfileViewModel::UsingNoIcon() const noexcept
|
|
{
|
|
const auto iconPath{ IconPath() };
|
|
return iconPath.empty() || iconPath == IconPicker::HideIconValue;
|
|
}
|
|
|
|
hstring ProfileViewModel::BellStylePreview() const
|
|
{
|
|
const auto bellStyle = BellStyle();
|
|
if (WI_AreAllFlagsSet(bellStyle, BellStyle::Audible | BellStyle::Window | BellStyle::Taskbar | BellStyle::Notification))
|
|
{
|
|
return RS_(L"Profile_BellStyleAll/Content");
|
|
}
|
|
else if (bellStyle == static_cast<Model::BellStyle>(0))
|
|
{
|
|
return RS_(L"Profile_BellStyleNone/Content");
|
|
}
|
|
|
|
std::vector<hstring> resultList;
|
|
resultList.reserve(4);
|
|
if (WI_IsFlagSet(bellStyle, BellStyle::Audible))
|
|
{
|
|
resultList.emplace_back(RS_(L"Profile_BellStyleAudible/Content"));
|
|
}
|
|
if (WI_IsFlagSet(bellStyle, BellStyle::Window))
|
|
{
|
|
resultList.emplace_back(RS_(L"Profile_BellStyleWindow/Content"));
|
|
}
|
|
if (WI_IsFlagSet(bellStyle, BellStyle::Taskbar))
|
|
{
|
|
resultList.emplace_back(RS_(L"Profile_BellStyleTaskbar/Content"));
|
|
}
|
|
if (WI_IsFlagSet(bellStyle, BellStyle::Notification))
|
|
{
|
|
resultList.emplace_back(RS_(L"Profile_BellStyleNotification/Content"));
|
|
}
|
|
|
|
// add in the commas
|
|
hstring result{};
|
|
for (auto&& entry : resultList)
|
|
{
|
|
if (result.empty())
|
|
{
|
|
result = entry;
|
|
}
|
|
else
|
|
{
|
|
result = result + L", " + entry;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool ProfileViewModel::IsBellStyleFlagSet(const uint32_t flag)
|
|
{
|
|
return (WI_EnumValue(BellStyle()) & flag) == flag;
|
|
}
|
|
|
|
void ProfileViewModel::SetBellStyleAudible(winrt::Windows::Foundation::IReference<bool> on)
|
|
{
|
|
auto currentStyle = BellStyle();
|
|
WI_UpdateFlag(currentStyle, Model::BellStyle::Audible, winrt::unbox_value<bool>(on));
|
|
BellStyle(currentStyle);
|
|
}
|
|
|
|
void ProfileViewModel::SetBellStyleWindow(winrt::Windows::Foundation::IReference<bool> on)
|
|
{
|
|
auto currentStyle = BellStyle();
|
|
WI_UpdateFlag(currentStyle, Model::BellStyle::Window, winrt::unbox_value<bool>(on));
|
|
BellStyle(currentStyle);
|
|
}
|
|
|
|
void ProfileViewModel::SetBellStyleTaskbar(winrt::Windows::Foundation::IReference<bool> on)
|
|
{
|
|
auto currentStyle = BellStyle();
|
|
WI_UpdateFlag(currentStyle, Model::BellStyle::Taskbar, winrt::unbox_value<bool>(on));
|
|
BellStyle(currentStyle);
|
|
}
|
|
|
|
void ProfileViewModel::SetBellStyleNotification(winrt::Windows::Foundation::IReference<bool> on)
|
|
{
|
|
auto currentStyle = BellStyle();
|
|
WI_UpdateFlag(currentStyle, Model::BellStyle::Notification, winrt::unbox_value<bool>(on));
|
|
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)
|
|
{
|
|
_CurrentBellSounds.Append(winrt::make<BellSoundViewModel>(bellSound));
|
|
}
|
|
}
|
|
_MarkDuplicateBellSoundDirectories();
|
|
_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 (!_profile.HasBellSound())
|
|
{
|
|
std::vector<IMediaResource> newSounds;
|
|
if (const auto inheritedSounds = _profile.BellSound())
|
|
{
|
|
newSounds = wil::to_vector(inheritedSounds);
|
|
}
|
|
// if we didn't inherit any bell sounds,
|
|
// we should still set the bell sound to an empty list (instead of null)
|
|
_profile.BellSound(winrt::single_threaded_vector(std::move(newSounds)));
|
|
}
|
|
}
|
|
|
|
// Method Description:
|
|
// - Check if any bell sounds share the same name.
|
|
// If they do, mark them so that they show the directory path in the UI
|
|
void ProfileViewModel::_MarkDuplicateBellSoundDirectories()
|
|
{
|
|
for (uint32_t i = 0; i < _CurrentBellSounds.Size(); i++)
|
|
{
|
|
auto soundA = _CurrentBellSounds.GetAt(i);
|
|
for (uint32_t j = i + 1; j < _CurrentBellSounds.Size(); j++)
|
|
{
|
|
auto soundB = _CurrentBellSounds.GetAt(j);
|
|
if (soundA.DisplayPath() == soundB.DisplayPath())
|
|
{
|
|
get_self<BellSoundViewModel>(soundA)->ShowDirectory(true);
|
|
get_self<BellSoundViewModel>(soundB)->ShowDirectory(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BellSoundViewModel::BellSoundViewModel(const Model::IMediaResource& resource) :
|
|
_resource{ resource }
|
|
{
|
|
if (_resource.Ok() && _resource.Path() != _resource.Resolved())
|
|
{
|
|
// If the resource was resolved to something other than its path, show the path!
|
|
_ShowDirectory = true;
|
|
}
|
|
}
|
|
|
|
hstring BellSoundViewModel::DisplayPath() const
|
|
{
|
|
if (_resource.Ok())
|
|
{
|
|
// filename; start from the resolved path to show where it actually landed
|
|
auto resolvedPath{ _resource.Resolved() };
|
|
const std::filesystem::path filePath{ std::wstring_view{ resolvedPath } };
|
|
return hstring{ filePath.filename().wstring() };
|
|
}
|
|
return _resource.Path();
|
|
}
|
|
|
|
hstring BellSoundViewModel::SubText() const
|
|
{
|
|
if (_resource.Ok())
|
|
{
|
|
// Directory
|
|
auto resolvedPath{ _resource.Resolved() };
|
|
const std::filesystem::path filePath{ std::wstring_view{ resolvedPath } };
|
|
return hstring{ filePath.parent_path().wstring() };
|
|
}
|
|
return RS_(L"Profile_BellSoundNotFound");
|
|
}
|
|
|
|
hstring ProfileViewModel::BellSoundPreview()
|
|
{
|
|
if (!_CurrentBellSounds || _CurrentBellSounds.Size() == 0)
|
|
{
|
|
return RS_(L"Profile_BellSoundPreviewDefault");
|
|
}
|
|
if (_CurrentBellSounds.Size() > 1)
|
|
{
|
|
return RS_(L"Profile_BellSoundPreviewMultiple");
|
|
}
|
|
|
|
const auto currentBellSound = _CurrentBellSounds.GetAt(0);
|
|
if (currentBellSound.FileExists())
|
|
{
|
|
return currentBellSound.DisplayPath();
|
|
}
|
|
|
|
return RS_(L"Profile_BellSoundNotFound");
|
|
}
|
|
|
|
void ProfileViewModel::RequestAddBellSound(hstring path)
|
|
{
|
|
// If we were inheriting our bell sound,
|
|
// copy it over to the current layer and apply modifications
|
|
_PrepareModelForBellSoundModification();
|
|
|
|
auto bellResource{ MediaResourceHelper::FromString(path) };
|
|
bellResource.Resolve(path); // No need to check if the file exists. We came from the FilePicker. That's good enough.
|
|
_CurrentBellSounds.Append(winrt::make<BellSoundViewModel>(bellResource));
|
|
_profile.BellSound().Append(bellResource);
|
|
_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);
|
|
_profile.BellSound().RemoveAt(index);
|
|
_NotifyChanges(L"CurrentBellSounds");
|
|
}
|
|
}
|
|
|
|
void ProfileViewModel::_RefreshDefaultAppearanceViewModel()
|
|
{
|
|
Windows::Foundation::Collections::IObservableVector<Editor::ColorSchemeViewModel> schemesList{ nullptr };
|
|
if (_defaultAppearanceViewModel)
|
|
{
|
|
schemesList = _defaultAppearanceViewModel.SchemesList();
|
|
}
|
|
|
|
_defaultAppearanceViewModel = winrt::make<implementation::AppearanceViewModel>(_profile.DefaultAppearance().try_as<AppearanceConfig>());
|
|
_defaultAppearanceViewModel.IsDefault(true);
|
|
if (schemesList)
|
|
{
|
|
_defaultAppearanceViewModel.SchemesList(schemesList);
|
|
}
|
|
|
|
_defaultAppearanceViewModel.PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) {
|
|
const auto viewModelProperty{ args.PropertyName() };
|
|
if (viewModelProperty == L"DarkColorSchemeName" || viewModelProperty == L"LightColorSchemeName")
|
|
{
|
|
_NotifyChanges(L"TabThemeColorPreview");
|
|
}
|
|
});
|
|
}
|
|
|
|
void ProfileViewModel::DeleteProfile()
|
|
{
|
|
const auto deleteProfileArgs{ winrt::make_self<DeleteProfileEventArgs>(Guid()) };
|
|
DeleteProfileRequested.raise(*this, *deleteProfileArgs);
|
|
}
|
|
|
|
void ProfileViewModel::ResetSettings()
|
|
{
|
|
// Clear every projected profile setting. Each Clear##name() notifies the bound
|
|
// properties and cascades to derived previews via our PropertyChanged handler.
|
|
#define PROFILE_VIEW_MODEL_CLEAR_SETTING(target, name) Clear##name();
|
|
PROFILE_VIEW_MODEL_PROJECTED_SETTINGS(PROFILE_VIEW_MODEL_CLEAR_SETTING)
|
|
#undef PROFILE_VIEW_MODEL_CLEAR_SETTING
|
|
_lastStartingDirectoryPath.clear();
|
|
|
|
// Not exposed to SUI
|
|
_profile.ClearEnvironmentVariables();
|
|
_profile.ClearAllowKeypadMode();
|
|
|
|
// Clear complex child objects
|
|
_profile.DefaultAppearance().ClearAllSettings();
|
|
_profile.FontInfo().ClearAllSettings();
|
|
|
|
if (HasUnfocusedAppearance())
|
|
{
|
|
DeleteUnfocusedAppearance();
|
|
}
|
|
|
|
_RefreshDefaultAppearanceViewModel();
|
|
_NotifyChanges(L"DefaultAppearance", L"TabThemeColorPreview", L"TabColorPreview");
|
|
}
|
|
|
|
void ProfileViewModel::SetupAppearances(Windows::Foundation::Collections::IObservableVector<Editor::ColorSchemeViewModel> schemesList)
|
|
{
|
|
DefaultAppearance().SchemesList(schemesList);
|
|
if (UnfocusedAppearance())
|
|
{
|
|
UnfocusedAppearance().SchemesList(schemesList);
|
|
}
|
|
}
|
|
}
|