#region Using Directives using System; using System.Collections.Generic; using System.Text; using ScintillaNET.Properties; #endregion Using Directives namespace ScintillaNET { /// /// Represents a customizable read-only block of text which can be displayed below /// each line in a control. /// public class Annotation { #region Constants private const int INDIVIDUAL_STYLES = 0x100; // From the Scintilla source code: "implies array of styles" #endregion Constants #region Fields private Scintilla _scintilla; private int _lineIndex; #endregion Fields #region Methods private void CheckInvalid() { // Are we in a state where we can no longer accurately // represent the annotation we were originally created for? if (_lineIndex == -1) throw new InvalidOperationException(Resources.Exception_InvalidAnnotation); } /// /// Removes all text and styles associated with the annotation. /// public virtual void Clear() { CheckInvalid(); // Remove the annotation _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONSETTEXT, new IntPtr(_lineIndex), IntPtr.Zero); } /// /// Overridden. Determines whether the specified is equal to the current . /// /// The object to compare with the current object. /// /// true if the specified is equal to the /// current ; otherwise, false. /// public override bool Equals(object obj) { if (obj is Annotation) { // If another annotation has the same Scintilla // control and line index--it is the same. Annotation a = (Annotation)obj; if (a._scintilla == _scintilla && a._lineIndex == _lineIndex) return true; } return false; } /// /// Determines whether the specified is equal to the current . /// /// The annotation to compare with the current annotation. /// /// true if the specified is equal to the /// current ; otherwise, false. /// public virtual bool Equals(Annotation a) { // Per Microsoft's recommendations we have an Equals for // our specific type "to enhance performance". if (a != null) { // Just remember to keep in sync with standard Equals above if (a._scintilla == _scintilla && a._lineIndex == _lineIndex) return true; } return false; } /// /// Overridden. Serves as a hash function for a particular type. /// /// A hash code for the current . public override int GetHashCode() { return _scintilla.GetHashCode() ^ _lineIndex; } /// /// Returns a enumerable representing the individual character styling of the annotation text. /// /// /// A enumerable representing the individual character styling, /// where the property of each run represents the number /// of characters the run spans. /// public virtual IEnumerable GetStyles() { CheckInvalid(); // We need to translate the array Scintilla gives us representing the style of each text // byte into a list of style runs. Our run lengths, however, are measured in characters, // not bytes, so we need to also read the annotation text and adjust as necessary when we find // characters that span more than one byte. int length = _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32(); byte[] textBuffer = new byte[length]; byte[] stylesBuffer = new byte[length]; unsafe { fixed (byte* bp = textBuffer) _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), new IntPtr(bp)).ToInt32(); fixed (byte* bp = stylesBuffer) _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETSTYLES, new IntPtr(_lineIndex), new IntPtr(bp)).ToInt32(); } List styles = new List(); Decoder decoder = _scintilla.Encoding.GetDecoder(); StyleRun sr = new StyleRun() { Style = -1 }; int index = 0; int count = 1; while (index < stylesBuffer.Length) { if (sr.Style != stylesBuffer[index]) { // A new style has been encountered. Save the last one // to the list we're building and start tracking a new one if (sr.Length > 0) styles.Add(sr); sr = new StyleRun(); sr.Style = stylesBuffer[index]; } // At the end of this loop, the 'count' variable will tell us // how many bytes there are for one character. while (decoder.GetCharCount(textBuffer, index, count) != 1) count++; sr.Length++; index += count; count = 1; } // Add the last style run styles.Add(sr); return styles.ToArray(); } /* private void ScintillaTextChangedHandler(object sender, EventArgs e) { // Why listen for text change events? We're not meant to be a long living // object. Should any user forget that and assume we're still valid after // the text has changed we'll remind them. In the future we could optimze // this by only invalidating if our line has been changed (deleted). _scintilla.TextChanged -= new EventHandler(ScintillaTextChangedHandler); _lineIndex = -1; } */ /// /// Uses the enumerable specified to individually style characters in the annotation text. /// /// /// The enumerable indicating how to style the annotation text, /// where the property of each run represents the number /// of characters the run spans. /// /// is null. /// /// The property must be set prior to styling and the sum length of /// all runs should match the text length. /// public virtual void SetStyles(IEnumerable styles) { CheckInvalid(); if (styles == null) throw new ArgumentNullException("styles"); // We need to build a byte array with a style byte for each text byte. // Our style runs are in character lengths (good for our user) but we need to // convert them to byte lengths (bad for us). To do that we need to analyze the // annotation text and determine if any of our character lengths span more than // one byte and fill the array accordingly. int length = _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32(); byte[] textBuffer = new byte[length]; byte[] stylesBuffer = new byte[length]; unsafe { fixed (byte* bp = textBuffer) _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), new IntPtr(bp)).ToInt32(); } Decoder decoder = _scintilla.Encoding.GetDecoder(); StyleRun sr; int index = 0; int count = 1; using (IEnumerator enumerator = styles.GetEnumerator()) { while (enumerator.MoveNext()) { sr = enumerator.Current; while (sr.Length > 0 && index < stylesBuffer.Length) { // At the end of this loop, the 'count' variable will tell us // how many bytes there are for one character. while (decoder.GetCharCount(textBuffer, index, count) != 1) count++; // For each character of text (the unit of our style runs) // add the appropriate number of style bytes. for (int i = 0; i < count; i++) stylesBuffer[index + i] = (byte)sr.Style; index += count; count = 1; sr.Length--; }; } } // Our processing above is designed to stop if we are given more style run data // than we have bytes to fill. If we are given less style run data than we have // bytes to fill, the remaining bytes just get their default value of 0. unsafe { fixed (byte* bp = stylesBuffer) _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONSETSTYLES, new IntPtr(_lineIndex), new IntPtr(bp)); } } #endregion Methods #region Properties /// /// Gets the total number of text lines in the annotation. /// /// An representing the total number of text lines in the annotation. public virtual int LineCount { get { CheckInvalid(); return _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETLINES, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32(); } } /// /// Gets the index of the document line containing the annotation. /// /// /// An representing the zero-based index of the document line /// containing the annotation, or -1 if the annotation has been rendered invalid /// from a change in the control that created it. /// public int LineIndex { get { return _lineIndex; } } /// /// Gets or sets the index of the style used to style the annotation text. /// /// /// An representing the zero-based index of the style used to style the annotation text, /// or -1 if the annotation has individually style characters. /// public virtual int Style { get { CheckInvalid(); // By default Scintilla will return a value of 256 when there are individual styles. In the .NET // world that would be a little weird for our users. A more common pattern would be to return -1. int style = _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETSTYLE, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32(); if (style == INDIVIDUAL_STYLES) return -1; return style; } set { CheckInvalid(); _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONSETSTYLE, new IntPtr(_lineIndex), new IntPtr(value)).ToInt32(); } } /// /// Gets or sets the text of the annotation. /// /// A representing the annotation text, or null if there is no annotation. /// /// Only line feed characters ('\n') are recognized as line breaks. /// All other control characters are not rendered. /// public virtual string Text { get { CheckInvalid(); // Normally you wouldn't want to return null from a text property because // an empty string usually means the same thing and avoids null reference // exceptions. However, in Scintilla a null annotation is very different // from an empty string in the way they are rendered. For that reason we have // to support returning null from this property and this is the only reliable // way that I've found to do it. if (LineCount == 0) return null; // Determine the buffer size, fill it, and convert it to a string int length = _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), IntPtr.Zero).ToInt32(); byte[] buffer = new byte[length]; unsafe { fixed (byte* bp = buffer) length = (int)_scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONGETTEXT, new IntPtr(_lineIndex), new IntPtr(bp)).ToInt32(); } return _scintilla.Encoding.GetString(buffer, 0, length); } set { CheckInvalid(); if (value == null) { // Same thing... Clear(); return; } unsafe { // Set the annotation text fixed (byte* bp = Utilities.GetZeroTerminatedBytes(value, _scintilla.Encoding)) _scintilla.DirectMessage(NativeMethods.SCI_ANNOTATIONSETTEXT, new IntPtr(_lineIndex), new IntPtr(bp)); } } } #endregion Properties #region Operators /// /// Tests whether two object differ in location or content. /// /// The object that is to the left of the inequality operator. /// The object that is to the right of the inequality operator. /// true if the objects are considered unequal; otherwise, false. public static bool operator !=(Annotation left, Annotation right) { return !(left == right); } /// /// Tests whether two objects have equal location and content. /// /// The object that is to the left of the equality operator. /// The object that is to the right of the equality operator. /// true if the objects are considered equal; otherwise, false. public static bool operator ==(Annotation left, Annotation right) { // If both are null, or both are same instance if (Object.ReferenceEquals(left, right)) return true; // If one is null, but not both if (((object)left == null) || ((object)right == null)) return false; return left.Equals((Annotation)right); } #endregion Operators #region Constructors /// /// Initializes a new instance of the class. /// /// The control that created this object. /// The zero-based index of the document line containing the annotation. protected internal Annotation(Scintilla scintilla, int lineIndex) { _lineIndex = lineIndex; _scintilla = scintilla; /*_scintilla.TextChanged += new EventHandler(ScintillaTextChangedHandler);*/ } #endregion Constructors } }