diff options
Diffstat (limited to 'source/ShiftUI/Internal/LineTag.cs')
| -rw-r--r-- | source/ShiftUI/Internal/LineTag.cs | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/source/ShiftUI/Internal/LineTag.cs b/source/ShiftUI/Internal/LineTag.cs new file mode 100644 index 0000000..e34132c --- /dev/null +++ b/source/ShiftUI/Internal/LineTag.cs @@ -0,0 +1,618 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com) +// +// Authors: +// Peter Bartok [email protected] +// +// + +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Text; +using System.Text; + +namespace ShiftUI +{ + internal class LineTag + { + #region Local Variables + // Formatting + private Font font; // System.Drawing.Font object for this tag + private Color color; // The font color for this tag + private Color back_color; // In 2.0 tags can have background colours. + private Font link_font; // Cached font used for link if IsLink + private bool is_link; // Whether this tag is a link + private string link_text; // The full link text e.g. this might be + // word-wrapped to "w" but this would be + // "www.mono-project.com" + + // Payload; text + private int start; // start, in chars; index into Line.text + // 1 based!! + + // Drawing support + private int height; // Height in pixels of the text this tag describes + private int ascent; // Ascent of the font for this tag + private int descent; // Descent of the font for this tag + private int shift; // Shift down for this tag, to stay on baseline + + // Administrative + private Line line; // The line we're on + private LineTag next; // Next tag on the same line + private LineTag previous; // Previous tag on the same line + #endregion + + #region Constructors + public LineTag (Line line, int start) + { + this.line = line; + Start = start; + link_font = null; + is_link = false; + link_text = null; + } + #endregion // Constructors + + #region Public Properties + public int Ascent { + get { return ascent; } + } + + public Color BackColor { + get { return back_color; } + set { back_color = value; } + } + + public Color ColorToDisplay { + get { + if (IsLink == true) + return Color.Blue; + + return color; + } + } + + public Color Color { + get { return color; } + set { color = value; } + } + + public int Descent { + get { return descent; } + } + + public int End { + get { return start + Length; } + } + + public Font FontToDisplay { + get { + if (IsLink) { + if (link_font == null) + link_font = new Font (font.FontFamily, font.Size, font.Style | FontStyle.Underline); + + return link_font; + } + + return font; + } + } + + public Font Font { + get { return font; } + set { + if (font != value) { + link_font = null; + font = value; + + height = Font.Height; + XplatUI.GetFontMetrics (Hwnd.GraphicsContext, Font, out ascent, out descent); + line.recalc = true; + } + } + } + + public int Height { + get { return height; } + set { height = value; } + } + + public virtual bool IsTextTag { + get { return true; } + } + + public int Length { + get { + int res = 0; + if (next != null) + res = next.start - start; + else + res = line.text.Length - (start - 1); + + return res > 0 ? res : 0; + } + } + + public Line Line { + get { return line; } + set { line = value; } + } + + public LineTag Next { + get { return next; } + set { next = value; } + } + + public LineTag Previous { + get { return previous; } + set { previous = value; } + } + + public int Shift { + get { return shift; } + set { shift = value; } + } + + public int Start { + get { return start; } + set { +#if DEBUG + if (value <= 0) + throw new Exception("Start of tag must be 1 or higher!"); + + if (this.Previous != null) { + if (this.Previous.Start == value) + System.Console.Write("Creating empty tag"); + if (this.Previous.Start > value) + throw new Exception("New tag makes an insane tag"); + } +#endif + start = value; + } + } + + public int TextEnd { + get { return start + TextLength; } + } + + public int TextLength { + get { + int res = 0; + if (next != null) + res = next.start - start; + else + res = line.TextLengthWithoutEnding () - (start - 1); + + return res > 0 ? res : 0; + } + } + + public float Width { + get { + if (Length == 0) + return 0; + return line.widths [start + Length - 1] - (start != 0 ? line.widths [start - 1] : 0); + } + } + + public float X { + get { + if (start == 0) + return line.X; + return line.X + line.widths [start - 1]; + } + } + + public bool IsLink { + get { return is_link; } + set { is_link = value; } + } + + public string LinkText { + get { return link_text; } + set { link_text = value; } + } + #endregion + + #region Public Methods + ///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at >pos< or null if end-of-line</summary> + public LineTag Break (int pos) + { + LineTag new_tag; + +#if DEBUG + // Sanity + if (pos < this.Start) + throw new Exception ("Breaking at a negative point"); +#endif + +#if DEBUG + if (pos > End) + throw new Exception ("Breaking past the end of a line"); +#endif + + new_tag = new LineTag(line, pos); + new_tag.CopyFormattingFrom (this); + + new_tag.next = this.next; + this.next = new_tag; + new_tag.previous = this; + + if (new_tag.next != null) + new_tag.next.previous = new_tag; + + return new_tag; + } + + /// <summary>Combines 'this' tag with 'other' tag</summary> + public bool Combine (LineTag other) + { + if (!this.Equals (other)) + return false; + + this.next = other.next; + + if (this.next != null) + this.next.previous = this; + + return true; + } + + public void CopyFormattingFrom (LineTag other) + { + Font = other.font; + color = other.color; + back_color = other.back_color; + } + + public void Delete () + { + // If we are the only tag, we can't be deleted + if (previous == null && next == null) + return; + + // If we are the last tag, deletion is easy + if (next == null) { + previous.next = null; + return; + } + + // Easy cases gone, little tougher, delete ourself + // Update links, and start + next.previous = null; + + LineTag loop = next; + + while (loop != null) { + loop.Start -= Length; + loop = loop.next; + } + + return; + } + + public virtual void Draw (Graphics dc, Color color, float x, float y, int start, int end) + { + TextBoxTextRenderer.DrawText (dc, line.text.ToString (start, end).Replace ("\r", string.Empty), FontToDisplay, color, x, y, false); + } + + public virtual void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text) + { + Rectangle measured_text; + Draw (dc, color, xoff, y, start, end, text, out measured_text, false); + } + + /// <summary> + /// + /// </summary> + /// <param name="drawStart">0 based start index</param> + public virtual void Draw (Graphics dc, Color color, float xoff, float y, int drawStart, int drawEnd, + string text, out Rectangle measuredText, bool measureText) + { + if (measureText) { + int xstart = (int)line.widths [drawStart] + (int)xoff; + int xend = (int)line.widths [drawEnd] - (int)line.widths [drawStart]; + int ystart = (int)y; + int yend = (int)TextBoxTextRenderer.MeasureText (dc, Text (), FontToDisplay).Height; + + measuredText = new Rectangle (xstart, ystart, xend, yend); + } else { + measuredText = new Rectangle (); + } + + while (drawStart < drawEnd) { + int tab_index = text.IndexOf ("\t", drawStart); + + if (tab_index == -1) + tab_index = drawEnd; + + TextBoxTextRenderer.DrawText (dc, text.Substring (drawStart, tab_index - drawStart).Replace ("\r", string.Empty), FontToDisplay, color, xoff + line.widths [drawStart], y, false); + + // non multilines get the unknown char + if (!line.document.multiline && tab_index != drawEnd) + TextBoxTextRenderer.DrawText (dc, "\u0013", FontToDisplay, color, xoff + line.widths [tab_index], y, true); + + drawStart = tab_index + 1; + } + } + + /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary> + public override bool Equals (object obj) + { + LineTag other; + + if (obj == null) + return false; + + if (!(obj is LineTag)) + return false; + + if (obj == this) + return true; + + other = (LineTag)obj; + + if (other.IsTextTag != IsTextTag) + return false; + + if (this.IsLink != other.IsLink) + return false; + + if (this.LinkText != other.LinkText) + return false; + + if (this.font.Equals (other.font) && this.color.Equals (other.color)) + return true; + + return false; + } + + /// <summary>Finds the tag that describes the character at position 'pos' (0 based) on 'line'</summary> + public static LineTag FindTag (Line line, int pos) + { + LineTag tag = line.tags; + + // Beginning of line is a bit special + if (pos == 0) + return tag; // Not sure if we should get the final tag here + + while (tag != null) { + // [H e][l][l o _ W][o r] Text + // [1 2][3][4 5 6 7][8 9] Start + // 3 4 8 10 End + // 0 1 2 3 4 5 6 7 8 9 Pos + if ((tag.start <= pos) && (pos < tag.End)) + return GetFinalTag (tag); + + tag = tag.next; + } + + return null; + } + + /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars; + /// Removes any previous tags overlapping the same area; + /// returns true if lineheight has changed</summary> + /// <param name="formatStart">1-based character position on line</param> + public static bool FormatText (Line line, int formatStart, int length, Font font, Color color, Color backColor, FormatSpecified specified) + { + LineTag tag; + LineTag start_tag; + LineTag end_tag; + int end; + bool retval = false; // Assume line-height doesn't change + + // Too simple? + if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height) + retval = true; + + line.recalc = true; // This forces recalculation of the line in RecalculateDocument + + // A little sanity, not sure if it's needed, might be able to remove for speed + if (length > line.text.Length) + length = line.text.Length; + + tag = line.tags; + end = formatStart + length; + + // Common special case + if ((formatStart == 1) && (length == tag.Length)) { + SetFormat (tag, font, color, backColor, specified); + return retval; + } + + // empty selection style at begining of line means + // we only need one new tag + if (formatStart == 1 && length == 0) { + line.tags.Break (1); + SetFormat (line.tags, font, color, backColor, specified); + return retval; + } + + start_tag = FindTag (line, formatStart - 1); + + // we are at an empty tag already! + // e.g. [Tag 0 - "He"][Tag 1 = 0 length][Tag 2 "llo world"] + // Find Tag will return tag 0 at position 3, but we should just + // use the empty tag after.. + if (start_tag.End == formatStart && length == 0 && start_tag.Next != null && start_tag.Next.Length == 0) { + SetFormat (start_tag.Next, font, color, backColor, specified); + return retval; + } + + // if we are at the end of a tag, we want to move to the next tag + while (start_tag.End == formatStart && start_tag.Next != null) + start_tag = start_tag.Next; + + tag = start_tag.Break (formatStart); + + // empty selection style at end of line - its the only situation + // where the rest of the tag would be empty, since we moved to the + // begining of next non empty tag + if (tag.Length == 0) { + SetFormat (tag, font, color, backColor, specified); + return retval; + } + + // empty - so we just create another tag for + // after our new (now) empty one.. + if (length == 0) { + tag.Break (formatStart); + SetFormat (tag, font, color, backColor, specified); + return retval; + } + + while (tag != null && tag.End <= end) { + SetFormat (tag, font, color, backColor, specified); + tag = tag.next; + } + + // did the last tag conveniently fit? + if (tag != null && tag.End == end) + return retval; + + /// Now do the last tag + end_tag = FindTag (line, end-1); + + if (end_tag != null) { + end_tag.Break (end); + SetFormat (end_tag, font, color, backColor, specified); + } + + return retval; + } + + // Gets the character at the x-coordinate. Index is based from the + // line, not the start of the tag. + // returns 0 based index (0 means before character at 1, 1 means at character 1) + public int GetCharIndex (int x) + { + int low = start; + int high = low + Length; + int length_no_ending = line.TextLengthWithoutEnding (); + + if (Length == 0) + return low-1; + + if (length_no_ending == 0) + return 0; + + if (x < line.widths [low]) { + if (low == 1 && x > (line.widths [1] / 2)) + return low; + return low - 1; + } + + if (x > line.widths[length_no_ending]) + return length_no_ending; + + while (low < high - 1) { + int mid = (high + low) / 2; + float width = line.widths[mid]; + + if (width < x) + low = mid; + else + high = mid; + } + + float char_width = line.widths[high] - line.widths[low]; + + if ((x - line.widths[low]) >= (char_width / 2)) + return high; + else + return low; + } + + // There can be multiple tags at the same position, we want to make + // sure we are using the very last tag at the given position + // Empty tags are necessary if style is set at a position with + // no length. + public static LineTag GetFinalTag (LineTag tag) + { + LineTag res = tag; + + while (res.Length == 0 && res.next != null && res.next.Length == 0) + res = res.next; + + return res; + } + + public override int GetHashCode () + { + return base.GetHashCode (); + } + + internal virtual int MaxHeight () + { + return font.Height; + } + + private static void SetFormat (LineTag tag, Font font, Color color, Color back_color, FormatSpecified specified) + { + if ((FormatSpecified.Font & specified) == FormatSpecified.Font) { + tag.Font = font; + } + if ((FormatSpecified.Color & specified) == FormatSpecified.Color) + tag.color = color; + if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) { + tag.back_color = back_color; + } + // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color); + } + + public virtual SizeF SizeOfPosition (Graphics dc, int pos) + { + if (pos >= line.TextLengthWithoutEnding () && line.document.multiline) + return SizeF.Empty; + + string text = line.text.ToString (pos, 1); + switch ((int) text [0]) { + case '\t': + if (!line.document.multiline) + goto case 10; + SizeF res = TextBoxTextRenderer.MeasureText (dc, " ", font); + res.Width *= 8.0F; + return res; + case 10: + case 13: + return TextBoxTextRenderer.MeasureText (dc, "\u000D", font); + } + + return TextBoxTextRenderer.MeasureText (dc, text, font); + } + + public virtual string Text () + { + return line.text.ToString (start - 1, Length); + } + + public override string ToString () + { + if (Length > 0) + return string.Format ("{0} Tag starts at index: {1}, length: {2}, text: {3}, font: {4}", GetType (), start, Length, Text (), font.ToString ()); + + return string.Format ("Zero Length tag at index: {0}", start); + } + #endregion // Internal Methods + } +} |
