releases/VideoDownloader.App/Services/ShortcutManager.cs

78 lines
3.5 KiB
C#

namespace VideoDownloader.App.Services;
/// <summary>단일 단축키 정의(명령 키 + 표시 조합).</summary>
public sealed record ShortcutBinding(string Command, string Gesture);
/// <summary>
/// UC-K02/K03 단축키 관리 — 기본 조합표 위에 사용자 재배치(AppConfig.Shortcuts)를 얹고,
/// 저장 전에 같은 조합을 쓰는 명령 충돌을 검증한다.
/// </summary>
public sealed class ShortcutManager
{
private readonly Dictionary<string, string> _user;
/// <summary>기본 단축키표(명령 키 → 기본 조합).</summary>
public static readonly IReadOnlyDictionary<string, string> Defaults = new Dictionary<string, string>
{
["newtab"] = "Ctrl+T",
["closetab"] = "Ctrl+W",
["reopentab"] = "Ctrl+Shift+T",
["find"] = "Ctrl+F",
["address"] = "Ctrl+L",
["downloads"] = "Ctrl+J",
["palette"] = "Ctrl+K",
["devtools"] = "F12",
["fullscreen"] = "F11",
["refresh"] = "F5",
["print"] = "Ctrl+P",
["pauseall"] = "Ctrl+Shift+P",
};
public ShortcutManager(IDictionary<string, string>? userShortcuts = null)
{
_user = new Dictionary<string, string>(userShortcuts ?? new Dictionary<string, string>(), StringComparer.OrdinalIgnoreCase);
}
/// <summary>명령의 현재 조합(사용자 지정 우선, 없으면 기본값).</summary>
public string GestureFor(string command)
=> _user.TryGetValue(command, out var g) ? g : Defaults.GetValueOrDefault(command, "");
/// <summary>전체 목록(기본 전체 + 사용자 오버라이드 반영).</summary>
public List<ShortcutBinding> All()
=> Defaults.Keys
.Select(c => new ShortcutBinding(c, GestureFor(c)))
.Concat(_user.Where(kv => !Defaults.ContainsKey(kv.Key)).Select(kv => new ShortcutBinding(kv.Key, kv.Value)))
.ToList();
/// <summary>UC-K03 충돌 검증 — 같은 조합에 묶인 명령 그룹(2개 이상일 때만 반환).</summary>
public List<List<ShortcutBinding>> FindConflicts()
=> All()
.GroupBy(b => Normalize(b.Gesture))
.Where(g => g.Count() > 1 && g.Key.Length > 0)
.Select(g => g.ToList())
.ToList();
/// <summary>조합 문자열 파싱("Ctrl+Shift+P" → ModifierKeys+Key). 파싱 실패 시 null.</summary>
public static (System.Windows.Input.ModifierKeys Mods, System.Windows.Input.Key Key)? Parse(string gesture)
{
if (string.IsNullOrWhiteSpace(gesture)) return null;
var mods = System.Windows.Input.ModifierKeys.None;
var key = System.Windows.Input.Key.None;
foreach (var part in gesture.Split('+'))
{
switch (part.Trim().ToLowerInvariant())
{
case "ctrl": mods |= System.Windows.Input.ModifierKeys.Control; continue;
case "shift": mods |= System.Windows.Input.ModifierKeys.Shift; continue;
case "alt": mods |= System.Windows.Input.ModifierKeys.Alt; continue;
case "win": mods |= System.Windows.Input.ModifierKeys.Windows; continue;
}
if (!Enum.TryParse(part.Trim(), ignoreCase: true, out key)) return null;
}
return key == System.Windows.Input.Key.None ? null : (mods, key);
}
/// <summary>조합 비교 정규화(대소문자·순서 무관).</summary>
public static string Normalize(string gesture)
=> string.Join("+", (gesture ?? "").Split('+').Select(p => p.Trim().ToLowerInvariant()).OrderBy(p => p));
}