Skip to content

Commit 9f6a752

Browse files
Merge pull request #523 from NeoCoderMatrix86/502-editing-an-track-in-import-view-triggers-undo-to-be-possible
Editing an track in import view triggers undo to be possible
2 parents 7b274d3 + 4596b04 commit 9f6a752

5 files changed

Lines changed: 157 additions & 20 deletions

File tree

AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using AudioCuesheetEditor.Model.AudioCuesheet.Import;
1919
using AudioCuesheetEditor.Model.IO;
2020
using AudioCuesheetEditor.Model.IO.Import;
21+
using AudioCuesheetEditor.Model.UI;
2122
using AudioCuesheetEditor.Services.IO;
2223
using AudioCuesheetEditor.Services.UI;
2324
using AudioCuesheetEditor.Tests.Utility;
@@ -43,8 +44,8 @@ public async Task AnalyseImportfile_WithTextfile_SetsImportCuesheet()
4344
{
4445
// Arrange
4546
var fileContent = "This is just a test";
46-
var traceChangeManager = new TraceChangeManager(TestHelper.CreateLogger<TraceChangeManager>());
47-
var sessionStateContainer = new SessionStateContainer(traceChangeManager);
47+
var traceChangeManagerMock = new Mock<ITraceChangeManager>();
48+
var sessionStateContainer = new SessionStateContainer(traceChangeManagerMock.Object);
4849
var fileInputManagerMock = new Mock<IFileInputManager>();
4950
var textImportServiceMock = new Mock<ITextImportService>();
5051
var importCuesheet = new ImportCuesheet()
@@ -74,8 +75,7 @@ public async Task AnalyseImportfile_WithTextfile_SetsImportCuesheet()
7475
};
7576
textImportServiceMock.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importfile);
7677
var loggerMock = new Mock<ILogger<ImportManager>>();
77-
var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object);
78-
var testHelper = new TestHelper();
78+
var importManager = new ImportManager(sessionStateContainer, traceChangeManagerMock.Object, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object);
7979
sessionStateContainer.Importfile = new Importfile()
8080
{
8181
FileContent = "This is just a test",
@@ -101,6 +101,8 @@ public async Task AnalyseImportfile_WithTextfile_SetsImportCuesheet()
101101
Assert.AreEqual(importCuesheet.Tracks.First().Position, sessionStateContainer.ImportCuesheet.Tracks.First().Position);
102102
Assert.AreEqual(importCuesheet.Tracks.First().PreGap, sessionStateContainer.ImportCuesheet.Tracks.First().PreGap);
103103
Assert.AreEqual(importCuesheet.Tracks.First().PostGap, sessionStateContainer.ImportCuesheet.Tracks.First().PostGap);
104+
traceChangeManagerMock.Verify(x => x.TraceChanges(It.IsAny<Cuesheet>()));
105+
traceChangeManagerMock.Verify(x => x.TraceChanges(It.IsAny<Track>()));
104106
}
105107

106108
[TestMethod()]
@@ -120,7 +122,6 @@ public async Task AnalyseImportfile_WithoutAnalysedCuesheet_DoesNothing()
120122
textImportServiceMock.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importfile);
121123
var loggerMock = new Mock<ILogger<ImportManager>>();
122124
var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object);
123-
var testHelper = new TestHelper();
124125
sessionStateContainer.Importfile = importfile;
125126
// Act
126127
await importManager.AnalyseImportfile();
@@ -207,6 +208,50 @@ public async Task ImportFilesAsync_TextFile_ImportsCorrectly()
207208
Assert.AreEqual(ImportFileType.Textfile, sessionStateContainer.Importfile.FileType);
208209
}
209210

211+
[TestMethod]
212+
public void ImportCuesheet_WithImportCuesheetAvailable_ImportsCuesheetData()
213+
{
214+
// Arrange
215+
var traceChangeManagerMock = new Mock<ITraceChangeManager>();
216+
var sessionStateContainer = new SessionStateContainer(traceChangeManagerMock.Object);
217+
var analyzedCuesheet = new Cuesheet()
218+
{
219+
Artist = "Artist 123",
220+
Title = "Title 456"
221+
};
222+
analyzedCuesheet.AddTrack(new()
223+
{
224+
Artist = "Track Artist 1",
225+
Title = "Track Title 1",
226+
End = new TimeSpan(0, 4, 23),
227+
});
228+
analyzedCuesheet.AddTrack(new()
229+
{
230+
Artist = "Track Artist 2",
231+
Title = "Track Title 2",
232+
End = new TimeSpan(0, 8, 54),
233+
});
234+
sessionStateContainer.ImportCuesheet = analyzedCuesheet;
235+
var fileInputManagerMock = new Mock<IFileInputManager>();
236+
var textImportServiceMock = new Mock<ITextImportService>();
237+
var loggerMock = new Mock<ILogger<ImportManager>>();
238+
var importManager = new ImportManager(sessionStateContainer, traceChangeManagerMock.Object, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object);
239+
// Act
240+
importManager.ImportCuesheet();
241+
// Assert
242+
Assert.AreEqual(analyzedCuesheet.Artist, sessionStateContainer.Cuesheet.Artist);
243+
Assert.AreEqual(analyzedCuesheet.Title, sessionStateContainer.Cuesheet.Title);
244+
Assert.AreEqual(analyzedCuesheet.Tracks.First().Artist, sessionStateContainer.Cuesheet.Tracks.First().Artist);
245+
Assert.AreEqual(analyzedCuesheet.Tracks.First().Title, sessionStateContainer.Cuesheet.Tracks.First().Title);
246+
Assert.AreEqual(analyzedCuesheet.Tracks.First().Begin, sessionStateContainer.Cuesheet.Tracks.First().Begin);
247+
Assert.AreEqual(analyzedCuesheet.Tracks.First().End, sessionStateContainer.Cuesheet.Tracks.First().End);
248+
Assert.AreEqual(analyzedCuesheet.Tracks.Last().Artist, sessionStateContainer.Cuesheet.Tracks.Last().Artist);
249+
Assert.AreEqual(analyzedCuesheet.Tracks.Last().Title, sessionStateContainer.Cuesheet.Tracks.Last().Title);
250+
Assert.AreEqual(analyzedCuesheet.Tracks.Last().Begin, sessionStateContainer.Cuesheet.Tracks.Last().Begin);
251+
Assert.AreEqual(analyzedCuesheet.Tracks.Last().End, sessionStateContainer.Cuesheet.Tracks.Last().End);
252+
traceChangeManagerMock.Verify(x => x.RemoveTracedChanges(It.IsAny<IEnumerable<ITraceable>>()));
253+
}
254+
210255
private static IBrowserFile CreateBrowserFileMock(string name, string content = "TestContent")
211256
{
212257
var fileMock = new Mock<IBrowserFile>();

AudioCuesheetEditor.Tests/Services/UI/TraceChangeManagerTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,5 +354,59 @@ public void BulkEditTracksLengthTest()
354354
Assert.IsNull(track3.End);
355355
Assert.IsNull(track4.End);
356356
}
357+
358+
[TestMethod()]
359+
public void RemoveTracedChanges_RemovesChanges_WhenChangesAvailable()
360+
{
361+
// Arrange
362+
var manager = new TraceChangeManager(TestHelper.CreateLogger<TraceChangeManager>());
363+
var cuesheet1 = new Cuesheet();
364+
var cuesheet2 = new Cuesheet();
365+
manager.TraceChanges(cuesheet1);
366+
manager.TraceChanges(cuesheet2);
367+
cuesheet1.Artist = "Test Artist Cuesheet 1";
368+
cuesheet1.Title = "Test Title Cuesheet 1";
369+
cuesheet2.CDTextfile = new("CD Testfile.cdt");
370+
cuesheet2.AddSection();
371+
var tracedObjectHistoryChangedFired = false;
372+
manager.TracedObjectHistoryChanged += delegate
373+
{
374+
tracedObjectHistoryChangedFired = true;
375+
};
376+
// Act
377+
manager.RemoveTracedChanges([cuesheet1]);
378+
// Assert
379+
Assert.IsTrue(tracedObjectHistoryChangedFired);
380+
Assert.IsTrue(manager.CanUndo);
381+
manager.Undo();
382+
manager.Undo();
383+
Assert.IsFalse(manager.CanUndo);
384+
}
385+
386+
[TestMethod()]
387+
public void RemoveTracedChanges_RemovesNoChanges_WhenNoChangesAvailable()
388+
{
389+
// Arrange
390+
var manager = new TraceChangeManager(TestHelper.CreateLogger<TraceChangeManager>());
391+
var cuesheet1 = new Cuesheet();
392+
var cuesheet2 = new Cuesheet();
393+
manager.TraceChanges(cuesheet1);
394+
manager.TraceChanges(cuesheet2);
395+
cuesheet2.CDTextfile = new("CD Testfile.cdt");
396+
cuesheet2.AddSection();
397+
var tracedObjectHistoryChangedFired = false;
398+
manager.TracedObjectHistoryChanged += delegate
399+
{
400+
tracedObjectHistoryChangedFired = true;
401+
};
402+
// Act
403+
manager.RemoveTracedChanges([cuesheet1]);
404+
// Assert
405+
Assert.IsTrue(tracedObjectHistoryChangedFired);
406+
Assert.IsTrue(manager.CanUndo);
407+
manager.Undo();
408+
manager.Undo();
409+
Assert.IsFalse(manager.CanUndo);
410+
}
357411
}
358412
}

AudioCuesheetEditor/Services/IO/ImportManager.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using AudioCuesheetEditor.Model.IO;
1919
using AudioCuesheetEditor.Model.IO.Audio;
2020
using AudioCuesheetEditor.Model.IO.Import;
21+
using AudioCuesheetEditor.Model.UI;
2122
using AudioCuesheetEditor.Services.UI;
2223
using Microsoft.AspNetCore.Components.Forms;
2324
using System.Diagnostics;
@@ -88,9 +89,10 @@ public async Task ImportFilesAsync(IEnumerable<IBrowserFile> files)
8889
stopwatch.Stop();
8990
_logger.LogDebug("ImportFilesAsync duration: {stopwatch.Elapsed}", stopwatch.Elapsed);
9091
}
91-
92+
9293
public async Task AnalyseImportfile()
9394
{
95+
ResetTracing();
9496
var stopwatch = Stopwatch.StartNew();
9597
var fileContent = _sessionStateContainer.Importfile?.FileContent;
9698
if (String.IsNullOrEmpty(fileContent) == false)
@@ -116,6 +118,7 @@ public async Task AnalyseImportfile()
116118
var importCuesheet = new Cuesheet();
117119
CopyCuesheet(importCuesheet, _sessionStateContainer.Importfile.AnalyzedCuesheet);
118120
_sessionStateContainer.ImportCuesheet = importCuesheet;
121+
StartTracing();
119122
break;
120123
case ImportFileType.Cuesheet:
121124
_traceChangeManager.BulkEdit = true;
@@ -127,10 +130,11 @@ public async Task AnalyseImportfile()
127130
stopwatch.Stop();
128131
_logger.LogDebug("ImportTextAsync duration: {stopwatch.Elapsed}", stopwatch.Elapsed);
129132
}
130-
133+
131134
public void ImportCuesheet()
132135
{
133136
var stopwatch = Stopwatch.StartNew();
137+
ResetTracing();
134138
if (_sessionStateContainer.ImportCuesheet != null)
135139
{
136140
_traceChangeManager.BulkEdit = true;
@@ -217,5 +221,25 @@ private static void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy)
217221
}
218222
target.IsImporting = false;
219223
}
224+
225+
private void StartTracing()
226+
{
227+
if (_sessionStateContainer.ImportCuesheet != null)
228+
{
229+
_traceChangeManager.TraceChanges(_sessionStateContainer.ImportCuesheet);
230+
foreach (var track in _sessionStateContainer.ImportCuesheet.Tracks)
231+
{
232+
_traceChangeManager.TraceChanges(track);
233+
}
234+
}
235+
}
236+
237+
private void ResetTracing()
238+
{
239+
if (_sessionStateContainer.ImportCuesheet != null)
240+
{
241+
_traceChangeManager.RemoveTracedChanges([_sessionStateContainer.ImportCuesheet, .. _sessionStateContainer.ImportCuesheet.Tracks]);
242+
}
243+
}
220244
}
221245
}

AudioCuesheetEditor/Services/UI/ITraceChangeManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ public interface ITraceChangeManager
2424
public void Undo();
2525
public void Redo();
2626
public void MergeLastEditWithEdit(Func<TracedChanges, bool> targetEdit);
27+
public void RemoveTracedChanges(IEnumerable<ITraceable> traceables);
2728
}
2829
}

AudioCuesheetEditor/Services/UI/TraceChangeManager.cs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ public class TraceChangeManager(ILogger<TraceChangeManager> logger) : ITraceChan
5050
{
5151
private readonly ILogger<TraceChangeManager> _logger = logger;
5252

53-
private readonly Stack<TracedChanges> undoStack = new();
54-
private readonly Stack<TracedChanges> redoStack = new();
53+
private readonly List<TracedChanges> undoStack = [];
54+
private readonly List<TracedChanges> redoStack = [];
5555

5656
private List<TracedChange>? bulkEditTracedChanges;
5757

@@ -84,8 +84,8 @@ public void TraceChanges(ITraceable traceable)
8484

8585
public void Reset()
8686
{
87-
ResetStack(redoStack);
88-
ResetStack(undoStack);
87+
Reset(redoStack);
88+
Reset(undoStack);
8989
}
9090

9191
public void Undo()
@@ -97,7 +97,8 @@ public void Undo()
9797
TracedChanges? changes = null;
9898
while (undoStack.Count > 0 && changes == null)
9999
{
100-
changes = undoStack.Pop();
100+
changes = undoStack[^1];
101+
undoStack.Remove(changes);
101102
if (changes.HasTraceableObject == false)
102103
{
103104
changes = null;
@@ -133,7 +134,7 @@ public void Undo()
133134
}
134135
}
135136
//Push the old value to redo stack
136-
redoStack.Push(new TracedChanges(redoChanges));
137+
redoStack.Add(new TracedChanges(redoChanges));
137138
}
138139
CurrentlyHandlingRedoOrUndoChanges = false;
139140
UndoDone?.Invoke(this, EventArgs.Empty);
@@ -149,7 +150,8 @@ public void Redo()
149150
TracedChanges? changes = null;
150151
while (redoStack.Count > 0 && changes == null)
151152
{
152-
changes = redoStack.Pop();
153+
changes = redoStack[^1];
154+
redoStack.Remove(changes);
153155
if (changes.HasTraceableObject == false)
154156
{
155157
changes = null;
@@ -185,7 +187,7 @@ public void Redo()
185187
}
186188
}
187189
//Push the old value to redo stack
188-
undoStack.Push(new TracedChanges(undoChanges));
190+
undoStack.Add(new TracedChanges(undoChanges));
189191
}
190192
CurrentlyHandlingRedoOrUndoChanges = false;
191193
RedoDone?.Invoke(this, EventArgs.Empty);
@@ -206,7 +208,7 @@ public bool BulkEdit
206208
{
207209
if (bulkEditTracedChanges != null)
208210
{
209-
undoStack.Push(new TracedChanges(bulkEditTracedChanges));
211+
undoStack.Add(new TracedChanges(bulkEditTracedChanges));
210212
TracedObjectHistoryChanged?.Invoke(this, EventArgs.Empty);
211213
bulkEditTracedChanges = null;
212214
}
@@ -219,17 +221,28 @@ public void MergeLastEditWithEdit(Func<TracedChanges, bool> targetEdit)
219221
var edit = undoStack.FirstOrDefault(targetEdit);
220222
if ((edit != null) && (undoStack.Count > 0))
221223
{
222-
var lastEdits = undoStack.Pop();
224+
var lastEdits = undoStack[^1];
225+
undoStack.Remove(lastEdits);
223226
edit.Changes.AddRange(lastEdits.Changes);
224227
UndoDone?.Invoke(this, EventArgs.Empty);
225228
}
226229
}
227230

228-
private void ResetStack(Stack<TracedChanges> stack)
231+
public void RemoveTracedChanges(IEnumerable<ITraceable> traceables)
232+
{
233+
undoStack.RemoveAll(x => x.HasTraceableObject == false);
234+
undoStack.RemoveAll(x => x.Changes.Any(y => traceables.Contains(y.TraceableObject)));
235+
redoStack.RemoveAll(x => x.HasTraceableObject == false);
236+
redoStack.RemoveAll(x => x.Changes.Any(y => traceables.Contains(y.TraceableObject)));
237+
TracedObjectHistoryChanged?.Invoke(this, EventArgs.Empty);
238+
}
239+
240+
private void Reset(List<TracedChanges> stack)
229241
{
230242
while (stack.Count > 0)
231243
{
232-
var tracedChange = stack.Pop();
244+
var tracedChange = stack[^1];
245+
stack.Remove(tracedChange);
233246
foreach (var change in tracedChange.Changes)
234247
{
235248
if (change.TraceableObject != null)
@@ -254,7 +267,7 @@ private void Traceable_TraceablePropertyChanged(object? sender, TraceablePropert
254267
{
255268
//Single change
256269
var changes = new TracedChanges([new((ITraceable)sender, e.TraceableChange)]);
257-
undoStack.Push(changes);
270+
undoStack.Add(changes);
258271
redoStack.Clear();
259272
TracedObjectHistoryChanged?.Invoke(this, EventArgs.Empty);
260273
}

0 commit comments

Comments
 (0)