KeybindingManager.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. using System.Diagnostics;
  2. using System.Text.Json;
  3. using System.Text.Json.Serialization;
  4. using Avalonia.Input;
  5. using PicView.Avalonia.UI;
  6. using PicView.Core.Keybindings;
  7. namespace PicView.Avalonia.Input;
  8. [JsonSourceGenerationOptions(AllowTrailingCommas = true, WriteIndented = true)]
  9. [JsonSerializable(typeof(Dictionary<string, string>))]
  10. internal partial class SourceGenerationContext : JsonSerializerContext;
  11. public static class KeybindingManager
  12. {
  13. // TODO move to an interface, use this as default for Windows and make a macOS default
  14. private const string DefaultKeybindings = """
  15. {
  16. "D": "Next",
  17. "Right": "Next",
  18. "Ctrl+Right": "Last",
  19. "Ctrl+D": "Last",
  20. "Ctrl+Left": "First",
  21. "Ctrl+A": "First",
  22. "Shift+D": "NextFolder",
  23. "Shift+Right": "NextFolder",
  24. "Shift+A": "PrevFolder",
  25. "Shift+Left": "PrevFolder",
  26. "A": "Prev",
  27. "Left": "Prev",
  28. "W": "Up",
  29. "Up": "Up",
  30. "S": "Down",
  31. "Down": "Down",
  32. "PageUp": "ScrollUp",
  33. "PageDown": "ScrollDown",
  34. "Add": "ZoomIn",
  35. "OemPlus": "ZoomIn",
  36. "OemMinus": "ZoomOut",
  37. "Subtract": "ZoomOut",
  38. "Scroll": "ToggleScroll",
  39. "Home": "ScrollToTop",
  40. "End": "ScrollToBottom",
  41. "G": "ToggleGallery",
  42. "F": "Flip",
  43. "J": "ResizeImage",
  44. "L": "ToggleLooping",
  45. "C": "Crop",
  46. "E": "GalleryClick",
  47. "Enter": "GalleryClick",
  48. "I": "ImageInfoWindow",
  49. "F6": "EffectsWindow",
  50. "F1": "AboutWindow",
  51. "F3": "OpenInExplorer",
  52. "F4": "SettingsWindow",
  53. "F5": "Slideshow",
  54. "F11": "Fullscreen",
  55. "F12": "Fullscreen",
  56. "B": "ChangeBackground",
  57. "Space": "Center",
  58. "K": "KeybindingsWindow",
  59. "D0": "Set0Star",
  60. "D1": "Set1Star",
  61. "D2": "Set2Star",
  62. "D3": "Set3Star",
  63. "D4": "Set4Star",
  64. "D5": "Set5Star",
  65. "Ctrl+O": "Open",
  66. "Ctrl+E": "OpenWith",
  67. "Ctrl+R": "Reload",
  68. "Ctrl+S": "Save",
  69. "Ctrl+Shift+S": "SaveAs",
  70. "F2": "Rename",
  71. "Ctrl+C": "CopyFile",
  72. "Ctrl+Alt+V": "CopyFilePath",
  73. "Ctrl+Shift+C": "CopyImage",
  74. "Ctrl+X": "CutFile",
  75. "Ctrl+V": "Paste",
  76. "Ctrl+P": "Print",
  77. "Alt+Z": "ToggleInterface",
  78. "Delete": "DeleteFile"
  79. }
  80. """;
  81. public static Dictionary<KeyGesture, Func<Task>>? CustomShortcuts { get; private set; }
  82. public static async Task LoadKeybindings()
  83. {
  84. try
  85. {
  86. var keybindings = await KeybindingFunctions.LoadKeyBindingsFile().ConfigureAwait(false);
  87. await UpdateKeybindings(keybindings).ConfigureAwait(false);
  88. }
  89. catch (Exception)
  90. {
  91. await SetDefaultKeybindings().ConfigureAwait(false);
  92. }
  93. }
  94. public static async Task UpdateKeybindings(string json)
  95. {
  96. // Deserialize JSON into a dictionary of string keys and string values
  97. var keyValues = JsonSerializer.Deserialize(
  98. json, typeof(Dictionary<string, string>), SourceGenerationContext.Default)
  99. as Dictionary<string, string>;
  100. CustomShortcuts ??= new Dictionary<KeyGesture, Func<Task>>();
  101. await Loop(keyValues).ConfigureAwait(false);
  102. }
  103. public static async Task UpdateKeyBindingsFile()
  104. {
  105. try
  106. {
  107. var json = JsonSerializer.Serialize(
  108. CustomShortcuts.ToDictionary(kvp => kvp.Key.ToString(),
  109. kvp => GetFunctionNameByFunction(kvp.Value)), typeof(Dictionary<string, string>),
  110. SourceGenerationContext.Default).Replace("\\u002B", "+"); // Fix plus sign encoded to Unicode
  111. await KeybindingFunctions.SaveKeyBindingsFile(json).ConfigureAwait(false);
  112. }
  113. catch (Exception exception)
  114. {
  115. #if DEBUG
  116. Trace.WriteLine($"{nameof(UpdateKeyBindingsFile)} exception:\n{exception.Message}");
  117. #endif
  118. }
  119. }
  120. private static async Task Loop(Dictionary<string, string> keyValues)
  121. {
  122. foreach (var kvp in keyValues)
  123. {
  124. try
  125. {
  126. var gesture = KeyGesture.Parse(kvp.Key);
  127. if (gesture is null)
  128. {
  129. continue;
  130. }
  131. var function = await FunctionsHelper.GetFunctionByName(kvp.Value).ConfigureAwait(false);
  132. // Add to the dictionary
  133. CustomShortcuts[gesture] = function;
  134. }
  135. catch (Exception exception)
  136. {
  137. #if DEBUG
  138. Trace.WriteLine($"{nameof(Loop)} exception:\n{exception.Message}");
  139. #endif
  140. }
  141. }
  142. }
  143. internal static async Task SetDefaultKeybindings()
  144. {
  145. if (CustomShortcuts is not null)
  146. {
  147. CustomShortcuts.Clear();
  148. }
  149. else
  150. {
  151. CustomShortcuts = new Dictionary<KeyGesture, Func<Task>>();
  152. }
  153. var keyValues = JsonSerializer.Deserialize(
  154. DefaultKeybindings, typeof(Dictionary<string, string>), SourceGenerationContext.Default)
  155. as Dictionary<string, string>;
  156. await Loop(keyValues).ConfigureAwait(false);
  157. }
  158. private static string? GetFunctionNameByFunction(Func<Task> function)
  159. {
  160. // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
  161. if (function == null)
  162. return "";
  163. return CustomShortcuts.FirstOrDefault(x => x.Value == function).Value.Method.Name ?? "";
  164. }
  165. }