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. 229
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs
  4. 103
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs
  5. 312
      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 @@
using System; using System;
using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Document;
using System.Windows.Threading;
namespace ICSharpCode.AvalonEdit.Gui namespace ICSharpCode.AvalonEdit.Gui
{ {
@ -40,7 +41,7 @@ namespace ICSharpCode.AvalonEdit.Gui
collapsedSection = null; 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;
namespace ICSharpCode.AvalonEdit.Gui namespace ICSharpCode.AvalonEdit.Gui
{ {
/// <summary>
/// <see cref="IReadOnlySectionProvider"/> that has no read-only sections; all text is editable.
/// </summary>
sealed class NoReadOnlySections : IReadOnlySectionProvider sealed class NoReadOnlySections : IReadOnlySectionProvider
{ {
public static readonly NoReadOnlySections Instance = new NoReadOnlySections(); public static readonly NoReadOnlySections Instance = new NoReadOnlySections();

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

@ -23,8 +23,43 @@ namespace ICSharpCode.AvalonEdit.Gui
/// </summary> /// </summary>
sealed class SelectionMouseHandler 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; readonly TextArea textArea;
SelectionMode mode;
AnchorSegment startWord;
Point possibleDragStartMousePos;
#region Constructor + Attach + Detach
public SelectionMouseHandler(TextArea textArea) public SelectionMouseHandler(TextArea textArea)
{ {
this.textArea = textArea; this.textArea = textArea;
@ -46,7 +81,27 @@ namespace ICSharpCode.AvalonEdit.Gui
textArea.Drop += textArea_Drop; textArea.Drop += textArea_Drop;
} }
} }
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")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
void textArea_DragEnter(object sender, DragEventArgs e) void textArea_DragEnter(object sender, DragEventArgs e)
{ {
@ -131,22 +186,60 @@ namespace ICSharpCode.AvalonEdit.Gui
})); }));
} }
public void Detach() void textArea_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{ {
mode = SelectionMode.None; e.UseDefaultCursors = true;
textArea.MouseLeftButtonDown -= textArea_MouseLeftButtonDown; e.Handled = true;
textArea.MouseMove -= textArea_MouseMove; }
textArea.MouseLeftButtonUp -= textArea_MouseLeftButtonUp;
textArea.QueryCursor -= textArea_QueryCursor; void textArea_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
if (AllowTextDragDrop) { {
textArea.GiveFeedback -= textArea_GiveFeedback; if (e.EscapePressed) {
textArea.QueryContinueDrag -= textArea_QueryContinueDrag; 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 #region Start Drag
const bool AllowTextDragDrop = true; 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 // provide the IBeam Cursor for the text area
void textArea_QueryCursor(object sender, QueryCursorEventArgs e) void textArea_QueryCursor(object sender, QueryCursorEventArgs e)
{ {
@ -171,7 +264,9 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
} }
} }
#endregion
#region ContainsOffset helper methods
static bool SelectionContains(Selection selection, int offset) static bool SelectionContains(Selection selection, int offset)
{ {
if (selection.IsEmpty) if (selection.IsEmpty)
@ -194,36 +289,9 @@ namespace ICSharpCode.AvalonEdit.Gui
int end = start + segment.Length; int end = start + segment.Length;
return offset >= start && offset <= end; return offset >= start && offset <= end;
} }
#endregion
enum SelectionMode #region LeftButtonDown
{
/// <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;
void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{ {
mode = SelectionMode.None; mode = SelectionMode.None;
@ -279,7 +347,9 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
e.Handled = true; e.Handled = true;
} }
#endregion
#region Mouse Position <-> Text coordinates
SimpleSegment GetWordAtMousePosition(MouseEventArgs e) SimpleSegment GetWordAtMousePosition(MouseEventArgs e)
{ {
TextView textView = textArea.TextView; TextView textView = textArea.TextView;
@ -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) int GetOffsetFromMousePosition(MouseEventArgs e, out int visualColumn)
{ {
return GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn); return GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn);
@ -342,7 +402,9 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
return -1; return -1;
} }
#endregion
#region MouseMove
void textArea_MouseMove(object sender, MouseEventArgs e) void textArea_MouseMove(object sender, MouseEventArgs e)
{ {
if (e.Handled) if (e.Handled)
@ -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) void ExtendSelectionToMouse(MouseEventArgs e)
{ {
@ -379,7 +453,9 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
} }
} }
#endregion
#region MouseLeftButtonUp
void textArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) void textArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{ {
if (mode == SelectionMode.None || e.Handled) if (mode == SelectionMode.None || e.Handled)
@ -395,55 +471,6 @@ namespace ICSharpCode.AvalonEdit.Gui
mode = SelectionMode.None; mode = SelectionMode.None;
textArea.ReleaseMouseCapture(); textArea.ReleaseMouseCapture();
} }
#endregion
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;
}
} }
} }

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

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

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

@ -26,9 +26,13 @@ namespace ICSharpCode.AvalonEdit.Gui
{ {
/// <summary> /// <summary>
/// A virtualizing panel producing+showing <see cref="VisualLine"/>s for a <see cref="TextDocument"/>. /// 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> /// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", [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 public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener
{ {
#region Constructor #region Constructor
@ -50,7 +54,7 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
#endregion #endregion
#region Properties #region Document Property
/// <summary> /// <summary>
/// Document property. /// Document property.
/// </summary> /// </summary>
@ -119,6 +123,7 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
#endregion #endregion
#region Collection Properties
readonly ObservableCollection<VisualLineElementGenerator> elementGenerators = new ObservableCollection<VisualLineElementGenerator>(); readonly ObservableCollection<VisualLineElementGenerator> elementGenerators = new ObservableCollection<VisualLineElementGenerator>();
/// <summary> /// <summary>
@ -145,13 +150,15 @@ namespace ICSharpCode.AvalonEdit.Gui
public UIElementCollection Adorners { public UIElementCollection Adorners {
get { return adorners; } get { return adorners; }
} }
#endregion
#region Redraw methods / VisualLine invalidation
/// <summary> /// <summary>
/// Causes the text editor to regenerate all visual lines. /// Causes the text editor to regenerate all visual lines.
/// </summary> /// </summary>
public void Redraw() public void Redraw()
{ {
Redraw(DispatcherPriority.Render); Redraw(DispatcherPriority.Normal);
} }
/// <summary> /// <summary>
@ -183,23 +190,21 @@ namespace ICSharpCode.AvalonEdit.Gui
public void Redraw(int offset, int length, DispatcherPriority redrawPriority) public void Redraw(int offset, int length, DispatcherPriority redrawPriority)
{ {
VerifyAccess(); VerifyAccess();
if (allVisualLines.Count != 0 || visibleVisualLines != null) { bool removedLine = false;
bool removedLine = false; for (int i = 0; i < allVisualLines.Count; i++) {
for (int i = 0; i < allVisualLines.Count; i++) { VisualLine visualLine = allVisualLines[i];
VisualLine visualLine = allVisualLines[i]; int lineStart = visualLine.FirstDocumentLine.Offset;
int lineStart = visualLine.FirstDocumentLine.Offset; int lineEnd = visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength;
int lineEnd = visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength; if (!(lineEnd < offset || lineStart > offset + length)) {
if (!(lineEnd < offset || lineStart > offset + length)) { removedLine = true;
removedLine = true; allVisualLines.RemoveAt(i--);
allVisualLines.RemoveAt(i--); DisposeVisualLine(visualLine);
DisposeVisualLine(visualLine);
}
}
if (removedLine) {
visibleVisualLines = null;
InvalidateMeasure(redrawPriority);
} }
} }
if (removedLine) {
visibleVisualLines = null;
InvalidateMeasure(redrawPriority);
}
} }
/// <summary> /// <summary>
@ -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; DispatcherOperation invalidateMeasureOperation;
void InvalidateMeasure(DispatcherPriority priority) void InvalidateMeasure(DispatcherPriority priority)
@ -239,44 +274,9 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
} }
} }
#endregion
/// <summary> #region Get(OrConstruct)VisualLine
/// 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);
}
/// <summary> /// <summary>
/// Gets the visual line that contains the document line with the specified number. /// Gets the visual line that contains the document line with the specified number.
/// Returns null if the document line is outside the visible range. /// Returns null if the document line is outside the visible range.
@ -324,39 +324,27 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
return l; return l;
} }
#endregion
/// <summary> #region Visual Lines (fields and properties)
/// 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;
List<VisualLine> allVisualLines = new List<VisualLine>(); List<VisualLine> allVisualLines = new List<VisualLine>();
ReadOnlyCollection<VisualLine> visibleVisualLines; ReadOnlyCollection<VisualLine> visibleVisualLines;
double clippedPixelsOnTop; double clippedPixelsOnTop;
List<VisualLine> newVisualLines;
/// <summary> /// <summary>
/// Gets the currently visible visual lines. /// Gets the currently visible visual lines.
/// </summary> /// </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 { public ReadOnlyCollection<VisualLine> VisualLines {
get { get {
EnsureVisualLines(); if (visibleVisualLines == null)
throw new VisualLinesInvalidException();
return visibleVisualLines; return visibleVisualLines;
} }
} }
@ -375,56 +363,75 @@ namespace ICSharpCode.AvalonEdit.Gui
/// </summary> /// </summary>
public event EventHandler VisualLinesChanged; public event EventHandler VisualLinesChanged;
TextRunProperties CreateGlobalTextRunProperties() /// <summary>
{ /// If the visual lines are invalid, creates new visual lines for the visible part
return new GlobalTextRunProperties { /// of the document.
typeface = this.CreateTypeface(), /// If all visual lines are valid, this method does nothing.
fontRenderingEmSize = LineHeight, /// </summary>
foregroundBrush = (Brush)GetValue(Control.ForegroundProperty), /// <exception cref="InvalidOperationException">The visual line build process is already running.
cultureInfo = CultureInfo.CurrentCulture /// It is not allowed to call this method during the construction of a visual line.</exception>
}; public void EnsureVisualLines()
}
TextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
{ {
return new VisualLineTextParagraphProperties { Dispatcher.VerifyAccess();
defaultTextRunProperties = defaultTextRunProperties, if (inMeasure)
textWrapping = canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap, throw new InvalidOperationException("The visual line build process is already running! Cannot EnsureVisualLines() during Measure!");
tabSize = 4 * WideSpaceWidth if (visibleVisualLines == null) {
}; // increase priority for re-measure
InvalidateMeasure(DispatcherPriority.Normal);
// force immediate re-measure
UpdateLayout();
}
} }
#endregion
#region Measure
Size lastAvailableSize; Size lastAvailableSize;
List<VisualLine> newVisualLines; bool inMeasure;
/// <summary> /// <inheritdoc/>
/// Measure implementation.
/// </summary>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
if (!canHorizontallyScroll && !availableSize.Width.IsClose(lastAvailableSize.Width)) if (!canHorizontallyScroll && !availableSize.Width.IsClose(lastAvailableSize.Width))
ClearVisualLines(); ClearVisualLines();
lastAvailableSize = availableSize; lastAvailableSize = availableSize;
return DoMeasure(availableSize);
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> /// <summary>
/// Immediately performs the text creation. /// Build all VisualLines in the visible range.
/// </summary> /// </summary>
/// <param name="availableSize">The size of the text view.</param> /// <returns>Width the longest line</returns>
/// <returns></returns> double CreateAndMeasureVisualLines(Size availableSize)
Size DoMeasure(Size availableSize)
{ {
bool isRealMeasure = true;
if (isRealMeasure)
RemoveInlineObjectsNow();
if (document == null)
return Size.Empty;
TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties(); TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties();
TextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties); TextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
InvalidateVisual(); // = InvalidateArrange+InvalidateRender
Debug.WriteLine("Measure availableSize=" + availableSize + ", scrollOffset=" + scrollOffset); Debug.WriteLine("Measure availableSize=" + availableSize + ", scrollOffset=" + scrollOffset);
var firstLineInView = heightTree.GetLineByVisualPosition(scrollOffset.Y); var firstLineInView = heightTree.GetLineByVisualPosition(scrollOffset.Y);
@ -472,8 +479,6 @@ namespace ICSharpCode.AvalonEdit.Gui
if (!newVisualLines.Contains(line)) if (!newVisualLines.Contains(line))
DisposeVisualLine(line); DisposeVisualLine(line);
} }
if (isRealMeasure)
RemoveInlineObjectsNow();
allVisualLines = newVisualLines; allVisualLines = newVisualLines;
// visibleVisualLines = readonly copy of visual lines // visibleVisualLines = readonly copy of visual lines
@ -485,17 +490,30 @@ namespace ICSharpCode.AvalonEdit.Gui
"This can happen when Redraw() is called during measure for lines " + "This can happen when Redraw() is called during measure for lines " +
"that are already constructed."); "that are already constructed.");
} }
return maxWidth;
SetScrollData(availableSize, }
new Size(maxWidth, heightTree.TotalHeight), #endregion
scrollOffset);
if (VisualLinesChanged != null) #region BuildVisualLine
VisualLinesChanged(this, EventArgs.Empty); TextFormatter formatter;
if (canHorizontallyScroll) {
return availableSize; TextRunProperties CreateGlobalTextRunProperties()
} else { {
return new Size(maxWidth, availableSize.Height); 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, VisualLine BuildVisualLine(DocumentLine documentLine,
@ -969,17 +987,6 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
#endregion #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 #region Visual element mouse handling
/// <inheritdoc/> /// <inheritdoc/>
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
@ -1041,15 +1048,17 @@ namespace ICSharpCode.AvalonEdit.Gui
} }
} }
} }
#endregion
#region Getting elements from Visual Position
/// <summary> /// <summary>
/// Gets the visual line at the specified document position (relative to start of document). /// 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 /// Returns null if there is no visual line for the position (e.g. the position is outside the visible
/// text area). /// text area).
/// You may want to call <see cref="EnsureVisualLines"/>() before calling this method.
/// </summary> /// </summary>
public VisualLine GetVisualLineFromVisualTop(double visualTop) public VisualLine GetVisualLineFromVisualTop(double visualTop)
{ {
EnsureVisualLines();
foreach (VisualLine vl in this.VisualLines) { foreach (VisualLine vl in this.VisualLines) {
if (visualTop < vl.VisualTop) if (visualTop < vl.VisualTop)
continue; continue;
@ -1074,5 +1083,36 @@ namespace ICSharpCode.AvalonEdit.Gui
return null; return null;
} }
#endregion #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 @@
// <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 @@
<Compile Include="Gui\VisualLineElement.cs" /> <Compile Include="Gui\VisualLineElement.cs" />
<Compile Include="Gui\VisualLineElementGenerator.cs"> <Compile Include="Gui\VisualLineElementGenerator.cs">
</Compile> </Compile>
<Compile Include="Gui\VisualLinesInvalidException.cs" />
<Compile Include="Gui\VisualLineText.cs" /> <Compile Include="Gui\VisualLineText.cs" />
<Compile Include="Gui\VisualLineTextParagraphProperties.cs"> <Compile Include="Gui\VisualLineTextParagraphProperties.cs">
<DependentUpon>VisualLine.cs</DependentUpon> <DependentUpon>VisualLine.cs</DependentUpon>

Loading…
Cancel
Save