diff --git a/src/cascadia/TerminalSettingsEditor/Actions.cpp b/src/cascadia/TerminalSettingsEditor/Actions.cpp index f27d89eb27d..5fcd910e9a0 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Actions.cpp @@ -19,52 +19,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Automation::AutomationProperties::SetName(AddNewButton(), RS_(L"Actions_AddNewTextBlock/Text")); } - Automation::Peers::AutomationPeer Actions::OnCreateAutomationPeer() - { - _ViewModel.OnAutomationPeerAttached(); - return nullptr; - } - void Actions::OnNavigatedTo(const NavigationEventArgs& e) { _ViewModel = e.Parameter().as(); - - // Subscribe to the view model's FocusContainer event. - // Use the KeyBindingViewModel or index provided in the event to focus the corresponding container - _ViewModel.FocusContainer([this](const auto& /*sender*/, const auto& args) { - if (auto kbdVM{ args.try_as() }) - { - if (const auto& container = KeyBindingsListView().ContainerFromItem(*kbdVM)) - { - container.as().Focus(FocusState::Programmatic); - } - } - else if (const auto& index = args.try_as()) - { - if (const auto& container = KeyBindingsListView().ContainerFromIndex(*index)) - { - container.as().Focus(FocusState::Programmatic); - } - } - }); - - // Subscribe to the view model's UpdateBackground event. - // The view model does not have access to the page resources, so it asks us - // to update the key binding's container background - _ViewModel.UpdateBackground([this](const auto& /*sender*/, const auto& args) { - if (auto kbdVM{ args.try_as() }) - { - if (kbdVM->IsInEditMode()) - { - const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackgroundEditing")).as() }; - kbdVM->ContainerBackground(containerBackground); - } - else - { - const auto& containerBackground{ Resources().Lookup(box_value(L"ActionContainerBackground")).as() }; - kbdVM->ContainerBackground(containerBackground); - } - } + _ViewModel.CurrentPage(ActionsSubPage::Base); + auto vmImpl = get_self(_ViewModel); + vmImpl->MarkAsVisited(); + _layoutUpdatedRevoker = LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) { + // Only let this succeed once. + _layoutUpdatedRevoker.revoke(); + + AddNewButton().Focus(FocusState::Programmatic); }); TraceLoggingWrite( @@ -78,6 +43,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void Actions::AddNew_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/) { - _ViewModel.AddNewKeybinding(); + _ViewModel.AddNewCommand(); } } diff --git a/src/cascadia/TerminalSettingsEditor/Actions.h b/src/cascadia/TerminalSettingsEditor/Actions.h index a79003afbfb..f11fac633f1 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.h +++ b/src/cascadia/TerminalSettingsEditor/Actions.h @@ -16,12 +16,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Actions(); void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e); - Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer(); void AddNew_Click(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); til::property_changed_event PropertyChanged; WINRT_OBSERVABLE_PROPERTY(Editor::ActionsViewModel, ViewModel, PropertyChanged.raise, nullptr); + + private: + winrt::Windows::UI::Xaml::FrameworkElement::LayoutUpdated_revoker _layoutUpdatedRevoker; }; } diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index b94977d9a2e..90a1118e57c 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -123,7 +123,7 @@ @@ -138,24 +138,6 @@ - 32 - 14 - - - - + @@ -180,146 +156,25 @@ - - - - - - - + Text="{x:Bind DisplayName, Mode=OneWay}" /> - + Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(FirstKeyChordText)}"> - - - - - - - - - - - - - - - - - - - @@ -331,6 +186,10 @@ HorizontalAlignment="Left" Spacing="8" Style="{StaticResource SettingsStackStyle}"> + - - + + diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp index 2c1860d1f70..fe037c13686 100644 --- a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.cpp @@ -4,9 +4,15 @@ #include "pch.h" #include "ActionsViewModel.h" #include "ActionsViewModel.g.cpp" -#include "KeyBindingViewModel.g.cpp" +#include "CommandViewModel.g.cpp" +#include "ArgWrapper.g.cpp" +#include "ActionArgsViewModel.g.cpp" +#include "KeyChordViewModel.g.cpp" #include "LibraryResources.h" #include "../TerminalSettingsModel/AllShortcutActions.h" +#include "EnumEntry.h" +#include "ColorSchemeViewModel.h" +#include "..\WinRTUtils\inc\Utils.h" using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; @@ -18,383 +24,1380 @@ using namespace winrt::Windows::UI::Xaml::Data; using namespace winrt::Windows::UI::Xaml::Navigation; using namespace winrt::Microsoft::Terminal::Settings::Model; +// TODO: GH 19056 +// multiple actions +// selection color +// the above arg types aren't implemented yet - they all have multiple values within them +// and require a different approach to binding/displaying. Selection color has color and IsIndex16, +// multiple actions is... multiple actions +// for now, do not support these shortcut actions in the new action editor +inline const std::set UnimplementedShortcutActions = { + winrt::Microsoft::Terminal::Settings::Model::ShortcutAction::MultipleActions, + winrt::Microsoft::Terminal::Settings::Model::ShortcutAction::ColorSelection +}; + namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - KeyBindingViewModel::KeyBindingViewModel(const IObservableVector& availableActions) : - KeyBindingViewModel(nullptr, availableActions.First().Current(), availableActions) {} + static constexpr std::wstring_view ActionsPageId{ L"page.actions" }; + + CommandViewModel::CommandViewModel(Command cmd, std::vector keyChordList, const Editor::ActionsViewModel actionsPageVM) : + _command{ cmd }, + _keyChordList{ keyChordList }, + _actionsPageVM{ actionsPageVM } + { + } - KeyBindingViewModel::KeyBindingViewModel(const Control::KeyChord& keys, const hstring& actionName, const IObservableVector& availableActions) : - _CurrentKeys{ keys }, - _KeyChordText{ KeyChordSerialization::ToString(keys) }, - _CurrentAction{ actionName }, - _ProposedAction{ box_value(actionName) }, - _AvailableActions{ availableActions } + void CommandViewModel::Initialize() { + const auto actionsPageVM{ _actionsPageVM.get() }; + if (!actionsPageVM) + { + // The parent page is gone, just return early + return; + } + const auto shortcutActionsAndNames = actionsPageVM.AvailableShortcutActionsAndNames(); + std::vector keyChordVMs; + for (const auto keys : _keyChordList) + { + auto kcVM{ make(keys) }; + _RegisterKeyChordVMEvents(kcVM); + keyChordVMs.push_back(kcVM); + } + _KeyChordViewModelList = single_threaded_observable_vector(std::move(keyChordVMs)); + + std::vector shortcutActions; + for (const auto [action, name] : shortcutActionsAndNames) + { + shortcutActions.emplace_back(name); + } + std::sort(shortcutActions.begin(), shortcutActions.end()); + _AvailableShortcutActions = single_threaded_observable_vector(std::move(shortcutActions)); + + const auto shortcutActionString = shortcutActionsAndNames.Lookup(_command.ActionAndArgs().Action()); + ProposedShortcutActionName(winrt::box_value(shortcutActionString)); + _CreateAndInitializeActionArgsVMHelper(); + // 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. + // This allows us to create a new ActionArgsVM when the shortcut action changes PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) { - const auto viewModelProperty{ args.PropertyName() }; - if (viewModelProperty == L"CurrentKeys") + if (const auto actionsPageVM{ _actionsPageVM.get() }) { - _KeyChordText = KeyChordSerialization::ToString(_CurrentKeys); - _NotifyChanges(L"KeyChordText"); + const auto viewModelProperty{ args.PropertyName() }; + if (viewModelProperty == L"ProposedShortcutActionName") + { + const auto actionString = unbox_value(ProposedShortcutActionName()); + const auto actionEnum = actionsPageVM.NameToActionMap().Lookup(actionString); + const auto emptyArgs = ActionArgFactory::GetEmptyArgsForAction(actionEnum); + // TODO: GH 19056 + // probably need some better default values for empty args and/or validation + // eg. for sendInput, where "input" is a required argument, "input" gets set to an empty string which does not satisfy the requirement + // i.e. if the user hits "save" immediately after switching to sendInput as the action (without adding something to the input field), they'll get an error + // there are some other cases as well + Model::ActionAndArgs newActionAndArgs{ actionEnum, emptyArgs }; + _command.ActionAndArgs(newActionAndArgs); + if (_IsNewCommand) + { + actionsPageVM.AttemptRegenerateCommandID(_command); + } + else if (!IsUserAction()) + { + _ReplaceCommandWithUserCopy(true); + return; + } + _CreateAndInitializeActionArgsVMHelper(); + } } - else if (viewModelProperty == L"IsContainerFocused" || - viewModelProperty == L"IsEditButtonFocused" || - viewModelProperty == L"IsHovered" || - viewModelProperty == L"IsAutomationPeerAttached" || - viewModelProperty == L"IsInEditMode") + }); + } + + winrt::hstring CommandViewModel::DisplayName() + { + if (_cachedDisplayName.empty()) + { + _cachedDisplayName = _command.Name(); + } + return _cachedDisplayName; + } + + winrt::hstring CommandViewModel::Name() + { + return _command.HasName() ? _command.Name() : L""; + } + + void CommandViewModel::Name(const winrt::hstring& newName) + { + _command.Name(newName); + if (newName.empty()) + { + // if the name was cleared, refresh the DisplayName + _NotifyChanges(L"DisplayName", L"DisplayNameAndKeyChordAutomationPropName"); + } + _cachedDisplayName.clear(); + } + + winrt::hstring CommandViewModel::DisplayNameAndKeyChordAutomationPropName() + { + return DisplayName() + L", " + FirstKeyChordText(); + } + + winrt::hstring CommandViewModel::FirstKeyChordText() + { + if (_KeyChordViewModelList.Size() != 0) + { + return _KeyChordViewModelList.GetAt(0).KeyChordText(); + } + return L""; + } + + winrt::hstring CommandViewModel::ID() + { + return _command.ID(); + } + + bool CommandViewModel::IsUserAction() + { + return _command.Origin() == OriginTag::User; + } + + void CommandViewModel::Edit_Click() + { + EditRequested.raise(*this, *this); + } + + void CommandViewModel::Delete_Click() + { + DeleteRequested.raise(*this, *this); + } + + void CommandViewModel::AddKeybinding_Click() + { + auto kbdVM{ make_self(nullptr) }; + kbdVM->IsInEditMode(true); + _RegisterKeyChordVMEvents(*kbdVM); + KeyChordViewModelList().Append(*kbdVM); + } + + winrt::hstring CommandViewModel::ActionNameTextBoxAutomationPropName() + { + return RS_(L"Actions_Name/Text"); + } + + winrt::hstring CommandViewModel::ShortcutActionComboBoxAutomationPropName() + { + return RS_(L"Actions_ShortcutAction/Text"); + } + + winrt::hstring CommandViewModel::AdditionalArgumentsControlAutomationPropName() + { + return RS_(L"Actions_Arguments/Text"); + } + + void CommandViewModel::_RegisterKeyChordVMEvents(Editor::KeyChordViewModel kcVM) + { + const auto id = ID(); + kcVM.AddKeyChordRequested([actionsPageVMWeakRef = _actionsPageVM, id](const Editor::KeyChordViewModel& sender, const Control::KeyChord& keys) { + if (const auto actionsPageVM{ actionsPageVMWeakRef.get() }) + { + actionsPageVM.AttemptAddOrModifyKeyChord(sender, id, keys, nullptr); + } + }); + kcVM.ModifyKeyChordRequested([actionsPageVMWeakRef = _actionsPageVM, id](const Editor::KeyChordViewModel& sender, const Editor::ModifyKeyChordEventArgs& args) { + if (const auto actionsPageVM{ actionsPageVMWeakRef.get() }) + { + actionsPageVM.AttemptAddOrModifyKeyChord(sender, id, args.NewKeys(), args.OldKeys()); + } + }); + kcVM.DeleteKeyChordRequested([&, actionsPageVMWeakRef = _actionsPageVM](const Editor::KeyChordViewModel& sender, const Control::KeyChord& args) { + if (const auto actionsPageVM{ actionsPageVMWeakRef.get() }) + { + std::erase_if(_keyChordList, + [&](const Control::KeyChord& kc) { return kc == args; }); + for (uint32_t i = 0; i < _KeyChordViewModelList.Size(); i++) + { + if (_KeyChordViewModelList.GetAt(i) == sender) + { + KeyChordViewModelList().RemoveAt(i); + break; + } + } + actionsPageVM.AttemptDeleteKeyChord(args); + } + }); + kcVM.PropertyChanged([weakThis{ get_weak() }](const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) { + if (const auto self{ weakThis.get() }) + { + const auto senderVM{ sender.as() }; + const auto propertyName{ args.PropertyName() }; + if (propertyName == L"IsInEditMode") + { + if (!senderVM.IsInEditMode()) + { + self->FocusContainer.raise(*self, senderVM); + } + } + } + }); + } + + void CommandViewModel::_RegisterActionArgsVMEvents(Editor::ActionArgsViewModel actionArgsVM) + { + actionArgsVM.PropagateColorSchemeRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateColorSchemeRequested.raise(*weak, wrapper); + } + } + }); + actionArgsVM.PropagateColorSchemeNamesRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) { - _NotifyChanges(L"ShowEditButton"); + if (wrapper) + { + weak->PropagateColorSchemeNamesRequested.raise(*weak, wrapper); + } } - else if (viewModelProperty == L"CurrentAction") + }); + actionArgsVM.PropagateWindowRootRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) { - _NotifyChanges(L"Name"); + if (wrapper) + { + weak->PropagateWindowRootRequested.raise(*weak, wrapper); + } + } + }); + actionArgsVM.WrapperValueChanged([weakThis = get_weak()](const IInspectable& /*sender*/, const IInspectable& /*args*/) { + if (auto weak = weakThis.get()) + { + // for new commands, make sure we generate a new ID every time any arg value changes + if (weak->_IsNewCommand) + { + if (const auto actionsPageVM{ weak->_actionsPageVM.get() }) + { + actionsPageVM.AttemptRegenerateCommandID(weak->_command); + } + } + else if (!weak->IsUserAction()) + { + weak->_ReplaceCommandWithUserCopy(false); + } + weak->_NotifyChanges(L"DisplayName"); } }); } - hstring KeyBindingViewModel::EditButtonName() const noexcept { return RS_(L"Actions_EditButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - hstring KeyBindingViewModel::CancelButtonName() const noexcept { return RS_(L"Actions_CancelButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - hstring KeyBindingViewModel::AcceptButtonName() const noexcept { return RS_(L"Actions_AcceptButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } - hstring KeyBindingViewModel::DeleteButtonName() const noexcept { return RS_(L"Actions_DeleteButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } + void CommandViewModel::_ReplaceCommandWithUserCopy(bool reinitialize) + { + // the user is attempting to edit an in-box action + // to handle this, we create a new command with the new values that has the same ID as the in-box action + // swap out our underlying command with the copy, tell the ActionsVM that the copy needs to be added to the action map + if (const auto actionsPageVM{ _actionsPageVM.get() }) + { + const auto newCmd = Model::Command::CopyAsUserCommand(_command); + _command = newCmd; + actionsPageVM.AttemptAddCopiedCommand(_command); + if (reinitialize) + { + // full reinitialize needed, recreate the action args VM + // (this happens when the shortcut action is being changed on an in-box action) + _CreateAndInitializeActionArgsVMHelper(); + } + else + { + // no need to reinitialize, just swap out the underlying data model + // (this happens when an additional argument is being changed on an in-box action) + auto actionArgsVMImpl{ get_self(_ActionArgsVM) }; + actionArgsVMImpl->ReplaceActionAndArgs(_command.ActionAndArgs()); + } + } + } - bool KeyBindingViewModel::ShowEditButton() const noexcept + void CommandViewModel::_CreateAndInitializeActionArgsVMHelper() { - return (IsContainerFocused() || IsEditButtonFocused() || IsHovered() || IsAutomationPeerAttached()) && !IsInEditMode(); + const auto actionArgsVM = make_self(_command.ActionAndArgs()); + _RegisterActionArgsVMEvents(*actionArgsVM); + actionArgsVM->Initialize(); + ActionArgsVM(*actionArgsVM); + _NotifyChanges(L"DisplayName"); } - void KeyBindingViewModel::ToggleEditMode() + ArgWrapper::ArgWrapper(const winrt::hstring& name, const winrt::hstring& type, const bool required, const Model::ArgTypeHint typeHint, const Windows::Foundation::IInspectable& value) : + _name{ name }, + _type{ type }, + _typeHint{ typeHint }, + _required{ required } { - // toggle edit mode - IsInEditMode(!_IsInEditMode); - if (_IsInEditMode) + Value(value); + } + + void ArgWrapper::Initialize() + { + if (_type == L"Model::ResizeDirection") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::ResizeDirection().GetView(), + L"Actions_ResizeDirection", + L"Content"); + } + else if (_type == L"Model::FocusDirection") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::FocusDirection().GetView(), + L"Actions_FocusDirection", + L"Content"); + } + else if (_type == L"SettingsTarget") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::SettingsTarget().GetView(), + L"Actions_SettingsTarget", + L"Content"); + } + else if (_type == L"MoveTabDirection") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::MoveTabDirection().GetView(), + L"Actions_MoveTabDirection", + L"Content"); + } + else if (_type == L"Microsoft::Terminal::Control::ScrollToMarkDirection") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::ScrollToMarkDirection().GetView(), + L"Actions_ScrollToMarkDirection", + L"Content"); + } + else if (_type == L"CommandPaletteLaunchMode") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::CommandPaletteLaunchMode().GetView(), + L"Actions_CommandPaletteLaunchMode", + L"Content"); + } + else if (_type == L"SuggestionsSource") + { + _InitializeFlagListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::SuggestionsSource().GetView(), + L"Actions_SuggestionsSource", + L"Content"); + } + else if (_type == L"FindMatchDirection") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::FindMatchDirection().GetView(), + L"Actions_FindMatchDirection", + L"Content"); + } + else if (_type == L"Model::DesktopBehavior") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::DesktopBehavior().GetView(), + L"Actions_DesktopBehavior", + L"Content"); + } + else if (_type == L"Model::MonitorBehavior") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::MonitorBehavior().GetView(), + L"Actions_MonitorBehavior", + L"Content"); + } + else if (_type == L"winrt::Microsoft::Terminal::Control::ClearBufferType") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::ClearBufferType().GetView(), + L"Actions_ClearBufferType", + L"Content"); + } + else if (_type == L"SelectOutputDirection") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::SelectOutputDirection().GetView(), + L"Actions_SelectOutputDirection", + L"Content"); + } + else if (_type == L"Model::SplitDirection") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::SplitDirection().GetView(), + L"Actions_SplitDirection", + L"Content"); + } + else if (_type == L"SplitType") + { + _InitializeEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::SplitType().GetView(), + L"Actions_SplitType", + L"Content"); + } + else if (_type == L"Windows::Foundation::IReference") + { + _InitializeNullableEnumListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::TabSwitcherMode().GetView(), + L"Actions_TabSwitcherMode", + L"Content"); + } + else if (_type == L"Windows::Foundation::IReference") + { + _InitializeNullableFlagListAndValue( + winrt::Microsoft::Terminal::Settings::Model::EnumMappings::CopyFormat().GetView(), + L"Actions_CopyFormat", + L"Content"); + } + + else if (_type == L"Windows::Foundation::IReference" || + _type == L"Windows::Foundation::IReference") + { + ColorSchemeRequested.raise(*this, *this); + } + else if (_typeHint == Model::ArgTypeHint::ColorScheme) + { + // special case of string, emit an event letting the actionsVM know we need the list of color scheme names + ColorSchemeNamesRequested.raise(*this, *this); + + // even though the arg type is technically a string, we want an enum list for color schemes specifically + std::vector namesList; + const auto currentSchemeName = unbox_value(_Value); + auto nullEntry = winrt::make(RS_(L"Actions_NullEnumValue"), nullptr, -1); + if (currentSchemeName.empty()) + { + _EnumValue = nullEntry; + } + for (const auto colorSchemeName : _ColorSchemeNamesList) + { + // eventually we will want to use localized names for the enum entries, for now just use what the settings model gives us + auto entry = winrt::make(colorSchemeName, winrt::box_value(colorSchemeName)); + namesList.emplace_back(entry); + if (currentSchemeName == colorSchemeName) + { + _EnumValue = entry; + } + } + namesList.emplace_back(nullEntry); + _EnumList = winrt::single_threaded_observable_vector(std::move(namesList)); + _NotifyChanges(L"EnumList", L"EnumValue"); + } + } + + safe_void_coroutine ArgWrapper::BrowseForFile_Click(const IInspectable&, const RoutedEventArgs&) + { + WindowRootRequested.raise(*this, *this); + auto lifetime = get_strong(); + + static constexpr winrt::guid clientGuidFiles{ 0xbd00ae34, 0x839b, 0x43f6, { 0x8b, 0x94, 0x12, 0x37, 0x1a, 0xfe, 0xea, 0xb5 } }; + const auto parentHwnd{ reinterpret_cast(_WindowRoot.GetHostingWindow()) }; + auto path = co_await OpenFilePicker(parentHwnd, [](auto&& dialog) { + THROW_IF_FAILED(dialog->SetClientGuid(clientGuidFiles)); + try + { + auto folderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_ComputerFolder, KF_FLAG_DEFAULT, nullptr) }; + dialog->SetDefaultFolder(folderShellItem.get()); + } + CATCH_LOG(); // non-fatal + }); + + if (!path.empty()) + { + StringBindBack(path); + } + } + + safe_void_coroutine ArgWrapper::BrowseForFolder_Click(const IInspectable&, const RoutedEventArgs&) + { + WindowRootRequested.raise(*this, *this); + auto lifetime = get_strong(); + + static constexpr winrt::guid clientGuidFolders{ 0xa611027, 0x42be, 0x4665, { 0xaf, 0xf1, 0x3f, 0x22, 0x26, 0xe9, 0xf7, 0x4d } }; + ; + const auto parentHwnd{ reinterpret_cast(_WindowRoot.GetHostingWindow()) }; + auto path = co_await OpenFilePicker(parentHwnd, [](auto&& dialog) { + THROW_IF_FAILED(dialog->SetClientGuid(clientGuidFolders)); + try + { + auto folderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_ComputerFolder, KF_FLAG_DEFAULT, nullptr) }; + dialog->SetDefaultFolder(folderShellItem.get()); + } + CATCH_LOG(); // non-fatal + + DWORD flags{}; + THROW_IF_FAILED(dialog->GetOptions(&flags)); + THROW_IF_FAILED(dialog->SetOptions(flags | FOS_PICKFOLDERS)); // folders only + }); + + if (!path.empty()) + { + StringBindBack(path); + } + } + + void ArgWrapper::EnumValue(const Windows::Foundation::IInspectable& enumValue) + { + if (_EnumValue != enumValue) { - // if we're in edit mode, - // - prepopulate the text box with the current keys - // - reset the combo box with the current action - ProposedKeys(_CurrentKeys); - ProposedAction(box_value(_CurrentAction)); + _EnumValue = enumValue; + Value(_EnumValue.as().EnumValue()); } } - void KeyBindingViewModel::AttemptAcceptChanges() + winrt::hstring ArgWrapper::UnboxString(const Windows::Foundation::IInspectable& value) { - AttemptAcceptChanges(_ProposedKeys); + return winrt::unbox_value(value); } - void KeyBindingViewModel::AttemptAcceptChanges(const Control::KeyChord newKeys) + int32_t ArgWrapper::UnboxInt32(const Windows::Foundation::IInspectable& value) { - const auto args{ make_self(_CurrentKeys, // OldKeys - newKeys, // NewKeys - _IsNewlyAdded ? hstring{} : _CurrentAction, // OldAction - unbox_value(_ProposedAction)) }; // NewAction - ModifyKeyBindingRequested.raise(*this, *args); + return winrt::unbox_value(value); } - void KeyBindingViewModel::CancelChanges() + float ArgWrapper::UnboxInt32Optional(const Windows::Foundation::IInspectable& value) { - if (_IsNewlyAdded) + const auto unboxed = winrt::unbox_value>(value); + if (unboxed) { - DeleteNewlyAddedKeyBinding.raise(*this, nullptr); + return static_cast(unboxed.Value()); } else { - ToggleEditMode(); + return NAN; } } - ActionsViewModel::ActionsViewModel(Model::CascadiaSettings settings) : - _Settings{ settings } + uint32_t ArgWrapper::UnboxUInt32(const Windows::Foundation::IInspectable& value) + { + return winrt::unbox_value(value); + } + + float ArgWrapper::UnboxUInt32Optional(const Windows::Foundation::IInspectable& value) + { + const auto unboxed = winrt::unbox_value>(value); + if (unboxed) + { + return static_cast(unboxed.Value()); + } + else + { + return NAN; + } + } + + float ArgWrapper::UnboxFloat(const Windows::Foundation::IInspectable& value) + { + return winrt::unbox_value(value); + } + + bool ArgWrapper::UnboxBool(const Windows::Foundation::IInspectable& value) + { + return winrt::unbox_value(value); + } + + winrt::Windows::Foundation::IReference ArgWrapper::UnboxBoolOptional(const Windows::Foundation::IInspectable& value) + { + if (!value) + { + return nullptr; + } + return winrt::unbox_value>(value); + } + + winrt::Windows::Foundation::IReference ArgWrapper::UnboxTerminalCoreColorOptional(const Windows::Foundation::IInspectable& value) { - // Populate AvailableActionAndArgs - _AvailableActionMap = single_threaded_map(); - std::vector availableActionAndArgs; - for (const auto& [name, actionAndArgs] : _Settings.ActionMap().AvailableActions()) + if (value) + { + return unbox_value>(value); + } + else { - availableActionAndArgs.push_back(name); - _AvailableActionMap.Insert(name, actionAndArgs); + return nullptr; } - std::sort(begin(availableActionAndArgs), end(availableActionAndArgs)); - _AvailableActionAndArgs = single_threaded_observable_vector(std::move(availableActionAndArgs)); + } - // Convert the key bindings from our settings into a view model representation - const auto& keyBindingMap{ _Settings.ActionMap().KeyBindings() }; - std::vector keyBindingList; - keyBindingList.reserve(keyBindingMap.Size()); - for (const auto& [keys, cmd] : keyBindingMap) + winrt::Windows::Foundation::IReference ArgWrapper::UnboxWindowsUIColorOptional(const Windows::Foundation::IInspectable& value) + { + if (value) + { + const auto winUIColor = unbox_value>(value).Value(); + const Microsoft::Terminal::Core::Color terminalColor{ winUIColor.R, winUIColor.G, winUIColor.B, winUIColor.A }; + return Windows::Foundation::IReference{ terminalColor }; + } + else { - // convert the cmd into a KeyBindingViewModel - auto container{ make_self(keys, cmd.Name(), _AvailableActionAndArgs) }; - _RegisterEvents(container); - keyBindingList.push_back(*container); + return nullptr; } + } - std::sort(begin(keyBindingList), end(keyBindingList), KeyBindingViewModelComparator{}); - _KeyBindingList = single_threaded_observable_vector(std::move(keyBindingList)); + void ArgWrapper::StringBindBack(const winrt::hstring& newValue) + { + if (UnboxString(_Value) != newValue) + { + Value(box_value(newValue)); + } } - void ActionsViewModel::OnAutomationPeerAttached() + void ArgWrapper::Int32BindBack(const double newValue) { - _AutomationPeerAttached = true; - for (const auto& kbdVM : _KeyBindingList) + if (UnboxInt32(_Value) != newValue) { - // To create a more accessible experience, we want the "edit" buttons to _always_ - // appear when a screen reader is attached. This ensures that the edit buttons are - // accessible via the UIA tree. - get_self(kbdVM)->IsAutomationPeerAttached(_AutomationPeerAttached); + Value(box_value(static_cast(newValue))); } } - void ActionsViewModel::AddNewKeybinding() + void ArgWrapper::Int32OptionalBindBack(const double newValue) { - // Create the new key binding and register all of the event handlers. - auto kbdVM{ make_self(_AvailableActionAndArgs) }; - _RegisterEvents(kbdVM); - kbdVM->DeleteNewlyAddedKeyBinding({ this, &ActionsViewModel::_KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler }); + if (!isnan(newValue)) + { + const auto currentValue = UnboxInt32Optional(_Value); + if (isnan(currentValue) || static_cast(currentValue) != static_cast(newValue)) + { + Value(box_value(static_cast(newValue))); + } + } + else if (!_Value) + { + Value(nullptr); + } + } - // Manually add the editing background. This needs to be done in Actions not the view model. - // We also have to do this manually because it hasn't been added to the list yet. - kbdVM->IsInEditMode(true); - // Emit an event to let the page know to update the background of this key binding VM - UpdateBackground.raise(*this, *kbdVM); + void ArgWrapper::UInt32BindBack(const double newValue) + { + if (UnboxUInt32(_Value) != newValue) + { + Value(box_value(static_cast(newValue))); + } + } + + void ArgWrapper::UInt32OptionalBindBack(const double newValue) + { + if (!isnan(newValue)) + { + const auto currentValue = UnboxUInt32Optional(_Value); + if (isnan(currentValue) || static_cast(currentValue) != static_cast(newValue)) + { + Value(box_value(static_cast(newValue))); + } + } + else if (!_Value) + { + Value(nullptr); + } + } + + void ArgWrapper::FloatBindBack(const double newValue) + { + if (UnboxFloat(_Value) != newValue) + { + Value(box_value(static_cast(newValue))); + } + } + + void ArgWrapper::BoolOptionalBindBack(const Windows::Foundation::IReference newValue) + { + if (newValue) + { + const auto currentValue = UnboxBoolOptional(_Value); + if (!currentValue || currentValue.Value() != newValue.Value()) + { + Value(box_value(newValue)); + } + } + else if (_Value) + { + Value(nullptr); + } + } - // IMPORTANT: do this _after_ setting IsInEditMode. Otherwise, it'll get deleted immediately - // by the PropertyChangedHandler below (where we delete any IsNewlyAdded items) - kbdVM->IsNewlyAdded(true); - _KeyBindingList.InsertAt(0, *kbdVM); - FocusContainer.raise(*this, *kbdVM); + void ArgWrapper::TerminalCoreColorBindBack(const winrt::Windows::Foundation::IReference newValue) + { + if (newValue) + { + const auto currentValue = UnboxTerminalCoreColorOptional(_Value); + if (!currentValue || currentValue.Value() != newValue.Value()) + { + Value(box_value(newValue)); + } + } + else if (_Value) + { + Value(nullptr); + } } - void ActionsViewModel::_KeyBindingViewModelPropertyChangedHandler(const IInspectable& sender, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) + void ArgWrapper::WindowsUIColorBindBack(const winrt::Windows::Foundation::IReference newValue) { - const auto senderVM{ sender.as() }; - const auto propertyName{ args.PropertyName() }; - if (propertyName == L"IsInEditMode") + if (newValue) { - if (senderVM.IsInEditMode()) + const auto terminalCoreColor = unbox_value>(newValue).Value(); + const Windows::UI::Color winuiColor{ + .A = terminalCoreColor.A, + .R = terminalCoreColor.R, + .G = terminalCoreColor.G, + .B = terminalCoreColor.B + }; + // only set to the new value if our current value is not the same + // unfortunately the Value setter does not do this check properly since + // we create a whole new IReference even for the same underlying color + if (_Value) { - // Ensure that... - // 1. we move focus to the edit mode controls - // 2. any actions that were newly added are removed - // 3. this is the only entry that is in edit mode - for (int32_t i = _KeyBindingList.Size() - 1; i >= 0; --i) + const auto currentValue = unbox_value>(_Value).Value(); + if (currentValue == winuiColor) { - const auto& kbdVM{ _KeyBindingList.GetAt(i) }; - if (senderVM == kbdVM) + return; + } + } + Value(box_value(Windows::Foundation::IReference{ winuiColor })); + } + else if (_Value) + { + Value(nullptr); + } + } + + template + void ArgWrapper::_InitializeEnumListAndValue( + const winrt::Windows::Foundation::Collections::IMapView& mappings, + const winrt::hstring& resourceSectionAndType, + const winrt::hstring& resourceProperty) + { + std::vector enumList; + std::unordered_set addedEnums; + EnumType unboxedValue{}; + + if (_Value) + { + unboxedValue = winrt::unbox_value(_Value); + } + + for (const auto& [enumKey, enumValue] : mappings) + { + if (addedEnums.emplace(enumValue).second) + { + winrt::hstring enumName = LocalizedNameForEnumName(resourceSectionAndType, enumKey, resourceProperty); + auto entry = winrt::make( + enumName, winrt::box_value(enumValue), static_cast(enumValue)); + enumList.emplace_back(entry); + if (_Value && unboxedValue == enumValue) + { + _EnumValue = entry; + } + } + } + std::sort(enumList.begin(), enumList.end(), winrt::Microsoft::Terminal::Settings::Editor::implementation::EnumEntryReverseComparator()); + _EnumList = winrt::single_threaded_observable_vector(std::move(enumList)); + if (!_EnumValue) + { + _EnumValue = _EnumList.GetAt(0); + } + } + + template + void ArgWrapper::_InitializeNullableEnumListAndValue( + const winrt::Windows::Foundation::Collections::IMapView& mappings, + const winrt::hstring& resourceSectionAndType, + const winrt::hstring& resourceProperty) + { + std::vector enumList; + std::unordered_set addedEnums; + EnumType unboxedValue{}; + + auto nullEntry = winrt::make( + RS_(L"Actions_NullEnumValue"), nullptr, -1); + + if (_Value) + { + unboxedValue = winrt::unbox_value(_Value); + } + else + { + _EnumValue = nullEntry; + } + + for (const auto& [enumKey, enumValue] : mappings) + { + if (addedEnums.emplace(enumValue).second) + { + winrt::hstring enumName = LocalizedNameForEnumName(resourceSectionAndType, enumKey, resourceProperty); + auto entry = winrt::make( + enumName, winrt::box_value(enumValue), static_cast(enumValue)); + enumList.emplace_back(entry); + if (_Value && unboxedValue == enumValue) + { + _EnumValue = entry; + } + } + } + std::sort(enumList.begin(), enumList.end(), winrt::Microsoft::Terminal::Settings::Editor::implementation::EnumEntryReverseComparator()); + enumList.emplace_back(nullEntry); + _EnumList = winrt::single_threaded_observable_vector(std::move(enumList)); + } + + template + void ArgWrapper::_InitializeFlagListAndValue( + const winrt::Windows::Foundation::Collections::IMapView& mappings, + const winrt::hstring& resourceSectionAndType, + const winrt::hstring& resourceProperty) + { + std::vector flagList; + std::unordered_set addedEnums; + EnumType unboxedValue{ 0 }; + + if (_Value) + { + unboxedValue = winrt::unbox_value(_Value); + } + + for (const auto& [flagKey, flagValue] : mappings) + { + if (flagKey != L"all" && flagKey != L"none" && addedEnums.emplace(flagValue).second) + { + winrt::hstring flagName = LocalizedNameForEnumName(resourceSectionAndType, flagKey, resourceProperty); + bool isSet = WI_IsAnyFlagSet(unboxedValue, flagValue); + auto entry = winrt::make( + flagName, winrt::box_value(flagValue), isSet, static_cast(flagValue)); + entry.PropertyChanged([this, flagValue](const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) { + const auto itemProperty = args.PropertyName(); + if (itemProperty == L"IsSet") { - // This is the view model entry that went into edit mode. - // Emit an event to let the page know to move focus to - // this VM's container. - FocusContainer.raise(*this, senderVM); + auto flagWrapper = sender.as(); + auto unboxed = winrt::unbox_value(_Value); + if (flagWrapper.IsSet()) + { + WI_SetAllFlags(unboxed, flagValue); + } + else + { + WI_ClearAllFlags(unboxed, flagValue); + } + Value(winrt::box_value(unboxed)); } - else if (kbdVM.IsNewlyAdded()) + }); + flagList.emplace_back(entry); + } + } + std::sort(flagList.begin(), flagList.end(), winrt::Microsoft::Terminal::Settings::Editor::implementation::FlagEntryReverseComparator()); + _FlagList = winrt::single_threaded_observable_vector(std::move(flagList)); + } + + template + void ArgWrapper::_InitializeNullableFlagListAndValue( + const winrt::Windows::Foundation::Collections::IMapView& mappings, + const winrt::hstring& resourceSectionAndType, + const winrt::hstring& resourceProperty) + { + std::vector flagList; + std::unordered_set addedEnums; + EnumType unboxedValue{ 0 }; + + auto nullEntry = winrt::make( + RS_(L"Actions_NullEnumValue"), nullptr, true, -1); + + if (_Value) + { + if (auto unboxedRef = winrt::unbox_value>(_Value)) + { + unboxedValue = unboxedRef.Value(); + nullEntry.IsSet(false); + } + } + + for (const auto& [flagKey, flagValue] : mappings) + { + if (flagKey != L"all" && flagKey != L"none" && addedEnums.emplace(flagValue).second) + { + winrt::hstring flagName = LocalizedNameForEnumName(resourceSectionAndType, flagKey, resourceProperty); + bool isSet = WI_IsAnyFlagSet(unboxedValue, flagValue); + auto entry = winrt::make( + flagName, winrt::box_value(flagValue), isSet, static_cast(flagValue)); + entry.PropertyChanged([this, flagValue, nullEntry](const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) { + const auto itemProperty = args.PropertyName(); + if (itemProperty == L"IsSet") { - // Remove any actions that were newly added - _KeyBindingList.RemoveAt(i); + auto flagWrapper = sender.as(); + EnumType localUnboxed{ 0 }; + auto unboxedRef = winrt::unbox_value>(_Value); + if (unboxedRef) + { + localUnboxed = unboxedRef.Value(); + } + + if (flagWrapper.IsSet()) + { + nullEntry.IsSet(false); + WI_SetAllFlags(localUnboxed, flagValue); + } + else + { + WI_ClearAllFlags(localUnboxed, flagValue); + } + Value(winrt::box_value(winrt::Windows::Foundation::IReference(localUnboxed))); } - else + }); + flagList.emplace_back(entry); + } + } + std::sort(flagList.begin(), flagList.end(), winrt::Microsoft::Terminal::Settings::Editor::implementation::FlagEntryReverseComparator()); + // nullEntry handler that toggles/clears other flags + nullEntry.PropertyChanged([this](const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) { + const auto itemProperty = args.PropertyName(); + if (itemProperty == L"IsSet") + { + auto flagWrapper = sender.as(); + if (flagWrapper.IsSet()) + { + for (const auto& flagEntry : _FlagList) { - // Exit edit mode for all other containers - get_self(kbdVM)->DisableEditMode(); + if (flagEntry.FlagName() != RS_(L"Actions_NullEnumValue")) + { + flagEntry.IsSet(false); + } } + Value(winrt::box_value(winrt::Windows::Foundation::IReference(nullptr))); + } + else + { + Value(winrt::box_value(winrt::Windows::Foundation::IReference(EnumType{ 0 }))); } } - else + }); + flagList.emplace_back(nullEntry); + _FlagList = winrt::single_threaded_observable_vector(std::move(flagList)); + } + + ActionArgsViewModel::ActionArgsViewModel(const Model::ActionAndArgs actionAndArgs) : + _actionAndArgs{ actionAndArgs } + { + } + + void ActionArgsViewModel::Initialize() + { + const auto shortcutArgs = _actionAndArgs.Args().as(); + if (shortcutArgs) + { + const auto shortcutArgsDescriptors = shortcutArgs.GetArgDescriptors(); + std::vector argValues; + uint32_t i = 0; + for (const auto argDescription : shortcutArgsDescriptors) { - // Emit an event to let the page know to move focus to - // this VM's container. - FocusContainer.raise(*this, senderVM); + const auto argAtIndex = shortcutArgs.GetArgAt(i); + const auto argName = argDescription.Name; + const auto argType = argDescription.Type; + const auto argTypeHint = argDescription.TypeHint; + const auto argRequired = argDescription.Required; + const auto item = make_self(argName, argType, argRequired, argTypeHint, argAtIndex); + item->PropertyChanged([weakThis = get_weak(), i](const IInspectable& sender, const PropertyChangedEventArgs& args) { + if (auto weak = weakThis.get()) + { + const auto itemProperty{ args.PropertyName() }; + if (itemProperty == L"Value") + { + const auto argWrapper = sender.as(); + const auto newValue = argWrapper.Value(); + weak->_actionAndArgs.Args().as().SetArgAt(i, newValue); + weak->WrapperValueChanged.raise(*weak, nullptr); + } + } + }); + item->ColorSchemeRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateColorSchemeRequested.raise(*weak, wrapper); + } + } + }); + item->ColorSchemeNamesRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateColorSchemeNamesRequested.raise(*weak, wrapper); + } + } + }); + item->WindowRootRequested([weakThis = get_weak()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (auto weak = weakThis.get()) + { + if (wrapper) + { + weak->PropagateWindowRootRequested.raise(*weak, wrapper); + } + } + }); + item->Initialize(); + argValues.push_back(*item); + i++; } - // Emit an event to let the page know to update the background of this key binding VM - UpdateBackground.raise(*this, senderVM); + _ArgValues = single_threaded_observable_vector(std::move(argValues)); } } - void ActionsViewModel::_KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& keys) + bool ActionArgsViewModel::HasArgs() const noexcept { - // Update the settings model - _Settings.ActionMap().DeleteKeyBinding(keys); + return _actionAndArgs.Args() != nullptr; + } + + void ActionArgsViewModel::ReplaceActionAndArgs(Model::ActionAndArgs newActionAndArgs) + { + _actionAndArgs = newActionAndArgs; + } + + KeyChordViewModel::KeyChordViewModel(Control::KeyChord currentKeys) + { + CurrentKeys(currentKeys); + } + + void KeyChordViewModel::CurrentKeys(const Control::KeyChord& newKeys) + { + _currentKeys = newKeys; + KeyChordText(Model::KeyChordSerialization::ToString(_currentKeys)); + } - // Find the current container in our list and remove it. - // This is much faster than rebuilding the entire ActionMap. - uint32_t index; - if (_KeyBindingList.IndexOf(senderVM, index)) + Control::KeyChord KeyChordViewModel::CurrentKeys() const noexcept + { + return _currentKeys; + } + + void KeyChordViewModel::ToggleEditMode() + { + // toggle edit mode + IsInEditMode(!_IsInEditMode); + if (_IsInEditMode) { - _KeyBindingList.RemoveAt(index); + // if we're in edit mode, populate the text box with the current keys + ProposedKeys(_currentKeys); + } + } - // Focus the new item at this index - if (_KeyBindingList.Size() != 0) - { - const auto newFocusedIndex{ std::clamp(index, 0u, _KeyBindingList.Size() - 1) }; - // Emit an event to let the page know to move focus to - // this VM's container. - FocusContainer.raise(*this, winrt::box_value(newFocusedIndex)); - } + void KeyChordViewModel::AttemptAcceptChanges() + { + if (!_currentKeys) + { + AddKeyChordRequested.raise(*this, _ProposedKeys); + } + else if (_currentKeys.Modifiers() != _ProposedKeys.Modifiers() || _currentKeys.Vkey() != _ProposedKeys.Vkey()) + { + const auto args{ make_self(_currentKeys, // OldKeys + _ProposedKeys) }; // NewKeys + ModifyKeyChordRequested.raise(*this, *args); } + else + { + // no changes being requested, toggle edit mode + ToggleEditMode(); + } + } + + void KeyChordViewModel::CancelChanges() + { + ToggleEditMode(); } - void ActionsViewModel::_KeyBindingViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args) + void KeyChordViewModel::DeleteKeyChord() { - const auto isNewAction{ !args.OldKeys() && args.OldActionName().empty() }; + DeleteKeyChordRequested.raise(*this, _currentKeys); + } - auto applyChangesToSettingsModel = [=]() { - // If the key chord was changed, - // update the settings model and view model appropriately - // NOTE: we still need to update the view model if we're working with a newly added action - if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey()) + hstring KeyChordViewModel::CancelButtonName() const noexcept { return RS_(L"Actions_CancelButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } + hstring KeyChordViewModel::AcceptButtonName() const noexcept { return RS_(L"Actions_AcceptButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } + hstring KeyChordViewModel::DeleteButtonName() const noexcept { return RS_(L"Actions_DeleteButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); } + + ActionsViewModel::ActionsViewModel(Model::CascadiaSettings settings) : + _Settings{ settings } + { + // Initialize the action->name and name->action maps before initializing the CommandVMs, they're going to need the maps + _AvailableActionsAndNamesMap = Model::ActionArgFactory::AvailableShortcutActionsAndNames(); + for (const auto unimplemented : UnimplementedShortcutActions) + { + _AvailableActionsAndNamesMap.Remove(unimplemented); + } + std::unordered_map actionNames; + for (const auto [action, name] : _AvailableActionsAndNamesMap) + { + actionNames.emplace(name, action); + } + _NameToActionMap = winrt::single_threaded_map(std::move(actionNames)); + + _MakeCommandVMsHelper(); + } + + Windows::Foundation::Collections::IMap ActionsViewModel::AvailableShortcutActionsAndNames() + { + return _AvailableActionsAndNamesMap; + } + + Windows::Foundation::Collections::IMap ActionsViewModel::NameToActionMap() + { + return _NameToActionMap; + } + + void ActionsViewModel::UpdateSettings(const Model::CascadiaSettings& settings) + { + _Settings = settings; + + // We want to re-initialize our CommandList, but we want to make sure + // we still have the same CurrentCommand as before (if that command still exists) + + // Store the ID of the current command + const auto currentCommandID = CurrentCommand() ? CurrentCommand().ID() : hstring{}; + + // Re-initialize the command vm list + _MakeCommandVMsHelper(); + + // Re-select the previously selected command if it exists + if (!currentCommandID.empty()) + { + const auto it = _CommandList.First(); + while (it.HasCurrent()) { - if (!isNewAction) + auto cmd = *it; + if (cmd.ID() == currentCommandID) { - // update settings model - _Settings.ActionMap().RebindKeys(args.OldKeys(), args.NewKeys()); + CurrentCommand(cmd); + break; } - - // update view model - auto senderVMImpl{ get_self(senderVM) }; - senderVMImpl->CurrentKeys(args.NewKeys()); + it.MoveNext(); + } + if (!it.HasCurrent()) + { + // we didn't find the previously selected command + CurrentCommand(nullptr); + CurrentPage(ActionsSubPage::Base); } + } + else + { + // didn't have a command, + // so skip over looking through the command + CurrentCommand(nullptr); + CurrentPage(ActionsSubPage::Base); + } + } + + void ActionsViewModel::MarkAsVisited() + { + Model::ApplicationState::SharedInstance().DismissBadge(ActionsPageId); + _NotifyChanges(L"DisplayBadge"); + } + + bool ActionsViewModel::DisplayBadge() const noexcept + { + return !Model::ApplicationState::SharedInstance().BadgeDismissed(ActionsPageId); + } - // If the action was changed, - // update the settings model and view model appropriately - // NOTE: no need to check for "isNewAction" here. != already. - if (args.OldActionName() != args.NewActionName()) + void ActionsViewModel::_MakeCommandVMsHelper() + { + const auto& allCommands{ _Settings.ActionMap().AllCommands() }; + std::vector commandList; + commandList.reserve(allCommands.Size()); + for (const auto& cmd : allCommands) + { + if (!UnimplementedShortcutActions.contains(cmd.ActionAndArgs().Action())) { - // convert the action's name into a view model. - const auto& newAction{ _AvailableActionMap.Lookup(args.NewActionName()) }; + std::vector keyChordList = wil::to_vector(_Settings.ActionMap().AllKeyBindingsForAction(cmd.ID())); + auto cmdVM{ make_self(cmd, std::move(keyChordList), *this) }; + _RegisterCmdVMEvents(cmdVM); + cmdVM->Initialize(); + commandList.push_back(*cmdVM); + } + } + + std::sort(commandList.begin(), commandList.end(), [](const Editor::CommandViewModel& lhs, const Editor::CommandViewModel& rhs) { + return lhs.DisplayName() < rhs.DisplayName(); + }); + _CommandList = single_threaded_observable_vector(std::move(commandList)); + } + + void ActionsViewModel::AddNewCommand() + { + const auto newCmd = Model::Command::NewUserCommand(); + // construct a command using the first shortcut action from our list + const auto shortcutAction = _AvailableActionsAndNamesMap.First().Current().Key(); + const auto args = ActionArgFactory::GetEmptyArgsForAction(shortcutAction); + newCmd.ActionAndArgs(Model::ActionAndArgs{ shortcutAction, args }); + _Settings.ActionMap().AddAction(newCmd, nullptr); + auto cmdVM{ make_self(newCmd, std::vector{}, *this) }; + cmdVM->IsNewCommand(true); + _RegisterCmdVMEvents(cmdVM); + cmdVM->Initialize(); + _CommandList.Append(*cmdVM); + CurrentCommand(*cmdVM); + CurrentPage(ActionsSubPage::Edit); + } + + void ActionsViewModel::CurrentCommand(const Editor::CommandViewModel& newCommand) + { + _CurrentCommand = newCommand; + } + + Editor::CommandViewModel ActionsViewModel::CurrentCommand() + { + return _CurrentCommand; + } - // update settings model - _Settings.ActionMap().RegisterKeyBinding(args.NewKeys(), newAction); + void ActionsViewModel::CmdListItemClicked(const IInspectable& /*sender*/, const winrt::Windows::UI::Xaml::Controls::ItemClickEventArgs& e) + { + if (const auto item = e.ClickedItem()) + { + CurrentCommand(item.try_as()); + CurrentPage(ActionsSubPage::Edit); + } + } + + void ActionsViewModel::AttemptDeleteKeyChord(const Control::KeyChord& keys) + { + // Update the settings model + assert(keys); + if (keys) + { + _Settings.ActionMap().DeleteKeyBinding(keys); + } + } + + void ActionsViewModel::AttemptAddOrModifyKeyChord(const Editor::KeyChordViewModel& senderVM, winrt::hstring commandID, const Control::KeyChord& newKeys, const Control::KeyChord& oldKeys) + { + auto applyChangesToSettingsModel = [=]() { + // update settings model + if (oldKeys) + { + // if oldKeys is not null, this is a rebinding + // delete oldKeys and then add newKeys + _Settings.ActionMap().DeleteKeyBinding(oldKeys); + } + if (!Model::KeyChordSerialization::ToString(newKeys).empty()) + { + _Settings.ActionMap().AddKeyBinding(newKeys, commandID); // update view model - auto senderVMImpl{ get_self(senderVM) }; - senderVMImpl->CurrentAction(args.NewActionName()); - senderVMImpl->IsNewlyAdded(false); + auto senderVMImpl{ get_self(senderVM) }; + senderVMImpl->CurrentKeys(newKeys); + } + + // reset the flyout if it's there + if (const auto flyout = senderVM.AcceptChangesFlyout()) + { + flyout.Hide(); + senderVM.AcceptChangesFlyout(nullptr); } + // toggle edit mode + senderVM.ToggleEditMode(); }; - // Check for this special case: - // we're changing the key chord, - // but the new key chord is already in use - bool conflictFound{ false }; - if (isNewAction || args.OldKeys().Modifiers() != args.NewKeys().Modifiers() || args.OldKeys().Vkey() != args.NewKeys().Vkey()) - { - const auto& conflictingCmd{ _Settings.ActionMap().GetActionByKeyChord(args.NewKeys()) }; - if (conflictingCmd) - { - conflictFound = true; - // We're about to overwrite another key chord. - // Display a confirmation dialog. - TextBlock errorMessageTB{}; - errorMessageTB.Text(RS_(L"Actions_RenameConflictConfirmationMessage")); - errorMessageTB.TextWrapping(TextWrapping::Wrap); - - const auto conflictingCmdName{ conflictingCmd.Name() }; - TextBlock conflictingCommandNameTB{}; - conflictingCommandNameTB.Text(fmt::format(L"\"{}\"", conflictingCmdName.empty() ? RS_(L"Actions_UnnamedCommandName") : conflictingCmdName)); - conflictingCommandNameTB.FontStyle(Windows::UI::Text::FontStyle::Italic); - conflictingCommandNameTB.TextWrapping(TextWrapping::Wrap); - - TextBlock confirmationQuestionTB{}; - confirmationQuestionTB.Text(RS_(L"Actions_RenameConflictConfirmationQuestion")); - confirmationQuestionTB.TextWrapping(TextWrapping::Wrap); - - Button acceptBTN{}; - acceptBTN.Content(box_value(RS_(L"Actions_RenameConflictConfirmationAcceptButton"))); - acceptBTN.Click([=](auto&, auto&) { - // remove conflicting key binding from list view - const auto containerIndex{ _GetContainerIndexByKeyChord(args.NewKeys()) }; - _KeyBindingList.RemoveAt(*containerIndex); - - // remove flyout - senderVM.AcceptChangesFlyout().Hide(); - senderVM.AcceptChangesFlyout(nullptr); - - // update settings model and view model - applyChangesToSettingsModel(); - senderVM.ToggleEditMode(); - }); + const auto& conflictingCmd{ _Settings.ActionMap().GetActionByKeyChord(newKeys) }; + if (conflictingCmd) + { + // We're about to overwrite another key chord. + // Display a confirmation dialog. + TextBlock errorMessageTB{}; + errorMessageTB.Text(RS_(L"Actions_RenameConflictConfirmationMessage")); - StackPanel flyoutStack{}; - flyoutStack.Children().Append(errorMessageTB); - flyoutStack.Children().Append(conflictingCommandNameTB); - flyoutStack.Children().Append(confirmationQuestionTB); - flyoutStack.Children().Append(acceptBTN); + const auto conflictingCmdName{ conflictingCmd.Name() }; + TextBlock conflictingCommandNameTB{}; + conflictingCommandNameTB.Text(fmt::format(L"\"{}\"", conflictingCmdName.empty() ? RS_(L"Actions_UnnamedCommandName") : conflictingCmdName)); + conflictingCommandNameTB.FontStyle(Windows::UI::Text::FontStyle::Italic); - // This should match CustomFlyoutPresenterStyle in CommonResources.xaml! - // We don't have access to those resources here, so it's easier to just copy them over. - // This allows the flyout text to wrap - Style acceptChangesFlyoutStyle{ winrt::xaml_typename() }; - Setter horizontalScrollModeStyleSetter{ ScrollViewer::HorizontalScrollModeProperty(), box_value(ScrollMode::Disabled) }; - Setter horizontalScrollBarVisibilityStyleSetter{ ScrollViewer::HorizontalScrollBarVisibilityProperty(), box_value(ScrollBarVisibility::Disabled) }; - acceptChangesFlyoutStyle.Setters().Append(horizontalScrollModeStyleSetter); - acceptChangesFlyoutStyle.Setters().Append(horizontalScrollBarVisibilityStyleSetter); + TextBlock confirmationQuestionTB{}; + confirmationQuestionTB.Text(RS_(L"Actions_RenameConflictConfirmationQuestion")); - Flyout acceptChangesFlyout{}; - acceptChangesFlyout.FlyoutPresenterStyle(acceptChangesFlyoutStyle); - acceptChangesFlyout.Content(flyoutStack); - senderVM.AcceptChangesFlyout(acceptChangesFlyout); - } - } + Button acceptBTN{}; + acceptBTN.Content(box_value(RS_(L"Actions_RenameConflictConfirmationAcceptButton"))); + acceptBTN.Click([=](auto&, auto&) { + // update settings model and view model + applyChangesToSettingsModel(); + }); + + StackPanel flyoutStack{}; + flyoutStack.Children().Append(errorMessageTB); + flyoutStack.Children().Append(conflictingCommandNameTB); + flyoutStack.Children().Append(confirmationQuestionTB); + flyoutStack.Children().Append(acceptBTN); - // if there was a conflict, the flyout we created will handle whether changes need to be propagated - // otherwise, go ahead and apply the changes - if (!conflictFound) + Flyout acceptChangesFlyout{}; + acceptChangesFlyout.Content(flyoutStack); + senderVM.AcceptChangesFlyout(acceptChangesFlyout); + } + else { // update settings model and view model applyChangesToSettingsModel(); - - // We NEED to toggle the edit mode here, - // so that if nothing changed, we still exit - // edit mode. - senderVM.ToggleEditMode(); } } - void ActionsViewModel::_KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& /*args*/) + void ActionsViewModel::AttemptAddCopiedCommand(const Model::Command& newCommand) { - for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i) + // The command VM calls this when the user has edited an in-box action + // newCommand is a copy of the in-box action that was edited, but with OriginTag::User + // add it to the action map + _Settings.ActionMap().AddAction(newCommand, nullptr); + } + + void ActionsViewModel::AttemptRegenerateCommandID(const Model::Command& command) + { + _Settings.UpdateCommandID(command, {}); + } + + void ActionsViewModel::_CmdVMEditRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& /*args*/) + { + CurrentCommand(senderVM); + CurrentPage(ActionsSubPage::Edit); + } + + void ActionsViewModel::_CmdVMDeleteRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& /*args*/) + { + for (uint32_t i = 0; i < _CommandList.Size(); i++) { - const auto& kbdVM{ _KeyBindingList.GetAt(i) }; - if (kbdVM == senderVM) + if (_CommandList.GetAt(i) == senderVM) { - _KeyBindingList.RemoveAt(i); - return; + CommandList().RemoveAt(i); + break; } } + _Settings.ActionMap().DeleteUserCommand(senderVM.ID()); + CurrentCommand(nullptr); + CurrentPage(ActionsSubPage::Base); } - // Method Description: - // - performs a search on KeyBindingList by key chord. - // Arguments: - // - keys - the associated key chord of the command we're looking for - // Return Value: - // - the index of the view model referencing the command. If the command doesn't exist, nullopt - std::optional ActionsViewModel::_GetContainerIndexByKeyChord(const Control::KeyChord& keys) + void ActionsViewModel::_CmdVMPropagateColorSchemeRequestedHandler(const IInspectable& /*senderVM*/, const Editor::ArgWrapper& wrapper) { - for (uint32_t i = 0; i < _KeyBindingList.Size(); ++i) + if (wrapper) { - const auto kbdVM{ get_self(_KeyBindingList.GetAt(i)) }; - const auto& otherKeys{ kbdVM->CurrentKeys() }; - if (otherKeys && keys.Modifiers() == otherKeys.Modifiers() && keys.Vkey() == otherKeys.Vkey()) + const auto schemes = _Settings.GlobalSettings().ColorSchemes(); + const auto defaultAppearanceSchemeName = _Settings.ProfileDefaults().DefaultAppearance().LightColorSchemeName(); + for (const auto [name, scheme] : schemes) { - return i; + if (name == defaultAppearanceSchemeName) + { + const auto schemeVM = winrt::make(scheme, nullptr, _Settings); + wrapper.DefaultColorScheme(schemeVM); + break; + } } } + } - // TODO GH #6900: - // an expedited search can be done if we use cmd.Name() - // to quickly search through the sorted list. - return std::nullopt; + void ActionsViewModel::_CmdVMPropagateColorSchemeNamesRequestedHandler(const IInspectable& /*senderVM*/, const Editor::ArgWrapper& wrapper) + { + if (wrapper) + { + std::vector namesList; + const auto schemes = _Settings.GlobalSettings().ColorSchemes(); + for (const auto [name, _] : schemes) + { + namesList.emplace_back(name); + } + wrapper.ColorSchemeNamesList(winrt::single_threaded_vector(std::move(namesList))); + } } - void ActionsViewModel::_RegisterEvents(com_ptr& kbdVM) + void ActionsViewModel::_RegisterCmdVMEvents(com_ptr& cmdVM) { - kbdVM->PropertyChanged({ this, &ActionsViewModel::_KeyBindingViewModelPropertyChangedHandler }); - kbdVM->DeleteKeyBindingRequested({ this, &ActionsViewModel::_KeyBindingViewModelDeleteKeyBindingHandler }); - kbdVM->ModifyKeyBindingRequested({ this, &ActionsViewModel::_KeyBindingViewModelModifyKeyBindingHandler }); - kbdVM->IsAutomationPeerAttached(_AutomationPeerAttached); + cmdVM->EditRequested({ this, &ActionsViewModel::_CmdVMEditRequestedHandler }); + cmdVM->DeleteRequested({ this, &ActionsViewModel::_CmdVMDeleteRequestedHandler }); + cmdVM->PropagateColorSchemeRequested({ this, &ActionsViewModel::_CmdVMPropagateColorSchemeRequestedHandler }); + cmdVM->PropagateColorSchemeNamesRequested({ this, &ActionsViewModel::_CmdVMPropagateColorSchemeNamesRequestedHandler }); } } diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h index e78cd946fd8..13cc3b166d5 100644 --- a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.h @@ -1,130 +1,317 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- ActionsViewModel.h + +Abstract: +- This contains the view models for everything related to the Actions pages (Actions.xaml and EditAction.xaml) +- ActionsViewModel: + - Contains the "current page" enum, which dictates whether we're in the top-level Actions page or the EditAction page + - Contains the full command list, and keeps track of the "current command" that is being edited + - These are in the form of CommandViewModel(s) + - Handles modification to the list of commands, i.e. addition/deletion + - Listens to each CommandViewModel for key chord events for addition/modification/deletion of keychords +- CommandViewModel: + - Constructed with a Model::Command object + - View model for each specific command item + - Contains higher-level detail about the command itself such as name, whether it is a user command, and the shortcut action type + - Contains an ActionArgsViewModel, which it creates according to the shortcut action type + - Recreates the ActionArgsViewModel whenever the shortcut action type changes + - Contains the full keybinding list, in the form of KeyChordViewModel(s) +- ActionArgsViewModel: + - Constructed with a Model::ActionAndArgs object + - Contains a vector of ArgWrapper(s), one ArgWrapper for each arg + - Listens and propagates changes to the ArgWrappers +- ArgWrapper: + - Wrapper for each argument + - Handles binding and bind back logic for the presentation and modification of the argument via the UI + - Has separate binding and bind back logic depending on the type of the argument +- KeyChordViewModel: + - Constructed with a Control::KeyChord object + - Handles binding and bind back logic for the presentation and modification of a keybinding via the UI + +--*/ #pragma once #include "ActionsViewModel.g.h" -#include "KeyBindingViewModel.g.h" -#include "ModifyKeyBindingEventArgs.g.h" +#include "NavigateToCommandArgs.g.h" +#include "CommandViewModel.g.h" +#include "ArgWrapper.g.h" +#include "ActionArgsViewModel.g.h" +#include "KeyChordViewModel.g.h" +#include "ModifyKeyChordEventArgs.g.h" #include "Utils.h" #include "ViewModelHelpers.h" namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - struct KeyBindingViewModelComparator + struct NavigateToCommandArgs : NavigateToCommandArgsT { - bool operator()(const Editor::KeyBindingViewModel& lhs, const Editor::KeyBindingViewModel& rhs) const - { - return lhs.Name() < rhs.Name(); - } + public: + NavigateToCommandArgs(CommandViewModel command, Editor::IHostedInWindow windowRoot) : + _Command(command), + _WindowRoot(windowRoot) {} + + Editor::IHostedInWindow WindowRoot() const noexcept { return _WindowRoot; } + Editor::CommandViewModel Command() const noexcept { return _Command; } + + private: + Editor::IHostedInWindow _WindowRoot; + Editor::CommandViewModel _Command{ nullptr }; }; - struct ModifyKeyBindingEventArgs : ModifyKeyBindingEventArgsT + struct ModifyKeyChordEventArgs : ModifyKeyChordEventArgsT { public: - ModifyKeyBindingEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys, const hstring oldActionName, const hstring newActionName) : + ModifyKeyChordEventArgs(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys) : _OldKeys{ oldKeys }, - _NewKeys{ newKeys }, - _OldActionName{ std::move(oldActionName) }, - _NewActionName{ std::move(newActionName) } {} + _NewKeys{ newKeys } {} WINRT_PROPERTY(Control::KeyChord, OldKeys, nullptr); WINRT_PROPERTY(Control::KeyChord, NewKeys, nullptr); - WINRT_PROPERTY(hstring, OldActionName); - WINRT_PROPERTY(hstring, NewActionName); }; - struct KeyBindingViewModel : KeyBindingViewModelT, ViewModelHelper + struct CommandViewModel : CommandViewModelT, ViewModelHelper { public: - KeyBindingViewModel(const Windows::Foundation::Collections::IObservableVector& availableActions); - KeyBindingViewModel(const Control::KeyChord& keys, const hstring& name, const Windows::Foundation::Collections::IObservableVector& availableActions); + CommandViewModel(winrt::Microsoft::Terminal::Settings::Model::Command cmd, + std::vector keyChordList, + const Editor::ActionsViewModel actionsPageVM); + void Initialize(); - hstring Name() const { return _CurrentAction; } - hstring KeyChordText() const { return _KeyChordText; } + winrt::hstring DisplayName(); + winrt::hstring Name(); + void Name(const winrt::hstring& newName); + winrt::hstring DisplayNameAndKeyChordAutomationPropName(); - // UIA Text - hstring EditButtonName() const noexcept; - hstring CancelButtonName() const noexcept; - hstring AcceptButtonName() const noexcept; - hstring DeleteButtonName() const noexcept; + winrt::hstring FirstKeyChordText(); + + winrt::hstring ID(); + bool IsUserAction(); + + void Edit_Click(); + til::typed_event EditRequested; + + void Delete_Click(); + til::typed_event DeleteRequested; + + void AddKeybinding_Click(); + + // UIA text + winrt::hstring ActionNameTextBoxAutomationPropName(); + winrt::hstring ShortcutActionComboBoxAutomationPropName(); + winrt::hstring AdditionalArgumentsControlAutomationPropName(); + + til::typed_event PropagateColorSchemeRequested; + til::typed_event PropagateColorSchemeNamesRequested; + til::typed_event PropagateWindowRootRequested; + til::typed_event FocusContainer; + + VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedShortcutActionName); + VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ActionArgsViewModel, ActionArgsVM, nullptr); + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, AvailableShortcutActions, nullptr); + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, KeyChordViewModelList, nullptr); + WINRT_PROPERTY(bool, IsNewCommand, false); + + private: + winrt::hstring _cachedDisplayName; + winrt::Microsoft::Terminal::Settings::Model::Command _command; + std::vector _keyChordList; + weak_ref _actionsPageVM{ nullptr }; + void _RegisterKeyChordVMEvents(Editor::KeyChordViewModel kcVM); + void _RegisterActionArgsVMEvents(Editor::ActionArgsViewModel actionArgsVM); + void _ReplaceCommandWithUserCopy(bool reinitialize); + void _CreateAndInitializeActionArgsVMHelper(); + }; + + struct ArgWrapper : ArgWrapperT, ViewModelHelper + { + public: + ArgWrapper(const winrt::hstring& name, const winrt::hstring& type, const bool required, const Model::ArgTypeHint typeHint, const Windows::Foundation::IInspectable& value); + void Initialize(); + + winrt::hstring Name() const noexcept { return _name; }; + winrt::hstring Type() const noexcept { return _type; }; + Model::ArgTypeHint TypeHint() const noexcept { return _typeHint; }; + bool Required() const noexcept { return _required; }; + + // We cannot use the macro here because we need to implement additional logic for the setter + Windows::Foundation::IInspectable EnumValue() const noexcept { return _EnumValue; }; + void EnumValue(const Windows::Foundation::IInspectable& value); + Windows::Foundation::Collections::IObservableVector EnumList() const noexcept { return _EnumList; }; + Windows::Foundation::Collections::IObservableVector FlagList() const noexcept { return _FlagList; }; + + // unboxing functions + winrt::hstring UnboxString(const Windows::Foundation::IInspectable& value); + int32_t UnboxInt32(const Windows::Foundation::IInspectable& value); + float UnboxInt32Optional(const Windows::Foundation::IInspectable& value); + uint32_t UnboxUInt32(const Windows::Foundation::IInspectable& value); + float UnboxUInt32Optional(const Windows::Foundation::IInspectable& value); + float UnboxFloat(const Windows::Foundation::IInspectable& value); + bool UnboxBool(const Windows::Foundation::IInspectable& value); + winrt::Windows::Foundation::IReference UnboxBoolOptional(const Windows::Foundation::IInspectable& value); + winrt::Windows::Foundation::IReference UnboxTerminalCoreColorOptional(const Windows::Foundation::IInspectable& value); + winrt::Windows::Foundation::IReference UnboxWindowsUIColorOptional(const Windows::Foundation::IInspectable& value); + + // bind back functions + void StringBindBack(const winrt::hstring& newValue); + void Int32BindBack(const double newValue); + void Int32OptionalBindBack(const double newValue); + void UInt32BindBack(const double newValue); + void UInt32OptionalBindBack(const double newValue); + void FloatBindBack(const double newValue); + void BoolOptionalBindBack(const Windows::Foundation::IReference newValue); + void TerminalCoreColorBindBack(const winrt::Windows::Foundation::IReference newValue); + void WindowsUIColorBindBack(const winrt::Windows::Foundation::IReference newValue); + + safe_void_coroutine BrowseForFile_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + safe_void_coroutine BrowseForFolder_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + + // some argWrappers need to know additional information (like the default color scheme or the list of all color scheme names) + // to avoid populating all ArgWrappers with that information, instead we emit an event when we need that information + // (these events then get propagated up to the ActionsVM) and then the actionsVM will populate the value in us + // since there's an actionArgsVM above us and a commandVM above that, the event does get propagated through a few times but that's + // probably better than having every argWrapper contain the information by default + til::typed_event ColorSchemeRequested; + til::typed_event ColorSchemeNamesRequested; + til::typed_event WindowRootRequested; + + VIEW_MODEL_OBSERVABLE_PROPERTY(Editor::ColorSchemeViewModel, DefaultColorScheme, nullptr); + VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::Foundation::IInspectable, Value, nullptr); + WINRT_PROPERTY(Windows::Foundation::Collections::IVector, ColorSchemeNamesList, nullptr); + WINRT_PROPERTY(Editor::IHostedInWindow, WindowRoot, nullptr); + + private: + winrt::hstring _name; + winrt::hstring _type; + Model::ArgTypeHint _typeHint; + bool _required; + Windows::Foundation::IInspectable _EnumValue{ nullptr }; + Windows::Foundation::Collections::IObservableVector _EnumList; + Windows::Foundation::Collections::IObservableVector _FlagList; + + // Regular enum list + value + template + void _InitializeEnumListAndValue( + const winrt::Windows::Foundation::Collections::IMapView& mappings, + const winrt::hstring& resourceSectionAndType, + const winrt::hstring& resourceProperty); + + // Nullable enum list + value + template + void _InitializeNullableEnumListAndValue( + const winrt::Windows::Foundation::Collections::IMapView& mappings, + const winrt::hstring& resourceSectionAndType, + const winrt::hstring& resourceProperty); + + // Flag list + value + template + void _InitializeFlagListAndValue( + const winrt::Windows::Foundation::Collections::IMapView& mappings, + const winrt::hstring& resourceSectionAndType, + const winrt::hstring& resourceProperty); + + // Nullable flag list + value + template + void _InitializeNullableFlagListAndValue( + const winrt::Windows::Foundation::Collections::IMapView& mappings, + const winrt::hstring& resourceSectionAndType, + const winrt::hstring& resourceProperty); + }; + + struct ActionArgsViewModel : ActionArgsViewModelT, ViewModelHelper + { + public: + ActionArgsViewModel(const Microsoft::Terminal::Settings::Model::ActionAndArgs actionAndArgs); + void Initialize(); + + bool HasArgs() const noexcept; + void ReplaceActionAndArgs(Model::ActionAndArgs newActionAndArgs); + + til::typed_event WrapperValueChanged; + til::typed_event PropagateColorSchemeRequested; + til::typed_event PropagateColorSchemeNamesRequested; + til::typed_event PropagateWindowRootRequested; + + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, ArgValues, nullptr); + + private: + Model::ActionAndArgs _actionAndArgs{ nullptr }; + }; + + struct KeyChordViewModel : KeyChordViewModelT, ViewModelHelper + { + public: + KeyChordViewModel(Control::KeyChord CurrentKeys); + + void CurrentKeys(const Control::KeyChord& newKeys); + Control::KeyChord CurrentKeys() const noexcept; - void EnterHoverMode() { IsHovered(true); }; - void ExitHoverMode() { IsHovered(false); }; - void ActionGotFocus() { IsContainerFocused(true); }; - void ActionLostFocus() { IsContainerFocused(false); }; - void EditButtonGettingFocus() { IsEditButtonFocused(true); }; - void EditButtonLosingFocus() { IsEditButtonFocused(false); }; - bool ShowEditButton() const noexcept; void ToggleEditMode(); - void DisableEditMode() { IsInEditMode(false); } void AttemptAcceptChanges(); - void AttemptAcceptChanges(const Control::KeyChord newKeys); void CancelChanges(); - void DeleteKeyBinding() { DeleteKeyBindingRequested.raise(*this, _CurrentKeys); } - - // ProposedAction: the entry selected by the combo box; may disagree with the settings model. - // CurrentAction: the combo box item that maps to the settings model value. - // AvailableActions: the list of options in the combo box; both actions above must be in this list. - // NOTE: ProposedAction and CurrentAction may disagree mainly due to the "edit mode" system in place. - // Current Action serves as... - // 1 - a record of what to set ProposedAction to on a cancellation - // 2 - a form of translation between ProposedAction and the settings model - // We would also need an ActionMap reference to remove this, but this is a better separation - // of responsibilities. - VIEW_MODEL_OBSERVABLE_PROPERTY(IInspectable, ProposedAction); - VIEW_MODEL_OBSERVABLE_PROPERTY(hstring, CurrentAction); - WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, AvailableActions, nullptr); - - // ProposedKeys: the keys proposed by the control; may disagree with the settings model. - // CurrentKeys: the key chord bound in the settings model. - VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys); - VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, CurrentKeys, nullptr); + void DeleteKeyChord(); + + // UIA Text + hstring CancelButtonName() const noexcept; + hstring AcceptButtonName() const noexcept; + hstring DeleteButtonName() const noexcept; VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsInEditMode, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsNewlyAdded, false); + VIEW_MODEL_OBSERVABLE_PROPERTY(Control::KeyChord, ProposedKeys); + VIEW_MODEL_OBSERVABLE_PROPERTY(winrt::hstring, KeyChordText); VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Controls::Flyout, AcceptChangesFlyout, nullptr); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsAutomationPeerAttached, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsHovered, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsContainerFocused, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(bool, IsEditButtonFocused, false); - VIEW_MODEL_OBSERVABLE_PROPERTY(Windows::UI::Xaml::Media::Brush, ContainerBackground, nullptr); public: - til::typed_event ModifyKeyBindingRequested; - til::typed_event DeleteKeyBindingRequested; - til::typed_event DeleteNewlyAddedKeyBinding; + til::typed_event AddKeyChordRequested; + til::typed_event ModifyKeyChordRequested; + til::typed_event DeleteKeyChordRequested; private: - hstring _KeyChordText{}; + Control::KeyChord _currentKeys; }; struct ActionsViewModel : ActionsViewModelT, ViewModelHelper { public: ActionsViewModel(Model::CascadiaSettings settings); + void UpdateSettings(const Model::CascadiaSettings& settings); + void MarkAsVisited(); + bool DisplayBadge() const noexcept; - void OnAutomationPeerAttached(); - void AddNewKeybinding(); + void AddNewCommand(); - til::typed_event FocusContainer; - til::typed_event UpdateBackground; + void CurrentCommand(const Editor::CommandViewModel& newCommand); + Editor::CommandViewModel CurrentCommand(); + void CmdListItemClicked(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::Controls::ItemClickEventArgs& e); + + void AttemptDeleteKeyChord(const Control::KeyChord& keys); + void AttemptAddOrModifyKeyChord(const Editor::KeyChordViewModel& senderVM, winrt::hstring commandID, const Control::KeyChord& newKeys, const Control::KeyChord& oldKeys); + void AttemptAddCopiedCommand(const Model::Command& newCommand); + void AttemptRegenerateCommandID(const Model::Command& command); + + Windows::Foundation::Collections::IMap AvailableShortcutActionsAndNames(); + Windows::Foundation::Collections::IMap NameToActionMap(); - WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, KeyBindingList); + WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, CommandList); + WINRT_OBSERVABLE_PROPERTY(ActionsSubPage, CurrentPage, _propertyChangedHandlers, ActionsSubPage::Base); private: - bool _AutomationPeerAttached{ false }; + Editor::CommandViewModel _CurrentCommand{ nullptr }; Model::CascadiaSettings _Settings; - Windows::Foundation::Collections::IObservableVector _AvailableActionAndArgs; - Windows::Foundation::Collections::IMap _AvailableActionMap; + Windows::Foundation::Collections::IMap _AvailableActionsAndNamesMap; + Windows::Foundation::Collections::IMap _NameToActionMap; - std::optional _GetContainerIndexByKeyChord(const Control::KeyChord& keys); - void _RegisterEvents(com_ptr& kbdVM); + void _MakeCommandVMsHelper(); + void _RegisterCmdVMEvents(com_ptr& cmdVM); - void _KeyBindingViewModelPropertyChangedHandler(const Windows::Foundation::IInspectable& senderVM, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args); - void _KeyBindingViewModelDeleteKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Control::KeyChord& args); - void _KeyBindingViewModelModifyKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const Editor::ModifyKeyBindingEventArgs& args); - void _KeyBindingViewModelDeleteNewlyAddedKeyBindingHandler(const Editor::KeyBindingViewModel& senderVM, const IInspectable& args); + void _CmdVMEditRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& args); + void _CmdVMDeleteRequestedHandler(const Editor::CommandViewModel& senderVM, const IInspectable& args); + void _CmdVMPropagateColorSchemeRequestedHandler(const IInspectable& sender, const Editor::ArgWrapper& wrapper); + void _CmdVMPropagateColorSchemeNamesRequestedHandler(const IInspectable& sender, const Editor::ArgWrapper& wrapper); }; } diff --git a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl index f091149d778..57a8308c1ec 100644 --- a/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ActionsViewModel.idl @@ -1,60 +1,161 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import "EnumEntry.idl"; +import "ColorSchemeViewModel.idl"; +import "MainPage.idl"; + namespace Microsoft.Terminal.Settings.Editor { - runtimeclass ModifyKeyBindingEventArgs + runtimeclass NavigateToCommandArgs + { + CommandViewModel Command { get; }; + IHostedInWindow WindowRoot { get; }; + } + + runtimeclass ModifyKeyChordEventArgs { Microsoft.Terminal.Control.KeyChord OldKeys { get; }; Microsoft.Terminal.Control.KeyChord NewKeys { get; }; - String OldActionName { get; }; - String NewActionName { get; }; } - runtimeclass KeyBindingViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + runtimeclass CommandViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { // Settings Model side + String Name; + String ID { get; }; + Boolean IsUserAction { get; }; + // keybindings + IObservableVector KeyChordViewModelList { get; }; + // action args + ActionArgsViewModel ActionArgsVM { get; }; + + // View-model specific + String DisplayName { get; }; + String FirstKeyChordText { get; }; + String DisplayNameAndKeyChordAutomationPropName { get; }; + + // UI side (command list page) + void Edit_Click(); + + // UI side (edit command page) + IObservableVector AvailableShortcutActions { get; }; + Object ProposedShortcutActionName; + void Delete_Click(); + void AddKeybinding_Click(); + event Windows.Foundation.TypedEventHandler PropagateColorSchemeRequested; + event Windows.Foundation.TypedEventHandler PropagateColorSchemeNamesRequested; + event Windows.Foundation.TypedEventHandler PropagateWindowRootRequested; + event Windows.Foundation.TypedEventHandler FocusContainer; + + // UI side (edit command page, automation property names) + String ActionNameTextBoxAutomationPropName { get; }; + String ShortcutActionComboBoxAutomationPropName { get; }; + String AdditionalArgumentsControlAutomationPropName { get; }; + } + + runtimeclass ArgWrapper : Windows.UI.Xaml.Data.INotifyPropertyChanged + { String Name { get; }; + String Type { get; }; + Microsoft.Terminal.Settings.Model.ArgTypeHint TypeHint { get; }; + Boolean Required { get; }; + IInspectable Value; + IInspectable EnumValue; + Windows.Foundation.Collections.IObservableVector EnumList { get; }; + Windows.Foundation.Collections.IObservableVector FlagList { get; }; + ColorSchemeViewModel DefaultColorScheme; + Windows.Foundation.Collections.IVector ColorSchemeNamesList; + IHostedInWindow WindowRoot; + + // unboxing functions + String UnboxString(Object value); + UInt32 UnboxInt32(Object value); + Single UnboxInt32Optional(Object value); + UInt32 UnboxUInt32(Object value); + Single UnboxUInt32Optional(Object value); + Single UnboxFloat(Object value); + Boolean UnboxBool(Object value); + Windows.Foundation.IReference UnboxBoolOptional(Object value); + Windows.Foundation.IReference UnboxTerminalCoreColorOptional(Object value); + Windows.Foundation.IReference UnboxWindowsUIColorOptional(Object value); + + // bind back functions + void StringBindBack(String newValue); + void Int32BindBack(Double newValue); + void Int32OptionalBindBack(Double newValue); + void UInt32BindBack(Double newValue); + void UInt32OptionalBindBack(Double newValue); + void FloatBindBack(Double newValue); + void BoolOptionalBindBack(Windows.Foundation.IReference newValue); + void TerminalCoreColorBindBack(Windows.Foundation.IReference newValue); + void WindowsUIColorBindBack(Windows.Foundation.IReference newValue); + + void BrowseForFile_Click(IInspectable sender, Windows.UI.Xaml.RoutedEventArgs args); + void BrowseForFolder_Click(IInspectable sender, Windows.UI.Xaml.RoutedEventArgs args); + + event Windows.Foundation.TypedEventHandler ColorSchemeRequested; + event Windows.Foundation.TypedEventHandler ColorSchemeNamesRequested; + event Windows.Foundation.TypedEventHandler WindowRootRequested; + } + + runtimeclass ActionArgsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + Boolean HasArgs { get; }; + IObservableVector ArgValues; + event Windows.Foundation.TypedEventHandler WrapperValueChanged; + event Windows.Foundation.TypedEventHandler PropagateColorSchemeRequested; + event Windows.Foundation.TypedEventHandler PropagateColorSchemeNamesRequested; + event Windows.Foundation.TypedEventHandler PropagateWindowRootRequested; + } + + runtimeclass KeyChordViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { String KeyChordText { get; }; // UI side - Boolean ShowEditButton { get; }; - Boolean IsInEditMode { get; }; - Boolean IsNewlyAdded { get; }; Microsoft.Terminal.Control.KeyChord ProposedKeys; - Object ProposedAction; Windows.UI.Xaml.Controls.Flyout AcceptChangesFlyout; - String EditButtonName { get; }; - String CancelButtonName { get; }; - String AcceptButtonName { get; }; - String DeleteButtonName { get; }; - Windows.UI.Xaml.Media.Brush ContainerBackground { get; }; - - void EnterHoverMode(); - void ExitHoverMode(); - void ActionGotFocus(); - void ActionLostFocus(); - void EditButtonGettingFocus(); - void EditButtonLosingFocus(); - IObservableVector AvailableActions { get; }; + Boolean IsInEditMode { get; }; void ToggleEditMode(); void AttemptAcceptChanges(); void CancelChanges(); - void DeleteKeyBinding(); + void DeleteKeyChord(); + String CancelButtonName { get; }; + String AcceptButtonName { get; }; + String DeleteButtonName { get; }; - event Windows.Foundation.TypedEventHandler ModifyKeyBindingRequested; - event Windows.Foundation.TypedEventHandler DeleteKeyBindingRequested; + event Windows.Foundation.TypedEventHandler AddKeyChordRequested; + event Windows.Foundation.TypedEventHandler ModifyKeyChordRequested; + event Windows.Foundation.TypedEventHandler DeleteKeyChordRequested; } + enum ActionsSubPage + { + Base = 0, + Edit = 1 + }; + runtimeclass ActionsViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { ActionsViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); + void UpdateSettings(Microsoft.Terminal.Settings.Model.CascadiaSettings settings); - void OnAutomationPeerAttached(); - void AddNewKeybinding(); + void AddNewCommand(); - IObservableVector KeyBindingList { get; }; - event Windows.Foundation.TypedEventHandler FocusContainer; - event Windows.Foundation.TypedEventHandler UpdateBackground; + ActionsSubPage CurrentPage; + CommandViewModel CurrentCommand; + Boolean DisplayBadge { get; }; + + void AttemptAddOrModifyKeyChord(KeyChordViewModel senderVM, String commandID, Microsoft.Terminal.Control.KeyChord newKeys, Microsoft.Terminal.Control.KeyChord oldKeys); + void AttemptDeleteKeyChord(Microsoft.Terminal.Control.KeyChord keys); + void AttemptAddCopiedCommand(Microsoft.Terminal.Settings.Model.Command newCommand); + void AttemptRegenerateCommandID(Microsoft.Terminal.Settings.Model.Command command); + + IObservableVector CommandList { get; }; + void CmdListItemClicked(IInspectable sender, Windows.UI.Xaml.Controls.ItemClickEventArgs args); + + Windows.Foundation.Collections.IMap AvailableShortcutActionsAndNames(); + Windows.Foundation.Collections.IMap NameToActionMap(); } } diff --git a/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.cpp b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.cpp new file mode 100644 index 00000000000..ec321a05827 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.cpp @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "ArgsTemplateSelectors.h" +#include "ArgsTemplateSelectors.g.cpp" + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + Windows::UI::Xaml::DataTemplate ArgsTemplateSelectors::SelectTemplateCore(const winrt::Windows::Foundation::IInspectable& item, const winrt::Windows::UI::Xaml::DependencyObject& /*container*/) + { + return SelectTemplateCore(item); + } + + // Method Description: + // - This method is called once command palette decides how to render a filtered command. + // Currently we support two ways to render command, that depend on its palette item type: + // - For TabPalette item we render an icon, a title, and some tab-related indicators like progress bar (as defined by TabItemTemplate) + // - All other items are currently rendered with icon, title and optional key-chord (as defined by GeneralItemTemplate) + // Arguments: + // - item - an instance of filtered command to render + // Return Value: + // - data template to use for rendering + Windows::UI::Xaml::DataTemplate ArgsTemplateSelectors::SelectTemplateCore(const winrt::Windows::Foundation::IInspectable& item) + { + static constexpr std::pair lut[] = { + { L"int32_t", &ArgsTemplateSelectors::_Int32Template }, + { L"uint32_t", &ArgsTemplateSelectors::_UInt32Template }, + { L"float", &ArgsTemplateSelectors::_FloatTemplate }, + { L"bool", &ArgsTemplateSelectors::_BoolTemplate }, + { L"Windows::Foundation::IReference", &ArgsTemplateSelectors::_BoolOptionalTemplate }, + { L"Windows::Foundation::IReference", &ArgsTemplateSelectors::_Int32OptionalTemplate }, + { L"Windows::Foundation::IReference", &ArgsTemplateSelectors::_UInt32OptionalTemplate }, + { L"SuggestionsSource", &ArgsTemplateSelectors::_FlagTemplate }, + { L"Windows::Foundation::IReference", &ArgsTemplateSelectors::_FlagTemplate }, + { L"Windows::Foundation::IReference", &ArgsTemplateSelectors::_TerminalCoreColorOptionalTemplate }, + { L"Windows::Foundation::IReference", &ArgsTemplateSelectors::_WindowsUIColorOptionalTemplate }, + { L"Model::ResizeDirection", &ArgsTemplateSelectors::_EnumTemplate }, + { L"Model::FocusDirection", &ArgsTemplateSelectors::_EnumTemplate }, + { L"SettingsTarget", &ArgsTemplateSelectors::_EnumTemplate }, + { L"MoveTabDirection", &ArgsTemplateSelectors::_EnumTemplate }, + { L"Microsoft::Terminal::Control::ScrollToMarkDirection", &ArgsTemplateSelectors::_EnumTemplate }, + { L"CommandPaletteLaunchMode", &ArgsTemplateSelectors::_EnumTemplate }, + { L"FindMatchDirection", &ArgsTemplateSelectors::_EnumTemplate }, + { L"Model::DesktopBehavior", &ArgsTemplateSelectors::_EnumTemplate }, + { L"Model::MonitorBehavior", &ArgsTemplateSelectors::_EnumTemplate }, + { L"winrt::Microsoft::Terminal::Control::ClearBufferType", &ArgsTemplateSelectors::_EnumTemplate }, + { L"SelectOutputDirection", &ArgsTemplateSelectors::_EnumTemplate }, + { L"Windows::Foundation::IReference", &ArgsTemplateSelectors::_EnumTemplate }, + { L"Model::SplitDirection", &ArgsTemplateSelectors::_EnumTemplate }, + { L"SplitType", &ArgsTemplateSelectors::_EnumTemplate }, + }; + + if (const auto argWrapper{ item.try_as() }) + { + const auto argType = argWrapper.Type(); + if (argType == L"winrt::hstring") + { + // string has some special cases - check the tag + const auto argTag = argWrapper.TypeHint(); + switch (argTag) + { + case Model::ArgTypeHint::ColorScheme: + return ColorSchemeTemplate(); + case Model::ArgTypeHint::FilePath: + return FilePickerTemplate(); + case Model::ArgTypeHint::FolderPath: + return FolderPickerTemplate(); + default: + // no special handling required, just return the normal string template + return StringTemplate(); + } + } + + for (const auto& [type, member] : lut) + { + if (type == argType) + { + return this->*member; + } + } + } + return nullptr; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.h b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.h new file mode 100644 index 00000000000..10224445c9a --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.h @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "ArgsTemplateSelectors.g.h" + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + struct ArgsTemplateSelectors : ArgsTemplateSelectorsT + { + ArgsTemplateSelectors() = default; + + Windows::UI::Xaml::DataTemplate SelectTemplateCore(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::DependencyObject&); + Windows::UI::Xaml::DataTemplate SelectTemplateCore(const winrt::Windows::Foundation::IInspectable&); + + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, Int32Template); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, Int32OptionalTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, UInt32Template); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, UInt32OptionalTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FloatTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, StringTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, ColorSchemeTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FilePickerTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FolderPickerTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, BoolTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, BoolOptionalTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, EnumTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, FlagTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, TerminalCoreColorOptionalTemplate); + WINRT_PROPERTY(winrt::Windows::UI::Xaml::DataTemplate, WindowsUIColorOptionalTemplate); + }; +} + +namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation +{ + BASIC_FACTORY(ArgsTemplateSelectors); +} diff --git a/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.idl b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.idl new file mode 100644 index 00000000000..d69f12152b6 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ArgsTemplateSelectors.idl @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Microsoft.Terminal.Settings.Editor +{ + [default_interface] runtimeclass ArgsTemplateSelectors : Windows.UI.Xaml.Controls.DataTemplateSelector + { + ArgsTemplateSelectors(); + + Windows.UI.Xaml.DataTemplate Int32Template; + Windows.UI.Xaml.DataTemplate Int32OptionalTemplate; + Windows.UI.Xaml.DataTemplate UInt32Template; + Windows.UI.Xaml.DataTemplate UInt32OptionalTemplate; + Windows.UI.Xaml.DataTemplate FloatTemplate; + Windows.UI.Xaml.DataTemplate StringTemplate; + Windows.UI.Xaml.DataTemplate ColorSchemeTemplate; + Windows.UI.Xaml.DataTemplate FilePickerTemplate; + Windows.UI.Xaml.DataTemplate FolderPickerTemplate; + Windows.UI.Xaml.DataTemplate BoolTemplate; + Windows.UI.Xaml.DataTemplate BoolOptionalTemplate; + Windows.UI.Xaml.DataTemplate EnumTemplate; + Windows.UI.Xaml.DataTemplate FlagTemplate; + Windows.UI.Xaml.DataTemplate TerminalCoreColorOptionalTemplate; + Windows.UI.Xaml.DataTemplate WindowsUIColorOptionalTemplate; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/EditAction.cpp b/src/cascadia/TerminalSettingsEditor/EditAction.cpp new file mode 100644 index 00000000000..25946d011d3 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/EditAction.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "EditAction.h" +#include "EditAction.g.cpp" +#include "LibraryResources.h" +#include "../TerminalSettingsModel/AllShortcutActions.h" + +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Navigation; + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + EditAction::EditAction() + { + InitializeComponent(); + } + + void EditAction::OnNavigatedTo(const NavigationEventArgs& e) + { + const auto args = e.Parameter().as(); + _ViewModel = args.Command(); + _ViewModel.PropagateWindowRootRequested([windowRoot = args.WindowRoot()](const IInspectable& /*sender*/, const Editor::ArgWrapper& wrapper) { + if (wrapper) + { + wrapper.WindowRoot(windowRoot); + } + }); + auto weakThis = get_weak(); + _ViewModel.FocusContainer([weakThis](const auto& /*sender*/, const auto& args) { + if (auto page{ weakThis.get() }) + { + if (auto kcVM{ args.try_as() }) + { + if (const auto& container = page->KeyChordListView().ContainerFromItem(*kcVM)) + { + container.as().Focus(FocusState::Programmatic); + } + } + } + }); + _layoutUpdatedRevoker = LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) { + // Only let this succeed once. + _layoutUpdatedRevoker.revoke(); + + CommandNameTextBox().Focus(FocusState::Programmatic); + }); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/EditAction.h b/src/cascadia/TerminalSettingsEditor/EditAction.h new file mode 100644 index 00000000000..c86f2da0bcd --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/EditAction.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "EditAction.g.h" +#include "ActionsViewModel.h" +#include "Utils.h" +#include "ViewModelHelpers.h" + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + struct EditAction : public HasScrollViewer, EditActionT + { + public: + EditAction(); + void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e); + + til::property_changed_event PropertyChanged; + + WINRT_OBSERVABLE_PROPERTY(Editor::CommandViewModel, ViewModel, PropertyChanged.raise, nullptr); + + private: + friend struct EditActionT; // for Xaml to bind events + winrt::Windows::UI::Xaml::FrameworkElement::LayoutUpdated_revoker _layoutUpdatedRevoker; + }; +} + +namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation +{ + BASIC_FACTORY(EditAction); +} diff --git a/src/cascadia/TerminalSettingsEditor/EditAction.idl b/src/cascadia/TerminalSettingsEditor/EditAction.idl new file mode 100644 index 00000000000..3e078ccb96e --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/EditAction.idl @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "ActionsViewModel.idl"; + +namespace Microsoft.Terminal.Settings.Editor +{ + [default_interface] runtimeclass EditAction : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged + { + EditAction(); + CommandViewModel ViewModel { get; }; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/EditAction.xaml b/src/cascadia/TerminalSettingsEditor/EditAction.xaml new file mode 100644 index 00000000000..edb2f6f01ea --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/EditAction.xaml @@ -0,0 +1,715 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 32 + 14 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/EnumEntry.h b/src/cascadia/TerminalSettingsEditor/EnumEntry.h index fe45a583961..3c3588a0dba 100644 --- a/src/cascadia/TerminalSettingsEditor/EnumEntry.h +++ b/src/cascadia/TerminalSettingsEditor/EnumEntry.h @@ -17,6 +17,7 @@ Author(s): #pragma once #include "EnumEntry.g.h" +#include "FlagEntry.g.h" #include "Utils.h" namespace winrt::Microsoft::Terminal::Settings::Editor::implementation @@ -26,7 +27,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { bool operator()(const Editor::EnumEntry& lhs, const Editor::EnumEntry& rhs) const { - return lhs.EnumValue().as() < rhs.EnumValue().as(); + return lhs.IntValue() < rhs.IntValue(); } }; @@ -35,7 +36,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { bool operator()(const Editor::EnumEntry& lhs, const Editor::EnumEntry& rhs) const { - return lhs.EnumValue().as() > rhs.EnumValue().as(); + return lhs.IntValue() > rhs.IntValue(); } }; @@ -46,6 +47,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _EnumName{ enumName }, _EnumValue{ enumValue } {} + EnumEntry(const winrt::hstring enumName, const winrt::Windows::Foundation::IInspectable& enumValue, const int32_t intValue) : + _EnumName{ enumName }, + _EnumValue{ enumValue }, + _IntValue{ intValue } {} + hstring ToString() { return EnumName(); @@ -54,5 +60,50 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation til::property_changed_event PropertyChanged; WINRT_OBSERVABLE_PROPERTY(winrt::hstring, EnumName, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(winrt::Windows::Foundation::IInspectable, EnumValue, PropertyChanged.raise); + WINRT_PROPERTY(int32_t, IntValue, 0); + }; + + template + struct FlagEntryComparator + { + bool operator()(const Editor::FlagEntry& lhs, const Editor::FlagEntry& rhs) const + { + return lhs.FlagValue().as() < rhs.FlagValue().as(); + } + }; + + template + struct FlagEntryReverseComparator + { + bool operator()(const Editor::FlagEntry& lhs, const Editor::FlagEntry& rhs) const + { + return lhs.FlagValue().as() > rhs.FlagValue().as(); + } + }; + + struct FlagEntry : FlagEntryT + { + public: + FlagEntry(const winrt::hstring flagName, const winrt::Windows::Foundation::IInspectable& flagValue, const bool isSet) : + _FlagName{ flagName }, + _FlagValue{ flagValue }, + _IsSet{ isSet } {} + + FlagEntry(const winrt::hstring flagName, const winrt::Windows::Foundation::IInspectable& flagValue, const bool isSet, const int32_t intValue) : + _FlagName{ flagName }, + _FlagValue{ flagValue }, + _IsSet{ isSet }, + _IntValue{ intValue } {} + + hstring ToString() + { + return FlagName(); + } + + til::property_changed_event PropertyChanged; + WINRT_OBSERVABLE_PROPERTY(winrt::hstring, FlagName, PropertyChanged.raise); + WINRT_OBSERVABLE_PROPERTY(winrt::Windows::Foundation::IInspectable, FlagValue, PropertyChanged.raise); + WINRT_OBSERVABLE_PROPERTY(bool, IsSet, PropertyChanged.raise); + WINRT_PROPERTY(int32_t, IntValue, 0); }; } diff --git a/src/cascadia/TerminalSettingsEditor/EnumEntry.idl b/src/cascadia/TerminalSettingsEditor/EnumEntry.idl index ce92d75762a..87139c8a3c3 100644 --- a/src/cascadia/TerminalSettingsEditor/EnumEntry.idl +++ b/src/cascadia/TerminalSettingsEditor/EnumEntry.idl @@ -7,5 +7,14 @@ namespace Microsoft.Terminal.Settings.Editor { String EnumName { get; }; IInspectable EnumValue { get; }; + Int32 IntValue { get; }; + } + + [default_interface] runtimeclass FlagEntry : Windows.UI.Xaml.Data.INotifyPropertyChanged, Windows.Foundation.IStringable + { + String FlagName { get; }; + IInspectable FlagValue { get; }; + Int32 IntValue { get; }; + Boolean IsSet; } } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 19345746aa3..fdd484722d4 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -114,6 +114,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } }); + _actionsVM = winrt::make(_settingsClone); + _actionsViewModelChangedRevoker = _actionsVM.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { + const auto settingName{ args.PropertyName() }; + if (settingName == L"CurrentPage") + { + if (_actionsVM.CurrentPage() == ActionsSubPage::Edit) + { + contentFrame().Navigate(xaml_typename(), winrt::make(_actionsVM.CurrentCommand(), *this)); + const auto crumb = winrt::make(box_value(actionsTag), RS_(L"Nav_EditAction/Content"), BreadcrumbSubPage::Actions_Edit); + _breadcrumbs.Append(crumb); + } + else if (_actionsVM.CurrentPage() == ActionsSubPage::Base) + { + _Navigate(winrt::hstring{ actionsTag }, BreadcrumbSubPage::None); + } + } + }); + auto extensionsVMImpl = winrt::make_self(_settingsClone, _colorSchemesPageVM); extensionsVMImpl->NavigateToProfileRequested({ this, &MainPage::_NavigateToProfileHandler }); extensionsVMImpl->NavigateToColorSchemeRequested({ this, &MainPage::_NavigateToColorSchemeHandler }); @@ -190,6 +208,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _InitializeProfilesList(); // Update the Nav State with the new version of the settings _colorSchemesPageVM.UpdateSettings(_settingsClone); + _actionsVM.UpdateSettings(_settingsClone); _newTabMenuPageVM.UpdateSettings(_settingsClone); _extensionsVM.UpdateSettings(_settingsClone, _colorSchemesPageVM); @@ -487,9 +506,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (clickedItemTag == actionsTag) { - contentFrame().Navigate(xaml_typename(), winrt::make(_settingsClone)); const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_Actions/Content"), BreadcrumbSubPage::None); _breadcrumbs.Append(crumb); + contentFrame().Navigate(xaml_typename(), _actionsVM); + + if (subPage == BreadcrumbSubPage::Actions_Edit && _actionsVM.CurrentCommand() != nullptr) + { + _actionsVM.CurrentPage(ActionsSubPage::Edit); + } } else if (clickedItemTag == newTabMenuTag) { diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index ff87f4910b6..7c8558b479b 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -44,6 +44,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Windows::Foundation::Collections::IObservableVector Breadcrumbs() noexcept; Editor::ExtensionsViewModel ExtensionsVM() const noexcept { return _extensionsVM; } + Editor::ActionsViewModel ActionsVM() const noexcept { return _actionsVM; } til::typed_event OpenJson; til::typed_event> ShowLoadWarningsDialog; @@ -78,11 +79,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void _MoveXamlParsedNavItemsIntoItemSource(); winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr }; + winrt::Microsoft::Terminal::Settings::Editor::ActionsViewModel _actionsVM{ nullptr }; winrt::Microsoft::Terminal::Settings::Editor::NewTabMenuViewModel _newTabMenuPageVM{ nullptr }; winrt::Microsoft::Terminal::Settings::Editor::ExtensionsViewModel _extensionsVM{ nullptr }; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _profileViewModelChangedRevoker; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _colorSchemesPageViewModelChangedRevoker; + Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _actionsViewModelChangedRevoker; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ntmViewModelChangedRevoker; Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _extensionsViewModelChangedRevoker; }; diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.idl b/src/cascadia/TerminalSettingsEditor/MainPage.idl index 20f3bab869d..5c023b54e9c 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.idl +++ b/src/cascadia/TerminalSettingsEditor/MainPage.idl @@ -2,6 +2,7 @@ // Licensed under the MIT license. import "Extensions.idl"; +import "ActionsViewModel.idl"; namespace Microsoft.Terminal.Settings.Editor { @@ -22,6 +23,7 @@ namespace Microsoft.Terminal.Settings.Editor Profile_Terminal, Profile_Advanced, ColorSchemes_Edit, + Actions_Edit, NewTabMenu_Folder, Extensions_Extension }; @@ -47,6 +49,7 @@ namespace Microsoft.Terminal.Settings.Editor Windows.Foundation.Collections.IObservableVector Breadcrumbs { get; }; ExtensionsViewModel ExtensionsVM { get; }; + ActionsViewModel ActionsVM { get; }; Windows.UI.Xaml.Media.Brush BackgroundBrush { get; }; } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index 77c1bd8488f..3601358ef33 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -149,6 +149,10 @@ + + + Actions.xaml + + ArgsTemplateSelectors.idl + Code + + + EditAction.xaml + AddProfile.xaml @@ -168,6 +175,9 @@ Designer + + Designer + Designer @@ -238,6 +248,13 @@ Actions.xaml + + ArgsTemplateSelectors.idl + Code + + + EditAction.xaml + AddProfile.xaml @@ -363,6 +380,13 @@ Actions.xaml Code + + Designer + + + EditAction.xaml + Code + AddProfile.xaml Code diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters index f49f4c9bc74..6b8db4b3df3 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters @@ -30,6 +30,7 @@ + @@ -50,6 +51,7 @@ + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index cbde5854346..37376cf9702 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -682,7 +682,11 @@ Actions - Header for the "actions" menu item. This navigates to a page that lets you see and modify commands, key bindings, and actions that can be done in the app. + Header for the "actions" menu item. This navigates to a page that lets you see the available commands in the app. + + + Edit Action... + Header for the "edit action" page. This is the page that lets you modify a specific command and its key bindings. Extensions @@ -1800,6 +1804,42 @@ Delete the unfocused appearance for this profile. A description for what the delete unfocused appearance button does. + + Learn more about actions + Disclaimer presented at the top of the actions page to redirect the user to documentation regarding actions. + + + Delete action + Button label that deletes the selected action. + + + Action name + Label for the text box that edits the action name. + + + Action name + Placeholder text for the text box where the user can edit the action name. + + + Action type + Label for the combo box that edits the action type. + + + Keybindings + Name for a control which contains the list of keybindings for the current command. + + + Additional arguments + Label for the list of editable arguments for the currently selected action. + + + Keybindings + Label for the list of editable keybindings for the current command. + + + Add keybinding + Button label that adds a keybinding to the current action. + Yes, delete key binding Button label that confirms deletion of a key binding entry. @@ -1808,6 +1848,14 @@ Are you sure you want to delete this key binding? Confirmation message displayed when the user attempts to delete a key binding entry. + + Yes, delete action + Button label that confirms deletion of an action. + + + Are you sure you want to delete this action? + Confirmation message displayed when the user attempts to delete an action. + Invalid key chord. Please enter a valid key chord. Error message displayed when an invalid key chord is input by the user. @@ -1852,6 +1900,278 @@ Action Label for a control that sets the action of a key binding. + + Use global setting + An option to choose from for nullable enums. Clears the enum value. + + + HTML + An option to choose from for the "copy format". Copies content in HTML format. + + + RTF + An option to choose from for the "copy format". Copies content in Rich Text Format (RTF). + + + Automatic + An option to choose from for the "split direction". Automatically determines the split direction. + + + Up + An option to choose from for the "split direction". Splits upward. + + + Right + An option to choose from for the "split direction". Splits to the right. + + + Down + An option to choose from for the "split direction". Splits downward. + + + Left + An option to choose from for the "split direction". Splits to the left. + + + Vertical + An option to choose from for the "split direction". Splits to the left. + + + Horizontal + An option to choose from for the "split direction". Splits to the left. + + + Manual + An option to choose from for the "split type". Creates a manual split. + + + Duplicate + An option to choose from for the "split type". Creates a split by duplicating the current session. + + + None + An option to choose from for the "resize direction". None option. + + + Left + An option to choose from for the "resize direction". Left option. + + + Right + An option to choose from for the "resize direction". Right option. + + + Up + An option to choose from for the "resize direction". Up option. + + + Down + An option to choose from for the "resize direction". Down option. + + + None + An option to choose from for the "focus direction". None option. + + + Left + An option to choose from for the "focus direction". Left option. + + + Right + An option to choose from for the "focus direction". Right option. + + + Up + An option to choose from for the "focus direction". Up option. + + + Down + An option to choose from for the "focus direction". Down option. + + + Previous + An option to choose from for the "focus direction". Previous option. + + + Previous In Order + An option to choose from for the "focus direction". Previous in order option. + + + Next In Order + An option to choose from for the "focus direction". Next in order option. + + + First + An option to choose from for the "focus direction". First option. + + + Parent + An option to choose from for the "focus direction". Parent option. + + + Child + An option to choose from for the "focus direction". Child option. + + + Settings File + An option to choose from for the "settings target". Targets the settings file. + + + Defaults File + An option to choose from for the "settings target". Targets the defaults file. + + + All Files + An option to choose from for the "settings target". Targets all files. + + + Settings UI + An option to choose from for the "settings target". Targets the settings UI. + + + Directory + An option to choose from for the "settings target". Targets the directory. + + + None + An option to choose from for the "move tab direction". No movement. + + + Forward + An option to choose from for the "move tab direction". Moves the tab forward. + + + Backward + An option to choose from for the "move tab direction". Moves the tab backward. + + + Previous + An option to choose from for the "scroll to mark direction". Scrolls to the previous mark. + + + Next + An option to choose from for the "scroll to mark direction". Scrolls to the next mark. + + + First + An option to choose from for the "scroll to mark direction". Scrolls to the first mark. + + + Last + An option to choose from for the "scroll to mark direction". Scrolls to the last mark. + + + Action + An option to choose from for the "command palette launch mode". Launches in action mode. + + + Command Line + An option to choose from for the "command palette launch mode". Launches in command line mode. + + + None + An option to choose from for the "suggestions source". No suggestions source. + + + Tasks + An option to choose from for the "suggestions source". Suggestions come from tasks. + + + Snippets + An option to choose from for the "suggestions source". Suggestions come from snippets. + + + Command History + An option to choose from for the "suggestions source". Suggestions come from command history. + + + Directory History + An option to choose from for the "suggestions source". Suggestions come from directory history. + + + Quick Fixes + An option to choose from for the "suggestions source". Suggestions come from quick fixes. + + + All + An option to choose from for the "suggestions source". Includes all suggestion sources. + + + None + An option to choose from for the "find match direction". No direction selected. + + + Next + An option to choose from for the "find match direction". Finds the next match. + + + Previous + An option to choose from for the "find match direction". Finds the previous match. + + + Any + An option to choose from for the "desktop behavior". Applies to any desktop. + + + To Current + An option to choose from for the "desktop behavior". Moves to the current desktop. + + + On Current + An option to choose from for the "desktop behavior". Stays on the current desktop. + + + Any + An option to choose from for the "monitor behavior". Applies to any monitor. + + + To Current + An option to choose from for the "monitor behavior". Moves to the current monitor. + + + To Mouse + An option to choose from for the "monitor behavior". Moves to the monitor where the mouse is located. + + + Screen + An option to choose from for the "clear buffer type". Clears only the screen. + + + Scrollback + An option to choose from for the "clear buffer type". Clears only the scrollback buffer. + + + All + An option to choose from for the "clear buffer type". Clears both the screen and the scrollback buffer. + + + Previous + An option to choose from for the "select output direction". Selects the previous output. + + + Next + An option to choose from for the "select output direction". Selects the next output. + + + Most Recently Used + An option to choose from for the "tab switcher mode". Switches tabs based on most recently used order. + + + In Order + An option to choose from for the "tab switcher mode". Switches tabs in sequential order. + + + Disabled + An option to choose from for the "tab switcher mode". Disables tab switching. + + + No color + Label for a button directing the user to opt out of choosing a color. + + + Browse... + Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window. + Input your desired keyboard shortcut. Help text directing users how to use the "KeyChordListener" control. Pressing a keyboard shortcut will be recorded by this control. diff --git a/src/cascadia/TerminalSettingsEditor/Utils.h b/src/cascadia/TerminalSettingsEditor/Utils.h index 37fe4a81ec8..a4148303f81 100644 --- a/src/cascadia/TerminalSettingsEditor/Utils.h +++ b/src/cascadia/TerminalSettingsEditor/Utils.h @@ -9,38 +9,38 @@ // being its localized name, and also initializes the enum to EnumEntry // map that's required to tell XAML what enum value the currently active // setting has. -#define INITIALIZE_BINDABLE_ENUM_SETTING(name, enumMappingsName, enumType, resourceSectionAndType, resourceProperty) \ - do \ - { \ - std::vector name##List; \ - _##name##Map = winrt::single_threaded_map(); \ - auto enumMapping##name = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \ - for (auto [key, value] : enumMapping##name) \ - { \ - auto enumName = LocalizedNameForEnumName(resourceSectionAndType, key, resourceProperty); \ - auto entry = winrt::make(enumName, winrt::box_value(value)); \ - name##List.emplace_back(entry); \ - _##name##Map.Insert(value, entry); \ - } \ - std::sort(name##List.begin(), name##List.end(), EnumEntryComparator()); \ - _##name##List = winrt::single_threaded_observable_vector(std::move(name##List)); \ +#define INITIALIZE_BINDABLE_ENUM_SETTING(name, enumMappingsName, enumType, resourceSectionAndType, resourceProperty) \ + do \ + { \ + std::vector name##List; \ + _##name##Map = winrt::single_threaded_map(); \ + auto enumMapping##name = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \ + for (auto [key, value] : enumMapping##name) \ + { \ + auto enumName = LocalizedNameForEnumName(resourceSectionAndType, key, resourceProperty); \ + auto entry = winrt::make(enumName, winrt::box_value(value), static_cast(value)); \ + name##List.emplace_back(entry); \ + _##name##Map.Insert(value, entry); \ + } \ + std::sort(name##List.begin(), name##List.end(), EnumEntryComparator()); \ + _##name##List = winrt::single_threaded_observable_vector(std::move(name##List)); \ } while (0); -#define INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(name, enumMappingsName, enumType, resourceSectionAndType, resourceProperty) \ - do \ - { \ - std::vector name##List; \ - _##name##Map = winrt::single_threaded_map(); \ - auto enumMapping##name = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \ - for (auto [key, value] : enumMapping##name) \ - { \ - auto enumName = LocalizedNameForEnumName(resourceSectionAndType, key, resourceProperty); \ - auto entry = winrt::make(enumName, winrt::box_value(value)); \ - name##List.emplace_back(entry); \ - _##name##Map.Insert(value, entry); \ - } \ - std::sort(name##List.begin(), name##List.end(), EnumEntryReverseComparator()); \ - _##name##List = winrt::single_threaded_observable_vector(std::move(name##List)); \ +#define INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(name, enumMappingsName, enumType, resourceSectionAndType, resourceProperty) \ + do \ + { \ + std::vector name##List; \ + _##name##Map = winrt::single_threaded_map(); \ + auto enumMapping##name = winrt::Microsoft::Terminal::Settings::Model::EnumMappings::enumMappingsName(); \ + for (auto [key, value] : enumMapping##name) \ + { \ + auto enumName = LocalizedNameForEnumName(resourceSectionAndType, key, resourceProperty); \ + auto entry = winrt::make(enumName, winrt::box_value(value), static_cast(value)); \ + name##List.emplace_back(entry); \ + _##name##Map.Insert(value, entry); \ + } \ + std::sort(name##List.begin(), name##List.end(), EnumEntryReverseComparator()); \ + _##name##List = winrt::single_threaded_observable_vector(std::move(name##List)); \ } while (0); // This macro must be used alongside INITIALIZE_BINDABLE_ENUM_SETTING. diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index ec032770454..3151e27a569 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -239,7 +239,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (!_name.has_value() || _name->name != value) { - _name = CommandNameOrResource{ .name = std::wstring{ value } }; + if (value.empty()) + { + _name = std::nullopt; + } + else + { + _name = CommandNameOrResource{ .name = std::wstring{ value } }; + } } } diff --git a/src/cascadia/UIHelpers/Converters.cpp b/src/cascadia/UIHelpers/Converters.cpp index 8c0cffb8af8..3c364c0a1b6 100644 --- a/src/cascadia/UIHelpers/Converters.cpp +++ b/src/cascadia/UIHelpers/Converters.cpp @@ -77,4 +77,36 @@ namespace winrt::Microsoft::Terminal::UI::implementation { return fontWeight.Weight; } + + double Converters::MaxValueFromPaddingString(const winrt::hstring& paddingString) + { + std::wstring buffer; + double maxVal = 0; + + auto& errnoRef = errno; // Nonzero cost, pay it once + + // Get padding values till we run out of delimiter separated values in the stream + // Non-numeral values detected will default to 0 + // std::stod will throw invalid_argument exception if the input is an invalid double value + // std::stod will throw out_of_range exception if the input value is more than DBL_MAX + for (const auto& part : til::split_iterator{ std::wstring_view{ paddingString }, L',' }) + { + buffer.assign(part); + + // wcstod handles whitespace prefix (which is ignored) & stops the + // scan when first char outside the range of radix is encountered. + // We'll be permissive till the extent that stod function allows us to be by default + // Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail + errnoRef = 0; + wchar_t* end; + const double val = wcstod(buffer.c_str(), &end); + + if (end != buffer.c_str() && errnoRef != ERANGE) + { + maxVal = std::max(maxVal, val); + } + } + + return maxVal; + } } diff --git a/src/cascadia/UIHelpers/Converters.h b/src/cascadia/UIHelpers/Converters.h index 998daad41f2..af8bcecb458 100644 --- a/src/cascadia/UIHelpers/Converters.h +++ b/src/cascadia/UIHelpers/Converters.h @@ -28,6 +28,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation static winrt::Windows::UI::Text::FontWeight DoubleToFontWeight(double value); static winrt::Windows::UI::Xaml::Media::SolidColorBrush ColorToBrush(winrt::Windows::UI::Color color); static double FontWeightToDouble(winrt::Windows::UI::Text::FontWeight fontWeight); + static double MaxValueFromPaddingString(const winrt::hstring& paddingString); }; } diff --git a/src/cascadia/UIHelpers/Converters.idl b/src/cascadia/UIHelpers/Converters.idl index 5cd5b553257..baf8a97af65 100644 --- a/src/cascadia/UIHelpers/Converters.idl +++ b/src/cascadia/UIHelpers/Converters.idl @@ -26,5 +26,6 @@ namespace Microsoft.Terminal.UI static Windows.UI.Text.FontWeight DoubleToFontWeight(Double value); static Windows.UI.Xaml.Media.SolidColorBrush ColorToBrush(Windows.UI.Color color); static Double FontWeightToDouble(Windows.UI.Text.FontWeight fontWeight); + static Double MaxValueFromPaddingString(String paddingString); } }