Implement checking for duplicate hotkeys and overwriting them

This commit is contained in:
Yusyuriv 2024-04-25 14:02:05 +06:00
parent de8036bdba
commit 737d29a0fb
No known key found for this signature in database
GPG key ID: A91C52E6F73148E0
11 changed files with 201 additions and 56 deletions

View file

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Flow.Launcher.Infrastructure.Hotkey;
public interface IHotkeySettings
{
public List<RegisteredHotkeyData> RegisteredHotkeys { get; }
}

View file

@ -0,0 +1,19 @@
using System;
namespace Flow.Launcher.Infrastructure.Hotkey;
#nullable enable
public record RegisteredHotkeyData
{
public HotkeyModel Hotkey { get; }
public string Description { get; }
public Action? RemoveHotkey { get; }
public RegisteredHotkeyData(string hotkey, string description, Action? removeHotkey = null)
{
Hotkey = new HotkeyModel(hotkey);
Description = description;
RemoveHotkey = removeHotkey;
}
}

View file

@ -4,13 +4,14 @@ using System.Collections.ObjectModel;
using System.Drawing;
using System.Text.Json.Serialization;
using System.Windows;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.ViewModel;
namespace Flow.Launcher.Infrastructure.UserSettings
{
public class Settings : BaseModel
public class Settings : BaseModel, IHotkeySettings
{
private string language = "en";
private string _theme = Constant.DefaultTheme;
@ -207,17 +208,17 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public double WindowLeft { get; set; }
public double WindowTop { get; set; }
/// <summary>
/// Custom left position on selected monitor
/// </summary>
public double CustomWindowLeft { get; set; } = 0;
/// <summary>
/// Custom top position on selected monitor
/// </summary>
public double CustomWindowTop { get; set; } = 0;
public int MaxResultsToShow { get; set; } = 5;
public int ActivateTimes { get; set; }
@ -229,7 +230,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
[JsonIgnore]
public ObservableCollection<BuiltinShortcutModel> BuiltinShortcuts { get; set; } = new()
{
new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText),
new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText),
new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath)
};
@ -253,7 +254,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor;
[JsonConverter(typeof(JsonStringEnumConverter))]
public SearchWindowAligns SearchWindowAlign { get; set; } = SearchWindowAligns.Center;
@ -273,6 +274,78 @@ namespace Flow.Launcher.Infrastructure.UserSettings
// This needs to be loaded last by staying at the bottom
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
[JsonIgnore]
public List<RegisteredHotkeyData> RegisteredHotkeys
{
get
{
var list = new List<RegisteredHotkeyData>
{
new("Escape", "Escape"), // TODO
new("F5", "ReloadPluginHotkey"), // TODO
new("Alt+Home", "Select last result"), // TODO
new("Alt+End", "Select last result"), // TODO
new("Ctrl+R", "Requery"), // TODO
new("Ctrl+H", "ToggleHistoryHotkey"), // TODO
new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"), // TODO
new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"), // TODO
new("Ctrl+OemPlus", "QuickHeightHotkey"), // TODO
new("Ctrl+OemMinus", "QuickHeightHotkey"), // TODO
new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"), // TODO
new("Shift+Enter", "OpenContextMenuHotkey"), // TODO
new("Enter", "HotkeyRunDesc"), // TODO
new("Ctrl+Enter", "Open result"), // TODO
new("Alt+Enter", "Open result"), // TODO
new("Ctrl+F12", "ToggleGameModeHotkey"), // TODO
new("Ctrl+Shift+C", "Copy alternative"), // TODO
new($"{OpenResultModifiers}+D1", "Open Result"), // TODO
new($"{OpenResultModifiers}+D2", "Open Result"), // TODO
new($"{OpenResultModifiers}+D3", "Open Result"), // TODO
new($"{OpenResultModifiers}+D4", "Open Result"), // TODO
new($"{OpenResultModifiers}+D5", "Open Result"), // TODO
new($"{OpenResultModifiers}+D6", "Open Result"), // TODO
new($"{OpenResultModifiers}+D7", "Open Result"), // TODO
new($"{OpenResultModifiers}+D8", "Open Result"), // TODO
new($"{OpenResultModifiers}+D9", "Open Result"), // TODO
new($"{OpenResultModifiers}+D0", "Open Result"), // TODO
};
if(!string.IsNullOrEmpty(Hotkey))
list.Add(new(Hotkey, "Open main window", () => Hotkey = "")); // TODO
if(!string.IsNullOrEmpty(PreviewHotkey))
list.Add(new(PreviewHotkey, "Preview Hotkey", () => PreviewHotkey = "")); // TODO
if(!string.IsNullOrEmpty(AutoCompleteHotkey))
list.Add(new(AutoCompleteHotkey, "AutoCompleteHotkey", () => AutoCompleteHotkey = "")); // TODO
if(!string.IsNullOrEmpty(AutoCompleteHotkey2))
list.Add(new(AutoCompleteHotkey2, "AutoCompleteHotkey", () => AutoCompleteHotkey2 = "")); // TODO
if(!string.IsNullOrEmpty(SelectNextItemHotkey))
list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = "")); // TODO
if(!string.IsNullOrEmpty(SelectNextItemHotkey2))
list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = "")); // TODO
if(!string.IsNullOrEmpty(SelectPrevItemHotkey))
list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = "")); // TODO
if(!string.IsNullOrEmpty(SelectPrevItemHotkey2))
list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = "")); // TODO
if(!string.IsNullOrEmpty(SettingWindowHotkey))
list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = "")); // TODO
if(!string.IsNullOrEmpty(OpenContextMenuHotkey))
list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = "")); // TODO
if(!string.IsNullOrEmpty(SelectNextPageHotkey))
list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = "")); // TODO
if(!string.IsNullOrEmpty(SelectPrevPageHotkey))
list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = "")); // TODO
foreach (var customPluginHotkey in CustomPluginHotkeys)
{
if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey))
list.Add(new(customPluginHotkey.Hotkey, "Custom plugin hotkey", () => customPluginHotkey.Hotkey = "")); // TODO
}
return list;
}
}
}
public enum LastQueryMode
@ -288,7 +361,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Light,
Dark
}
public enum SearchWindowScreens
{
RememberLastLaunchLocation,
@ -297,7 +370,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Primary,
Custom
}
public enum SearchWindowAligns
{
Center,

View file

@ -106,6 +106,7 @@
HorizontalAlignment="Left"
VerticalAlignment="Center"
HorizontalContentAlignment="Left"
HotkeySettings="{Binding Settings}"
DefaultHotkey="" />
<TextBlock
Grid.Row="1"

View file

@ -15,13 +15,13 @@ namespace Flow.Launcher
private SettingWindow _settingWidow;
private bool update;
private CustomPluginHotkey updateCustomHotkey;
private Settings _settings;
public Settings Settings { get; }
public CustomQueryHotkeySetting(SettingWindow settingWidow, Settings settings)
{
_settingWidow = settingWidow;
Settings = settings;
InitializeComponent();
_settings = settings;
}
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
@ -33,13 +33,13 @@ namespace Flow.Launcher
{
if (!update)
{
_settings.CustomPluginHotkeys ??= new ObservableCollection<CustomPluginHotkey>();
Settings.CustomPluginHotkeys ??= new ObservableCollection<CustomPluginHotkey>();
var pluginHotkey = new CustomPluginHotkey
{
Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text
};
_settings.CustomPluginHotkeys.Add(pluginHotkey);
Settings.CustomPluginHotkeys.Add(pluginHotkey);
HotKeyMapper.SetCustomQueryHotkey(pluginHotkey);
}
@ -59,7 +59,7 @@ namespace Flow.Launcher
public void UpdateItem(CustomPluginHotkey item)
{
updateCustomHotkey = _settings.CustomPluginHotkeys.FirstOrDefault(o =>
updateCustomHotkey = Settings.CustomPluginHotkeys.FirstOrDefault(o =>
o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey);
if (updateCustomHotkey == null)
{

View file

@ -12,6 +12,17 @@ namespace Flow.Launcher
{
public partial class HotkeyControl
{
public IHotkeySettings HotkeySettings {
get { return (IHotkeySettings)GetValue(HotkeySettingsProperty); }
set { SetValue(HotkeySettingsProperty, value); }
}
public static readonly DependencyProperty HotkeySettingsProperty = DependencyProperty.Register(
nameof(HotkeySettings),
typeof(IHotkeySettings),
typeof(HotkeyControl),
new PropertyMetadata()
);
public string WindowTitle {
get { return (string)GetValue(WindowTitleProperty); }
set { SetValue(WindowTitleProperty, value); }
@ -122,7 +133,7 @@ namespace Flow.Launcher
HotKeyMapper.RemoveHotkey(Hotkey);
}
var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, WindowTitle);
var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, HotkeySettings, WindowTitle);
await dialog.ShowAsync();
switch (dialog.ResultType)
{

View file

@ -106,6 +106,7 @@
Grid.Column="1"
x:Name="tbMsg"
Margin="0,0,0,2"
Padding="0,0,8,0"
HorizontalAlignment="Left"
FontSize="13"
FontWeight="SemiBold"
@ -127,6 +128,15 @@
Margin="10"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Button
x:Name="OverwriteBtn"
Height="30"
MinWidth="100"
Margin="0,0,4,0"
Click="Overwrite"
Content="{DynamicResource commonOverwrite}"
Visibility="Collapsed"
Style="{StaticResource AccentButtonStyle}" />
<Button
x:Name="SaveBtn"
Height="30"

View file

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using Flow.Launcher.Core.Resource;
@ -10,35 +11,17 @@ using ModernWpf.Controls;
namespace Flow.Launcher;
#nullable enable
public partial class HotkeyControlDialog : ContentDialog
{
private IHotkeySettings _hotkeySettings;
private Action? _overwriteOtherHotkey;
private string DefaultHotkey { get; }
public string WindowTitle { get; }
public HotkeyModel CurrentHotkey { get; private set; }
public ObservableCollection<string> KeysToDisplay { get; } = new();
private readonly Dictionary<HotkeyModel, string> StaticHotkeys = new()
{
[new HotkeyModel("Escape")] = "", // TODO
[new HotkeyModel("F5")] = "ReloadPluginHotkey",
[new HotkeyModel("Alt+Home")] = "Select first result", // TODO
[new HotkeyModel("Alt+End")] = "Select last result", // TODO
[new HotkeyModel("Ctrl+R")] = "Requery", // TODO
[new HotkeyModel("Ctrl+H")] = "ToggleHistoryHotkey",
[new HotkeyModel("Ctrl+OemCloseBrackets")] = "QuickWidthHotkey",
[new HotkeyModel("Ctrl+OemOpenBrackets")] = "QuickWidthHotkey",
[new HotkeyModel("Ctrl+OemPlus")] = "QuickHeightHotkey",
[new HotkeyModel("Ctrl+OemMinus")] = "QuickHeightHotkey",
[new HotkeyModel("Ctrl+Shift+Enter")] = "HotkeyCtrlShiftEnterDesc",
[new HotkeyModel("Shift+Enter")] = "OpenContextMenuHotkey",
[new HotkeyModel("Enter")] = "HotkeyRunDesc",
[new HotkeyModel("Ctrl+Enter")] = "Open result", // TODO
[new HotkeyModel("Alt+Enter")] = "Open result", // TODO
// TODO D0-D9 But not here since they're not completely static, they can be Ctrl+D0-D9, Alt+D0-D9, or Ctrl+Alt+D0-D9
[new HotkeyModel("Ctrl+F12")] = "ToggleGameModeHotkey",
[new HotkeyModel("Ctrl+Shift+C")] = "Copy alternative", // TODO
};
public enum EResultType
{
Cancel,
@ -50,7 +33,7 @@ public partial class HotkeyControlDialog : ContentDialog
public string ResultValue { get; private set; } = string.Empty;
public static string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none");
public HotkeyControlDialog(string hotkey, string defaultHotkey, string windowTitle = "")
public HotkeyControlDialog(string hotkey, string defaultHotkey, IHotkeySettings hotkeySettings, string windowTitle = "")
{
WindowTitle = windowTitle switch
{
@ -59,6 +42,7 @@ public partial class HotkeyControlDialog : ContentDialog
};
DefaultHotkey = defaultHotkey;
CurrentHotkey = new HotkeyModel(hotkey);
_hotkeySettings = hotkeySettings;
SetKeysToDisplay(CurrentHotkey);
InitializeComponent();
@ -116,6 +100,7 @@ public partial class HotkeyControlDialog : ContentDialog
private void SetKeysToDisplay(HotkeyModel? hotkey)
{
_overwriteOtherHotkey = null;
KeysToDisplay.Clear();
if (hotkey == null || hotkey == default(HotkeyModel))
@ -132,35 +117,59 @@ public partial class HotkeyControlDialog : ContentDialog
if (tbMsg == null)
return;
if (StaticHotkeys.TryGetValue((HotkeyModel)hotkey, out var staticHotkey))
if (_hotkeySettings.RegisteredHotkeys.FirstOrDefault(v => v.Hotkey == hotkey) is { } registeredHotkeyData)
{
ShowWarningAndDisableSaveButton(
string.Format(
InternationalizationManager.Instance.GetTranslation("hotkeyUnavailableInUseStatic"),
InternationalizationManager.Instance.GetTranslation(staticHotkey)
)
);
Alert.Visibility = Visibility.Visible;
if (registeredHotkeyData.RemoveHotkey is not null)
{
tbMsg.Text = string.Format(
InternationalizationManager.Instance.GetTranslation("hotkeyUnavailableEditable"),
registeredHotkeyData.Description
);
SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Collapsed;
OverwriteBtn.IsEnabled = true;
OverwriteBtn.Visibility = Visibility.Visible;
_overwriteOtherHotkey = registeredHotkeyData.RemoveHotkey;
}
else
{
tbMsg.Text = string.Format(
InternationalizationManager.Instance.GetTranslation("hotkeyUnavailableUneditable"),
registeredHotkeyData.Description
);
SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible;
OverwriteBtn.IsEnabled = false;
OverwriteBtn.Visibility = Visibility.Collapsed;
}
return;
}
OverwriteBtn.IsEnabled = false;
OverwriteBtn.Visibility = Visibility.Collapsed;
if (!CheckHotkeyAvailability(hotkey.Value, true))
{
ShowWarningAndDisableSaveButton(InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable"));
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
Alert.Visibility = Visibility.Visible;
SaveBtn.IsEnabled = false;
SaveBtn.Visibility = Visibility.Visible;
}
else
{
Alert.Visibility = Visibility.Collapsed;
SaveBtn.IsEnabled = true;
SaveBtn.Visibility = Visibility.Visible;
}
}
private void ShowWarningAndDisableSaveButton(string message)
{
tbMsg.Text = message;
Alert.Visibility = Visibility.Visible;
SaveBtn.IsEnabled = false;
}
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) =>
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
private void Overwrite(object sender, RoutedEventArgs e)
{
_overwriteOtherHotkey?.Invoke();
Save(sender, e);
}
}

View file

@ -311,8 +311,8 @@
<system:String x:Key="update">Update</system:String>
<system:String x:Key="hotkeyRegTitle">Binding Hotkey</system:String>
<system:String x:Key="hotkeyUnavailable">Current hotkey is unavailable.</system:String>
<system:String x:Key="hotkeyUnavailableInUseStatic">This hotkey is unavailable because it is reserved for Flow Launcher's functionality: {0}.</system:String>
<system:String x:Key="hotkeyUnavailableInUse">This hotkey is unavailable because it is already used for {0}.</system:String>
<system:String x:Key="hotkeyUnavailableUneditable">This hotkey is reserved for "{0}" and can't be used. Please choose another hotkey.</system:String>
<system:String x:Key="hotkeyUnavailableEditable">This hotkey is already in use by "{0}". If you press "Overwrite", it will be removed from "{0}".</system:String>
<system:String x:Key="hotkeyRegGuide">Press the keys you want to use for this function.</system:String>
<!-- Custom Query Shortcut Dialog -->
@ -325,6 +325,7 @@
<!-- Common Action -->
<system:String x:Key="commonSave">Save</system:String>
<system:String x:Key="commonOverwrite">Overwrite</system:String>
<system:String x:Key="commonCancel">Cancel</system:String>
<system:String x:Key="commonReset">Reset</system:String>
<system:String x:Key="commonDelete">Delete</system:String>

View file

@ -115,6 +115,7 @@
ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
DefaultHotkey="Alt+Space"
Hotkey="{Binding Settings.Hotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="True"
WindowTitle="{DynamicResource flowlauncherHotkey}" />
</StackPanel>

View file

@ -2663,6 +2663,7 @@
ChangeHotkey="{Binding SetTogglingHotkeyCommand}"
DefaultHotkey="Alt+Space"
Hotkey="{Binding Settings.Hotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="True"
WindowTitle="{DynamicResource flowlauncherHotkey}" />
</cc:Card>
@ -2676,6 +2677,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey="F1"
Hotkey="{Binding Settings.PreviewHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False"
WindowTitle="{DynamicResource previewHotkey}" />
</cc:Card>
@ -2776,6 +2778,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey="Ctrl+I"
Hotkey="{Binding Settings.OpenContextMenuHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
@ -2792,6 +2795,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey="Ctrl+I"
Hotkey="{Binding Settings.SettingWindowHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
@ -2814,6 +2818,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.SelectNextPageHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
<cc:Card
@ -2823,6 +2828,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.SelectPrevPageHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
@ -2857,6 +2863,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey="Ctrl+Tab"
Hotkey="{Binding Settings.AutoCompleteHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:ExCard.SideContent>
<cc:Card
@ -2866,6 +2873,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.AutoCompleteHotkey2}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
</cc:ExCard>
@ -2879,6 +2887,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey="Tab"
Hotkey="{Binding Settings.SelectNextItemHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:ExCard.SideContent>
<cc:Card
@ -2888,6 +2897,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.SelectNextItemHotkey2}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
</cc:ExCard>
@ -2900,6 +2910,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey="Shift+Tab"
Hotkey="{Binding Settings.SelectPrevItemHotkey}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:ExCard.SideContent>
<cc:Card
@ -2909,6 +2920,7 @@
<flowlauncher:HotkeyControl
DefaultHotkey=""
Hotkey="{Binding Settings.SelectPrevItemHotkey2}"
HotkeySettings="{Binding Settings}"
ValidateKeyGesture="False" />
</cc:Card>
</cc:ExCard>