2007-11-06 13:29:18 +00:00
|
|
|
|
#region --- License ---
|
2008-05-05 17:13:22 +00:00
|
|
|
|
/* Licensed under the MIT/X11 license.
|
|
|
|
|
* Copyright (c) 2006-2008 the OpenTK Team.
|
|
|
|
|
* This notice may not be removed from any source distribution.
|
|
|
|
|
* See license.txt for licensing details.
|
2007-11-06 13:29:18 +00:00
|
|
|
|
*/
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
using System;
|
2007-11-01 23:22:00 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Drawing;
|
|
|
|
|
using System.Text.RegularExpressions;
|
2007-11-06 20:59:15 +00:00
|
|
|
|
using System.Runtime.InteropServices;
|
2007-11-01 23:22:00 +00:00
|
|
|
|
|
|
|
|
|
using OpenTK.Math;
|
2008-02-02 00:58:26 +00:00
|
|
|
|
using OpenTK.Graphics.OpenGL;
|
|
|
|
|
using OpenTK.Graphics.OpenGL.Enums;
|
2007-11-08 15:56:49 +00:00
|
|
|
|
using System.Diagnostics;
|
2007-11-01 23:22:00 +00:00
|
|
|
|
|
2008-03-08 14:38:10 +00:00
|
|
|
|
namespace OpenTK.Fonts { }
|
|
|
|
|
|
|
|
|
|
namespace OpenTK.Graphics
|
2007-11-01 23:22:00 +00:00
|
|
|
|
{
|
2007-11-06 13:29:18 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Provides methods to perform layout and print hardware accelerated text.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class TextPrinter : ITextPrinter
|
2007-11-01 23:22:00 +00:00
|
|
|
|
{
|
2007-11-06 13:29:18 +00:00
|
|
|
|
//static Regex break_point = new Regex("[ .,/*-+?\\!=]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
2008-05-05 17:13:22 +00:00
|
|
|
|
//static char[] split_chars = new char[]
|
|
|
|
|
//{
|
|
|
|
|
// ' ', '\n', '\t', ',', '.', '/', '?', '!', ';', '\\', '-', '+', '*', '='
|
|
|
|
|
//};
|
2007-11-06 20:59:15 +00:00
|
|
|
|
static ITextPrinterImplementation printer;
|
2008-05-05 17:13:22 +00:00
|
|
|
|
float[] viewport = new float[4];
|
|
|
|
|
// Interleaved, vertex, texcoord, vertex, etc... Starts with 8 chars, will expand as needed.
|
|
|
|
|
Vector2[] vertices = new Vector2[8 * 8];
|
2008-01-06 02:19:53 +00:00
|
|
|
|
ushort[] indices = new ushort[6 * 8];
|
2007-11-06 13:29:18 +00:00
|
|
|
|
|
|
|
|
|
#region --- Constructor ---
|
2007-11-01 23:22:00 +00:00
|
|
|
|
|
2007-11-06 13:29:18 +00:00
|
|
|
|
/// <summary>
|
2008-04-13 18:29:36 +00:00
|
|
|
|
/// Constructs a new TextPrinter object.
|
2007-11-06 13:29:18 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
public TextPrinter() { }
|
2007-11-01 23:22:00 +00:00
|
|
|
|
|
2007-11-06 13:29:18 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2008-04-13 18:29:36 +00:00
|
|
|
|
#region --- Private Members ---
|
|
|
|
|
|
|
|
|
|
#region static ITextPrinterImplementation Printer
|
2007-11-08 15:56:49 +00:00
|
|
|
|
|
2007-11-06 13:29:18 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Checks the machine's capabilities and selects the fastest method to print text.
|
|
|
|
|
/// </summary>
|
2008-04-13 18:29:36 +00:00
|
|
|
|
static ITextPrinterImplementation Printer
|
2007-11-01 23:22:00 +00:00
|
|
|
|
{
|
2008-04-13 18:29:36 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (printer == null)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
printer = (ITextPrinterImplementation)new DisplayListTextPrinter();
|
|
|
|
|
//GL.SupportsExtension("VERSION_1_5") ?
|
|
|
|
|
//(ITextPrinterImplementation)new VboTextPrinter() :
|
|
|
|
|
//GL.SupportsExtension("ARB_vertex_buffer_object") ? null :
|
|
|
|
|
//GL.SupportsExtension("VERSION_1_1") ? null : null;
|
|
|
|
|
if (printer == null)
|
|
|
|
|
throw new NotSupportedException("TextPrinter requires at least OpenGL 1.1 support.");
|
|
|
|
|
|
|
|
|
|
Debug.Print("Using {0} for font printing.", printer);
|
|
|
|
|
}
|
|
|
|
|
return printer;
|
|
|
|
|
}
|
2007-11-06 13:29:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-11-08 15:56:49 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2008-04-13 18:29:36 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2007-11-06 13:29:18 +00:00
|
|
|
|
#region --- ITextPrinter Members ---
|
|
|
|
|
|
2007-11-12 07:36:34 +00:00
|
|
|
|
#region public void Prepare(string text, TextureFont font, out TextHandle handle)
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Prepares text for drawing.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="text">The string to draw.</param>
|
|
|
|
|
/// <param name="font">The font to use for drawing.</param>
|
|
|
|
|
/// <param name="handle">The handle to the cached text. Use this to draw the text with the Draw() function.</param>
|
|
|
|
|
/// <see cref="TextPrinter.Draw()"/>
|
2007-11-06 13:29:18 +00:00
|
|
|
|
public void Prepare(string text, TextureFont font, out TextHandle handle)
|
|
|
|
|
{
|
|
|
|
|
this.Prepare(text, font, out handle, 0, false, StringAlignment.Near, false);
|
|
|
|
|
}
|
|
|
|
|
|
2007-11-12 07:36:34 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp)
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Prepares text for drawing.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="text">The string to draw.</param>
|
|
|
|
|
/// <param name="font">The font to use for drawing.</param>
|
|
|
|
|
/// <param name="handle">The handle to the cached text. Use this to draw the text with the Draw() function.</param>
|
|
|
|
|
/// <param name="width">Not implemented.</param>
|
|
|
|
|
/// <param name="wordWarp">Not implemented.</param>
|
|
|
|
|
/// <see cref="TextPrinter.Draw()"/>
|
2007-11-06 13:29:18 +00:00
|
|
|
|
public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp)
|
|
|
|
|
{
|
|
|
|
|
this.Prepare(text, font, out handle, width, wordWarp, StringAlignment.Near, false);
|
|
|
|
|
}
|
|
|
|
|
|
2007-11-12 07:36:34 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment)
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Prepares text for drawing.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="text">The string to draw.</param>
|
|
|
|
|
/// <param name="font">The font to use for drawing.</param>
|
|
|
|
|
/// <param name="handle">The handle to the cached text. Use this to draw the text with the Draw() function.</param>
|
|
|
|
|
/// <param name="width">Not implemented.</param>
|
|
|
|
|
/// <param name="wordWarp">Not implemented.</param>
|
|
|
|
|
/// <param name="alignment">Not implemented.</param>
|
|
|
|
|
/// <see cref="TextPrinter.Draw()"/>
|
2007-11-06 13:29:18 +00:00
|
|
|
|
public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment)
|
|
|
|
|
{
|
|
|
|
|
this.Prepare(text, font, out handle, width, wordWarp, alignment, false);
|
|
|
|
|
}
|
|
|
|
|
|
2007-11-12 07:36:34 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment, bool rightToLeft)
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Prepares text for drawing.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="text">The string to draw.</param>
|
|
|
|
|
/// <param name="font">The font to use for drawing.</param>
|
|
|
|
|
/// <param name="handle">The handle to the cached text. Use this to draw the text with the Draw() function.</param>
|
|
|
|
|
/// <param name="width">Not implemented.</param>
|
|
|
|
|
/// <param name="wordWarp">Not implemented.</param>
|
|
|
|
|
/// <param name="alignment">Not implemented.</param>
|
|
|
|
|
/// <param name="rightToLeft">Not implemented.</param>
|
|
|
|
|
/// <see cref="TextPrinter.Draw()"/>
|
2008-01-06 02:19:53 +00:00
|
|
|
|
/// <exception cref="NotSupportedException">Occurs when OpenGL 1.1 is not supported.</exception>
|
2007-11-06 13:29:18 +00:00
|
|
|
|
public void Prepare(string text, TextureFont font, out TextHandle handle, float width, bool wordWarp, StringAlignment alignment, bool rightToLeft)
|
|
|
|
|
{
|
2008-01-06 02:19:53 +00:00
|
|
|
|
int num_indices;
|
|
|
|
|
|
|
|
|
|
PerformLayout(text, font, width, wordWarp, alignment, rightToLeft, ref vertices, ref indices, out num_indices);
|
|
|
|
|
|
2008-04-13 18:29:36 +00:00
|
|
|
|
handle = Printer.Load(vertices, indices, num_indices);
|
2008-01-06 02:19:53 +00:00
|
|
|
|
handle.font = font;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region void PerformLayout(string text, TextureFont font, float width, bool wordWarp, StringAlignment alignment, bool rightToLeft, ref Vector2[] vertices, ref ushort[] indices, out int num_indices)
|
|
|
|
|
|
|
|
|
|
// Performs layout on the given string.
|
|
|
|
|
void PerformLayout(string text, TextureFont font, float width, bool wordWarp, StringAlignment alignment,
|
|
|
|
|
bool rightToLeft, ref Vector2[] vertices, ref ushort[] indices, out int num_indices)
|
|
|
|
|
{
|
2007-11-06 13:29:18 +00:00
|
|
|
|
if (text == null)
|
|
|
|
|
throw new ArgumentNullException("Parameter cannot be null.", "text");
|
|
|
|
|
|
2007-11-06 20:59:15 +00:00
|
|
|
|
if (text.Length > 8192)
|
|
|
|
|
throw new ArgumentOutOfRangeException("text", text.Length, "Text length must be between 1 and 8192 characters");
|
2007-11-06 13:29:18 +00:00
|
|
|
|
|
2007-11-12 07:36:34 +00:00
|
|
|
|
if (wordWarp || rightToLeft || alignment != StringAlignment.Near)
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
|
2008-01-06 02:19:53 +00:00
|
|
|
|
while (8 * text.Length > vertices.Length)
|
|
|
|
|
vertices = new Vector2[vertices.Length << 1];
|
|
|
|
|
|
|
|
|
|
while (6 * text.Length > indices.Length)
|
|
|
|
|
indices = new ushort[indices.Length << 1];
|
|
|
|
|
|
|
|
|
|
num_indices = 6 * text.Length;
|
|
|
|
|
|
|
|
|
|
//Vector2[] vertices = new Vector2[8 * text.Length]; // Interleaved, vertex, texcoord, vertex, etc...
|
|
|
|
|
//ushort[] indices = new ushort[6 * text.Length];
|
2007-11-06 13:29:18 +00:00
|
|
|
|
float x_pos = 0, y_pos = 0;
|
|
|
|
|
ushort i = 0, index_count = 0, vertex_count = 0, last_break_point = 0;
|
|
|
|
|
Box2 rect = new Box2();
|
|
|
|
|
float char_width, char_height, measured_width, measured_height;
|
|
|
|
|
int texture;
|
|
|
|
|
|
2007-11-06 20:59:15 +00:00
|
|
|
|
font.LoadGlyphs(text);
|
|
|
|
|
|
2007-11-06 13:29:18 +00:00
|
|
|
|
// Every character comprises of 4 vertices, forming two triangles. We generate an index array which
|
|
|
|
|
// indexes vertices in a triangle-strip fashion. To create a single strip for the whole string, we
|
|
|
|
|
// need to add a degenerate triangle (0 height) to connect the last vertex of the previous line with
|
|
|
|
|
// the first vertex of the next line.
|
|
|
|
|
// This algorithm works for left-to-right scripts.
|
|
|
|
|
|
2007-11-01 23:22:00 +00:00
|
|
|
|
if (alignment == StringAlignment.Near && !rightToLeft || alignment == StringAlignment.Far && rightToLeft)
|
|
|
|
|
{
|
|
|
|
|
foreach (char c in text)
|
|
|
|
|
{
|
|
|
|
|
if (Char.IsSeparator(c))
|
2007-11-06 13:29:18 +00:00
|
|
|
|
last_break_point = index_count;
|
2007-11-01 23:22:00 +00:00
|
|
|
|
|
2007-11-08 15:56:49 +00:00
|
|
|
|
if (c != '\n' && c != '\r')
|
2007-11-06 13:29:18 +00:00
|
|
|
|
{
|
|
|
|
|
font.GlyphData(c, out char_width, out char_height, out rect, out texture);
|
|
|
|
|
|
2008-01-06 02:19:53 +00:00
|
|
|
|
vertices[vertex_count].X = x_pos; // Vertex
|
|
|
|
|
vertices[vertex_count++].Y = y_pos;
|
|
|
|
|
vertices[vertex_count].X = rect.Left; // Texcoord
|
|
|
|
|
vertices[vertex_count++].Y = rect.Top;
|
|
|
|
|
vertices[vertex_count].X = x_pos; // Vertex
|
|
|
|
|
vertices[vertex_count++].Y = y_pos + char_height;
|
|
|
|
|
vertices[vertex_count].X = rect.Left; // Texcoord
|
|
|
|
|
vertices[vertex_count++].Y = rect.Bottom;
|
2007-11-01 23:22:00 +00:00
|
|
|
|
|
2008-01-06 02:19:53 +00:00
|
|
|
|
vertices[vertex_count].X = x_pos + char_width; // Vertex
|
2007-11-06 13:29:18 +00:00
|
|
|
|
vertices[vertex_count++].Y = y_pos + char_height;
|
2008-01-06 02:19:53 +00:00
|
|
|
|
vertices[vertex_count].X = rect.Right; // Texcoord
|
|
|
|
|
vertices[vertex_count++].Y = rect.Bottom;
|
|
|
|
|
vertices[vertex_count].X = x_pos + char_width; // Vertex
|
|
|
|
|
vertices[vertex_count++].Y = y_pos;
|
|
|
|
|
vertices[vertex_count].X = rect.Right; // Texcoord
|
|
|
|
|
vertices[vertex_count++].Y = rect.Top;
|
2007-11-06 13:29:18 +00:00
|
|
|
|
|
|
|
|
|
indices[index_count++] = (ushort)(vertex_count - 8);
|
|
|
|
|
indices[index_count++] = (ushort)(vertex_count - 6);
|
|
|
|
|
indices[index_count++] = (ushort)(vertex_count - 4);
|
|
|
|
|
|
|
|
|
|
indices[index_count++] = (ushort)(vertex_count - 4);
|
|
|
|
|
indices[index_count++] = (ushort)(vertex_count - 2);
|
|
|
|
|
indices[index_count++] = (ushort)(vertex_count - 8);
|
|
|
|
|
|
|
|
|
|
|
2008-02-02 12:29:21 +00:00
|
|
|
|
font.MeasureString(text.Substring(i, 1), out measured_width, out measured_height, false);
|
2007-11-06 13:29:18 +00:00
|
|
|
|
x_pos += measured_width;
|
|
|
|
|
}
|
2007-11-08 15:56:49 +00:00
|
|
|
|
else if (c == '\n')
|
|
|
|
|
{
|
|
|
|
|
//x_pos = layoutRect.Left;
|
|
|
|
|
x_pos = 0;
|
|
|
|
|
y_pos += font.Height;
|
|
|
|
|
}
|
2007-11-01 23:22:00 +00:00
|
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (alignment != StringAlignment.Center)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException("This feature is not yet implemented. Sorry for the inconvenience.");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException("This feature is not yet implemented. Sorry for the inconvenience.");
|
|
|
|
|
}
|
2007-11-06 13:29:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-11-12 07:36:34 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region public void Draw(TextHandle handle)
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Draws the cached text referred to by the TextHandle.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="handle">The TextHandle to the cached text.</param>
|
2007-11-06 13:29:18 +00:00
|
|
|
|
public void Draw(TextHandle handle)
|
|
|
|
|
{
|
2008-01-24 09:16:15 +00:00
|
|
|
|
GL.BindTexture(TextureTarget.Texture2D, handle.font.Texture);
|
2008-04-13 18:29:36 +00:00
|
|
|
|
|
|
|
|
|
Printer.Draw(handle);
|
2007-11-12 07:36:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2007-11-12 07:39:56 +00:00
|
|
|
|
#region public void Draw(string text, TextureFont font)
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Draws dynamic text without caching. Not implemented yet!
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="text">The System.String to draw.</param>
|
2008-03-08 14:38:10 +00:00
|
|
|
|
/// <param name="font">The OpenTK.Graphics.TextureFont to draw the text in.</param>
|
2007-11-12 07:39:56 +00:00
|
|
|
|
public void Draw(string text, TextureFont font)
|
|
|
|
|
{
|
2008-02-02 12:29:21 +00:00
|
|
|
|
int num_indices;
|
|
|
|
|
PerformLayout(text, font, 0, false, StringAlignment.Near, false, ref vertices, ref indices, out num_indices);
|
2008-04-13 18:29:36 +00:00
|
|
|
|
|
|
|
|
|
GL.BindTexture(TextureTarget.Texture2D, font.Texture);
|
|
|
|
|
|
|
|
|
|
Printer.Draw(vertices, indices, num_indices);
|
2007-11-12 07:39:56 +00:00
|
|
|
|
}
|
2007-11-12 07:36:34 +00:00
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region public void Begin()
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets up OpenGL state for drawing text.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Begin()
|
|
|
|
|
{
|
2008-04-13 18:29:36 +00:00
|
|
|
|
if (GraphicsContext.CurrentContext == null)
|
|
|
|
|
throw new GraphicsContextException("No GraphicsContext is current in the calling thread.");
|
|
|
|
|
|
2007-11-12 07:36:34 +00:00
|
|
|
|
GL.GetFloat(GetPName.Viewport, viewport);
|
|
|
|
|
|
|
|
|
|
// Prepare to draw text. We want pixel perfect precision, so we setup a 2D mode,
|
|
|
|
|
// with size equal to the window (in pixels).
|
|
|
|
|
// While we could also render text in 3D mode, it would be very hard to get
|
|
|
|
|
// pixel-perfect precision.
|
|
|
|
|
GL.MatrixMode(MatrixMode.Projection);
|
2007-12-01 16:51:30 +00:00
|
|
|
|
GL.PushMatrix();
|
2007-11-12 07:36:34 +00:00
|
|
|
|
GL.LoadIdentity();
|
2008-02-02 12:29:21 +00:00
|
|
|
|
GL.Ortho(viewport[0], viewport[2], viewport[3], viewport[1], -1.0, 1.0);
|
2007-11-12 07:36:34 +00:00
|
|
|
|
|
|
|
|
|
GL.MatrixMode(MatrixMode.Modelview);
|
2007-12-01 16:51:30 +00:00
|
|
|
|
GL.PushMatrix();
|
2007-11-12 07:36:34 +00:00
|
|
|
|
GL.LoadIdentity();
|
|
|
|
|
|
2008-04-13 18:29:36 +00:00
|
|
|
|
GL.PushAttrib(AttribMask.TextureBit | AttribMask.EnableBit | AttribMask.ColorBufferBit);
|
2007-11-06 13:29:18 +00:00
|
|
|
|
|
2008-01-24 09:16:15 +00:00
|
|
|
|
GL.Enable(EnableCap.Texture2D);
|
2007-11-06 13:29:18 +00:00
|
|
|
|
GL.Enable(EnableCap.Blend);
|
|
|
|
|
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
|
|
|
|
|
|
2007-11-08 15:56:49 +00:00
|
|
|
|
GL.Disable(EnableCap.DepthTest);
|
2007-11-12 07:36:34 +00:00
|
|
|
|
}
|
2007-11-06 20:59:15 +00:00
|
|
|
|
|
2007-11-12 07:36:34 +00:00
|
|
|
|
#endregion
|
2007-11-06 13:29:18 +00:00
|
|
|
|
|
2007-11-12 07:36:34 +00:00
|
|
|
|
#region public void End()
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Restores OpenGL state.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void End()
|
|
|
|
|
{
|
2007-11-06 13:29:18 +00:00
|
|
|
|
GL.PopAttrib();
|
2007-12-01 16:51:30 +00:00
|
|
|
|
|
2008-02-02 12:29:21 +00:00
|
|
|
|
GL.MatrixMode(MatrixMode.Modelview);
|
2007-12-01 16:51:30 +00:00
|
|
|
|
GL.PopMatrix();
|
|
|
|
|
|
2008-02-02 12:29:21 +00:00
|
|
|
|
GL.MatrixMode(MatrixMode.Projection);
|
2007-11-12 07:36:34 +00:00
|
|
|
|
GL.PopMatrix();
|
2007-11-06 13:29:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
2007-11-12 07:36:34 +00:00
|
|
|
|
|
|
|
|
|
#endregion
|
2007-11-06 13:29:18 +00:00
|
|
|
|
}
|
2007-11-01 23:22:00 +00:00
|
|
|
|
}
|