[AdvancedPaste] Custom Actions (#34395)

* [AdvancedPaste] Custom Actions

* Renamed pipe name to make spellcheck happy

* Improved settings page for Custom Actions

* UI improvements, disabled standard paste actions when no clipboard text, update clipboard text and gpo state every second

* Bug fixes, single query/prompt box, Ctrl+num shortcuts for custom actions, error box

* Spellcheck issue

* Bug fixes and used Advanced Paste Window as wait indicator for keyboard shortcuts

* Improvements to PromptBox, incluing show error message as tooltip

* Refactoring

* Fixed issue where ESC sometimes didn't close paste window

* Update src/settings-ui/Settings.UI/Strings/en-us/Resources.resw

Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>

---------

Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
This commit is contained in:
Ani
2024-08-22 16:17:12 +02:00
committed by GitHub
parent bfa35d65a4
commit 2a8e211cfd
30 changed files with 1581 additions and 491 deletions

View File

@@ -115,13 +115,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
internalSettings = new HotkeySettings();
this.Unloaded += ShortcutControl_Unloaded;
hook = new HotkeySettingsControlHook(Hotkey_KeyDown, Hotkey_KeyUp, Hotkey_IsActive, FilterAccessibleKeyboardEvents);
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
this.Loaded += ShortcutControl_Loaded;
if (App.GetSettingsWindow() != null)
{
App.GetSettingsWindow().Activated += ShortcutDialog_SettingsWindow_Activated;
}
var resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
// We create the Dialog in C# because doing it in XAML is giving WinUI/XAML Island bugs when using dark theme.
shortcutDialog = new ContentDialog
@@ -134,11 +130,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
CloseButtonText = resourceLoader.GetString("Activation_Shortcut_Cancel"),
DefaultButton = ContentDialogButton.Primary,
};
shortcutDialog.PrimaryButtonClick += ShortcutDialog_PrimaryButtonClick;
shortcutDialog.SecondaryButtonClick += ShortcutDialog_Reset;
shortcutDialog.RightTapped += ShortcutDialog_Disable;
shortcutDialog.Opened += ShortcutDialog_Opened;
shortcutDialog.Closing += ShortcutDialog_Closing;
AutomationProperties.SetName(EditButton, resourceLoader.GetString("Activation_Shortcut_Title"));
OnAllowDisableChanged(this, null);
@@ -156,14 +150,28 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
}
// Dispose the HotkeySettingsControlHook object to terminate the hook threads when the textbox is unloaded
if (hook != null)
{
hook.Dispose();
}
hook?.Dispose();
hook = null;
}
private void ShortcutControl_Loaded(object sender, RoutedEventArgs e)
{
// These all belong here; because of virtualization in e.g. a ListView, the control can go through several Loaded / Unloaded cycles.
hook?.Dispose();
hook = new HotkeySettingsControlHook(Hotkey_KeyDown, Hotkey_KeyUp, Hotkey_IsActive, FilterAccessibleKeyboardEvents);
shortcutDialog.PrimaryButtonClick += ShortcutDialog_PrimaryButtonClick;
shortcutDialog.Opened += ShortcutDialog_Opened;
shortcutDialog.Closing += ShortcutDialog_Closing;
if (App.GetSettingsWindow() != null)
{
App.GetSettingsWindow().Activated += ShortcutDialog_SettingsWindow_Activated;
}
}
private void KeyEventHandler(int key, bool matchValue, int matchValueCode)
{
VirtualKey virtualKey = (VirtualKey)key;

View File

@@ -5,8 +5,10 @@
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:Microsoft.PowerToys.Settings.UI.Library"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:ui="using:CommunityToolkit.WinUI"
x:Name="RootPage"
AutomationProperties.LandmarkType="Main"
mc:Ignorable="d">
<Page.Resources>
@@ -100,33 +102,95 @@
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="AdvancedPaste_Direct_Access_Hotkeys_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
<tkcontrols:SettingsExpander
x:Uid="AdvancedPasteUI_Shortcut"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}"
IsExpanded="True">
<tkcontrols:SettingsCard x:Uid="AdvancedPasteUI_Actions" HeaderIcon="{ui:FontIcon Glyph=&#xE792;}">
<Button
x:Uid="AdvancedPasteUI_AddCustomActionButton"
Click="AddCustomActionButton_Click"
Style="{ThemeResource AccentButtonStyle}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="AdvancedPasteUI_Shortcut" HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.AdvancedPasteUIShortcut, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard Visibility="Collapsed">
<!-- There's a bug that makes it so that the first shortcut control inside an expander doesn't work. We add this dummy one so the other entries aren't affected. -->
<TextBox />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsPlainText_Shortcut">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.PasteAsPlainTextShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsMarkdown_Shortcut">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=ViewModel.PasteAsMarkdownShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsJson_Shortcut">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=ViewModel.PasteAsJsonShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsPlainText_Shortcut">
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.PasteAsPlainTextShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsMarkdown_Shortcut">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=ViewModel.PasteAsMarkdownShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard x:Uid="PasteAsJson_Shortcut">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=ViewModel.PasteAsJsonShortcut, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<ItemsControl
x:Name="CustomActions"
x:Uid="CustomActions"
HorizontalAlignment="Stretch"
IsTabStop="False"
ItemsSource="{x:Bind ViewModel.CustomActions, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="models:AdvancedPasteCustomAction">
<tkcontrols:SettingsCard
Margin="0,0,0,2"
Click="EditCustomActionButton_Click"
Description="{x:Bind Prompt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Header="{x:Bind Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsActionIconVisible="False"
IsClickEnabled="True">
<tkcontrols:SettingsCard.Resources>
<x:Double x:Key="SettingsCardActionButtonWidth">0</x:Double>
</tkcontrols:SettingsCard.Resources>
<StackPanel Orientation="Horizontal" Spacing="4">
<controls:ShortcutControl
MinWidth="{StaticResource SettingActionControlMinWidth}"
AllowDisable="True"
HotkeySettings="{x:Bind Path=Shortcut, Mode=TwoWay}" />
<ToggleSwitch
x:Uid="Enable_CustomAction"
AutomationProperties.HelpText="{x:Bind Name}"
IsOn="{x:Bind IsShown, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
OffContent=""
OnContent="" />
<Button
x:Uid="More_Options_Button"
Grid.Column="1"
VerticalAlignment="Center"
Content="&#xE712;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Style="{StaticResource SubtleButtonStyle}">
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem
x:Uid="MoveUp"
Click="ReorderButtonUp_Click"
Icon="{ui:FontIcon Glyph=&#xE74A;}"
IsEnabled="{x:Bind CanMoveUp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<MenuFlyoutItem
x:Uid="MoveDown"
Click="ReorderButtonDown_Click"
Icon="{ui:FontIcon Glyph=&#xE74B;}"
IsEnabled="{x:Bind CanMoveDown, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<MenuFlyoutSeparator />
<MenuFlyoutItem
x:Uid="RemoveItem"
Click="DeleteCustomActionButton_Click"
Icon="{ui:FontIcon Glyph=&#xE74D;}"
IsEnabled="true" />
</MenuFlyout>
</Button.Flyout>
<ToolTipService.ToolTip>
<TextBlock x:Uid="More_Options_ButtonTooltip" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</tkcontrols:SettingsCard>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<InfoBar
x:Uid="AdvancedPaste_ShortcutWarning"
IsClosable="False"
@@ -202,5 +266,31 @@
</Grid>
</Grid>
</ContentDialog>
<ContentDialog
x:Name="CustomActionDialog"
x:Uid="CustomActionDialog"
Closed="CustomActionDialog_Closed"
IsPrimaryButtonEnabled="{Binding IsValid, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsSecondaryButtonEnabled="True"
PrimaryButtonStyle="{ThemeResource AccentButtonStyle}">
<ContentDialog.DataContext>
<models:AdvancedPasteCustomAction />
</ContentDialog.DataContext>
<StackPanel Spacing="16">
<TextBox
x:Uid="AdvancedPasteUI_CustomAction_Name"
Width="340"
HorizontalAlignment="Left"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox
x:Uid="AdvancedPasteUI_CustomAction_Prompt"
Width="340"
Height="280"
HorizontalAlignment="Left"
AcceptsReturn="true"
Text="{Binding Prompt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" />
</StackPanel>
</ContentDialog>
</Grid>
</Page>

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Helpers;
@@ -10,7 +11,6 @@ using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Security.Credentials;
namespace Microsoft.PowerToys.Settings.UI.Views
{
@@ -69,14 +69,85 @@ namespace Microsoft.PowerToys.Settings.UI.Views
private void AdvancedPaste_EnableAIDialogOpenAIApiKey_TextChanged(object sender, TextChangedEventArgs e)
{
if (AdvancedPaste_EnableAIDialogOpenAIApiKey.Text.Length > 0)
EnableAIDialog.IsPrimaryButtonEnabled = AdvancedPaste_EnableAIDialogOpenAIApiKey.Text.Length > 0;
}
public async void DeleteCustomActionButton_Click(object sender, RoutedEventArgs e)
{
var customAction = GetBoundCustomAction(sender);
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
ContentDialog dialog = new()
{
EnableAIDialog.IsPrimaryButtonEnabled = true;
XamlRoot = RootPage.XamlRoot,
Title = customAction.Name,
PrimaryButtonText = resourceLoader.GetString("Yes"),
CloseButtonText = resourceLoader.GetString("No"),
DefaultButton = ContentDialogButton.Primary,
Content = new TextBlock() { Text = resourceLoader.GetString("Delete_Dialog_Description") },
};
dialog.PrimaryButtonClick += (_, _) => ViewModel.DeleteCustomAction(customAction);
await dialog.ShowAsync();
}
private async void AddCustomActionButton_Click(object sender, RoutedEventArgs e)
{
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
CustomActionDialog.Title = resourceLoader.GetString("AddCustomAction");
CustomActionDialog.DataContext = ViewModel.GetNewCustomAction(resourceLoader.GetString("AdvancedPasteUI_NewCustomActionPrefix"));
CustomActionDialog.PrimaryButtonText = resourceLoader.GetString("CustomActionSave");
await CustomActionDialog.ShowAsync();
}
private async void EditCustomActionButton_Click(object sender, RoutedEventArgs e)
{
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
CustomActionDialog.Title = resourceLoader.GetString("EditCustomAction");
CustomActionDialog.DataContext = GetBoundCustomAction(sender).Clone();
CustomActionDialog.PrimaryButtonText = resourceLoader.GetString("CustomActionUpdate");
await CustomActionDialog.ShowAsync();
}
private void ReorderButtonDown_Click(object sender, RoutedEventArgs e)
{
var index = ViewModel.CustomActions.IndexOf(GetBoundCustomAction(sender));
ViewModel.CustomActions.Move(index, index + 1);
}
private void ReorderButtonUp_Click(object sender, RoutedEventArgs e)
{
var index = ViewModel.CustomActions.IndexOf(GetBoundCustomAction(sender));
ViewModel.CustomActions.Move(index, index - 1);
}
private void CustomActionDialog_Closed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
if (args.Result != ContentDialogResult.Primary)
{
return;
}
var dialogCustomAction = GetBoundCustomAction(sender);
var existingCustomAction = ViewModel.CustomActions.FirstOrDefault(candidate => candidate.Id == dialogCustomAction.Id);
if (existingCustomAction == null)
{
ViewModel.AddCustomAction(dialogCustomAction);
var element = (ContentPresenter)CustomActions.ContainerFromIndex(CustomActions.Items.Count - 1);
element.StartBringIntoView(new BringIntoViewOptions { VerticalOffset = -60, AnimationDesired = true });
element.Focus(FocusState.Programmatic);
}
else
{
EnableAIDialog.IsPrimaryButtonEnabled = false;
existingCustomAction.Update(dialogCustomAction);
}
}
private static AdvancedPasteCustomAction GetBoundCustomAction(object sender) => (AdvancedPasteCustomAction)((FrameworkElement)sender).DataContext;
}
}

View File

@@ -720,7 +720,7 @@
<value>Save multiple items to your clipboard. This is an OS feature.</value>
</data>
<data name="AdvancedPaste_Direct_Access_Hotkeys_GroupSettings.Header" xml:space="preserve">
<value>Shortcuts</value>
<value>Actions</value>
</data>
<data name="RemapKeysList.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Current Key Remappings</value>
@@ -1996,9 +1996,43 @@ Made with 💗 by Microsoft and the PowerToys community.</value>
<data name="PasteAsPlainText_Shortcut.Header" xml:space="preserve">
<value>Paste as plain text directly</value>
</data>
<data name="AdvancedPasteUI_Actions.Header" xml:space="preserve">
<value>Actions</value>
</data>
<data name="AdvancedPasteUI_Actions.Description" xml:space="preserve">
<value>Create and manage advanced paste custom actions</value>
</data>
<data name="AdvancedPasteUI_AddCustomActionButton.Content" xml:space="preserve">
<value>Add custom action</value>
</data>
<data name="AdvancedPasteUI_Shortcut.Header" xml:space="preserve">
<value>Open Advanced Paste window</value>
</data>
<data name="AdvancedPasteUI_NewCustomActionPrefix" xml:space="preserve">
<value>New custom action</value>
<comment>First part of the default name of new custom action that can be added in PT's settings ui.</comment>
</data>
<data name="AdvancedPasteUI_CustomAction_Name.Header" xml:space="preserve">
<value>Name</value>
</data>
<data name="AdvancedPasteUI_CustomAction_Prompt.Header" xml:space="preserve">
<value>Prompt</value>
</data>
<data name="CustomActionDialog.SecondaryButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="AddCustomAction" xml:space="preserve">
<value>Add custom action</value>
</data>
<data name="CustomActionSave" xml:space="preserve">
<value>Save</value>
</data>
<data name="EditCustomAction" xml:space="preserve">
<value>Edit custom action</value>
</data>
<data name="CustomActionUpdate" xml:space="preserve">
<value>Update</value>
</data>
<data name="PasteAsMarkdown_Shortcut.Header" xml:space="preserve">
<value>Paste as Markdown directly</value>
</data>

View File

@@ -3,8 +3,15 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Timers;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -17,6 +24,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class AdvancedPasteViewModel : Observable, IDisposable
{
private static readonly HashSet<string> WarnHotkeys = ["Ctrl + V", "Ctrl + Shift + V"];
private bool disposedValue;
// Delay saving of settings in order to avoid calling save multiple times and hitting file in use exception. If there is no other request to save settings in given interval, we proceed to save it, otherwise we schedule saving it after this interval
@@ -28,6 +37,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private readonly object _delayedActionLock = new object();
private readonly AdvancedPasteSettings _advancedPasteSettings;
private readonly ObservableCollection<AdvancedPasteCustomAction> _customActions;
private Timer _delayedTimer;
private GpoRuleConfigured _enabledGpoRuleConfiguration;
@@ -58,6 +68,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_advancedPasteSettings = advancedPasteSettingsRepository.SettingsConfig;
_customActions = _advancedPasteSettings.Properties.CustomActions.Value;
InitializeEnabledValue();
// set the callback functions value to handle outgoing IPC message.
@@ -67,6 +79,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_delayedTimer.Interval = SaveSettingsDelayInMs;
_delayedTimer.Elapsed += DelayedTimer_Tick;
_delayedTimer.AutoReset = false;
foreach (var customAction in _customActions)
{
customAction.PropertyChanged += OnCustomActionPropertyChanged;
}
_customActions.CollectionChanged += OnCustomActionsCollectionChanged;
UpdateCustomActionsCanMoveUpDown();
}
private void InitializeEnabledValue()
@@ -120,6 +140,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public ObservableCollection<AdvancedPasteCustomAction> CustomActions => _customActions;
private bool OpenAIKeyExists()
{
PasswordVault vault = new PasswordVault();
@@ -234,8 +256,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
OnPropertyChanged(nameof(AdvancedPasteUIShortcut));
OnPropertyChanged(nameof(IsConflictingCopyShortcut));
_settingsUtils.SaveSettings(_advancedPasteSettings.ToJsonString(), AdvancedPasteSettings.ModuleName);
NotifySettingsChanged();
SaveAndNotifySettings();
}
}
}
@@ -251,8 +272,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
OnPropertyChanged(nameof(PasteAsPlainTextShortcut));
OnPropertyChanged(nameof(IsConflictingCopyShortcut));
_settingsUtils.SaveSettings(_advancedPasteSettings.ToJsonString(), AdvancedPasteSettings.ModuleName);
NotifySettingsChanged();
SaveAndNotifySettings();
}
}
}
@@ -268,8 +288,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
OnPropertyChanged(nameof(PasteAsMarkdownShortcut));
OnPropertyChanged(nameof(IsConflictingCopyShortcut));
_settingsUtils.SaveSettings(_advancedPasteSettings.ToJsonString(), AdvancedPasteSettings.ModuleName);
NotifySettingsChanged();
SaveAndNotifySettings();
}
}
}
@@ -285,8 +304,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
OnPropertyChanged(nameof(PasteAsJsonShortcut));
OnPropertyChanged(nameof(IsConflictingCopyShortcut));
_settingsUtils.SaveSettings(_advancedPasteSettings.ToJsonString(), AdvancedPasteSettings.ModuleName);
NotifySettingsChanged();
SaveAndNotifySettings();
}
}
}
@@ -319,13 +337,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
public bool IsConflictingCopyShortcut
{
get
{
return PasteAsPlainTextShortcut.ToString() == "Ctrl + V" || PasteAsPlainTextShortcut.ToString() == "Ctrl + Shift + V" ||
AdvancedPasteUIShortcut.ToString() == "Ctrl + V" || AdvancedPasteUIShortcut.ToString() == "Ctrl + Shift + V" ||
PasteAsMarkdownShortcut.ToString() == "Ctrl + V" || PasteAsMarkdownShortcut.ToString() == "Ctrl + Shift + V" ||
PasteAsJsonShortcut.ToString() == "Ctrl + V" || PasteAsJsonShortcut.ToString() == "Ctrl + Shift + V";
}
get => _customActions.Select(customAction => customAction.Shortcut)
.Concat([PasteAsPlainTextShortcut, AdvancedPasteUIShortcut, PasteAsMarkdownShortcut, PasteAsJsonShortcut])
.Any(hotkey => WarnHotkeys.Contains(hotkey.ToString()));
}
private void DelayedTimer_Tick(object sender, EventArgs e)
@@ -402,5 +416,114 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
}
}
internal AdvancedPasteCustomAction GetNewCustomAction(string namePrefix)
{
ArgumentException.ThrowIfNullOrEmpty(namePrefix);
var maxUsedPrefix = _customActions.Select(customAction => customAction.Name)
.Where(name => name.StartsWith(namePrefix, StringComparison.InvariantCulture))
.Select(name => int.TryParse(name.AsSpan(namePrefix.Length), out int number) ? number : 0)
.DefaultIfEmpty(0)
.Max();
var maxUsedId = _customActions.Select(customAction => customAction.Id)
.DefaultIfEmpty(-1)
.Max();
return new()
{
Id = maxUsedId + 1,
Name = $"{namePrefix} {maxUsedPrefix + 1}",
IsShown = true,
};
}
internal void AddCustomAction(AdvancedPasteCustomAction customAction)
{
if (_customActions.Any(existingCustomAction => existingCustomAction.Id == customAction.Id))
{
throw new ArgumentException("Duplicate custom action", nameof(customAction));
}
_customActions.Add(customAction);
}
internal void DeleteCustomAction(AdvancedPasteCustomAction customAction) => _customActions.Remove(customAction);
private void SaveCustomActions() => SaveAndNotifySettings();
private void SaveAndNotifySettings()
{
_settingsUtils.SaveSettings(_advancedPasteSettings.ToJsonString(), AdvancedPasteSettings.ModuleName);
NotifySettingsChanged();
}
private void OnCustomActionPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (typeof(AdvancedPasteCustomAction).GetProperty(e.PropertyName).GetCustomAttribute<JsonIgnoreAttribute>() == null)
{
SaveCustomActions();
}
if (e.PropertyName == nameof(AdvancedPasteCustomAction.Shortcut))
{
OnPropertyChanged(nameof(IsConflictingCopyShortcut));
}
}
private void OnCustomActionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
void AddRange(System.Collections.IList items)
{
foreach (AdvancedPasteCustomAction item in items)
{
item.PropertyChanged += OnCustomActionPropertyChanged;
}
}
void RemoveRange(System.Collections.IList items)
{
foreach (AdvancedPasteCustomAction item in items)
{
item.PropertyChanged -= OnCustomActionPropertyChanged;
}
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddRange(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemoveRange(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
AddRange(e.NewItems);
RemoveRange(e.OldItems);
break;
case NotifyCollectionChangedAction.Move:
break;
default:
throw new ArgumentException($"Unsupported {nameof(e.Action)} {e.Action}", nameof(e));
}
OnPropertyChanged(nameof(IsConflictingCopyShortcut));
UpdateCustomActionsCanMoveUpDown();
SaveCustomActions();
}
private void UpdateCustomActionsCanMoveUpDown()
{
for (int index = 0; index < _customActions.Count; index++)
{
var customAction = _customActions[index];
customAction.CanMoveUp = index != 0;
customAction.CanMoveDown = index != _customActions.Count - 1;
}
}
}
}