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.
405 lines
14 KiB
405 lines
14 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;
|
|
|
|
/// <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 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));
|
|
}
|
|
|
|
~MultiFormatTextBox()
|
|
{
|
|
// unregister events
|
|
if (this.CurrentMultiFormatString != null && this.CurrentMultiFormatString is INotifyCollectionChanged incc)
|
|
{
|
|
incc.CollectionChanged -= this.MultiFormatString_CollectionChanged;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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.richTextBox!.Document.Blocks.Clear();
|
|
this.richTextBox!.Document.Blocks.Add(new Paragraph());
|
|
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.currentlySelectedFormat!.BackgroundBrush
|
|
};
|
|
}
|
|
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);
|
|
}
|
|
|
|
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.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);
|
|
}
|
|
}
|
|
}
|
|
|