Browse Source

More changes to the handling of invalid visual lines.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3835 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
7d39cff890
  1. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/FoldingSection.cs
  2. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/NoReadOnlySections.cs
  3. 227
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs
  4. 101
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs
  5. 278
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
  6. 48
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLinesInvalidException.cs
  7. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

3
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/FoldingSection.cs

@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
using System;
using ICSharpCode.AvalonEdit.Document;
using System.Windows.Threading;
namespace ICSharpCode.AvalonEdit.Gui
{
@ -40,7 +41,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -40,7 +41,7 @@ namespace ICSharpCode.AvalonEdit.Gui
collapsedSection = null;
}
}
manager.textView.Redraw();
manager.textView.Redraw(this, DispatcherPriority.Normal);
}
}
}

3
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/NoReadOnlySections.cs

@ -12,6 +12,9 @@ using System.Collections.Generic; @@ -12,6 +12,9 @@ using System.Collections.Generic;
namespace ICSharpCode.AvalonEdit.Gui
{
/// <summary>
/// <see cref="IReadOnlySectionProvider"/> that has no read-only sections; all text is editable.
/// </summary>
sealed class NoReadOnlySections : IReadOnlySectionProvider
{
public static readonly NoReadOnlySections Instance = new NoReadOnlySections();

227
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs

@ -23,8 +23,43 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -23,8 +23,43 @@ namespace ICSharpCode.AvalonEdit.Gui
/// </summary>
sealed class SelectionMouseHandler
{
#region enum SelectionMode
enum SelectionMode
{
/// <summary>
/// no selection (no mouse button down)
/// </summary>
None,
/// <summary>
/// left mouse button down on selection, might be normal click
/// or might be drag'n'drop
/// </summary>
PossibleDragStart,
/// <summary>
/// dragging text
/// </summary>
Drag,
/// <summary>
/// normal selection (click+drag)
/// </summary>
Normal,
/// <summary>
/// whole-word selection (double click+drag)
/// </summary>
WholeWord
}
#endregion
// TODO: allow disabling text drag'n'drop
const bool AllowTextDragDrop = true;
readonly TextArea textArea;
SelectionMode mode;
AnchorSegment startWord;
Point possibleDragStartMousePos;
#region Constructor + Attach + Detach
public SelectionMouseHandler(TextArea textArea)
{
this.textArea = textArea;
@ -47,6 +82,26 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -47,6 +82,26 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
public void Detach()
{
mode = SelectionMode.None;
textArea.MouseLeftButtonDown -= textArea_MouseLeftButtonDown;
textArea.MouseMove -= textArea_MouseMove;
textArea.MouseLeftButtonUp -= textArea_MouseLeftButtonUp;
textArea.QueryCursor -= textArea_QueryCursor;
if (AllowTextDragDrop) {
textArea.AllowDrop = false;
textArea.GiveFeedback -= textArea_GiveFeedback;
textArea.QueryContinueDrag -= textArea_QueryContinueDrag;
textArea.DragEnter -= textArea_DragEnter;
textArea.DragOver -= textArea_DragOver;
textArea.DragLeave -= textArea_DragLeave;
textArea.Drop -= textArea_Drop;
}
}
#endregion
#region Dropping text
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
void textArea_DragEnter(object sender, DragEventArgs e)
{
@ -131,22 +186,60 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -131,22 +186,60 @@ namespace ICSharpCode.AvalonEdit.Gui
}));
}
public void Detach()
void textArea_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
mode = SelectionMode.None;
textArea.MouseLeftButtonDown -= textArea_MouseLeftButtonDown;
textArea.MouseMove -= textArea_MouseMove;
textArea.MouseLeftButtonUp -= textArea_MouseLeftButtonUp;
textArea.QueryCursor -= textArea_QueryCursor;
if (AllowTextDragDrop) {
textArea.GiveFeedback -= textArea_GiveFeedback;
textArea.QueryContinueDrag -= textArea_QueryContinueDrag;
e.UseDefaultCursors = true;
e.Handled = true;
}
void textArea_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
if (e.EscapePressed) {
e.Action = DragAction.Cancel;
} else if ((e.KeyStates & DragDropKeyStates.LeftMouseButton) != DragDropKeyStates.LeftMouseButton) {
e.Action = DragAction.Drop;
} else {
e.Action = DragAction.Continue;
}
e.Handled = true;
}
#endregion
// TODO: allow disabling text drag'n'drop
const bool AllowTextDragDrop = true;
#region Start Drag
void StartDrag()
{
// prevent nested StartDrag calls
mode = SelectionMode.Drag;
// mouse capture and Drag'n'Drop doesn't mix
textArea.ReleaseMouseCapture();
string text = textArea.Selection.GetText(textArea.Document);
DataObject dataObject = new DataObject();
dataObject.SetText(text);
DragDropEffects allowedEffects = DragDropEffects.All;
List<AnchorSegment> deleteOnMove;
deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList();
Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects);
DragDropEffects resultEffect = DragDrop.DoDragDrop(textArea, dataObject, allowedEffects);
Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect);
if (deleteOnMove != null && resultEffect == DragDropEffects.Move) {
textArea.Document.BeginUpdate();
try {
foreach (ISegment s in deleteOnMove) {
textArea.Document.Remove(s.Offset, s.Length);
}
} finally {
textArea.Document.EndUpdate();
}
}
}
#endregion
#region QueryCursor
// provide the IBeam Cursor for the text area
void textArea_QueryCursor(object sender, QueryCursorEventArgs e)
{
@ -171,7 +264,9 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -171,7 +264,9 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
}
#endregion
#region ContainsOffset helper methods
static bool SelectionContains(Selection selection, int offset)
{
if (selection.IsEmpty)
@ -194,36 +289,9 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -194,36 +289,9 @@ namespace ICSharpCode.AvalonEdit.Gui
int end = start + segment.Length;
return offset >= start && offset <= end;
}
#endregion
enum SelectionMode
{
/// <summary>
/// no selection (no mouse button down)
/// </summary>
None,
/// <summary>
/// left mouse button down on selection, might be normal click
/// or might be drag'n'drop
/// </summary>
PossibleDragStart,
/// <summary>
/// dragging text
/// </summary>
Drag,
/// <summary>
/// normal selection (click+drag)
/// </summary>
Normal,
/// <summary>
/// whole-word selection (double click+drag)
/// </summary>
WholeWord
}
SelectionMode mode;
AnchorSegment startWord;
Point possibleDragStartMousePos;
#region LeftButtonDown
void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
mode = SelectionMode.None;
@ -279,7 +347,9 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -279,7 +347,9 @@ namespace ICSharpCode.AvalonEdit.Gui
}
e.Handled = true;
}
#endregion
#region Mouse Position <-> Text coordinates
SimpleSegment GetWordAtMousePosition(MouseEventArgs e)
{
TextView textView = textArea.TextView;
@ -308,16 +378,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -308,16 +378,6 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
void SetCaretOffsetToMousePosition(MouseEventArgs e)
{
int visualColumn;
int offset = GetOffsetFromMousePosition(e, out visualColumn);
if (offset >= 0) {
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn);
textArea.Caret.DesiredXPos = double.NaN;
}
}
int GetOffsetFromMousePosition(MouseEventArgs e, out int visualColumn)
{
return GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn);
@ -342,7 +402,9 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -342,7 +402,9 @@ namespace ICSharpCode.AvalonEdit.Gui
}
return -1;
}
#endregion
#region MouseMove
void textArea_MouseMove(object sender, MouseEventArgs e)
{
if (e.Handled)
@ -365,6 +427,18 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -365,6 +427,18 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
}
#endregion
#region ExtendSelection
void SetCaretOffsetToMousePosition(MouseEventArgs e)
{
int visualColumn;
int offset = GetOffsetFromMousePosition(e, out visualColumn);
if (offset >= 0) {
textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn);
textArea.Caret.DesiredXPos = double.NaN;
}
}
void ExtendSelectionToMouse(MouseEventArgs e)
{
@ -379,7 +453,9 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -379,7 +453,9 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
}
#endregion
#region MouseLeftButtonUp
void textArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (mode == SelectionMode.None || e.Handled)
@ -395,55 +471,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -395,55 +471,6 @@ namespace ICSharpCode.AvalonEdit.Gui
mode = SelectionMode.None;
textArea.ReleaseMouseCapture();
}
void StartDrag()
{
// prevent nested StartDrag calls
mode = SelectionMode.Drag;
// mouse capture and Drag'n'Drop doesn't mix
textArea.ReleaseMouseCapture();
string text = textArea.Selection.GetText(textArea.Document);
DataObject dataObject = new DataObject();
dataObject.SetText(text);
DragDropEffects allowedEffects = DragDropEffects.All;
List<AnchorSegment> deleteOnMove;
deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList();
Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects);
DragDropEffects resultEffect = DragDrop.DoDragDrop(textArea, dataObject, allowedEffects);
Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect);
if (deleteOnMove != null && resultEffect == DragDropEffects.Move) {
textArea.Document.BeginUpdate();
try {
foreach (ISegment s in deleteOnMove) {
textArea.Document.Remove(s.Offset, s.Length);
}
} finally {
textArea.Document.EndUpdate();
}
}
}
void textArea_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
e.UseDefaultCursors = true;
e.Handled = true;
}
void textArea_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
{
if (e.EscapePressed) {
e.Action = DragAction.Cancel;
} else if ((e.KeyStates & DragDropKeyStates.LeftMouseButton) != DragDropKeyStates.LeftMouseButton) {
e.Action = DragAction.Drop;
} else {
e.Action = DragAction.Continue;
}
e.Handled = true;
}
#endregion
}
}

101
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
@ -126,6 +127,7 @@ namespace ICSharpCode.AvalonEdit @@ -126,6 +127,7 @@ namespace ICSharpCode.AvalonEdit
}
#endregion
#region Caret handling on document changes
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {
@ -181,18 +183,37 @@ namespace ICSharpCode.AvalonEdit @@ -181,18 +183,37 @@ namespace ICSharpCode.AvalonEdit
Undo();
}
}
#endregion
readonly Caret caret;
#region TextView property
readonly TextView textView;
IScrollInfo scrollInfo;
/// <summary>
/// Gets the Caret used for this text area.
/// Gets the text view used to display text in this text area.
/// </summary>
public Caret Caret {
get { return caret; }
public TextView TextView {
get {
return textView;
}
}
/// <inheritdoc/>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
scrollInfo = textView;
ApplyScrollInfo();
}
#endregion
#region Selection property
Selection selection = Selection.Empty;
/// <summary>
/// Occurs when the selection has changed.
/// </summary>
public event EventHandler SelectionChanged;
/// <summary>
/// Gets/Sets the selection in this text area.
/// </summary>
@ -213,30 +234,16 @@ namespace ICSharpCode.AvalonEdit @@ -213,30 +234,16 @@ namespace ICSharpCode.AvalonEdit
}
}
}
#endregion
/// <summary>
/// Occurs when the selection has changed.
/// </summary>
public event EventHandler SelectionChanged;
readonly TextView textView;
IScrollInfo scrollInfo;
/// <inheritdoc/>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
scrollInfo = textView;
ApplyScrollInfo();
}
#region Properties
readonly Caret caret;
/// <summary>
/// Gets the text view used to display text in this text area.
/// Gets the Caret used for this text area.
/// </summary>
public TextView TextView {
get {
return textView;
}
public Caret Caret {
get { return caret; }
}
ObservableCollection<UIElement> leftMargins = new ObservableCollection<UIElement>();
@ -250,6 +257,21 @@ namespace ICSharpCode.AvalonEdit @@ -250,6 +257,21 @@ namespace ICSharpCode.AvalonEdit
}
}
IReadOnlySectionProvider readOnlySectionProvider = NoReadOnlySections.Instance;
/// <summary>
/// Gets/Sets an object that provides read-only sections for the text area.
/// </summary>
public IReadOnlySectionProvider ReadOnlySectionProvider {
get { return readOnlySectionProvider; }
set {
if (value == null)
throw new ArgumentNullException("value");
readOnlySectionProvider = value;
}
}
#endregion
#region Undo / Redo
UndoStack GetUndoStack()
{
@ -432,6 +454,7 @@ namespace ICSharpCode.AvalonEdit @@ -432,6 +454,7 @@ namespace ICSharpCode.AvalonEdit
}
#endregion
#region Focus Handling (Show/Hide Caret)
/// <inheritdoc/>
protected override void OnMouseDown(MouseButtonEventArgs e)
{
@ -454,21 +477,9 @@ namespace ICSharpCode.AvalonEdit @@ -454,21 +477,9 @@ namespace ICSharpCode.AvalonEdit
caret.Hide();
e.Handled = true;
}
#endregion
IReadOnlySectionProvider readOnlySectionProvider = NoReadOnlySections.Instance;
/// <summary>
/// Gets/Sets an object that provides read-only sections for the text area.
/// </summary>
public IReadOnlySectionProvider ReadOnlySectionProvider {
get { return readOnlySectionProvider; }
set {
if (value == null)
throw new ArgumentNullException("value");
readOnlySectionProvider = value;
}
}
#region OnTextInput / RemoveSelectedText / ReplaceSelectionWithText
/// <inheritdoc/>
protected override void OnTextInput(TextCompositionEventArgs e)
{
@ -476,14 +487,7 @@ namespace ICSharpCode.AvalonEdit @@ -476,14 +487,7 @@ namespace ICSharpCode.AvalonEdit
if (!e.Handled) {
TextDocument document = this.Document;
if (document != null) {
document.BeginUpdate();
try {
RemoveSelectedText();
if (readOnlySectionProvider.CanInsert(caret.Offset))
document.Insert(caret.Offset, e.Text);
} finally {
document.EndUpdate();
}
ReplaceSelectionWithText(e.Text);
caret.BringCaretToView();
e.Handled = true;
}
@ -495,7 +499,9 @@ namespace ICSharpCode.AvalonEdit @@ -495,7 +499,9 @@ namespace ICSharpCode.AvalonEdit
selection.RemoveSelectedText(this);
#if DEBUG
if (!selection.IsEmpty) {
// TODO: assert that the remaining selection is read-only
foreach (ISegment s in selection.Segments) {
Debug.Assert(ReadOnlySectionProvider.GetDeletableSegments(s).Count() == 0);
}
}
#endif
}
@ -512,5 +518,6 @@ namespace ICSharpCode.AvalonEdit @@ -512,5 +518,6 @@ namespace ICSharpCode.AvalonEdit
Document.EndUpdate();
}
}
#endregion
}
}

278
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs

@ -26,9 +26,13 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -26,9 +26,13 @@ namespace ICSharpCode.AvalonEdit.Gui
{
/// <summary>
/// A virtualizing panel producing+showing <see cref="VisualLine"/>s for a <see cref="TextDocument"/>.
///
/// This is the heart of the text editor, this class controls the text rendering process.
///
/// Taken as a standalone control, it's a text viewer without any editing capability.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
Justification = "The user usually doesn't work with TextView but with TextEditor; nulling the Document property is sufficient to dispose everything.")]
Justification = "The user usually doesn't work with TextView but with TextEditor; and nulling the Document property is sufficient to dispose everything.")]
public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener
{
#region Constructor
@ -50,7 +54,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -50,7 +54,7 @@ namespace ICSharpCode.AvalonEdit.Gui
}
#endregion
#region Properties
#region Document Property
/// <summary>
/// Document property.
/// </summary>
@ -119,6 +123,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -119,6 +123,7 @@ namespace ICSharpCode.AvalonEdit.Gui
}
#endregion
#region Collection Properties
readonly ObservableCollection<VisualLineElementGenerator> elementGenerators = new ObservableCollection<VisualLineElementGenerator>();
/// <summary>
@ -145,13 +150,15 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -145,13 +150,15 @@ namespace ICSharpCode.AvalonEdit.Gui
public UIElementCollection Adorners {
get { return adorners; }
}
#endregion
#region Redraw methods / VisualLine invalidation
/// <summary>
/// Causes the text editor to regenerate all visual lines.
/// </summary>
public void Redraw()
{
Redraw(DispatcherPriority.Render);
Redraw(DispatcherPriority.Normal);
}
/// <summary>
@ -183,7 +190,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -183,7 +190,6 @@ namespace ICSharpCode.AvalonEdit.Gui
public void Redraw(int offset, int length, DispatcherPriority redrawPriority)
{
VerifyAccess();
if (allVisualLines.Count != 0 || visibleVisualLines != null) {
bool removedLine = false;
for (int i = 0; i < allVisualLines.Count; i++) {
VisualLine visualLine = allVisualLines[i];
@ -200,7 +206,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -200,7 +206,6 @@ namespace ICSharpCode.AvalonEdit.Gui
InvalidateMeasure(redrawPriority);
}
}
}
/// <summary>
/// Causes the text editor to redraw all lines overlapping with the specified segment.
@ -213,6 +218,36 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -213,6 +218,36 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
/// <summary>
/// Invalidates all visual lines.
/// The caller of ClearVisualLines() must also call InvalidateMeasure() to ensure
/// that the visual lines will be recreated.
/// </summary>
void ClearVisualLines()
{
visibleVisualLines = null;
if (allVisualLines.Count != 0) {
foreach (VisualLine visualLine in allVisualLines) {
DisposeVisualLine(visualLine);
}
allVisualLines.Clear();
}
}
void DisposeVisualLine(VisualLine visualLine)
{
if (newVisualLines != null && newVisualLines.Contains(visualLine)) {
throw new ArgumentException("Cannot dispose visual line because it is in construction!");
}
visualLine.IsDisposed = true;
foreach (TextLine textLine in visualLine.TextLines) {
textLine.Dispose();
}
RemoveInlineObjects(visualLine);
}
#endregion
#region InvalidateMeasure(DispatcherPriority)
DispatcherOperation invalidateMeasureOperation;
void InvalidateMeasure(DispatcherPriority priority)
@ -239,44 +274,9 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -239,44 +274,9 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
}
#endregion
/// <summary>
/// Waits for the visual lines to be built.
/// </summary>
private void EnsureVisualLines()
{
Dispatcher.VerifyAccess();
if (visibleVisualLines == null) {
// increase priority for real Redraw
InvalidateMeasure(DispatcherPriority.Normal);
// force immediate re-measure
UpdateLayout();
}
}
void ClearVisualLines()
{
visibleVisualLines = null;
if (allVisualLines.Count != 0) {
foreach (VisualLine visualLine in allVisualLines) {
DisposeVisualLine(visualLine);
}
allVisualLines.Clear();
}
}
void DisposeVisualLine(VisualLine visualLine)
{
if (newVisualLines != null && newVisualLines.Contains(visualLine)) {
throw new ArgumentException("Cannot dispose visual line because it is in construction!");
}
visualLine.IsDisposed = true;
foreach (TextLine textLine in visualLine.TextLines) {
textLine.Dispose();
}
RemoveInlineObjects(visualLine);
}
#region Get(OrConstruct)VisualLine
/// <summary>
/// Gets the visual line that contains the document line with the specified number.
/// Returns null if the document line is outside the visible range.
@ -324,39 +324,27 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -324,39 +324,27 @@ namespace ICSharpCode.AvalonEdit.Gui
}
return l;
}
#endregion
/// <summary>
/// Collapses lines for the purpose of scrolling. This method is meant for
/// <see cref="VisualLineElementGenerator"/>s that cause <see cref="VisualLine"/>s to span
/// multiple <see cref="DocumentLine"/>s. Do not call it without providing a corresponding
/// <see cref="VisualLineElementGenerator"/>.
/// If you want to create collapsible text sections, see <see cref="FoldingManager"/>.
/// </summary>
public CollapsedLineSection CollapseLines(DocumentLine start, DocumentLine end)
{
VerifyAccess();
return heightTree.CollapseText(start, end);
}
/// <summary>
/// Gets the height of the document.
/// </summary>
public double DocumentHeight {
get { return heightTree.TotalHeight; }
}
#region Measure
TextFormatter formatter;
#region Visual Lines (fields and properties)
List<VisualLine> allVisualLines = new List<VisualLine>();
ReadOnlyCollection<VisualLine> visibleVisualLines;
double clippedPixelsOnTop;
List<VisualLine> newVisualLines;
/// <summary>
/// Gets the currently visible visual lines.
/// </summary>
/// <exception cref="VisualLinesInvalidException">
/// Gets thrown if there are invalid visual lines when this property is accessed.
/// You can use the <see cref="VisualLinesValid"/> property to check for this case,
/// or use the <see cref="EnsureVisualLines()"/> method to force creating the visual lines
/// when they are invalid.
/// </exception>
public ReadOnlyCollection<VisualLine> VisualLines {
get {
EnsureVisualLines();
if (visibleVisualLines == null)
throw new VisualLinesInvalidException();
return visibleVisualLines;
}
}
@ -375,56 +363,75 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -375,56 +363,75 @@ namespace ICSharpCode.AvalonEdit.Gui
/// </summary>
public event EventHandler VisualLinesChanged;
TextRunProperties CreateGlobalTextRunProperties()
/// <summary>
/// If the visual lines are invalid, creates new visual lines for the visible part
/// of the document.
/// If all visual lines are valid, this method does nothing.
/// </summary>
/// <exception cref="InvalidOperationException">The visual line build process is already running.
/// It is not allowed to call this method during the construction of a visual line.</exception>
public void EnsureVisualLines()
{
return new GlobalTextRunProperties {
typeface = this.CreateTypeface(),
fontRenderingEmSize = LineHeight,
foregroundBrush = (Brush)GetValue(Control.ForegroundProperty),
cultureInfo = CultureInfo.CurrentCulture
};
Dispatcher.VerifyAccess();
if (inMeasure)
throw new InvalidOperationException("The visual line build process is already running! Cannot EnsureVisualLines() during Measure!");
if (visibleVisualLines == null) {
// increase priority for re-measure
InvalidateMeasure(DispatcherPriority.Normal);
// force immediate re-measure
UpdateLayout();
}
TextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
{
return new VisualLineTextParagraphProperties {
defaultTextRunProperties = defaultTextRunProperties,
textWrapping = canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
tabSize = 4 * WideSpaceWidth
};
}
#endregion
#region Measure
Size lastAvailableSize;
List<VisualLine> newVisualLines;
bool inMeasure;
/// <summary>
/// Measure implementation.
/// </summary>
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
if (!canHorizontallyScroll && !availableSize.Width.IsClose(lastAvailableSize.Width))
ClearVisualLines();
lastAvailableSize = availableSize;
return DoMeasure(availableSize);
}
/// <summary>
/// Immediately performs the text creation.
/// </summary>
/// <param name="availableSize">The size of the text view.</param>
/// <returns></returns>
Size DoMeasure(Size availableSize)
{
bool isRealMeasure = true;
if (isRealMeasure)
RemoveInlineObjectsNow();
if (document == null)
return Size.Empty;
InvalidateVisual(); // = InvalidateArrange+InvalidateRender
double maxWidth;
inMeasure = true;
try {
maxWidth = CreateAndMeasureVisualLines(availableSize);
} finally {
inMeasure = false;
}
RemoveInlineObjectsNow();
SetScrollData(availableSize,
new Size(maxWidth, heightTree.TotalHeight),
scrollOffset);
if (VisualLinesChanged != null)
VisualLinesChanged(this, EventArgs.Empty);
if (canHorizontallyScroll) {
return availableSize;
} else {
return new Size(maxWidth, availableSize.Height);
}
}
/// <summary>
/// Build all VisualLines in the visible range.
/// </summary>
/// <returns>Width the longest line</returns>
double CreateAndMeasureVisualLines(Size availableSize)
{
TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties();
TextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
InvalidateVisual(); // = InvalidateArrange+InvalidateRender
Debug.WriteLine("Measure availableSize=" + availableSize + ", scrollOffset=" + scrollOffset);
var firstLineInView = heightTree.GetLineByVisualPosition(scrollOffset.Y);
@ -472,8 +479,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -472,8 +479,6 @@ namespace ICSharpCode.AvalonEdit.Gui
if (!newVisualLines.Contains(line))
DisposeVisualLine(line);
}
if (isRealMeasure)
RemoveInlineObjectsNow();
allVisualLines = newVisualLines;
// visibleVisualLines = readonly copy of visual lines
@ -485,17 +490,30 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -485,17 +490,30 @@ namespace ICSharpCode.AvalonEdit.Gui
"This can happen when Redraw() is called during measure for lines " +
"that are already constructed.");
}
return maxWidth;
}
#endregion
SetScrollData(availableSize,
new Size(maxWidth, heightTree.TotalHeight),
scrollOffset);
if (VisualLinesChanged != null)
VisualLinesChanged(this, EventArgs.Empty);
if (canHorizontallyScroll) {
return availableSize;
} else {
return new Size(maxWidth, availableSize.Height);
#region BuildVisualLine
TextFormatter formatter;
TextRunProperties CreateGlobalTextRunProperties()
{
return new GlobalTextRunProperties {
typeface = this.CreateTypeface(),
fontRenderingEmSize = LineHeight,
foregroundBrush = (Brush)GetValue(Control.ForegroundProperty),
cultureInfo = CultureInfo.CurrentCulture
};
}
TextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
{
return new VisualLineTextParagraphProperties {
defaultTextRunProperties = defaultTextRunProperties,
textWrapping = canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
tabSize = 4 * WideSpaceWidth
};
}
VisualLine BuildVisualLine(DocumentLine documentLine,
@ -969,17 +987,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -969,17 +987,6 @@ namespace ICSharpCode.AvalonEdit.Gui
}
#endregion
/// <summary>
/// Gets the document line at the specified visual position.
/// </summary>
public DocumentLine GetDocumentLineByVisualTop(double visualTop)
{
VerifyAccess();
if (heightTree == null)
throw new InvalidOperationException();
return heightTree.GetLineByVisualPosition(visualTop);
}
#region Visual element mouse handling
/// <inheritdoc/>
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
@ -1041,15 +1048,17 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -1041,15 +1048,17 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
}
#endregion
#region Getting elements from Visual Position
/// <summary>
/// Gets the visual line at the specified document position (relative to start of document).
/// Returns null if there is no visual line for the position (e.g. the position is outside the visible
/// text area).
/// You may want to call <see cref="EnsureVisualLines"/>() before calling this method.
/// </summary>
public VisualLine GetVisualLineFromVisualTop(double visualTop)
{
EnsureVisualLines();
foreach (VisualLine vl in this.VisualLines) {
if (visualTop < vl.VisualTop)
continue;
@ -1074,5 +1083,36 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -1074,5 +1083,36 @@ namespace ICSharpCode.AvalonEdit.Gui
return null;
}
#endregion
/// <summary>
/// Collapses lines for the purpose of scrolling. This method is meant for
/// <see cref="VisualLineElementGenerator"/>s that cause <see cref="VisualLine"/>s to span
/// multiple <see cref="DocumentLine"/>s. Do not call it without providing a corresponding
/// <see cref="VisualLineElementGenerator"/>.
/// If you want to create collapsible text sections, see <see cref="FoldingManager"/>.
/// </summary>
public CollapsedLineSection CollapseLines(DocumentLine start, DocumentLine end)
{
VerifyAccess();
return heightTree.CollapseText(start, end);
}
/// <summary>
/// Gets the height of the document.
/// </summary>
public double DocumentHeight {
get { return heightTree.TotalHeight; }
}
/// <summary>
/// Gets the document line at the specified visual position.
/// </summary>
public DocumentLine GetDocumentLineByVisualTop(double visualTop)
{
VerifyAccess();
if (heightTree == null)
throw new InvalidOperationException();
return heightTree.GetLineByVisualPosition(visualTop);
}
}
}

48
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLinesInvalidException.cs

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <author name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Runtime.Serialization;
namespace ICSharpCode.AvalonEdit.Gui
{
/// <summary>
/// A VisualLinesInvalidException indicates that you accessed the <see cref="TextView.VisualLines"/> property
/// of the <see cref="TextView"/> while the visual lines were invalid.
/// </summary>
[Serializable]
public class VisualLinesInvalidException : Exception
{
/// <summary>
/// Creates a new VisualLinesInvalidException instance.
/// </summary>
public VisualLinesInvalidException() : base()
{
}
/// <summary>
/// Creates a new VisualLinesInvalidException instance.
/// </summary>
public VisualLinesInvalidException(string message) : base(message)
{
}
/// <summary>
/// Creates a new VisualLinesInvalidException instance.
/// </summary>
public VisualLinesInvalidException(string message, Exception innerException) : base(message, innerException)
{
}
/// <summary>
/// Creates a new VisualLinesInvalidException instance.
/// </summary>
protected VisualLinesInvalidException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

1
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -168,6 +168,7 @@ @@ -168,6 +168,7 @@
<Compile Include="Gui\VisualLineElement.cs" />
<Compile Include="Gui\VisualLineElementGenerator.cs">
</Compile>
<Compile Include="Gui\VisualLinesInvalidException.cs" />
<Compile Include="Gui\VisualLineText.cs" />
<Compile Include="Gui\VisualLineTextParagraphProperties.cs">
<DependentUpon>VisualLine.cs</DependentUpon>

Loading…
Cancel
Save