first commit
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
148
ICSharpCode.TextEditor/Project/Src/Util/CheckedList.cs
Normal file
148
ICSharpCode.TextEditor/Project/Src/Util/CheckedList.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
152
ICSharpCode.TextEditor/Project/Src/Util/FileReader.cs
Normal file
152
ICSharpCode.TextEditor/Project/Src/Util/FileReader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
ICSharpCode.TextEditor/Project/Src/Util/LoggingService.cs
Normal file
24
ICSharpCode.TextEditor/Project/Src/Util/LoggingService.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
156
ICSharpCode.TextEditor/Project/Src/Util/LookupTable.cs
Normal file
156
ICSharpCode.TextEditor/Project/Src/Util/LookupTable.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
ICSharpCode.TextEditor/Project/Src/Util/MouseWheelHandler.cs
Normal file
36
ICSharpCode.TextEditor/Project/Src/Util/MouseWheelHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
190
ICSharpCode.TextEditor/Project/Src/Util/RtfWriter.cs
Normal file
190
ICSharpCode.TextEditor/Project/Src/Util/RtfWriter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
ICSharpCode.TextEditor/Project/Src/Util/TextUtility.cs
Normal file
81
ICSharpCode.TextEditor/Project/Src/Util/TextUtility.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
193
ICSharpCode.TextEditor/Project/Src/Util/TipPainter.cs
Normal file
193
ICSharpCode.TextEditor/Project/Src/Util/TipPainter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
301
ICSharpCode.TextEditor/Project/Src/Util/TipPainterTools.cs
Normal file
301
ICSharpCode.TextEditor/Project/Src/Util/TipPainterTools.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
84
ICSharpCode.TextEditor/Project/Src/Util/TipSection.cs
Normal file
84
ICSharpCode.TextEditor/Project/Src/Util/TipSection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
ICSharpCode.TextEditor/Project/Src/Util/TipSpacer.cs
Normal file
36
ICSharpCode.TextEditor/Project/Src/Util/TipSpacer.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
99
ICSharpCode.TextEditor/Project/Src/Util/TipSplitter.cs
Normal file
99
ICSharpCode.TextEditor/Project/Src/Util/TipSplitter.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
183
ICSharpCode.TextEditor/Project/Src/Util/TipText.cs
Normal file
183
ICSharpCode.TextEditor/Project/Src/Util/TipText.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
136
ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs
Normal file
136
ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user