diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/FoldingSection.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/FoldingSection.cs
index c4077d1ac1..a1be0a6a43 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/FoldingSection.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/FoldingSection.cs
@@ -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
collapsedSection = null;
}
}
- manager.textView.Redraw();
+ manager.textView.Redraw(this, DispatcherPriority.Normal);
}
}
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/NoReadOnlySections.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/NoReadOnlySections.cs
index c658a4b86c..2849d28137 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/NoReadOnlySections.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/NoReadOnlySections.cs
@@ -12,6 +12,9 @@ using System.Collections.Generic;
namespace ICSharpCode.AvalonEdit.Gui
{
+ ///
+ /// that has no read-only sections; all text is editable.
+ ///
sealed class NoReadOnlySections : IReadOnlySectionProvider
{
public static readonly NoReadOnlySections Instance = new NoReadOnlySections();
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs
index 567d398f21..ab74a08921 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs
@@ -23,8 +23,43 @@ namespace ICSharpCode.AvalonEdit.Gui
///
sealed class SelectionMouseHandler
{
+ #region enum SelectionMode
+ enum SelectionMode
+ {
+ ///
+ /// no selection (no mouse button down)
+ ///
+ None,
+ ///
+ /// left mouse button down on selection, might be normal click
+ /// or might be drag'n'drop
+ ///
+ PossibleDragStart,
+ ///
+ /// dragging text
+ ///
+ Drag,
+ ///
+ /// normal selection (click+drag)
+ ///
+ Normal,
+ ///
+ /// whole-word selection (double click+drag)
+ ///
+ 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;
@@ -46,7 +81,27 @@ namespace ICSharpCode.AvalonEdit.Gui
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")]
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;
- 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 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
}
}
}
+ #endregion
+ #region ContainsOffset helper methods
static bool SelectionContains(Selection selection, int offset)
{
if (selection.IsEmpty)
@@ -194,36 +289,9 @@ namespace ICSharpCode.AvalonEdit.Gui
int end = start + segment.Length;
return offset >= start && offset <= end;
}
+ #endregion
- enum SelectionMode
- {
- ///
- /// no selection (no mouse button down)
- ///
- None,
- ///
- /// left mouse button down on selection, might be normal click
- /// or might be drag'n'drop
- ///
- PossibleDragStart,
- ///
- /// dragging text
- ///
- Drag,
- ///
- /// normal selection (click+drag)
- ///
- Normal,
- ///
- /// whole-word selection (double click+drag)
- ///
- 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
}
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
}
}
- 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
}
return -1;
}
+ #endregion
+ #region MouseMove
void textArea_MouseMove(object sender, MouseEventArgs e)
{
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)
{
@@ -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
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 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
}
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs
index 1543c86264..50866ab912 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs
@@ -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
}
#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
Undo();
}
}
+ #endregion
- readonly Caret caret;
-
+ #region TextView property
+ readonly TextView textView;
+ IScrollInfo scrollInfo;
+
///
- /// Gets the Caret used for this text area.
+ /// Gets the text view used to display text in this text area.
///
- public Caret Caret {
- get { return caret; }
+ public TextView TextView {
+ get {
+ return textView;
+ }
}
+ ///
+ public override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+ scrollInfo = textView;
+ ApplyScrollInfo();
+ }
+ #endregion
+ #region Selection property
Selection selection = Selection.Empty;
+ ///
+ /// Occurs when the selection has changed.
+ ///
+ public event EventHandler SelectionChanged;
+
///
/// Gets/Sets the selection in this text area.
///
@@ -213,30 +234,16 @@ namespace ICSharpCode.AvalonEdit
}
}
}
+ #endregion
- ///
- /// Occurs when the selection has changed.
- ///
- public event EventHandler SelectionChanged;
-
- readonly TextView textView;
- IScrollInfo scrollInfo;
-
- ///
- public override void OnApplyTemplate()
- {
- base.OnApplyTemplate();
- scrollInfo = textView;
- ApplyScrollInfo();
- }
+ #region Properties
+ readonly Caret caret;
///
- /// Gets the text view used to display text in this text area.
+ /// Gets the Caret used for this text area.
///
- public TextView TextView {
- get {
- return textView;
- }
+ public Caret Caret {
+ get { return caret; }
}
ObservableCollection leftMargins = new ObservableCollection();
@@ -250,6 +257,21 @@ namespace ICSharpCode.AvalonEdit
}
}
+ IReadOnlySectionProvider readOnlySectionProvider = NoReadOnlySections.Instance;
+
+ ///
+ /// Gets/Sets an object that provides read-only sections for the text area.
+ ///
+ 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
}
#endregion
+ #region Focus Handling (Show/Hide Caret)
///
protected override void OnMouseDown(MouseButtonEventArgs e)
{
@@ -454,21 +477,9 @@ namespace ICSharpCode.AvalonEdit
caret.Hide();
e.Handled = true;
}
+ #endregion
- IReadOnlySectionProvider readOnlySectionProvider = NoReadOnlySections.Instance;
-
- ///
- /// Gets/Sets an object that provides read-only sections for the text area.
- ///
- public IReadOnlySectionProvider ReadOnlySectionProvider {
- get { return readOnlySectionProvider; }
- set {
- if (value == null)
- throw new ArgumentNullException("value");
- readOnlySectionProvider = value;
- }
- }
-
+ #region OnTextInput / RemoveSelectedText / ReplaceSelectionWithText
///
protected override void OnTextInput(TextCompositionEventArgs e)
{
@@ -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
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
Document.EndUpdate();
}
}
+ #endregion
}
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
index 898c57ac7b..a0304ad1cc 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
@@ -26,9 +26,13 @@ namespace ICSharpCode.AvalonEdit.Gui
{
///
/// A virtualizing panel producing+showing s for a .
+ ///
+ /// 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.
///
[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
}
#endregion
- #region Properties
+ #region Document Property
///
/// Document property.
///
@@ -119,6 +123,7 @@ namespace ICSharpCode.AvalonEdit.Gui
}
#endregion
+ #region Collection Properties
readonly ObservableCollection elementGenerators = new ObservableCollection();
///
@@ -145,13 +150,15 @@ namespace ICSharpCode.AvalonEdit.Gui
public UIElementCollection Adorners {
get { return adorners; }
}
+ #endregion
+ #region Redraw methods / VisualLine invalidation
///
/// Causes the text editor to regenerate all visual lines.
///
public void Redraw()
{
- Redraw(DispatcherPriority.Render);
+ Redraw(DispatcherPriority.Normal);
}
///
@@ -183,23 +190,21 @@ 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];
- int lineStart = visualLine.FirstDocumentLine.Offset;
- int lineEnd = visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength;
- if (!(lineEnd < offset || lineStart > offset + length)) {
- removedLine = true;
- allVisualLines.RemoveAt(i--);
- DisposeVisualLine(visualLine);
- }
- }
- if (removedLine) {
- visibleVisualLines = null;
- InvalidateMeasure(redrawPriority);
+ bool removedLine = false;
+ for (int i = 0; i < allVisualLines.Count; i++) {
+ VisualLine visualLine = allVisualLines[i];
+ int lineStart = visualLine.FirstDocumentLine.Offset;
+ int lineEnd = visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength;
+ if (!(lineEnd < offset || lineStart > offset + length)) {
+ removedLine = true;
+ allVisualLines.RemoveAt(i--);
+ DisposeVisualLine(visualLine);
}
}
+ if (removedLine) {
+ visibleVisualLines = null;
+ InvalidateMeasure(redrawPriority);
+ }
}
///
@@ -213,6 +218,36 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
+ ///
+ /// Invalidates all visual lines.
+ /// The caller of ClearVisualLines() must also call InvalidateMeasure() to ensure
+ /// that the visual lines will be recreated.
+ ///
+ 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
}
}
}
+ #endregion
- ///
- /// Waits for the visual lines to be built.
- ///
- 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
///
/// 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
}
return l;
}
+ #endregion
- ///
- /// Collapses lines for the purpose of scrolling. This method is meant for
- /// s that cause s to span
- /// multiple s. Do not call it without providing a corresponding
- /// .
- /// If you want to create collapsible text sections, see .
- ///
- public CollapsedLineSection CollapseLines(DocumentLine start, DocumentLine end)
- {
- VerifyAccess();
- return heightTree.CollapseText(start, end);
- }
-
- ///
- /// Gets the height of the document.
- ///
- public double DocumentHeight {
- get { return heightTree.TotalHeight; }
- }
-
- #region Measure
- TextFormatter formatter;
+ #region Visual Lines (fields and properties)
List allVisualLines = new List();
ReadOnlyCollection visibleVisualLines;
double clippedPixelsOnTop;
+ List newVisualLines;
///
/// Gets the currently visible visual lines.
///
+ ///
+ /// Gets thrown if there are invalid visual lines when this property is accessed.
+ /// You can use the property to check for this case,
+ /// or use the method to force creating the visual lines
+ /// when they are invalid.
+ ///
public ReadOnlyCollection VisualLines {
get {
- EnsureVisualLines();
+ if (visibleVisualLines == null)
+ throw new VisualLinesInvalidException();
return visibleVisualLines;
}
}
@@ -375,56 +363,75 @@ namespace ICSharpCode.AvalonEdit.Gui
///
public event EventHandler VisualLinesChanged;
- TextRunProperties CreateGlobalTextRunProperties()
- {
- return new GlobalTextRunProperties {
- typeface = this.CreateTypeface(),
- fontRenderingEmSize = LineHeight,
- foregroundBrush = (Brush)GetValue(Control.ForegroundProperty),
- cultureInfo = CultureInfo.CurrentCulture
- };
- }
-
- TextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
+ ///
+ /// 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.
+ ///
+ /// The visual line build process is already running.
+ /// It is not allowed to call this method during the construction of a visual line.
+ public void EnsureVisualLines()
{
- return new VisualLineTextParagraphProperties {
- defaultTextRunProperties = defaultTextRunProperties,
- textWrapping = canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
- tabSize = 4 * WideSpaceWidth
- };
+ 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();
+ }
}
+ #endregion
+ #region Measure
Size lastAvailableSize;
- List newVisualLines;
+ bool inMeasure;
- ///
- /// Measure implementation.
- ///
+ ///
protected override Size MeasureOverride(Size availableSize)
{
if (!canHorizontallyScroll && !availableSize.Width.IsClose(lastAvailableSize.Width))
ClearVisualLines();
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);
+ }
}
///
- /// Immediately performs the text creation.
+ /// Build all VisualLines in the visible range.
///
- /// The size of the text view.
- ///
- Size DoMeasure(Size availableSize)
+ /// Width the longest line
+ double CreateAndMeasureVisualLines(Size availableSize)
{
- bool isRealMeasure = true;
- if (isRealMeasure)
- RemoveInlineObjectsNow();
-
- if (document == null)
- return Size.Empty;
-
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
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
"This can happen when Redraw() is called during measure for lines " +
"that are already constructed.");
}
-
- 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);
- }
+ return maxWidth;
+ }
+ #endregion
+
+ #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
}
#endregion
- ///
- /// Gets the document line at the specified visual position.
- ///
- public DocumentLine GetDocumentLineByVisualTop(double visualTop)
- {
- VerifyAccess();
- if (heightTree == null)
- throw new InvalidOperationException();
- return heightTree.GetLineByVisualPosition(visualTop);
- }
-
#region Visual element mouse handling
///
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
@@ -1041,15 +1048,17 @@ namespace ICSharpCode.AvalonEdit.Gui
}
}
}
+ #endregion
+ #region Getting elements from Visual Position
///
/// 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 () before calling this method.
///
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
return null;
}
#endregion
+
+ ///
+ /// Collapses lines for the purpose of scrolling. This method is meant for
+ /// s that cause s to span
+ /// multiple s. Do not call it without providing a corresponding
+ /// .
+ /// If you want to create collapsible text sections, see .
+ ///
+ public CollapsedLineSection CollapseLines(DocumentLine start, DocumentLine end)
+ {
+ VerifyAccess();
+ return heightTree.CollapseText(start, end);
+ }
+
+ ///
+ /// Gets the height of the document.
+ ///
+ public double DocumentHeight {
+ get { return heightTree.TotalHeight; }
+ }
+
+ ///
+ /// Gets the document line at the specified visual position.
+ ///
+ public DocumentLine GetDocumentLineByVisualTop(double visualTop)
+ {
+ VerifyAccess();
+ if (heightTree == null)
+ throw new InvalidOperationException();
+ return heightTree.GetLineByVisualPosition(visualTop);
+ }
}
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLinesInvalidException.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLinesInvalidException.cs
new file mode 100644
index 0000000000..268f94c267
--- /dev/null
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/VisualLinesInvalidException.cs
@@ -0,0 +1,48 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.Runtime.Serialization;
+
+namespace ICSharpCode.AvalonEdit.Gui
+{
+ ///
+ /// A VisualLinesInvalidException indicates that you accessed the property
+ /// of the while the visual lines were invalid.
+ ///
+ [Serializable]
+ public class VisualLinesInvalidException : Exception
+ {
+ ///
+ /// Creates a new VisualLinesInvalidException instance.
+ ///
+ public VisualLinesInvalidException() : base()
+ {
+ }
+
+ ///
+ /// Creates a new VisualLinesInvalidException instance.
+ ///
+ public VisualLinesInvalidException(string message) : base(message)
+ {
+ }
+
+ ///
+ /// Creates a new VisualLinesInvalidException instance.
+ ///
+ public VisualLinesInvalidException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ ///
+ /// Creates a new VisualLinesInvalidException instance.
+ ///
+ protected VisualLinesInvalidException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
index 283e1c5137..103d09ca76 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
@@ -168,6 +168,7 @@
+
VisualLine.cs