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.

285 lines
11 KiB

using MultiTerm.Core.Model;
using MultiTerm.Core.Types;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
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, accepts all keys
new Format("CHAR", "MultiFormatTextBox.CHAR.Background", FormatType.Character,
delegate(Key k) { return true; }),
// hex input, ignores all keys that are not inbetween 0 and F
new Format("HEX", "MultiFormatTextBox.HEX.Background", FormatType.Hexadecimal,
delegate(Key k) { return (k >= Key.D0 && k <= Key.F); }),
// binary input, ignores all keys except 0 and 1
new Format("BIN", "MultiFormatTextBox.BIN.Background", FormatType.Binary,
delegate(Key k) { return (k == Key.D0 || k == Key.D1); })
};
#endregion
#region private content
private const string comboBoxTemplateKey = "comboBox";
private const string richTextBoxTemplateKey = "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("CurrentMultiFormatString",
typeof(MultiFormatString), typeof(MultiFormatTextBox),
new PropertyMetadata(null, OnCurrentMultiFormatStringChanged));
/// <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)));
}
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.KeyDown += RichTextBox_KeyDown;
this.richTextBox.TextChanged += RichTextBox_TextChanged;
}
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; }
if (e.NewValue is not MultiFormatString newString) { return; }
// new value is an empty string => clear
if(newString.FormatValuePairs.Count == 0)
{
mftb.richTextBox!.Document.Blocks.Clear();
}
}
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();
// insert space to separate formats
this.InsertSeparation();
// focus textbox
this.richTextBox!.Focus();
}
}
private void InsertSeparation()
{
// disable event handler
this.richTextBox!.TextChanged -= RichTextBox_TextChanged;
// insert separator, manually updating caret position because wpf somehow does not do it....
int caretIndexBefore = this.richTextBox.Document.ContentStart.GetOffsetToPosition(this.richTextBox.CaretPosition);
Debug.WriteLine($"caret position before inserting separator: offset={this.richTextBox.Document.ContentStart.GetOffsetToPosition(this.richTextBox.CaretPosition)}");
this.richTextBox.AppendText(" ");
this.richTextBox.CaretPosition = this.richTextBox.Document.ContentStart.GetPositionAtOffset(caretIndexBefore + 1);
Debug.WriteLine($"caret position after inserting separator: offset={this.richTextBox.Document.ContentStart.GetOffsetToPosition(this.richTextBox.CaretPosition)}");
// change background of last one character
RtbChangeTextBackground(this.richTextBox,
start: this.richTextBox!.Document.ContentStart.GetPositionAtOffset(caretIndexBefore),
end: this.richTextBox.CaretPosition,
color: defaultBackgroundBrush);
// reenable
this.richTextBox.TextChanged += RichTextBox_TextChanged;
}
private void RichTextBox_KeyDown(object sender, KeyEventArgs e)
{
// guard combobox null
if (this.comboBox == null) throw new Exception($"{nameof(this.comboBox)} cannot be null");
// if key is invalid for this format => ignore it (handled = true)
if (this.currentlySelectedFormat!.IsKeyValid(e.Key) == false)
{
e.Handled = true;
}
// ignore enter
if(e.Key == Key.Enter)
{
e.Handled = true;
}
}
private void RichTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
// no changes => exit
if (e.Changes.Count == 0) return;
lock (lockObj) // lock so no concurrent combobox change can happen
{
foreach (var change in e.Changes)
{
// print new text
Debug.WriteLine($"{nameof(RichTextBox_TextChanged)} changed to '{new TextRange(this.richTextBox!.Document.ContentStart, this.richTextBox.Document.ContentEnd).Text}'");
Debug.WriteLine($"{nameof(RichTextBox_TextChanged)} caret position '{this.richTextBox!.Document.ContentStart.GetOffsetToPosition(this.richTextBox.CaretPosition)}'");
// ignore changes that replace something
if (change.RemovedLength == change.AddedLength) continue;
// if something was added => change background color
if (change.AddedLength > 0)
{
Debug.WriteLine($"{nameof(RichTextBox_TextChanged)} offset of change = {change.Offset}");
// disable event handler so the update does not trigger any TextChanged events (which it does, interestingly)
this.richTextBox!.TextChanged -= RichTextBox_TextChanged;
// update color
RtbChangeTextBackground(this.richTextBox!,
start: this.richTextBox!.Document.ContentStart.GetPositionAtOffset(change.Offset),
end: this.richTextBox!.Document.ContentStart.GetPositionAtOffset(change.Offset + change.AddedLength),
color: this.currentlySelectedFormat!.BackgroundBrush);
// reenable event handler
this.richTextBox.TextChanged += RichTextBox_TextChanged;
}
}
// update CurrentMultiFormatString
this.CurrentMultiFormatString = ConvertRtbContentToMultiFormatString(this.richTextBox!);
}
}
private static void RtbChangeTextBackground(RichTextBox rtb, TextPointer start, TextPointer end, Brush color)
{
// print offsets to start and end position
Debug.WriteLine($"Changing background of textbox offset {rtb.Document.ContentStart.GetOffsetToPosition(start)} until {rtb.Document.ContentStart.GetOffsetToPosition(end)} to {color}");
// get editable selection
var textRange = rtb.Selection;
textRange.Select(start, end);
// Apply property to the selection:
textRange.ApplyPropertyValue(TextElement.BackgroundProperty, color);
// deselect everything (set to end)
rtb.Selection.Select(end, end);
}
private static MultiFormatString ConvertRtbContentToMultiFormatString(RichTextBox rtb)
{
MultiFormatString multiFormatString = new();
// store current caret position
int offsetToCaretPosition = rtb.Document.ContentStart.GetOffsetToPosition(rtb.CaretPosition);
// get number of symbols
var textLength = rtb.Document.ContentStart.GetOffsetToPosition(rtb.Document.ContentEnd);
// get full content as TextSelection object
var textRange = rtb.Selection;
// loop through every character
for (int offset = 0; offset < textLength; offset++)
{
// select one character
textRange.Select(rtb.Document.ContentStart.GetPositionAtOffset(offset), rtb.Document.ContentStart.GetPositionAtOffset(offset + 1));
//skip empty textRange
if (textRange.IsEmpty)
{ continue; }
// extract background brush
var brush = (Brush)textRange.GetPropertyValue(TextElement.BackgroundProperty);
// ignore this symbol, since its a separator
if (brush == defaultBackgroundBrush)
{ continue; }
// search if it is a brush of a format
foreach (var format in formats)
{
// format has this background brush => add to string with format and text
if (brush == format.BackgroundBrush)
{
multiFormatString.Add(format.AssociatedFormatType, textRange.Text);
break; // end loop
}
}
}
// deselect everything (set to end)
textRange.Select(
rtb.Document.ContentStart.GetPositionAtOffset(offsetToCaretPosition),
rtb.Document.ContentStart.GetPositionAtOffset(offsetToCaretPosition));
return multiFormatString;
}
}