You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
14 KiB
349 lines
14 KiB
using Common;
|
|
using Common.Helpers;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using MultiTerm.Core.Types;
|
|
using MultiTerm.Protocols.Model;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
|
|
namespace MultiTerm.Core.ViewModel;
|
|
|
|
public partial class MultiFormatDataViewModel : ObservableObject
|
|
{
|
|
private readonly IContext uiContext;
|
|
private int dataCharacterCount = 0;
|
|
private List<byte>? listOfPreviousCharacters = null;
|
|
|
|
#region Observable Properties
|
|
[ObservableProperty]
|
|
private ObservableCollection<ByteDataViewModel> data = new();
|
|
|
|
[ObservableProperty]
|
|
private string dataAsString = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private ObservableCollection<ByteDataViewModel>? selected = new();
|
|
|
|
[ObservableProperty]
|
|
NewlineSeparatorType newlineSeparator = NewlineSeparatorType.None;
|
|
|
|
[ObservableProperty]
|
|
private string selectedDataFirstAbsoluteTime = string.Empty;
|
|
|
|
[ObservableProperty]
|
|
private string selectedDataTimediff = string.Empty;
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Newline Sequence string inserted on every newline in the <see cref="DataAsString"/>.
|
|
/// </summary>
|
|
public static readonly string NewlineSequence = Environment.NewLine;
|
|
|
|
public MultiFormatDataViewModel(IContext context)
|
|
{
|
|
this.uiContext = context;
|
|
}
|
|
|
|
public void HandleNewData(IEnumerable<ExtendedByte> newRawData)
|
|
{
|
|
// update collection and string, invoke ui thread if necessary
|
|
ContextHelpers.InvokeIfNecessary(this.uiContext, (Action)delegate
|
|
{
|
|
this.DataAsString = InsertNewNewCharactersIntoCollection(this.Data, this.DataAsString,
|
|
ref this.dataCharacterCount, ref this.listOfPreviousCharacters,
|
|
this.NewlineSeparator, newRawData);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// After changing the newline separator, the data needs to be reordered.
|
|
/// </summary>
|
|
partial void OnNewlineSeparatorChanged(NewlineSeparatorType value)
|
|
{
|
|
// update collection, invoke ui thread if necessary
|
|
ContextHelpers.InvokeIfNecessary(this.uiContext, (Action)delegate
|
|
{
|
|
var reorderedCollection = ReorderCollection(this.Data, value);
|
|
this.Data = reorderedCollection.Item1;
|
|
this.DataAsString = reorderedCollection.Item2;
|
|
});
|
|
}
|
|
|
|
[RelayCommand]
|
|
public void Clear()
|
|
{
|
|
// update collection and string, invoke ui thread if necessary
|
|
ContextHelpers.InvokeIfNecessary(this.uiContext, (Action)delegate
|
|
{
|
|
this.DataAsString = string.Empty;
|
|
this.Data.Clear();
|
|
});
|
|
}
|
|
|
|
partial void OnSelectedChanging(ObservableCollection<ByteDataViewModel>? value)
|
|
{
|
|
// remove event handler when there was previously a collection of selected
|
|
if(this.Selected != null)
|
|
{
|
|
this.Selected.CollectionChanged -= this.Selected_CollectionChanged;
|
|
}
|
|
}
|
|
|
|
partial void OnSelectedChanged(ObservableCollection<ByteDataViewModel>? value)
|
|
{
|
|
// add event handler
|
|
if (this.Selected != null)
|
|
{
|
|
this.Selected.CollectionChanged += Selected_CollectionChanged;
|
|
}
|
|
|
|
// manually update
|
|
this.UpdateTimeProperties();
|
|
}
|
|
|
|
private void Selected_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
{
|
|
this.UpdateTimeProperties();
|
|
}
|
|
|
|
private void UpdateTimeProperties()
|
|
{
|
|
if (this.Selected == null || this.Selected.Count == 0)
|
|
{
|
|
this.SelectedDataFirstAbsoluteTime = string.Empty;
|
|
this.SelectedDataTimediff = string.Empty;
|
|
return;
|
|
}
|
|
|
|
if (this.Selected.Count == 1)
|
|
{
|
|
this.SelectedDataFirstAbsoluteTime = $"Time: {this.Selected.First().Time:HH:mm:ss.ff}";
|
|
this.SelectedDataTimediff = string.Empty;
|
|
}
|
|
|
|
if (this.Selected.Count > 1)
|
|
{
|
|
TimeSpan timediff;
|
|
TimeOnly lastSelected = this.Selected.Last().Time, firstSelected = this.Selected.First().Time;
|
|
|
|
Debug.WriteLine($"First time selected: {firstSelected:HH.mm:ss.ff} Ticks: {firstSelected.Ticks}");
|
|
Debug.WriteLine($"Last time selected: {lastSelected:HH.mm:ss.ff} Ticks: {lastSelected.Ticks}");
|
|
|
|
// calculate timedifference according to selection direction
|
|
// (preventive. selection should arrive sorted correctly)
|
|
if (lastSelected > firstSelected)
|
|
{
|
|
timediff = lastSelected - firstSelected;
|
|
}
|
|
else
|
|
{
|
|
timediff = firstSelected - lastSelected;
|
|
}
|
|
|
|
Debug.WriteLine($"Calculated timediff Ticks: {timediff.Ticks}");
|
|
|
|
this.SelectedDataFirstAbsoluteTime = $"Time (first selected): {firstSelected:HH:mm:ss.ff}";
|
|
this.SelectedDataTimediff = $"Difference (first to last): {timediff.TotalMilliseconds:n} ms";
|
|
}
|
|
}
|
|
|
|
#region Collection Manipulation
|
|
/// <summary>
|
|
/// Function that reorders a given collection with item type <see cref="ByteDataViewModel"/> using the given <paramref name="newlineSeparatorType"/>.
|
|
/// Reordered collection is returned, but LineIdentifier is also overwritten in the <paramref name="currentCollection"/> parameter.
|
|
/// </summary>
|
|
/// <param name="currentCollection">items in this collection will be reordered</param>
|
|
/// <param name="newlineSeparatorType">separator between lines</param>
|
|
/// <returns>reordered collection and string</returns>
|
|
private static Tuple<ObservableCollection<ByteDataViewModel>, string> ReorderCollection(ObservableCollection<ByteDataViewModel> currentCollection, NewlineSeparatorType newlineSeparatorType)
|
|
{
|
|
ObservableCollection<ByteDataViewModel> newCollection = new();
|
|
StringBuilder stringBuilder = new();
|
|
|
|
// local vars
|
|
int lineCounter = 0;
|
|
List<byte>? previousBytes = null;
|
|
|
|
// iterate through items
|
|
foreach (ByteDataViewModel item in currentCollection)
|
|
{
|
|
item.LineIdentifier = lineCounter;
|
|
newCollection.Add(item);
|
|
stringBuilder.Append(item.DisplayStringChar);
|
|
|
|
switch (ShouldIntroduceNewlineAfterThisByte(item.Byte, previousBytes, newlineSeparatorType))
|
|
{
|
|
case ShouldIntroduceNewlineAfterThisByteResult.NoNewline:
|
|
// a decision could be made, previousBytes can be cleared
|
|
if (previousBytes != null) { previousBytes = null; }
|
|
// nothing to do
|
|
break;
|
|
|
|
case ShouldIntroduceNewlineAfterThisByteResult.IntroduceNewline:
|
|
// a decision could be made, previousBytes can be cleared
|
|
if (previousBytes != null) { previousBytes = null; }
|
|
// increase line count
|
|
lineCounter++;
|
|
// append line in string
|
|
stringBuilder.Append(NewlineSequence);
|
|
break;
|
|
|
|
case ShouldIntroduceNewlineAfterThisByteResult.RequiresMoreCharacters:
|
|
// first time that more bytes are required => create new list
|
|
previousBytes ??= new List<byte>();
|
|
// add current byte to list
|
|
previousBytes.Add(item.Byte);
|
|
break;
|
|
|
|
default:
|
|
throw new Exception($"'{nameof(ReorderCollection)}()' failed because of error when checking if a newline should be introduced.");
|
|
}
|
|
}
|
|
|
|
return Tuple.Create(newCollection, stringBuilder.ToString());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function that handles a collection of new bytes that should end up in the collection <paramref name="dataCollection"/>.
|
|
/// In case a new line is required, according to the given <paramref name="newlineSeparatorType"/>, it is automatically introduced.
|
|
/// Following parameters need to be referenced and stored outside: <paramref name="collectionLineCounter"/> and <paramref name="previousBytes"/>.
|
|
/// </summary>
|
|
/// <param name="dataCollection">collection to add the bytes to</param>
|
|
/// <param name="dataCollectionAsString">collection as string</param>
|
|
/// <param name="collectionLineCounter">current line count</param>
|
|
/// <param name="previousBytes">list of previous bytes, newest at the last position of the list, null if nothing is stored</param>
|
|
/// <param name="newlineSeparatorType">separator between seperate lines</param>
|
|
/// <param name="newBytes">bytes to add to the <paramref name="dataCollection"/></param>
|
|
/// <returns>string representation of data</returns>
|
|
/// <exception cref="Exception">in case of any error</exception>
|
|
private static string InsertNewNewCharactersIntoCollection(
|
|
ObservableCollection<ByteDataViewModel> dataCollection,
|
|
string dataCollectionAsString,
|
|
ref int collectionLineCounter,
|
|
ref List<byte>? previousBytes,
|
|
NewlineSeparatorType newlineSeparatorType,
|
|
IEnumerable<ExtendedByte> newBytes)
|
|
{
|
|
StringBuilder stringBuilder = new();
|
|
stringBuilder.Append(dataCollectionAsString);
|
|
|
|
// go through every byte
|
|
foreach (ExtendedByte newByte in newBytes)
|
|
{
|
|
// add to collection and string with the current counter, invoking UI context if necssary
|
|
var currentLineCounter = collectionLineCounter;
|
|
dataCollection.Add(new ByteDataViewModel(newByte, currentLineCounter));
|
|
stringBuilder.Append(newByte.ToCharacterString());
|
|
|
|
switch (ShouldIntroduceNewlineAfterThisByte(newByte.Byte, previousBytes, newlineSeparatorType))
|
|
{
|
|
case ShouldIntroduceNewlineAfterThisByteResult.NoNewline:
|
|
// a decision could be made, previousBytes can be cleared
|
|
if (previousBytes != null) { previousBytes = null; }
|
|
// nothing to do
|
|
break;
|
|
|
|
case ShouldIntroduceNewlineAfterThisByteResult.IntroduceNewline:
|
|
// a decision could be made, previousBytes can be cleared
|
|
if (previousBytes != null) { previousBytes = null; }
|
|
// increase line count
|
|
collectionLineCounter++;
|
|
// append line in string
|
|
stringBuilder.Append(NewlineSequence);
|
|
break;
|
|
|
|
case ShouldIntroduceNewlineAfterThisByteResult.RequiresMoreCharacters:
|
|
// first time that more bytes are required => create new list
|
|
previousBytes ??= new List<byte>();
|
|
// add current byte to list
|
|
previousBytes.Add(newByte.Byte);
|
|
break;
|
|
|
|
default:
|
|
throw new Exception($"'{nameof(InsertNewNewCharactersIntoCollection)}()' failed because of error when checking if a newline should be introduced.");
|
|
}
|
|
}
|
|
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Result type for <see cref="ShouldIntroduceNewlineAfterThisByte"/>
|
|
/// </summary>
|
|
public enum ShouldIntroduceNewlineAfterThisByteResult
|
|
{
|
|
/// <summary>
|
|
/// No newline is required.
|
|
/// </summary>
|
|
NoNewline,
|
|
|
|
/// <summary>
|
|
/// A newline shall be introduced after this byte.
|
|
/// </summary>
|
|
IntroduceNewline,
|
|
|
|
/// <summary>
|
|
/// Following characters are required to finalize result wether a newline shall be introduced or not.
|
|
/// </summary>
|
|
RequiresMoreCharacters
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function to check wether a newline shall be introduced after the given <paramref name="dataByte"/>.
|
|
/// Since some newline sequences will require multiple bytes in correct order, a more complex handling is required, which is possible using this function.
|
|
/// </summary>
|
|
/// <param name="dataByte">the current dataByte in the collection (or a single byte)</param>
|
|
/// <param name="previousBytes">list of previous bytes, newest at the last position of the list, null if not required</param>
|
|
/// <param name="newlineSeparatorType">separator type</param>
|
|
/// <returns>enum result of type <see cref="ShouldIntroduceNewlineAfterThisByteResult"/></returns>
|
|
/// <exception cref="NotImplementedException">if the handling for the <paramref name="newlineSeparatorType"/> is not implemented</exception>
|
|
private static ShouldIntroduceNewlineAfterThisByteResult ShouldIntroduceNewlineAfterThisByte(byte dataByte, List<byte>? previousBytes, NewlineSeparatorType newlineSeparatorType)
|
|
{
|
|
var result = ShouldIntroduceNewlineAfterThisByteResult.NoNewline;
|
|
|
|
switch (newlineSeparatorType)
|
|
{
|
|
case NewlineSeparatorType.None:
|
|
break;
|
|
|
|
case NewlineSeparatorType.CR:
|
|
if (dataByte == (byte)'\r')
|
|
{
|
|
result = ShouldIntroduceNewlineAfterThisByteResult.IntroduceNewline;
|
|
}
|
|
break;
|
|
|
|
case NewlineSeparatorType.LF:
|
|
if (dataByte == (byte)'\n')
|
|
{
|
|
result = ShouldIntroduceNewlineAfterThisByteResult.IntroduceNewline;
|
|
}
|
|
break;
|
|
|
|
case NewlineSeparatorType.CR_LF:
|
|
if (dataByte == (byte)'\r')
|
|
{
|
|
result = ShouldIntroduceNewlineAfterThisByteResult.RequiresMoreCharacters;
|
|
}
|
|
if (dataByte == (byte)'\n')
|
|
{
|
|
if (previousBytes != null && previousBytes.Last() == (byte)'\r')
|
|
{
|
|
result = ShouldIntroduceNewlineAfterThisByteResult.IntroduceNewline;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw new NotImplementedException($"'{nameof(ShouldIntroduceNewlineAfterThisByte)}()' does not implement handling for {nameof(NewlineSeparatorType)} {newlineSeparatorType}");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
}
|
|
|