mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
Add New Tab Menu Customization to Settings UI (#18015)
## Summary of the Pull Request Adds customization for the New Tab Menu to the settings UI. - Settings Model changes: - The Settings UI generally works by creating a copy of the entire settings model objects on which we apply the changes to. Turns out, we completely left the NewTabMenu out of that process. So I went ahead and implemented it. - `FolderEntry` - `FolderEntry` exposes `Entries()` (used by the new tab menu to figure out what to actually render) and `RawEntries()` (the actual JSON data deserialized into settings model objects). I went ahead and exposed `RawEntries()` since we'll need to apply changes to it to then serialize. - View Model: - `NewTabMenuViewModel` is the main view model that interacts with the page. It maintains the current view of items and applies changes to the settings model. - `NewTabMenuEntryViewModel` and all of the other `_EntryViewModel` classes are wrappers for the settings model NTM entries. - `FolderTreeViewEntry` encapsulates `FolderEntryViewModel`. It allows us to construct a `TreeView` of just folders. - View changes and additions: - Added FontIconGlyph to the SettingContainer - Added a New Tab Menu item to the navigation view - Adding entries: a stack of SettingContainers is used here. We use the new `FontIconGlyph` to make this look nice! - Reordering entries: drag and drop is supported! This might not work in admin mode though, and we can't drag and drop into folders. Buttons were added to make this keyboard accessible. - To move entries into a folder, a button was added which then displays a TreeView of all folders. - Multiple entries can be moved to a folder or deleted at once! - Breadcrumbs are used for folders - When a folder is entered, additional controls are displayed to customize that folder. ## Verification - ✅ a11y pass - ✅ keyboard accessible - scenarios: - ✅ add entries (except actions) - ✅ changes propagated to settings model (aka "saving works") - ✅ reorder entries - ✅ move entries to an existing folder - ✅ delete multiple entries - ✅ delete individual entries - ✅ display entries (including actions) ## Follow-ups - [ ] add support for adding and editing action entries - [ ] when we discard changes or save, it would be cool if we could stay on the same page - [ ] allow customizing the folder entry _before_ adding it (current workaround is to add it, then edit it) - [ ] improve UI for setting icon (reuse UI from #17965)
This commit is contained in:
parent
5c55144c28
commit
0d846aeb4d
2
.github/actions/spelling/allow/allow.txt
vendored
2
.github/actions/spelling/allow/allow.txt
vendored
@ -56,6 +56,7 @@ Powerline
|
||||
ptys
|
||||
pwn
|
||||
pwshw
|
||||
QOL
|
||||
qof
|
||||
qps
|
||||
quickfix
|
||||
@ -73,6 +74,7 @@ shcha
|
||||
similaritytolerance
|
||||
slnt
|
||||
stakeholders
|
||||
subpage
|
||||
sustainability
|
||||
sxn
|
||||
TLDR
|
||||
|
||||
@ -67,6 +67,7 @@
|
||||
<Thickness x:Key="StandardControlMargin">0,24,0,0</Thickness>
|
||||
<x:Double x:Key="StandardBoxMinWidth">250</x:Double>
|
||||
<x:Double x:Key="StandardControlMaxWidth">1000</x:Double>
|
||||
<Thickness x:Key="SettingStackMargin">13,0,13,48</Thickness>
|
||||
|
||||
<!--
|
||||
This is for styling the entire items control used on the
|
||||
@ -80,13 +81,13 @@
|
||||
<!-- Used to stack a group of settings -->
|
||||
<Style x:Key="SettingsStackStyle"
|
||||
TargetType="StackPanel">
|
||||
<Setter Property="Margin" Value="13,0,13,48" />
|
||||
<Setter Property="Margin" Value="{StaticResource SettingStackMargin}" />
|
||||
</Style>
|
||||
|
||||
<!-- Used to stack a group of settings inside a pivot -->
|
||||
<Style x:Key="PivotStackStyle"
|
||||
TargetType="StackPanel">
|
||||
<Setter Property="Margin" Value="0,0,13,48" />
|
||||
<Setter Property="Margin" Value="{StaticResource SettingStackMargin}" />
|
||||
</Style>
|
||||
|
||||
<!-- Combo Box -->
|
||||
@ -255,6 +256,17 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ExtraSmallButtonStyle"
|
||||
BasedOn="{StaticResource BrowseButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Height" Value="25" />
|
||||
<Setter Property="Width" Value="25" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SmallButtonStyle"
|
||||
BasedOn="{StaticResource BrowseButtonStyle}"
|
||||
TargetType="Button">
|
||||
@ -288,6 +300,17 @@
|
||||
<Setter Property="Padding" Value="5" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="DeleteExtraSmallButtonStyle"
|
||||
BasedOn="{StaticResource DeleteButtonStyle}"
|
||||
TargetType="Button">
|
||||
<Setter Property="Height" Value="25" />
|
||||
<Setter Property="Width" Value="25" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="IconButtonTextBlockStyle"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="Margin" Value="10,0,0,0" />
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "AddProfile.h"
|
||||
#include "InteractionViewModel.h"
|
||||
#include "LaunchViewModel.h"
|
||||
#include "NewTabMenuViewModel.h"
|
||||
#include "..\types\inc\utils.hpp"
|
||||
#include <..\WinRTUtils\inc\Utils.h>
|
||||
|
||||
@ -42,6 +43,7 @@ static const std::wstring_view interactionTag{ L"Interaction_Nav" };
|
||||
static const std::wstring_view renderingTag{ L"Rendering_Nav" };
|
||||
static const std::wstring_view compatibilityTag{ L"Compatibility_Nav" };
|
||||
static const std::wstring_view actionsTag{ L"Actions_Nav" };
|
||||
static const std::wstring_view newTabMenuTag{ L"NewTabMenu_Nav" };
|
||||
static const std::wstring_view globalProfileTag{ L"GlobalProfile_Nav" };
|
||||
static const std::wstring_view addProfileTag{ L"AddProfile" };
|
||||
static const std::wstring_view colorSchemesTag{ L"ColorSchemes_Nav" };
|
||||
@ -61,6 +63,28 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
InitializeComponent();
|
||||
_UpdateBackgroundForMica();
|
||||
|
||||
_newTabMenuPageVM = winrt::make<NewTabMenuViewModel>(_settingsClone);
|
||||
_ntmViewModelChangedRevoker = _newTabMenuPageVM.PropertyChanged(winrt::auto_revoke, [this](auto&&, const PropertyChangedEventArgs& args) {
|
||||
const auto settingName{ args.PropertyName() };
|
||||
if (settingName == L"CurrentFolder")
|
||||
{
|
||||
if (const auto& currentFolder = _newTabMenuPageVM.CurrentFolder())
|
||||
{
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(currentFolder), currentFolder.Name(), BreadcrumbSubPage::NewTabMenu_Folder);
|
||||
_breadcrumbs.Append(crumb);
|
||||
SettingsMainPage_ScrollViewer().ScrollToVerticalOffset(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we don't have a current folder, we're at the root of the NTM
|
||||
_breadcrumbs.Clear();
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(newTabMenuTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
|
||||
}
|
||||
});
|
||||
|
||||
_colorSchemesPageVM = winrt::make<ColorSchemesPageViewModel>(_settingsClone);
|
||||
_colorSchemesPageViewModelChangedRevoker = _colorSchemesPageVM.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) {
|
||||
const auto settingName{ args.PropertyName() };
|
||||
@ -136,12 +160,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_InitializeProfilesList();
|
||||
// Update the Nav State with the new version of the settings
|
||||
_colorSchemesPageVM.UpdateSettings(_settingsClone);
|
||||
_newTabMenuPageVM.UpdateSettings(_settingsClone);
|
||||
|
||||
// We'll update the profile in the _profilesNavState whenever we actually navigate to one
|
||||
|
||||
// now that the menuItems are repopulated,
|
||||
// refresh the current page using the breadcrumb data we collected before the refresh
|
||||
if (const auto& crumb{ lastBreadcrumb.try_as<Breadcrumb>() })
|
||||
if (const auto& crumb{ lastBreadcrumb.try_as<Breadcrumb>() }; crumb && crumb->Tag())
|
||||
{
|
||||
for (const auto& item : _menuItemSource)
|
||||
{
|
||||
@ -161,6 +186,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (const auto& breadcrumbFolderEntry{ crumb->Tag().try_as<Editor::FolderEntryViewModel>() })
|
||||
{
|
||||
if (stringTag == newTabMenuTag)
|
||||
{
|
||||
// navigate to the NewTabMenu page,
|
||||
// _Navigate() will handle trying to find the right subpage
|
||||
SettingsNav().SelectedItem(item);
|
||||
_Navigate(breadcrumbFolderEntry, BreadcrumbSubPage::NewTabMenu_Folder);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (const auto& profileTag{ tag.try_as<ProfileViewModel>() })
|
||||
{
|
||||
@ -393,6 +429,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
else if (clickedItemTag == newTabMenuTag)
|
||||
{
|
||||
if (_newTabMenuPageVM.CurrentFolder())
|
||||
{
|
||||
// Setting CurrentFolder triggers the PropertyChanged event,
|
||||
// which will navigate to the correct page and update the breadcrumbs appropriately
|
||||
_newTabMenuPageVM.CurrentFolder(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Navigate to the NewTabMenu page
|
||||
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
}
|
||||
else if (clickedItemTag == globalProfileTag)
|
||||
{
|
||||
auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone) };
|
||||
@ -490,6 +542,39 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void MainPage::_Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage)
|
||||
{
|
||||
_PreNavigateHelper();
|
||||
|
||||
contentFrame().Navigate(xaml_typename<Editor::NewTabMenu>(), _newTabMenuPageVM);
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(newTabMenuTag), RS_(L"Nav_NewTabMenu/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
|
||||
if (subPage == BreadcrumbSubPage::None)
|
||||
{
|
||||
_newTabMenuPageVM.CurrentFolder(nullptr);
|
||||
}
|
||||
else if (const auto& folderEntryVM = ntmEntryVM.try_as<Editor::FolderEntryViewModel>(); subPage == BreadcrumbSubPage::NewTabMenu_Folder && folderEntryVM)
|
||||
{
|
||||
// The given ntmEntryVM doesn't exist anymore since the whole tree had to be recreated.
|
||||
// Instead, let's look for a match by name and navigate to it.
|
||||
if (const auto& folderPath = _newTabMenuPageVM.FindFolderPathByName(folderEntryVM.Name()); folderPath.Size() > 0)
|
||||
{
|
||||
for (const auto& step : folderPath)
|
||||
{
|
||||
// Take advantage of the PropertyChanged event to navigate
|
||||
// to the correct folder and build the breadcrumbs as we go
|
||||
_newTabMenuPageVM.CurrentFolder(step);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we couldn't find a reasonable match, just go back to the root
|
||||
_newTabMenuPageVM.CurrentFolder(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainPage::OpenJsonTapped(const IInspectable& /*sender*/, const Windows::UI::Xaml::Input::TappedRoutedEventArgs& /*args*/)
|
||||
{
|
||||
const auto window = CoreWindow::GetForCurrentThread();
|
||||
@ -532,6 +617,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
_Navigate(*profileViewModel, subPage);
|
||||
}
|
||||
else if (const auto ntmEntryViewModel = tag.try_as<NewTabMenuEntryViewModel>())
|
||||
{
|
||||
_Navigate(*ntmEntryViewModel, subPage);
|
||||
}
|
||||
else
|
||||
{
|
||||
_Navigate(tag.as<hstring>(), subPage);
|
||||
|
||||
@ -69,14 +69,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
void _PreNavigateHelper();
|
||||
void _Navigate(hstring clickedItemTag, BreadcrumbSubPage subPage);
|
||||
void _Navigate(const Editor::ProfileViewModel& profile, BreadcrumbSubPage subPage);
|
||||
void _Navigate(const Editor::NewTabMenuEntryViewModel& ntmEntryVM, BreadcrumbSubPage subPage);
|
||||
|
||||
void _UpdateBackgroundForMica();
|
||||
void _MoveXamlParsedNavItemsIntoItemSource();
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr };
|
||||
winrt::Microsoft::Terminal::Settings::Editor::NewTabMenuViewModel _newTabMenuPageVM{ nullptr };
|
||||
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _profileViewModelChangedRevoker;
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _colorSchemesPageViewModelChangedRevoker;
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ntmViewModelChangedRevoker;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,8 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
Profile_Appearance,
|
||||
Profile_Terminal,
|
||||
Profile_Advanced,
|
||||
ColorSchemes_Edit
|
||||
ColorSchemes_Edit,
|
||||
NewTabMenu_Folder
|
||||
};
|
||||
|
||||
runtimeclass Breadcrumb : Windows.Foundation.IStringable
|
||||
|
||||
@ -148,6 +148,13 @@
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
</muxc:NavigationViewItem>
|
||||
|
||||
<muxc:NavigationViewItem x:Uid="Nav_NewTabMenu"
|
||||
Tag="NewTabMenu_Nav">
|
||||
<muxc:NavigationViewItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
</muxc:NavigationViewItem>
|
||||
|
||||
<muxc:NavigationViewItemHeader x:Uid="Nav_Profiles" />
|
||||
|
||||
<muxc:NavigationViewItem x:Name="BaseLayerMenuItem"
|
||||
|
||||
@ -73,6 +73,9 @@
|
||||
<ClInclude Include="Launch.h">
|
||||
<DependentUpon>Launch.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NewTabMenu.h">
|
||||
<DependentUpon>NewTabMenu.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="MainPage.h">
|
||||
<DependentUpon>MainPage.xaml</DependentUpon>
|
||||
@ -109,6 +112,10 @@
|
||||
<DependentUpon>LaunchViewModel.idl</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NewTabMenuViewModel.h">
|
||||
<DependentUpon>NewTabMenuViewModel.idl</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Profiles_Base.h">
|
||||
<DependentUpon>Profiles_Base.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
@ -174,6 +181,9 @@
|
||||
<Page Include="Launch.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="NewTabMenu.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="MainPage.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
@ -233,6 +243,9 @@
|
||||
<ClCompile Include="Launch.cpp">
|
||||
<DependentUpon>Launch.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="NewTabMenu.cpp">
|
||||
<DependentUpon>NewTabMenu.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@ -272,6 +285,10 @@
|
||||
<DependentUpon>LaunchViewModel.idl</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="NewTabMenuViewModel.cpp">
|
||||
<DependentUpon>NewTabMenuViewModel.idl</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Profiles_Base.cpp">
|
||||
<DependentUpon>Profiles_Base.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
@ -338,6 +355,10 @@
|
||||
<DependentUpon>Launch.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="NewTabMenu.idl">
|
||||
<DependentUpon>NewTabMenu.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="Interaction.idl">
|
||||
<DependentUpon>Interaction.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
@ -361,6 +382,7 @@
|
||||
<Midl Include="InteractionViewModel.idl" />
|
||||
<Midl Include="GlobalAppearanceViewModel.idl" />
|
||||
<Midl Include="LaunchViewModel.idl" />
|
||||
<Midl Include="NewTabMenuViewModel.idl" />
|
||||
<Midl Include="Profiles_Base.idl">
|
||||
<DependentUpon>Profiles_Base.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
<Midl Include="LaunchViewModel.idl" />
|
||||
<Midl Include="EnumEntry.idl" />
|
||||
<Midl Include="SettingContainer.idl" />
|
||||
<Midl Include="NewTabMenuViewModel.idl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
@ -49,5 +50,6 @@
|
||||
<Page Include="SettingContainerStyle.xaml" />
|
||||
<Page Include="AddProfile.xaml" />
|
||||
<Page Include="KeyChordListener.xaml" />
|
||||
<Page Include="NewTabMenu.xaml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
203
src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp
Normal file
203
src/cascadia/TerminalSettingsEditor/NewTabMenu.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "NewTabMenu.h"
|
||||
#include "NewTabMenu.g.cpp"
|
||||
#include "NewTabMenuEntryTemplateSelector.g.cpp"
|
||||
#include "EnumEntry.h"
|
||||
|
||||
#include "NewTabMenuViewModel.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Navigation;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
NewTabMenu::NewTabMenu()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_entryTemplateSelector = Resources().Lookup(box_value(L"NewTabMenuEntryTemplateSelector")).as<Editor::NewTabMenuEntryTemplateSelector>();
|
||||
|
||||
// Ideally, we'd bind IsEnabled to something like mtu:Converters.isEmpty(NewTabMenuListView.SelectedItems.Size) in the XAML,
|
||||
// but the XAML compiler can't find NewTabMenuListView when we try that. Rather than copying the list of selected items over
|
||||
// to the view model, we'll just do this instead (much simpler).
|
||||
NewTabMenuListView().SelectionChanged([this](auto&&, auto&&) {
|
||||
const auto list = NewTabMenuListView();
|
||||
MoveToFolderButton().IsEnabled(list.SelectedItems().Size() > 0);
|
||||
DeleteMultipleButton().IsEnabled(list.SelectedItems().Size() > 0);
|
||||
});
|
||||
|
||||
Automation::AutomationProperties::SetName(MoveToFolderButton(), RS_(L"NewTabMenu_MoveToFolderTextBlock/Text"));
|
||||
Automation::AutomationProperties::SetName(DeleteMultipleButton(), RS_(L"NewTabMenu_DeleteMultipleTextBlock/Text"));
|
||||
Automation::AutomationProperties::SetName(AddProfileComboBox(), RS_(L"NewTabMenu_AddProfile/Header"));
|
||||
Automation::AutomationProperties::SetName(AddProfileButton(), RS_(L"NewTabMenu_AddProfileButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
|
||||
Automation::AutomationProperties::SetName(AddSeparatorButton(), RS_(L"NewTabMenu_AddSeparatorButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
|
||||
Automation::AutomationProperties::SetName(AddFolderButton(), RS_(L"NewTabMenu_AddFolderButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
|
||||
Automation::AutomationProperties::SetName(AddMatchProfilesButton(), RS_(L"NewTabMenu_AddMatchProfilesTextBlock/Text"));
|
||||
Automation::AutomationProperties::SetName(AddRemainingProfilesButton(), RS_(L"NewTabMenu_AddRemainingProfilesButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
|
||||
}
|
||||
|
||||
void NewTabMenu::OnNavigatedTo(const NavigationEventArgs& e)
|
||||
{
|
||||
_ViewModel = e.Parameter().as<Editor::NewTabMenuViewModel>();
|
||||
}
|
||||
|
||||
void NewTabMenu::FolderPickerDialog_Opened(const IInspectable& /*sender*/, const Controls::ContentDialogOpenedEventArgs& /*e*/)
|
||||
{
|
||||
// Ideally, we'd bind IsPrimaryButtonEnabled to something like mtu:Converters.isEmpty(FolderTree.SelectedItems.Size) in the XAML.
|
||||
// Similar to above, the XAML compiler can't find FolderTree when we try that.
|
||||
// To make matters worse, SelectionChanged doesn't exist for WinUI 2's TreeView.
|
||||
// Let's just select the first item and call it a day.
|
||||
_ViewModel.GenerateFolderTree();
|
||||
_ViewModel.CurrentFolderTreeViewSelectedItem(_ViewModel.FolderTree().First().Current());
|
||||
}
|
||||
|
||||
void NewTabMenu::FolderPickerDialog_PrimaryButtonClick(const IInspectable& /*sender*/, const Controls::ContentDialogButtonClickEventArgs& /*e*/)
|
||||
{
|
||||
// copy selected items first (it updates as we move entries)
|
||||
std::vector<Editor::NewTabMenuEntryViewModel> entries;
|
||||
for (const auto& item : NewTabMenuListView().SelectedItems())
|
||||
{
|
||||
entries.push_back(item.as<Editor::NewTabMenuEntryViewModel>());
|
||||
}
|
||||
|
||||
// now actually move them
|
||||
_ViewModel.RequestMoveEntriesToFolder(single_threaded_vector<Editor::NewTabMenuEntryViewModel>(std::move(entries)), FolderTreeView().SelectedItem().as<Editor::FolderTreeViewEntry>().FolderEntryVM());
|
||||
}
|
||||
|
||||
void NewTabMenu::EditEntry_Clicked(const IInspectable& sender, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
const auto folderVM = sender.as<FrameworkElement>().DataContext().as<Editor::FolderEntryViewModel>();
|
||||
_ViewModel.CurrentFolder(folderVM);
|
||||
}
|
||||
|
||||
void NewTabMenu::ReorderEntry_Clicked(const IInspectable& sender, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
const auto btn = sender.as<Controls::Button>();
|
||||
const auto entry = btn.DataContext().as<Editor::NewTabMenuEntryViewModel>();
|
||||
const auto direction = unbox_value<hstring>(btn.Tag());
|
||||
|
||||
_ViewModel.RequestReorderEntry(entry, direction == L"Up");
|
||||
}
|
||||
|
||||
void NewTabMenu::DeleteEntry_Clicked(const IInspectable& sender, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
const auto entry = sender.as<Controls::Button>().DataContext().as<Editor::NewTabMenuEntryViewModel>();
|
||||
_ViewModel.RequestDeleteEntry(entry);
|
||||
}
|
||||
|
||||
safe_void_coroutine NewTabMenu::MoveMultiple_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
co_await FindName(L"FolderPickerDialog").as<Controls::ContentDialog>().ShowAsync();
|
||||
}
|
||||
|
||||
void NewTabMenu::DeleteMultiple_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
// copy selected items first (it updates as we delete entries)
|
||||
std::vector<Editor::NewTabMenuEntryViewModel> entries;
|
||||
for (const auto& item : NewTabMenuListView().SelectedItems())
|
||||
{
|
||||
entries.push_back(item.as<Editor::NewTabMenuEntryViewModel>());
|
||||
}
|
||||
|
||||
// now actually delete them
|
||||
for (const auto& vm : entries)
|
||||
{
|
||||
_ViewModel.RequestDeleteEntry(vm);
|
||||
}
|
||||
}
|
||||
|
||||
void NewTabMenu::AddProfileButton_Clicked(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
_ScrollToEntry(_ViewModel.RequestAddSelectedProfileEntry());
|
||||
}
|
||||
|
||||
void NewTabMenu::AddSeparatorButton_Clicked(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
_ScrollToEntry(_ViewModel.RequestAddSeparatorEntry());
|
||||
}
|
||||
|
||||
void NewTabMenu::AddFolderButton_Clicked(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
_ScrollToEntry(_ViewModel.RequestAddFolderEntry());
|
||||
}
|
||||
|
||||
void NewTabMenu::AddMatchProfilesButton_Clicked(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
_ScrollToEntry(_ViewModel.RequestAddProfileMatcherEntry());
|
||||
}
|
||||
|
||||
void NewTabMenu::AddRemainingProfilesButton_Clicked(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
|
||||
{
|
||||
_ScrollToEntry(_ViewModel.RequestAddRemainingProfilesEntry());
|
||||
}
|
||||
|
||||
// As a QOL improvement, we scroll to the newly added entry.
|
||||
// Calling ScrollIntoView() on its own causes the items to briefly disappear.
|
||||
// Calling UpdateLayout() beforehand remedies this issue.
|
||||
void NewTabMenu::_ScrollToEntry(const Editor::NewTabMenuEntryViewModel& entry)
|
||||
{
|
||||
const auto& listView = NewTabMenuListView();
|
||||
listView.UpdateLayout();
|
||||
listView.ScrollIntoView(entry);
|
||||
}
|
||||
|
||||
void NewTabMenu::AddFolderNameTextBox_KeyDown(const IInspectable& /*sender*/, const Input::KeyRoutedEventArgs& e)
|
||||
{
|
||||
if (e.Key() == Windows::System::VirtualKey::Enter)
|
||||
{
|
||||
// We need to manually set the FolderName here because the TextBox's TextChanged event hasn't fired yet.
|
||||
if (const auto folderName = FolderNameTextBox().Text(); !folderName.empty())
|
||||
{
|
||||
_ViewModel.AddFolderName(folderName);
|
||||
const auto entry = _ViewModel.RequestAddFolderEntry();
|
||||
NewTabMenuListView().ScrollIntoView(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NewTabMenu::AddFolderNameTextBox_TextChanged(const IInspectable& sender, const Controls::TextChangedEventArgs& /*e*/)
|
||||
{
|
||||
const auto isTextEmpty = sender.as<Controls::TextBox>().Text().empty();
|
||||
AddFolderButton().IsEnabled(!isTextEmpty);
|
||||
}
|
||||
|
||||
DataTemplate NewTabMenuEntryTemplateSelector::SelectTemplateCore(const IInspectable& item, const DependencyObject& /*container*/)
|
||||
{
|
||||
return SelectTemplateCore(item);
|
||||
}
|
||||
|
||||
DataTemplate NewTabMenuEntryTemplateSelector::SelectTemplateCore(const IInspectable& item)
|
||||
{
|
||||
if (const auto entryVM = item.try_as<Editor::NewTabMenuEntryViewModel>())
|
||||
{
|
||||
switch (entryVM.Type())
|
||||
{
|
||||
case Model::NewTabMenuEntryType::Profile:
|
||||
return ProfileEntryTemplate();
|
||||
case Model::NewTabMenuEntryType::Action:
|
||||
return ActionEntryTemplate();
|
||||
case Model::NewTabMenuEntryType::Separator:
|
||||
return SeparatorEntryTemplate();
|
||||
case Model::NewTabMenuEntryType::Folder:
|
||||
return FolderEntryTemplate();
|
||||
case Model::NewTabMenuEntryType::MatchProfiles:
|
||||
return MatchProfilesEntryTemplate();
|
||||
case Model::NewTabMenuEntryType::RemainingProfiles:
|
||||
return RemainingProfilesEntryTemplate();
|
||||
case Model::NewTabMenuEntryType::Invalid:
|
||||
default:
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
72
src/cascadia/TerminalSettingsEditor/NewTabMenu.h
Normal file
72
src/cascadia/TerminalSettingsEditor/NewTabMenu.h
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NewTabMenu.g.h"
|
||||
#include "NewTabMenuEntryTemplateSelector.g.h"
|
||||
#include "Utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct NewTabMenu : public HasScrollViewer<NewTabMenu>, NewTabMenuT<NewTabMenu>
|
||||
{
|
||||
public:
|
||||
NewTabMenu();
|
||||
|
||||
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
|
||||
|
||||
// FolderPickerDialog handlers
|
||||
void FolderPickerDialog_Opened(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Controls::ContentDialogOpenedEventArgs& e);
|
||||
void FolderPickerDialog_PrimaryButtonClick(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Controls::ContentDialogButtonClickEventArgs& e);
|
||||
|
||||
// NTM Entry handlers
|
||||
void EditEntry_Clicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void ReorderEntry_Clicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void DeleteEntry_Clicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
|
||||
// Multiple Entry handlers
|
||||
safe_void_coroutine MoveMultiple_Click(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void DeleteMultiple_Click(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
|
||||
// New Entry handlers
|
||||
void AddProfileButton_Clicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void AddSeparatorButton_Clicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void AddFolderButton_Clicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void AddMatchProfilesButton_Clicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void AddRemainingProfilesButton_Clicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
void AddFolderNameTextBox_KeyDown(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
|
||||
void AddFolderNameTextBox_TextChanged(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs& e);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
WINRT_OBSERVABLE_PROPERTY(Editor::NewTabMenuViewModel, ViewModel, _PropertyChangedHandlers, nullptr);
|
||||
|
||||
private:
|
||||
Editor::NewTabMenuEntryTemplateSelector _entryTemplateSelector{ nullptr };
|
||||
Editor::NewTabMenuEntryViewModel _draggedEntry{ nullptr };
|
||||
|
||||
void _ScrollToEntry(const Editor::NewTabMenuEntryViewModel& entry);
|
||||
};
|
||||
|
||||
struct NewTabMenuEntryTemplateSelector : public NewTabMenuEntryTemplateSelectorT<NewTabMenuEntryTemplateSelector>
|
||||
{
|
||||
public:
|
||||
NewTabMenuEntryTemplateSelector() = default;
|
||||
|
||||
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item, const Windows::UI::Xaml::DependencyObject& container);
|
||||
Windows::UI::Xaml::DataTemplate SelectTemplateCore(const Windows::Foundation::IInspectable& item);
|
||||
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, ProfileEntryTemplate, nullptr);
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, ActionEntryTemplate, nullptr);
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, SeparatorEntryTemplate, nullptr);
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, FolderEntryTemplate, nullptr);
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, MatchProfilesEntryTemplate, nullptr);
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::DataTemplate, RemainingProfilesEntryTemplate, nullptr);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(NewTabMenu);
|
||||
BASIC_FACTORY(NewTabMenuEntryTemplateSelector);
|
||||
}
|
||||
25
src/cascadia/TerminalSettingsEditor/NewTabMenu.idl
Normal file
25
src/cascadia/TerminalSettingsEditor/NewTabMenu.idl
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "NewTabMenuViewModel.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
[default_interface] runtimeclass NewTabMenu : Windows.UI.Xaml.Controls.Page
|
||||
{
|
||||
NewTabMenu();
|
||||
NewTabMenuViewModel ViewModel { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass NewTabMenuEntryTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector
|
||||
{
|
||||
NewTabMenuEntryTemplateSelector();
|
||||
|
||||
Windows.UI.Xaml.DataTemplate ProfileEntryTemplate;
|
||||
Windows.UI.Xaml.DataTemplate ActionEntryTemplate;
|
||||
Windows.UI.Xaml.DataTemplate SeparatorEntryTemplate;
|
||||
Windows.UI.Xaml.DataTemplate FolderEntryTemplate;
|
||||
Windows.UI.Xaml.DataTemplate MatchProfilesEntryTemplate;
|
||||
Windows.UI.Xaml.DataTemplate RemainingProfilesEntryTemplate;
|
||||
}
|
||||
}
|
||||
466
src/cascadia/TerminalSettingsEditor/NewTabMenu.xaml
Normal file
466
src/cascadia/TerminalSettingsEditor/NewTabMenu.xaml
Normal file
@ -0,0 +1,466 @@
|
||||
<!--
|
||||
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information.
|
||||
-->
|
||||
<Page x:Class="Microsoft.Terminal.Settings.Editor.NewTabMenu"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:model="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="CommonResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Wrapper for NewTabMenuEntry that adds buttons to the ListView entries -->
|
||||
<Style x:Key="NewTabMenuEntryControlsWrapper"
|
||||
TargetType="ContentControl">
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ContentControl">
|
||||
<Grid XYFocusKeyboardNavigation="Enabled">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentPresenter Grid.Column="0"
|
||||
Content="{TemplateBinding Content}" />
|
||||
<StackPanel Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="5">
|
||||
<!-- Reorder: Up -->
|
||||
<Button x:Uid="NewTabMenuEntry_ReorderUp"
|
||||
Click="ReorderEntry_Clicked"
|
||||
DataContext="{TemplateBinding DataContext}"
|
||||
Style="{StaticResource ExtraSmallButtonStyle}"
|
||||
Tag="Up">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Reorder: Down -->
|
||||
<Button x:Uid="NewTabMenuEntry_ReorderDown"
|
||||
Click="ReorderEntry_Clicked"
|
||||
DataContext="{TemplateBinding DataContext}"
|
||||
Style="{StaticResource ExtraSmallButtonStyle}"
|
||||
Tag="Down">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Delete Entry -->
|
||||
<Button x:Uid="NewTabMenuEntry_Delete"
|
||||
Click="DeleteEntry_Clicked"
|
||||
DataContext="{TemplateBinding DataContext}"
|
||||
Style="{StaticResource DeleteExtraSmallButtonStyle}">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="ProfileEntryTemplate"
|
||||
x:DataType="local:ProfileEntryViewModel">
|
||||
<ContentControl AutomationProperties.Name="{x:Bind ProfileEntry.Profile.Name, Mode=OneWay}"
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource NewTabMenuEntryControlsWrapper}">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<IconSourceElement Width="16"
|
||||
Height="16"
|
||||
VerticalAlignment="Center"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(ProfileEntry.Profile.EvaluatedIcon), Mode=OneTime}" />
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{x:Bind ProfileEntry.Profile.Name, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ActionEntryTemplate"
|
||||
x:DataType="local:ActionEntryViewModel">
|
||||
<ContentControl AutomationProperties.Name="{x:Bind DisplayText, Mode=OneWay}"
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource NewTabMenuEntryControlsWrapper}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<IconSourceElement Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon), Mode=OneWay}"
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Icon), Mode=OneWay}" />
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{x:Bind DisplayText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="SeparatorEntryTemplate"
|
||||
x:DataType="local:SeparatorEntryViewModel">
|
||||
<ContentControl x:Uid="NewTabMenuEntry_SeparatorItem"
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource NewTabMenuEntryControlsWrapper}">
|
||||
<TextBlock x:Uid="NewTabMenuEntry_Separator"
|
||||
VerticalAlignment="Center"
|
||||
FontStyle="Italic" />
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="FolderEntryTemplate"
|
||||
x:DataType="local:FolderEntryViewModel">
|
||||
<!--
|
||||
Most of this was copied from NewTabMenuEntryControlsWrapper.
|
||||
We really just added the Edit button here.
|
||||
-->
|
||||
<Grid AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
XYFocusKeyboardNavigation="Enabled">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal">
|
||||
<IconSourceElement Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon), Mode=OneWay}"
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Icon), Mode=OneWay}" />
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{x:Bind Name, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="5">
|
||||
<Button x:Uid="NewTabMenuEntry_EditFolder"
|
||||
Click="EditEntry_Clicked"
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource ExtraSmallButtonStyle}">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<Button x:Uid="NewTabMenuEntry_ReorderUp"
|
||||
Click="ReorderEntry_Clicked"
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource ExtraSmallButtonStyle}"
|
||||
Tag="Up">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<Button x:Uid="NewTabMenuEntry_ReorderDown"
|
||||
Click="ReorderEntry_Clicked"
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource ExtraSmallButtonStyle}"
|
||||
Tag="Down">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<Button x:Uid="NewTabMenuEntry_Delete"
|
||||
Click="DeleteEntry_Clicked"
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource DeleteExtraSmallButtonStyle}">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="MatchProfilesEntryTemplate"
|
||||
x:DataType="local:MatchProfilesEntryViewModel">
|
||||
<ContentControl AutomationProperties.Name="{x:Bind DisplayText, Mode=OneWay}"
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource NewTabMenuEntryControlsWrapper}">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
FontStyle="Italic"
|
||||
Text="{x:Bind DisplayText, Mode=OneWay}" />
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="RemainingProfilesEntryTemplate"
|
||||
x:DataType="local:RemainingProfilesEntryViewModel">
|
||||
<ContentControl x:Uid="NewTabMenuEntry_RemainingProfilesItem"
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource NewTabMenuEntryControlsWrapper}">
|
||||
<TextBlock x:Uid="NewTabMenuEntry_RemainingProfiles"
|
||||
VerticalAlignment="Center"
|
||||
FontStyle="Italic" />
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
<local:NewTabMenuEntryTemplateSelector x:Key="NewTabMenuEntryTemplateSelector"
|
||||
ActionEntryTemplate="{StaticResource ActionEntryTemplate}"
|
||||
FolderEntryTemplate="{StaticResource FolderEntryTemplate}"
|
||||
MatchProfilesEntryTemplate="{StaticResource MatchProfilesEntryTemplate}"
|
||||
ProfileEntryTemplate="{StaticResource ProfileEntryTemplate}"
|
||||
RemainingProfilesEntryTemplate="{StaticResource RemainingProfilesEntryTemplate}"
|
||||
SeparatorEntryTemplate="{StaticResource SeparatorEntryTemplate}" />
|
||||
</ResourceDictionary>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid Margin="{StaticResource SettingStackMargin}"
|
||||
RowSpacing="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Folder Picker Dialog: used to select a folder to move entries to -->
|
||||
<ContentDialog x:Name="FolderPickerDialog"
|
||||
x:Uid="NewTabMenu_FolderPickerDialog"
|
||||
x:Load="False"
|
||||
DefaultButton="Primary"
|
||||
Opened="FolderPickerDialog_Opened"
|
||||
PrimaryButtonClick="FolderPickerDialog_PrimaryButtonClick">
|
||||
<muxc:TreeView x:Name="FolderTreeView"
|
||||
ItemsSource="{x:Bind ViewModel.FolderTree, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.CurrentFolderTreeViewSelectedItem, Mode=TwoWay}"
|
||||
SelectionMode="Single">
|
||||
<muxc:TreeView.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:FolderTreeViewEntry">
|
||||
<muxc:TreeViewItem AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
IsExpanded="True"
|
||||
ItemsSource="{x:Bind Children, Mode=OneWay}">
|
||||
<StackPanel AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
Orientation="Horizontal">
|
||||
<IconSourceElement Width="16"
|
||||
Height="16"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Center"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(Icon), Mode=OneWay}"
|
||||
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(Icon), Mode=OneWay}" />
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{x:Bind Name, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</muxc:TreeViewItem>
|
||||
</DataTemplate>
|
||||
</muxc:TreeView.ItemTemplate>
|
||||
</muxc:TreeView>
|
||||
</ContentDialog>
|
||||
|
||||
<!-- New Tab Menu Content -->
|
||||
<StackPanel Grid.Row="0"
|
||||
MaxWidth="{StaticResource StandardControlMaxWidth}"
|
||||
Spacing="10">
|
||||
<Border Height="300"
|
||||
MaxWidth="{StaticResource StandardControlMaxWidth}"
|
||||
Margin="0,12,0,0"
|
||||
BorderBrush="{ThemeResource SystemControlForegroundBaseMediumLowBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}">
|
||||
<ListView x:Name="NewTabMenuListView"
|
||||
AllowDrop="True"
|
||||
CanDragItems="True"
|
||||
CanReorderItems="True"
|
||||
ItemTemplateSelector="{StaticResource NewTabMenuEntryTemplateSelector}"
|
||||
ItemsSource="{x:Bind ViewModel.CurrentView, Mode=OneWay}"
|
||||
SelectionMode="Multiple" />
|
||||
</Border>
|
||||
|
||||
<!-- General Controls -->
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<Button x:Name="MoveToFolderButton"
|
||||
Click="MoveMultiple_Click"
|
||||
IsEnabled="False">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="NewTabMenu_MoveToFolderTextBlock"
|
||||
Margin="10,0,0,0" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button x:Name="DeleteMultipleButton"
|
||||
Click="DeleteMultiple_Click"
|
||||
IsEnabled="False"
|
||||
Style="{StaticResource DeleteButtonStyle}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="NewTabMenu_DeleteMultipleTextBlock"
|
||||
Margin="10,0,0,0" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Folder View Controls -->
|
||||
<StackPanel Grid.Row="1"
|
||||
MaxWidth="{StaticResource StandardControlMaxWidth}"
|
||||
Visibility="{x:Bind ViewModel.IsFolderView, Mode=OneWay}">
|
||||
<TextBlock x:Uid="NewTabMenu_CurrentFolderTextBlock"
|
||||
Style="{StaticResource TextBlockSubHeaderStyle}" />
|
||||
|
||||
<!-- TODO CARLOS: Icon -->
|
||||
<!-- Once PR #17965 merges, we can add that kind of control to set an icon -->
|
||||
|
||||
<!-- Name -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderName"
|
||||
Grid.Row="0"
|
||||
CurrentValue="{x:Bind ViewModel.CurrentFolderName, Mode=OneWay}"
|
||||
Style="{StaticResource ExpanderSettingContainerStyle}">
|
||||
<TextBox Style="{StaticResource TextBoxSettingStyle}"
|
||||
Text="{x:Bind ViewModel.CurrentFolderName, Mode=TwoWay}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Inlining -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderInlining"
|
||||
Grid.Row="1">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.CurrentFolderInlining, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Allow Empty -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_CurrentFolderAllowEmpty"
|
||||
Grid.Row="2">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.CurrentFolderAllowEmpty, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</local:SettingContainer>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Add Entries -->
|
||||
<StackPanel Grid.Row="2">
|
||||
<TextBlock x:Uid="NewTabMenu_AddEntriesTextBlock"
|
||||
Style="{StaticResource TextBlockSubHeaderStyle}" />
|
||||
|
||||
<!-- Add Profile -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_AddProfile"
|
||||
FontIconGlyph="">
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="5">
|
||||
<!-- Select profile to add -->
|
||||
<ComboBox x:Name="AddProfileComboBox"
|
||||
MinWidth="{StaticResource StandardBoxMinWidth}"
|
||||
ItemsSource="{x:Bind ViewModel.AvailableProfiles, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedProfile, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="model:Profile">
|
||||
<Grid HorizontalAlignment="Stretch"
|
||||
ColumnSpacing="8">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<!-- icon -->
|
||||
<ColumnDefinition Width="16" />
|
||||
<!-- profile name -->
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<IconSourceElement Grid.Column="0"
|
||||
Width="16"
|
||||
Height="16"
|
||||
IconSource="{x:Bind mtu:IconPathConverter.IconSourceWUX(EvaluatedIcon), Mode=OneTime}" />
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{x:Bind Name}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Button x:Name="AddProfileButton"
|
||||
x:Uid="NewTabMenu_AddProfileButton"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Click="AddProfileButton_Clicked">
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Add Separator -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_AddSeparator"
|
||||
FontIconGlyph="">
|
||||
<Button x:Name="AddSeparatorButton"
|
||||
x:Uid="NewTabMenu_AddSeparatorButton"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Click="AddSeparatorButton_Clicked">
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Add Folder -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_AddFolder"
|
||||
FontIconGlyph="">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="5">
|
||||
<TextBox x:Name="FolderNameTextBox"
|
||||
x:Uid="NewTabMenu_AddFolder_FolderName"
|
||||
MinWidth="{StaticResource StandardBoxMinWidth}"
|
||||
KeyDown="AddFolderNameTextBox_KeyDown"
|
||||
Text="{x:Bind ViewModel.AddFolderName, Mode=TwoWay}"
|
||||
TextChanged="AddFolderNameTextBox_TextChanged" />
|
||||
<Button x:Name="AddFolderButton"
|
||||
x:Uid="NewTabMenu_AddFolderButton"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Click="AddFolderButton_Clicked"
|
||||
IsEnabled="False">
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Add Match Profiles -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_AddMatchProfiles"
|
||||
FontIconGlyph=""
|
||||
Style="{StaticResource ExpanderSettingContainerStyle}">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBox x:Uid="NewTabMenu_AddMatchProfiles_Name"
|
||||
Text="{x:Bind ViewModel.ProfileMatcherName, Mode=TwoWay}" />
|
||||
<TextBox x:Uid="NewTabMenu_AddMatchProfiles_Source"
|
||||
Text="{x:Bind ViewModel.ProfileMatcherSource, Mode=TwoWay}" />
|
||||
<TextBox x:Uid="NewTabMenu_AddMatchProfiles_Commandline"
|
||||
Text="{x:Bind ViewModel.ProfileMatcherCommandline, Mode=TwoWay}" />
|
||||
<Button x:Name="AddMatchProfilesButton"
|
||||
Click="AddMatchProfilesButton_Clicked">
|
||||
<Button.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="NewTabMenu_AddMatchProfilesTextBlock"
|
||||
Style="{StaticResource IconButtonTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Add Remaining Profiles -->
|
||||
<local:SettingContainer x:Uid="NewTabMenu_AddRemainingProfiles"
|
||||
FontIconGlyph="">
|
||||
<Button x:Name="AddRemainingProfilesButton"
|
||||
x:Uid="NewTabMenu_AddRemainingProfilesButton"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Click="AddRemainingProfilesButton_Clicked"
|
||||
IsEnabled="{x:Bind ViewModel.IsRemainingProfilesEntryMissing, Mode=OneWay}">
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</local:SettingContainer>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Page>
|
||||
812
src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.cpp
Normal file
812
src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.cpp
Normal file
@ -0,0 +1,812 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "NewTabMenuViewModel.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
#include "NewTabMenuViewModel.g.cpp"
|
||||
#include "FolderTreeViewEntry.g.cpp"
|
||||
#include "NewTabMenuEntryViewModel.g.cpp"
|
||||
#include "ProfileEntryViewModel.g.cpp"
|
||||
#include "ActionEntryViewModel.g.cpp"
|
||||
#include "SeparatorEntryViewModel.g.cpp"
|
||||
#include "FolderEntryViewModel.g.cpp"
|
||||
#include "MatchProfilesEntryViewModel.g.cpp"
|
||||
#include "RemainingProfilesEntryViewModel.g.cpp"
|
||||
|
||||
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;
|
||||
using namespace winrt::Windows::UI::Xaml::Data;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
static IObservableVector<Editor::NewTabMenuEntryViewModel> _ConvertToViewModelEntries(const IVector<Model::NewTabMenuEntry>& settingsModelEntries, const Model::CascadiaSettings& settings)
|
||||
{
|
||||
std::vector<Editor::NewTabMenuEntryViewModel> result{};
|
||||
if (!settingsModelEntries)
|
||||
{
|
||||
return single_threaded_observable_vector<Editor::NewTabMenuEntryViewModel>(std::move(result));
|
||||
}
|
||||
|
||||
for (const auto& entry : settingsModelEntries)
|
||||
{
|
||||
switch (entry.Type())
|
||||
{
|
||||
case NewTabMenuEntryType::Profile:
|
||||
{
|
||||
// If the Profile isn't set, this is an invalid entry. Skip it.
|
||||
if (const auto& profileEntry = entry.as<Model::ProfileEntry>(); profileEntry.Profile())
|
||||
{
|
||||
result.push_back(make<ProfileEntryViewModel>(profileEntry));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Action:
|
||||
{
|
||||
if (const auto& actionEntry = entry.as<Model::ActionEntry>())
|
||||
{
|
||||
result.push_back(make<ActionEntryViewModel>(actionEntry, settings));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Separator:
|
||||
{
|
||||
if (const auto& separatorEntry = entry.as<Model::SeparatorEntry>())
|
||||
{
|
||||
result.push_back(make<SeparatorEntryViewModel>(separatorEntry));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
if (const auto& folderEntry = entry.as<Model::FolderEntry>())
|
||||
{
|
||||
// The ctor will convert the children of the folder to view models
|
||||
result.push_back(make<FolderEntryViewModel>(folderEntry, settings));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
{
|
||||
if (const auto& matchProfilesEntry = entry.as<Model::MatchProfilesEntry>())
|
||||
{
|
||||
result.push_back(make<MatchProfilesEntryViewModel>(matchProfilesEntry));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
{
|
||||
if (const auto& remainingProfilesEntry = entry.as<Model::RemainingProfilesEntry>())
|
||||
{
|
||||
result.push_back(make<RemainingProfilesEntryViewModel>(remainingProfilesEntry));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Invalid:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return single_threaded_observable_vector<Editor::NewTabMenuEntryViewModel>(std::move(result));
|
||||
}
|
||||
|
||||
bool NewTabMenuViewModel::IsRemainingProfilesEntryMissing() const
|
||||
{
|
||||
return _IsRemainingProfilesEntryMissing(_rootEntries);
|
||||
}
|
||||
|
||||
bool NewTabMenuViewModel::_IsRemainingProfilesEntryMissing(const IVector<Editor::NewTabMenuEntryViewModel>& entries)
|
||||
{
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
switch (entry.Type())
|
||||
{
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
if (!_IsRemainingProfilesEntryMissing(entry.as<Editor::FolderEntryViewModel>().Entries()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NewTabMenuViewModel::IsFolderView() const noexcept
|
||||
{
|
||||
return _CurrentFolder != nullptr;
|
||||
}
|
||||
|
||||
NewTabMenuViewModel::NewTabMenuViewModel(Model::CascadiaSettings settings)
|
||||
{
|
||||
UpdateSettings(settings);
|
||||
|
||||
// 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"AvailableProfiles")
|
||||
{
|
||||
_NotifyChanges(L"SelectedProfile");
|
||||
}
|
||||
else if (viewModelProperty == L"CurrentFolder")
|
||||
{
|
||||
if (_CurrentFolder)
|
||||
{
|
||||
CurrentFolderName(_CurrentFolder.Name());
|
||||
_CurrentFolder.PropertyChanged({ this, &NewTabMenuViewModel::_FolderPropertyChanged });
|
||||
}
|
||||
_NotifyChanges(L"IsFolderView", L"CurrentView");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::_FolderPropertyChanged(const IInspectable& /*sender*/, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args)
|
||||
{
|
||||
const auto viewModelProperty{ args.PropertyName() };
|
||||
if (viewModelProperty == L"Name")
|
||||
{
|
||||
// FolderTree needs to be updated when a folder is renamed
|
||||
_folderTreeCache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
hstring NewTabMenuViewModel::CurrentFolderName() const
|
||||
{
|
||||
if (!_CurrentFolder)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return _CurrentFolder.Name();
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::CurrentFolderName(const hstring& value)
|
||||
{
|
||||
if (_CurrentFolder && _CurrentFolder.Name() != value)
|
||||
{
|
||||
_CurrentFolder.Name(value);
|
||||
_NotifyChanges(L"CurrentFolderName");
|
||||
}
|
||||
}
|
||||
|
||||
bool NewTabMenuViewModel::CurrentFolderInlining() const
|
||||
{
|
||||
if (!_CurrentFolder)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return _CurrentFolder.Inlining();
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::CurrentFolderInlining(bool value)
|
||||
{
|
||||
if (_CurrentFolder && _CurrentFolder.Inlining() != value)
|
||||
{
|
||||
_CurrentFolder.Inlining(value);
|
||||
_NotifyChanges(L"CurrentFolderInlining");
|
||||
}
|
||||
}
|
||||
|
||||
bool NewTabMenuViewModel::CurrentFolderAllowEmpty() const
|
||||
{
|
||||
if (!_CurrentFolder)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return _CurrentFolder.AllowEmpty();
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::CurrentFolderAllowEmpty(bool value)
|
||||
{
|
||||
if (_CurrentFolder && _CurrentFolder.AllowEmpty() != value)
|
||||
{
|
||||
_CurrentFolder.AllowEmpty(value);
|
||||
_NotifyChanges(L"CurrentFolderAllowEmpty");
|
||||
}
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel> NewTabMenuViewModel::CurrentView() const
|
||||
{
|
||||
if (!_CurrentFolder)
|
||||
{
|
||||
return _rootEntries;
|
||||
}
|
||||
return _CurrentFolder.Entries();
|
||||
}
|
||||
|
||||
static bool _FindFolderPathByName(const IVector<Editor::NewTabMenuEntryViewModel>& entries, const hstring& name, std::vector<Editor::FolderEntryViewModel>& result)
|
||||
{
|
||||
for (const auto& entry : entries)
|
||||
{
|
||||
if (const auto& folderVM = entry.try_as<Editor::FolderEntryViewModel>())
|
||||
{
|
||||
result.push_back(folderVM);
|
||||
if (folderVM.Name() == name)
|
||||
{
|
||||
// Found the folder
|
||||
return true;
|
||||
}
|
||||
else if (_FindFolderPathByName(folderVM.Entries(), name, result))
|
||||
{
|
||||
// Found the folder in the children of this folder
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This folder and its descendants are not the folder we're looking for
|
||||
result.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
IVector<Editor::FolderEntryViewModel> NewTabMenuViewModel::FindFolderPathByName(const hstring& name)
|
||||
{
|
||||
std::vector<Editor::FolderEntryViewModel> entries;
|
||||
_FindFolderPathByName(_rootEntries, name, entries);
|
||||
return single_threaded_vector<Editor::FolderEntryViewModel>(std::move(entries));
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::UpdateSettings(const Model::CascadiaSettings& settings)
|
||||
{
|
||||
_Settings = settings;
|
||||
_NotifyChanges(L"AvailableProfiles");
|
||||
|
||||
SelectedProfile(AvailableProfiles().GetAt(0));
|
||||
|
||||
_rootEntries = _ConvertToViewModelEntries(_Settings.GlobalSettings().NewTabMenu(), _Settings);
|
||||
_rootEntriesChangedRevoker = _rootEntries.VectorChanged(winrt::auto_revoke, [this](auto&&, const IVectorChangedEventArgs& args) {
|
||||
switch (args.CollectionChange())
|
||||
{
|
||||
case CollectionChange::Reset:
|
||||
{
|
||||
// fully replace settings model with view model structure
|
||||
std::vector<Model::NewTabMenuEntry> modelEntries;
|
||||
for (const auto& entry : _rootEntries)
|
||||
{
|
||||
modelEntries.push_back(NewTabMenuEntryViewModel::GetModel(entry));
|
||||
}
|
||||
_Settings.GlobalSettings().NewTabMenu(single_threaded_vector<Model::NewTabMenuEntry>(std::move(modelEntries)));
|
||||
return;
|
||||
}
|
||||
case CollectionChange::ItemInserted:
|
||||
{
|
||||
const auto& insertedEntryVM = _rootEntries.GetAt(args.Index());
|
||||
const auto& insertedEntry = NewTabMenuEntryViewModel::GetModel(insertedEntryVM);
|
||||
_Settings.GlobalSettings().NewTabMenu().InsertAt(args.Index(), insertedEntry);
|
||||
return;
|
||||
}
|
||||
case CollectionChange::ItemRemoved:
|
||||
{
|
||||
_Settings.GlobalSettings().NewTabMenu().RemoveAt(args.Index());
|
||||
return;
|
||||
}
|
||||
case CollectionChange::ItemChanged:
|
||||
{
|
||||
const auto& modifiedEntry = _rootEntries.GetAt(args.Index());
|
||||
_Settings.GlobalSettings().NewTabMenu().SetAt(args.Index(), NewTabMenuEntryViewModel::GetModel(modifiedEntry));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::RequestReorderEntry(const Editor::NewTabMenuEntryViewModel& vm, bool goingUp)
|
||||
{
|
||||
uint32_t idx;
|
||||
if (CurrentView().IndexOf(vm, idx))
|
||||
{
|
||||
if (goingUp && idx > 0)
|
||||
{
|
||||
CurrentView().RemoveAt(idx);
|
||||
CurrentView().InsertAt(idx - 1, vm);
|
||||
}
|
||||
else if (!goingUp && idx < CurrentView().Size() - 1)
|
||||
{
|
||||
CurrentView().RemoveAt(idx);
|
||||
CurrentView().InsertAt(idx + 1, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::RequestDeleteEntry(const Editor::NewTabMenuEntryViewModel& vm)
|
||||
{
|
||||
uint32_t idx;
|
||||
if (CurrentView().IndexOf(vm, idx))
|
||||
{
|
||||
CurrentView().RemoveAt(idx);
|
||||
|
||||
if (vm.try_as<Editor::FolderEntryViewModel>())
|
||||
{
|
||||
_folderTreeCache = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::RequestMoveEntriesToFolder(const Windows::Foundation::Collections::IVector<Editor::NewTabMenuEntryViewModel>& entries, const Editor::FolderEntryViewModel& destinationFolder)
|
||||
{
|
||||
auto destination{ destinationFolder == nullptr ? _rootEntries : destinationFolder.Entries() };
|
||||
for (auto&& e : entries)
|
||||
{
|
||||
// Don't move the folder into itself (just skip over it)
|
||||
if (e == destinationFolder)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove entry from the current layer,
|
||||
// and add it to the destination folder
|
||||
RequestDeleteEntry(e);
|
||||
destination.Append(e);
|
||||
}
|
||||
}
|
||||
|
||||
Editor::NewTabMenuEntryViewModel NewTabMenuViewModel::RequestAddSelectedProfileEntry()
|
||||
{
|
||||
if (_SelectedProfile)
|
||||
{
|
||||
Model::ProfileEntry profileEntry;
|
||||
profileEntry.Profile(_SelectedProfile);
|
||||
|
||||
const auto& entryVM = make<ProfileEntryViewModel>(profileEntry);
|
||||
CurrentView().Append(entryVM);
|
||||
_PrintAll();
|
||||
return entryVM;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Editor::NewTabMenuEntryViewModel NewTabMenuViewModel::RequestAddSeparatorEntry()
|
||||
{
|
||||
Model::SeparatorEntry separatorEntry;
|
||||
const auto& entryVM = make<SeparatorEntryViewModel>(separatorEntry);
|
||||
CurrentView().Append(entryVM);
|
||||
|
||||
_PrintAll();
|
||||
return entryVM;
|
||||
}
|
||||
|
||||
Editor::NewTabMenuEntryViewModel NewTabMenuViewModel::RequestAddFolderEntry()
|
||||
{
|
||||
Model::FolderEntry folderEntry;
|
||||
folderEntry.Name(_AddFolderName);
|
||||
|
||||
const auto& entryVM = make<FolderEntryViewModel>(folderEntry, _Settings);
|
||||
CurrentView().Append(entryVM);
|
||||
|
||||
// Reset state after adding the entry
|
||||
AddFolderName({});
|
||||
_folderTreeCache = nullptr;
|
||||
|
||||
_PrintAll();
|
||||
return entryVM;
|
||||
}
|
||||
|
||||
Editor::NewTabMenuEntryViewModel NewTabMenuViewModel::RequestAddProfileMatcherEntry()
|
||||
{
|
||||
Model::MatchProfilesEntry matchProfilesEntry;
|
||||
matchProfilesEntry.Name(_ProfileMatcherName);
|
||||
matchProfilesEntry.Source(_ProfileMatcherSource);
|
||||
matchProfilesEntry.Commandline(_ProfileMatcherCommandline);
|
||||
|
||||
const auto& entryVM = make<MatchProfilesEntryViewModel>(matchProfilesEntry);
|
||||
CurrentView().Append(entryVM);
|
||||
|
||||
// Clear the fields after adding the entry
|
||||
ProfileMatcherName({});
|
||||
ProfileMatcherSource({});
|
||||
ProfileMatcherCommandline({});
|
||||
|
||||
_PrintAll();
|
||||
return entryVM;
|
||||
}
|
||||
|
||||
Editor::NewTabMenuEntryViewModel NewTabMenuViewModel::RequestAddRemainingProfilesEntry()
|
||||
{
|
||||
Model::RemainingProfilesEntry remainingProfilesEntry;
|
||||
const auto& entryVM = make<RemainingProfilesEntryViewModel>(remainingProfilesEntry);
|
||||
CurrentView().Append(entryVM);
|
||||
|
||||
_NotifyChanges(L"IsRemainingProfilesEntryMissing");
|
||||
|
||||
_PrintAll();
|
||||
return entryVM;
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::GenerateFolderTree()
|
||||
{
|
||||
if (!_folderTreeCache)
|
||||
{
|
||||
// Add the root folder
|
||||
auto root = winrt::make<FolderTreeViewEntry>(nullptr);
|
||||
|
||||
for (const auto&& entry : _rootEntries)
|
||||
{
|
||||
if (entry.Type() == NewTabMenuEntryType::Folder)
|
||||
{
|
||||
root.Children().Append(winrt::make<FolderTreeViewEntry>(entry.as<Editor::FolderEntryViewModel>()));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Editor::FolderTreeViewEntry> folderTreeCache;
|
||||
folderTreeCache.emplace_back(std::move(root));
|
||||
_folderTreeCache = single_threaded_observable_vector<Editor::FolderTreeViewEntry>(std::move(folderTreeCache));
|
||||
|
||||
_NotifyChanges(L"FolderTree");
|
||||
}
|
||||
}
|
||||
|
||||
Collections::IObservableVector<Editor::FolderTreeViewEntry> NewTabMenuViewModel::FolderTree() const
|
||||
{
|
||||
// We could do this...
|
||||
// if (!_folderTreeCache){ GenerateFolderTree(); }
|
||||
// But FolderTree() gets called when we open the page.
|
||||
// Instead, we generate the tree as needed using GenerateFolderTree()
|
||||
// which caches the tree.
|
||||
return _folderTreeCache;
|
||||
}
|
||||
|
||||
// This recursively constructs the FolderTree
|
||||
FolderTreeViewEntry::FolderTreeViewEntry(Editor::FolderEntryViewModel folderEntry) :
|
||||
_folderEntry{ folderEntry },
|
||||
_Children{ single_threaded_observable_vector<Editor::FolderTreeViewEntry>() }
|
||||
{
|
||||
if (!_folderEntry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto&& entry : _folderEntry.Entries())
|
||||
{
|
||||
if (entry.Type() == NewTabMenuEntryType::Folder)
|
||||
{
|
||||
_Children.Append(winrt::make<FolderTreeViewEntry>(entry.as<Editor::FolderEntryViewModel>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hstring FolderTreeViewEntry::Name() const
|
||||
{
|
||||
if (!_folderEntry)
|
||||
{
|
||||
return RS_(L"NewTabMenu_RootFolderName");
|
||||
}
|
||||
return _folderEntry.Name();
|
||||
}
|
||||
|
||||
hstring FolderTreeViewEntry::Icon() const
|
||||
{
|
||||
if (!_folderEntry)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return _folderEntry.Icon();
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::_PrintAll()
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
OutputDebugString(L"---Model:---\n");
|
||||
_PrintModel(_Settings.GlobalSettings().NewTabMenu());
|
||||
OutputDebugString(L"\n");
|
||||
OutputDebugString(L"---VM:---\n");
|
||||
_PrintVM(_rootEntries);
|
||||
OutputDebugString(L"\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
void NewTabMenuViewModel::_PrintModel(Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> list, std::wstring prefix)
|
||||
{
|
||||
if (!list)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto&& e : list)
|
||||
{
|
||||
_PrintModel(e, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::_PrintModel(const Model::NewTabMenuEntry& e, std::wstring prefix)
|
||||
{
|
||||
switch (e.Type())
|
||||
{
|
||||
case NewTabMenuEntryType::Profile:
|
||||
{
|
||||
const auto& pe = e.as<Model::ProfileEntry>();
|
||||
OutputDebugString(fmt::format(L"{}Profile: {}\n", prefix, pe.Profile().Name()).c_str());
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Action:
|
||||
{
|
||||
const auto& actionEntry = e.as<Model::ActionEntry>();
|
||||
OutputDebugString(fmt::format(L"{}Action: {}\n", prefix, actionEntry.ActionId()).c_str());
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Separator:
|
||||
{
|
||||
OutputDebugString(fmt::format(L"{}Separator\n", prefix).c_str());
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
const auto& fe = e.as<Model::FolderEntry>();
|
||||
OutputDebugString(fmt::format(L"{}Folder: {}\n", prefix, fe.Name()).c_str());
|
||||
_PrintModel(fe.RawEntries(), prefix + L" ");
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
{
|
||||
const auto& matchProfilesEntry = e.as<Model::MatchProfilesEntry>();
|
||||
OutputDebugString(fmt::format(L"{}MatchProfiles: {}\n", prefix, matchProfilesEntry.Name()).c_str());
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
{
|
||||
OutputDebugString(fmt::format(L"{}RemainingProfiles\n", prefix).c_str());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::_PrintVM(Windows::Foundation::Collections::IVector<Editor::NewTabMenuEntryViewModel> list, std::wstring prefix)
|
||||
{
|
||||
if (!list)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto&& e : list)
|
||||
{
|
||||
_PrintVM(e, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
void NewTabMenuViewModel::_PrintVM(const Editor::NewTabMenuEntryViewModel& e, std::wstring prefix)
|
||||
{
|
||||
switch (e.Type())
|
||||
{
|
||||
case NewTabMenuEntryType::Profile:
|
||||
{
|
||||
const auto& pe = e.as<Editor::ProfileEntryViewModel>();
|
||||
OutputDebugString(fmt::format(L"{}Profile: {}\n", prefix, pe.ProfileEntry().Profile().Name()).c_str());
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Action:
|
||||
{
|
||||
const auto& actionEntry = e.as<Editor::ActionEntryViewModel>();
|
||||
OutputDebugString(fmt::format(L"{}Action: {}\n", prefix, actionEntry.ActionEntry().ActionId()).c_str());
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Separator:
|
||||
{
|
||||
OutputDebugString(fmt::format(L"{}Separator\n", prefix).c_str());
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
const auto& fe = e.as<Editor::FolderEntryViewModel>();
|
||||
OutputDebugString(fmt::format(L"{}Folder: {}\n", prefix, fe.Name()).c_str());
|
||||
_PrintVM(fe.Entries(), prefix + L" ");
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
{
|
||||
const auto& matchProfilesEntry = e.as<Editor::MatchProfilesEntryViewModel>();
|
||||
OutputDebugString(fmt::format(L"{}MatchProfiles: {}\n", prefix, matchProfilesEntry.DisplayText()).c_str());
|
||||
break;
|
||||
}
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
{
|
||||
OutputDebugString(fmt::format(L"{}RemainingProfiles\n", prefix).c_str());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
NewTabMenuEntryViewModel::NewTabMenuEntryViewModel(const NewTabMenuEntryType type) noexcept :
|
||||
_Type{ type }
|
||||
{
|
||||
}
|
||||
|
||||
Model::NewTabMenuEntry NewTabMenuEntryViewModel::GetModel(const Editor::NewTabMenuEntryViewModel& viewModel)
|
||||
{
|
||||
switch (viewModel.Type())
|
||||
{
|
||||
case NewTabMenuEntryType::Profile:
|
||||
{
|
||||
const auto& projVM = viewModel.as<Editor::ProfileEntryViewModel>();
|
||||
return get_self<ProfileEntryViewModel>(projVM)->ProfileEntry();
|
||||
}
|
||||
case NewTabMenuEntryType::Action:
|
||||
{
|
||||
const auto& projVM = viewModel.as<Editor::ActionEntryViewModel>();
|
||||
return get_self<ActionEntryViewModel>(projVM)->ActionEntry();
|
||||
}
|
||||
case NewTabMenuEntryType::Separator:
|
||||
{
|
||||
const auto& projVM = viewModel.as<Editor::SeparatorEntryViewModel>();
|
||||
return get_self<SeparatorEntryViewModel>(projVM)->SeparatorEntry();
|
||||
}
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
const auto& projVM = viewModel.as<Editor::FolderEntryViewModel>();
|
||||
return get_self<FolderEntryViewModel>(projVM)->FolderEntry();
|
||||
}
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
{
|
||||
const auto& projVM = viewModel.as<Editor::MatchProfilesEntryViewModel>();
|
||||
return get_self<MatchProfilesEntryViewModel>(projVM)->MatchProfilesEntry();
|
||||
}
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
{
|
||||
const auto& projVM = viewModel.as<Editor::RemainingProfilesEntryViewModel>();
|
||||
return get_self<RemainingProfilesEntryViewModel>(projVM)->RemainingProfilesEntry();
|
||||
}
|
||||
case NewTabMenuEntryType::Invalid:
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ProfileEntryViewModel::ProfileEntryViewModel(Model::ProfileEntry profileEntry) :
|
||||
ProfileEntryViewModelT<ProfileEntryViewModel, NewTabMenuEntryViewModel>(Model::NewTabMenuEntryType::Profile),
|
||||
_ProfileEntry{ profileEntry }
|
||||
{
|
||||
}
|
||||
|
||||
ActionEntryViewModel::ActionEntryViewModel(Model::ActionEntry actionEntry, Model::CascadiaSettings settings) :
|
||||
ActionEntryViewModelT<ActionEntryViewModel, NewTabMenuEntryViewModel>(Model::NewTabMenuEntryType::Action),
|
||||
_ActionEntry{ actionEntry },
|
||||
_Settings{ settings }
|
||||
{
|
||||
}
|
||||
|
||||
hstring ActionEntryViewModel::DisplayText() const
|
||||
{
|
||||
assert(_Settings);
|
||||
|
||||
const auto actionID = _ActionEntry.ActionId();
|
||||
if (const auto& action = _Settings.ActionMap().GetActionByID(actionID))
|
||||
{
|
||||
return action.Name();
|
||||
}
|
||||
return hstring{ fmt::format(L"{}: {}", RS_(L"NewTabMenu_ActionNotFound"), actionID) };
|
||||
}
|
||||
|
||||
hstring ActionEntryViewModel::Icon() const
|
||||
{
|
||||
assert(_Settings);
|
||||
|
||||
const auto actionID = _ActionEntry.ActionId();
|
||||
if (const auto& action = _Settings.ActionMap().GetActionByID(actionID))
|
||||
{
|
||||
return action.IconPath();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
SeparatorEntryViewModel::SeparatorEntryViewModel(Model::SeparatorEntry separatorEntry) :
|
||||
SeparatorEntryViewModelT<SeparatorEntryViewModel, NewTabMenuEntryViewModel>(Model::NewTabMenuEntryType::Separator),
|
||||
_SeparatorEntry{ separatorEntry }
|
||||
{
|
||||
}
|
||||
|
||||
FolderEntryViewModel::FolderEntryViewModel(Model::FolderEntry folderEntry) :
|
||||
FolderEntryViewModel(folderEntry, nullptr) {}
|
||||
|
||||
FolderEntryViewModel::FolderEntryViewModel(Model::FolderEntry folderEntry, Model::CascadiaSettings settings) :
|
||||
FolderEntryViewModelT<FolderEntryViewModel, NewTabMenuEntryViewModel>(Model::NewTabMenuEntryType::Folder),
|
||||
_FolderEntry{ folderEntry },
|
||||
_Settings{ settings }
|
||||
{
|
||||
_Entries = _ConvertToViewModelEntries(_FolderEntry.RawEntries(), _Settings);
|
||||
|
||||
_entriesChangedRevoker = _Entries.VectorChanged(winrt::auto_revoke, [this](auto&&, const IVectorChangedEventArgs& args) {
|
||||
switch (args.CollectionChange())
|
||||
{
|
||||
case CollectionChange::Reset:
|
||||
{
|
||||
// fully replace settings model with _Entries
|
||||
std::vector<Model::NewTabMenuEntry> modelEntries;
|
||||
for (const auto& entry : _Entries)
|
||||
{
|
||||
modelEntries.push_back(NewTabMenuEntryViewModel::GetModel(entry));
|
||||
}
|
||||
_FolderEntry.RawEntries(single_threaded_vector<Model::NewTabMenuEntry>(std::move(modelEntries)));
|
||||
return;
|
||||
}
|
||||
case CollectionChange::ItemInserted:
|
||||
{
|
||||
const auto& insertedEntryVM = _Entries.GetAt(args.Index());
|
||||
const auto& insertedEntry = NewTabMenuEntryViewModel::GetModel(insertedEntryVM);
|
||||
if (!_FolderEntry.RawEntries())
|
||||
{
|
||||
_FolderEntry.RawEntries(single_threaded_vector<Model::NewTabMenuEntry>());
|
||||
}
|
||||
_FolderEntry.RawEntries().InsertAt(args.Index(), insertedEntry);
|
||||
return;
|
||||
}
|
||||
case CollectionChange::ItemRemoved:
|
||||
{
|
||||
_FolderEntry.RawEntries().RemoveAt(args.Index());
|
||||
return;
|
||||
}
|
||||
case CollectionChange::ItemChanged:
|
||||
{
|
||||
const auto& modifiedEntry = _Entries.GetAt(args.Index());
|
||||
_FolderEntry.RawEntries().SetAt(args.Index(), NewTabMenuEntryViewModel::GetModel(modifiedEntry));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool FolderEntryViewModel::Inlining() const
|
||||
{
|
||||
return _FolderEntry.Inlining() == FolderEntryInlining::Auto;
|
||||
}
|
||||
|
||||
void FolderEntryViewModel::Inlining(bool value)
|
||||
{
|
||||
const auto valueAsEnum = value ? FolderEntryInlining::Auto : FolderEntryInlining::Never;
|
||||
if (_FolderEntry.Inlining() != valueAsEnum)
|
||||
{
|
||||
_FolderEntry.Inlining(valueAsEnum);
|
||||
_NotifyChanges(L"Inlining");
|
||||
}
|
||||
};
|
||||
|
||||
MatchProfilesEntryViewModel::MatchProfilesEntryViewModel(Model::MatchProfilesEntry matchProfilesEntry) :
|
||||
MatchProfilesEntryViewModelT<MatchProfilesEntryViewModel, NewTabMenuEntryViewModel>(Model::NewTabMenuEntryType::MatchProfiles),
|
||||
_MatchProfilesEntry{ matchProfilesEntry }
|
||||
{
|
||||
}
|
||||
|
||||
hstring MatchProfilesEntryViewModel::DisplayText() const
|
||||
{
|
||||
std::wstring displayText;
|
||||
if (const auto profileName = _MatchProfilesEntry.Name(); !profileName.empty())
|
||||
{
|
||||
fmt::format_to(std::back_inserter(displayText), FMT_COMPILE(L"profile: {}, "), profileName);
|
||||
}
|
||||
if (const auto commandline = _MatchProfilesEntry.Commandline(); !commandline.empty())
|
||||
{
|
||||
fmt::format_to(std::back_inserter(displayText), FMT_COMPILE(L"commandline: {}, "), commandline);
|
||||
}
|
||||
if (const auto source = _MatchProfilesEntry.Source(); !source.empty())
|
||||
{
|
||||
fmt::format_to(std::back_inserter(displayText), FMT_COMPILE(L"source: {}, "), source);
|
||||
}
|
||||
|
||||
// Chop off the last ", "
|
||||
displayText.resize(displayText.size() - 2);
|
||||
return winrt::hstring{ displayText };
|
||||
}
|
||||
|
||||
RemainingProfilesEntryViewModel::RemainingProfilesEntryViewModel(Model::RemainingProfilesEntry remainingProfilesEntry) :
|
||||
RemainingProfilesEntryViewModelT<RemainingProfilesEntryViewModel, NewTabMenuEntryViewModel>(Model::NewTabMenuEntryType::RemainingProfiles),
|
||||
_RemainingProfilesEntry{ remainingProfilesEntry }
|
||||
{
|
||||
}
|
||||
}
|
||||
183
src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.h
Normal file
183
src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.h
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NewTabMenuViewModel.g.h"
|
||||
#include "FolderTreeViewEntry.g.h"
|
||||
#include "NewTabMenuEntryViewModel.g.h"
|
||||
#include "ProfileEntryViewModel.g.h"
|
||||
#include "ActionEntryViewModel.g.h"
|
||||
#include "SeparatorEntryViewModel.g.h"
|
||||
#include "FolderEntryViewModel.g.h"
|
||||
#include "MatchProfilesEntryViewModel.g.h"
|
||||
#include "RemainingProfilesEntryViewModel.g.h"
|
||||
|
||||
#include "ProfileViewModel.h"
|
||||
#include "ViewModelHelpers.h"
|
||||
#include "Utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct NewTabMenuViewModel : NewTabMenuViewModelT<NewTabMenuViewModel>, ViewModelHelper<NewTabMenuViewModel>
|
||||
{
|
||||
public:
|
||||
NewTabMenuViewModel(Model::CascadiaSettings settings);
|
||||
void UpdateSettings(const Model::CascadiaSettings& settings);
|
||||
void GenerateFolderTree();
|
||||
Windows::Foundation::Collections::IVector<Editor::FolderEntryViewModel> FindFolderPathByName(const hstring& name);
|
||||
|
||||
bool IsRemainingProfilesEntryMissing() const;
|
||||
bool IsFolderView() const noexcept;
|
||||
|
||||
void RequestReorderEntry(const Editor::NewTabMenuEntryViewModel& vm, bool goingUp);
|
||||
void RequestDeleteEntry(const Editor::NewTabMenuEntryViewModel& vm);
|
||||
void RequestMoveEntriesToFolder(const Windows::Foundation::Collections::IVector<Editor::NewTabMenuEntryViewModel>& entries, const Editor::FolderEntryViewModel& destinationFolder);
|
||||
|
||||
Editor::NewTabMenuEntryViewModel RequestAddSelectedProfileEntry();
|
||||
Editor::NewTabMenuEntryViewModel RequestAddSeparatorEntry();
|
||||
Editor::NewTabMenuEntryViewModel RequestAddFolderEntry();
|
||||
Editor::NewTabMenuEntryViewModel RequestAddProfileMatcherEntry();
|
||||
Editor::NewTabMenuEntryViewModel RequestAddRemainingProfilesEntry();
|
||||
|
||||
hstring CurrentFolderName() const;
|
||||
void CurrentFolderName(const hstring& value);
|
||||
bool CurrentFolderInlining() const;
|
||||
void CurrentFolderInlining(bool value);
|
||||
bool CurrentFolderAllowEmpty() const;
|
||||
void CurrentFolderAllowEmpty(bool value);
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<Model::Profile> AvailableProfiles() const { return _Settings.AllProfiles(); }
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FolderTreeViewEntry> FolderTree() const;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel> CurrentView() const;
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::FolderEntryViewModel, CurrentFolder, nullptr);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::FolderTreeViewEntry, CurrentFolderTreeViewSelectedItem, nullptr);
|
||||
|
||||
// Bound to the UI to create new entries
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Model::Profile, SelectedProfile, nullptr);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, ProfileMatcherName);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, ProfileMatcherSource);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, ProfileMatcherCommandline);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, AddFolderName);
|
||||
|
||||
private:
|
||||
Model::CascadiaSettings _Settings{ nullptr };
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel> _rootEntries;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::FolderTreeViewEntry> _folderTreeCache;
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel>::VectorChanged_revoker _rootEntriesChangedRevoker;
|
||||
|
||||
static bool _IsRemainingProfilesEntryMissing(const Windows::Foundation::Collections::IVector<Editor::NewTabMenuEntryViewModel>& entries);
|
||||
void _FolderPropertyChanged(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
|
||||
|
||||
void _PrintAll();
|
||||
#ifdef _DEBUG
|
||||
void _PrintModel(Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> list, std::wstring prefix = L"");
|
||||
void _PrintModel(const Model::NewTabMenuEntry& e, std::wstring prefix = L"");
|
||||
void _PrintVM(Windows::Foundation::Collections::IVector<Editor::NewTabMenuEntryViewModel> list, std::wstring prefix = L"");
|
||||
void _PrintVM(const Editor::NewTabMenuEntryViewModel& vm, std::wstring prefix = L"");
|
||||
#endif
|
||||
};
|
||||
|
||||
struct FolderTreeViewEntry : FolderTreeViewEntryT<FolderTreeViewEntry>
|
||||
{
|
||||
public:
|
||||
FolderTreeViewEntry(Editor::FolderEntryViewModel folderEntry);
|
||||
|
||||
hstring Name() const;
|
||||
hstring Icon() const;
|
||||
Editor::FolderEntryViewModel FolderEntryVM() { return _folderEntry; }
|
||||
|
||||
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Microsoft::Terminal::Settings::Editor::FolderTreeViewEntry>, Children);
|
||||
|
||||
private:
|
||||
Editor::FolderEntryViewModel _folderEntry;
|
||||
};
|
||||
|
||||
struct NewTabMenuEntryViewModel : NewTabMenuEntryViewModelT<NewTabMenuEntryViewModel>, ViewModelHelper<NewTabMenuEntryViewModel>
|
||||
{
|
||||
public:
|
||||
static Model::NewTabMenuEntry GetModel(const Editor::NewTabMenuEntryViewModel& viewModel);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Model::NewTabMenuEntryType, Type, Model::NewTabMenuEntryType::Invalid);
|
||||
|
||||
protected:
|
||||
explicit NewTabMenuEntryViewModel(const Model::NewTabMenuEntryType type) noexcept;
|
||||
};
|
||||
|
||||
struct ProfileEntryViewModel : ProfileEntryViewModelT<ProfileEntryViewModel, NewTabMenuEntryViewModel>
|
||||
{
|
||||
public:
|
||||
ProfileEntryViewModel(Model::ProfileEntry profileEntry);
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Model::ProfileEntry, ProfileEntry, nullptr);
|
||||
};
|
||||
|
||||
struct ActionEntryViewModel : ActionEntryViewModelT<ActionEntryViewModel, NewTabMenuEntryViewModel>
|
||||
{
|
||||
public:
|
||||
ActionEntryViewModel(Model::ActionEntry actionEntry, Model::CascadiaSettings settings);
|
||||
|
||||
hstring DisplayText() const;
|
||||
hstring Icon() const;
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Model::ActionEntry, ActionEntry, nullptr);
|
||||
|
||||
private:
|
||||
Model::CascadiaSettings _Settings;
|
||||
};
|
||||
|
||||
struct SeparatorEntryViewModel : SeparatorEntryViewModelT<SeparatorEntryViewModel, NewTabMenuEntryViewModel>
|
||||
{
|
||||
public:
|
||||
SeparatorEntryViewModel(Model::SeparatorEntry separatorEntry);
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Model::SeparatorEntry, SeparatorEntry, nullptr);
|
||||
};
|
||||
|
||||
struct FolderEntryViewModel : FolderEntryViewModelT<FolderEntryViewModel, NewTabMenuEntryViewModel>
|
||||
{
|
||||
public:
|
||||
FolderEntryViewModel(Model::FolderEntry folderEntry, Model::CascadiaSettings settings);
|
||||
explicit FolderEntryViewModel(Model::FolderEntry folderEntry);
|
||||
|
||||
bool Inlining() const;
|
||||
void Inlining(bool value);
|
||||
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, Name);
|
||||
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, Icon);
|
||||
GETSET_OBSERVABLE_PROJECTED_SETTING(_FolderEntry, AllowEmpty);
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel>, Entries);
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Model::FolderEntry, FolderEntry, nullptr);
|
||||
|
||||
private:
|
||||
Windows::Foundation::Collections::IObservableVector<Editor::NewTabMenuEntryViewModel>::VectorChanged_revoker _entriesChangedRevoker;
|
||||
Model::CascadiaSettings _Settings;
|
||||
};
|
||||
|
||||
struct MatchProfilesEntryViewModel : MatchProfilesEntryViewModelT<MatchProfilesEntryViewModel, NewTabMenuEntryViewModel>
|
||||
{
|
||||
public:
|
||||
MatchProfilesEntryViewModel(Model::MatchProfilesEntry matchProfilesEntry);
|
||||
|
||||
hstring DisplayText() const;
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Model::MatchProfilesEntry, MatchProfilesEntry, nullptr);
|
||||
};
|
||||
|
||||
struct RemainingProfilesEntryViewModel : RemainingProfilesEntryViewModelT<RemainingProfilesEntryViewModel, NewTabMenuEntryViewModel>
|
||||
{
|
||||
public:
|
||||
RemainingProfilesEntryViewModel(Model::RemainingProfilesEntry remainingProfilesEntry);
|
||||
|
||||
VIEW_MODEL_OBSERVABLE_PROPERTY(Model::RemainingProfilesEntry, RemainingProfilesEntry, nullptr);
|
||||
};
|
||||
};
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(NewTabMenuViewModel);
|
||||
BASIC_FACTORY(FolderTreeViewEntry);
|
||||
BASIC_FACTORY(ProfileEntryViewModel);
|
||||
BASIC_FACTORY(ActionEntryViewModel);
|
||||
BASIC_FACTORY(SeparatorEntryViewModel);
|
||||
BASIC_FACTORY(FolderEntryViewModel);
|
||||
BASIC_FACTORY(MatchProfilesEntryViewModel);
|
||||
BASIC_FACTORY(RemainingProfilesEntryViewModel);
|
||||
}
|
||||
103
src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.idl
Normal file
103
src/cascadia/TerminalSettingsEditor/NewTabMenuViewModel.idl
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "ProfileViewModel.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
[default_interface] runtimeclass FolderTreeViewEntry
|
||||
{
|
||||
FolderTreeViewEntry(FolderEntryViewModel folderEntry);
|
||||
|
||||
String Name { get; };
|
||||
String Icon { get; };
|
||||
FolderEntryViewModel FolderEntryVM { get; };
|
||||
|
||||
IObservableVector<Microsoft.Terminal.Settings.Editor.FolderTreeViewEntry> Children { get; };
|
||||
}
|
||||
|
||||
runtimeclass NewTabMenuViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
NewTabMenuViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
void GenerateFolderTree();
|
||||
Windows.Foundation.Collections.IVector<FolderEntryViewModel> FindFolderPathByName(String name);
|
||||
|
||||
FolderEntryViewModel CurrentFolder;
|
||||
Boolean IsFolderView { get; };
|
||||
FolderTreeViewEntry CurrentFolderTreeViewSelectedItem;
|
||||
Boolean IsRemainingProfilesEntryMissing { get; };
|
||||
|
||||
IObservableVector<NewTabMenuEntryViewModel> CurrentView { get; };
|
||||
IObservableVector<Microsoft.Terminal.Settings.Model.Profile> AvailableProfiles { get; };
|
||||
IObservableVector<FolderTreeViewEntry> FolderTree { get; };
|
||||
Microsoft.Terminal.Settings.Model.Profile SelectedProfile;
|
||||
|
||||
String CurrentFolderName;
|
||||
Boolean CurrentFolderInlining;
|
||||
Boolean CurrentFolderAllowEmpty;
|
||||
String ProfileMatcherName;
|
||||
String ProfileMatcherSource;
|
||||
String ProfileMatcherCommandline;
|
||||
String AddFolderName;
|
||||
|
||||
void RequestReorderEntry(NewTabMenuEntryViewModel vm, Boolean goingUp);
|
||||
void RequestDeleteEntry(NewTabMenuEntryViewModel vm);
|
||||
void RequestMoveEntriesToFolder(IVector<NewTabMenuEntryViewModel> entries, FolderEntryViewModel folderEntry);
|
||||
|
||||
NewTabMenuEntryViewModel RequestAddSelectedProfileEntry();
|
||||
NewTabMenuEntryViewModel RequestAddSeparatorEntry();
|
||||
NewTabMenuEntryViewModel RequestAddFolderEntry();
|
||||
NewTabMenuEntryViewModel RequestAddProfileMatcherEntry();
|
||||
NewTabMenuEntryViewModel RequestAddRemainingProfilesEntry();
|
||||
}
|
||||
|
||||
[default_interface] unsealed runtimeclass NewTabMenuEntryViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
Microsoft.Terminal.Settings.Model.NewTabMenuEntryType Type;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ProfileEntryViewModel : Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel
|
||||
{
|
||||
ProfileEntryViewModel(Microsoft.Terminal.Settings.Model.ProfileEntry profileEntry);
|
||||
|
||||
Microsoft.Terminal.Settings.Model.ProfileEntry ProfileEntry { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass ActionEntryViewModel : Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel
|
||||
{
|
||||
ActionEntryViewModel(Microsoft.Terminal.Settings.Model.ActionEntry actionEntry, Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
|
||||
Microsoft.Terminal.Settings.Model.ActionEntry ActionEntry { get; };
|
||||
String DisplayText { get; };
|
||||
String Icon { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass SeparatorEntryViewModel : Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel
|
||||
{
|
||||
SeparatorEntryViewModel(Microsoft.Terminal.Settings.Model.SeparatorEntry separatorEntry);
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass FolderEntryViewModel : Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel
|
||||
{
|
||||
FolderEntryViewModel(Microsoft.Terminal.Settings.Model.FolderEntry folderEntry, Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
|
||||
String Name;
|
||||
String Icon;
|
||||
Boolean Inlining;
|
||||
Boolean AllowEmpty;
|
||||
IObservableVector<Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel> Entries;
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass MatchProfilesEntryViewModel : Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel
|
||||
{
|
||||
MatchProfilesEntryViewModel(Microsoft.Terminal.Settings.Model.MatchProfilesEntry matchProfilesEntry);
|
||||
|
||||
String DisplayText { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass RemainingProfilesEntryViewModel : Microsoft.Terminal.Settings.Editor.NewTabMenuEntryViewModel
|
||||
{
|
||||
RemainingProfilesEntryViewModel(Microsoft.Terminal.Settings.Model.RemainingProfilesEntry remainingProfilesEntry);
|
||||
}
|
||||
}
|
||||
@ -1929,6 +1929,178 @@
|
||||
<value>Non-monospace fonts:</value>
|
||||
<comment>This is a label that is followed by a list of proportional fonts.</comment>
|
||||
</data>
|
||||
<data name="Nav_NewTabMenu.Content" xml:space="preserve">
|
||||
<value>New Tab Menu</value>
|
||||
<comment>Header for the "new tab menu" menu item. This navigates to a page that lets you see and modify settings related to the app's new tab menu (i.e. profile ordering, nested folders, dividers, etc.)</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_Separator.Text" xml:space="preserve">
|
||||
<value><Separator></value>
|
||||
<comment>{Locked="<"}, {Locked=">"} Text label for an entry that represents a visual separator in a list.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_RemainingProfiles.Text" xml:space="preserve">
|
||||
<value><Remaining profiles></value>
|
||||
<comment>{Locked="<"}{Locked=">"} Text label for an entry that represents inserting any remaining profiles that have not been inserted.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_RemainingProfilesItem.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value><Remaining profiles></value>
|
||||
<comment>{Locked="<"}{Locked=">"} Text label for an entry that represents inserting any remaining profiles that have not been inserted. Should match "NewTabMenuEntry_RemainingProfiles.Text".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddProfile.Header" xml:space="preserve">
|
||||
<value>Profile</value>
|
||||
<comment>Header for a control that adds a terminal profile to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles.Header" xml:space="preserve">
|
||||
<value>Profile matcher</value>
|
||||
<comment>Header for a control that adds a terminal profile matcher to the new tab menu. This entry adds profiles that match the given parameters.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddRemainingProfiles.Header" xml:space="preserve">
|
||||
<value>Remaining profiles</value>
|
||||
<comment>Header for a control that adds any remaining profiles to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles.HelpText" xml:space="preserve">
|
||||
<value>Add a group of profiles that match at least one of the defined properties</value>
|
||||
<comment>Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddRemainingProfiles.HelpText" xml:space="preserve">
|
||||
<value>There can only be one "remaining profiles" entry</value>
|
||||
<comment>Additional information for a control that adds any remaining profiles to the new tab menu. Presented near "NewTabMenu_AddRemainingProfiles".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddSeparator.Header" xml:space="preserve">
|
||||
<value>Separator</value>
|
||||
<comment>Header for a control that adds a separator to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddFolder.Header" xml:space="preserve">
|
||||
<value>Folder</value>
|
||||
<comment>Header for a control that adds a folder to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles_Name.Header" xml:space="preserve">
|
||||
<value>Profile name</value>
|
||||
<comment>Header for a text box used to define a regex for the names of profiles to add.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles_Source.Header" xml:space="preserve">
|
||||
<value>Profile source</value>
|
||||
<comment>Header for a text box used to define a regex for the sources of profiles to add.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles_Commandline.Header" xml:space="preserve">
|
||||
<value>Commandline</value>
|
||||
<comment>Header for a text box used to define a regex for the commandlines of profiles to add.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfilesTextBlock.Text" xml:space="preserve">
|
||||
<value>Add profile matcher</value>
|
||||
<comment>Label for a button confirming to add the profile matcher to the new tab menu as an entry.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddFolder_FolderName.PlaceholderText" xml:space="preserve">
|
||||
<value>Folder name</value>
|
||||
<comment>Placeholder text for a text box control used to set the name of the folder.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_DeleteMultipleTextBlock.Text" xml:space="preserve">
|
||||
<value>Delete selected entries</value>
|
||||
<comment>Label for a button that can be used to delete any new tab menu entries that are currently selected</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_MoveToFolderTextBlock.Text" xml:space="preserve">
|
||||
<value>Move selected entries to folder...</value>
|
||||
<comment>Label for a button that can be used to move any new tab menu entries that are currently selected into an existing folder</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_FolderPickerDialog.Title" xml:space="preserve">
|
||||
<value>Move to folder</value>
|
||||
<comment>Title displayed on a content dialog directing the user to pick a folder to move the selected entries to.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_FolderPickerDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
<comment>Button label for the folder picker content dialog. Used as confirmation to pick the selected folder.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_FolderPickerDialog.SecondaryButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
<comment>Text label for the secondary button on the folder picker content dialog. When clicked, the operation of picking a folder is cancelled by the user.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_RootFolderName" xml:space="preserve">
|
||||
<value><root></value>
|
||||
<comment>{Locked="<"}{Locked=">"} Text label for the name of the "root" folder. This is used to allow the user to select the root as a destination folder.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_CurrentFolderTextBlock.Text" xml:space="preserve">
|
||||
<value>Current Folder Properties</value>
|
||||
<comment>Header for a group of controls that can be used to modify the current folder entry's properties.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddEntriesTextBlock.Text" xml:space="preserve">
|
||||
<value>Add Entry</value>
|
||||
<comment>Header for a group of controls that can be used to add an entry to the new tab menu</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_CurrentFolderName.Header" xml:space="preserve">
|
||||
<value>Folder Name</value>
|
||||
<comment>Header for a control that allows the user to modify the name of the current folder entry.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_CurrentFolderInlining.Header" xml:space="preserve">
|
||||
<value>Allow inlining</value>
|
||||
<comment>Header for a control that allows the nested entries to be presented inline rather than with a folder.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_CurrentFolderInlining.HelpText" xml:space="preserve">
|
||||
<value>When enabled, if the folder only has a single entry, the entry will show directly and no folder will be rendered.</value>
|
||||
<comment>Additional text displayed near "NewTabMenu_CurrentFolderInlining.Header".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_CurrentFolderAllowEmpty.Header" xml:space="preserve">
|
||||
<value>Allow empty</value>
|
||||
<comment>Header for a control that allows the current folder entry to be empty.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_CurrentFolderAllowEmpty.HelpText" xml:space="preserve">
|
||||
<value>When enabled, if the folder has no entries, it will still be displayed. Otherwise, the folder will not be rendered.</value>
|
||||
<comment>Additional text displayed near "NewTabMenu_CurrentFolderAllowEmpty.Header".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_ActionNotFound" xml:space="preserve">
|
||||
<value>Action ID not found</value>
|
||||
<comment>Displayed text for an entry who's action identifier wasn't found. The action ID is presented in the format "Action ID not found: <actionID>"</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_ReorderUp.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Move up</value>
|
||||
<comment>Accessible name for a button that reorders the entry to be moved up when clicked. Should match "NewTabMenuEntry_ReorderUp.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_ReorderUp.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Move up</value>
|
||||
<comment>Accessible name for a button that reorders the entry to be moved up when clicked. Should match "NewTabMenuEntry_ReorderUp.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_ReorderDown.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Move down</value>
|
||||
<comment>Accessible name for a button that reorders the entry to be moved down when clicked. Should match "NewTabMenuEntry_ReorderDown.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_EditFolder.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Edit folder</value>
|
||||
<comment>Accessible name for a button that begins editing the folder when clicked. Should match "NewTabMenuEntry_EditFolder.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_ReorderDown.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Move down</value>
|
||||
<comment>Accessible name for a button that reorders the entry to be moved down when clicked. Should match "NewTabMenuEntry_ReorderDown.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_EditFolder.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Edit folder</value>
|
||||
<comment>Accessible name for a button that begins editing the folder when clicked. Should match "NewTabMenuEntry_EditFolder.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_Delete.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
<comment>Accessible name for a button that deletes the entry when clicked. Should match "NewTabMenuEntry_Delete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip"</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_Delete.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
<comment>Accessible name for a button that deletes the entry when clicked. Should match "NewTabMenuEntry_Delete.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name"</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddProfileButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Add selected profile</value>
|
||||
<comment>Tooltip for a button that adds the selected profile to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddSeparatorButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Add separator</value>
|
||||
<comment>Tooltip for a button that adds a separator to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddFolderButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Add folder</value>
|
||||
<comment>Tooltip for a button that adds a folder to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddRemainingProfilesButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Add remaining profiles</value>
|
||||
<comment>Tooltip for a button that adds an entry that represents the remaining profiles to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenuEntry_SeparatorItem.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value><Separator></value>
|
||||
<comment>{Locked="<"}{Locked=">"}Accessible name for an entry that represents a visual separator in a list. Should match "NewTabMenuEntry_Separator.Text".</comment>
|
||||
</data>
|
||||
<data name="Profile_AnswerbackMessage.Header" xml:space="preserve">
|
||||
<value>ENQ (Request Terminal Status) response</value>
|
||||
<comment>{Locked=ENQ}{Locked="Request Terminal Status"} Header for a control to determine the response to the ENQ escape sequence. This is represented using a text box.</comment>
|
||||
|
||||
@ -12,6 +12,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
DependencyProperty SettingContainer::_HeaderProperty{ nullptr };
|
||||
DependencyProperty SettingContainer::_HelpTextProperty{ nullptr };
|
||||
DependencyProperty SettingContainer::_FontIconGlyphProperty{ nullptr };
|
||||
DependencyProperty SettingContainer::_CurrentValueProperty{ nullptr };
|
||||
DependencyProperty SettingContainer::_HasSettingValueProperty{ nullptr };
|
||||
DependencyProperty SettingContainer::_SettingOverrideSourceProperty{ nullptr };
|
||||
@ -45,6 +46,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
xaml_typename<Editor::SettingContainer>(),
|
||||
PropertyMetadata{ box_value(L"") });
|
||||
}
|
||||
if (!_FontIconGlyphProperty)
|
||||
{
|
||||
_FontIconGlyphProperty =
|
||||
DependencyProperty::Register(
|
||||
L"FontIconGlyph",
|
||||
xaml_typename<hstring>(),
|
||||
xaml_typename<Editor::SettingContainer>(),
|
||||
PropertyMetadata{ box_value(L"") });
|
||||
}
|
||||
if (!_CurrentValueProperty)
|
||||
{
|
||||
_CurrentValueProperty =
|
||||
|
||||
@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, Header);
|
||||
DEPENDENCY_PROPERTY(hstring, HelpText);
|
||||
DEPENDENCY_PROPERTY(hstring, FontIconGlyph);
|
||||
DEPENDENCY_PROPERTY(hstring, CurrentValue);
|
||||
DEPENDENCY_PROPERTY(bool, HasSettingValue);
|
||||
DEPENDENCY_PROPERTY(bool, StartExpanded);
|
||||
|
||||
@ -15,6 +15,9 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
String HelpText;
|
||||
static Windows.UI.Xaml.DependencyProperty HelpTextProperty { get; };
|
||||
|
||||
String FontIconGlyph;
|
||||
static Windows.UI.Xaml.DependencyProperty FontIconGlyphProperty { get; };
|
||||
|
||||
String CurrentValue;
|
||||
static Windows.UI.Xaml.DependencyProperty CurrentValueProperty { get; };
|
||||
|
||||
|
||||
@ -189,10 +189,15 @@
|
||||
<Grid AutomationProperties.Name="{TemplateBinding Header}"
|
||||
Style="{StaticResource NonExpanderGrid}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Style="{StaticResource StackPanelInExpanderStyle}">
|
||||
<FontIcon Grid.Column="0"
|
||||
Margin="0,0,10,0"
|
||||
Glyph="{TemplateBinding FontIconGlyph}" />
|
||||
<StackPanel Grid.Column="1"
|
||||
Style="{StaticResource StackPanelInExpanderStyle}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
|
||||
Text="{TemplateBinding Header}" />
|
||||
@ -206,7 +211,7 @@
|
||||
Style="{StaticResource SettingsPageItemDescriptionStyle}"
|
||||
Text="{TemplateBinding HelpText}" />
|
||||
</StackPanel>
|
||||
<ContentPresenter Grid.Column="1"
|
||||
<ContentPresenter Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Content="{TemplateBinding Content}" />
|
||||
@ -233,10 +238,15 @@
|
||||
<muxc:Expander.Header>
|
||||
<Grid MinHeight="64">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Style="{StaticResource StackPanelInExpanderStyle}">
|
||||
<FontIcon Grid.Column="0"
|
||||
Margin="0,0,10,0"
|
||||
Glyph="{TemplateBinding FontIconGlyph}" />
|
||||
<StackPanel Grid.Column="1"
|
||||
Style="{StaticResource StackPanelInExpanderStyle}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Style="{StaticResource SettingsPageItemHeaderStyle}"
|
||||
Text="{TemplateBinding Header}" />
|
||||
@ -250,7 +260,7 @@
|
||||
Style="{StaticResource SettingsPageItemDescriptionStyle}"
|
||||
Text="{TemplateBinding HelpText}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1"
|
||||
<TextBlock Grid.Column="2"
|
||||
MaxWidth="250"
|
||||
Margin="0,0,-16,0"
|
||||
HorizontalAlignment="Right"
|
||||
|
||||
@ -37,22 +37,24 @@ protected:
|
||||
winrt::event<::winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler> _propertyChangedHandlers;
|
||||
};
|
||||
|
||||
#define GETSET_OBSERVABLE_PROJECTED_SETTING(target, name) \
|
||||
public: \
|
||||
auto name() const \
|
||||
{ \
|
||||
return target.name(); \
|
||||
}; \
|
||||
template<typename T> \
|
||||
void name(const T& value) \
|
||||
{ \
|
||||
if (target.name() != value) \
|
||||
{ \
|
||||
target.name(value); \
|
||||
_NotifyChanges(L"Has" #name, L## #name); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define _BASE_OBSERVABLE_PROJECTED_SETTING(target, name) \
|
||||
public: \
|
||||
auto name() const \
|
||||
{ \
|
||||
return target.name(); \
|
||||
}; \
|
||||
template<typename T> \
|
||||
void name(const T& value) \
|
||||
{ \
|
||||
const auto t = target; \
|
||||
if (t.name() != value) \
|
||||
{ \
|
||||
t.name(value); \
|
||||
_NotifyChanges(L"Has" #name, L## #name); \
|
||||
} \
|
||||
} \
|
||||
GETSET_OBSERVABLE_PROJECTED_SETTING(target, name) \
|
||||
bool Has##name() const \
|
||||
{ \
|
||||
return target.Has##name(); \
|
||||
|
||||
@ -8,32 +8,43 @@
|
||||
#include "ActionEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
static constexpr std::string_view ActionIdKey{ "id" };
|
||||
static constexpr std::string_view IconKey{ "icon" };
|
||||
|
||||
ActionEntry::ActionEntry() noexcept :
|
||||
ActionEntryT<ActionEntry, NewTabMenuEntry>(NewTabMenuEntryType::Action)
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value ActionEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, ActionIdKey, _ActionId);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> ActionEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<ActionEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, ActionIdKey, entry->_ActionId);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
|
||||
return entry;
|
||||
|
||||
ActionEntry::ActionEntry() noexcept :
|
||||
ActionEntryT<ActionEntry, NewTabMenuEntry>(NewTabMenuEntryType::Action)
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value ActionEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, ActionIdKey, _ActionId);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> ActionEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<ActionEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, ActionIdKey, entry->_ActionId);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
Model::NewTabMenuEntry ActionEntry::Copy() const
|
||||
{
|
||||
auto entry = winrt::make_self<ActionEntry>();
|
||||
entry->_ActionId = _ActionId;
|
||||
entry->_Icon = _Icon;
|
||||
return *entry;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
public:
|
||||
ActionEntry() noexcept;
|
||||
|
||||
Model::NewTabMenuEntry Copy() const;
|
||||
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
#include "FolderEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
@ -18,107 +17,128 @@ static constexpr std::string_view EntriesKey{ "entries" };
|
||||
static constexpr std::string_view InliningKey{ "inline" };
|
||||
static constexpr std::string_view AllowEmptyKey{ "allowEmpty" };
|
||||
|
||||
FolderEntry::FolderEntry() noexcept :
|
||||
FolderEntry{ winrt::hstring{} }
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
}
|
||||
|
||||
FolderEntry::FolderEntry(const winrt::hstring& name) noexcept :
|
||||
FolderEntryT<FolderEntry, NewTabMenuEntry>(NewTabMenuEntryType::Folder),
|
||||
_Name{ name }
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value FolderEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, NameKey, _Name);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
JsonUtils::SetValueForKey(json, EntriesKey, _Entries);
|
||||
JsonUtils::SetValueForKey(json, InliningKey, _Inlining);
|
||||
JsonUtils::SetValueForKey(json, AllowEmptyKey, _AllowEmpty);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> FolderEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<FolderEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
JsonUtils::GetValueForKey(json, EntriesKey, entry->_Entries);
|
||||
JsonUtils::GetValueForKey(json, InliningKey, entry->_Inlining);
|
||||
JsonUtils::GetValueForKey(json, AllowEmptyKey, entry->_AllowEmpty);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
// A FolderEntry should only expose the entries to actually render to WinRT,
|
||||
// to keep the logic for collapsing/expanding more centralised.
|
||||
using NewTabMenuEntryModel = winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntry;
|
||||
IVector<NewTabMenuEntryModel> FolderEntry::Entries() const
|
||||
{
|
||||
// We filter the full list of entries from JSON to just include the
|
||||
// non-empty ones.
|
||||
IVector<NewTabMenuEntryModel> result{ winrt::single_threaded_vector<NewTabMenuEntryModel>() };
|
||||
if (_Entries == nullptr)
|
||||
FolderEntry::FolderEntry() noexcept :
|
||||
FolderEntry{ winrt::hstring{} }
|
||||
{
|
||||
}
|
||||
|
||||
FolderEntry::FolderEntry(const winrt::hstring& name) noexcept :
|
||||
FolderEntryT<FolderEntry, NewTabMenuEntry>(NewTabMenuEntryType::Folder),
|
||||
_Name{ name }
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value FolderEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, NameKey, _Name);
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
JsonUtils::SetValueForKey(json, EntriesKey, _RawEntries);
|
||||
JsonUtils::SetValueForKey(json, InliningKey, _Inlining);
|
||||
JsonUtils::SetValueForKey(json, AllowEmptyKey, _AllowEmpty);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> FolderEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<FolderEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
JsonUtils::GetValueForKey(json, EntriesKey, entry->_RawEntries);
|
||||
JsonUtils::GetValueForKey(json, InliningKey, entry->_Inlining);
|
||||
JsonUtils::GetValueForKey(json, AllowEmptyKey, entry->_AllowEmpty);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
// A FolderEntry should only expose the entries to actually render to WinRT,
|
||||
// to keep the logic for collapsing/expanding more centralised.
|
||||
IVector<Model::NewTabMenuEntry> FolderEntry::Entries() const
|
||||
{
|
||||
// We filter the full list of entries from JSON to just include the
|
||||
// non-empty ones.
|
||||
IVector<Model::NewTabMenuEntry> result{ winrt::single_threaded_vector<Model::NewTabMenuEntry>() };
|
||||
if (_RawEntries == nullptr)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto& entry : _RawEntries)
|
||||
{
|
||||
if (entry == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (entry.Type())
|
||||
{
|
||||
case NewTabMenuEntryType::Invalid:
|
||||
continue;
|
||||
|
||||
// A profile is filtered out if it is not valid, so if it was not resolved
|
||||
case NewTabMenuEntryType::Profile:
|
||||
{
|
||||
const auto profileEntry = entry.as<Model::ProfileEntry>();
|
||||
if (profileEntry.Profile() == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Any profile collection is filtered out if there are no results
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
{
|
||||
const auto profileCollectionEntry = entry.as<Model::ProfileCollectionEntry>();
|
||||
if (profileCollectionEntry.Profiles().Size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// A folder is filtered out if it has an effective size of 0 (calling
|
||||
// this filtering method recursively), and if it is not allowed to be
|
||||
// empty, or if it should auto-inline.
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
const auto folderEntry = entry.as<Model::FolderEntry>();
|
||||
if (folderEntry.Entries().Size() == 0 && (!folderEntry.AllowEmpty() || folderEntry.Inlining() == FolderEntryInlining::Auto))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.Append(entry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto& entry : _Entries)
|
||||
Model::NewTabMenuEntry FolderEntry::Copy() const
|
||||
{
|
||||
if (entry == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto entry = winrt::make_self<FolderEntry>();
|
||||
entry->_Name = _Name;
|
||||
entry->_Icon = _Icon;
|
||||
entry->_Inlining = _Inlining;
|
||||
entry->_AllowEmpty = _AllowEmpty;
|
||||
|
||||
switch (entry.Type())
|
||||
if (_RawEntries)
|
||||
{
|
||||
case NewTabMenuEntryType::Invalid:
|
||||
continue;
|
||||
|
||||
// A profile is filtered out if it is not valid, so if it was not resolved
|
||||
case NewTabMenuEntryType::Profile:
|
||||
{
|
||||
const auto profileEntry = entry.as<ProfileEntry>();
|
||||
if (profileEntry.Profile() == nullptr)
|
||||
entry->_RawEntries = winrt::single_threaded_vector<Model::NewTabMenuEntry>();
|
||||
for (const auto& e : _RawEntries)
|
||||
{
|
||||
continue;
|
||||
entry->_RawEntries.Append(get_self<NewTabMenuEntry>(e)->Copy());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Any profile collection is filtered out if there are no results
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
{
|
||||
const auto profileCollectionEntry = entry.as<ProfileCollectionEntry>();
|
||||
if (profileCollectionEntry.Profiles().Size() == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// A folder is filtered out if it has an effective size of 0 (calling
|
||||
// this filtering method recursively), and if it is not allowed to be
|
||||
// empty, or if it should auto-inline.
|
||||
case NewTabMenuEntryType::Folder:
|
||||
{
|
||||
const auto folderEntry = entry.as<Model::FolderEntry>();
|
||||
if (folderEntry.Entries().Size() == 0 && (!folderEntry.AllowEmpty() || folderEntry.Inlining() == FolderEntryInlining::Auto))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.Append(entry);
|
||||
return *entry;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -26,6 +26,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
FolderEntry() noexcept;
|
||||
explicit FolderEntry(const winrt::hstring& name) noexcept;
|
||||
|
||||
Model::NewTabMenuEntry Copy() const override;
|
||||
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
@ -34,18 +36,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
// Therefore, we will store the JSON entries list internally, and then expose only the
|
||||
// entries to be rendered to WinRT.
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> Entries() const;
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> RawEntries() const
|
||||
{
|
||||
return _Entries;
|
||||
};
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, Name);
|
||||
WINRT_PROPERTY(winrt::hstring, Icon);
|
||||
WINRT_PROPERTY(FolderEntryInlining, Inlining, FolderEntryInlining::Never);
|
||||
WINRT_PROPERTY(bool, AllowEmpty, false);
|
||||
|
||||
private:
|
||||
winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry> _Entries{};
|
||||
WINRT_PROPERTY(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, RawEntries);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -84,6 +84,14 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
|
||||
globals->_themes.Insert(kv.Key(), *themeImpl->Copy());
|
||||
}
|
||||
}
|
||||
if (_NewTabMenu)
|
||||
{
|
||||
globals->_NewTabMenu = winrt::single_threaded_vector<Model::NewTabMenuEntry>();
|
||||
for (const auto& entry : *_NewTabMenu)
|
||||
{
|
||||
globals->_NewTabMenu->Append(get_self<NewTabMenuEntry>(entry)->Copy());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
|
||||
@ -8,66 +8,77 @@
|
||||
#include "MatchProfilesEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view CommandlineKey{ "commandline" };
|
||||
static constexpr std::string_view SourceKey{ "source" };
|
||||
|
||||
MatchProfilesEntry::MatchProfilesEntry() noexcept :
|
||||
MatchProfilesEntryT<MatchProfilesEntry, ProfileCollectionEntry>(NewTabMenuEntryType::MatchProfiles)
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value MatchProfilesEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, NameKey, _Name);
|
||||
JsonUtils::SetValueForKey(json, CommandlineKey, _Commandline);
|
||||
JsonUtils::SetValueForKey(json, SourceKey, _Source);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> MatchProfilesEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<MatchProfilesEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
|
||||
JsonUtils::GetValueForKey(json, CommandlineKey, entry->_Commandline);
|
||||
JsonUtils::GetValueForKey(json, SourceKey, entry->_Source);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
bool MatchProfilesEntry::MatchesProfile(const Model::Profile& profile)
|
||||
{
|
||||
// We use an optional here instead of a simple bool directly, since there is no
|
||||
// sensible default value for the desired semantics: the first property we want
|
||||
// to match on should always be applied (so one would set "true" as a default),
|
||||
// but if none of the properties are set, the default return value should be false
|
||||
// since this entry type is expected to behave like a positive match/whitelist.
|
||||
//
|
||||
// The easiest way to deal with this neatly is to use an optional, then for any
|
||||
// property to match we consider a null value to be "true", and for the return
|
||||
// value of the function we consider the null value to be "false".
|
||||
auto isMatching = std::optional<bool>{};
|
||||
|
||||
if (!_Name.empty())
|
||||
MatchProfilesEntry::MatchProfilesEntry() noexcept :
|
||||
MatchProfilesEntryT<MatchProfilesEntry, ProfileCollectionEntry>(NewTabMenuEntryType::MatchProfiles)
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Name == profile.Name() };
|
||||
}
|
||||
|
||||
if (!_Source.empty())
|
||||
Json::Value MatchProfilesEntry::ToJson() const
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Source == profile.Source() };
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
JsonUtils::SetValueForKey(json, NameKey, _Name);
|
||||
JsonUtils::SetValueForKey(json, CommandlineKey, _Commandline);
|
||||
JsonUtils::SetValueForKey(json, SourceKey, _Source);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
if (!_Commandline.empty())
|
||||
winrt::com_ptr<NewTabMenuEntry> MatchProfilesEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Commandline == profile.Commandline() };
|
||||
auto entry = winrt::make_self<MatchProfilesEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
|
||||
JsonUtils::GetValueForKey(json, CommandlineKey, entry->_Commandline);
|
||||
JsonUtils::GetValueForKey(json, SourceKey, entry->_Source);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
return isMatching.value_or(false);
|
||||
bool MatchProfilesEntry::MatchesProfile(const Model::Profile& profile)
|
||||
{
|
||||
// We use an optional here instead of a simple bool directly, since there is no
|
||||
// sensible default value for the desired semantics: the first property we want
|
||||
// to match on should always be applied (so one would set "true" as a default),
|
||||
// but if none of the properties are set, the default return value should be false
|
||||
// since this entry type is expected to behave like a positive match/whitelist.
|
||||
//
|
||||
// The easiest way to deal with this neatly is to use an optional, then for any
|
||||
// property to match we consider a null value to be "true", and for the return
|
||||
// value of the function we consider the null value to be "false".
|
||||
auto isMatching = std::optional<bool>{};
|
||||
|
||||
if (!_Name.empty())
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Name == profile.Name() };
|
||||
}
|
||||
|
||||
if (!_Source.empty())
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Source == profile.Source() };
|
||||
}
|
||||
|
||||
if (!_Commandline.empty())
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Commandline == profile.Commandline() };
|
||||
}
|
||||
|
||||
return isMatching.value_or(false);
|
||||
}
|
||||
|
||||
Model::NewTabMenuEntry MatchProfilesEntry::Copy() const
|
||||
{
|
||||
auto entry = winrt::make_self<MatchProfilesEntry>();
|
||||
entry->_Name = _Name;
|
||||
entry->_Commandline = _Commandline;
|
||||
entry->_Source = _Source;
|
||||
return *entry;
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
public:
|
||||
MatchProfilesEntry() noexcept;
|
||||
|
||||
Model::NewTabMenuEntry Copy() const override;
|
||||
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
|
||||
@ -15,47 +15,49 @@
|
||||
#include "NewTabMenuEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
using NewTabMenuEntryType = winrt::Microsoft::Terminal::Settings::Model::NewTabMenuEntryType;
|
||||
|
||||
static constexpr std::string_view TypeKey{ "type" };
|
||||
|
||||
NewTabMenuEntry::NewTabMenuEntry(const NewTabMenuEntryType type) noexcept :
|
||||
_Type{ type }
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
}
|
||||
|
||||
// This method will be overridden by the subclasses, which will then call this
|
||||
// parent implementation for a "base" json object.
|
||||
Json::Value NewTabMenuEntry::ToJson() const
|
||||
{
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
JsonUtils::SetValueForKey(json, TypeKey, _Type);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
// Deserialize the JSON object based on the given type. We use the map from above for that.
|
||||
winrt::com_ptr<NewTabMenuEntry> NewTabMenuEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
const auto type = JsonUtils::GetValueForKey<NewTabMenuEntryType>(json, TypeKey);
|
||||
|
||||
switch (type)
|
||||
NewTabMenuEntry::NewTabMenuEntry(const NewTabMenuEntryType type) noexcept :
|
||||
_Type{ type }
|
||||
{
|
||||
case NewTabMenuEntryType::Separator:
|
||||
return SeparatorEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::Folder:
|
||||
return FolderEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::Profile:
|
||||
return ProfileEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
return RemainingProfilesEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
return MatchProfilesEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::Action:
|
||||
return ActionEntry::FromJson(json);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This method will be overridden by the subclasses, which will then call this
|
||||
// parent implementation for a "base" json object.
|
||||
Json::Value NewTabMenuEntry::ToJson() const
|
||||
{
|
||||
Json::Value json{ Json::ValueType::objectValue };
|
||||
|
||||
JsonUtils::SetValueForKey(json, TypeKey, _Type);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
// Deserialize the JSON object based on the given type. We use the map from above for that.
|
||||
winrt::com_ptr<NewTabMenuEntry> NewTabMenuEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
const auto type = JsonUtils::GetValueForKey<NewTabMenuEntryType>(json, TypeKey);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NewTabMenuEntryType::Separator:
|
||||
return SeparatorEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::Folder:
|
||||
return FolderEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::Profile:
|
||||
return ProfileEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::RemainingProfiles:
|
||||
return RemainingProfilesEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::MatchProfiles:
|
||||
return MatchProfilesEntry::FromJson(json);
|
||||
case NewTabMenuEntryType::Action:
|
||||
return ActionEntry::FromJson(json);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
public:
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
virtual Json::Value ToJson() const;
|
||||
virtual Model::NewTabMenuEntry Copy() const = 0;
|
||||
|
||||
WINRT_PROPERTY(NewTabMenuEntryType, Type, NewTabMenuEntryType::Invalid);
|
||||
|
||||
|
||||
@ -61,6 +61,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
Boolean AllowEmpty;
|
||||
|
||||
IVector<NewTabMenuEntry> Entries();
|
||||
IVector<NewTabMenuEntry> RawEntries;
|
||||
}
|
||||
|
||||
[default_interface] unsealed runtimeclass ProfileCollectionEntry : NewTabMenuEntry
|
||||
|
||||
@ -8,56 +8,67 @@
|
||||
#include "ProfileEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
static constexpr std::string_view ProfileKey{ "profile" };
|
||||
static constexpr std::string_view IconKey{ "icon" };
|
||||
|
||||
ProfileEntry::ProfileEntry() noexcept :
|
||||
ProfileEntry{ winrt::hstring{} }
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
}
|
||||
|
||||
ProfileEntry::ProfileEntry(const winrt::hstring& profile) noexcept :
|
||||
ProfileEntryT<ProfileEntry, NewTabMenuEntry>(NewTabMenuEntryType::Profile),
|
||||
_ProfileName{ profile }
|
||||
{
|
||||
}
|
||||
|
||||
Json::Value ProfileEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
// We will now return a profile reference to the JSON representation. Logic is
|
||||
// as follows:
|
||||
// - When Profile is null, this is typically because an existing profile menu entry
|
||||
// in the JSON config is invalid (nonexistent or hidden profile). Then, we store
|
||||
// the original profile string value as read from JSON, to improve portability
|
||||
// of the settings file and limit modifications to the JSON.
|
||||
// - Otherwise, we always store the GUID of the profile. This will effectively convert
|
||||
// all name-matched profiles from the settings file to GUIDs. This might be unexpected
|
||||
// to some users, but is less error-prone and will continue to work when profile
|
||||
// names are changed.
|
||||
if (_Profile == nullptr)
|
||||
ProfileEntry::ProfileEntry() noexcept :
|
||||
ProfileEntry{ winrt::hstring{} }
|
||||
{
|
||||
JsonUtils::SetValueForKey(json, ProfileKey, _ProfileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils::SetValueForKey(json, ProfileKey, _Profile.Guid());
|
||||
}
|
||||
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
ProfileEntry::ProfileEntry(const winrt::hstring& profile) noexcept :
|
||||
ProfileEntryT<ProfileEntry, NewTabMenuEntry>(NewTabMenuEntryType::Profile),
|
||||
_ProfileName{ profile }
|
||||
{
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> ProfileEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<ProfileEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, ProfileKey, entry->_ProfileName);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
|
||||
return entry;
|
||||
Json::Value ProfileEntry::ToJson() const
|
||||
{
|
||||
auto json = NewTabMenuEntry::ToJson();
|
||||
|
||||
// We will now return a profile reference to the JSON representation. Logic is
|
||||
// as follows:
|
||||
// - When Profile is null, this is typically because an existing profile menu entry
|
||||
// in the JSON config is invalid (nonexistent or hidden profile). Then, we store
|
||||
// the original profile string value as read from JSON, to improve portability
|
||||
// of the settings file and limit modifications to the JSON.
|
||||
// - Otherwise, we always store the GUID of the profile. This will effectively convert
|
||||
// all name-matched profiles from the settings file to GUIDs. This might be unexpected
|
||||
// to some users, but is less error-prone and will continue to work when profile
|
||||
// names are changed.
|
||||
if (_Profile == nullptr)
|
||||
{
|
||||
JsonUtils::SetValueForKey(json, ProfileKey, _ProfileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
JsonUtils::SetValueForKey(json, ProfileKey, _Profile.Guid());
|
||||
}
|
||||
JsonUtils::SetValueForKey(json, IconKey, _Icon);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> ProfileEntry::FromJson(const Json::Value& json)
|
||||
{
|
||||
auto entry = winrt::make_self<ProfileEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, ProfileKey, entry->_ProfileName);
|
||||
JsonUtils::GetValueForKey(json, IconKey, entry->_Icon);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
Model::NewTabMenuEntry ProfileEntry::Copy() const
|
||||
{
|
||||
auto entry{ winrt::make_self<ProfileEntry>() };
|
||||
entry->_Profile = _Profile;
|
||||
entry->_ProfileIndex = _ProfileIndex;
|
||||
entry->_ProfileName = _ProfileName;
|
||||
entry->_Icon = _Icon;
|
||||
return *entry;
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
ProfileEntry() noexcept;
|
||||
explicit ProfileEntry(const winrt::hstring& profile) noexcept;
|
||||
|
||||
Model::NewTabMenuEntry Copy() const override;
|
||||
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
|
||||
@ -9,14 +9,21 @@
|
||||
#include "RemainingProfilesEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
RemainingProfilesEntry::RemainingProfilesEntry() noexcept :
|
||||
RemainingProfilesEntryT<RemainingProfilesEntry, ProfileCollectionEntry>(NewTabMenuEntryType::RemainingProfiles)
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
}
|
||||
RemainingProfilesEntry::RemainingProfilesEntry() noexcept :
|
||||
RemainingProfilesEntryT<RemainingProfilesEntry, ProfileCollectionEntry>(NewTabMenuEntryType::RemainingProfiles)
|
||||
{
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> RemainingProfilesEntry::FromJson(const Json::Value&)
|
||||
{
|
||||
return winrt::make_self<RemainingProfilesEntry>();
|
||||
winrt::com_ptr<NewTabMenuEntry> RemainingProfilesEntry::FromJson(const Json::Value&)
|
||||
{
|
||||
return winrt::make_self<RemainingProfilesEntry>();
|
||||
}
|
||||
|
||||
Model::NewTabMenuEntry RemainingProfilesEntry::Copy() const
|
||||
{
|
||||
return winrt::make<RemainingProfilesEntry>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
public:
|
||||
RemainingProfilesEntry() noexcept;
|
||||
|
||||
Model::NewTabMenuEntry Copy() const override;
|
||||
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
};
|
||||
}
|
||||
|
||||
@ -8,14 +8,21 @@
|
||||
#include "SeparatorEntry.g.cpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
|
||||
|
||||
SeparatorEntry::SeparatorEntry() noexcept :
|
||||
SeparatorEntryT<SeparatorEntry, NewTabMenuEntry>(NewTabMenuEntryType::Separator)
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
}
|
||||
SeparatorEntry::SeparatorEntry() noexcept :
|
||||
SeparatorEntryT<SeparatorEntry, NewTabMenuEntry>(NewTabMenuEntryType::Separator)
|
||||
{
|
||||
}
|
||||
|
||||
winrt::com_ptr<NewTabMenuEntry> SeparatorEntry::FromJson(const Json::Value&)
|
||||
{
|
||||
return winrt::make_self<SeparatorEntry>();
|
||||
winrt::com_ptr<NewTabMenuEntry> SeparatorEntry::FromJson(const Json::Value&)
|
||||
{
|
||||
return winrt::make_self<SeparatorEntry>();
|
||||
}
|
||||
|
||||
Model::NewTabMenuEntry SeparatorEntry::Copy() const
|
||||
{
|
||||
return winrt::make<SeparatorEntry>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
public:
|
||||
SeparatorEntry() noexcept;
|
||||
|
||||
Model::NewTabMenuEntry Copy() const override;
|
||||
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user