Multiprocotol Terminalprogram (BAT)
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.

458 lines
16 KiB

using MultiTerm.Core.Model;
using MultiTerm.Core.Types;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace MultiTerm.Wpf.CustomControl;
public class MultiFormatTextBox : Control
{
#region static content
private static readonly Brush defaultBackgroundBrush = Brushes.White;
private static readonly List<Format> formats = new()
{
// character input
new Format("CHAR", "MultiFormatTextBox.CHAR.Background", FormatType.Character),
// hex input
new Format("HEX", "MultiFormatTextBox.HEX.Background", FormatType.Hexadecimal),
// binary input
new Format("BIN", "MultiFormatTextBox.BIN.Background", FormatType.Binary)
};
#endregion
#region private content
private const string comboBoxTemplateKey = "PART_ComboBox";
private const string richTextBoxTemplateKey = "PART_RichTextBox";
private ComboBox? comboBox;
private RichTextBox? richTextBox;
private Format? currentlySelectedFormat;
private readonly object lockObj = new();
#endregion
#region Dependency Properties
public static readonly DependencyProperty CurrentMultiFormatStringProperty =
DependencyProperty.Register(
name: "CurrentMultiFormatString",
propertyType: typeof(MultiFormatString),
ownerType: typeof(MultiFormatTextBox),
typeMetadata: new FrameworkPropertyMetadata(null, OnCurrentMultiFormatStringChanged));
public static readonly RoutedEvent EnterPressedEvent;
public static readonly RoutedEvent ArrowUpPressedEvent;
public static readonly RoutedEvent ArrowDownPressedEvent;
/// <summary>
/// .NET Property for <see cref="EnterPressedEvent"/>
/// </summary>
public event RoutedEventHandler EnterPressed
{
add { this.AddHandler(EnterPressedEvent, value); }
remove { this.RemoveHandler(EnterPressedEvent, value); }
}
/// <summary>
/// .NET Property for <see cref="ArrowUpPressedEvent"/>
/// </summary>
public event RoutedEventHandler ArrowUpPressed
{
add { this.AddHandler(ArrowUpPressedEvent, value); }
remove { this.RemoveHandler(ArrowUpPressedEvent, value); }
}
/// <summary>
/// .NET Property for <see cref="ArrowDownPressedEvent"/>
/// </summary>
public event RoutedEventHandler ArrowDownPressed
{
add { this.AddHandler(ArrowDownPressedEvent, value); }
remove { this.RemoveHandler(ArrowDownPressedEvent, value); }
}
/// <summary>
/// .NET Property for CurrentMultiFormatString.
/// </summary>
[Bindable(true)]
public MultiFormatString CurrentMultiFormatString
{
get { return (MultiFormatString)GetValue(CurrentMultiFormatStringProperty); }
set { SetValue(CurrentMultiFormatStringProperty, value); }
}
#endregion
static MultiFormatTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiFormatTextBox), new FrameworkPropertyMetadata(typeof(MultiFormatTextBox)));
EnterPressedEvent = EventManager.RegisterRoutedEvent("EnterPressed",
RoutingStrategy.Bubble, typeof(RoutedEventArgs),
typeof(MultiFormatTextBox));
ArrowUpPressedEvent = EventManager.RegisterRoutedEvent("ArrowUpPressed",
RoutingStrategy.Bubble, typeof(RoutedEventArgs),
typeof(MultiFormatTextBox));
ArrowDownPressedEvent = EventManager.RegisterRoutedEvent("ArrowDownPressed",
RoutingStrategy.Bubble, typeof(RoutedEventArgs),
typeof(MultiFormatTextBox));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// initialize format background brushes
Format.UpdateBackgroundBrushesFromResources(this, formats);
// set initially selected format
this.currentlySelectedFormat = formats.First();
// get comboBox from template
if (GetTemplateChild(comboBoxTemplateKey) is ComboBox comboBox)
{
this.comboBox = comboBox;
this.comboBox.ItemsSource = Format.GetListOfNames(formats);
this.comboBox.SelectedItem = currentlySelectedFormat.Name;
this.comboBox.SelectionChanged += ComboBox_SelectionChanged;
}
else
{
throw new Exception($"Implementation fault, {comboBoxTemplateKey} not found in template.");
}
// get richTextBox from template
if (GetTemplateChild(richTextBoxTemplateKey) is RichTextBox richTextBox)
{
this.richTextBox = richTextBox;
this.richTextBox.AcceptsReturn = false;
this.richTextBox.AcceptsTab = false;
this.richTextBox.PreviewKeyDown += RichTextBox_KeyDown;
this.richTextBox.PreviewTextInput += RichTextBox_PreviewTextInput;
this.richTextBox.SelectionChanged += RichTextBox_SelectionChanged;
DataObject.AddPastingHandler(this.richTextBox, OnRtbPaste);
}
else
{
throw new Exception($"Implementation fault, {richTextBoxTemplateKey} not found in template.");
}
}
private static void OnCurrentMultiFormatStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// extract instance and guard null
if (d is not MultiFormatTextBox mftb) { return; }
// register to collection changed event
if (mftb.CurrentMultiFormatString is INotifyCollectionChanged incc)
{
// remove handler
incc.CollectionChanged -= mftb.MultiFormatString_CollectionChanged;
// add new handler if not null
if(e.NewValue != null)
{
incc.CollectionChanged += mftb.MultiFormatString_CollectionChanged;
}
}
// reset textbox and insert items if there are any
mftb.ResetTextBox();
if(mftb.CurrentMultiFormatString.Count > 0)
{
mftb.AddItemsToTextBox(mftb.CurrentMultiFormatString, 0);
}
}
/// <summary>
/// Reacts on Collection Changed Events.
/// </summary>
private void MultiFormatString_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
// guard null
if(e.NewItems == null) { return; }
// add list to textbox
this.AddItemsToTextBox(e.NewItems, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
// guard null
if (e.OldItems == null) { return; }
// remove list to textbox
this.RemoveItemsFromTextBox(e.OldItems, e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Reset:
// clear richtextbox add new paragraph
this.ResetTextBox();
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
default:
throw new NotImplementedException($"'{nameof(MultiFormatString_CollectionChanged)}()' does not support Action '{e.Action}'");
}
}
private void RemoveItemsFromTextBox(System.Collections.IList items, int startingIndex)
{
// extract paragraph from richtextbox (last block is paragraph per default)
if (this.richTextBox!.Document.Blocks.LastBlock is not Paragraph paragraph)
{
throw new Exception($"'{nameof(RemoveItemsFromTextBox)}()' found that {nameof(richTextBox)} has no paragraph as last block.");
}
if (items.Count > 1)
{
throw new Exception("Not supported");
}
string toRemoveString = string.Empty;
if (items[0] is IFormattedCharacter character)
{
toRemoveString = character.Character;
}
else
{
throw new Exception("Not supported");
}
var inlineToRemove = paragraph.Inlines.ElementAt(new Index(startingIndex)); // paragraph.Inlines = zero based counting
var textInline = new TextRange(inlineToRemove.ContentStart, inlineToRemove.ContentEnd);
if(textInline.Text != toRemoveString)
{
throw new Exception("Tried to remove other element...");
}
paragraph.Inlines.Remove(inlineToRemove);
}
private void AddItemsToTextBox(System.Collections.IList items, int startingIndex)
{
int indexCounter = startingIndex;
// extract paragraph from richtextbox (last block is paragraph per default)
var paragraph = this.richTextBox!.Document.Blocks.LastBlock as Paragraph ?? throw new Exception($"'{nameof(AddItemsToTextBox)}()' found that {nameof(richTextBox)} has no paragraph as last block.");
// iterate through formatted characters to add
foreach (IFormattedCharacter item in items)
{
Run? run = null;
if (item is FormattedCharacter formattedCharacter)
{
// text as run with correct background brush
run = new Run(formattedCharacter.Character)
{
Background = this.GetBackgroundBrushForFormat(formattedCharacter.Format)
};
}
else if(item is SpacingCharacter spacingCharacter)
{
// text as run with default background brush
run = new Run(spacingCharacter.Character)
{
Background = defaultBackgroundBrush
};
}
else
{
throw new Exception($"'{nameof(AddItemsToTextBox)}()' cannot handle items of type {item.GetType()}");
}
// if this is the first element to enter in the paragraph => simply add it
if (paragraph!.Inlines.Count == 0)
{
paragraph.Inlines.Add(run);
}
// if the previous item is outside of the range => insert at first position
else if(indexCounter - 1 < 0)
{
paragraph.Inlines.InsertBefore(paragraph.Inlines.FirstInline, run);
}
else // add to paragraph of richtextbox after previous inline
{
var previousInline = paragraph.Inlines.ElementAt(new Index(indexCounter - 1)); // paragraph.Inlines = zero based counting
paragraph.Inlines.InsertAfter(previousInline, run);
}
// increment counter
indexCounter++;
}
// update caret position to index position
this.SetRtbCaretPosition(indexCounter);
}
/// <summary>
/// Clear richtextbox adds new paragraph.
/// </summary>
private void ResetTextBox()
{
this.richTextBox!.Document.Blocks.Clear();
this.richTextBox!.Document.Blocks.Add(new Paragraph());
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
lock (lockObj) // lock so no richtextbox textchanged event can happen
{
// match all formats with the name selected in the combobox
var matchingFormats = formats.Where(format => format.Name == (string)this.comboBox!.SelectedItem);
// check if exactly one format was matched
if (matchingFormats.Count() != 1)
{
throw new Exception($"{nameof(ComboBox_SelectionChanged)} could not match a correct amount of formats");
}
// set currently selected format, reset offset counter
this.currentlySelectedFormat = matchingFormats.First();
// focus textbox
this.richTextBox!.Focus();
}
}
private void RichTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
e.Handled = true;
// raise event
RoutedEventArgs args = new(EnterPressedEvent);
RaiseEvent(args);
}
else if(e.Key == Key.Up)
{
e.Handled = true;
// raise event
RoutedEventArgs args = new(ArrowUpPressedEvent);
RaiseEvent(args);
}
else if (e.Key == Key.Down)
{
e.Handled = true;
// raise event
RoutedEventArgs args = new(ArrowDownPressedEvent);
RaiseEvent(args);
}
else if(e.Key == Key.Space)
{
e.Handled = true;
// add space to MultiFormatString (will only work if currently character type is selected)
InsertCharacterAtCaretPositionIntoCurrentMultiFormatString(" ");
}
// back one
else if (e.Key == Key.Back)
{
var cursorPos = GetRtbCaretPosition();
// remove previous element if it exists
try
{
if (this.CurrentMultiFormatString.ElementAt(cursorPos - 1) != null)
{
this.CurrentMultiFormatString.RemoveAt(cursorPos - 1);
}
}
catch { }
e.Handled = true;
}
// delete next (selection is not allowed)
else if (e.Key == Key.Delete)
{
var cursorPos = GetRtbCaretPosition();
// remove next element if it exists
try
{
if (this.CurrentMultiFormatString.ElementAt(cursorPos) != null)
{
this.CurrentMultiFormatString.RemoveAt(cursorPos);
}
}
catch { }
e.Handled = true;
}
}
private int GetRtbCaretPosition()
{
// magic dividor of 3.... nobody knows why...
return this.richTextBox!.Document.ContentStart.GetOffsetToPosition(this.richTextBox.CaretPosition) / 3;
}
private void SetRtbCaretPosition(int index)
{
// magic multiplicator of 3.... nobody knows why...
this.richTextBox!.CaretPosition = this.richTextBox.Document.ContentStart.GetPositionAtOffset(index*3);
}
private void RichTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
// text is never directly inserted => therefore always handled
e.Handled = true;
// if more than one char inserted => illegal => cancel
if(e.Text.Length > 1) { return; }
this.InsertCharacterAtCaretPositionIntoCurrentMultiFormatString(e.Text);
}
private void OnRtbPaste(object sender, DataObjectPastingEventArgs e)
{
// get pasted data
if (e.DataObject.GetDataPresent(typeof(string)))
{
// get data as string
var text = (string)e.DataObject.GetData(typeof(string));
// insert each character seperately
foreach (var character in text)
{
this.InsertCharacterAtCaretPositionIntoCurrentMultiFormatString(character.ToString());
}
}
// cancel pasting
e.CancelCommand();
}
private void InsertCharacterAtCaretPositionIntoCurrentMultiFormatString(string text)
{
var caretPosition = this.GetRtbCaretPosition();
// add char to MultiFormatString
FormattedCharacter formattedCharacter = new(this.currentlySelectedFormat!.AssociatedFormatType, text);
this.CurrentMultiFormatString.Insert(caretPosition, formattedCharacter);
//this.CurrentMultiFormatString.Add(formattedCharacter);
}
private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
// do not allow selection
if (this.richTextBox!.Selection.Text.Length > 0)
{
this.richTextBox!.Selection.Select(this.richTextBox.Selection.End, this.richTextBox.Selection.End);
}
}
private Brush GetBackgroundBrushForFormat(FormatType format)
{
var formatObj = formats.Where(f => f.AssociatedFormatType == format).First();
return formatObj.BackgroundBrush;
}
}