first commit

This commit is contained in:
2026-01-07 11:33:05 +08:00
commit fc54ffd43b
215 changed files with 31856 additions and 0 deletions

View File

@@ -0,0 +1,590 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Text;
using System.Diagnostics;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Util
{
internal sealed class RedBlackTreeNode<T>
{
internal RedBlackTreeNode<T> left, right, parent;
internal T val;
internal bool color;
internal RedBlackTreeNode(T val)
{
this.val = val;
}
internal RedBlackTreeNode<T> LeftMost {
get {
RedBlackTreeNode<T> node = this;
while (node.left != null)
node = node.left;
return node;
}
}
internal RedBlackTreeNode<T> RightMost {
get {
RedBlackTreeNode<T> node = this;
while (node.right != null)
node = node.right;
return node;
}
}
}
internal interface IRedBlackTreeHost<T> : IComparer<T>
{
bool Equals(T a, T b);
void UpdateAfterChildrenChange(RedBlackTreeNode<T> node);
void UpdateAfterRotateLeft(RedBlackTreeNode<T> node);
void UpdateAfterRotateRight(RedBlackTreeNode<T> node);
}
/// <summary>
/// Description of RedBlackTree.
/// </summary>
internal sealed class AugmentableRedBlackTree<T, Host> : ICollection<T> where Host : IRedBlackTreeHost<T>
{
readonly Host host;
int count;
internal RedBlackTreeNode<T> root;
public AugmentableRedBlackTree(Host host)
{
if (host == null) throw new ArgumentNullException("host");
this.host = host;
}
public int Count {
get { return count; }
}
public void Clear()
{
root = null;
count = 0;
}
#region Debugging code
#if DEBUG
/// <summary>
/// Check tree for consistency and being balanced.
/// </summary>
[Conditional("DATACONSISTENCYTEST")]
void CheckProperties()
{
int blackCount = -1;
CheckNodeProperties(root, null, RED, 0, ref blackCount);
int nodeCount = 0;
foreach (T val in this) {
nodeCount++;
}
Debug.Assert(count == nodeCount);
}
/*
1. A node is either red or black.
2. The root is black.
3. All leaves are black. (The leaves are the NIL children.)
4. Both children of every red node are black. (So every red node must have a black parent.)
5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.)
*/
void CheckNodeProperties(RedBlackTreeNode<T> node, RedBlackTreeNode<T> parentNode, bool parentColor, int blackCount, ref int expectedBlackCount)
{
if (node == null) return;
Debug.Assert(node.parent == parentNode);
if (parentColor == RED) {
Debug.Assert(node.color == BLACK);
}
if (node.color == BLACK) {
blackCount++;
}
if (node.left == null && node.right == null) {
// node is a leaf node:
if (expectedBlackCount == -1)
expectedBlackCount = blackCount;
else
Debug.Assert(expectedBlackCount == blackCount);
}
CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount);
CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount);
}
public string GetTreeAsString()
{
StringBuilder b = new StringBuilder();
AppendTreeToString(root, b, 0);
return b.ToString();
}
static void AppendTreeToString(RedBlackTreeNode<T> node, StringBuilder b, int indent)
{
if (node.color == RED)
b.Append("RED ");
else
b.Append("BLACK ");
b.AppendLine(node.val.ToString());
indent += 2;
if (node.left != null) {
b.Append(' ', indent);
b.Append("L: ");
AppendTreeToString(node.left, b, indent);
}
if (node.right != null) {
b.Append(' ', indent);
b.Append("R: ");
AppendTreeToString(node.right, b, indent);
}
}
#endif
#endregion
#region Add
public void Add(T item)
{
AddInternal(new RedBlackTreeNode<T>(item));
#if DEBUG
CheckProperties();
#endif
}
void AddInternal(RedBlackTreeNode<T> newNode)
{
Debug.Assert(newNode.color == BLACK);
if (root == null) {
count = 1;
root = newNode;
return;
}
// Insert into the tree
RedBlackTreeNode<T> parentNode = root;
while (true) {
if (host.Compare(newNode.val, parentNode.val) <= 0) {
if (parentNode.left == null) {
InsertAsLeft(parentNode, newNode);
return;
}
parentNode = parentNode.left;
} else {
if (parentNode.right == null) {
InsertAsRight(parentNode, newNode);
return;
}
parentNode = parentNode.right;
}
}
}
internal void InsertAsLeft(RedBlackTreeNode<T> parentNode, RedBlackTreeNode<T> newNode)
{
Debug.Assert(parentNode.left == null);
parentNode.left = newNode;
newNode.parent = parentNode;
newNode.color = RED;
host.UpdateAfterChildrenChange(parentNode);
FixTreeOnInsert(newNode);
count++;
}
internal void InsertAsRight(RedBlackTreeNode<T> parentNode, RedBlackTreeNode<T> newNode)
{
Debug.Assert(parentNode.right == null);
parentNode.right = newNode;
newNode.parent = parentNode;
newNode.color = RED;
host.UpdateAfterChildrenChange(parentNode);
FixTreeOnInsert(newNode);
count++;
}
void FixTreeOnInsert(RedBlackTreeNode<T> node)
{
Debug.Assert(node != null);
Debug.Assert(node.color == RED);
Debug.Assert(node.left == null || node.left.color == BLACK);
Debug.Assert(node.right == null || node.right.color == BLACK);
RedBlackTreeNode<T> parentNode = node.parent;
if (parentNode == null) {
// we inserted in the root -> the node must be black
// since this is a root node, making the node black increments the number of black nodes
// on all paths by one, so it is still the same for all paths.
node.color = BLACK;
return;
}
if (parentNode.color == BLACK) {
// if the parent node where we inserted was black, our red node is placed correctly.
// since we inserted a red node, the number of black nodes on each path is unchanged
// -> the tree is still balanced
return;
}
// parentNode is red, so there is a conflict here!
// because the root is black, parentNode is not the root -> there is a grandparent node
RedBlackTreeNode<T> grandparentNode = parentNode.parent;
RedBlackTreeNode<T> uncleNode = Sibling(parentNode);
if (uncleNode != null && uncleNode.color == RED) {
parentNode.color = BLACK;
uncleNode.color = BLACK;
grandparentNode.color = RED;
FixTreeOnInsert(grandparentNode);
return;
}
// now we know: parent is red but uncle is black
// First rotation:
if (node == parentNode.right && parentNode == grandparentNode.left) {
RotateLeft(parentNode);
node = node.left;
} else if (node == parentNode.left && parentNode == grandparentNode.right) {
RotateRight(parentNode);
node = node.right;
}
// because node might have changed, reassign variables:
parentNode = node.parent;
grandparentNode = parentNode.parent;
// Now recolor a bit:
parentNode.color = BLACK;
grandparentNode.color = RED;
// Second rotation:
if (node == parentNode.left && parentNode == grandparentNode.left) {
RotateRight(grandparentNode);
} else {
// because of the first rotation, this is guaranteed:
Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right);
RotateLeft(grandparentNode);
}
}
void ReplaceNode(RedBlackTreeNode<T> replacedNode, RedBlackTreeNode<T> newNode)
{
if (replacedNode.parent == null) {
Debug.Assert(replacedNode == root);
root = newNode;
} else {
if (replacedNode.parent.left == replacedNode)
replacedNode.parent.left = newNode;
else
replacedNode.parent.right = newNode;
}
if (newNode != null) {
newNode.parent = replacedNode.parent;
}
replacedNode.parent = null;
}
void RotateLeft(RedBlackTreeNode<T> p)
{
// let q be p's right child
RedBlackTreeNode<T> q = p.right;
Debug.Assert(q != null);
Debug.Assert(q.parent == p);
// set q to be the new root
ReplaceNode(p, q);
// set p's right child to be q's left child
p.right = q.left;
if (p.right != null) p.right.parent = p;
// set q's left child to be p
q.left = p;
p.parent = q;
host.UpdateAfterRotateLeft(p);
}
void RotateRight(RedBlackTreeNode<T> p)
{
// let q be p's left child
RedBlackTreeNode<T> q = p.left;
Debug.Assert(q != null);
Debug.Assert(q.parent == p);
// set q to be the new root
ReplaceNode(p, q);
// set p's left child to be q's right child
p.left = q.right;
if (p.left != null) p.left.parent = p;
// set q's right child to be p
q.right = p;
p.parent = q;
host.UpdateAfterRotateRight(p);
}
RedBlackTreeNode<T> Sibling(RedBlackTreeNode<T> node)
{
if (node == node.parent.left)
return node.parent.right;
else
return node.parent.left;
}
#endregion
#region Remove
public void RemoveAt(RedBlackTreeIterator<T> iterator)
{
RedBlackTreeNode<T> node = iterator.node;
if (node == null)
throw new ArgumentException("Invalid iterator");
while (node.parent != null)
node = node.parent;
if (node != root)
throw new ArgumentException("Iterator does not belong to this tree");
RemoveNode(iterator.node);
#if DEBUG
CheckProperties();
#endif
}
internal void RemoveNode(RedBlackTreeNode<T> removedNode)
{
if (removedNode.left != null && removedNode.right != null) {
// replace removedNode with it's in-order successor
RedBlackTreeNode<T> leftMost = removedNode.right.LeftMost;
RemoveNode(leftMost); // remove leftMost from its current location
// and overwrite the removedNode with it
ReplaceNode(removedNode, leftMost);
leftMost.left = removedNode.left;
if (leftMost.left != null) leftMost.left.parent = leftMost;
leftMost.right = removedNode.right;
if (leftMost.right != null) leftMost.right.parent = leftMost;
leftMost.color = removedNode.color;
host.UpdateAfterChildrenChange(leftMost);
if (leftMost.parent != null) host.UpdateAfterChildrenChange(leftMost.parent);
return;
}
count--;
// now either removedNode.left or removedNode.right is null
// get the remaining child
RedBlackTreeNode<T> parentNode = removedNode.parent;
RedBlackTreeNode<T> childNode = removedNode.left ?? removedNode.right;
ReplaceNode(removedNode, childNode);
if (parentNode != null) host.UpdateAfterChildrenChange(parentNode);
if (removedNode.color == BLACK) {
if (childNode != null && childNode.color == RED) {
childNode.color = BLACK;
} else {
FixTreeOnDelete(childNode, parentNode);
}
}
}
static RedBlackTreeNode<T> Sibling(RedBlackTreeNode<T> node, RedBlackTreeNode<T> parentNode)
{
Debug.Assert(node == null || node.parent == parentNode);
if (node == parentNode.left)
return parentNode.right;
else
return parentNode.left;
}
const bool RED = true;
const bool BLACK = false;
static bool GetColor(RedBlackTreeNode<T> node)
{
return node != null ? node.color : BLACK;
}
void FixTreeOnDelete(RedBlackTreeNode<T> node, RedBlackTreeNode<T> parentNode)
{
Debug.Assert(node == null || node.parent == parentNode);
if (parentNode == null)
return;
// warning: node may be null
RedBlackTreeNode<T> sibling = Sibling(node, parentNode);
if (sibling.color == RED) {
parentNode.color = RED;
sibling.color = BLACK;
if (node == parentNode.left) {
RotateLeft(parentNode);
} else {
RotateRight(parentNode);
}
sibling = Sibling(node, parentNode); // update value of sibling after rotation
}
if (parentNode.color == BLACK
&& sibling.color == BLACK
&& GetColor(sibling.left) == BLACK
&& GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
FixTreeOnDelete(parentNode, parentNode.parent);
return;
}
if (parentNode.color == RED
&& sibling.color == BLACK
&& GetColor(sibling.left) == BLACK
&& GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
parentNode.color = BLACK;
return;
}
if (node == parentNode.left &&
sibling.color == BLACK &&
GetColor(sibling.left) == RED &&
GetColor(sibling.right) == BLACK)
{
sibling.color = RED;
sibling.left.color = BLACK;
RotateRight(sibling);
}
else if (node == parentNode.right &&
sibling.color == BLACK &&
GetColor(sibling.right) == RED &&
GetColor(sibling.left) == BLACK)
{
sibling.color = RED;
sibling.right.color = BLACK;
RotateLeft(sibling);
}
sibling = Sibling(node, parentNode); // update value of sibling after rotation
sibling.color = parentNode.color;
parentNode.color = BLACK;
if (node == parentNode.left) {
if (sibling.right != null) {
Debug.Assert(sibling.right.color == RED);
sibling.right.color = BLACK;
}
RotateLeft(parentNode);
} else {
if (sibling.left != null) {
Debug.Assert(sibling.left.color == RED);
sibling.left.color = BLACK;
}
RotateRight(parentNode);
}
}
#endregion
#region Find/LowerBound/UpperBound/GetEnumerator
/// <summary>
/// Returns the iterator pointing to the specified item, or an iterator in End state if the item is not found.
/// </summary>
public RedBlackTreeIterator<T> Find(T item)
{
RedBlackTreeIterator<T> it = LowerBound(item);
while (it.IsValid && host.Compare(it.Current, item) == 0) {
if (host.Equals(it.Current, item))
return it;
it.MoveNext();
}
return default(RedBlackTreeIterator<T>);
}
/// <summary>
/// Returns the iterator pointing to the first item greater or equal to <paramref name="item"/>.
/// </summary>
public RedBlackTreeIterator<T> LowerBound(T item)
{
RedBlackTreeNode<T> node = root;
RedBlackTreeNode<T> resultNode = null;
while (node != null) {
if (host.Compare(node.val, item) < 0) {
node = node.right;
} else {
resultNode = node;
node = node.left;
}
}
return new RedBlackTreeIterator<T>(resultNode);
}
/// <summary>
/// Returns the iterator pointing to the first item greater than <paramref name="item"/>.
/// </summary>
public RedBlackTreeIterator<T> UpperBound(T item)
{
RedBlackTreeIterator<T> it = LowerBound(item);
while (it.IsValid && host.Compare(it.Current, item) == 0) {
it.MoveNext();
}
return it;
}
/// <summary>
/// Gets a tree iterator that starts on the first node.
/// </summary>
public RedBlackTreeIterator<T> Begin()
{
if (root == null) return default(RedBlackTreeIterator<T>);
return new RedBlackTreeIterator<T>(root.LeftMost);
}
/// <summary>
/// Gets a tree iterator that starts one node before the first node.
/// </summary>
public RedBlackTreeIterator<T> GetEnumerator()
{
if (root == null) return default(RedBlackTreeIterator<T>);
RedBlackTreeNode<T> dummyNode = new RedBlackTreeNode<T>(default(T));
dummyNode.right = root;
return new RedBlackTreeIterator<T>(dummyNode);
}
#endregion
#region ICollection members
public bool Contains(T item)
{
return Find(item).IsValid;
}
public bool Remove(T item)
{
RedBlackTreeIterator<T> it = Find(item);
if (!it.IsValid) {
return false;
} else {
RemoveAt(it);
return true;
}
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
bool ICollection<T>.IsReadOnly {
get { return false; }
}
public void CopyTo(T[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException("array");
foreach (T val in this) {
array[arrayIndex++] = val;
}
}
#endregion
}
}

View File

@@ -0,0 +1,148 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Threading;
namespace ICSharpCode.TextEditor.Util
{
/// <summary>
/// A IList{T} that checks that it is only accessed on the thread that created it, and that
/// it is not modified while an enumerator is running.
/// </summary>
sealed class CheckedList<T> : IList<T>
{
readonly int threadID;
readonly IList<T> baseList;
int enumeratorCount;
public CheckedList() : this(new List<T>()) {}
public CheckedList(IList<T> baseList)
{
if (baseList == null)
throw new ArgumentNullException("baseList");
this.baseList = baseList;
this.threadID = Thread.CurrentThread.ManagedThreadId;
}
void CheckRead()
{
if (Thread.CurrentThread.ManagedThreadId != threadID)
throw new InvalidOperationException("CheckList cannot be accessed from this thread!");
}
void CheckWrite()
{
if (Thread.CurrentThread.ManagedThreadId != threadID)
throw new InvalidOperationException("CheckList cannot be accessed from this thread!");
if (enumeratorCount != 0)
throw new InvalidOperationException("CheckList cannot be written to while enumerators are active!");
}
public T this[int index] {
get {
CheckRead();
return baseList[index];
}
set {
CheckWrite();
baseList[index] = value;
}
}
public int Count {
get {
CheckRead();
return baseList.Count;
}
}
public bool IsReadOnly {
get {
CheckRead();
return baseList.IsReadOnly;
}
}
public int IndexOf(T item)
{
CheckRead();
return baseList.IndexOf(item);
}
public void Insert(int index, T item)
{
CheckWrite();
baseList.Insert(index, item);
}
public void RemoveAt(int index)
{
CheckWrite();
baseList.RemoveAt(index);
}
public void Add(T item)
{
CheckWrite();
baseList.Add(item);
}
public void Clear()
{
CheckWrite();
baseList.Clear();
}
public bool Contains(T item)
{
CheckRead();
return baseList.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
CheckRead();
baseList.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
CheckWrite();
return baseList.Remove(item);
}
public IEnumerator<T> GetEnumerator()
{
CheckRead();
return Enumerate();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
CheckRead();
return Enumerate();
}
IEnumerator<T> Enumerate()
{
CheckRead();
try {
enumeratorCount++;
foreach (T val in baseList) {
yield return val;
CheckRead();
}
} finally {
enumeratorCount--;
CheckRead();
}
}
}
}

View File

@@ -0,0 +1,152 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.IO;
using System.Text;
namespace ICSharpCode.TextEditor.Util
{
/// <summary>
/// Class that can open text files with auto-detection of the encoding.
/// </summary>
public static class FileReader
{
public static bool IsUnicode(Encoding encoding)
{
int codepage = encoding.CodePage;
// return true if codepage is any UTF codepage
return codepage == 65001 || codepage == 65000 || codepage == 1200 || codepage == 1201;
}
public static string ReadFileContent(Stream fs, ref Encoding encoding)
{
using (StreamReader reader = OpenStream(fs, encoding)) {
reader.Peek();
encoding = reader.CurrentEncoding;
return reader.ReadToEnd();
}
}
public static string ReadFileContent(string fileName, Encoding encoding)
{
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
return ReadFileContent(fs, ref encoding);
}
}
public static StreamReader OpenStream(Stream fs, Encoding defaultEncoding)
{
if (fs == null)
throw new ArgumentNullException("fs");
if (fs.Length >= 2) {
// the autodetection of StreamReader is not capable of detecting the difference
// between ISO-8859-1 and UTF-8 without BOM.
int firstByte = fs.ReadByte();
int secondByte = fs.ReadByte();
switch ((firstByte << 8) | secondByte) {
case 0x0000: // either UTF-32 Big Endian or a binary file; use StreamReader
case 0xfffe: // Unicode BOM (UTF-16 LE or UTF-32 LE)
case 0xfeff: // UTF-16 BE BOM
case 0xefbb: // start of UTF-8 BOM
// StreamReader autodetection works
fs.Position = 0;
return new StreamReader(fs);
default:
return AutoDetect(fs, (byte)firstByte, (byte)secondByte, defaultEncoding);
}
} else {
if (defaultEncoding != null) {
return new StreamReader(fs, defaultEncoding);
} else {
return new StreamReader(fs);
}
}
}
static StreamReader AutoDetect(Stream fs, byte firstByte, byte secondByte, Encoding defaultEncoding)
{
int max = (int)Math.Min(fs.Length, 500000); // look at max. 500 KB
const int ASCII = 0;
const int Error = 1;
const int UTF8 = 2;
const int UTF8Sequence = 3;
int state = ASCII;
int sequenceLength = 0;
byte b;
for (int i = 0; i < max; i++) {
if (i == 0) {
b = firstByte;
} else if (i == 1) {
b = secondByte;
} else {
b = (byte)fs.ReadByte();
}
if (b < 0x80) {
// normal ASCII character
if (state == UTF8Sequence) {
state = Error;
break;
}
} else if (b < 0xc0) {
// 10xxxxxx : continues UTF8 byte sequence
if (state == UTF8Sequence) {
--sequenceLength;
if (sequenceLength < 0) {
state = Error;
break;
} else if (sequenceLength == 0) {
state = UTF8;
}
} else {
state = Error;
break;
}
} else if (b >= 0xc2 && b < 0xf5) {
// beginning of byte sequence
if (state == UTF8 || state == ASCII) {
state = UTF8Sequence;
if (b < 0xe0) {
sequenceLength = 1; // one more byte following
} else if (b < 0xf0) {
sequenceLength = 2; // two more bytes following
} else {
sequenceLength = 3; // three more bytes following
}
} else {
state = Error;
break;
}
} else {
// 0xc0, 0xc1, 0xf5 to 0xff are invalid in UTF-8 (see RFC 3629)
state = Error;
break;
}
}
fs.Position = 0;
switch (state) {
case ASCII:
case Error:
// when the file seems to be ASCII or non-UTF8,
// we read it using the user-specified encoding so it is saved again
// using that encoding.
if (IsUnicode(defaultEncoding)) {
// the file is not Unicode, so don't read it using Unicode even if the
// user has choosen Unicode as the default encoding.
// If we don't do this, SD will end up always adding a Byte Order Mark
// to ASCII files.
defaultEncoding = Encoding.Default; // use system encoding instead
}
return new StreamReader(fs, defaultEncoding);
default:
return new StreamReader(fs);
}
}
}
}

View File

@@ -0,0 +1,24 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Util
{
/// <summary>
/// Central location for logging calls in the text editor.
/// </summary>
static class LoggingService
{
public static void Debug(string text)
{
#if DEBUG
Console.WriteLine(text);
#endif
}
}
}

View File

@@ -0,0 +1,156 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Util
{
/// <summary>
/// This class implements a keyword map. It implements a digital search trees (tries) to find
/// a word.
/// </summary>
public class LookupTable
{
Node root = new Node(null, null);
bool casesensitive;
int length;
/// <value>
/// The number of elements in the table
/// </value>
public int Count {
get {
return length;
}
}
/// <summary>
/// Get the object, which was inserted under the keyword (line, at offset, with length length),
/// returns null, if no such keyword was inserted.
/// </summary>
public object this[IDocument document, LineSegment line, int offset, int length] {
get {
if(length == 0) {
return null;
}
Node next = root;
int wordOffset = line.Offset + offset;
if (casesensitive) {
for (int i = 0; i < length; ++i) {
int index = ((int)document.GetCharAt(wordOffset + i)) % 256;
next = next[index];
if (next == null) {
return null;
}
if (next.color != null && TextUtility.RegionMatches(document, wordOffset, length, next.word)) {
return next.color;
}
}
} else {
for (int i = 0; i < length; ++i) {
int index = ((int)char.ToUpper(document.GetCharAt(wordOffset + i))) % 256;
next = next[index];
if (next == null) {
return null;
}
if (next.color != null && TextUtility.RegionMatches(document, casesensitive, wordOffset, length, next.word)) {
return next.color;
}
}
}
return null;
}
}
/// <summary>
/// Inserts an object in the tree, under keyword
/// </summary>
public object this[string keyword] {
set {
Node node = root;
Node next = root;
if (!casesensitive) {
keyword = keyword.ToUpper();
}
++length;
// insert word into the tree
for (int i = 0; i < keyword.Length; ++i) {
int index = ((int)keyword[i]) % 256; // index of curchar
bool d = keyword[i] == '\\';
next = next[index]; // get node to this index
if (next == null) { // no node created -> insert word here
node[index] = new Node(value, keyword);
break;
}
if (next.word != null && next.word.Length != i) { // node there, take node content and insert them again
string tmpword = next.word; // this word will be inserted 1 level deeper (better, don't need too much
object tmpcolor = next.color; // string comparisons for finding.)
next.color = next.word = null;
this[tmpword] = tmpcolor;
}
if (i == keyword.Length - 1) { // end of keyword reached, insert node there, if a node was here it was
next.word = keyword; // reinserted, if it has the same length (keyword EQUALS this word) it will be overwritten
next.color = value;
break;
}
node = next;
}
}
}
/// <summary>
/// Creates a new instance of <see cref="LookupTable"/>
/// </summary>
public LookupTable(bool casesensitive)
{
this.casesensitive = casesensitive;
}
class Node
{
public Node(object color, string word)
{
this.word = word;
this.color = color;
}
public string word;
public object color;
// Lazily initialize children array. Saves 200 KB of memory for the C# highlighting
// because we don't have to store the array for leaf nodes.
public Node this[int index] {
get {
if (children != null)
return children[index];
else
return null;
}
set {
if (children == null)
children = new Node[256];
children[index] = value;
}
}
private Node[] children;
}
}
}

View File

@@ -0,0 +1,36 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Windows.Forms;
namespace ICSharpCode.TextEditor.Util
{
/// <summary>
/// Accumulates mouse wheel deltas and reports the actual number of lines to scroll.
/// </summary>
class MouseWheelHandler
{
// CODE DUPLICATION: See ICSharpCode.SharpDevelop.Widgets.MouseWheelHandler
const int WHEEL_DELTA = 120;
int mouseWheelDelta;
public int GetScrollAmount(MouseEventArgs e)
{
// accumulate the delta to support high-resolution mice
mouseWheelDelta += e.Delta;
int linesPerClick = Math.Max(SystemInformation.MouseWheelScrollLines, 1);
int scrollDistance = mouseWheelDelta * linesPerClick / WHEEL_DELTA;
mouseWheelDelta %= Math.Max(1, WHEEL_DELTA / linesPerClick);
return scrollDistance;
}
}
}

View File

@@ -0,0 +1,85 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Diagnostics;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Util
{
internal struct RedBlackTreeIterator<T> : IEnumerator<T>
{
internal RedBlackTreeNode<T> node;
internal RedBlackTreeIterator(RedBlackTreeNode<T> node)
{
this.node = node;
}
public bool IsValid {
get { return node != null; }
}
public T Current {
get {
if (node != null)
return node.val;
else
throw new InvalidOperationException();
}
}
object System.Collections.IEnumerator.Current {
get {
return this.Current;
}
}
void IDisposable.Dispose()
{
}
void System.Collections.IEnumerator.Reset()
{
throw new NotSupportedException();
}
public bool MoveNext()
{
if (node == null)
return false;
if (node.right != null) {
node = node.right.LeftMost;
} else {
RedBlackTreeNode<T> oldNode;
do {
oldNode = node;
node = node.parent;
// we are on the way up from the right part, don't output node again
} while (node != null && node.right == oldNode);
}
return node != null;
}
public bool MoveBack()
{
if (node == null)
return false;
if (node.left != null) {
node = node.left.RightMost;
} else {
RedBlackTreeNode<T> oldNode;
do {
oldNode = node;
node = node.parent;
// we are on the way up from the left part, don't output node again
} while (node != null && node.left == oldNode);
}
return node != null;
}
}
}

View File

@@ -0,0 +1,190 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Util
{
public class RtfWriter
{
static Dictionary<string, int> colors;
static int colorNum;
static StringBuilder colorString;
public static string GenerateRtf(TextArea textArea)
{
colors = new Dictionary<string, int>();
colorNum = 0;
colorString = new StringBuilder();
StringBuilder rtf = new StringBuilder();
rtf.Append(@"{\rtf1\ansi\ansicpg1252\deff0\deflang1031");
BuildFontTable(textArea.Document, rtf);
rtf.Append('\n');
string fileContent = BuildFileContent(textArea);
BuildColorTable(textArea.Document, rtf);
rtf.Append('\n');
rtf.Append(@"\viewkind4\uc1\pard");
rtf.Append(fileContent);
rtf.Append("}");
return rtf.ToString();
}
static void BuildColorTable(IDocument doc, StringBuilder rtf)
{
rtf.Append(@"{\colortbl ;");
rtf.Append(colorString.ToString());
rtf.Append("}");
}
static void BuildFontTable(IDocument doc, StringBuilder rtf)
{
rtf.Append(@"{\fonttbl");
rtf.Append(@"{\f0\fmodern\fprq1\fcharset0 " + doc.TextEditorProperties.Font.Name + ";}");
rtf.Append("}");
}
static string BuildFileContent(TextArea textArea)
{
StringBuilder rtf = new StringBuilder();
bool firstLine = true;
Color curColor = Color.Black;
bool oldItalic = false;
bool oldBold = false;
bool escapeSequence = false;
foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) {
int selectionOffset = textArea.Document.PositionToOffset(selection.StartPosition);
int selectionEndOffset = textArea.Document.PositionToOffset(selection.EndPosition);
for (int i = selection.StartPosition.Y; i <= selection.EndPosition.Y; ++i) {
LineSegment line = textArea.Document.GetLineSegment(i);
int offset = line.Offset;
if (line.Words == null) {
continue;
}
foreach (TextWord word in line.Words) {
switch (word.Type) {
case TextWordType.Space:
if (selection.ContainsOffset(offset)) {
rtf.Append(' ');
}
++offset;
break;
case TextWordType.Tab:
if (selection.ContainsOffset(offset)) {
rtf.Append(@"\tab");
}
++offset;
escapeSequence = true;
break;
case TextWordType.Word:
Color c = word.Color;
if (offset + word.Word.Length > selectionOffset && offset < selectionEndOffset) {
string colorstr = c.R + ", " + c.G + ", " + c.B;
if (!colors.ContainsKey(colorstr)) {
colors[colorstr] = ++colorNum;
colorString.Append(@"\red" + c.R + @"\green" + c.G + @"\blue" + c.B + ";");
}
if (c != curColor || firstLine) {
rtf.Append(@"\cf" + colors[colorstr].ToString());
curColor = c;
escapeSequence = true;
}
if (oldItalic != word.Italic) {
if (word.Italic) {
rtf.Append(@"\i");
} else {
rtf.Append(@"\i0");
}
oldItalic = word.Italic;
escapeSequence = true;
}
if (oldBold != word.Bold) {
if (word.Bold) {
rtf.Append(@"\b");
} else {
rtf.Append(@"\b0");
}
oldBold = word.Bold;
escapeSequence = true;
}
if (firstLine) {
rtf.Append(@"\f0\fs" + Math.Round(textArea.TextEditorProperties.Font.Size * 2)); // Stef_H
firstLine = false;
}
if (escapeSequence) {
rtf.Append(' ');
escapeSequence = false;
}
string printWord;
if (offset < selectionOffset) {
printWord = word.Word.Substring(selectionOffset - offset);
} else if (offset + word.Word.Length > selectionEndOffset) {
printWord = word.Word.Substring(0, (offset + word.Word.Length) - selectionEndOffset);
} else {
printWord = word.Word;
}
AppendText(rtf, printWord);
}
offset += word.Length;
break;
}
}
if (offset < selectionEndOffset) {
rtf.Append(@"\par");
}
rtf.Append('\n');
}
}
return rtf.ToString();
}
static void AppendText(StringBuilder rtfOutput, string text)
{
//rtf.Append(printWord.Replace(@"\", @"\\").Replace("{", "\\{").Replace("}", "\\}"));
foreach (char c in text) {
switch (c) {
case '\\':
rtfOutput.Append(@"\\");
break;
case '{':
rtfOutput.Append("\\{");
break;
case '}':
rtfOutput.Append("\\}");
break;
default:
if (c < 256) {
rtfOutput.Append(c);
} else {
// yes, RTF really expects signed 16-bit integers!
rtfOutput.Append("\\u" + unchecked((short)c).ToString() + "?");
}
break;
}
}
}
}
}

View File

@@ -0,0 +1,81 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Util
{
public class TextUtility
{
public static bool RegionMatches(IDocument document, int offset, int length, string word)
{
if (length != word.Length || document.TextLength < offset + length) {
return false;
}
for (int i = 0; i < length; ++i) {
if (document.GetCharAt(offset + i) != word[i]) {
return false;
}
}
return true;
}
public static bool RegionMatches(IDocument document, bool casesensitive, int offset, int length, string word)
{
if (casesensitive) {
return RegionMatches(document, offset, length, word);
}
if (length != word.Length || document.TextLength < offset + length) {
return false;
}
for (int i = 0; i < length; ++i) {
if (char.ToUpper(document.GetCharAt(offset + i)) != char.ToUpper(word[i])) {
return false;
}
}
return true;
}
public static bool RegionMatches(IDocument document, int offset, int length, char[] word)
{
if (length != word.Length || document.TextLength < offset + length) {
return false;
}
for (int i = 0; i < length; ++i) {
if (document.GetCharAt(offset + i) != word[i]) {
return false;
}
}
return true;
}
public static bool RegionMatches(IDocument document, bool casesensitive, int offset, int length, char[] word)
{
if (casesensitive) {
return RegionMatches(document, offset, length, word);
}
if (length != word.Length || document.TextLength < offset + length) {
return false;
}
for (int i = 0; i < length; ++i) {
if (char.ToUpper(document.GetCharAt(offset + i)) != char.ToUpper(word[i])) {
return false;
}
}
return true;
}
}
}

View File

@@ -0,0 +1,193 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="none" email=""/>
// <version>$Revision$</version>
// </file>
using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms;
namespace ICSharpCode.TextEditor.Util
{
static class TipPainter
{
const float HorizontalBorder = 2;
const float VerticalBorder = 1;
//static StringFormat centerTipFormat = CreateTipStringFormat();
public static Size GetTipSize(Control control, Graphics graphics, Font font, string description)
{
return GetTipSize(control, graphics, new TipText (graphics, font, description));
}
static Rectangle GetWorkingArea(Control control)
{
Form ownerForm = control.FindForm();
if (ownerForm.Owner != null) {
ownerForm = ownerForm.Owner;
}
return Screen.GetWorkingArea(ownerForm);
}
public static Size GetTipSize(Control control, Graphics graphics, TipSection tipData)
{
Size tipSize = Size.Empty;
SizeF tipSizeF = SizeF.Empty;
RectangleF workingArea = GetWorkingArea(control);
PointF screenLocation = control.PointToScreen(Point.Empty);
SizeF maxLayoutSize = new SizeF(workingArea.Right - screenLocation.X - HorizontalBorder * 2,
workingArea.Bottom - screenLocation.Y - VerticalBorder * 2);
if (maxLayoutSize.Width > 0 && maxLayoutSize.Height > 0) {
graphics.TextRenderingHint =
TextRenderingHint.AntiAliasGridFit;
tipData.SetMaximumSize(maxLayoutSize);
tipSizeF = tipData.GetRequiredSize();
tipData.SetAllocatedSize(tipSizeF);
tipSizeF += new SizeF(HorizontalBorder * 2,
VerticalBorder * 2);
tipSize = Size.Ceiling(tipSizeF);
}
if (control.ClientSize != tipSize) {
control.ClientSize = tipSize;
}
return tipSize;
}
public static Size GetLeftHandSideTipSize(Control control, Graphics graphics, TipSection tipData, Point p)
{
Size tipSize = Size.Empty;
SizeF tipSizeF = SizeF.Empty;
RectangleF workingArea = GetWorkingArea(control);
PointF screenLocation = p;
SizeF maxLayoutSize = new SizeF(screenLocation.X - HorizontalBorder * 2,
workingArea.Bottom - screenLocation.Y - VerticalBorder * 2);
if (maxLayoutSize.Width > 0 && maxLayoutSize.Height > 0) {
graphics.TextRenderingHint =
TextRenderingHint.AntiAliasGridFit;
tipData.SetMaximumSize(maxLayoutSize);
tipSizeF = tipData.GetRequiredSize();
tipData.SetAllocatedSize(tipSizeF);
tipSizeF += new SizeF(HorizontalBorder * 2,
VerticalBorder * 2);
tipSize = Size.Ceiling(tipSizeF);
}
return tipSize;
}
public static Size DrawTip(Control control, Graphics graphics, Font font, string description)
{
return DrawTip(control, graphics, new TipText (graphics, font, description));
}
public static Size DrawTip(Control control, Graphics graphics, TipSection tipData)
{
Size tipSize = Size.Empty;
SizeF tipSizeF = SizeF.Empty;
PointF screenLocation = control.PointToScreen(Point.Empty);
RectangleF workingArea = GetWorkingArea(control);
SizeF maxLayoutSize = new SizeF(workingArea.Right - screenLocation.X - HorizontalBorder * 2,
workingArea.Bottom - screenLocation.Y - VerticalBorder * 2);
if (maxLayoutSize.Width > 0 && maxLayoutSize.Height > 0) {
graphics.TextRenderingHint =
TextRenderingHint.AntiAliasGridFit;
tipData.SetMaximumSize(maxLayoutSize);
tipSizeF = tipData.GetRequiredSize();
tipData.SetAllocatedSize(tipSizeF);
tipSizeF += new SizeF(HorizontalBorder * 2,
VerticalBorder * 2);
tipSize = Size.Ceiling(tipSizeF);
}
if (control.ClientSize != tipSize) {
control.ClientSize = tipSize;
}
if (tipSize != Size.Empty) {
Rectangle borderRectangle = new Rectangle
(Point.Empty, tipSize - new Size(1, 1));
RectangleF displayRectangle = new RectangleF
(HorizontalBorder, VerticalBorder,
tipSizeF.Width - HorizontalBorder * 2,
tipSizeF.Height - VerticalBorder * 2);
// DrawRectangle draws from Left to Left + Width. A bug? :-/
graphics.DrawRectangle(SystemPens.WindowFrame,
borderRectangle);
tipData.Draw(new PointF(HorizontalBorder, VerticalBorder));
}
return tipSize;
}
public static Size DrawFixedWidthTip(Control control, Graphics graphics, TipSection tipData)
{
Size tipSize = Size.Empty;
SizeF tipSizeF = SizeF.Empty;
PointF screenLocation = control.PointToScreen(new Point(control.Width, 0));
RectangleF workingArea = GetWorkingArea(control);
SizeF maxLayoutSize = new SizeF(screenLocation.X - HorizontalBorder * 2,
workingArea.Bottom - screenLocation.Y - VerticalBorder * 2);
if (maxLayoutSize.Width > 0 && maxLayoutSize.Height > 0) {
graphics.TextRenderingHint =
TextRenderingHint.AntiAliasGridFit;
tipData.SetMaximumSize(maxLayoutSize);
tipSizeF = tipData.GetRequiredSize();
tipData.SetAllocatedSize(tipSizeF);
tipSizeF += new SizeF(HorizontalBorder * 2,
VerticalBorder * 2);
tipSize = Size.Ceiling(tipSizeF);
}
if (control.Height != tipSize.Height) {
control.Height = tipSize.Height;
}
if (tipSize != Size.Empty) {
Rectangle borderRectangle = new Rectangle
(Point.Empty, control.Size - new Size(1, 1));
RectangleF displayRectangle = new RectangleF
(HorizontalBorder, VerticalBorder,
tipSizeF.Width - HorizontalBorder * 2,
tipSizeF.Height - VerticalBorder * 2);
// DrawRectangle draws from Left to Left + Width. A bug? :-/
graphics.DrawRectangle(SystemPens.WindowFrame,
borderRectangle);
tipData.Draw(new PointF(HorizontalBorder, VerticalBorder));
}
return tipSize;
}
}
}

View File

@@ -0,0 +1,301 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="none" email=""/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ICSharpCode.TextEditor.Util
{
static class TipPainterTools
{
const int SpacerSize = 4;
public static Size GetLeftHandSideDrawingSizeHelpTipFromCombinedDescription(Control control,
Graphics graphics,
Font font,
string countMessage,
string description,
Point p)
{
string basicDescription = null;
string documentation = null;
if (IsVisibleText(description)) {
string[] splitDescription = description.Split(new char[] { '\n' }, 2);
if (splitDescription.Length > 0) {
basicDescription = splitDescription[0];
if (splitDescription.Length > 1) {
documentation = splitDescription[1].Trim();
}
}
}
return GetLeftHandSideDrawingSizeDrawHelpTip(control, graphics, font, countMessage, basicDescription, documentation, p);
}
public static Size GetDrawingSizeHelpTipFromCombinedDescription(Control control,
Graphics graphics,
Font font,
string countMessage,
string description)
{
string basicDescription = null;
string documentation = null;
if (IsVisibleText(description)) {
string[] splitDescription = description.Split(new char[] { '\n' }, 2);
if (splitDescription.Length > 0) {
basicDescription = splitDescription[0];
if (splitDescription.Length > 1) {
documentation = splitDescription[1].Trim();
}
}
}
return GetDrawingSizeDrawHelpTip(control, graphics, font, countMessage, basicDescription, documentation);
}
public static Size DrawHelpTipFromCombinedDescription(Control control,
Graphics graphics,
Font font,
string countMessage,
string description)
{
string basicDescription = null;
string documentation = null;
if (IsVisibleText(description)) {
string[] splitDescription = description.Split
(new char[] { '\n' }, 2);
if (splitDescription.Length > 0) {
basicDescription = splitDescription[0];
if (splitDescription.Length > 1) {
documentation = splitDescription[1].Trim();
}
}
}
return DrawHelpTip(control, graphics, font, countMessage,
basicDescription, documentation);
}
public static Size DrawFixedWidthHelpTipFromCombinedDescription(Control control,
Graphics graphics,
Font font,
string countMessage,
string description)
{
string basicDescription = null;
string documentation = null;
if (IsVisibleText(description)) {
string[] splitDescription = description.Split
(new char[] { '\n' }, 2);
if (splitDescription.Length > 0) {
basicDescription = splitDescription[0];
if (splitDescription.Length > 1) {
documentation = splitDescription[1].Trim();
}
}
}
return DrawFixedWidthHelpTip(control, graphics, font, countMessage,
basicDescription, documentation);
}
// btw. I know it's ugly.
public static Rectangle DrawingRectangle1;
public static Rectangle DrawingRectangle2;
public static Size GetDrawingSizeDrawHelpTip(Control control,
Graphics graphics, Font font,
string countMessage,
string basicDescription,
string documentation)
{
if (IsVisibleText(countMessage) ||
IsVisibleText(basicDescription) ||
IsVisibleText(documentation)) {
// Create all the TipSection objects.
CountTipText countMessageTip = new CountTipText(graphics, font, countMessage);
TipSpacer countSpacer = new TipSpacer(graphics, new SizeF(IsVisibleText(countMessage) ? 4 : 0, 0));
TipText descriptionTip = new TipText(graphics, font, basicDescription);
TipSpacer docSpacer = new TipSpacer(graphics, new SizeF(0, IsVisibleText(documentation) ? 4 : 0));
TipText docTip = new TipText(graphics, font, documentation);
// Now put them together.
TipSplitter descSplitter = new TipSplitter(graphics, false,
descriptionTip,
docSpacer
);
TipSplitter mainSplitter = new TipSplitter(graphics, true,
countMessageTip,
countSpacer,
descSplitter);
TipSplitter mainSplitter2 = new TipSplitter(graphics, false,
mainSplitter,
docTip);
// Show it.
Size size = TipPainter.GetTipSize(control, graphics, mainSplitter2);
DrawingRectangle1 = countMessageTip.DrawingRectangle1;
DrawingRectangle2 = countMessageTip.DrawingRectangle2;
return size;
}
return Size.Empty;
}
public static Size GetLeftHandSideDrawingSizeDrawHelpTip(Control control,
Graphics graphics, Font font,
string countMessage,
string basicDescription,
string documentation,
Point p)
{
if (IsVisibleText(countMessage) ||
IsVisibleText(basicDescription) ||
IsVisibleText(documentation)) {
// Create all the TipSection objects.
CountTipText countMessageTip = new CountTipText(graphics, font, countMessage);
TipSpacer countSpacer = new TipSpacer(graphics, new SizeF(IsVisibleText(countMessage) ? 4 : 0, 0));
TipText descriptionTip = new TipText(graphics, font, basicDescription);
TipSpacer docSpacer = new TipSpacer(graphics, new SizeF(0, IsVisibleText(documentation) ? 4 : 0));
TipText docTip = new TipText(graphics, font, documentation);
// Now put them together.
TipSplitter descSplitter = new TipSplitter(graphics, false,
descriptionTip,
docSpacer
);
TipSplitter mainSplitter = new TipSplitter(graphics, true,
countMessageTip,
countSpacer,
descSplitter);
TipSplitter mainSplitter2 = new TipSplitter(graphics, false,
mainSplitter,
docTip);
// Show it.
Size size = TipPainter.GetLeftHandSideTipSize(control, graphics, mainSplitter2, p);
return size;
}
return Size.Empty;
}
public static Size DrawHelpTip(Control control,
Graphics graphics, Font font,
string countMessage,
string basicDescription,
string documentation)
{
if (IsVisibleText(countMessage) ||
IsVisibleText(basicDescription) ||
IsVisibleText(documentation)) {
// Create all the TipSection objects.
CountTipText countMessageTip = new CountTipText(graphics, font, countMessage);
TipSpacer countSpacer = new TipSpacer(graphics, new SizeF(IsVisibleText(countMessage) ? 4 : 0, 0));
TipText descriptionTip = new TipText(graphics, font, basicDescription);
TipSpacer docSpacer = new TipSpacer(graphics, new SizeF(0, IsVisibleText(documentation) ? 4 : 0));
TipText docTip = new TipText(graphics, font, documentation);
// Now put them together.
TipSplitter descSplitter = new TipSplitter(graphics, false,
descriptionTip,
docSpacer
);
TipSplitter mainSplitter = new TipSplitter(graphics, true,
countMessageTip,
countSpacer,
descSplitter);
TipSplitter mainSplitter2 = new TipSplitter(graphics, false,
mainSplitter,
docTip);
// Show it.
Size size = TipPainter.DrawTip(control, graphics, mainSplitter2);
DrawingRectangle1 = countMessageTip.DrawingRectangle1;
DrawingRectangle2 = countMessageTip.DrawingRectangle2;
return size;
}
return Size.Empty;
}
public static Size DrawFixedWidthHelpTip(Control control,
Graphics graphics, Font font,
string countMessage,
string basicDescription,
string documentation)
{
if (IsVisibleText(countMessage) ||
IsVisibleText(basicDescription) ||
IsVisibleText(documentation)) {
// Create all the TipSection objects.
CountTipText countMessageTip = new CountTipText(graphics, font, countMessage);
TipSpacer countSpacer = new TipSpacer(graphics, new SizeF(IsVisibleText(countMessage) ? 4 : 0, 0));
TipText descriptionTip = new TipText(graphics, font, basicDescription);
TipSpacer docSpacer = new TipSpacer(graphics, new SizeF(0, IsVisibleText(documentation) ? 4 : 0));
TipText docTip = new TipText(graphics, font, documentation);
// Now put them together.
TipSplitter descSplitter = new TipSplitter(graphics, false,
descriptionTip,
docSpacer
);
TipSplitter mainSplitter = new TipSplitter(graphics, true,
countMessageTip,
countSpacer,
descSplitter);
TipSplitter mainSplitter2 = new TipSplitter(graphics, false,
mainSplitter,
docTip);
// Show it.
Size size = TipPainter.DrawFixedWidthTip(control, graphics, mainSplitter2);
DrawingRectangle1 = countMessageTip.DrawingRectangle1;
DrawingRectangle2 = countMessageTip.DrawingRectangle2;
return size;
}
return Size.Empty;
}
static bool IsVisibleText(string text)
{
return text != null && text.Length > 0;
}
}
}

View File

@@ -0,0 +1,84 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="none" email=""/>
// <version>$Revision$</version>
// </file>
using System;
using System.Diagnostics;
using System.Drawing;
namespace ICSharpCode.TextEditor.Util
{
abstract class TipSection
{
SizeF tipAllocatedSize;
Graphics tipGraphics;
SizeF tipMaxSize;
SizeF tipRequiredSize;
protected TipSection(Graphics graphics)
{
tipGraphics = graphics;
}
public abstract void Draw(PointF location);
public SizeF GetRequiredSize()
{
return tipRequiredSize;
}
public void SetAllocatedSize(SizeF allocatedSize)
{
Debug.Assert(allocatedSize.Width >= tipRequiredSize.Width &&
allocatedSize.Height >= tipRequiredSize.Height);
tipAllocatedSize = allocatedSize; OnAllocatedSizeChanged();
}
public void SetMaximumSize(SizeF maximumSize)
{
tipMaxSize = maximumSize; OnMaximumSizeChanged();
}
protected virtual void OnAllocatedSizeChanged()
{
}
protected virtual void OnMaximumSizeChanged()
{
}
protected void SetRequiredSize(SizeF requiredSize)
{
requiredSize.Width = Math.Max(0, requiredSize.Width);
requiredSize.Height = Math.Max(0, requiredSize.Height);
requiredSize.Width = Math.Min(tipMaxSize.Width, requiredSize.Width);
requiredSize.Height = Math.Min(tipMaxSize.Height, requiredSize.Height);
tipRequiredSize = requiredSize;
}
protected Graphics Graphics {
get {
return tipGraphics;
}
}
protected SizeF AllocatedSize {
get {
return tipAllocatedSize;
}
}
protected SizeF MaximumSize {
get {
return tipMaxSize;
}
}
}
}

View File

@@ -0,0 +1,36 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="none" email=""/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
namespace ICSharpCode.TextEditor.Util
{
class TipSpacer: TipSection
{
SizeF spacerSize;
public TipSpacer(Graphics graphics, SizeF size): base(graphics)
{
spacerSize = size;
}
public override void Draw(PointF location)
{
}
protected override void OnMaximumSizeChanged()
{
base.OnMaximumSizeChanged();
SetRequiredSize(new SizeF
(Math.Min(MaximumSize.Width, spacerSize.Width),
Math.Min(MaximumSize.Height, spacerSize.Height)));
}
}
}

View File

@@ -0,0 +1,99 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="none" email=""/>
// <version>$Revision$</version>
// </file>
using System;
using System.Diagnostics;
using System.Drawing;
namespace ICSharpCode.TextEditor.Util
{
class TipSplitter: TipSection
{
bool isHorizontal;
float [] offsets;
TipSection[] tipSections;
public TipSplitter(Graphics graphics, bool horizontal, params TipSection[] sections): base(graphics)
{
Debug.Assert(sections != null);
isHorizontal = horizontal;
offsets = new float[sections.Length];
tipSections = (TipSection[])sections.Clone();
}
public override void Draw(PointF location)
{
if (isHorizontal) {
for (int i = 0; i < tipSections.Length; i ++) {
tipSections[i].Draw
(new PointF(location.X + offsets[i], location.Y));
}
} else {
for (int i = 0; i < tipSections.Length; i ++) {
tipSections[i].Draw
(new PointF(location.X, location.Y + offsets[i]));
}
}
}
protected override void OnMaximumSizeChanged()
{
base.OnMaximumSizeChanged();
float currentDim = 0;
float otherDim = 0;
SizeF availableArea = MaximumSize;
for (int i = 0; i < tipSections.Length; i ++) {
TipSection section = (TipSection)tipSections[i];
section.SetMaximumSize(availableArea);
SizeF requiredArea = section.GetRequiredSize();
offsets[i] = currentDim;
// It's best to start on pixel borders, so this will
// round up to the nearest pixel. Otherwise there are
// weird cutoff artifacts.
float pixelsUsed;
if (isHorizontal) {
pixelsUsed = (float)Math.Ceiling(requiredArea.Width);
currentDim += pixelsUsed;
availableArea.Width = Math.Max
(0, availableArea.Width - pixelsUsed);
otherDim = Math.Max(otherDim, requiredArea.Height);
} else {
pixelsUsed = (float)Math.Ceiling(requiredArea.Height);
currentDim += pixelsUsed;
availableArea.Height = Math.Max
(0, availableArea.Height - pixelsUsed);
otherDim = Math.Max(otherDim, requiredArea.Width);
}
}
foreach (TipSection section in tipSections) {
if (isHorizontal) {
section.SetAllocatedSize(new SizeF(section.GetRequiredSize().Width, otherDim));
} else {
section.SetAllocatedSize(new SizeF(otherDim, section.GetRequiredSize().Height));
}
}
if (isHorizontal) {
SetRequiredSize(new SizeF(currentDim, otherDim));
} else {
SetRequiredSize(new SizeF(otherDim, currentDim));
}
}
}
}

View File

@@ -0,0 +1,183 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="none" email=""/>
// <version>$Revision$</version>
// </file>
using System;
using System.Drawing;
namespace ICSharpCode.TextEditor.Util
{
class CountTipText: TipText
{
float triHeight = 10;
float triWidth = 10;
public CountTipText(Graphics graphics, Font font, string text) : base(graphics, font, text)
{
}
void DrawTriangle(float x, float y, bool flipped)
{
Brush brush = BrushRegistry.GetBrush(Color.FromArgb(192, 192, 192));
base.Graphics.FillRectangle(brush, new RectangleF(x, y, triHeight, triHeight));
float triHeight2 = triHeight / 2;
float triHeight4 = triHeight / 4;
brush = Brushes.Black;
if (flipped) {
base.Graphics.FillPolygon(brush, new PointF[] {
new PointF(x, y + triHeight2 - triHeight4),
new PointF(x + triWidth / 2, y + triHeight2 + triHeight4),
new PointF(x + triWidth, y + triHeight2 - triHeight4),
});
} else {
base.Graphics.FillPolygon(brush, new PointF[] {
new PointF(x, y + triHeight2 + triHeight4),
new PointF(x + triWidth / 2, y + triHeight2 - triHeight4),
new PointF(x + triWidth, y + triHeight2 + triHeight4),
});
}
}
public Rectangle DrawingRectangle1;
public Rectangle DrawingRectangle2;
public override void Draw(PointF location)
{
if (tipText != null && tipText.Length > 0) {
base.Draw(new PointF(location.X + triWidth + 4, location.Y));
DrawingRectangle1 = new Rectangle((int)location.X + 2,
(int)location.Y + 2,
(int)(triWidth),
(int)(triHeight));
DrawingRectangle2 = new Rectangle((int)(location.X + base.AllocatedSize.Width - triWidth - 2),
(int)location.Y + 2,
(int)(triWidth),
(int)(triHeight));
DrawTriangle(location.X + 2, location.Y + 2, false);
DrawTriangle(location.X + base.AllocatedSize.Width - triWidth - 2, location.Y + 2, true);
}
}
protected override void OnMaximumSizeChanged()
{
if (IsTextVisible()) {
SizeF tipSize = Graphics.MeasureString
(tipText, tipFont, MaximumSize,
GetInternalStringFormat());
tipSize.Width += triWidth * 2 + 8;
SetRequiredSize(tipSize);
} else {
SetRequiredSize(SizeF.Empty);
}
}
}
class TipText: TipSection
{
protected StringAlignment horzAlign;
protected StringAlignment vertAlign;
protected Color tipColor;
protected Font tipFont;
protected StringFormat tipFormat;
protected string tipText;
public TipText(Graphics graphics, Font font, string text):
base(graphics)
{
tipFont = font; tipText = text;
if (text != null && text.Length > short.MaxValue)
throw new ArgumentException("TipText: text too long (max. is " + short.MaxValue + " characters)", "text");
Color = SystemColors.InfoText;
HorizontalAlignment = StringAlignment.Near;
VerticalAlignment = StringAlignment.Near;
}
public override void Draw(PointF location)
{
if (IsTextVisible()) {
RectangleF drawRectangle = new RectangleF(location, AllocatedSize);
Graphics.DrawString(tipText, tipFont,
BrushRegistry.GetBrush(Color),
drawRectangle,
GetInternalStringFormat());
}
}
protected StringFormat GetInternalStringFormat()
{
if (tipFormat == null) {
tipFormat = CreateTipStringFormat(horzAlign, vertAlign);
}
return tipFormat;
}
protected override void OnMaximumSizeChanged()
{
base.OnMaximumSizeChanged();
if (IsTextVisible()) {
SizeF tipSize = Graphics.MeasureString
(tipText, tipFont, MaximumSize,
GetInternalStringFormat());
SetRequiredSize(tipSize);
} else {
SetRequiredSize(SizeF.Empty);
}
}
static StringFormat CreateTipStringFormat(StringAlignment horizontalAlignment, StringAlignment verticalAlignment)
{
StringFormat format = (StringFormat)StringFormat.GenericTypographic.Clone();
format.FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.MeasureTrailingSpaces;
// note: Align Near, Line Center seemed to do something before
format.Alignment = horizontalAlignment;
format.LineAlignment = verticalAlignment;
return format;
}
protected bool IsTextVisible()
{
return tipText != null && tipText.Length > 0;
}
public Color Color {
get {
return tipColor;
}
set {
tipColor = value;
}
}
public StringAlignment HorizontalAlignment {
get {
return horzAlign;
}
set {
horzAlign = value;
tipFormat = null;
}
}
public StringAlignment VerticalAlignment {
get {
return vertAlign;
}
set {
vertAlign = value;
tipFormat = null;
}
}
}
}

View File

@@ -0,0 +1,136 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Util
{
/// <summary>
/// A collection that does not allows its elements to be garbage-collected (unless there are other
/// references to the elements). Elements will disappear from the collection when they are
/// garbage-collected.
///
/// The WeakCollection is not thread-safe, not even for read-only access!
/// No methods may be called on the WeakCollection while it is enumerated, not even a Contains or
/// creating a second enumerator.
/// The WeakCollection does not preserve any order among its contents; the ordering may be different each
/// time the collection is enumerated.
///
/// Since items may disappear at any time when they are garbage collected, this class
/// cannot provide a useful implementation for Count and thus cannot implement the ICollection interface.
/// </summary>
public class WeakCollection<T> : IEnumerable<T> where T : class
{
readonly List<WeakReference> innerList = new List<WeakReference>();
/// <summary>
/// Adds an element to the collection. Runtime: O(n).
/// </summary>
public void Add(T item)
{
if (item == null)
throw new ArgumentNullException("item");
CheckNoEnumerator();
if (innerList.Count == innerList.Capacity || (innerList.Count % 32) == 31)
innerList.RemoveAll(delegate(WeakReference r) { return !r.IsAlive; });
innerList.Add(new WeakReference(item));
}
/// <summary>
/// Removes all elements from the collection. Runtime: O(n).
/// </summary>
public void Clear()
{
innerList.Clear();
CheckNoEnumerator();
}
/// <summary>
/// Checks if the collection contains an item. Runtime: O(n).
/// </summary>
public bool Contains(T item)
{
if (item == null)
throw new ArgumentNullException("item");
CheckNoEnumerator();
foreach (T element in this) {
if (item.Equals(element))
return true;
}
return false;
}
/// <summary>
/// Removes an element from the collection. Returns true if the item is found and removed,
/// false when the item is not found.
/// Runtime: O(n).
/// </summary>
public bool Remove(T item)
{
if (item == null)
throw new ArgumentNullException("item");
CheckNoEnumerator();
for (int i = 0; i < innerList.Count;) {
T element = (T)innerList[i].Target;
if (element == null) {
RemoveAt(i);
} else if (element == item) {
RemoveAt(i);
return true;
} else {
i++;
}
}
return false;
}
void RemoveAt(int i)
{
int lastIndex = innerList.Count - 1;
innerList[i] = innerList[lastIndex];
innerList.RemoveAt(lastIndex);
}
bool hasEnumerator;
void CheckNoEnumerator()
{
if (hasEnumerator)
throw new InvalidOperationException("The WeakCollection is already being enumerated, it cannot be modified at the same time. Ensure you dispose the first enumerator before modifying the WeakCollection.");
}
/// <summary>
/// Enumerates the collection.
/// Each MoveNext() call on the enumerator is O(1), thus the enumeration is O(n).
/// </summary>
public IEnumerator<T> GetEnumerator()
{
if (hasEnumerator)
throw new InvalidOperationException("The WeakCollection is already being enumerated, it cannot be enumerated twice at the same time. Ensure you dispose the first enumerator before using another enumerator.");
try {
hasEnumerator = true;
for (int i = 0; i < innerList.Count;) {
T element = (T)innerList[i].Target;
if (element == null) {
RemoveAt(i);
} else {
yield return element;
i++;
}
}
} finally {
hasEnumerator = false;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}