Browse Source

AvalonEdit: Initial implementation of virtual space.

pull/23/head
Daniel Grunwald 15 years ago committed by Siegfried Pammer
parent
commit
600ba51dab
  1. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
  2. 18
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs
  3. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs
  4. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs
  5. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
  6. 91
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
  7. 19
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs

@ -289,7 +289,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -289,7 +289,7 @@ namespace ICSharpCode.AvalonEdit.Editing
if (offsetFromVisualColumn != caretOffset) {
position.VisualColumn = visualLine.GetVisualColumn(caretOffset - firstDocumentLineOffset);
} else {
if (position.VisualColumn > visualLine.VisualLength) {
if (position.VisualColumn > visualLine.VisualLength && !textArea.Options.EnableVirtualSpace) {
position.VisualColumn = visualLine.VisualLength;
}
}
@ -347,7 +347,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -347,7 +347,7 @@ namespace ICSharpCode.AvalonEdit.Editing
}
TextLine textLine = visualLine.GetTextLine(position.VisualColumn);
double xPos = textLine.GetDistanceFromCharacterHit(new CharacterHit(position.VisualColumn, 0));
double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn);
double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineBottom);

18
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/CaretNavigationCommandHandler.cs

@ -4,10 +4,10 @@ @@ -4,10 +4,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media.TextFormatting;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Utils;
@ -250,7 +250,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -250,7 +250,7 @@ namespace ICSharpCode.AvalonEdit.Editing
// moving up/down happens using the desired visual X position
double xPos = textArea.Caret.DesiredXPos;
if (double.IsNaN(xPos))
xPos = textLine.GetDistanceFromCharacterHit(new CharacterHit(caretVisualColumn, 0));
xPos = visualLine.GetTextLineVisualXPosition(textLine, caretVisualColumn);
// now find the TextLine+VisualLine where the caret will end up in
VisualLine targetVisualLine = visualLine;
TextLine targetLine;
@ -306,8 +306,9 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -306,8 +306,9 @@ namespace ICSharpCode.AvalonEdit.Editing
throw new NotSupportedException(direction.ToString());
}
if (targetLine != null) {
CharacterHit ch = targetLine.GetCharacterHitFromDistance(xPos);
SetCaretPosition(textArea, targetVisualLine, targetLine, ch, false);
double yPos = targetVisualLine.GetTextLineVisualYPosition(targetLine, VisualYPosition.LineMiddle);
int newVisualColumn = targetVisualLine.GetVisualColumn(new Point(xPos, yPos));
SetCaretPosition(textArea, targetVisualLine, targetLine, newVisualColumn, false);
textArea.Caret.DesiredXPos = xPos;
}
}
@ -315,12 +316,13 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -315,12 +316,13 @@ namespace ICSharpCode.AvalonEdit.Editing
#region SetCaretPosition
static void SetCaretPosition(TextArea textArea, VisualLine targetVisualLine, TextLine targetLine,
CharacterHit ch, bool allowWrapToNextLine)
int newVisualColumn, bool allowWrapToNextLine)
{
int newVisualColumn = ch.FirstCharacterIndex + ch.TrailingLength;
int targetLineStartCol = targetVisualLine.GetTextLineVisualStartColumn(targetLine);
if (!allowWrapToNextLine && newVisualColumn >= targetLineStartCol + targetLine.Length)
newVisualColumn = targetLineStartCol + targetLine.Length - 1;
if (!allowWrapToNextLine && newVisualColumn >= targetLineStartCol + targetLine.Length) {
if (newVisualColumn <= targetVisualLine.VisualLength)
newVisualColumn = targetLineStartCol + targetLine.Length - 1;
}
int newOffset = targetVisualLine.GetRelativeOffset(newVisualColumn) + targetVisualLine.FirstDocumentLine.Offset;
SetCaretPosition(textArea, newVisualColumn, newOffset);
}

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/RectangleSelection.cs

@ -13,7 +13,7 @@ using ICSharpCode.AvalonEdit.Utils; @@ -13,7 +13,7 @@ using ICSharpCode.AvalonEdit.Utils;
namespace ICSharpCode.AvalonEdit.Editing
{
/// <summary>
/// Rectangular selection.
/// Rectangular selection ("box selection").
/// </summary>
public sealed class RectangleSelection : Selection
{

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/BackgroundGeometryBuilder.cs

@ -129,7 +129,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -129,7 +129,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
if (segmentStartVCInLine == segmentEndVCInLine) {
// GetTextBounds crashes for length=0, so we'll handle this case with GetDistanceFromCharacterHit
// We need to return a rectangle to ensure empty lines are still visible
double pos = line.GetDistanceFromCharacterHit(new CharacterHit(segmentStartVCInLine, 0));
double pos = vl.GetTextLineVisualXPosition(line, segmentStartVCInLine);
pos -= scrollOffset.X;
// The following special cases are necessary to get rid of empty rectangles at the end of a TextLine if "Show Spaces" is active.
// If not excluded once, the same rectangle is calculated (and added) twice (since the offset could be mapped to two visual positions; end/start of line), if there is no trailing whitespace.

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs

@ -1360,7 +1360,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -1360,7 +1360,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
double wideSpaceWidth; // Width of an 'x'. Used as basis for the tab width, and for scrolling.
double defaultLineHeight; // Height of a line containing 'x'. Used for scrolling.
double WideSpaceWidth {
internal double WideSpaceWidth {
get {
if (wideSpaceWidth == 0) {
MeasureWideSpaceWidthAndDefaultLineHeight();

91
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs

@ -224,8 +224,9 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -224,8 +224,9 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// </summary>
public TextLine GetTextLine(int visualColumn)
{
ThrowUtil.CheckInRangeInclusive(visualColumn, "visualColumn", 0, VisualLength);
if (visualColumn == VisualLength)
if (visualColumn < 0)
throw new ArgumentOutOfRangeException("visualColumn");
if (visualColumn >= VisualLength)
return TextLines[TextLines.Count - 1];
foreach (TextLine line in TextLines) {
if (visualColumn < line.Length)
@ -307,18 +308,45 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -307,18 +308,45 @@ namespace ICSharpCode.AvalonEdit.Rendering
public Point GetVisualPosition(int visualColumn, VisualYPosition yPositionMode)
{
TextLine textLine = GetTextLine(visualColumn);
double xPos = textLine.GetDistanceFromCharacterHit(new CharacterHit(visualColumn, 0));
double xPos = GetTextLineVisualXPosition(textLine, visualColumn);
double yPos = GetTextLineVisualYPosition(textLine, yPositionMode);
return new Point(xPos, yPos);
}
/// <summary>
/// Gets the distance to the left border of the text area of the specified visual column.
/// The visual column must belong to the specified text line.
/// </summary>
public double GetTextLineVisualXPosition(TextLine textLine, int visualColumn)
{
if (textLine == null)
throw new ArgumentNullException("textLine");
double xPos = textLine.GetDistanceFromCharacterHit(
new CharacterHit(Math.Min(visualColumn, VisualLength), 0));
if (visualColumn > VisualLength) {
xPos += (visualColumn - VisualLength) * textView.WideSpaceWidth;
}
return xPos;
}
/// <summary>
/// Gets the visual column from a document position (relative to top left of the document).
/// If the user clicks between two visual columns, rounds to the nearest column.
/// </summary>
public int GetVisualColumn(Point point)
{
return GetVisualColumn(point, textView.Options.EnableVirtualSpace);
}
public int GetVisualColumn(Point point, bool allowVirtualSpace)
{
TextLine textLine = GetTextLineByVisualYPosition(point.Y);
if (point.X > textLine.WidthIncludingTrailingWhitespace) {
if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
int virtualX = (int)Math.Round((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
return VisualLength + virtualX;
}
}
CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X);
return ch.FirstCharacterIndex + ch.TrailingLength;
}
@ -328,13 +356,24 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -328,13 +356,24 @@ namespace ICSharpCode.AvalonEdit.Rendering
/// If the user clicks between two visual columns, returns the first of those columns.
/// </summary>
public int GetVisualColumnFloor(Point point)
{
return GetVisualColumnFloor(point, textView.Options.EnableVirtualSpace);
}
public int GetVisualColumnFloor(Point point, bool allowVirtualSpace)
{
TextLine textLine = GetTextLineByVisualYPosition(point.Y);
if (point.X > textLine.WidthIncludingTrailingWhitespace) {
// GetCharacterHitFromDistance returns a hit with FirstCharacterIndex=last character inline
// and TrailingLength=1 when clicking behind the line, so the floor function needs to handle this case
// specially end return the line's end column instead.
return GetTextLineVisualStartColumn(textLine) + textLine.Length;
if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
// clicking virtual space in the last line
int virtualX = (int)((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
return VisualLength + virtualX;
} else {
// GetCharacterHitFromDistance returns a hit with FirstCharacterIndex=last character in line
// and TrailingLength=1 when clicking behind the line, so the floor function needs to handle this case
// specially and return the line's end column instead.
return GetTextLineVisualStartColumn(textLine) + textLine.Length;
}
}
CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X);
return ch.FirstCharacterIndex;
@ -352,14 +391,23 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -352,14 +391,23 @@ namespace ICSharpCode.AvalonEdit.Rendering
{
if (elements.Count == 0) {
// special handling for empty visual lines:
// even though we don't have any elements,
// there's a single caret stop at visualColumn 0
if (visualColumn < 0 && direction == LogicalDirection.Forward)
return 0;
else if (visualColumn > 0 && direction == LogicalDirection.Backward)
return 0;
else
return -1;
if (textView.Options.EnableVirtualSpace) {
if (direction == LogicalDirection.Forward)
return Math.Max(0, visualColumn + 1);
else if (visualColumn > 0)
return visualColumn - 1;
else
return -1;
} else {
// even though we don't have any elements,
// there's a single caret stop at visualColumn 0
if (visualColumn < 0 && direction == LogicalDirection.Forward)
return 0;
else if (visualColumn > 0 && direction == LogicalDirection.Backward)
return 0;
else
return -1;
}
}
int i;
@ -368,7 +416,10 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -368,7 +416,10 @@ namespace ICSharpCode.AvalonEdit.Rendering
// If the last element doesn't handle line borders, return the line end as caret stop
if (visualColumn > this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) {
return this.VisualLength;
if (textView.Options.EnableVirtualSpace)
return visualColumn - 1;
else
return this.VisualLength;
}
// skip elements that start after or at visualColumn
for (i = elements.Count - 1; i >= 0; i--) {
@ -407,8 +458,12 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -407,8 +458,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
}
// if we've found nothing, and the last element doesn't handle line borders,
// return the line end as caret stop
if (visualColumn < this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode))
return this.VisualLength;
if (!elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) {
if (visualColumn < this.VisualLength)
return this.VisualLength;
else if (textView.Options.EnableVirtualSpace)
return visualColumn + 1;
}
}
// we've found nothing, return -1 and let the caret search continue in the next line
return -1;

19
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs

@ -362,5 +362,24 @@ namespace ICSharpCode.AvalonEdit @@ -362,5 +362,24 @@ namespace ICSharpCode.AvalonEdit
}
}
}
bool enableVirtualSpace;
/// <summary>
/// Gets/Sets whether the user can set the caret behind the line ending
/// (into "virtual space").
/// Note that virtual space is always used (independent from this setting)
/// when doing rectangle selections.
/// </summary>
[DefaultValue(false)]
public virtual bool EnableVirtualSpace {
get { return enableVirtualSpace; }
set {
if (enableVirtualSpace != value) {
enableVirtualSpace = value;
OnPropertyChanged("EnableVirtualSpace");
}
}
}
}
}

Loading…
Cancel
Save