From e5aa76e51e7860ad3e51ff88381d0bff3d2a7cef Mon Sep 17 00:00:00 2001 From: cadlaxa Date: Sun, 3 May 2026 14:06:47 +0800 Subject: [PATCH 01/22] Add Keyboard Shortcut Remapper --- OpenUtau.Core/Util/Preferences.cs | 119 +++- OpenUtau/Controls/PianoRoll.axaml | 216 ++++-- OpenUtau/Controls/PianoRoll.axaml.cs | 739 +++++--------------- OpenUtau/Strings/Strings.axaml | 128 +++- OpenUtau/ViewModels/KeyTranslator.cs | 65 ++ OpenUtau/ViewModels/PianoRollViewModel.cs | 17 + OpenUtau/ViewModels/PreferencesViewModel.cs | 146 ++++ OpenUtau/Views/PreferencesDialog.axaml | 106 ++- OpenUtau/Views/PreferencesDialog.axaml.cs | 32 + 9 files changed, 882 insertions(+), 686 deletions(-) create mode 100644 OpenUtau/ViewModels/KeyTranslator.cs diff --git a/OpenUtau.Core/Util/Preferences.cs b/OpenUtau.Core/Util/Preferences.cs index ef576bf9f..88c06a5d5 100644 --- a/OpenUtau.Core/Util/Preferences.cs +++ b/OpenUtau.Core/Util/Preferences.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using OpenUtau.Core.Render; using Serilog; +using System.Collections.Generic; namespace OpenUtau.Core.Util { @@ -16,7 +17,11 @@ public static class Preferences { static Preferences() { Load(); } - + public class ShortcutBinding { + public string ActionId { get; set; } + public string KeyName { get; set; } + public string ModifiersName { get; set; } + } public static void Save() { try { File.WriteAllText(PathManager.Inst.PrefsFilePath, @@ -214,6 +219,118 @@ public class SerializablePreferences { public bool LockUnselectedNotesPitch = true; public bool LockUnselectedNotesVibrato = true; public bool LockUnselectedNotesExpressions = true; + [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)] + public List Shortcuts { get; set; } = new List { + // Playback & Selection + new ShortcutBinding { ActionId = "PlayOrPause", KeyName = "Space", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "PlaySelection", KeyName = "Space", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ClearSelection", KeyName = "Escape", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "SelectAll", KeyName = "A", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "DeselectAll", KeyName = "D", ModifiersName = "Control" }, + + // UI & Windows + new ShortcutBinding { ActionId = "HideDetachedWindow", KeyName = "F4", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "FullScreen", KeyName = "F11", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "OpenPluginMenu", KeyName = "N", ModifiersName = "None" }, + + // Lyrics + new ShortcutBinding { ActionId = "EditLyrics", KeyName = "Enter", ModifiersName = "None" }, + + // Tools + new ShortcutBinding { ActionId = "ToolSelect1", KeyName = "D1", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ToolSelect2Main", KeyName = "D2", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ToolSelect2Alt", KeyName = "D2", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "ToolSelect3", KeyName = "D3", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ToolSelect4Main", KeyName = "D4", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ToolSelect4Overwrite", KeyName = "D4", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "ToolSelect4Line", KeyName = "D4", ModifiersName = "Shift" }, + new ShortcutBinding { ActionId = "ToolSelect4LineOverwrite", KeyName = "D4", ModifiersName = "Control, Shift" }, + new ShortcutBinding { ActionId = "ToolSelect5", KeyName = "D5", ModifiersName = "None" }, + + // Expressions + new ShortcutBinding { ActionId = "ExpSelect1", KeyName = "D1", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ExpSelect2", KeyName = "D2", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ExpSelect3", KeyName = "D3", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ExpSelect4", KeyName = "D4", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ExpSelect5", KeyName = "D5", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ExpSelect6", KeyName = "D6", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ExpSelect7", KeyName = "D7", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ExpSelect8", KeyName = "D8", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ExpSelect9", KeyName = "D9", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ExpSelect10", KeyName = "D0", ModifiersName = "Alt" }, + + // Toggles + new ShortcutBinding { ActionId = "ToggleFinalPitch", KeyName = "R", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ToggleTips", KeyName = "T", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ToggleVibrato", KeyName = "U", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "TogglePitch", KeyName = "I", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "TogglePhoneme", KeyName = "O", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ToggleExpressions", KeyName = "L", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ToggleSnap", KeyName = "P", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "OpenSnapMenu", KeyName = "P", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ToggleNoteParams", KeyName = "OemPipe", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "TogglePlayTone", KeyName = "Y", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ToggleWaveform", KeyName = "W", ModifiersName = "None" }, + + // Transposition + new ShortcutBinding { ActionId = "TransposeUp", KeyName = "Up", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "TransposeOctaveUp", KeyName = "Up", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "TransposeDown", KeyName = "Down", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "TransposeOctaveDown", KeyName = "Down", ModifiersName = "Control" }, + + // Note Movement & Sizing + new ShortcutBinding { ActionId = "MoveCursorLeft", KeyName = "Left", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ResizeNotesLeft", KeyName = "Left", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "MoveNotesLeft", KeyName = "Left", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "ExtendSelectionLeft", KeyName = "Left", ModifiersName = "Shift" }, + new ShortcutBinding { ActionId = "MoveCursorRight", KeyName = "Right", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ResizeNotesRight", KeyName = "Right", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "MoveNotesRight", KeyName = "Right", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "ExtendSelectionRight", KeyName = "Right", ModifiersName = "Shift" }, + + // Edit Operations + new ShortcutBinding { ActionId = "Undo", KeyName = "Z", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "Redo", KeyName = "Y", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "Copy", KeyName = "C", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "Cut", KeyName = "X", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "Paste", KeyName = "V", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "PastePlain", KeyName = "V", ModifiersName = "Control, Shift" }, + new ShortcutBinding { ActionId = "PasteParameters", KeyName = "V", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "InsertNote", KeyName = "Insert", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "DeleteNotes", KeyName = "Delete", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "MergeNotes", KeyName = "U", ModifiersName = "Control" }, + + // Playhead & Timeline Navigation + new ShortcutBinding { ActionId = "PlayheadHome", KeyName = "Home", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "SelectToStart", KeyName = "Home", ModifiersName = "Shift" }, + new ShortcutBinding { ActionId = "PlayheadEnd", KeyName = "End", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "SelectToEnd", KeyName = "End", ModifiersName = "Shift" }, + new ShortcutBinding { ActionId = "PlayheadLeft", KeyName = "OemOpenBrackets", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "PlayheadToSelectionStart", KeyName = "OemOpenBrackets", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "PlayheadToViewStart", KeyName = "OemOpenBrackets", ModifiersName = "Shift" }, + new ShortcutBinding { ActionId = "PlayheadRight", KeyName = "OemCloseBrackets", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "PlayheadToSelectionEnd", KeyName = "OemCloseBrackets", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "PlayheadToViewEnd", KeyName = "OemCloseBrackets", ModifiersName = "Shift" }, + + // Scrolling & Zooming + new ShortcutBinding { ActionId = "ScrollLeft", KeyName = "A", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ScrollRight", KeyName = "D", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ScrollUp", KeyName = "W", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ScrollDown", KeyName = "S", ModifiersName = "Alt" }, + new ShortcutBinding { ActionId = "ZoomIn", KeyName = "E", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "ZoomOut", KeyName = "Q", ModifiersName = "None" }, + + // Track & Project Operations + new ShortcutBinding { ActionId = "SaveProject", KeyName = "S", ModifiersName = "Control" }, + new ShortcutBinding { ActionId = "SoloTrack", KeyName = "S", ModifiersName = "Shift" }, + new ShortcutBinding { ActionId = "MuteTrack", KeyName = "M", ModifiersName = "Shift" }, + new ShortcutBinding { ActionId = "FocusSelection", KeyName = "F", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "SearchNote", KeyName = "F", ModifiersName = "Control" }, + + // Parts Navigation + new ShortcutBinding { ActionId = "MoveToNextPartUp", KeyName = "PageUp", ModifiersName = "None" }, + new ShortcutBinding { ActionId = "MoveToNextPartDown", KeyName = "PageDown", ModifiersName = "None" } + }; public bool VoicebankPublishUseIgnore = true; public string VoicebankPublishIgnores = @"#Adobe Audition *.pkf diff --git a/OpenUtau/Controls/PianoRoll.axaml b/OpenUtau/Controls/PianoRoll.axaml index c599bb633..4f89820cc 100644 --- a/OpenUtau/Controls/PianoRoll.axaml +++ b/OpenUtau/Controls/PianoRoll.axaml @@ -214,17 +214,17 @@ - - + + - - - - - + + + + + - - + + @@ -267,7 +267,7 @@ - + @@ -336,14 +336,19 @@ - + - + + + + + + + @@ -352,8 +357,13 @@ + Command="{Binding NotesViewModel.SelectToolCommand}" CommandParameter="1"> + + + + + + @@ -367,8 +377,13 @@ + Command="{Binding NotesViewModel.SelectToolCommand}" CommandParameter="2"> + + + + + + @@ -382,8 +397,13 @@ + Command="{Binding NotesViewModel.SelectToolCommand}" CommandParameter="2+"> + + + + + + @@ -397,8 +417,13 @@ + Command="{Binding NotesViewModel.SelectToolCommand}" CommandParameter="3"> + + + + + + @@ -412,8 +437,13 @@ + Command="{Binding NotesViewModel.SelectToolCommand}" CommandParameter="4"> + + + + + + @@ -427,8 +457,13 @@ + Command="{Binding NotesViewModel.SelectToolCommand}" CommandParameter="4+"> + + + + + + @@ -442,8 +477,13 @@ + Command="{Binding NotesViewModel.SelectToolCommand}" CommandParameter="4++"> + + + + + + @@ -457,8 +497,13 @@ + Command="{Binding NotesViewModel.SelectToolCommand}" CommandParameter="4+++"> + + + + + + @@ -472,8 +517,13 @@ + Command="{Binding NotesViewModel.SelectToolCommand}" CommandParameter="5"> + + + + + + @@ -486,8 +536,13 @@ - + + + + + + + @@ -499,8 +554,13 @@ - + + + + + + + @@ -509,22 +569,37 @@ - + + + + + + + - + + + + + + + - + + + + + + + @@ -536,14 +611,24 @@ - + + + + + + + - + + + + + + + @@ -551,14 +636,24 @@ - + + + + + + + - - + - + - + - + - + - + - + - + @@ -285,7 +315,7 @@ diff --git a/OpenUtau/Views/PreferencesDialog.axaml.cs b/OpenUtau/Views/PreferencesDialog.axaml.cs index 3813164f1..5c78beaf4 100644 --- a/OpenUtau/Views/PreferencesDialog.axaml.cs +++ b/OpenUtau/Views/PreferencesDialog.axaml.cs @@ -12,7 +12,7 @@ namespace OpenUtau.App.Views { public partial class PreferencesDialog : Window { - private PreferencesViewModel? viewModel => this.DataContext as PreferencesViewModel; + private PreferencesViewModel? viewModel => this.DataContext as PreferencesViewModel; public PreferencesDialog() { InitializeComponent(); @@ -23,6 +23,7 @@ protected override void OnKeyDown(KeyEventArgs e) { if (DataContext is PreferencesViewModel vm && vm.ActiveShortcut != null) { // If they hit escape without modifiers, cancel listening if (e.Key == Key.Escape && e.KeyModifiers == KeyModifiers.None) { + vm.ActiveShortcut.IsAdding = false; vm.ActiveShortcut.IsListening = false; vm.ActiveShortcut.RefreshDisplay(); vm.ActiveShortcut = null; @@ -34,19 +35,24 @@ protected override void OnKeyDown(KeyEventArgs e) { e.Handled = true; return; } - base.OnKeyDown(e); } public void OnShortcutRightClick(object sender, PointerReleasedEventArgs e) { - if (e.InitialPressMouseButton == MouseButton.Right && - sender is Button btn && + if (e.InitialPressMouseButton == MouseButton.Right && + sender is Button btn && btn.DataContext is ShortcutItemViewModel item) { - - if (DataContext is PreferencesViewModel vm) { - //vm.ResetShortcut(item); - e.Handled = true; - } + item.Reset(); + e.Handled = true; + } + } + + void OnShortcutMiscClick(object? sender, RoutedEventArgs e) { + if (sender is Button btn && + btn.DataContext is ShortcutItemViewModel item) { + item.CreateMenuItem(); + btn.ContextMenu?.Open(); + e.Handled = true; } } @@ -164,7 +170,7 @@ void OnCustomThemeCreate(object sender, RoutedEventArgs e) { dialog.SetPrompt(ThemeManager.GetString("prefs.appearance.customtheme.create.prompt")); dialog.onFinish = s => { if (string.IsNullOrEmpty(s)) { - MessageBox.ShowModal(this, + MessageBox.ShowModal(this, ThemeManager.GetString("prefs.appearance.customtheme.create.empty"), ThemeManager.GetString("prefs.appearance.customtheme.create.title")); return; diff --git a/OpenUtau/Views/ShortcutKeyDialog.axaml b/OpenUtau/Views/ShortcutKeyDialog.axaml deleted file mode 100644 index 97340d8dc..000000000 --- a/OpenUtau/Views/ShortcutKeyDialog.axaml +++ /dev/null @@ -1,9 +0,0 @@ - - Welcome to Avalonia! - diff --git a/OpenUtau/Views/ShortcutKeyDialog.axaml.cs b/OpenUtau/Views/ShortcutKeyDialog.axaml.cs deleted file mode 100644 index 7d3c50a9b..000000000 --- a/OpenUtau/Views/ShortcutKeyDialog.axaml.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace OpenUtau.Views; - -public partial class ShortcutKeyDialog : Window { - public ShortcutKeyDialog() { - InitializeComponent(); - } -} - From b3fefb811d39bd2d63563ca671fc7ed6b292e5e1 Mon Sep 17 00:00:00 2001 From: Maiko Date: Sun, 31 May 2026 17:50:27 +0900 Subject: [PATCH 18/22] support external batch edit --- OpenUtau/Controls/PianoRoll.axaml.cs | 19 +---- OpenUtau/Strings/Strings.axaml | 3 +- OpenUtau/ViewModels/KeyTranslator.cs | 43 ++++++++--- OpenUtau/ViewModels/PreferencesViewModel.cs | 86 ++++++++------------- OpenUtau/Views/PreferencesDialog.axaml | 3 +- OpenUtau/Views/PreferencesDialog.axaml.cs | 1 - 6 files changed, 75 insertions(+), 80 deletions(-) diff --git a/OpenUtau/Controls/PianoRoll.axaml.cs b/OpenUtau/Controls/PianoRoll.axaml.cs index 9cb27475e..aeec53f0e 100644 --- a/OpenUtau/Controls/PianoRoll.axaml.cs +++ b/OpenUtau/Controls/PianoRoll.axaml.cs @@ -28,7 +28,6 @@ interface IValueTip { } public partial class PianoRoll : UserControl, IValueTip, ICmdSubscriber { - public MainWindow? MainWindow { get; set; } public PianoRollViewModel ViewModel; @@ -43,7 +42,7 @@ public partial class PianoRoll : UserControl, IValueTip, ICmdSubscriber { private ReactiveCommand? noteDefaultsCommand; private ReactiveCommand? noteBatchEditCommand; - private Window RootWindow => (Window) TopLevel.GetTopLevel(this)!; + private Window RootWindow => (Window)TopLevel.GetTopLevel(this)!; public PianoRoll(PianoRollViewModel model) { InitializeComponent(); @@ -116,7 +115,6 @@ await MessageBox.ShowProcessing(RootWindow, $"{name} - ? / ?", Command = noteBatchEditCommand, CommandParameter = edit, })); - ViewModel.LyricBatchEdits.AddRange(new List() { new RomajiToHiragana(), new HiraganaToRomaji(), @@ -135,7 +133,6 @@ await MessageBox.ShowProcessing(RootWindow, $"{name} - ? / ?", Command = noteBatchEditCommand, CommandParameter = edit, })); - ViewModel.ResetBatchEdits.AddRange(new List() { new ResetAll(), new ResetPitchBends(), @@ -150,7 +147,6 @@ await MessageBox.ShowProcessing(RootWindow, $"{name} - ? / ?", Command = noteBatchEditCommand, CommandParameter = edit, })); - try { ViewModel.ExternalBatchEdits.AddRange( DocManager.Inst.ExternalBatchEditTypes @@ -1356,9 +1352,8 @@ void OnKeyDown(object? sender, KeyEventArgs args) { // To add a new keyboard shortcut to the Piano Roll: // // 1. Add a new `case "YourActionName":` inside the switch statement below. - // 2. Open `Preferences.cs` and add a default key binding to the `Shortcuts` list - // (e.g., new ShortcutBinding { ActionId = "YourActionName", KeyName = "...", ModifiersName = "..." }) - // 3. Open `Strings.axaml` (and other language files) and add the display name: + // 2. Open `KeyTranslator.cs` and add a default key binding to the `Shortcuts` list + // 3. Open `Strings.axaml` and add the display name: // Shortcut Name bool OnKeyExtendedHandler(KeyEventArgs args) { @@ -1372,13 +1367,7 @@ bool OnKeyExtendedHandler(KeyEventArgs args) { int snapUnit = project.resolution * 4 / notesVm.SnapDiv; int deltaTicks = notesVm.IsSnapOn ? snapUnit : 15; - bool isNone = args.KeyModifiers == KeyModifiers.None; - bool isAlt = args.KeyModifiers == KeyModifiers.Alt; - bool isCtrl = args.KeyModifiers == cmdKey; - bool isShift = args.KeyModifiers == KeyModifiers.Shift; - bool isBoth = args.KeyModifiers == (cmdKey | KeyModifiers.Shift); - - if (PluginMenu.IsSubMenuOpen && isNone) { + if (PluginMenu.IsSubMenuOpen && args.KeyModifiers == KeyModifiers.None) { if (ViewModel.LegacyPluginShortcuts.ContainsKey(args.Key)) { var plugin = ViewModel.LegacyPluginShortcuts[args.Key]; if (plugin != null && plugin.Command != null) { diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 4e2c2acdf..902ae867e 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -486,8 +486,7 @@ Warning: this option removes custom presets. Reset pitch bends Reset vibratos Part - Legacy Plugin (Experimental) - (legacy) + Legacy Plugin Singer and Oto settings Open folder Reload diff --git a/OpenUtau/ViewModels/KeyTranslator.cs b/OpenUtau/ViewModels/KeyTranslator.cs index 38d0fee18..3f8bde5f5 100644 --- a/OpenUtau/ViewModels/KeyTranslator.cs +++ b/OpenUtau/ViewModels/KeyTranslator.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Avalonia.Input; +using OpenUtau.Core; +using OpenUtau.Core.Editing; using OpenUtau.Core.Util; namespace OpenUtau.App.ViewModels { @@ -11,8 +14,7 @@ public class ShortcutKey { public ShortcutKey(string actionId, string shortcut) { ActionId = actionId; - KeyGesture key = KeyGesture.Parse(shortcut); - Gesture = KeyTranslator.GestureConverter(key); + Gesture = KeyTranslator.StringToGesture(shortcut); } public override string ToString() => $"{ActionId}: {KeyTranslator.GetFriendlyName(Gesture.Key, Gesture.KeyModifiers)}"; @@ -209,7 +211,15 @@ public static void LoadShortcuts() { .Select(key => new ShortcutKey(s.ActionId, key))) .Where(key => key.Gesture.Key != Key.None) .ToList(); - //merged.AddRange(Preferences.Default.PluginShortcuts); Todo + + // external batch edits + var edits = DocManager.Inst.ExternalBatchEditTypes + .Select(type => Activator.CreateInstance(type) as BatchEdit) + .OfType(); + Shortcuts.AddRange(Preferences.Default.PluginShortcuts.SelectMany(s => s.Shortcuts + .Where(key => edits.Any(edit => edit.Name == s.ActionId)) + .Select(key => new ShortcutKey(s.ActionId, key))) + .Where(key => key.Gesture.Key != Key.None)); } public static List GetMergedShortcuts() { @@ -228,20 +238,24 @@ public static void LoadShortcuts() { public static void SaveShortcuts(IEnumerable items) { var diff = new List(); var plugin = new List(); + var edits = DocManager.Inst.ExternalBatchEditTypes + .Select(type => Activator.CreateInstance(type) as BatchEdit) + .OfType(); + foreach (var item in items) { var defKey = DefShortcuts.FirstOrDefault(s => s.ActionId == item.ActionId); if (defKey != null) { item.Gestures.RemoveAll(g => g.Key == Key.None); - var gestures = item.Gestures.Select(g => GestureConverter(g).ToString()).ToArray(); + var gestures = item.Gestures.Select(GestureToString).ToArray(); if (defKey.Shortcuts.Length != gestures.Length) { diff.Add(new Preferences.ShortcutBinding(item.ActionId, gestures)); } else if (!defKey.Shortcuts.OrderBy(x => x).SequenceEqual(gestures.OrderBy(x => x))) { diff.Add(new Preferences.ShortcutBinding(item.ActionId, gestures)); } - } else { + } else if (edits.Any(edit => edit.Name == item.ActionId)) { item.Gestures.RemoveAll(g => g.Key == Key.None); if (item.Gestures.Count == 0) continue; - var gestures = item.Gestures.Select(g => GestureConverter(g).ToString()).ToArray(); + var gestures = item.Gestures.Select(GestureToString).ToArray(); plugin.Add(new Preferences.ShortcutBinding(item.ActionId, gestures)); } } @@ -258,14 +272,25 @@ public static void ResetShortcuts() { LoadShortcuts(); } - public static KeyGesture GestureConverter(KeyGesture gesture) { + public static KeyGesture StringToGesture(string shortcut) { + try { + var gesture = KeyGesture.Parse(shortcut); + return GestureConverter(gesture); + } catch { + return new KeyGesture(Key.None); + } + } + public static string GestureToString(KeyGesture gesture) { + return GestureConverter(gesture).ToString(); + } + private static KeyGesture GestureConverter(KeyGesture gesture) { if (IsMac) { var m = gesture.KeyModifiers; bool hasCtrl = m.HasFlag(KeyModifiers.Control); bool hasMeta = m.HasFlag(KeyModifiers.Meta); if (hasCtrl) { m &= ~KeyModifiers.Control; - m |= KeyModifiers.Meta; + m |= KeyModifiers.Meta; } if (hasMeta) { m &= ~KeyModifiers.Meta; diff --git a/OpenUtau/ViewModels/PreferencesViewModel.cs b/OpenUtau/ViewModels/PreferencesViewModel.cs index 93585ecc6..67d972b16 100644 --- a/OpenUtau/ViewModels/PreferencesViewModel.cs +++ b/OpenUtau/ViewModels/PreferencesViewModel.cs @@ -11,6 +11,7 @@ using OpenUtau.Audio; using OpenUtau.Classic; using OpenUtau.Core; +using OpenUtau.Core.Editing; using OpenUtau.Core.Render; using OpenUtau.Core.Util; using ReactiveUI; @@ -40,9 +41,9 @@ public class ShortcutItemViewModel : ViewModelBase { public string DisplayString { get { - if (IsListening) return ThemeManager.GetString("prefs.shortcuts.listening") ?? "Press keys..."; + if (IsListening) return ThemeManager.GetString("prefs.shortcuts.listening"); var text = string.Join(", ", Gestures.Select(g => KeyTranslator.GetFriendlyName(g.Key, g.KeyModifiers))); - return Gestures.Count == 0 ? "None" : text; + return Gestures.Count == 0 ? "(None)" : text; } } @@ -50,24 +51,32 @@ public string DisplayString { public Action? ListenForShortcut; public ShortcutItemViewModel() {} - public ShortcutItemViewModel(Preferences.ShortcutBinding binding, Action save, Action? listenForShortcut) { - string lookupKey = "shortcut." + binding.ActionId; + public ShortcutItemViewModel(Preferences.ShortcutBinding binding, Action save, Action? listenForShortcut, string prefix = "") { + ActionId = binding.ActionId; + ActionName = $"{prefix}{GetDisplayName(binding.ActionId)}"; + Gestures = binding.Shortcuts.Select(KeyTranslator.StringToGesture).ToList(); + Save = save; + ListenForShortcut = listenForShortcut; + } + public ShortcutItemViewModel(string actionId, Action save, Action? listenForShortcut, string prefix = "") { + ActionId = actionId; + ActionName = $"{prefix}{GetDisplayName(ActionId)}"; + Save = save; + ListenForShortcut = listenForShortcut; + } + private string GetDisplayName(string actionId) { + string lookupKey = "shortcut." + actionId; string displayName = ThemeManager.GetString(lookupKey); if (string.IsNullOrEmpty(displayName) || displayName == lookupKey) { - displayName = ThemeManager.GetString(binding.ActionId); + displayName = ThemeManager.GetString(actionId); } if (string.IsNullOrEmpty(displayName)) { - displayName = binding.ActionId; + displayName = actionId; } if (displayName.StartsWith("shortcut.")) { displayName = displayName.Substring(9); } - - ActionId = binding.ActionId; - ActionName = displayName; - Gestures = binding.Shortcuts.Select(s => KeyTranslator.GestureConverter(KeyGesture.Parse(s))).ToList(); - Save = save; - ListenForShortcut = listenForShortcut; + return displayName; } public void CreateMenuItem() { @@ -96,7 +105,7 @@ public void Reset() { if (binding == null) { Gestures.Clear(); } else { - Gestures = binding.Shortcuts.Select(s => KeyTranslator.GestureConverter(KeyGesture.Parse(s))).ToList(); + Gestures = binding.Shortcuts.Select(KeyTranslator.StringToGesture).ToList(); } IsAdding = false; IsListening = false; @@ -564,46 +573,19 @@ public void ToggleOnnxGpuDisplay(bool show) { private void LoadShortcuts() { var shortcuts = KeyTranslator.GetMergedShortcuts().Select(binding => new ShortcutItemViewModel(binding, () => SaveShortcuts(), item => ListenForShortcut(item))); allShortcuts.AddRange(shortcuts); - /* Todo + // external batch edits + var prefix = $"{ThemeManager.GetString("pianoroll.menu.external")}: "; foreach (var type in DocManager.Inst.ExternalBatchEditTypes) { - try { - if (Activator.CreateInstance(type) is BatchEdit edit) { - - var savedSc = Preferences.Default.Shortcuts?.FirstOrDefault(s => s.ActionId == edit.Name); - Key savedKey = Key.None; - KeyModifiers savedMods = KeyModifiers.None; - - if (savedSc != null) { - Enum.TryParse(savedSc.KeyName, out savedKey); - Enum.TryParse(savedSc.ModifiersName, out savedMods); - } - - string pluginName = edit.Name; - if (allShortcuts.Any(s => s.ActionId == pluginName)) { - continue; - } - - string lookupKey = "shortcut." + pluginName; - string displayName = ThemeManager.GetString(lookupKey); - - if (string.IsNullOrEmpty(displayName) || displayName == lookupKey) { - displayName = pluginName; - } - if (displayName.StartsWith("shortcut.")) { - displayName = displayName.Substring(9); - } - - allShortcuts.Add(new ShortcutItemViewModel { - ActionId = pluginName, - ActionName = displayName, - Key = savedKey, - Modifiers = savedMods - }); + if (Activator.CreateInstance(type) is BatchEdit edit) { + var customized = Preferences.Default.PluginShortcuts.FirstOrDefault(pref => pref.ActionId == edit.Name); + if (customized != null) { + allShortcuts.Add(new ShortcutItemViewModel(customized, () => SaveShortcuts(), item => ListenForShortcut(item), prefix)); + } else if (!allShortcuts.Any(s => s.ActionId == edit.Name)) { + allShortcuts.Add(new ShortcutItemViewModel(edit.Name, () => SaveShortcuts(), item => ListenForShortcut(item), prefix)); } - } catch { } - }*/ + } } public void ListenForShortcut(ShortcutItemViewModel item) { @@ -637,9 +619,9 @@ public void AssignShortcut(Key key, KeyModifiers modifiers) { ActiveShortcut.IsListening = false; ActiveShortcut.RefreshDisplay(); ActiveShortcut = null; - string formatString = ThemeManager.GetString("prefs.shortcuts.duplicate") ?? "The shortcut '{0}' is already assigned to '{1}'."; - string message = string.Format(formatString, duplicate.DisplayString, duplicate.ActionName); - DocManager.Inst.ExecuteCmd(new ErrorMessageNotification(message)); + + var e = new MessageCustomizableException("The shortcut is already assigned.", "", new ArgumentException(), false, [duplicate.DisplayString, duplicate.ActionName]); + DocManager.Inst.ExecuteCmd(new ErrorMessageNotification(e)); return; } if (!ActiveShortcut.IsAdding) { diff --git a/OpenUtau/Views/PreferencesDialog.axaml b/OpenUtau/Views/PreferencesDialog.axaml index eb7bd0e5c..394babcae 100644 --- a/OpenUtau/Views/PreferencesDialog.axaml +++ b/OpenUtau/Views/PreferencesDialog.axaml @@ -391,7 +391,8 @@ CommandParameter="{Binding}" PointerReleased="OnShortcutRightClick" ToolTip.Tip="{DynamicResource prefs.shortcut.tooltip}" - Focusable="False"/> + Focusable="False" + MinWidth="55"/>