From 0fbc57239d2d1714598156df76b53a9f15b36489 Mon Sep 17 00:00:00 2001 From: Maiko Date: Sun, 9 Nov 2025 19:52:12 +0900 Subject: [PATCH 01/13] Disable the menu when undo is not possible / Display the command name --- OpenUtau.Core/Commands/UCommand.cs | 6 ++++-- OpenUtau.Core/DocManager.cs | 23 +++++++++++++++++++-- OpenUtau.Core/Editing/LyricBatchEdits.cs | 6 +++--- OpenUtau.Core/Editing/NoteBatchEdits.cs | 24 +++++++++++----------- OpenUtau.Core/Editing/ResetBatchEdits.cs | 14 ++++++------- OpenUtau/ViewModels/MainWindowViewModel.cs | 24 +++++++++++++++++++--- OpenUtau/ViewModels/PianoRollViewModel.cs | 22 ++++++++++++++++++++ OpenUtau/Views/MainWindow.axaml | 4 ++-- OpenUtau/Views/NoteEditStates.cs | 14 ++++++++++++- OpenUtau/Views/PianoRollWindow.axaml | 4 ++-- 10 files changed, 107 insertions(+), 34 deletions(-) diff --git a/OpenUtau.Core/Commands/UCommand.cs b/OpenUtau.Core/Commands/UCommand.cs index 74e5f45a5..5058c0a9e 100644 --- a/OpenUtau.Core/Commands/UCommand.cs +++ b/OpenUtau.Core/Commands/UCommand.cs @@ -14,9 +14,11 @@ public abstract class UCommand { } public class UCommandGroup { + public string? Name; public bool DeferValidate; public List Commands; - public UCommandGroup(bool deferValidate) { + public UCommandGroup(string? name, bool deferValidate) { + Name = name; DeferValidate = deferValidate; Commands = new List(); } @@ -27,7 +29,7 @@ public void Merge() { Commands.Add(merged); } } - public override string ToString() { return Commands.Count == 0 ? "No op" : Commands.First().ToString(); } + public override string ToString() { return Name ?? (Commands.Count == 0 ? "No op" : Commands.First().ToString()); } } public interface ICmdSubscriber { diff --git a/OpenUtau.Core/DocManager.cs b/OpenUtau.Core/DocManager.cs index 533ce7a6d..812ca9ccc 100644 --- a/OpenUtau.Core/DocManager.cs +++ b/OpenUtau.Core/DocManager.cs @@ -245,12 +245,12 @@ public void ExecuteCmd(UCommand cmd) { } } - public void StartUndoGroup(bool deferValidate = false) { + public void StartUndoGroup(string? name = null, bool deferValidate = false) { if (undoGroup != null) { Log.Error("undoGroup already started"); EndUndoGroup(); } - undoGroup = new UCommandGroup(deferValidate); + undoGroup = new UCommandGroup(name, deferValidate); Log.Information("undoGroup started"); } @@ -325,6 +325,25 @@ public void Redo() { ExecuteCmd(new PreRenderNotification()); } + public bool GetUndoState(out string? name) { + name = null; + if (undoQueue.Count > 0) { + name = undoQueue.Last().Name; + return true; + } else { + return false; + } + } + public bool GetRedoState(out string? name) { + name = null; + if (redoQueue.Count > 0) { + name = redoQueue.Last().Name; + return true; + } else { + return false; + } + } + # endregion # region Command Subscribers diff --git a/OpenUtau.Core/Editing/LyricBatchEdits.cs b/OpenUtau.Core/Editing/LyricBatchEdits.cs index 8e4db3d45..71c44c367 100644 --- a/OpenUtau.Core/Editing/LyricBatchEdits.cs +++ b/OpenUtau.Core/Editing/LyricBatchEdits.cs @@ -14,7 +14,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do return; } var lyrics = notes.Select(note => Transform(note.lyric)).ToArray(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Lyrics batch edit", true); docManager.ExecuteCmd(new ChangeNoteLyricCommand(part, notes, lyrics)); docManager.EndUndoGroup(); } @@ -130,7 +130,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do suffixes.Sort((a, b) => b.Length - a.Length); // Set lyric and color - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Lyrics batch edit", true); foreach (var note in notes) { foreach (var suffix in suffixes) { if (note.lyric.Contains(suffix)) { @@ -196,7 +196,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do } var startPos = selectedNotes.First().position; Queue lyricsQueue = new Queue(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Lyrics batch edit", true); foreach(var note in part.notes.Where(n => n.position >= startPos)){ lyricsQueue.Enqueue(note.lyric); if(selectedNotes.Contains(note)){ diff --git a/OpenUtau.Core/Editing/NoteBatchEdits.cs b/OpenUtau.Core/Editing/NoteBatchEdits.cs index 1db3885b5..45381268a 100644 --- a/OpenUtau.Core/Editing/NoteBatchEdits.cs +++ b/OpenUtau.Core/Editing/NoteBatchEdits.cs @@ -32,7 +32,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (toAdd.Count == 0) { return; } - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in toAdd) { note.lyric = lyric; docManager.ExecuteCmd(new AddNoteCommand(part, note)); @@ -63,7 +63,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (toRemove.Count == 0) { return; } - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in toRemove) { note.lyric = lyric; docManager.ExecuteCmd(new RemoveNoteCommand(part, note)); @@ -108,7 +108,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (toAdd.Count == 0) { return; } - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in toAdd) { note.lyric = lyric; docManager.ExecuteCmd(new AddNoteCommand(part, note)); @@ -130,7 +130,7 @@ public Transpose(int deltaNoteNum, string name) { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in notes) { docManager.ExecuteCmd(new MoveNoteCommand(part, note, 0, deltaNoteNum)); } @@ -151,7 +151,7 @@ public QuantizeNotes(int quantize) { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in notes) { int pos = note.position; int end = note.End; @@ -180,7 +180,7 @@ public AutoLegato() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); notes.Sort((a, b) => a.position.CompareTo(b.position)); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); for (int i = 0; i < notes.Count - 1; i++) { docManager.ExecuteCmd(new ResizeNoteCommand(part, notes[i], notes[i + 1].position - notes[i].position - notes[i].duration)); } @@ -239,7 +239,7 @@ public HanziToPinyin() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var pinyinResult = BaseChinesePhonemizer.Romanize(selectedNotes.Select(note => note.lyric)); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Lyrics batch edit", true); foreach (var t in Enumerable.Zip(selectedNotes, pinyinResult, (note, pinyin) => Tuple.Create(note, pinyin))) { docManager.ExecuteCmd(new ChangeNoteLyricCommand(part, t.Item1, t.Item2)); @@ -263,7 +263,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (notes.Count == 0) { return; } - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); var track = project.tracks[part.trackNo]; foreach (var note in notes) { foreach (UPhoneme phoneme in part.phonemes) { @@ -308,7 +308,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (notes.Count == 0) { return; } - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); Random random = new Random(); foreach (var note in notes) { if (random.Next(2) == 0) { // + @@ -403,7 +403,7 @@ public void RunAsync( } DocManager.Inst.PostOnUIThread(() => { - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); commands.ForEach(docManager.ExecuteCmd); docManager.EndUndoGroup(); }); @@ -612,7 +612,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do pitch); } } - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); //Apply pitch points to notes foreach (var note in notes) { if (pitchPointsPerNote.TryGetValue(note.position, out var tickRangeAndPitch)) { @@ -732,7 +732,7 @@ public void RunAsync( .ToList(); DocManager.Inst.PostOnUIThread(() => { - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); commands.ForEach(docManager.ExecuteCmd); docManager.EndUndoGroup(); }); diff --git a/OpenUtau.Core/Editing/ResetBatchEdits.cs b/OpenUtau.Core/Editing/ResetBatchEdits.cs index c6c4e9331..86274cf31 100644 --- a/OpenUtau.Core/Editing/ResetBatchEdits.cs +++ b/OpenUtau.Core/Editing/ResetBatchEdits.cs @@ -92,7 +92,7 @@ public ResetPitchBends() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in notes) { docManager.ExecuteCmd(new ResetPitchPointsCommand(part, note)); } @@ -111,7 +111,7 @@ public ResetAllExpressions() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); //reset numerical and options expressions foreach (var note in notes) { if (note.phonemeExpressions.Count > 0) { @@ -156,7 +156,7 @@ public ClearVibratos() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in notes) { if (note.vibrato.length > 0) { docManager.ExecuteCmd(new VibratoLengthCommand(part, note, 0)); @@ -177,7 +177,7 @@ public ResetVibratos() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); var vibrato = new UVibrato(); foreach (var note in notes) { docManager.ExecuteCmd(new SetVibratoCommand(part, note, vibrato)); @@ -202,7 +202,7 @@ public ClearTimings() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in notes) { bool shouldClear = false; foreach (var o in note.phonemeOverrides) { @@ -230,7 +230,7 @@ public ResetAliases() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in notes) { foreach (var o in note.phonemeOverrides) { if (o.phoneme != null) { @@ -253,7 +253,7 @@ public ResetAll() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("Notes batch edit", true); foreach (var note in notes) { // pitch points docManager.ExecuteCmd(new ResetPitchPointsCommand(part, note)); diff --git a/OpenUtau/ViewModels/MainWindowViewModel.cs b/OpenUtau/ViewModels/MainWindowViewModel.cs index fe7f510ba..d4ef8f1e2 100644 --- a/OpenUtau/ViewModels/MainWindowViewModel.cs +++ b/OpenUtau/ViewModels/MainWindowViewModel.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using System.Reactive; -using System.Threading; using System.Threading.Tasks; using Avalonia.Threading; using DynamicData.Binding; @@ -74,6 +73,10 @@ public class MainWindowViewModel : ViewModelBase, ICmdSubscriber { public ReactiveCommand? DelTempoChangeCmd { get; set; } public ReactiveCommand? AddTimeSigChangeCmd { get; set; } public ReactiveCommand? DelTimeSigChangeCmd { get; set; } + [Reactive] public bool CanUndo { get; set; } = false; + [Reactive] public bool CanRedo { get; set; } = false; + [Reactive] public string UndoText { get; set; } = ThemeManager.GetString("menu.edit.undo"); + [Reactive] public string RedoText { get; set; } = ThemeManager.GetString("menu.edit.redo"); private ObservableCollectionExtended openRecentMenuItems = new ObservableCollectionExtended(); @@ -124,6 +127,20 @@ public void Undo() { public void Redo() { DocManager.Inst.Redo(); } + private void SetUndoState() { + CanUndo = DocManager.Inst.GetUndoState(out string? undoName); + if (undoName != null) { + UndoText = $"{ThemeManager.GetString("menu.edit.undo")}: {undoName}"; + } else { + UndoText = ThemeManager.GetString("menu.edit.undo"); + } + CanRedo = DocManager.Inst.GetRedoState(out string? redoName); + if (redoName != null) { + RedoText = $"{ThemeManager.GetString("menu.edit.redo")}: {redoName}"; + } else { + RedoText = ThemeManager.GetString("menu.edit.redo"); + } + } public async void InitProject(MainWindow window) { var recPath = Preferences.Default.RecoveryPath; @@ -419,10 +436,11 @@ public void OnNext(UCommand cmd, bool isUndo) { ProgressText = progressBarNotification.Info; }); } else if (cmd is LoadProjectNotification loadProject) { - Core.Util.Preferences.AddRecentFileIfEnabled(loadProject.project.FilePath); + Preferences.AddRecentFileIfEnabled(loadProject.project.FilePath); } else if (cmd is SaveProjectNotification saveProject) { - Core.Util.Preferences.AddRecentFileIfEnabled(saveProject.Path); + Preferences.AddRecentFileIfEnabled(saveProject.Path); } + SetUndoState(); this.RaisePropertyChanged(nameof(Title)); } diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs index 2bcec2b0b..2651ea173 100644 --- a/OpenUtau/ViewModels/PianoRollViewModel.cs +++ b/OpenUtau/ViewModels/PianoRollViewModel.cs @@ -79,6 +79,11 @@ public class PianoRollViewModel : ViewModelBase, ICmdSubscriber { = new Dictionary(); [Reactive] public double Progress { get; set; } + [Reactive] public bool CanUndo { get; set; } = false; + [Reactive] public bool CanRedo { get; set; } = false; + [Reactive] public string UndoText { get; set; } = ThemeManager.GetString("menu.edit.undo"); + [Reactive] public string RedoText { get; set; } = ThemeManager.GetString("menu.edit.redo"); + public ReactiveCommand NoteDeleteCommand { get; set; } public ReactiveCommand NoteCopyCommand { get; set; } public ReactiveCommand ClearPhraseCacheCommand { get; set; } @@ -175,6 +180,22 @@ await Task.Run(() => { } }); LoadLegacyPlugins(); + DocManager.Inst.AddSubscriber(this); + } + + private void SetUndoState() { + CanUndo = DocManager.Inst.GetUndoState(out string? undoName); + if (undoName != null) { + UndoText = $"{ThemeManager.GetString("menu.edit.undo")}: {undoName}"; + } else { + UndoText = ThemeManager.GetString("menu.edit.undo"); + } + CanRedo = DocManager.Inst.GetRedoState(out string? redoName); + if (redoName != null) { + RedoText = $"{ThemeManager.GetString("menu.edit.redo")}: {redoName}"; + } else { + RedoText = ThemeManager.GetString("menu.edit.redo"); + } } private void LoadLegacyPlugins() { @@ -236,6 +257,7 @@ public void OnNext(UCommand cmd, bool isUndo) { Progress = progressBarNotification.Progress; }); } + SetUndoState(); } #endregion diff --git a/OpenUtau/Views/MainWindow.axaml b/OpenUtau/Views/MainWindow.axaml index e874b0ffd..64430f81b 100644 --- a/OpenUtau/Views/MainWindow.axaml +++ b/OpenUtau/Views/MainWindow.axaml @@ -52,8 +52,8 @@ - - + + diff --git a/OpenUtau/Views/NoteEditStates.cs b/OpenUtau/Views/NoteEditStates.cs index 70e5f23f6..9f81b10e7 100644 --- a/OpenUtau/Views/NoteEditStates.cs +++ b/OpenUtau/Views/NoteEditStates.cs @@ -48,6 +48,8 @@ class NoteEditState { public Point startPoint; public IValueTip valueTip; protected virtual bool ShowValueTip => true; + protected virtual string? commandName => null; + public NoteEditState(Control control, PianoRollViewModel vm, IValueTip valueTip) { this.control = control; this.vm = vm; @@ -56,7 +58,7 @@ public NoteEditState(Control control, PianoRollViewModel vm, IValueTip valueTip) public virtual void Begin(IPointer pointer, Point point) { pointer.Capture(control); startPoint = point; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup(commandName); if (ShowValueTip) { valueTip.ShowValueTip(); } @@ -89,6 +91,7 @@ class NoteSelectionEditState : NoteEditState { protected override bool ShowValueTip => false; private int startTick; private int startTone; + public NoteSelectionEditState( Control control, PianoRollViewModel vm, @@ -137,6 +140,8 @@ class NoteMoveEditState : NoteEditState { public readonly UNote note; private double xOffset; protected override bool ShowValueTip => false; + protected override string? commandName => "Move notes"; + public NoteMoveEditState( Control control, PianoRollViewModel vm, @@ -212,6 +217,7 @@ class NoteDrawEditState : NoteEditState { private UNote? note; private bool playTone; private int activeTone; + protected override string? commandName => "Add notes"; public NoteDrawEditState( Control control, @@ -291,6 +297,8 @@ class NoteResizeEditState : NoteEditState { public readonly UNote? neighborNote; public readonly bool resizeNeighbor; public readonly bool fromStart; + protected override string? commandName => "Move notes"; + public NoteResizeEditState( Control control, PianoRollViewModel vm, @@ -389,6 +397,8 @@ class NoteSplitEditState : NoteEditState { private float oldVibFadeInTicks => oldVibFadeIn * oldVibLengthTicks / 100; private float oldVibFadeOutTicks => oldVibFadeOut * oldVibLengthTicks / 100; private float vibPeriod => note.vibrato.period; + protected override string? commandName => "Split notes"; + public NoteSplitEditState( Control control, PianoRollViewModel vm, @@ -499,6 +509,8 @@ class NoteEraseEditState : NoteEditState { public override MouseButton MouseButton => mouseButton; private MouseButton mouseButton; protected override bool ShowValueTip => false; + protected override string? commandName => "Delete notes"; + public NoteEraseEditState( Control control, PianoRollViewModel vm, diff --git a/OpenUtau/Views/PianoRollWindow.axaml b/OpenUtau/Views/PianoRollWindow.axaml index c937558dc..1fa974a59 100644 --- a/OpenUtau/Views/PianoRollWindow.axaml +++ b/OpenUtau/Views/PianoRollWindow.axaml @@ -219,8 +219,8 @@ - - + + From c9b461954d875661fbead62ad0611cdd2fd1516a Mon Sep 17 00:00:00 2001 From: Maiko Date: Wed, 19 Nov 2025 01:27:44 +0900 Subject: [PATCH 02/13] update command name --- OpenUtau.Core/Classic/PluginRunner.cs | 2 +- OpenUtau.Core/Commands/UCommand.cs | 8 +-- OpenUtau.Core/DocManager.cs | 16 +++--- OpenUtau.Core/Editing/LyricBatchEdits.cs | 6 +-- OpenUtau.Core/Editing/NoteBatchEdits.cs | 26 ++++----- OpenUtau.Core/Editing/ResetBatchEdits.cs | 14 ++--- .../Controls/NotePropertiesControl.axaml.cs | 6 +-- .../Controls/NotePropertyExpression.axaml.cs | 4 +- OpenUtau/Strings/Strings.axaml | 48 ++++++++++++++++- OpenUtau/ViewModels/ExpressionsViewModel.cs | 2 +- OpenUtau/ViewModels/LyricBoxViewModel.cs | 3 +- OpenUtau/ViewModels/LyricsReplaceViewModel.cs | 2 +- OpenUtau/ViewModels/LyricsViewModel.cs | 2 +- OpenUtau/ViewModels/MainWindowViewModel.cs | 16 +++--- .../ViewModels/NotePropertiesViewModel.cs | 8 +-- OpenUtau/ViewModels/NotesViewModel.cs | 22 ++++---- OpenUtau/ViewModels/PianoRollViewModel.cs | 26 ++++----- OpenUtau/ViewModels/PlaybackViewModel.cs | 6 +-- OpenUtau/ViewModels/TrackColorViewModel.cs | 2 +- OpenUtau/ViewModels/TrackHeaderViewModel.cs | 18 +++---- OpenUtau/ViewModels/TrackSettingsViewModel.cs | 2 +- OpenUtau/ViewModels/TracksViewModel.cs | 10 ++-- OpenUtau/Views/MainWindow.axaml.cs | 22 ++++---- OpenUtau/Views/NoteEditStates.cs | 53 ++++++++++++++++--- OpenUtau/Views/PartEditStates.cs | 17 ++++-- 25 files changed, 217 insertions(+), 124 deletions(-) diff --git a/OpenUtau.Core/Classic/PluginRunner.cs b/OpenUtau.Core/Classic/PluginRunner.cs index ab99288ac..122798b4c 100644 --- a/OpenUtau.Core/Classic/PluginRunner.cs +++ b/OpenUtau.Core/Classic/PluginRunner.cs @@ -19,7 +19,7 @@ public static PluginRunner from(PathManager pathManager, DocManager docManager) private static Action ReplaceNoteMethod(DocManager docManager) { return new Action((args) => { - docManager.StartUndoGroup(); + docManager.StartUndoGroup("command.batch.plugin"); docManager.ExecuteCmd(new RemoveNoteCommand(args.Part, args.ToRemove)); docManager.ExecuteCmd(new AddNoteCommand(args.Part, args.ToAdd)); docManager.EndUndoGroup(); diff --git a/OpenUtau.Core/Commands/UCommand.cs b/OpenUtau.Core/Commands/UCommand.cs index 5058c0a9e..a73eaf489 100644 --- a/OpenUtau.Core/Commands/UCommand.cs +++ b/OpenUtau.Core/Commands/UCommand.cs @@ -14,11 +14,11 @@ public abstract class UCommand { } public class UCommandGroup { - public string? Name; + public string? NameKey; public bool DeferValidate; public List Commands; - public UCommandGroup(string? name, bool deferValidate) { - Name = name; + public UCommandGroup(string? nameKey, bool deferValidate) { + NameKey = nameKey; DeferValidate = deferValidate; Commands = new List(); } @@ -29,7 +29,7 @@ public void Merge() { Commands.Add(merged); } } - public override string ToString() { return Name ?? (Commands.Count == 0 ? "No op" : Commands.First().ToString()); } + public override string ToString() { return Commands.Count == 0 ? "No op" : Commands.First().ToString(); } } public interface ICmdSubscriber { diff --git a/OpenUtau.Core/DocManager.cs b/OpenUtau.Core/DocManager.cs index 812ca9ccc..976712dd4 100644 --- a/OpenUtau.Core/DocManager.cs +++ b/OpenUtau.Core/DocManager.cs @@ -245,12 +245,12 @@ public void ExecuteCmd(UCommand cmd) { } } - public void StartUndoGroup(string? name = null, bool deferValidate = false) { + public void StartUndoGroup(string? nameKey = null, bool deferValidate = false) { if (undoGroup != null) { Log.Error("undoGroup already started"); EndUndoGroup(); } - undoGroup = new UCommandGroup(name, deferValidate); + undoGroup = new UCommandGroup(nameKey, deferValidate); Log.Information("undoGroup started"); } @@ -325,19 +325,19 @@ public void Redo() { ExecuteCmd(new PreRenderNotification()); } - public bool GetUndoState(out string? name) { - name = null; + public bool GetUndoState(out string? key) { + key = null; if (undoQueue.Count > 0) { - name = undoQueue.Last().Name; + key = undoQueue.Last().NameKey; return true; } else { return false; } } - public bool GetRedoState(out string? name) { - name = null; + public bool GetRedoState(out string? key) { + key = null; if (redoQueue.Count > 0) { - name = redoQueue.Last().Name; + key = redoQueue.Last().NameKey; return true; } else { return false; diff --git a/OpenUtau.Core/Editing/LyricBatchEdits.cs b/OpenUtau.Core/Editing/LyricBatchEdits.cs index 71c44c367..cea1f09cf 100644 --- a/OpenUtau.Core/Editing/LyricBatchEdits.cs +++ b/OpenUtau.Core/Editing/LyricBatchEdits.cs @@ -14,7 +14,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do return; } var lyrics = notes.Select(note => Transform(note.lyric)).ToArray(); - docManager.StartUndoGroup("Lyrics batch edit", true); + docManager.StartUndoGroup("command.batch.lyric", true); docManager.ExecuteCmd(new ChangeNoteLyricCommand(part, notes, lyrics)); docManager.EndUndoGroup(); } @@ -130,7 +130,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do suffixes.Sort((a, b) => b.Length - a.Length); // Set lyric and color - docManager.StartUndoGroup("Lyrics batch edit", true); + docManager.StartUndoGroup("command.batch.lyric", true); foreach (var note in notes) { foreach (var suffix in suffixes) { if (note.lyric.Contains(suffix)) { @@ -196,7 +196,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do } var startPos = selectedNotes.First().position; Queue lyricsQueue = new Queue(); - docManager.StartUndoGroup("Lyrics batch edit", true); + docManager.StartUndoGroup("command.batch.lyric", true); foreach(var note in part.notes.Where(n => n.position >= startPos)){ lyricsQueue.Enqueue(note.lyric); if(selectedNotes.Contains(note)){ diff --git a/OpenUtau.Core/Editing/NoteBatchEdits.cs b/OpenUtau.Core/Editing/NoteBatchEdits.cs index 45381268a..f8a87d4ed 100644 --- a/OpenUtau.Core/Editing/NoteBatchEdits.cs +++ b/OpenUtau.Core/Editing/NoteBatchEdits.cs @@ -32,7 +32,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (toAdd.Count == 0) { return; } - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); foreach (var note in toAdd) { note.lyric = lyric; docManager.ExecuteCmd(new AddNoteCommand(part, note)); @@ -63,7 +63,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (toRemove.Count == 0) { return; } - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); foreach (var note in toRemove) { note.lyric = lyric; docManager.ExecuteCmd(new RemoveNoteCommand(part, note)); @@ -108,7 +108,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (toAdd.Count == 0) { return; } - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); foreach (var note in toAdd) { note.lyric = lyric; docManager.ExecuteCmd(new AddNoteCommand(part, note)); @@ -130,7 +130,7 @@ public Transpose(int deltaNoteNum, string name) { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); foreach (var note in notes) { docManager.ExecuteCmd(new MoveNoteCommand(part, note, 0, deltaNoteNum)); } @@ -151,7 +151,7 @@ public QuantizeNotes(int quantize) { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); foreach (var note in notes) { int pos = note.position; int end = note.End; @@ -180,7 +180,7 @@ public AutoLegato() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); notes.Sort((a, b) => a.position.CompareTo(b.position)); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); for (int i = 0; i < notes.Count - 1; i++) { docManager.ExecuteCmd(new ResizeNoteCommand(part, notes[i], notes[i + 1].position - notes[i].position - notes[i].duration)); } @@ -207,7 +207,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (notes.Count == 0) { return; } - docManager.StartUndoGroup(); + docManager.StartUndoGroup("command.batch.note"); var currentNote = notes[0]; foreach (var note in notes.Skip(1)) { if (note.position == currentNote.position) { @@ -239,7 +239,7 @@ public HanziToPinyin() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var pinyinResult = BaseChinesePhonemizer.Romanize(selectedNotes.Select(note => note.lyric)); - docManager.StartUndoGroup("Lyrics batch edit", true); + docManager.StartUndoGroup("command.batch.lyric", true); foreach (var t in Enumerable.Zip(selectedNotes, pinyinResult, (note, pinyin) => Tuple.Create(note, pinyin))) { docManager.ExecuteCmd(new ChangeNoteLyricCommand(part, t.Item1, t.Item2)); @@ -263,7 +263,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (notes.Count == 0) { return; } - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); var track = project.tracks[part.trackNo]; foreach (var note in notes) { foreach (UPhoneme phoneme in part.phonemes) { @@ -308,7 +308,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (notes.Count == 0) { return; } - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); Random random = new Random(); foreach (var note in notes) { if (random.Next(2) == 0) { // + @@ -403,7 +403,7 @@ public void RunAsync( } DocManager.Inst.PostOnUIThread(() => { - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); commands.ForEach(docManager.ExecuteCmd); docManager.EndUndoGroup(); }); @@ -612,7 +612,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do pitch); } } - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); //Apply pitch points to notes foreach (var note in notes) { if (pitchPointsPerNote.TryGetValue(note.position, out var tickRangeAndPitch)) { @@ -732,7 +732,7 @@ public void RunAsync( .ToList(); DocManager.Inst.PostOnUIThread(() => { - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.note", true); commands.ForEach(docManager.ExecuteCmd); docManager.EndUndoGroup(); }); diff --git a/OpenUtau.Core/Editing/ResetBatchEdits.cs b/OpenUtau.Core/Editing/ResetBatchEdits.cs index 86274cf31..6ce7dfaf5 100644 --- a/OpenUtau.Core/Editing/ResetBatchEdits.cs +++ b/OpenUtau.Core/Editing/ResetBatchEdits.cs @@ -92,7 +92,7 @@ public ResetPitchBends() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.reset", true); foreach (var note in notes) { docManager.ExecuteCmd(new ResetPitchPointsCommand(part, note)); } @@ -111,7 +111,7 @@ public ResetAllExpressions() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.reset", true); //reset numerical and options expressions foreach (var note in notes) { if (note.phonemeExpressions.Count > 0) { @@ -156,7 +156,7 @@ public ClearVibratos() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.reset", true); foreach (var note in notes) { if (note.vibrato.length > 0) { docManager.ExecuteCmd(new VibratoLengthCommand(part, note, 0)); @@ -177,7 +177,7 @@ public ResetVibratos() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.reset", true); var vibrato = new UVibrato(); foreach (var note in notes) { docManager.ExecuteCmd(new SetVibratoCommand(part, note, vibrato)); @@ -202,7 +202,7 @@ public ClearTimings() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.reset", true); foreach (var note in notes) { bool shouldClear = false; foreach (var o in note.phonemeOverrides) { @@ -230,7 +230,7 @@ public ResetAliases() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.reset", true); foreach (var note in notes) { foreach (var o in note.phonemeOverrides) { if (o.phoneme != null) { @@ -253,7 +253,7 @@ public ResetAll() { public void Run(UProject project, UVoicePart part, List selectedNotes, DocManager docManager) { var notes = selectedNotes.Count > 0 ? selectedNotes : part.notes.ToList(); - docManager.StartUndoGroup("Notes batch edit", true); + docManager.StartUndoGroup("command.batch.reset", true); foreach (var note in notes) { // pitch points docManager.ExecuteCmd(new ResetPitchPointsCommand(part, note)); diff --git a/OpenUtau/Controls/NotePropertiesControl.axaml.cs b/OpenUtau/Controls/NotePropertiesControl.axaml.cs index 3344b4af8..fb33a1f7f 100644 --- a/OpenUtau/Controls/NotePropertiesControl.axaml.cs +++ b/OpenUtau/Controls/NotePropertiesControl.axaml.cs @@ -67,7 +67,7 @@ void OnTextBoxGotFocus(object? sender, GotFocusEventArgs args) { void OnTextBoxLostFocus(object? sender, RoutedEventArgs args) { Log.Debug("Note property textbox lost focus"); if (sender is TextBox textBox && textBoxValue != textBox.Text && textBox.Tag is string tag && !string.IsNullOrEmpty(tag)) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.property.edit"); NotePropertiesViewModel.PanelControlPressed = true; ViewModel.SetNoteParams(tag, textBox.Text); NotePropertiesViewModel.PanelControlPressed = false; @@ -80,11 +80,11 @@ void SliderPointerPressed(object? sender, PointerPressedEventArgs args) { if (sender is Control control) { var point = args.GetCurrentPoint(control); if (point.Properties.IsLeftButtonPressed) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.property.edit"); NotePropertiesViewModel.PanelControlPressed = true; } else if (point.Properties.IsRightButtonPressed) { if (control.Tag is string tag && !string.IsNullOrEmpty(tag)) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.property.reset"); NotePropertiesViewModel.PanelControlPressed = true; ViewModel.SetNoteParams(tag, null); NotePropertiesViewModel.PanelControlPressed = false; diff --git a/OpenUtau/Controls/NotePropertyExpression.axaml.cs b/OpenUtau/Controls/NotePropertyExpression.axaml.cs index c8d3c379d..474de2adc 100644 --- a/OpenUtau/Controls/NotePropertyExpression.axaml.cs +++ b/OpenUtau/Controls/NotePropertyExpression.axaml.cs @@ -38,7 +38,7 @@ void SliderPointerPressed(object? sender, PointerPressedEventArgs args) { if (sender is Control control) { var point = args.GetCurrentPoint(control); if (point.Properties.IsLeftButtonPressed) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.property.edit"); NotePropertiesViewModel.PanelControlPressed = true; } else if (point.Properties.IsRightButtonPressed) { SetNumericalExpressions(null); @@ -74,7 +74,7 @@ void OnComboBoxPointerPressed(object? sender, PointerPressedEventArgs args) { private void SetNumericalExpressions(string? expression) { if (DataContext is NotePropertyExpViewModel viewModel) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.property.edit"); NotePropertiesViewModel.PanelControlPressed = true; viewModel.SetNumericalExpressions(expression); NotePropertiesViewModel.PanelControlPressed = false; diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 7ec3f200d..950550f3a 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -2,6 +2,52 @@ xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + Lyrics batch edit + Notes batch edit + Apply plugin edit + Batch reset + Edit expression + Reset expression + Import audio + Import track(s) + Add note + Delete note(s) + Edit note(s) + Edit lyric(s) + Move note(s) + Paste note(s) + Split note + Paste parameter(s) + Add part + Delete part(s) + Edit part(s) + Move part(s) + Paste part(s) + Transcribe audio to create a note part + Edit phoneme + Reset phoneme + Add pitch point + Delete pitch point + Draw pitch + Edit pitch + Edit pitch point + Reset pitch + Edit project expressions + Change project key + Edit tempo + Edit time signature + Edit note(s) property + Reset note(s) property + Add track + Delete track + Duplicate track + Edit track expressions + Edit track order + Remap voice color + Change track setting + Change track singer + Edit vibrato + Clear cache of phrase Copy note Delete note @@ -312,7 +358,7 @@ Warning: this option removes custom presets. Running batch edit Lyrics Replace "-" with "+" - Replace "-" with "+~" + Replace "-" with "+~" Edit Lyrics Hiragana to Romaji Insert slur lyric diff --git a/OpenUtau/ViewModels/ExpressionsViewModel.cs b/OpenUtau/ViewModels/ExpressionsViewModel.cs index 294b95b29..44f232284 100644 --- a/OpenUtau/ViewModels/ExpressionsViewModel.cs +++ b/OpenUtau/ViewModels/ExpressionsViewModel.cs @@ -153,7 +153,7 @@ public void Apply() { if (flags.Count() > 0) { throw new MessageCustomizableException("", $": {string.Join(", ", flags)}", new ArgumentException($"Flags must be unique: {string.Join(", ", flags)}"), false); } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.exp"); DocManager.Inst.ExecuteCmd(new ConfigureExpressionsCommand(DocManager.Inst.Project, Expressions.Select(builder => builder.Build()).ToArray())); DocManager.Inst.EndUndoGroup(); } diff --git a/OpenUtau/ViewModels/LyricBoxViewModel.cs b/OpenUtau/ViewModels/LyricBoxViewModel.cs index c3665ddaf..097966379 100644 --- a/OpenUtau/ViewModels/LyricBoxViewModel.cs +++ b/OpenUtau/ViewModels/LyricBoxViewModel.cs @@ -90,13 +90,14 @@ public void Commit() { return; } } - DocManager.Inst.StartUndoGroup(); if (IsAliasBox) { + DocManager.Inst.StartUndoGroup("command.phoneme.edit"); var phoneme = (NoteOrPhoneme as LyricBoxPhoneme)!.Unwrap(); var note = phoneme.Parent; int index = phoneme.index; DocManager.Inst.ExecuteCmd(new ChangePhonemeAliasCommand(Part, note.Extends ?? note, index, Text)); } else { + DocManager.Inst.StartUndoGroup("command.note.lyric"); DocManager.Inst.ExecuteCmd(new ChangeNoteLyricCommand(Part, (NoteOrPhoneme as LyricBoxNote)!.Unwrap(), Text)); } DocManager.Inst.EndUndoGroup(); diff --git a/OpenUtau/ViewModels/LyricsReplaceViewModel.cs b/OpenUtau/ViewModels/LyricsReplaceViewModel.cs index 5d474d7fb..45a8c730f 100644 --- a/OpenUtau/ViewModels/LyricsReplaceViewModel.cs +++ b/OpenUtau/ViewModels/LyricsReplaceViewModel.cs @@ -68,7 +68,7 @@ public bool Finish() { return false; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.batch.lyric"); for (int i = 0; i < Lyrics.Length && i < notes.Length; ++i) { if (notes[i].lyric != Lyrics[i]) { DocManager.Inst.ExecuteCmd(new ChangeNoteLyricCommand(part, notes[i], Lyrics[i])); diff --git a/OpenUtau/ViewModels/LyricsViewModel.cs b/OpenUtau/ViewModels/LyricsViewModel.cs index 7e9a473d2..6388fd81a 100644 --- a/OpenUtau/ViewModels/LyricsViewModel.cs +++ b/OpenUtau/ViewModels/LyricsViewModel.cs @@ -43,7 +43,7 @@ public void Start(UVoicePart part, UNote[] notes, UNote[] selection) { UpdateTotalCount(); CurrentCount = TotalCount; Text = SplitLyrics.Join(startLyrics!); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.lyric"); } private void Preview(bool update) { diff --git a/OpenUtau/ViewModels/MainWindowViewModel.cs b/OpenUtau/ViewModels/MainWindowViewModel.cs index d4ef8f1e2..5baf8e85d 100644 --- a/OpenUtau/ViewModels/MainWindowViewModel.cs +++ b/OpenUtau/ViewModels/MainWindowViewModel.cs @@ -128,15 +128,15 @@ public void Redo() { DocManager.Inst.Redo(); } private void SetUndoState() { - CanUndo = DocManager.Inst.GetUndoState(out string? undoName); - if (undoName != null) { - UndoText = $"{ThemeManager.GetString("menu.edit.undo")}: {undoName}"; + CanUndo = DocManager.Inst.GetUndoState(out string? undoNameKey); + if (!string.IsNullOrWhiteSpace(undoNameKey)) { + UndoText = $"{ThemeManager.GetString("menu.edit.undo")}: {ThemeManager.GetString(undoNameKey)}"; } else { UndoText = ThemeManager.GetString("menu.edit.undo"); } - CanRedo = DocManager.Inst.GetRedoState(out string? redoName); - if (redoName != null) { - RedoText = $"{ThemeManager.GetString("menu.edit.redo")}: {redoName}"; + CanRedo = DocManager.Inst.GetRedoState(out string? redoNameKey); + if (!string.IsNullOrWhiteSpace(redoNameKey)) { + RedoText = $"{ThemeManager.GetString("menu.edit.redo")}: {ThemeManager.GetString(redoNameKey)}"; } else { RedoText = ThemeManager.GetString("menu.edit.redo"); } @@ -274,7 +274,7 @@ public void ImportAudio(string file) { } int trackNo = project.tracks.Count; part.trackNo = trackNo; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.import.audio"); DocManager.Inst.ExecuteCmd(new AddTrackCommand(project, new UTrack(project) { TrackNo = trackNo })); DocManager.Inst.ExecuteCmd(new AddPartCommand(project, part)); DocManager.Inst.EndUndoGroup(); @@ -286,7 +286,7 @@ public void ImportMidi(string file) { } var project = DocManager.Inst.Project; var parts = Core.Format.MidiWriter.Load(file, project); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.import.track"); foreach (var part in parts) { var track = new UTrack(project); track.TrackNo = project.tracks.Count; diff --git a/OpenUtau/ViewModels/NotePropertiesViewModel.cs b/OpenUtau/ViewModels/NotePropertiesViewModel.cs index 606f35570..9bd8d9a70 100644 --- a/OpenUtau/ViewModels/NotePropertiesViewModel.cs +++ b/OpenUtau/ViewModels/NotePropertiesViewModel.cs @@ -65,7 +65,7 @@ public NotePropertiesViewModel() { PortamentoLength = portamentoPreset.PortamentoLength; PortamentoStart = portamentoPreset.PortamentoStart; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.pitch.editpoint"); PanelControlPressed = true; SetNoteParams("PortamentoStart", portamentoPreset.PortamentoStart); PanelControlPressed = false; @@ -76,7 +76,7 @@ public NotePropertiesViewModel() { .WhereNotNull() .Subscribe(vibratoPreset => { if (vibratoPreset != null) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.vibrato.edit"); PanelControlPressed = true; SetNoteParams("VibratoLength", Math.Max(0, Math.Min(100, vibratoPreset.VibratoLength))); SetNoteParams("VibratoPeriod", Math.Max(5, Math.Min(500, vibratoPreset.VibratoPeriod))); @@ -481,7 +481,7 @@ public void SetNoteParams(string tag, object? obj) { } public void SetVibratoEnable() { if (Part != null && selectedNotes.Count > 0) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.vibrato.edit"); bool enable = VibratoEnable; UNote first = selectedNotes.First(); @@ -520,7 +520,7 @@ public void SetOptionalExpressionsChanges(string abbr, int? value) { if (track.TryGetExpression(DocManager.Inst.Project, abbr, out UExpression expression) && expression.value == value) { value = null; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.exp.edit"); DocManager.Inst.ExecuteCmd(new SetNotesSameExpressionCommand(DocManager.Inst.Project, track, Part, selectedNotes, abbr, value)); DocManager.Inst.EndUndoGroup(); } diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index 0a968ade8..e852960d4 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -125,7 +125,7 @@ public NotesViewModel() { Keys = new List(); SetKeyCommand = ReactiveCommand.Create(key => { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.key"); DocManager.Inst.ExecuteCmd(new KeyCommand(Project, key)); DocManager.Inst.EndUndoGroup(); UpdateKey(); @@ -741,7 +741,7 @@ public void InsertNote() { int tone = fromNote?.tone ?? DEFAULT_TONE; int tick = fromNote?.RightBound ?? (int)TickOffset; int dur = fromNote?.duration ?? snapUnit; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.add"); UNote note = DocManager.Inst.Project.CreateNote(tone, tick, dur); DocManager.Inst.ExecuteCmd(new AddNoteCommand(Part, note)); SelectNote(note); @@ -756,7 +756,7 @@ public void TransposeSelection(int deltaNoteNum) { if (selectedNotes.Any(note => note.tone + deltaNoteNum <= 0 || note.tone + deltaNoteNum >= ViewConstants.MaxTone)) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.move"); DocManager.Inst.ExecuteCmd(new MoveNoteCommand(Part, selectedNotes, 0, deltaNoteNum)); DocManager.Inst.EndUndoGroup(); } @@ -769,7 +769,7 @@ public void MoveSelectedNotes(int deltaTicks) { //var delta = Math.Clamp(deltaTicks, -1 * selectedNotes.First().position, Part.End - selectedNotes.Last().position); var delta = Math.Max(deltaTicks, -1 * selectedNotes.First().position); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.move"); DocManager.Inst.ExecuteCmd(new MoveNoteCommand(Part, selectedNotes, delta, 0)); DocManager.Inst.EndUndoGroup(); } @@ -793,7 +793,7 @@ public void ResizeSelectedNotes(int deltaTicks) { return; } } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.edit"); DocManager.Inst.ExecuteCmd(new ResizeNoteCommand(Part, selectedNotes, deltaTicks)); DocManager.Inst.EndUndoGroup(); } @@ -809,7 +809,7 @@ public void MergeSelectedNotes() { if(mergedLyrics == ""){ //If all notes are slur, the merged note is single slur note mergedLyrics = notes[0].lyric; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.edit"); DocManager.Inst.ExecuteCmd(new ChangeNoteLyricCommand(Part, notes[0], mergedLyrics)); DocManager.Inst.ExecuteCmd(new ResizeNoteCommand(Part, notes[0], notes.Last().End - notes[0].End)); notes.RemoveAt(0); @@ -821,7 +821,7 @@ internal void DeleteSelectedNotes() { if (Part == null || Selection.IsEmpty) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.delete"); DocManager.Inst.ExecuteCmd(new RemoveNoteCommand(Part, Selection.ToList())); DocManager.Inst.EndUndoGroup(); } @@ -837,7 +837,7 @@ public void CutNotes() { if (Part != null && !Selection.IsEmpty) { var selectedNotes = Selection.ToList(); DocManager.Inst.NotesClipboard = selectedNotes.Select(note => note.Clone()).ToList(); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.delete"); DocManager.Inst.ExecuteCmd(new RemoveNoteCommand(Part, selectedNotes)); DocManager.Inst.EndUndoGroup(); } @@ -853,7 +853,7 @@ public void PasteNotes() { int offset = left - minPosition - Part.position; var notes = DocManager.Inst.NotesClipboard.Select(note => note.Clone()).ToList(); notes.ForEach(note => note.position += offset); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.paste"); DocManager.Inst.ExecuteCmd(new AddNoteCommand(Part, notes)); int minDurTick = Part.GetMinDurTick(Project); if (Part.Duration < minDurTick) { @@ -884,7 +884,7 @@ public async void PasteSelectedParams(PianoRollWindow window) { await dialog.ShowDialog(window); if (dialog.Apply) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.parameter.paste"); int c = 0; var track = Project.tracks[Part.trackNo]; @@ -927,7 +927,7 @@ public void ToggleVibrato(UNote note) { return; } var vibrato = note.vibrato; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.vibrato.edit"); DocManager.Inst.ExecuteCmd(new VibratoLengthCommand(Part, note, vibrato.length == 0 ? NotePresets.Default.DefaultVibrato.VibratoLength : 0)); DocManager.Inst.EndUndoGroup(); } diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs index 2651ea173..d18806650 100644 --- a/OpenUtau/ViewModels/PianoRollViewModel.cs +++ b/OpenUtau/ViewModels/PianoRollViewModel.cs @@ -111,43 +111,43 @@ public PianoRollViewModel() { }); PitEaseInOutCommand = ReactiveCommand.Create(info => { if (NotesViewModel.Part == null) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.pitch.editpoint"); DocManager.Inst.ExecuteCmd(new ChangePitchPointShapeCommand(NotesViewModel.Part, info.Note.pitch.data[info.Index], PitchPointShape.io)); DocManager.Inst.EndUndoGroup(); }); PitLinearCommand = ReactiveCommand.Create(info => { if (NotesViewModel.Part == null) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.pitch.editpoint"); DocManager.Inst.ExecuteCmd(new ChangePitchPointShapeCommand(NotesViewModel.Part, info.Note.pitch.data[info.Index], PitchPointShape.l)); DocManager.Inst.EndUndoGroup(); }); PitEaseInCommand = ReactiveCommand.Create(info => { if (NotesViewModel.Part == null) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.pitch.editpoint"); DocManager.Inst.ExecuteCmd(new ChangePitchPointShapeCommand(NotesViewModel.Part, info.Note.pitch.data[info.Index], PitchPointShape.i)); DocManager.Inst.EndUndoGroup(); }); PitEaseOutCommand = ReactiveCommand.Create(info => { if (NotesViewModel.Part == null) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.pitch.editpoint"); DocManager.Inst.ExecuteCmd(new ChangePitchPointShapeCommand(NotesViewModel.Part, info.Note.pitch.data[info.Index], PitchPointShape.o)); DocManager.Inst.EndUndoGroup(); }); PitSnapCommand = ReactiveCommand.Create(info => { if (NotesViewModel.Part == null) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.pitch.editpoint"); DocManager.Inst.ExecuteCmd(new SnapPitchPointCommand(NotesViewModel.Part, info.Note)); DocManager.Inst.EndUndoGroup(); }); PitDelCommand = ReactiveCommand.Create(info => { if (NotesViewModel.Part == null) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.pitch.delete"); DocManager.Inst.ExecuteCmd(new DeletePitchPointCommand(NotesViewModel.Part, info.Note, info.Index)); DocManager.Inst.EndUndoGroup(); }); PitAddCommand = ReactiveCommand.Create(info => { if (NotesViewModel.Part == null) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.pitch.add"); DocManager.Inst.ExecuteCmd(new AddPitchPointCommand(NotesViewModel.Part, info.Note, new PitchPoint(info.X, info.Y), info.Index + 1)); DocManager.Inst.EndUndoGroup(); }); @@ -184,15 +184,15 @@ await Task.Run(() => { } private void SetUndoState() { - CanUndo = DocManager.Inst.GetUndoState(out string? undoName); - if (undoName != null) { - UndoText = $"{ThemeManager.GetString("menu.edit.undo")}: {undoName}"; + CanUndo = DocManager.Inst.GetUndoState(out string? undoNameKey); + if (!string.IsNullOrWhiteSpace(undoNameKey)) { + UndoText = $"{ThemeManager.GetString("menu.edit.undo")}: {ThemeManager.GetString(undoNameKey)}"; } else { UndoText = ThemeManager.GetString("menu.edit.undo"); } - CanRedo = DocManager.Inst.GetRedoState(out string? redoName); - if (redoName != null) { - RedoText = $"{ThemeManager.GetString("menu.edit.redo")}: {redoName}"; + CanRedo = DocManager.Inst.GetRedoState(out string? redoNameKey); + if (!string.IsNullOrWhiteSpace(redoNameKey)) { + RedoText = $"{ThemeManager.GetString("menu.edit.redo")}: {ThemeManager.GetString(redoNameKey)}"; } else { RedoText = ThemeManager.GetString("menu.edit.redo"); } diff --git a/OpenUtau/ViewModels/PlaybackViewModel.cs b/OpenUtau/ViewModels/PlaybackViewModel.cs index ae35820f1..65a2c0405 100644 --- a/OpenUtau/ViewModels/PlaybackViewModel.cs +++ b/OpenUtau/ViewModels/PlaybackViewModel.cs @@ -48,7 +48,7 @@ public void MovePlayPos(int tick) { public void SetTimeSignature(int beatPerBar, int beatUnit) { if (beatPerBar > 1 && (beatUnit == 2 || beatUnit == 4 || beatUnit == 8 || beatUnit == 16)) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.timesignature"); DocManager.Inst.ExecuteCmd(new TimeSignatureCommand(Project, beatPerBar, beatUnit)); DocManager.Inst.EndUndoGroup(); } @@ -58,7 +58,7 @@ public void SetBpm(double bpm) { if (bpm == DocManager.Inst.Project.tempos[0].bpm) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.tempo"); DocManager.Inst.ExecuteCmd(new BpmCommand(Project, bpm)); DocManager.Inst.EndUndoGroup(); } @@ -67,7 +67,7 @@ public void SetKey(int key) { if (key == DocManager.Inst.Project.key) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.key"); DocManager.Inst.ExecuteCmd(new KeyCommand(Project, key)); DocManager.Inst.EndUndoGroup(); } diff --git a/OpenUtau/ViewModels/TrackColorViewModel.cs b/OpenUtau/ViewModels/TrackColorViewModel.cs index 3e2eab80d..0416313ba 100644 --- a/OpenUtau/ViewModels/TrackColorViewModel.cs +++ b/OpenUtau/ViewModels/TrackColorViewModel.cs @@ -17,7 +17,7 @@ public TrackColorViewModel(UTrack track) { public void Finish() { if(SelectedColor.Name != track.TrackColor) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.setting"); DocManager.Inst.ExecuteCmd(new ChangeTrackColorCommand(DocManager.Inst.Project, track, SelectedColor.Name)); DocManager.Inst.EndUndoGroup(); } diff --git a/OpenUtau/ViewModels/TrackHeaderViewModel.cs b/OpenUtau/ViewModels/TrackHeaderViewModel.cs index 6d50c10a3..4fddddf1a 100644 --- a/OpenUtau/ViewModels/TrackHeaderViewModel.cs +++ b/OpenUtau/ViewModels/TrackHeaderViewModel.cs @@ -59,7 +59,7 @@ public TrackHeaderViewModel(UTrack track) { this.track = track; SelectSingerCommand = ReactiveCommand.Create(singer => { if (track.Singer != singer) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.singer"); Log.Information($"Loading Singer: {singer.Name}"); DocManager.Inst.ExecuteCmd(new TrackChangeSingerCommand(DocManager.Inst.Project, track, singer)); if (!string.IsNullOrEmpty(singer?.Id) && @@ -96,7 +96,7 @@ public TrackHeaderViewModel(UTrack track) { }); SelectPhonemizerCommand = ReactiveCommand.Create(factory => { if (track.Phonemizer.GetType() != factory.type) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.setting"); var phonemizer = factory.Create(); Log.Information($"Loading Phonemizer: {phonemizer.ToString()}"); DocManager.Inst.ExecuteCmd(new TrackChangePhonemizerCommand(DocManager.Inst.Project, track, phonemizer)); @@ -120,7 +120,7 @@ public TrackHeaderViewModel(UTrack track) { var settings = new URenderSettings { renderer = name, }; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.setting"); DocManager.Inst.ExecuteCmd(new TrackChangeRenderSettingCommand(DocManager.Inst.Project, track, settings)); DocManager.Inst.EndUndoGroup(); this.RaisePropertyChanged(nameof(Renderer)); @@ -466,7 +466,7 @@ public void ManuallyRaise() { } public void Remove() { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.delete"); DocManager.Inst.ExecuteCmd(new RemoveTrackCommand(DocManager.Inst.Project, track)); DocManager.Inst.EndUndoGroup(); } @@ -475,7 +475,7 @@ public void MoveUp() { if (track == DocManager.Inst.Project.tracks.First()) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.order"); DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, true)); DocManager.Inst.EndUndoGroup(); } @@ -484,7 +484,7 @@ public void MoveDown() { if (track == DocManager.Inst.Project.tracks.Last()) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.order"); DocManager.Inst.ExecuteCmd(new MoveTrackCommand(DocManager.Inst.Project, track, false)); DocManager.Inst.EndUndoGroup(); } @@ -495,7 +495,7 @@ public void Rename() { dialog.SetText(track.TrackName); dialog.onFinish = name => { if (!string.IsNullOrWhiteSpace(name) && name != track.TrackName) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.setting"); this.TrackName = name; DocManager.Inst.ExecuteCmd(new RenameTrackCommand(DocManager.Inst.Project, track, name)); DocManager.Inst.EndUndoGroup(); @@ -520,7 +520,7 @@ public async void SelectTrackColor() { } public void Duplicate() { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.duplicate"); //TODO var newTrack = new UTrack(track.TrackName + "_copy") { TrackNo = track.TrackNo + 1, @@ -546,7 +546,7 @@ public void Duplicate() { } public void DuplicateSettings() { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.duplicate"); //TODO DocManager.Inst.ExecuteCmd(new AddTrackCommand(DocManager.Inst.Project, new UTrack(track.TrackName + "_copy") { TrackNo = track.TrackNo + 1, diff --git a/OpenUtau/ViewModels/TrackSettingsViewModel.cs b/OpenUtau/ViewModels/TrackSettingsViewModel.cs index e4a04e75d..1fc53fc18 100644 --- a/OpenUtau/ViewModels/TrackSettingsViewModel.cs +++ b/OpenUtau/ViewModels/TrackSettingsViewModel.cs @@ -94,7 +94,7 @@ public void Finish() { if (Renderers.CLASSIC != Track.RendererSettings.renderer) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.setting"); var settings = Track.RendererSettings.Clone(); settings.resampler = Resampler?.ToString() ?? string.Empty; settings.wavtool = Wavtool?.ToString() ?? string.Empty; diff --git a/OpenUtau/ViewModels/TracksViewModel.cs b/OpenUtau/ViewModels/TracksViewModel.cs index 387b7dfbe..7bbcaa0be 100644 --- a/OpenUtau/ViewModels/TracksViewModel.cs +++ b/OpenUtau/ViewModels/TracksViewModel.cs @@ -232,7 +232,7 @@ public Size TickTrackToSize(int ticks, int tracks) { public void AddTrack() { var project = DocManager.Inst.Project; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.add"); DocManager.Inst.ExecuteCmd(new AddTrackCommand(project, new UTrack(project) { TrackNo = project.tracks.Count() })); DocManager.Inst.EndUndoGroup(); } @@ -251,7 +251,7 @@ public void AddTrack() { trackNo = trackNo, Duration = durTick, }; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.part.add"); DocManager.Inst.ExecuteCmd(new AddPartCommand(project, part)); DocManager.Inst.EndUndoGroup(); return part; @@ -309,7 +309,7 @@ public void DeleteSelectedParts() { if (SelectedParts.Count <= 0) { return; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.part.delete"); var selectedParts = SelectedParts.ToArray(); foreach (var part in selectedParts) { DocManager.Inst.ExecuteCmd(new RemovePartCommand(Project, part)); @@ -327,7 +327,7 @@ public void CopyParts() { public void CutParts() { if (SelectedParts.Count > 0) { DocManager.Inst.PartsClipboard = SelectedParts.Select(part => part.Clone()).ToList(); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.part.delete"); var toRemove = new List(SelectedParts); SelectedParts.Clear(); foreach (var part in toRemove) { @@ -353,7 +353,7 @@ public void PasteParts() { } part.trackNo = newTrackNo; } - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.part.paste"); while (Project.tracks.Count <= newTrackNo) { DocManager.Inst.ExecuteCmd(new AddTrackCommand(Project, new UTrack(Project) { TrackNo = Project.tracks.Count, diff --git a/OpenUtau/Views/MainWindow.axaml.cs b/OpenUtau/Views/MainWindow.axaml.cs index 673b6c6ea..c4997a461 100644 --- a/OpenUtau/Views/MainWindow.axaml.cs +++ b/OpenUtau/Views/MainWindow.axaml.cs @@ -134,7 +134,7 @@ private void AddTempoChange(int tick) { dialog.SetText(project.tempos[0].bpm.ToString()); dialog.onFinish = s => { if (double.TryParse(s, out double bpm)) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.tempo"); DocManager.Inst.ExecuteCmd(new AddTempoChangeCommand( project, tick, bpm)); DocManager.Inst.EndUndoGroup(); @@ -145,7 +145,7 @@ private void AddTempoChange(int tick) { private void DelTempoChange(int tick) { var project = DocManager.Inst.Project; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.tempo"); DocManager.Inst.ExecuteCmd(new DelTempoChangeCommand(project, tick)); DocManager.Inst.EndUndoGroup(); } @@ -161,7 +161,7 @@ void OnMenuRemapTimeaxis(object sender, RoutedEventArgs e) { dialog.onFinish = s => { try { if (double.TryParse(s, out double bpm)) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.tempo"); var oldTimeAxis = project.timeAxis.Clone(); DocManager.Inst.ExecuteCmd(new BpmCommand( project, bpm)); @@ -185,7 +185,7 @@ private void AddTimeSigChange(int bar) { var timeSig = project.timeAxis.TimeSignatureAtBar(bar); var dialog = new TimeSignatureDialog(timeSig.beatPerBar, timeSig.beatUnit); dialog.OnOk = (beatPerBar, beatUnit) => { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.timesignature"); DocManager.Inst.ExecuteCmd(new AddTimeSigCommand( project, bar, dialog.BeatPerBar, dialog.BeatUnit)); DocManager.Inst.EndUndoGroup(); @@ -195,7 +195,7 @@ private void AddTimeSigChange(int bar) { private void DelTimeSigChange(int bar) { var project = DocManager.Inst.Project; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.project.timesignature"); DocManager.Inst.ExecuteCmd(new DelTimeSigCommand(project, bar)); DocManager.Inst.EndUndoGroup(); } @@ -1174,7 +1174,7 @@ void RenamePart(UPart part) { dialog.onFinish = name => { if (!string.IsNullOrWhiteSpace(name) && name != part.name) { if (!string.IsNullOrWhiteSpace(name) && name != part.name) { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.part.edit"); DocManager.Inst.ExecuteCmd(new RenamePartCommand(DocManager.Inst.Project, part, name)); DocManager.Inst.EndUndoGroup(); } @@ -1206,7 +1206,7 @@ async void ReplaceAudio(UPart part) { position = part.position }; newPart.Load(DocManager.Inst.Project); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.import.audio"); DocManager.Inst.ExecuteCmd(new ReplacePartCommand(DocManager.Inst.Project, part, newPart)); DocManager.Inst.EndUndoGroup(); } @@ -1242,7 +1242,7 @@ void Transcribe(UPart part) { var track = new UTrack(project); track.TrackNo = project.tracks.Count; voicePart.trackNo = track.TrackNo; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.part.transcribe"); DocManager.Inst.ExecuteCmd(new AddTrackCommand(project, track)); DocManager.Inst.ExecuteCmd(new AddPartCommand(project, voicePart)); DocManager.Inst.EndUndoGroup(); @@ -1312,7 +1312,7 @@ void MergePart(UPart part) { SkipPhonemizer = false }; mergedPart.Validate(options, DocManager.Inst.Project, DocManager.Inst.Project.tracks[part.trackNo]); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.part.edit"); for (int i = selectedParts.Count - 1; i >= 0; i--) { // The index will shift by removing a part on each loop // Workaround by removing backwards from the largest index and going down @@ -1343,7 +1343,7 @@ public async void OnWelcomeTemplate(object sender, PointerPressedEventArgs args) } async void ValidateTracksVoiceColor() { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.remapvc"); foreach (var track in DocManager.Inst.Project.tracks) { if (track.ValidateVoiceColor(out var oldColors, out var newColors)) { await VoiceColorRemappingAsync(track, oldColors, newColors); @@ -1377,7 +1377,7 @@ void VoiceColorRemapping(UTrack track, string[] oldColors, string[] newColors) { VoiceColorMappingViewModel vm = new VoiceColorMappingViewModel(oldColors, newColors, track.TrackName); dialog.DataContext = vm; dialog.onFinish = () => { - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.track.remapvc"); SetVoiceColorRemapping(track, parts, vm); DocManager.Inst.EndUndoGroup(); }; diff --git a/OpenUtau/Views/NoteEditStates.cs b/OpenUtau/Views/NoteEditStates.cs index 9f81b10e7..6ebd56f33 100644 --- a/OpenUtau/Views/NoteEditStates.cs +++ b/OpenUtau/Views/NoteEditStates.cs @@ -48,7 +48,7 @@ class NoteEditState { public Point startPoint; public IValueTip valueTip; protected virtual bool ShowValueTip => true; - protected virtual string? commandName => null; + protected virtual string? commandNameKey => null; public NoteEditState(Control control, PianoRollViewModel vm, IValueTip valueTip) { this.control = control; @@ -58,7 +58,7 @@ public NoteEditState(Control control, PianoRollViewModel vm, IValueTip valueTip) public virtual void Begin(IPointer pointer, Point point) { pointer.Capture(control); startPoint = point; - DocManager.Inst.StartUndoGroup(commandName); + DocManager.Inst.StartUndoGroup(commandNameKey); if (ShowValueTip) { valueTip.ShowValueTip(); } @@ -140,7 +140,7 @@ class NoteMoveEditState : NoteEditState { public readonly UNote note; private double xOffset; protected override bool ShowValueTip => false; - protected override string? commandName => "Move notes"; + protected override string? commandNameKey => "command.note.move"; public NoteMoveEditState( Control control, @@ -217,7 +217,7 @@ class NoteDrawEditState : NoteEditState { private UNote? note; private bool playTone; private int activeTone; - protected override string? commandName => "Add notes"; + protected override string? commandNameKey => "command.note.add"; public NoteDrawEditState( Control control, @@ -297,7 +297,7 @@ class NoteResizeEditState : NoteEditState { public readonly UNote? neighborNote; public readonly bool resizeNeighbor; public readonly bool fromStart; - protected override string? commandName => "Move notes"; + protected override string? commandNameKey => "command.note.edit"; public NoteResizeEditState( Control control, @@ -397,7 +397,7 @@ class NoteSplitEditState : NoteEditState { private float oldVibFadeInTicks => oldVibFadeIn * oldVibLengthTicks / 100; private float oldVibFadeOutTicks => oldVibFadeOut * oldVibLengthTicks / 100; private float vibPeriod => note.vibrato.period; - protected override string? commandName => "Split notes"; + protected override string? commandNameKey => "command.note.split"; public NoteSplitEditState( Control control, @@ -509,7 +509,7 @@ class NoteEraseEditState : NoteEditState { public override MouseButton MouseButton => mouseButton; private MouseButton mouseButton; protected override bool ShowValueTip => false; - protected override string? commandName => "Delete notes"; + protected override string? commandNameKey => "command.note.delete"; public NoteEraseEditState( Control control, @@ -558,6 +558,8 @@ class PitchPointEditState : NoteEditState { private float y; private int index; private PitchPoint pitchPoint; + protected override string? commandNameKey => "command.pitch.editpoint"; + public PitchPointEditState( Control control, PianoRollViewModel vm, @@ -639,9 +641,10 @@ class ExpSetValueState : NoteEditState { private Point lastPoint; private UExpressionDescriptor? descriptor; private UTrack track; - private double startValue = 0; private bool shiftWasHeld = false; + protected override string? commandNameKey => "command.exp.edit"; + public ExpSetValueState( Control control, PianoRollViewModel vm, @@ -772,6 +775,8 @@ class ExpResetValueState : NoteEditState { private UExpressionDescriptor? descriptor; private UTrack track; public override MouseButton MouseButton => MouseButton.Right; + protected override string? commandNameKey => "command.exp.reset"; + public ExpResetValueState( Control control, PianoRollViewModel vm, @@ -837,6 +842,8 @@ private void ResetCurveExp(IPointer pointer, Point point) { class VibratoChangeStartState : NoteEditState { public readonly UNote note; + protected override string? commandNameKey => "command.vibrato.edit"; + public VibratoChangeStartState( Control control, PianoRollViewModel vm, @@ -857,6 +864,8 @@ public override void Update(IPointer pointer, Point point) { class VibratoChangeInState : NoteEditState { public readonly UNote note; + protected override string? commandNameKey => "command.vibrato.edit"; + public VibratoChangeInState( Control control, PianoRollViewModel vm, @@ -882,6 +891,8 @@ public override void Update(IPointer pointer, Point point) { class VibratoChangeOutState : NoteEditState { public readonly UNote note; + protected override string? commandNameKey => "command.vibrato.edit"; + public VibratoChangeOutState( Control control, PianoRollViewModel vm, @@ -906,6 +917,8 @@ public override void Update(IPointer pointer, Point point) { class VibratoChangeDepthState : NoteEditState { public readonly UNote note; + protected override string? commandNameKey => "command.vibrato.edit"; + public VibratoChangeDepthState( Control control, PianoRollViewModel vm, @@ -926,6 +939,8 @@ public override void Update(IPointer pointer, Point point) { class VibratoChangePeriodState : NoteEditState { public readonly UNote note; + protected override string? commandNameKey => "command.vibrato.edit"; + public VibratoChangePeriodState( Control control, PianoRollViewModel vm, @@ -956,6 +971,8 @@ class VibratoChangeShiftState : NoteEditState { public readonly UNote note; public readonly Point hitPoint; public readonly float initialShift; + protected override string? commandNameKey => "command.vibrato.edit"; + public VibratoChangeShiftState( Control control, PianoRollViewModel vm, @@ -986,6 +1003,8 @@ class PhonemeMoveState : NoteEditState { public readonly UPhoneme phoneme; public readonly int index; public int startOffset; + protected override string? commandNameKey => "command.phoneme.edit"; + public PhonemeMoveState( Control control, PianoRollViewModel vm, @@ -1020,6 +1039,8 @@ class PhonemeChangePreutterState : NoteEditState { public readonly UNote leadingNote; public readonly UPhoneme phoneme; public readonly int index; + protected override string? commandNameKey => "command.phoneme.edit"; + public PhonemeChangePreutterState( Control control, PianoRollViewModel vm, @@ -1049,6 +1070,8 @@ class PhonemeChangeOverlapState : NoteEditState { public readonly UNote leadingNote; public readonly UPhoneme phoneme; public readonly int index; + protected override string? commandNameKey => "command.phoneme.edit"; + public PhonemeChangeOverlapState( Control control, PianoRollViewModel vm, @@ -1077,6 +1100,8 @@ public override void Update(IPointer pointer, Point point) { class PhonemeResetState : NoteEditState { public override MouseButton MouseButton => MouseButton.Right; protected override bool ShowValueTip => false; + protected override string? commandNameKey => "command.phoneme.reset"; + public PhonemeResetState( Control control, PianoRollViewModel vm, @@ -1114,8 +1139,10 @@ public override void Update(IPointer pointer, Point point) { class DrawPitchState : NoteEditState { protected override bool ShowValueTip => false; + protected override string? commandNameKey => "command.pitch.draw"; double? lastPitch; Point lastPoint; + public DrawPitchState( Control control, PianoRollViewModel vm, @@ -1149,10 +1176,12 @@ public override void Update(IPointer pointer, Point point) { class DrawLinePitchState : NoteEditState { protected override bool ShowValueTip => false; + protected override string? commandNameKey => "command.pitch.draw"; double? firstPitch; Point firstPoint; double? lastPitch; Point lastPoint; + public DrawLinePitchState( Control control, PianoRollViewModel vm, @@ -1193,8 +1222,10 @@ public override void Update(IPointer pointer, Point point) { class OverwriteLinePitchState : NoteEditState { protected override bool ShowValueTip => false; + protected override string? commandNameKey => "command.pitch.draw"; Point firstPoint; Point lastPoint; + public OverwriteLinePitchState( Control control, PianoRollViewModel vm, @@ -1296,8 +1327,10 @@ public override void Update(IPointer pointer, Point point) { class OverwritePitchState : NoteEditState { protected override bool ShowValueTip => false; + protected override string? commandNameKey => "command.pitch.draw"; double? lastPitch; Point lastPoint; + public OverwritePitchState( Control control, PianoRollViewModel vm, @@ -1331,9 +1364,11 @@ public override void Update(IPointer pointer, Point point) { class SmoothenPitchState : NoteEditState { protected override bool ShowValueTip => false; + protected override string? commandNameKey => "command.pitch.edit"; int brushRadius = 10; int kernelRadius = 3; double kernelWeight = 1.0 / (2 * 3 + 1); + public SmoothenPitchState( Control control, PianoRollViewModel vm, @@ -1377,7 +1412,9 @@ public override void Update(IPointer pointer, Point point) { class ResetPitchState : NoteEditState { public override MouseButton MouseButton => MouseButton.Right; protected override bool ShowValueTip => false; + protected override string? commandNameKey => "command.pitch.reset"; Point lastPoint; + public ResetPitchState( Control control, PianoRollViewModel vm, diff --git a/OpenUtau/Views/PartEditStates.cs b/OpenUtau/Views/PartEditStates.cs index 8ded14666..d0fc658a3 100644 --- a/OpenUtau/Views/PartEditStates.cs +++ b/OpenUtau/Views/PartEditStates.cs @@ -15,6 +15,8 @@ class PartEditState { public readonly Control control; public readonly MainWindowViewModel vm; public Point startPoint; + protected virtual string? commandNameKey => null; + public PartEditState(Control control, MainWindowViewModel vm) { this.control = control; this.vm = vm; @@ -22,7 +24,7 @@ public PartEditState(Control control, MainWindowViewModel vm) { public virtual void Begin(IPointer pointer, Point point) { pointer.Capture(control); startPoint = point; - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup(commandNameKey); } public virtual void End(IPointer pointer, Point point) { pointer.Capture(null); @@ -39,20 +41,21 @@ public static void Swap(ref T a, ref T b) { class PartSelectionEditState : PartEditState { private int startTick; private int startTrack; - public readonly Rectangle selectionBox; + public PartSelectionEditState(Control control, MainWindowViewModel vm, Rectangle selectionBox) : base(control, vm) { this.selectionBox = selectionBox; } public override void Begin(IPointer pointer, Point point) { - base.Begin(pointer, point); + pointer.Capture(control); + startPoint = point; selectionBox.IsVisible = true; var tracksVm = vm.TracksViewModel; startTick = tracksVm.PointToTick(point); startTrack = tracksVm.PointToTrackNo(point); } public override void End(IPointer pointer, Point point) { - base.End(pointer, point); + pointer.Capture(null); selectionBox.IsVisible = false; var tracksVm = vm.TracksViewModel; tracksVm.CommitTempSelectParts(); @@ -84,6 +87,8 @@ class PartMoveEditState : PartEditState { public readonly UPart part; public readonly bool isVoice; private double xOffset; + protected override string? commandNameKey => "command.part.move"; + public PartMoveEditState(Control control, MainWindowViewModel vm, UPart part) : base(control, vm) { this.part = part; isVoice = part is UVoicePart; @@ -152,6 +157,8 @@ public override void Update(IPointer pointer, Point point) { class PartResizeEditState : PartEditState { public readonly UPart part; public readonly bool fromStart; + protected override string? commandNameKey => "command.part.edit"; + public PartResizeEditState(Control control, MainWindowViewModel vm, UPart part, bool fromStart = false) : base(control, vm) { this.part = part; var tracksVm = vm.TracksViewModel; @@ -190,6 +197,8 @@ public override void Update(IPointer pointer, Point point) { class PartEraseEditState : PartEditState { public override MouseButton MouseButton => MouseButton.Right; + protected override string? commandNameKey => "command.part.delete"; + public PartEraseEditState(Control control, MainWindowViewModel vm) : base(control, vm) { } public override void Update(IPointer pointer, Point point) { var project = DocManager.Inst.Project; From ce28f1501984b2e844e7d79f036d766fc6c65a14 Mon Sep 17 00:00:00 2001 From: Maiko Date: Mon, 1 Dec 2025 23:40:53 +0900 Subject: [PATCH 03/13] resolve conflict --- OpenUtau.Core/Editing/NoteBatchEdits.cs | 6 +++--- OpenUtau/ViewModels/ExpressionsViewModel.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenUtau.Core/Editing/NoteBatchEdits.cs b/OpenUtau.Core/Editing/NoteBatchEdits.cs index dd537f769..4c3114876 100644 --- a/OpenUtau.Core/Editing/NoteBatchEdits.cs +++ b/OpenUtau.Core/Editing/NoteBatchEdits.cs @@ -263,7 +263,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do } int offset = left - minPosition - part.position; notes.ForEach(note => note.position += offset); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.batch.note"); DocManager.Inst.ExecuteCmd(new AddNoteCommand(part, notes)); int minDurTick = part.GetMinDurTick(project); if (part.Duration < minDurTick) { @@ -351,7 +351,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (notes.Count == 0) { return; } - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("command.batch.note", true); const int maxTick = 20; int delta; Random random = new Random(); @@ -390,7 +390,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (notes.Count == 0) { return; } - docManager.StartUndoGroup(true); + docManager.StartUndoGroup("command.batch.note", true); const int maxTick = 20 ; Random random = new Random(); foreach (var note in notes) { diff --git a/OpenUtau/ViewModels/ExpressionsViewModel.cs b/OpenUtau/ViewModels/ExpressionsViewModel.cs index 9eed80087..51e29f948 100644 --- a/OpenUtau/ViewModels/ExpressionsViewModel.cs +++ b/OpenUtau/ViewModels/ExpressionsViewModel.cs @@ -226,7 +226,7 @@ public void Apply() { if (flags.Count() > 0) { throw new MessageCustomizableException("", $": {string.Join(", ", flags)}", new ArgumentException($"Flags must be unique: {string.Join(", ", flags)}"), false); } - DocManager.Inst.StartUndoGroup(command.track.exp); + DocManager.Inst.StartUndoGroup("command.track.exp"); DocManager.Inst.ExecuteCmd(new ConfigureExpressionsCommand( DocManager.Inst.Project, expressionsSourceProject.Select(builder => builder.Build()).ToArray(), @@ -235,7 +235,7 @@ public void Apply() { DocManager.Inst.EndUndoGroup(); return; } - DocManager.Inst.StartUndoGroup(command.project.exp); + DocManager.Inst.StartUndoGroup("command.project.exp"); DocManager.Inst.ExecuteCmd(new ConfigureExpressionsCommand(DocManager.Inst.Project, expressionsSourceProject.Select(builder => builder.Build()).ToArray())); DocManager.Inst.EndUndoGroup(); } From 2825b1da1213d91a5030c450381209bbb640ae9e Mon Sep 17 00:00:00 2001 From: Maiko Date: Mon, 1 Dec 2025 23:45:36 +0900 Subject: [PATCH 04/13] set text to PastePlainNotes() --- OpenUtau/ViewModels/NotesViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index fcc7c074a..bef858661 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -896,7 +896,7 @@ UNote toPlainNote(UNote note) { int offset = left - minPosition - Part.position; var notes = DocManager.Inst.NotesClipboard.Select(note => toPlainNote(note)).ToList(); notes.ForEach(note => note.position += offset); - DocManager.Inst.StartUndoGroup(); + DocManager.Inst.StartUndoGroup("command.note.paste"); DocManager.Inst.ExecuteCmd(new AddNoteCommand(Part, notes)); int minDurTick = Part.GetMinDurTick(Project); if (Part.Duration < minDurTick) { From e3f92b5fce69d1129934d925627562c6d467d620 Mon Sep 17 00:00:00 2001 From: Maiko Date: Sun, 4 Jan 2026 20:09:14 +0900 Subject: [PATCH 05/13] Add Curve Tools and Curve Selection/Copy/Paste --- OpenUtau.Core/Commands/ExpCommands.cs | 66 +++++++++++- OpenUtau.Core/DocManager.cs | 5 +- OpenUtau.Core/Editing/NoteBatchEdits.cs | 8 +- OpenUtau.Core/Editing/ResetBatchEdits.cs | 16 +-- OpenUtau.Core/Ustx/UCurve.cs | 96 ++++++++++++++++++ OpenUtau/Controls/ExpressionCanvas.cs | 55 ++++++++-- OpenUtau/Strings/Strings.axaml | 1 + OpenUtau/Styles/Styles.axaml | 31 ++++++ OpenUtau/ViewModels/CurveViewModel.cs | 116 ++++++++++++++++++++++ OpenUtau/ViewModels/NotesViewModel.cs | 10 ++ OpenUtau/ViewModels/PianoRollViewModel.cs | 3 + OpenUtau/Views/NoteEditStates.cs | 71 +++++++++++-- OpenUtau/Views/PianoRollWindow.axaml | 60 ++++++++++- OpenUtau/Views/PianoRollWindow.axaml.cs | 63 ++++++++++-- 14 files changed, 547 insertions(+), 54 deletions(-) create mode 100644 OpenUtau/ViewModels/CurveViewModel.cs diff --git a/OpenUtau.Core/Commands/ExpCommands.cs b/OpenUtau.Core/Commands/ExpCommands.cs index c8e0dae9a..a42822bb8 100644 --- a/OpenUtau.Core/Commands/ExpCommands.cs +++ b/OpenUtau.Core/Commands/ExpCommands.cs @@ -18,7 +18,7 @@ public override ValidateOptions ValidateOptions public ExpCommand(UVoicePart part) { Part = part; } - } + } public class SetNoteExpressionCommand : ExpCommand { static readonly HashSet needsPhonemizer = new HashSet { @@ -396,6 +396,70 @@ public override void Unexecute() { } } + public class PasteCurveCommand : ExpCommand { + readonly UProject project; + readonly string abbr; + readonly int[] xs; + readonly int[] ys; + int[]? oldXs; + int[]? oldYs; + public PasteCurveCommand(UProject project, UVoicePart part, string abbr, IEnumerable xs, IEnumerable ys) : base(part) { + this.project = project; + this.abbr = abbr; + this.xs = xs.ToArray(); + this.ys = ys.ToArray(); + var curve = part.curves.FirstOrDefault(c => c.abbr == abbr); + oldXs = curve?.xs.ToArray(); + oldYs = curve?.ys.ToArray(); + } + public PasteCurveCommand(UProject project, UVoicePart part, string abbr, int startX, int startY, int endX, int endY) : base(part) { + this.project = project; + this.abbr = abbr; + this.xs = new int[] { startX, endX }; + this.ys = new int[] { startY, endY }; + var curve = part.curves.FirstOrDefault(c => c.abbr == abbr); + oldXs = curve?.xs.ToArray(); + oldYs = curve?.ys.ToArray(); + } + public override string ToString() => "Edit Curve"; + public override void Execute() { + var curve = Part.curves.FirstOrDefault(c => c.abbr == abbr); + var track = project.tracks[Part.trackNo]; + if (track.TryGetExpDescriptor(project, abbr, out var descriptor)) { + if (curve == null) { + curve = new UCurve(descriptor); + Part.curves.Add(curve); + } + + var xs = this.xs.ToList(); + var ys = this.ys.ToList(); + xs.Insert(0, xs[0] - UCurve.interval); + ys.Insert(0, curve.Sample(xs[0])); + xs.Add(xs.Last() + UCurve.interval); + ys.Add(curve.Sample(xs.Last())); + ys = ys.Select(y => (int)Math.Clamp(y, descriptor.min, descriptor.max)).ToList(); + + curve.Set(xs.First(), ys.First(), xs.First(), ys.First()); + curve.Set(xs.Last(), ys.Last(), xs.Last(), ys.Last()); + for (int i = 0; i < xs.Count - 1; i++) { + curve.Set(xs[i + 1], ys[i + 1], xs[i], ys[i]); + } + } + } + public override void Unexecute() { + var curve = Part.curves.FirstOrDefault(c => c.abbr == abbr); + if (curve == null) { + return; + } + curve.xs.Clear(); + curve.ys.Clear(); + if (oldXs != null && oldYs != null) { + curve.xs.AddRange(oldXs); + curve.ys.AddRange(oldYs); + } + } + } + public class ClearCurveCommand : ExpCommand { readonly string abbr; readonly int[] oldXs; diff --git a/OpenUtau.Core/DocManager.cs b/OpenUtau.Core/DocManager.cs index 976712dd4..510ac689a 100644 --- a/OpenUtau.Core/DocManager.cs +++ b/OpenUtau.Core/DocManager.cs @@ -37,8 +37,9 @@ public class DocManager : SingletonBase { public PhonemizerFactory[] PhonemizerFactories { get; private set; } public UProject Project { get; private set; } public bool HasOpenUndoGroup => undoGroup != null; - public List PartsClipboard { get; set; } - public List NotesClipboard { get; set; } + public List? PartsClipboard { get; set; } + public List? NotesClipboard { get; set; } + public CurveSelection? CurvesClipboard { get; set; } internal PhonemizerRunner PhonemizerRunner { get; private set; } public void Initialize(Thread mainThread, TaskScheduler mainScheduler) { diff --git a/OpenUtau.Core/Editing/NoteBatchEdits.cs b/OpenUtau.Core/Editing/NoteBatchEdits.cs index 4c3114876..3ecc3de23 100644 --- a/OpenUtau.Core/Editing/NoteBatchEdits.cs +++ b/OpenUtau.Core/Editing/NoteBatchEdits.cs @@ -759,13 +759,7 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do if (pitchPointsPerNote.TryGetValue(note.position, out var tickRangeAndPitch)) { var start = tickRangeAndPitch.Item1 - part.position; var end = tickRangeAndPitch.Item2 - part.position; - docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD, - start, 0, - start, 0)); - docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD, - end, 0, - end, 0)); - docManager.ExecuteCmd(new SetCurveCommand(project, part, Format.Ustx.PITD, + docManager.ExecuteCmd(new PasteCurveCommand(project, part, Format.Ustx.PITD, start, 0, end, 0)); } diff --git a/OpenUtau.Core/Editing/ResetBatchEdits.cs b/OpenUtau.Core/Editing/ResetBatchEdits.cs index 6ce7dfaf5..c6f45a55c 100644 --- a/OpenUtau.Core/Editing/ResetBatchEdits.cs +++ b/OpenUtau.Core/Editing/ResetBatchEdits.cs @@ -129,14 +129,8 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do var selectedTickRanges = SelectionUtils.SelectedTickRanges(part, notes); int defaultValue = (int)part.curves.First(c => c.abbr == abbr).descriptor.defaultValue; foreach(var range in selectedTickRanges){ - docManager.ExecuteCmd(new SetCurveCommand(project, part, abbr, + docManager.ExecuteCmd(new PasteCurveCommand(project, part, Format.Ustx.PITD, range.start, defaultValue, - range.start, defaultValue)); - docManager.ExecuteCmd(new SetCurveCommand(project, part, abbr, - range.end, defaultValue, - range.end, defaultValue)); - docManager.ExecuteCmd(new SetCurveCommand(project, part, abbr, - range.start, defaultValue, range.end, defaultValue)); } } @@ -294,14 +288,8 @@ public void Run(UProject project, UVoicePart part, List selectedNotes, Do var selectedTickRanges = SelectionUtils.SelectedTickRanges(part, notes); int defaultValue = (int)part.curves.First(c => c.abbr == abbr).descriptor.defaultValue; foreach(var range in selectedTickRanges){ - docManager.ExecuteCmd(new SetCurveCommand(project, part, abbr, + docManager.ExecuteCmd(new PasteCurveCommand(project, part, abbr, range.start, defaultValue, - range.start, defaultValue)); - docManager.ExecuteCmd(new SetCurveCommand(project, part, abbr, - range.end, defaultValue, - range.end, defaultValue)); - docManager.ExecuteCmd(new SetCurveCommand(project, part, abbr, - range.start, defaultValue, range.end, defaultValue)); } } diff --git a/OpenUtau.Core/Ustx/UCurve.cs b/OpenUtau.Core/Ustx/UCurve.cs index 9480c4060..22e2a6020 100644 --- a/OpenUtau.Core/Ustx/UCurve.cs +++ b/OpenUtau.Core/Ustx/UCurve.cs @@ -181,4 +181,100 @@ public static List MergeCurves(params List[] merging) { return merged.Values.ToList(); } } + + public class CurveSelection { + public string? Abbr { get; private set; } + public (int x, int y) StartPoint { get; set; } = (0, 0); + public (int x, int y) EndPoint { get; set; } = (0, 0); + private List xs = new List(); // tick from part start + private List ys = new List(); + + public CurveSelection() { } + + public bool HasValue(string? abbr = null) { + return Abbr != null && (abbr == null || Abbr == abbr); + } + + public void Clear() { + Abbr = null; + StartPoint = (0, 0); + EndPoint = (0, 0); + xs.Clear(); + ys.Clear(); + } + + public void Add (string abbr, (int x, int y) startPoint, (int x, int y) endPoint, IEnumerable xs, IEnumerable ys) { + Abbr = abbr; + StartPoint = startPoint; + EndPoint = endPoint; + this.xs.AddRange(xs); + this.ys.AddRange(ys); + } + + public void GetWholeCurveAndSelection(string abbr, UCurve? curve, out List wholeXs, out List wholeYs) { + wholeXs = new List(); + wholeYs = new List(); + if (curve != null) { + wholeXs.AddRange(curve.xs); + wholeYs.AddRange(curve.ys); + } + if (HasValue(abbr)) { + bool flag = false; + for (int i = 0; i < wholeXs.Count; i++) { + int x = wholeXs[i]; + if (StartPoint.x < x) { + wholeXs.Insert(i, StartPoint.x); + wholeYs.Insert(i, StartPoint.y); + flag = true; + break; + } + } + if (!flag) { + wholeXs.Add(StartPoint.x); + wholeYs.Add(StartPoint.y); + } + + if (StartPoint.x != EndPoint.x) { + flag = false; + for (int i = 0; i < wholeXs.Count; i++) { + int x = wholeXs[i]; + if (EndPoint.x < x) { + wholeXs.Insert(i, EndPoint.x); + wholeYs.Insert(i, EndPoint.y); + flag = true; + break; + } + } + if (!flag) { + wholeXs.Add(EndPoint.x); + wholeYs.Add(EndPoint.y); + } + } + } + } + + public void GetSelectedRange(string abbr, out List xs, out List ys) { + xs = new List(); + ys = new List(); + if (!HasValue(abbr)) { + return; + } + xs.Add(StartPoint.x); + ys.Add(StartPoint.y); + xs.AddRange(this.xs); + ys.AddRange(this.ys); + xs.Add(EndPoint.x); + ys.Add(EndPoint.y); + } + + public CurveSelection Clone() { + return new CurveSelection() { + Abbr = Abbr, + StartPoint = StartPoint, + EndPoint = EndPoint, + xs = new List(xs), + ys = new List(ys) + }; + } + } } diff --git a/OpenUtau/Controls/ExpressionCanvas.cs b/OpenUtau/Controls/ExpressionCanvas.cs index 6dfccd07f..901ff5def 100644 --- a/OpenUtau/Controls/ExpressionCanvas.cs +++ b/OpenUtau/Controls/ExpressionCanvas.cs @@ -7,6 +7,7 @@ using OpenUtau.App.ViewModels; using OpenUtau.Core; using OpenUtau.Core.Ustx; +using OpenUtau.ViewModels; using ReactiveUI; namespace OpenUtau.App.Controls { @@ -67,6 +68,7 @@ public bool ShowRealCurve { private bool showRealCurve = true; private HashSet selectedNotes = new HashSet(); + private CurveSelection curveSelection = new CurveSelection(); private Geometry pointGeometry; private Geometry circleGeometry; @@ -83,6 +85,11 @@ public ExpressionCanvas() { selectedNotes.UnionWith(e.tempSelectedNotes); InvalidateVisual(); }); + MessageBus.Current.Listen() + .Subscribe(e => { + curveSelection = e.selection; + InvalidateVisual(); + }); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { @@ -118,34 +125,62 @@ public override void Render(DrawingContext context) { double defaultHeight = Math.Round(Bounds.Height - Bounds.Height * (descriptor.defaultValue - descriptor.min) / (descriptor.max - descriptor.min)); var lPen = ThemeManager.AccentPen1; var lPen2 = ThemeManager.AccentPen1Thickness2; + var lPenSelected = ThemeManager.AccentPen2; + var lPen2Selected = ThemeManager.AccentPen2Thickness2; var lPen3 = new Pen(ThemeManager.NeutralAccentBrush, 1, new DashStyle(new double[] { 4, 4 }, 0)); var brush = ThemeManager.AccentBrush1; double x3 = Math.Round(viewModel.TickToneToPoint(leftTick, 0).X); double x4 = Math.Round(viewModel.TickToneToPoint(rightTick, 0).X); context.DrawLine(lPen3, new Point(x3, defaultHeight), new Point(x4, defaultHeight)); + + curveSelection.GetWholeCurveAndSelection(descriptor.abbr, curve, out List xs, out List ys); if (curve == null) { - double x1 = Math.Round(viewModel.TickToneToPoint(leftTick, 0).X); - double x2 = Math.Round(viewModel.TickToneToPoint(rightTick, 0).X); - context.DrawLine(lPen, new Point(x1, defaultHeight), new Point(x2, defaultHeight)); + xs.Insert(0, (int)leftTick); + xs.Add((int)rightTick); + for (int i = 0; i < xs.Count - 1; i++) { + double x1 = Math.Round(viewModel.TickToneToPoint(xs[i], 0).X); + double x2 = Math.Round(viewModel.TickToneToPoint(xs[i + 1], 0).X); + if (curveSelection.HasValue(descriptor.abbr)) { + if (curveSelection.StartPoint.x <= xs[i] && xs[i] <= curveSelection.EndPoint.x + && curveSelection.StartPoint.x <= xs[i + 1] && xs[i + 1] <= curveSelection.EndPoint.x) { + context.DrawLine(lPenSelected, new Point(x1, defaultHeight), new Point(x2, defaultHeight)); + } else { + context.DrawLine(lPen, new Point(x1, defaultHeight), new Point(x2, defaultHeight)); + } + } else { + context.DrawLine(lPen, new Point(x1, defaultHeight), new Point(x2, defaultHeight)); + } + } return; } + int lTick = (int)Math.Floor(leftTick / 5) * 5; int rTick = (int)Math.Ceiling(rightTick / 5) * 5; - int index = curve.xs.BinarySearch(lTick); + int index = xs.BinarySearch(lTick); if (index < 0) { index = -index - 1; } index = Math.Max(0, index) - 1; - while (index < curve.xs.Count) { - float tick1 = index < 0 ? lTick : curve.xs[index]; - float value1 = index < 0 ? descriptor.defaultValue : curve.ys[index]; + while (index < xs.Count) { + float tick1 = index < 0 ? lTick : xs[index]; + float value1 = index < 0 ? descriptor.defaultValue : ys[index]; double x1 = viewModel.TickToneToPoint(tick1, 0).X; double y1 = defaultHeight - Bounds.Height * (value1 - descriptor.defaultValue) / (descriptor.max - descriptor.min); - float tick2 = index == curve.xs.Count - 1 ? rTick : curve.xs[index + 1]; - float value2 = index == curve.xs.Count - 1 ? descriptor.defaultValue : curve.ys[index + 1]; + float tick2 = index == xs.Count - 1 ? rTick : xs[index + 1]; + float value2 = index == xs.Count - 1 ? descriptor.defaultValue : ys[index + 1]; double x2 = viewModel.TickToneToPoint(tick2, 0).X; double y2 = defaultHeight - Bounds.Height * (value2 - descriptor.defaultValue) / (descriptor.max - descriptor.min); - var pen = value1 == descriptor.defaultValue && value2 == descriptor.defaultValue ? lPen : lPen2; + IPen pen; + if (curveSelection.HasValue(descriptor.abbr)) { + if (curveSelection.StartPoint.x <= tick1 && tick1 <= curveSelection.EndPoint.x + && curveSelection.StartPoint.x <= tick2 && tick2 <= curveSelection.EndPoint.x) { + pen = value1 == descriptor.defaultValue && value2 == descriptor.defaultValue ? lPenSelected : lPen2Selected; + } else { + pen = value1 == descriptor.defaultValue && value2 == descriptor.defaultValue ? lPen : lPen2; + } + } else { + pen = value1 == descriptor.defaultValue && value2 == descriptor.defaultValue ? lPen : lPen2; + } context.DrawLine(pen, new Point(x1, y1), new Point(x2, y2)); //using (var state = context.PushTransform(Matrix.CreateTranslation(x1, y1))) { // context.DrawGeometry(brush, null, pointGeometry); diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index a665ccbfc..c7aa58ae0 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -7,6 +7,7 @@ Apply plugin edit Batch reset Edit expression + Paste expression curve Reset expression Import audio Import track(s) diff --git a/OpenUtau/Styles/Styles.axaml b/OpenUtau/Styles/Styles.axaml index 2e42be9ba..cddc77e50 100644 --- a/OpenUtau/Styles/Styles.axaml +++ b/OpenUtau/Styles/Styles.axaml @@ -219,6 +219,37 @@ + + + + + + + + + + + + + + + + + + + - - - - - - - - @@ -33,29 +31,36 @@ - - - - - + + @@ -67,6 +72,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +