aboutsummaryrefslogtreecommitdiff
path: root/source/ShiftUI/Internal/LineTag.cs
diff options
context:
space:
mode:
authorMichaelTheShifter <[email protected]>2016-07-20 09:40:36 -0400
committerMichaelTheShifter <[email protected]>2016-07-20 09:40:36 -0400
commitd40fed5ce2bc806a91245adb18039634eac13ed0 (patch)
treef1d7168aee6db109ac2c738ad18c9db667a6ba69 /source/ShiftUI/Internal/LineTag.cs
parentf1856e8ed30ed882229fd3fa2a4038122a5fb441 (diff)
downloadshiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.tar.gz
shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.tar.bz2
shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.zip
Move ShiftUI source code to ShiftOS
This'll be a lot easier to work on.
Diffstat (limited to 'source/ShiftUI/Internal/LineTag.cs')
-rw-r--r--source/ShiftUI/Internal/LineTag.cs618
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 &gt;pos&lt; 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
+ }
+}