diff --git a/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs new file mode 100644 index 00000000000..448a70d191c --- /dev/null +++ b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Infrastructure.Hotkey; + +/// +/// Interface that you should implement in your settings class to be able to pass it to +/// Flow.Launcher.HotkeyControlDialog. It allows the dialog to display the hotkeys that have already been +/// registered, and optionally provide a way to unregister them. +/// +public interface IHotkeySettings +{ + /// + /// A list of hotkeys that have already been registered. The dialog will display these hotkeys and provide a way to + /// unregister them. + /// + public List RegisteredHotkeys { get; } +} diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs new file mode 100644 index 00000000000..b6a10fc3075 --- /dev/null +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -0,0 +1,119 @@ +using System; + +namespace Flow.Launcher.Infrastructure.Hotkey; + +#nullable enable + +/// +/// Represents a hotkey that has been registered. Used in Flow.Launcher.HotkeyControlDialog via +/// and to display errors if user tries to register a hotkey +/// that has already been registered, and optionally provides a way to unregister the hotkey. +/// +public record RegisteredHotkeyData +{ + /// + /// representation of this hotkey. + /// + public HotkeyModel Hotkey { get; } + + /// + /// String key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + public string DescriptionResourceKey { get; } + + /// + /// Array of values that will replace {0}, {1}, {2}, etc. in the localized string found via + /// . + /// + public object?[] DescriptionFormatVariables { get; } = Array.Empty(); + + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that + /// this hotkey can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public Action? RemoveHotkey { get; } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// descriptionResourceKey doesn't need any arguments for string.Format. If it does, + /// use one of the other constructors. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData(string hotkey, string descriptionResourceKey, Action? removeHotkey = null) + { + Hotkey = new HotkeyModel(hotkey); + DescriptionResourceKey = descriptionResourceKey; + RemoveHotkey = removeHotkey; + } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// descriptionResourceKey needs exactly one argument for string.Format. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// The value that will replace {0} in the localized string found via description. + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData( + string hotkey, string descriptionResourceKey, object? descriptionFormatVariable, Action? removeHotkey = null + ) + { + Hotkey = new HotkeyModel(hotkey); + DescriptionResourceKey = descriptionResourceKey; + DescriptionFormatVariables = new[] { descriptionFormatVariable }; + RemoveHotkey = removeHotkey; + } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// needs multiple arguments for string.Format. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// Array of values that will replace {0}, {1}, {2}, etc. + /// in the localized string found via description. + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData( + string hotkey, string descriptionResourceKey, object?[] descriptionFormatVariables, Action? removeHotkey = null + ) + { + Hotkey = new HotkeyModel(hotkey); + DescriptionResourceKey = descriptionResourceKey; + DescriptionFormatVariables = descriptionFormatVariables; + RemoveHotkey = removeHotkey; + } +} diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 343e91a8500..58b1cc467c6 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -4,13 +4,14 @@ 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 @@ public string QuerySearchPrecisionString public double WindowLeft { get; set; } public double WindowTop { get; set; } - + /// /// Custom left position on selected monitor /// public double CustomWindowLeft { get; set; } = 0; - + /// /// Custom top position on selected monitor /// public double CustomWindowTop { get; set; } = 0; - + public int MaxResultsToShow { get; set; } = 5; public int ActivateTimes { get; set; } @@ -229,7 +230,7 @@ public string QuerySearchPrecisionString [JsonIgnore] public ObservableCollection 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 @@ public bool HideNotifyIcon [JsonConverter(typeof(JsonStringEnumConverter))] public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor; - + [JsonConverter(typeof(JsonStringEnumConverter))] public SearchWindowAligns SearchWindowAlign { get; set; } = SearchWindowAligns.Center; @@ -273,6 +274,82 @@ public bool HideNotifyIcon // This needs to be loaded last by staying at the bottom public PluginsSettings PluginSettings { get; set; } = new PluginsSettings(); + + [JsonIgnore] + public List RegisteredHotkeys + { + get + { + var list = new List + { + new("Up", "HotkeyLeftRightDesc"), + new("Down", "HotkeyLeftRightDesc"), + new("Left", "HotkeyUpDownDesc"), + new("Right", "HotkeyUpDownDesc"), + new("Escape", "HotkeyESCDesc"), + new("F5", "ReloadPluginHotkey"), + new("Alt+Home", "HotkeySelectFirstResult"), + new("Alt+End", "HotkeySelectLastResult"), + new("Ctrl+R", "HotkeyRequery"), + new("Ctrl+H", "ToggleHistoryHotkey"), + new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"), + new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"), + new("Ctrl+OemPlus", "QuickHeightHotkey"), + new("Ctrl+OemMinus", "QuickHeightHotkey"), + new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"), + new("Shift+Enter", "OpenContextMenuHotkey"), + new("Enter", "HotkeyRunDesc"), + new("Ctrl+Enter", "OpenContainFolderHotkey"), + new("Alt+Enter", "HotkeyOpenResult"), + new("Ctrl+F12", "ToggleGameModeHotkey"), + new("Ctrl+Shift+C", "CopyFilePathHotkey"), + + new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1), + new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2), + new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3), + new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4), + new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5), + new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6), + new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7), + new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8), + new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9), + new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10) + }; + + if(!string.IsNullOrEmpty(Hotkey)) + list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = "")); + if(!string.IsNullOrEmpty(PreviewHotkey)) + list.Add(new(PreviewHotkey, "previewHotkey", () => PreviewHotkey = "")); + if(!string.IsNullOrEmpty(AutoCompleteHotkey)) + list.Add(new(AutoCompleteHotkey, "autoCompleteHotkey", () => AutoCompleteHotkey = "")); + if(!string.IsNullOrEmpty(AutoCompleteHotkey2)) + list.Add(new(AutoCompleteHotkey2, "autoCompleteHotkey", () => AutoCompleteHotkey2 = "")); + if(!string.IsNullOrEmpty(SelectNextItemHotkey)) + list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = "")); + if(!string.IsNullOrEmpty(SelectNextItemHotkey2)) + list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = "")); + if(!string.IsNullOrEmpty(SelectPrevItemHotkey)) + list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = "")); + if(!string.IsNullOrEmpty(SelectPrevItemHotkey2)) + list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = "")); + if(!string.IsNullOrEmpty(SettingWindowHotkey)) + list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = "")); + if(!string.IsNullOrEmpty(OpenContextMenuHotkey)) + list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = "")); + if(!string.IsNullOrEmpty(SelectNextPageHotkey)) + list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = "")); + if(!string.IsNullOrEmpty(SelectPrevPageHotkey)) + list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = "")); + + foreach (var customPluginHotkey in CustomPluginHotkeys) + { + if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) + list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); + } + + return list; + } + } } public enum LastQueryMode @@ -288,7 +365,7 @@ public enum ColorSchemes Light, Dark } - + public enum SearchWindowScreens { RememberLastLaunchLocation, @@ -297,7 +374,7 @@ public enum SearchWindowScreens Primary, Custom } - + public enum SearchWindowAligns { Center, diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml index de724ea819a..068afda15b4 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -106,6 +106,7 @@ HorizontalAlignment="Left" VerticalAlignment="Center" HorizontalContentAlignment="Left" + HotkeySettings="{Binding Settings}" DefaultHotkey="" /> (); + Settings.CustomPluginHotkeys ??= new ObservableCollection(); 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 @@ private void btnAdd_OnClick(object sender, RoutedEventArgs e) 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) { diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 546b252df95..a42bde7c9cd 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -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 @@ private async Task OpenHotkeyDialog() HotKeyMapper.RemoveHotkey(Hotkey); } - var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, WindowTitle); + var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, HotkeySettings, WindowTitle); await dialog.ShowAsync(); switch (dialog.ResultType) { diff --git a/Flow.Launcher/HotkeyControlDialog.xaml b/Flow.Launcher/HotkeyControlDialog.xaml index 322f82366d7..9a5872f4dc1 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml +++ b/Flow.Launcher/HotkeyControlDialog.xaml @@ -82,7 +82,8 @@ - - - - + + + + + + @@ -121,6 +128,15 @@ Margin="10" HorizontalAlignment="Center" Orientation="Horizontal"> +