mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
[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:
@@ -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;
|
||||
|
||||
@@ -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=}"
|
||||
IsExpanded="True">
|
||||
<tkcontrols:SettingsCard x:Uid="AdvancedPasteUI_Actions" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<Button
|
||||
x:Uid="AdvancedPasteUI_AddCustomActionButton"
|
||||
Click="AddCustomActionButton_Click"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard x:Uid="AdvancedPasteUI_Shortcut" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<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=""
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="MoveUp"
|
||||
Click="ReorderButtonUp_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind CanMoveUp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="MoveDown"
|
||||
Click="ReorderButtonDown_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
IsEnabled="{x:Bind CanMoveDown, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="RemoveItem"
|
||||
Click="DeleteCustomActionButton_Click"
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user