From 6af77ad90918d34a8741e56d9f0840a1be346db2 Mon Sep 17 00:00:00 2001 From: GoldenCrystal Date: Sat, 25 Oct 2014 01:15:03 +0200 Subject: [PATCH] Updated the tool to inspect a whole string instead of a mere character. --- .../CharacterInspectorViewModel.cs | 187 ++++++++++++++++++ UnicodeCharacterInspector/MainWindow.xaml | 29 ++- .../UnicodeCharacterInspector.csproj | 1 + 3 files changed, 201 insertions(+), 16 deletions(-) create mode 100644 UnicodeCharacterInspector/CharacterInspectorViewModel.cs diff --git a/UnicodeCharacterInspector/CharacterInspectorViewModel.cs b/UnicodeCharacterInspector/CharacterInspectorViewModel.cs new file mode 100644 index 0000000..6bb8e0f --- /dev/null +++ b/UnicodeCharacterInspector/CharacterInspectorViewModel.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnicodeCharacterInspector +{ + internal sealed class CharacterInspectorViewModel : BindableObject + { + private class CharacterCollection : INotifyCollectionChanged, IList + { + public event NotifyCollectionChangedEventHandler CollectionChanged + { + add { owner.CollectionChanged += value; } + remove { owner.CollectionChanged -= value; } + } + + private CharacterInspectorViewModel owner; + + internal CharacterCollection(CharacterInspectorViewModel owner) + { + this.owner = owner; + } + + public string this[int index] + { + get + { + if (index < 0 || index >= owner.characterCount) throw new IndexOutOfRangeException(); + + return owner.characters[index]; + } + } + + string IList.this[int index] + { + get { return this[index]; } + set { throw new NotSupportedException(); } + } + + public int Count { get { return owner.characterCount; } } + + bool ICollection.IsReadOnly { get { return true; } } + + void ICollection.Add(string item) { throw new NotSupportedException(); } + void IList.Insert(int index, string item) { throw new NotSupportedException(); } + + bool ICollection.Remove(string item) { throw new NotSupportedException(); } + void IList.RemoveAt(int index) { throw new NotSupportedException(); } + + void ICollection.Clear() { throw new NotSupportedException(); } + + public int IndexOf(string item) { return Array.IndexOf(owner.characters, item); } + public bool Contains(string item) { return Array.IndexOf(owner.characters, item) >= 0; } + + public void CopyTo(string[] array, int arrayIndex) { owner.characters.CopyTo(array, arrayIndex); } + + public IEnumerator GetEnumerator() + { + int length = owner.characterCount; + var array = owner.characters; + + for (int i = 0; i < length; ++i) + yield return array[i]; + } + + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + } + + private event NotifyCollectionChangedEventHandler CollectionChanged; + + private string text; + private int selectedCharacterIndex = -1; + private string[] characters = new string[1024]; + private int characterCount; + private CharacterInfoViewModel selectedCharacterInfo = new CharacterInfoViewModel(); + private readonly CharacterCollection characterCollection; + + public CharacterInspectorViewModel() + { + characterCollection = new CharacterCollection(this); + } + + private void NotifyCollectionChanged(NotifyCollectionChangedAction action) + { + var handler = CollectionChanged; + + if (handler != null) + handler(this, new NotifyCollectionChangedEventArgs(action)); + } + + public string Text + { + get { return text; } + set + { + // Analyze the string to detect if there are any similarities between the new and old values. + bool keepSelectedIndex = value != null + && text != null + && + ( + value.Length <= text.Length ? + text.StartsWith(value, StringComparison.Ordinal) : + value.StartsWith(text, StringComparison.Ordinal) + ); + + // Here, we can deduce the information on string equality and simply return if needed. + if (keepSelectedIndex && value.Length == text.Length) return; + + // We don't need the old value anymore. + text = value; + + // We always need to reparse the text, as it is not possible to directly map between UTF-16 elements and their respective code point indexes. + int index = 0; + if (!string.IsNullOrEmpty(value)) + { + var enumerator = StringInfo.GetTextElementEnumerator(value); + + while (enumerator.MoveNext()) + { + if (index >= characters.Length) Array.Resize(ref characters, characters.Length * 2); + + // We don't replace pre-existing elements if they already have the correct value. + // This should help a bit with GC, by making new string elements as short-lived as possible. + string element = enumerator.GetTextElement(); + if (characters[index] != element) characters[index] = element; + + ++index; + } + } + characterCount = index; + + // Now that we parsed the text, we really know whether the previous selected index should be left untouched. + if (!(keepSelectedIndex = keepSelectedIndex && selectedCharacterIndex < characterCount || selectedCharacterIndex == -1)) selectedCharacterIndex = -1; + + // After all this, raise the change notifications as needed. 😊 + NotifyPropertyChanged(); + NotifyCollectionChanged(NotifyCollectionChangedAction.Reset); + if (!keepSelectedIndex) + { + NotifyPropertyChanged("SelectedCharacterIndex"); + NotifyPropertyChanged("SelectedCharacter"); + selectedCharacterInfo.Character = null; + } + } + } + + public ICollection Characters { get { return characterCollection; } } + + public int SelectedCharacterIndex + { + get { return selectedCharacterIndex; } + set + { + if (selectedCharacterIndex < -1 || selectedCharacterIndex >= characters.Length) + throw new ArgumentOutOfRangeException("value"); + + if (value != selectedCharacterIndex) + { + string oldSelectedCharacter = SelectedCharacter; + + selectedCharacterIndex = value; + + string newSelectedCharacter = SelectedCharacter; + + NotifyPropertyChanged(); + if (newSelectedCharacter != oldSelectedCharacter) NotifyPropertyChanged("SelectedCharacter"); + selectedCharacterInfo.Character = newSelectedCharacter; + } + } + } + + public string SelectedCharacter + { + get { return selectedCharacterIndex >= 0 ? characters[selectedCharacterIndex] : null; } + } + + public CharacterInfoViewModel SelectedCharacterInfo + { + get { return selectedCharacterInfo; } + } + } +} diff --git a/UnicodeCharacterInspector/MainWindow.xaml b/UnicodeCharacterInspector/MainWindow.xaml index 0a7ee86..275fc8b 100644 --- a/UnicodeCharacterInspector/MainWindow.xaml +++ b/UnicodeCharacterInspector/MainWindow.xaml @@ -6,11 +6,11 @@ xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:UnicodeCharacterInspector" mc:Ignorable="d" - Title="MainWindow" + Title="Unicode character inspector" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Height="350" Width="525"> - + @@ -23,6 +23,9 @@ + @@ -34,20 +37,14 @@ + - - - - - - - - - - - diff --git a/UnicodeCharacterInspector/UnicodeCharacterInspector.csproj b/UnicodeCharacterInspector/UnicodeCharacterInspector.csproj index 3fdfd84..870d56d 100644 --- a/UnicodeCharacterInspector/UnicodeCharacterInspector.csproj +++ b/UnicodeCharacterInspector/UnicodeCharacterInspector.csproj @@ -63,6 +63,7 @@ + MainWindow.xaml Code