// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// 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.
using System;
using System.Collections.Generic;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Rendering
{
///
/// Represents a visual element in the document.
///
public abstract class VisualLineElement
{
///
/// Creates a new VisualLineElement.
///
/// The length of the element in VisualLine coordinates. Must be positive.
/// The length of the element in the document. Must be non-negative.
protected VisualLineElement(int visualLength, int documentLength)
{
if (visualLength < 1)
throw new ArgumentOutOfRangeException("visualLength", visualLength, "Value must be at least 1");
if (documentLength < 0)
throw new ArgumentOutOfRangeException("documentLength", documentLength, "Value must be at least 0");
this.VisualLength = visualLength;
this.DocumentLength = documentLength;
}
///
/// Gets the length of this element in visual columns.
///
public int VisualLength { get; private set; }
///
/// Gets the length of this element in the text document.
///
public int DocumentLength { get; private set; }
///
/// Gets the visual column where this element starts.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
Justification = "This property holds the start visual column, use GetVisualColumn to get inner visual columns.")]
public int VisualColumn { get; internal set; }
///
/// Gets the text offset where this element starts, relative to the start text offset of the visual line.
///
public int RelativeTextOffset { get; internal set; }
///
/// Gets the text run properties.
/// A unique instance is used for each
/// ; colorizing code may assume that modifying the
/// will affect only this
/// .
///
public VisualLineElementTextRunProperties TextRunProperties { get; private set; }
///
/// Gets/sets the brush used for the background of this .
///
public Brush BackgroundBrush { get; set; }
internal void SetTextRunProperties(VisualLineElementTextRunProperties p)
{
this.TextRunProperties = p;
}
///
/// Creates the TextRun for this line element.
///
///
/// The visual column from which the run should be constructed.
/// Normally the same value as the property is used to construct the full run;
/// but when word-wrapping is active, partial runs might be created.
///
///
/// Context object that contains information relevant for text run creation.
///
public abstract TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context);
///
/// Retrieves the text span immediately before the visual column.
///
/// This method is used for word-wrapping in bidirectional text.
public virtual TextSpan GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context)
{
return null;
}
///
/// Gets if this VisualLineElement can be split.
///
public virtual bool CanSplit {
get { return false; }
}
///
/// Splits the element.
///
/// Position inside this element at which it should be broken
/// The collection of line elements
/// The index at which this element is in the elements list.
public virtual void Split(int splitVisualColumn, IList elements, int elementIndex)
{
throw new NotSupportedException();
}
///
/// Helper method for splitting this line element into two, correctly updating the
/// , ,
/// and properties.
///
/// The element before the split position.
/// The element after the split position.
/// The split position as visual column.
/// The split position as text offset.
protected void SplitHelper(VisualLineElement firstPart, VisualLineElement secondPart, int splitVisualColumn, int splitRelativeTextOffset)
{
if (firstPart == null)
throw new ArgumentNullException("firstPart");
if (secondPart == null)
throw new ArgumentNullException("secondPart");
int relativeSplitVisualColumn = splitVisualColumn - VisualColumn;
int relativeSplitRelativeTextOffset = splitRelativeTextOffset - RelativeTextOffset;
if (relativeSplitVisualColumn <= 0 || relativeSplitVisualColumn >= VisualLength)
throw new ArgumentOutOfRangeException("splitVisualColumn", splitVisualColumn, "Value must be between " + (VisualColumn + 1) + " and " + (VisualColumn + VisualLength - 1));
if (relativeSplitRelativeTextOffset < 0 || relativeSplitRelativeTextOffset > DocumentLength)
throw new ArgumentOutOfRangeException("splitRelativeTextOffset", splitRelativeTextOffset, "Value must be between " + (RelativeTextOffset) + " and " + (RelativeTextOffset + DocumentLength));
int oldVisualLength = VisualLength;
int oldDocumentLength = DocumentLength;
int oldVisualColumn = VisualColumn;
int oldRelativeTextOffset = RelativeTextOffset;
firstPart.VisualColumn = oldVisualColumn;
secondPart.VisualColumn = oldVisualColumn + relativeSplitVisualColumn;
firstPart.RelativeTextOffset = oldRelativeTextOffset;
secondPart.RelativeTextOffset = oldRelativeTextOffset + relativeSplitRelativeTextOffset;
firstPart.VisualLength = relativeSplitVisualColumn;
secondPart.VisualLength = oldVisualLength - relativeSplitVisualColumn;
firstPart.DocumentLength = relativeSplitRelativeTextOffset;
secondPart.DocumentLength = oldDocumentLength - relativeSplitRelativeTextOffset;
if (firstPart.TextRunProperties == null)
firstPart.TextRunProperties = TextRunProperties.Clone();
if (secondPart.TextRunProperties == null)
secondPart.TextRunProperties = TextRunProperties.Clone();
}
///
/// Gets the visual column of a text location inside this element.
/// The text offset is given relative to the visual line start.
///
public virtual int GetVisualColumn(int relativeTextOffset)
{
if (relativeTextOffset >= this.RelativeTextOffset + DocumentLength)
return VisualColumn + VisualLength;
else
return VisualColumn;
}
///
/// Gets the text offset of a visual column inside this element.
///
/// A text offset relative to the visual line start.
public virtual int GetRelativeOffset(int visualColumn)
{
if (visualColumn >= this.VisualColumn + VisualLength)
return RelativeTextOffset + DocumentLength;
else
return RelativeTextOffset;
}
///
/// Gets the next caret position inside this element.
///
/// The visual column from which the search should be started.
/// The search direction (forwards or backwards).
/// Whether to stop only at word borders.
/// The visual column of the next caret position, or -1 if there is no next caret position.
///
/// In the space between two line elements, it is sufficient that one of them contains a caret position;
/// though in many cases, both of them contain one.
///
public virtual int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
{
int stop1 = this.VisualColumn;
int stop2 = this.VisualColumn + this.VisualLength;
if (direction == LogicalDirection.Backward) {
if (visualColumn > stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol)
return stop2;
else if (visualColumn > stop1)
return stop1;
} else {
if (visualColumn < stop1)
return stop1;
else if (visualColumn < stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol)
return stop2;
}
return -1;
}
///
/// Gets whether the specified offset in this element is considered whitespace.
///
public virtual bool IsWhitespace(int visualColumn)
{
return false;
}
///
/// Gets whether the implementation handles line borders.
/// If this property returns false, the caller of GetNextCaretPosition should handle the line
/// borders (i.e. place caret stops at the start and end of the line).
/// This property has an effect only for VisualLineElements that are at the start or end of a
/// .
///
public virtual bool HandlesLineBorders {
get { return false; }
}
///
/// Queries the cursor over the visual line element.
///
protected internal virtual void OnQueryCursor(QueryCursorEventArgs e)
{
}
///
/// Allows the visual line element to handle a mouse event.
///
protected internal virtual void OnMouseDown(MouseButtonEventArgs e)
{
}
///
/// Allows the visual line element to handle a mouse event.
///
protected internal virtual void OnMouseUp(MouseButtonEventArgs e)
{
}
}
}