2020-12-11 22:34:21 +01:00
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
|
|
|
// Licensed under the MIT license.
|
|
|
|
|
|
|
|
|
|
#include "pch.h"
|
|
|
|
|
#include "MainPage.h"
|
|
|
|
|
#include "MainPage.g.cpp"
|
|
|
|
|
#include "Launch.h"
|
|
|
|
|
#include "Interaction.h"
|
|
|
|
|
#include "Rendering.h"
|
2021-02-24 00:37:23 +01:00
|
|
|
|
#include "Actions.h"
|
2020-12-11 22:34:21 +01:00
|
|
|
|
#include "Profiles.h"
|
|
|
|
|
#include "GlobalAppearance.h"
|
|
|
|
|
#include "ColorSchemes.h"
|
2021-05-05 06:15:25 +02:00
|
|
|
|
#include "AddProfile.h"
|
2020-12-18 00:14:07 +01:00
|
|
|
|
#include "..\types\inc\utils.hpp"
|
2020-12-11 22:34:21 +01:00
|
|
|
|
|
|
|
|
|
#include <LibraryResources.h>
|
|
|
|
|
|
|
|
|
|
namespace winrt
|
|
|
|
|
{
|
|
|
|
|
namespace MUX = Microsoft::UI::Xaml;
|
|
|
|
|
namespace WUX = Windows::UI::Xaml;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using namespace winrt::Windows::Foundation;
|
|
|
|
|
using namespace winrt::Windows::UI::Xaml;
|
|
|
|
|
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
|
|
|
|
using namespace winrt::Windows::UI::Core;
|
|
|
|
|
using namespace winrt::Windows::System;
|
|
|
|
|
using namespace winrt::Windows::UI::Xaml::Controls;
|
|
|
|
|
|
|
|
|
|
static const std::wstring_view launchTag{ L"Launch_Nav" };
|
|
|
|
|
static const std::wstring_view interactionTag{ L"Interaction_Nav" };
|
|
|
|
|
static const std::wstring_view renderingTag{ L"Rendering_Nav" };
|
2021-02-24 00:37:23 +01:00
|
|
|
|
static const std::wstring_view actionsTag{ L"Actions_Nav" };
|
Reintroduce the Defaults page and the Reset buttons (#10588)
This pull request brings back the "Base Layer" page, now renamed to
"Defaults", and the "Reset to inherited value" buttons. The scope of
inheritance for which buttons will display has been widened.
The button will be visible in the following cases:
The user has set a setting for the current profile, and it overrides...
1. ... something in profiles.defaults.
2. ... something in a Fragment Extension profile.
3. ... something from a Dynamic Profile Generator.
4. ... something from the compiled-in defaults.
Compared to the original implementation of reset arrows, cases (1), (3)
and (4) are new. Rationale:
(1) The user can see a setting on the Defaults page, and they need a way
to reset back to it.
(3) Dynamic profiles are not meaningfully different from fragments, and
users may need a way to reset back to the default value generated
for WSL or PowerShell.
(4) The user can see a setting on the Defaults page, **BUT** they are
not the one who created it. They *still* need a way to get back to
it.
To support this, I've introduced another origin tag, "User", and renamed
"Custom" to "None". Due to the way origin/override detection works¹, we
cannot otherwise disambiguate between settings that came from the user
and settings that came from the compiled-in defaults.
Changes were required in TerminalSettings such that we could construct a
settings object with a profile that does not have a GUID. In making this
change, I fixed a bit of silliness where we took a profile, extracted
its guid, and used that guid to look up the same profile object. Oops.
I also fixed the PropertyChanged notifier to include the
XxxOverrideSource property.
The presence of the page and the reset arrows is restricted to
Preview- or Dev-branded builds. Stable builds will retain their current
behavior.
¹ `XxxOverrideSource` returns the profile *above* the current profile
that holds a value for setting `Xxx`. When the value is the
compiled-in value, `XxxOverrideSource` will be `null`. Since it's
supposed to be the profile above the current profile, it will also be
`null` if the profile contains a setting at this layer.
In short, `null` means "user specified" *or* "compiled in". Oops.
Fixes #10430
Validation
----------
* [x] Tested Release build to make sure it's mostly arrow-free (apart from fragments)
2021-07-10 00:03:41 +02:00
|
|
|
|
static const std::wstring_view globalProfileTag{ L"GlobalProfile_Nav" };
|
2020-12-11 22:34:21 +01:00
|
|
|
|
static const std::wstring_view addProfileTag{ L"AddProfile" };
|
|
|
|
|
static const std::wstring_view colorSchemesTag{ L"ColorSchemes_Nav" };
|
|
|
|
|
static const std::wstring_view globalAppearanceTag{ L"GlobalAppearance_Nav" };
|
|
|
|
|
|
|
|
|
|
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
|
|
|
|
{
|
2021-05-17 04:26:47 +02:00
|
|
|
|
static Editor::ProfileViewModel _viewModelForProfile(const Model::Profile& profile, const Model::CascadiaSettings& appSettings)
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-05-17 04:26:47 +02:00
|
|
|
|
return winrt::make<implementation::ProfileViewModel>(profile, appSettings);
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MainPage::MainPage(const CascadiaSettings& settings) :
|
|
|
|
|
_settingsSource{ settings },
|
|
|
|
|
_settingsClone{ settings.Copy() }
|
|
|
|
|
{
|
|
|
|
|
InitializeComponent();
|
|
|
|
|
|
|
|
|
|
_InitializeProfilesList();
|
2021-01-19 20:18:07 +01:00
|
|
|
|
|
2021-01-22 19:21:18 +01:00
|
|
|
|
_colorSchemesNavState = winrt::make<ColorSchemesPageNavigationState>(_settingsClone);
|
2021-02-17 18:43:47 +01:00
|
|
|
|
|
|
|
|
|
Automation::AutomationProperties::SetHelpText(SaveButton(), RS_(L"Settings_SaveSettingsButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
|
|
|
|
|
Automation::AutomationProperties::SetHelpText(ResetButton(), RS_(L"Settings_ResetSettingsButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
|
|
|
|
|
Automation::AutomationProperties::SetHelpText(OpenJsonNavItem(), RS_(L"Nav_OpenJSON/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"));
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Method Description:
|
|
|
|
|
// - Update the Settings UI with a new CascadiaSettings to bind to
|
|
|
|
|
// Arguments:
|
|
|
|
|
// - settings - the new settings source
|
|
|
|
|
// Return value:
|
|
|
|
|
// - <none>
|
2021-06-11 02:38:10 +02:00
|
|
|
|
void MainPage::UpdateSettings(const Model::CascadiaSettings& settings)
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
|
|
|
|
_settingsSource = settings;
|
|
|
|
|
_settingsClone = settings.Copy();
|
|
|
|
|
|
2021-01-19 23:14:07 +01:00
|
|
|
|
// Deduce information about the currently selected item
|
|
|
|
|
IInspectable selectedItemTag;
|
2020-12-11 22:34:21 +01:00
|
|
|
|
auto menuItems{ SettingsNav().MenuItems() };
|
2021-01-19 23:14:07 +01:00
|
|
|
|
if (const auto& selectedItem{ SettingsNav().SelectedItem() })
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-01-19 23:14:07 +01:00
|
|
|
|
if (const auto& navViewItem{ selectedItem.try_as<MUX::Controls::NavigationViewItem>() })
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-01-19 23:14:07 +01:00
|
|
|
|
selectedItemTag = navViewItem.Tag();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-11 02:38:10 +02:00
|
|
|
|
// We'll remove a bunch of items and iterate over it twice.
|
|
|
|
|
// --> Copy it into an STL vector to simplify our code and reduce COM overhead.
|
|
|
|
|
std::vector<IInspectable> menuItemsSTL(menuItems.Size(), nullptr);
|
|
|
|
|
menuItems.GetMany(0, menuItemsSTL);
|
|
|
|
|
|
|
|
|
|
// We want to refresh the list of profiles in the NavigationView.
|
|
|
|
|
// In order to add profiles we can use _InitializeProfilesList();
|
|
|
|
|
// But before we can do that we have to remove existing profiles first of course.
|
|
|
|
|
// This "erase-remove" idiom will achieve just that.
|
|
|
|
|
menuItemsSTL.erase(
|
|
|
|
|
std::remove_if(
|
|
|
|
|
menuItemsSTL.begin(),
|
|
|
|
|
menuItemsSTL.end(),
|
|
|
|
|
[](const auto& item) -> bool {
|
|
|
|
|
if (const auto& navViewItem{ item.try_as<MUX::Controls::NavigationViewItem>() })
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-06-11 02:38:10 +02:00
|
|
|
|
if (const auto& tag{ navViewItem.Tag() })
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-06-11 02:38:10 +02:00
|
|
|
|
if (tag.try_as<Editor::ProfileViewModel>())
|
|
|
|
|
{
|
|
|
|
|
// remove NavViewItem pointing to a Profile
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (const auto& stringTag{ tag.try_as<hstring>() })
|
|
|
|
|
{
|
|
|
|
|
if (stringTag == addProfileTag)
|
|
|
|
|
{
|
|
|
|
|
// remove the "Add Profile" item
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-11 02:38:10 +02:00
|
|
|
|
return false;
|
|
|
|
|
}),
|
|
|
|
|
menuItemsSTL.end());
|
|
|
|
|
|
2021-01-19 23:14:07 +01:00
|
|
|
|
menuItems.ReplaceAll(menuItemsSTL);
|
2020-12-11 22:34:21 +01:00
|
|
|
|
|
2021-01-19 23:14:07 +01:00
|
|
|
|
// Repopulate profile-related menu items
|
|
|
|
|
_InitializeProfilesList();
|
2021-01-19 20:18:07 +01:00
|
|
|
|
// Update the Nav State with the new version of the settings
|
2021-01-22 19:21:18 +01:00
|
|
|
|
_colorSchemesNavState.Settings(_settingsClone);
|
2021-01-26 00:06:33 +01:00
|
|
|
|
// We'll update the profile in the _profilesNavState whenever we actually navigate to one
|
2021-01-19 20:18:07 +01:00
|
|
|
|
|
2021-01-19 23:14:07 +01:00
|
|
|
|
// now that the menuItems are repopulated,
|
|
|
|
|
// refresh the current page using the SelectedItem data we collected before the refresh
|
|
|
|
|
if (selectedItemTag)
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-07-12 20:44:39 +02:00
|
|
|
|
for (const auto& item : menuItems)
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-01-19 23:14:07 +01:00
|
|
|
|
if (const auto& menuItem{ item.try_as<MUX::Controls::NavigationViewItem>() })
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-01-19 23:14:07 +01:00
|
|
|
|
if (const auto& tag{ menuItem.Tag() })
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-01-19 23:14:07 +01:00
|
|
|
|
if (const auto& stringTag{ tag.try_as<hstring>() })
|
|
|
|
|
{
|
2021-05-05 06:15:25 +02:00
|
|
|
|
if (const auto& selectedItemStringTag{ selectedItemTag.try_as<hstring>() })
|
2021-01-19 23:14:07 +01:00
|
|
|
|
{
|
2021-05-05 06:15:25 +02:00
|
|
|
|
if (stringTag == selectedItemStringTag)
|
|
|
|
|
{
|
|
|
|
|
// found the one that was selected before the refresh
|
|
|
|
|
SettingsNav().SelectedItem(item);
|
|
|
|
|
_Navigate(*stringTag);
|
2021-06-11 02:38:10 +02:00
|
|
|
|
return;
|
2021-05-05 06:15:25 +02:00
|
|
|
|
}
|
2021-01-19 23:14:07 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (const auto& profileTag{ tag.try_as<ProfileViewModel>() })
|
|
|
|
|
{
|
2021-05-05 06:15:25 +02:00
|
|
|
|
if (const auto& selectedItemProfileTag{ selectedItemTag.try_as<ProfileViewModel>() })
|
2021-01-19 23:14:07 +01:00
|
|
|
|
{
|
2021-05-21 19:34:25 +02:00
|
|
|
|
if (profileTag->OriginalProfileGuid() == selectedItemProfileTag->OriginalProfileGuid())
|
2021-05-05 06:15:25 +02:00
|
|
|
|
{
|
|
|
|
|
// found the one that was selected before the refresh
|
|
|
|
|
SettingsNav().SelectedItem(item);
|
|
|
|
|
_Navigate(*profileTag);
|
2021-06-11 02:38:10 +02:00
|
|
|
|
return;
|
2021-05-05 06:15:25 +02:00
|
|
|
|
}
|
2021-01-19 23:14:07 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-12 20:44:39 +02:00
|
|
|
|
// Couldn't find the selected item, fallback to first menu item
|
|
|
|
|
// This happens when the selected item was a profile which doesn't exist in the new configuration
|
|
|
|
|
// We can use menuItemsSTL here because the only things they miss are profile entries.
|
|
|
|
|
const auto& firstItem{ menuItemsSTL.at(0).as<MUX::Controls::NavigationViewItem>() };
|
2021-01-19 23:14:07 +01:00
|
|
|
|
SettingsNav().SelectedItem(firstItem);
|
2021-07-12 20:44:39 +02:00
|
|
|
|
_Navigate(unbox_value<hstring>(firstItem.Tag()));
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainPage::SetHostingWindow(uint64_t hostingWindow) noexcept
|
|
|
|
|
{
|
|
|
|
|
_hostingHwnd.emplace(reinterpret_cast<HWND>(hostingWindow));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MainPage::TryPropagateHostingWindow(IInspectable object) noexcept
|
|
|
|
|
{
|
|
|
|
|
if (_hostingHwnd)
|
|
|
|
|
{
|
|
|
|
|
if (auto initializeWithWindow{ object.try_as<IInitializeWithWindow>() })
|
|
|
|
|
{
|
|
|
|
|
return SUCCEEDED(initializeWithWindow->Initialize(*_hostingHwnd));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 06:15:25 +02:00
|
|
|
|
// Method Description:
|
|
|
|
|
// - Creates a new profile and navigates to it in the Settings UI
|
|
|
|
|
// Arguments:
|
|
|
|
|
// - profileGuid: the guid of the profile we want to duplicate,
|
|
|
|
|
// can be empty to indicate that we should create a fresh profile
|
|
|
|
|
void MainPage::_AddProfileHandler(winrt::guid profileGuid)
|
|
|
|
|
{
|
|
|
|
|
uint32_t insertIndex;
|
|
|
|
|
auto selectedItem{ SettingsNav().SelectedItem() };
|
|
|
|
|
auto menuItems{ SettingsNav().MenuItems() };
|
|
|
|
|
menuItems.IndexOf(selectedItem, insertIndex);
|
|
|
|
|
if (profileGuid != winrt::guid{})
|
|
|
|
|
{
|
|
|
|
|
// if we were given a non-empty guid, we want to duplicate the corresponding profile
|
|
|
|
|
const auto profile = _settingsClone.FindProfile(profileGuid);
|
|
|
|
|
if (profile)
|
|
|
|
|
{
|
|
|
|
|
const auto duplicated = _settingsClone.DuplicateProfile(profile);
|
|
|
|
|
_CreateAndNavigateToNewProfile(insertIndex, duplicated);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// we were given an empty guid, create a new profile
|
|
|
|
|
_CreateAndNavigateToNewProfile(insertIndex, nullptr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-12 18:55:51 +02:00
|
|
|
|
uint64_t MainPage::GetHostingWindow() const noexcept
|
|
|
|
|
{
|
|
|
|
|
return reinterpret_cast<uint64_t>(_hostingHwnd.value_or(nullptr));
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-11 22:34:21 +01:00
|
|
|
|
// Function Description:
|
|
|
|
|
// - Called when the NavigationView is loaded. Navigates to the first item in the NavigationView, if no item is selected
|
|
|
|
|
// Arguments:
|
|
|
|
|
// - <unused>
|
|
|
|
|
// Return Value:
|
|
|
|
|
// - <none>
|
|
|
|
|
void MainPage::SettingsNav_Loaded(IInspectable const&, RoutedEventArgs const&)
|
|
|
|
|
{
|
|
|
|
|
if (SettingsNav().SelectedItem() == nullptr)
|
|
|
|
|
{
|
|
|
|
|
const auto initialItem = SettingsNav().MenuItems().GetAt(0);
|
|
|
|
|
SettingsNav().SelectedItem(initialItem);
|
|
|
|
|
|
|
|
|
|
// Manually navigate because setting the selected item programmatically doesn't trigger ItemInvoked.
|
|
|
|
|
if (const auto tag = initialItem.as<MUX::Controls::NavigationViewItem>().Tag())
|
|
|
|
|
{
|
|
|
|
|
_Navigate(unbox_value<hstring>(tag));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Function Description:
|
|
|
|
|
// - Called when NavigationView items are invoked. Navigates to the corresponding page.
|
|
|
|
|
// Arguments:
|
|
|
|
|
// - args - additional event info from invoking the NavViewItem
|
|
|
|
|
// Return Value:
|
|
|
|
|
// - <none>
|
|
|
|
|
void MainPage::SettingsNav_ItemInvoked(MUX::Controls::NavigationView const&, MUX::Controls::NavigationViewItemInvokedEventArgs const& args)
|
|
|
|
|
{
|
|
|
|
|
if (const auto clickedItemContainer = args.InvokedItemContainer())
|
|
|
|
|
{
|
2021-01-19 23:14:07 +01:00
|
|
|
|
if (clickedItemContainer.IsSelected())
|
|
|
|
|
{
|
|
|
|
|
// Clicked on the selected item.
|
|
|
|
|
// Don't navigate to the same page again.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-11 22:34:21 +01:00
|
|
|
|
if (const auto navString = clickedItemContainer.Tag().try_as<hstring>())
|
|
|
|
|
{
|
2021-05-05 06:15:25 +02:00
|
|
|
|
_Navigate(*navString);
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
else if (const auto profile = clickedItemContainer.Tag().try_as<Editor::ProfileViewModel>())
|
|
|
|
|
{
|
|
|
|
|
// Navigate to a page with the given profile
|
2020-12-18 00:14:07 +01:00
|
|
|
|
_Navigate(profile);
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainPage::_Navigate(hstring clickedItemTag)
|
|
|
|
|
{
|
|
|
|
|
if (clickedItemTag == launchTag)
|
|
|
|
|
{
|
|
|
|
|
contentFrame().Navigate(xaml_typename<Editor::Launch>(), winrt::make<LaunchPageNavigationState>(_settingsClone));
|
|
|
|
|
}
|
|
|
|
|
else if (clickedItemTag == interactionTag)
|
|
|
|
|
{
|
|
|
|
|
contentFrame().Navigate(xaml_typename<Editor::Interaction>(), winrt::make<InteractionPageNavigationState>(_settingsClone.GlobalSettings()));
|
|
|
|
|
}
|
|
|
|
|
else if (clickedItemTag == renderingTag)
|
|
|
|
|
{
|
|
|
|
|
contentFrame().Navigate(xaml_typename<Editor::Rendering>(), winrt::make<RenderingPageNavigationState>(_settingsClone.GlobalSettings()));
|
|
|
|
|
}
|
2021-02-24 00:37:23 +01:00
|
|
|
|
else if (clickedItemTag == actionsTag)
|
|
|
|
|
{
|
2021-10-25 13:16:49 +02:00
|
|
|
|
contentFrame().Navigate(xaml_typename<Editor::Actions>(), winrt::make<ActionsPageNavigationState>(_settingsClone));
|
2021-02-24 00:37:23 +01:00
|
|
|
|
}
|
Reintroduce the Defaults page and the Reset buttons (#10588)
This pull request brings back the "Base Layer" page, now renamed to
"Defaults", and the "Reset to inherited value" buttons. The scope of
inheritance for which buttons will display has been widened.
The button will be visible in the following cases:
The user has set a setting for the current profile, and it overrides...
1. ... something in profiles.defaults.
2. ... something in a Fragment Extension profile.
3. ... something from a Dynamic Profile Generator.
4. ... something from the compiled-in defaults.
Compared to the original implementation of reset arrows, cases (1), (3)
and (4) are new. Rationale:
(1) The user can see a setting on the Defaults page, and they need a way
to reset back to it.
(3) Dynamic profiles are not meaningfully different from fragments, and
users may need a way to reset back to the default value generated
for WSL or PowerShell.
(4) The user can see a setting on the Defaults page, **BUT** they are
not the one who created it. They *still* need a way to get back to
it.
To support this, I've introduced another origin tag, "User", and renamed
"Custom" to "None". Due to the way origin/override detection works¹, we
cannot otherwise disambiguate between settings that came from the user
and settings that came from the compiled-in defaults.
Changes were required in TerminalSettings such that we could construct a
settings object with a profile that does not have a GUID. In making this
change, I fixed a bit of silliness where we took a profile, extracted
its guid, and used that guid to look up the same profile object. Oops.
I also fixed the PropertyChanged notifier to include the
XxxOverrideSource property.
The presence of the page and the reset arrows is restricted to
Preview- or Dev-branded builds. Stable builds will retain their current
behavior.
¹ `XxxOverrideSource` returns the profile *above* the current profile
that holds a value for setting `Xxx`. When the value is the
compiled-in value, `XxxOverrideSource` will be `null`. Since it's
supposed to be the profile above the current profile, it will also be
`null` if the profile contains a setting at this layer.
In short, `null` means "user specified" *or* "compiled in". Oops.
Fixes #10430
Validation
----------
* [x] Tested Release build to make sure it's mostly arrow-free (apart from fragments)
2021-07-10 00:03:41 +02:00
|
|
|
|
else if (clickedItemTag == globalProfileTag)
|
|
|
|
|
{
|
|
|
|
|
auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone) };
|
|
|
|
|
profileVM.IsBaseLayer(true);
|
|
|
|
|
_lastProfilesNavState = winrt::make<ProfilePageNavigationState>(profileVM,
|
|
|
|
|
_settingsClone.GlobalSettings().ColorSchemes(),
|
|
|
|
|
_lastProfilesNavState,
|
|
|
|
|
*this);
|
|
|
|
|
|
|
|
|
|
contentFrame().Navigate(xaml_typename<Editor::Profiles>(), _lastProfilesNavState);
|
|
|
|
|
}
|
2020-12-11 22:34:21 +01:00
|
|
|
|
else if (clickedItemTag == colorSchemesTag)
|
|
|
|
|
{
|
2021-01-19 20:18:07 +01:00
|
|
|
|
contentFrame().Navigate(xaml_typename<Editor::ColorSchemes>(), _colorSchemesNavState);
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
else if (clickedItemTag == globalAppearanceTag)
|
|
|
|
|
{
|
|
|
|
|
contentFrame().Navigate(xaml_typename<Editor::GlobalAppearance>(), winrt::make<GlobalAppearancePageNavigationState>(_settingsClone.GlobalSettings()));
|
|
|
|
|
}
|
2021-05-05 06:15:25 +02:00
|
|
|
|
else if (clickedItemTag == addProfileTag)
|
|
|
|
|
{
|
|
|
|
|
auto addProfileState{ winrt::make<AddProfilePageNavigationState>(_settingsClone) };
|
|
|
|
|
addProfileState.AddNew({ get_weak(), &MainPage::_AddProfileHandler });
|
|
|
|
|
contentFrame().Navigate(xaml_typename<Editor::AddProfile>(), addProfileState);
|
|
|
|
|
}
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-02-20 00:50:52 +01:00
|
|
|
|
// Method Description:
|
|
|
|
|
// - updates the content frame to present a view of the profile page
|
|
|
|
|
// - NOTE: this does not update the selected item.
|
|
|
|
|
// Arguments:
|
|
|
|
|
// - profile - the profile object we are getting a view of
|
2020-12-18 00:14:07 +01:00
|
|
|
|
void MainPage::_Navigate(const Editor::ProfileViewModel& profile)
|
|
|
|
|
{
|
2021-01-26 00:06:33 +01:00
|
|
|
|
_lastProfilesNavState = winrt::make<ProfilePageNavigationState>(profile,
|
|
|
|
|
_settingsClone.GlobalSettings().ColorSchemes(),
|
|
|
|
|
_lastProfilesNavState,
|
|
|
|
|
*this);
|
2021-01-21 11:50:49 +01:00
|
|
|
|
|
|
|
|
|
// Add an event handler for when the user wants to delete a profile.
|
2021-01-26 00:06:33 +01:00
|
|
|
|
_lastProfilesNavState.DeleteProfile({ this, &MainPage::_DeleteProfile });
|
2020-12-18 00:14:07 +01:00
|
|
|
|
|
2021-01-26 00:06:33 +01:00
|
|
|
|
contentFrame().Navigate(xaml_typename<Editor::Profiles>(), _lastProfilesNavState);
|
2020-12-18 00:14:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-11 22:34:21 +01:00
|
|
|
|
void MainPage::OpenJsonTapped(IInspectable const& /*sender*/, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& /*args*/)
|
|
|
|
|
{
|
|
|
|
|
const CoreWindow window = CoreWindow::GetForCurrentThread();
|
|
|
|
|
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
|
|
|
|
|
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
|
|
|
|
|
const bool altPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) ||
|
|
|
|
|
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
|
|
|
|
|
|
|
|
|
|
const auto target = altPressed ? SettingsTarget::DefaultsFile : SettingsTarget::SettingsFile;
|
|
|
|
|
_OpenJsonHandlers(nullptr, target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainPage::OpenJsonKeyDown(IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& args)
|
|
|
|
|
{
|
|
|
|
|
if (args.Key() == VirtualKey::Enter || args.Key() == VirtualKey::Space)
|
|
|
|
|
{
|
|
|
|
|
const auto target = args.KeyStatus().IsMenuKeyDown ? SettingsTarget::DefaultsFile : SettingsTarget::SettingsFile;
|
|
|
|
|
_OpenJsonHandlers(nullptr, target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainPage::SaveButton_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*args*/)
|
|
|
|
|
{
|
|
|
|
|
_settingsClone.WriteSettingsToDisk();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainPage::ResetButton_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*args*/)
|
|
|
|
|
{
|
2021-01-19 23:14:07 +01:00
|
|
|
|
UpdateSettings(_settingsSource);
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainPage::_InitializeProfilesList()
|
|
|
|
|
{
|
2021-08-24 00:00:08 +02:00
|
|
|
|
const auto menuItems = SettingsNav().MenuItems();
|
|
|
|
|
|
2020-12-11 22:34:21 +01:00
|
|
|
|
// Manually create a NavigationViewItem for each profile
|
|
|
|
|
// and keep a reference to them in a map so that we
|
|
|
|
|
// can easily modify the correct one when the associated
|
|
|
|
|
// profile changes.
|
|
|
|
|
for (const auto& profile : _settingsClone.AllProfiles())
|
|
|
|
|
{
|
2021-08-24 00:00:08 +02:00
|
|
|
|
if (!profile.Deleted())
|
|
|
|
|
{
|
|
|
|
|
auto navItem = _CreateProfileNavViewItem(_viewModelForProfile(profile, _settingsClone));
|
|
|
|
|
menuItems.Append(navItem);
|
|
|
|
|
}
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Top off (the end of the nav view) with the Add Profile item
|
|
|
|
|
MUX::Controls::NavigationViewItem addProfileItem;
|
|
|
|
|
addProfileItem.Content(box_value(RS_(L"Nav_AddNewProfile/Content")));
|
|
|
|
|
addProfileItem.Tag(box_value(addProfileTag));
|
|
|
|
|
|
2021-05-19 00:35:50 +02:00
|
|
|
|
FontIcon icon;
|
2020-12-11 22:34:21 +01:00
|
|
|
|
// This is the "Add" symbol
|
2021-05-19 00:35:50 +02:00
|
|
|
|
icon.Glyph(L"\xE710");
|
2020-12-11 22:34:21 +01:00
|
|
|
|
addProfileItem.Icon(icon);
|
|
|
|
|
|
2021-08-24 00:00:08 +02:00
|
|
|
|
menuItems.Append(addProfileItem);
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 06:15:25 +02:00
|
|
|
|
void MainPage::_CreateAndNavigateToNewProfile(const uint32_t index, const Model::Profile& profile)
|
2020-12-11 22:34:21 +01:00
|
|
|
|
{
|
2021-05-05 06:15:25 +02:00
|
|
|
|
const auto newProfile{ profile ? profile : _settingsClone.CreateNewProfile() };
|
2021-05-17 04:26:47 +02:00
|
|
|
|
const auto profileViewModel{ _viewModelForProfile(newProfile, _settingsClone) };
|
2020-12-11 22:34:21 +01:00
|
|
|
|
const auto navItem{ _CreateProfileNavViewItem(profileViewModel) };
|
|
|
|
|
SettingsNav().MenuItems().InsertAt(index, navItem);
|
|
|
|
|
|
|
|
|
|
// Select and navigate to the new profile
|
|
|
|
|
SettingsNav().SelectedItem(navItem);
|
2020-12-18 00:14:07 +01:00
|
|
|
|
_Navigate(profileViewModel);
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MUX::Controls::NavigationViewItem MainPage::_CreateProfileNavViewItem(const Editor::ProfileViewModel& profile)
|
|
|
|
|
{
|
|
|
|
|
MUX::Controls::NavigationViewItem profileNavItem;
|
|
|
|
|
profileNavItem.Content(box_value(profile.Name()));
|
|
|
|
|
profileNavItem.Tag(box_value<Editor::ProfileViewModel>(profile));
|
|
|
|
|
|
|
|
|
|
const auto iconSource{ IconPathConverter::IconSourceWUX(profile.Icon()) };
|
|
|
|
|
WUX::Controls::IconSourceElement icon;
|
|
|
|
|
icon.IconSource(iconSource);
|
|
|
|
|
profileNavItem.Icon(icon);
|
|
|
|
|
|
2021-01-23 00:43:54 +01:00
|
|
|
|
// Update the menu item when the icon/name changes
|
|
|
|
|
auto weakMenuItem{ make_weak(profileNavItem) };
|
|
|
|
|
profile.PropertyChanged([weakMenuItem](const auto&, const WUX::Data::PropertyChangedEventArgs& args) {
|
|
|
|
|
if (auto menuItem{ weakMenuItem.get() })
|
|
|
|
|
{
|
|
|
|
|
const auto& tag{ menuItem.Tag().as<Editor::ProfileViewModel>() };
|
|
|
|
|
if (args.PropertyName() == L"Icon")
|
|
|
|
|
{
|
|
|
|
|
const auto iconSource{ IconPathConverter::IconSourceWUX(tag.Icon()) };
|
|
|
|
|
WUX::Controls::IconSourceElement icon;
|
|
|
|
|
icon.IconSource(iconSource);
|
|
|
|
|
menuItem.Icon(icon);
|
|
|
|
|
}
|
|
|
|
|
else if (args.PropertyName() == L"Name")
|
|
|
|
|
{
|
|
|
|
|
menuItem.Content(box_value(tag.Name()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-12-11 22:34:21 +01:00
|
|
|
|
return profileNavItem;
|
|
|
|
|
}
|
2020-12-18 00:14:07 +01:00
|
|
|
|
|
|
|
|
|
void MainPage::_DeleteProfile(const IInspectable /*sender*/, const Editor::DeleteProfileEventArgs& args)
|
|
|
|
|
{
|
|
|
|
|
// Delete profile from settings model
|
|
|
|
|
const auto guid{ args.ProfileGuid() };
|
|
|
|
|
auto profileList{ _settingsClone.AllProfiles() };
|
|
|
|
|
for (uint32_t i = 0; i < profileList.Size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
if (profileList.GetAt(i).Guid() == guid)
|
|
|
|
|
{
|
|
|
|
|
profileList.RemoveAt(i);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// remove selected item
|
|
|
|
|
uint32_t index;
|
|
|
|
|
auto selectedItem{ SettingsNav().SelectedItem() };
|
|
|
|
|
auto menuItems{ SettingsNav().MenuItems() };
|
|
|
|
|
menuItems.IndexOf(selectedItem, index);
|
|
|
|
|
menuItems.RemoveAt(index);
|
|
|
|
|
|
|
|
|
|
// navigate to the profile next to this one
|
|
|
|
|
const auto newSelectedItem{ menuItems.GetAt(index < menuItems.Size() - 1 ? index : index - 1) };
|
|
|
|
|
SettingsNav().SelectedItem(newSelectedItem);
|
|
|
|
|
_Navigate(newSelectedItem.try_as<MUX::Controls::NavigationViewItem>().Tag().try_as<Editor::ProfileViewModel>());
|
|
|
|
|
}
|
Reintroduce the Defaults page and the Reset buttons (#10588)
This pull request brings back the "Base Layer" page, now renamed to
"Defaults", and the "Reset to inherited value" buttons. The scope of
inheritance for which buttons will display has been widened.
The button will be visible in the following cases:
The user has set a setting for the current profile, and it overrides...
1. ... something in profiles.defaults.
2. ... something in a Fragment Extension profile.
3. ... something from a Dynamic Profile Generator.
4. ... something from the compiled-in defaults.
Compared to the original implementation of reset arrows, cases (1), (3)
and (4) are new. Rationale:
(1) The user can see a setting on the Defaults page, and they need a way
to reset back to it.
(3) Dynamic profiles are not meaningfully different from fragments, and
users may need a way to reset back to the default value generated
for WSL or PowerShell.
(4) The user can see a setting on the Defaults page, **BUT** they are
not the one who created it. They *still* need a way to get back to
it.
To support this, I've introduced another origin tag, "User", and renamed
"Custom" to "None". Due to the way origin/override detection works¹, we
cannot otherwise disambiguate between settings that came from the user
and settings that came from the compiled-in defaults.
Changes were required in TerminalSettings such that we could construct a
settings object with a profile that does not have a GUID. In making this
change, I fixed a bit of silliness where we took a profile, extracted
its guid, and used that guid to look up the same profile object. Oops.
I also fixed the PropertyChanged notifier to include the
XxxOverrideSource property.
The presence of the page and the reset arrows is restricted to
Preview- or Dev-branded builds. Stable builds will retain their current
behavior.
¹ `XxxOverrideSource` returns the profile *above* the current profile
that holds a value for setting `Xxx`. When the value is the
compiled-in value, `XxxOverrideSource` will be `null`. Since it's
supposed to be the profile above the current profile, it will also be
`null` if the profile contains a setting at this layer.
In short, `null` means "user specified" *or* "compiled in". Oops.
Fixes #10430
Validation
----------
* [x] Tested Release build to make sure it's mostly arrow-free (apart from fragments)
2021-07-10 00:03:41 +02:00
|
|
|
|
|
|
|
|
|
bool MainPage::ShowBaseLayerMenuItem() const noexcept
|
|
|
|
|
{
|
|
|
|
|
return Feature_ShowProfileDefaultsInSettings::IsEnabled();
|
|
|
|
|
}
|
2020-12-11 22:34:21 +01:00
|
|
|
|
}
|