implemented newline sequence handling in ConsoleView,

removed some unnecessary code,
minor changes in UI
master
Jonas Arnold 3 years ago
parent e9a5424d1a
commit d86f4f106f
  1. 175
      MultiTerm.Core/ViewModel/ConsoleViewModel.cs
  2. 3
      MultiTerm.Core/ViewModel/ShellViewModel.cs
  3. 114
      MultiTerm.Wpf/View/ShellView.xaml

@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging;
using MultiTerm.Core.Model;
using MultiTerm.Core.Types;
using MultiTerm.Protocols.Model;
using System.Collections.ObjectModel;
using System.Text;
namespace MultiTerm.Core.ViewModel;
@ -16,11 +17,17 @@ public partial class ConsoleViewModel : TerminalViewModel
private readonly ILogger logger;
private Encoding currentEncoding;
private List<char>? listOfPrevCharacters;
/* settings */
private readonly Encoding defaultEncoding = Encoding.ASCII; // default encoding instance
private const EncodingType defaultEncodingType = EncodingType.ASCII; // default encoding type
private const int DataMaxLength = 5000; // maximum amount of characters stored int the data textbox
private const int DataMaxLength = 20000; // limit of maximum shown characters
/// <summary>
/// Newline Sequence string inserted on every newline in the <see cref="Data"/>.
/// </summary>
public static readonly string NewlineSequence = Environment.NewLine;
#region Observable Properties
/// <summary>
@ -53,7 +60,9 @@ public partial class ConsoleViewModel : TerminalViewModel
// extract bytes
var bytes = newData.Select(b => b.Byte).ToArray();
string newDataDecoded = currentEncoding.GetString(bytes);
// calculate new legth
// replace newlines
newDataDecoded = ReplaceNewlineCharacters(newDataDecoded, ref this.listOfPrevCharacters, this.DataDisplayNewlineSeparatorType, currentEncoding);
// calculate new lnegth
int newTotalLength = this.Data.Length + newDataDecoded.Length;
// trim current data at the beginning if new length is too long
if (newTotalLength > DataMaxLength)
@ -109,6 +118,158 @@ public partial class ConsoleViewModel : TerminalViewModel
this.currentEncoding = this.GetEncoding(value);
}
#region Newline Insertion
/// <summary>
/// Returns if the <paramref name="character"/> is a known newline characters.
/// </summary>
/// <param name="character">character to test if it is a known newline characters</param>
/// <returns>true if the character is a newline character</returns>
private static bool IsKnownNewlineCharacter(char character)
{
// must be extended if new newline characters shall be detected!
const string knownNewlineCharacters = "\n\r";
return knownNewlineCharacters.Contains(character);
}
/// <summary>
/// Searches <paramref name="text"/> for known Newline characters (using <see cref="IsKnownNewlineCharacter(char)"/>. Removes all known Newline characters from the text.
/// If the newline sequence according to <paramref name="newlineSeparatorType"/> is found, the configured <see cref="NewlineSequence"/> is introduced.
/// </summary>
/// <param name="text">text to search for newline characters</param>
/// <param name="previousCharacters">list of previous characters, managed by this method. to identify newline sequences over multiple calls of this method</param>
/// <param name="newlineSeparatorType">separator type</param>
/// <param name="encoding">encoding to use</param>
/// <returns>string with replaced newline characters</returns>
private static string ReplaceNewlineCharacters(
string text,
ref List<char>? previousCharacters,
NewlineSeparatorType newlineSeparatorType,
Encoding encoding)
{
StringBuilder stringBuilder = new();
// go through characters
foreach (char character in text)
{
// add to collection if the character is not a known newline character
if (IsKnownNewlineCharacter(character) == false)
{
stringBuilder.Append(character);
}
// check if a newline should be introduced after this character
switch (ShouldIntroduceNewlineAfterThisCharacter(character, previousCharacters, newlineSeparatorType, encoding))
{
case ShouldIntroduceNewlineAfterThisCharacterResult.NoNewline:
// a decision could be made, previousCharacters can be cleared
if (previousCharacters != null) { previousCharacters = null; }
// nothing to do
break;
case ShouldIntroduceNewlineAfterThisCharacterResult.IntroduceNewline:
// a decision could be made, previousCharacters can be cleared
if (previousCharacters != null) { previousCharacters = null; }
// append line in string
stringBuilder.Append(NewlineSequence);
break;
case ShouldIntroduceNewlineAfterThisCharacterResult.RequiresMoreCharacters:
// first time that more characters are required => create new list
previousCharacters ??= new List<char>();
// add current character to list
previousCharacters.Add(character);
break;
default:
throw new Exception($"'{nameof(ReplaceNewlineCharacters)}()' failed because of error when checking if a newline should be introduced.");
}
}
return stringBuilder.ToString();
}
/// <summary>
/// Result type for <see cref="ShouldIntroduceNewlineAfterThisCharacter"/>
/// </summary>
public enum ShouldIntroduceNewlineAfterThisCharacterResult
{
/// <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="character"/>.
/// Since some newline sequences will require multiple characters in correct order, a more complex handling is required, which is possible using this function.
/// </summary>
/// <param name="character">the current character in the collection</param>
/// <param name="previousCharacters">list of previous character, newest at the last position of the list, null if not required</param>
/// <param name="newlineSeparatorType">separator type</param>
/// <param name="encoding">encoding to be used to check if the characters are equal</param>
/// <returns>enum result of type <see cref="ShouldIntroduceNewlineAfterThisCharacterResult"/></returns>
/// <exception cref="NotImplementedException">if the handling for the <paramref name="newlineSeparatorType"/> is not implemented</exception>
private static ShouldIntroduceNewlineAfterThisCharacterResult ShouldIntroduceNewlineAfterThisCharacter(
char character,
List<char>? previousCharacters,
NewlineSeparatorType newlineSeparatorType,
Encoding encoding)
{
var result = ShouldIntroduceNewlineAfterThisCharacterResult.NoNewline;
switch (newlineSeparatorType)
{
case NewlineSeparatorType.None:
break;
case NewlineSeparatorType.CR:
if (EncodedCharacterEqualsAsciiCharacter(character, '\r', encoding))
{
result = ShouldIntroduceNewlineAfterThisCharacterResult.IntroduceNewline;
}
break;
case NewlineSeparatorType.LF:
if (EncodedCharacterEqualsAsciiCharacter(character, '\n', encoding))
{
result = ShouldIntroduceNewlineAfterThisCharacterResult.IntroduceNewline;
}
break;
case NewlineSeparatorType.CR_LF:
if (EncodedCharacterEqualsAsciiCharacter(character, '\r', encoding))
{
result = ShouldIntroduceNewlineAfterThisCharacterResult.RequiresMoreCharacters;
}
if (EncodedCharacterEqualsAsciiCharacter(character, '\n', encoding))
{
if (previousCharacters != null && EncodedCharacterEqualsAsciiCharacter(previousCharacters.Last(), '\r', encoding))
{
result = ShouldIntroduceNewlineAfterThisCharacterResult.IntroduceNewline;
}
}
break;
default:
throw new NotImplementedException($"'{nameof(ShouldIntroduceNewlineAfterThisCharacter)}()' does not implement handling for {nameof(NewlineSeparatorType)} {newlineSeparatorType}");
}
return result;
}
#endregion
#region Unused Methods
public override void DisplayNewSentData(IEnumerable<ExtendedByte> newData)
{
@ -155,5 +316,15 @@ public partial class ConsoleViewModel : TerminalViewModel
return encoding!;
}
/// <summary>
/// Returns wether the <paramref name="encodedChar"/> equals the <paramref name="asciiChar"/>.
/// <paramref name="encodedChar"/> must be encoded with <paramref name="encoding"/>.
/// </summary>
/// <returns>true if equal</returns>
private static bool EncodedCharacterEqualsAsciiCharacter(char encodedChar, char asciiChar, Encoding encoding)
{
return encodedChar.ToString() == encoding.GetString(Encoding.ASCII.GetBytes(new char[] { asciiChar }));
}
#endregion
}

@ -71,9 +71,6 @@ public partial class ShellViewModel : ObservableObject, IRecipient<IUserInterfac
// register to messages
this.messenger.Register(this);
// TEMP Init
//this.AppendTerminalWithSelectedViewType(ProtocolType.Serial);
}
/// <summary>

@ -263,7 +263,7 @@
<!-- Start Menu -->
<Border Grid.Row="1" Grid.Column="1" Width="400" Height="300"
BorderThickness="1" BorderBrush="Black" CornerRadius="10"
BorderThickness="1" BorderBrush="Gray" CornerRadius="10"
Panel.ZIndex="1">
<Border.Style>
<Style TargetType="{x:Type Border}">
@ -287,63 +287,65 @@
TextWrapping="Wrap"/>
</StackPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<!-- Left side-->
<StackPanel Orientation="Vertical" DockPanel.Dock="Left" VerticalAlignment="Center" Margin="40 10">
<Label Content="View type:"/>
<!-- Terminal View Types as ListBox, styled as RadioButton -->
<ListBox BorderBrush="Transparent"
ItemsSource="{Binding Source={StaticResource TerminalViewTypeValues}, Converter={StaticResource EnumValToDescConv}}"
SelectedItem="{Binding SelectedTerminalViewType, Mode=TwoWay, Converter={StaticResource EnumValToDescConv}}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}"
IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
</StackPanel>
<StackPanel Orientation="Vertical" VerticalAlignment="Top" Margin="40 10">
<Label Content="View type:"/>
<!-- Terminal View Types as ListBox, styled as RadioButton -->
<ListBox BorderBrush="Transparent"
ItemsSource="{Binding Source={StaticResource TerminalViewTypeValues}, Converter={StaticResource EnumValToDescConv}}"
SelectedItem="{Binding SelectedTerminalViewType, Mode=TwoWay, Converter={StaticResource EnumValToDescConv}}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}"
IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
</StackPanel>
<!-- Right side-->
<StackPanel Orientation="Vertical" DockPanel.Dock="Right" VerticalAlignment="Center" Margin="40 10">
<Label Content="Protocol type:"/>
<!-- ListBox with Protocol types-->
<ListBox BorderBrush="Transparent"
ItemsSource="{Binding Source={StaticResource ProtocolTypeValues}}"
SelectionMode="Single"
x:Name="listBoxStartMenuProtocols">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Button Margin="0 2" Width="Auto" HorizontalContentAlignment="Stretch"
Command="{Binding Path=Data.AppendTerminalWithSelectedViewTypeCommand, Source={StaticResource proxy}}"
CommandParameter="{Binding Path=.}">
<DockPanel LastChildFill="False">
<Image DockPanel.Dock="Left"
Height="17" Stretch="Fill" Margin="5 0 0 0"
Source="{Binding Path=., Converter={StaticResource ProtocolTypeIconConverter}}"
ToolTip="{Binding Path=., Converter={StaticResource EnumValToDescConv}}"
ToolTipService.InitialShowDelay="200"/>
<TextBlock DockPanel.Dock="Left" Margin="10 0 10 0"
Text="{Binding Path=., Converter={StaticResource EnumValToDescConv}}"/>
</DockPanel>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
<!-- Right side-->
<StackPanel Orientation="Vertical" VerticalAlignment="Top" Margin="40 10">
<Label Content="Protocol:"/>
<!-- ListBox with Protocol types-->
<ListBox BorderBrush="Transparent"
ItemsSource="{Binding Source={StaticResource ProtocolTypeValues}}"
SelectionMode="Single"
x:Name="listBoxStartMenuProtocols">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Button Margin="0 2" Width="Auto" HorizontalContentAlignment="Stretch"
Command="{Binding Path=Data.AppendTerminalWithSelectedViewTypeCommand, Source={StaticResource proxy}}"
CommandParameter="{Binding Path=.}">
<DockPanel LastChildFill="False">
<Image DockPanel.Dock="Left"
Height="17" Stretch="Fill" Margin="5 0 0 0"
Source="{Binding Path=., Converter={StaticResource ProtocolTypeIconConverter}, FallbackValue={x:Null}}"
ToolTip="{Binding Path=., Converter={StaticResource EnumValToDescConv}}"
ToolTipService.InitialShowDelay="200"/>
<TextBlock DockPanel.Dock="Left" Margin="10 0 10 0"
Text="{Binding Path=., Converter={StaticResource EnumValToDescConv}}"/>
</DockPanel>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
</ListBox>
</StackPanel>
</StackPanel>
</DockPanel>
</Border>

Loading…
Cancel
Save