Browse Source

AvalonEdit: Minor bugfixes / API improvements.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3843 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
8fe42afbd7
  1. 13
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs
  2. 20
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs
  3. 24
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/Selection.cs
  4. 54
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/SelectionMouseHandler.cs
  5. 69
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs
  6. 23
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs
  7. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
  8. 1
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  9. 39
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CallbackOnDispose.cs
  10. 15
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ExtensionMethods.cs

13
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs

@ -216,9 +216,20 @@ namespace ICSharpCode.AvalonEdit.Document @@ -216,9 +216,20 @@ namespace ICSharpCode.AvalonEdit.Document
}
}
/// <summary>
/// Immediately calls <see cref="BeginUpdate()"/>,
/// and returns an IDisposable that calls <see cref="EndUpdate()"/>.
/// </summary>
public IDisposable RunUpdate()
{
BeginUpdate();
return new CallbackOnDispose(EndUpdate);
}
/// <summary>
/// Begins a group of document changes.
/// DocumentParsers and some events are suspended until EndUpdate is called.
/// Some events are suspended until EndUpdate is called, and the <see cref="UndoStack"/> will
/// group all changes into a single action.
/// Calling BeginUpdate several times increments a counter, only after the appropriate number
/// of EndUpdate calls the DocumentParsers and events resume their work.
/// </summary>

20
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs

@ -72,8 +72,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -72,8 +72,7 @@ namespace ICSharpCode.AvalonEdit.Gui
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
textArea.Document.BeginUpdate();
try {
using (textArea.Document.RunUpdate()) {
if (textArea.Selection.GetIsMultiline(textArea.Document)) {
DocumentLine start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset);
DocumentLine end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.GetEndOffset());
@ -88,8 +87,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -88,8 +87,6 @@ namespace ICSharpCode.AvalonEdit.Gui
} else {
textArea.ReplaceSelectionWithText(indentationString);
}
} finally {
textArea.Document.EndUpdate();
}
textArea.Caret.BringCaretToView();
args.Handled = true;
@ -100,8 +97,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -100,8 +97,7 @@ namespace ICSharpCode.AvalonEdit.Gui
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
textArea.Document.BeginUpdate();
try {
using (textArea.Document.RunUpdate()) {
DocumentLine start, end;
if (textArea.Selection.IsEmpty) {
start = end = textArea.Document.GetLineByNumber(textArea.Caret.Line);
@ -122,8 +118,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -122,8 +118,6 @@ namespace ICSharpCode.AvalonEdit.Gui
break;
start = textArea.Document.GetLineByNumber(start.LineNumber + 1);
}
} finally {
textArea.Document.EndUpdate();
}
textArea.Caret.BringCaretToView();
args.Handled = true;
@ -159,9 +153,13 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -159,9 +153,13 @@ namespace ICSharpCode.AvalonEdit.Gui
return (target, args) => {
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
if (textArea.Selection.IsEmpty)
selectingCommand.Execute(args.Parameter, textArea);
textArea.RemoveSelectedText();
// call BeginUpdate before running the 'selectingCommand'
// so that undoing the delete does not select the deleted character
using (textArea.Document.RunUpdate()) {
if (textArea.Selection.IsEmpty)
selectingCommand.Execute(args.Parameter, textArea);
textArea.RemoveSelectedText();
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}

24
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/Selection.cs

@ -122,6 +122,25 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -122,6 +122,25 @@ namespace ICSharpCode.AvalonEdit.Gui
/// <inheritdoc/>
public abstract override int GetHashCode();
/// <summary>
/// Gets whether the specified offset is included in the selection.
/// </summary>
/// <returns>True, if the selection contains the offset (selection borders inclusive);
/// otherwise, false.</returns>
public bool Contains(int offset)
{
if (this.IsEmpty)
return false;
if (this.SurroundingSegment.Contains(offset)) {
foreach (ISegment s in this.Segments) {
if (s.Contains(offset)) {
return true;
}
}
}
return false;
}
}
/// <summary>
@ -177,13 +196,10 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -177,13 +196,10 @@ namespace ICSharpCode.AvalonEdit.Gui
{
if (!IsEmpty) {
var segmentsToDelete = textArea.ReadOnlySectionProvider.GetDeletableSegments(this).ToList();
textArea.Document.BeginUpdate();
try {
using (textArea.Document.RunUpdate()) {
for (int i = segmentsToDelete.Count - 1; i >= 0; i--) {
textArea.Document.Remove(segmentsToDelete[i].Offset, segmentsToDelete[i].Length);
}
} finally {
textArea.Document.EndUpdate();
}
}
}

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

@ -162,7 +162,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -162,7 +162,7 @@ namespace ICSharpCode.AvalonEdit.Gui
string text = e.Data.GetData(DataFormats.UnicodeText, true) as string;
if (text != null) {
int start = textArea.Caret.Offset;
if (mode == SelectionMode.Drag && Contains(textArea.Selection.SurroundingSegment, start)) {
if (mode == SelectionMode.Drag && textArea.Selection.Contains(start)) {
Debug.WriteLine("Drop: did not drop: drop target is inside selection");
e.Effects = DragDropEffects.None;
} else {
@ -244,14 +244,21 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -244,14 +244,21 @@ namespace ICSharpCode.AvalonEdit.Gui
this.currentDragDescriptor = dragDescriptor;
DragDropEffects resultEffect;
try {
Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects);
resultEffect = DragDrop.DoDragDrop(textArea, dataObject, allowedEffects);
Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect);
} catch (COMException ex) {
// ignore COM errors - don't crash on badly implemented drop targets
Debug.WriteLine("DoDragDrop failed: " + ex.ToString());
return;
using (textArea.AllowCaretOutsideSelection()) {
var oldCaretPosition = textArea.Caret.Position;
try {
Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects);
resultEffect = DragDrop.DoDragDrop(textArea, dataObject, allowedEffects);
Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect);
} catch (COMException ex) {
// ignore COM errors - don't crash on badly implemented drop targets
Debug.WriteLine("DoDragDrop failed: " + ex.ToString());
return;
}
if (resultEffect == DragDropEffects.None) {
// reset caret if drag was aborted
textArea.Caret.Position = oldCaretPosition;
}
}
this.currentDragDescriptor = null;
@ -290,7 +297,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -290,7 +297,7 @@ namespace ICSharpCode.AvalonEdit.Gui
if (p.X >= 0 && p.Y >= 0 && p.X <= textArea.TextView.ActualWidth && p.Y <= textArea.TextView.ActualHeight) {
int visualColumn;
int offset = GetOffsetFromMousePosition(e, out visualColumn);
if (SelectionContains(textArea.Selection, offset))
if (textArea.Selection.Contains(offset))
e.Cursor = Cursors.Arrow;
else
e.Cursor = Cursors.IBeam;
@ -301,31 +308,6 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -301,31 +308,6 @@ namespace ICSharpCode.AvalonEdit.Gui
}
#endregion
#region ContainsOffset helper methods
static bool SelectionContains(Selection selection, int offset)
{
if (selection.IsEmpty)
return false;
if (offset >= 0 && Contains(selection.SurroundingSegment, offset)) {
foreach (ISegment s in selection.Segments) {
if (Contains(s, offset)) {
return true;
}
}
}
return false;
}
static bool Contains(ISegment segment, int offset)
{
if (segment == null)
return false;
int start = segment.Offset;
int end = start + segment.Length;
return offset >= start && offset <= end;
}
#endregion
#region LeftButtonDown
void textArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
@ -335,7 +317,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -335,7 +317,7 @@ namespace ICSharpCode.AvalonEdit.Gui
if (AllowTextDragDrop && e.ClickCount == 1 && !shift) {
int visualColumn;
int offset = GetOffsetFromMousePosition(e, out visualColumn);
if (SelectionContains(textArea.Selection, offset)) {
if (textArea.Selection.Contains(offset)) {
if (textArea.CaptureMouse()) {
mode = SelectionMode.PossibleDragStart;
possibleDragStartMousePos = e.GetPosition(textArea);

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

@ -76,6 +76,7 @@ namespace ICSharpCode.AvalonEdit @@ -76,6 +76,7 @@ namespace ICSharpCode.AvalonEdit
textView.InsertLayer(new SelectionLayer(this), KnownLayer.Selection, LayerInsertionPosition.Replace);
caret = new Caret(this);
caret.PositionChanged += (sender, e) => RequestSelectionValidation();
this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, ExecuteUndo, CanExecuteUndo));
this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Redo, ExecuteRedo, CanExecuteRedo));
@ -234,6 +235,63 @@ namespace ICSharpCode.AvalonEdit @@ -234,6 +235,63 @@ namespace ICSharpCode.AvalonEdit
}
#endregion
#region Force caret to stay inside selection
bool ensureSelectionValidRequested;
int allowCaretOutsideSelection;
void RequestSelectionValidation()
{
if (!ensureSelectionValidRequested && allowCaretOutsideSelection == 0) {
ensureSelectionValidRequested = true;
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(EnsureSelectionValid));
}
}
/// <summary>
/// Code that updates only the caret but not the selection can cause confusion when
/// keys like 'Delete' delete the (possibly invisible) selected text and not the
/// text around the caret (where the will jump to).
///
/// So we'll ensure that the caret is inside the selection.
/// (when the caret is not in the selection, we'll clear the selection)
///
/// This method is invoked using the Dispatcher so that code may temporarily violate this rule
/// (e.g. most 'exten selection' methods work by first setting the caret, then the selection),
/// it's sufficient to fix it after any event handlers have run.
/// </summary>
void EnsureSelectionValid()
{
ensureSelectionValidRequested = false;
if (allowCaretOutsideSelection == 0) {
if (!selection.Contains(caret.Offset))
this.Selection = Selection.Empty;
}
}
/// <summary>
/// Temporarily allows positioning the caret outside the selection.
/// Dispose the returned IDisposable to revert the allowance.
/// </summary>
/// <remarks>
/// The text area only forces the caret to be inside the selection when other events
/// have finished running (using the dispatcher), so you don't have to use this method
/// for temporarily positioning the caret in event handlers.
/// This method is only necessary if you want to run the WPF dispatcher, e.g. if you
/// perform a drag'n'drop operation.
/// </remarks>
public IDisposable AllowCaretOutsideSelection()
{
VerifyAccess();
allowCaretOutsideSelection++;
return new CallbackOnDispose(
delegate {
VerifyAccess();
allowCaretOutsideSelection--;
RequestSelectionValidation();
});
}
#endregion
#region Properties
readonly Caret caret;
@ -483,6 +541,12 @@ namespace ICSharpCode.AvalonEdit @@ -483,6 +541,12 @@ namespace ICSharpCode.AvalonEdit
{
base.OnTextInput(e);
if (!e.Handled) {
if (e.Text == "\x1b") {
// ASCII 0x1b = ESC.
// WPF produces a TextInput event with that old ASCII control char
// when Escape is pressed. We'll just ignore it.
return;
}
TextDocument document = this.Document;
if (document != null) {
ReplaceSelectionWithText(e.Text);
@ -506,14 +570,11 @@ namespace ICSharpCode.AvalonEdit @@ -506,14 +570,11 @@ namespace ICSharpCode.AvalonEdit
internal void ReplaceSelectionWithText(string newText)
{
Document.BeginUpdate();
try {
using (Document.RunUpdate()) {
RemoveSelectedText();
if (ReadOnlySectionProvider.CanInsert(Caret.Offset)) {
Document.Insert(Caret.Offset, newText);
}
} finally {
Document.EndUpdate();
}
}
#endregion

23
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs

@ -246,23 +246,7 @@ namespace ICSharpCode.AvalonEdit @@ -246,23 +246,7 @@ namespace ICSharpCode.AvalonEdit
/// </summary>
public IDisposable DeclareChangeBlock()
{
return new ChangeBlock(GetOrCreateDocument());
}
sealed class ChangeBlock : IDisposable
{
TextDocument document;
public ChangeBlock(TextDocument document)
{
this.document = document;
document.BeginUpdate();
}
public void Dispose()
{
document.EndUpdate();
}
return GetOrCreateDocument().RunUpdate();
}
/// <summary>
@ -510,13 +494,10 @@ namespace ICSharpCode.AvalonEdit @@ -510,13 +494,10 @@ namespace ICSharpCode.AvalonEdit
throw new ArgumentNullException("value");
TextArea textArea = this.TextArea;
if (textArea != null && textArea.Document != null) {
textArea.Document.BeginUpdate();
try {
using (textArea.Document.RunUpdate()) {
textArea.RemoveSelectedText();
if (textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset))
textArea.Document.Insert(textArea.Caret.Offset, value);
} finally {
textArea.Document.EndUpdate();
}
}
}

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

@ -39,7 +39,7 @@ namespace ICSharpCode.AvalonEdit.Gui @@ -39,7 +39,7 @@ namespace ICSharpCode.AvalonEdit.Gui
#region Constructor
static TextView()
{
ClipToBoundsProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(true));
ClipToBoundsProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(Boxes.True));
}
/// <summary>

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

@ -254,6 +254,7 @@ @@ -254,6 +254,7 @@
<Compile Include="Utils\CompressingTreeList.cs" />
<Compile Include="Utils\Constants.cs" />
<Compile Include="Utils\DelayedEvents.cs" />
<Compile Include="Utils\CallbackOnDispose.cs" />
<Compile Include="Utils\Empty.cs" />
<Compile Include="Utils\ExtensionMethods.cs" />
<Compile Include="Utils\FileReader.cs" />

39
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/CallbackOnDispose.cs

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
// <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.Diagnostics;
using System.Threading;
namespace ICSharpCode.AvalonEdit.Utils
{
/// <summary>
/// Invokes an action when it is disposed.
/// </summary>
/// <remarks>
/// This class ensures the callback is invoked at most once,
/// even when Dispose is called on multiple theadeds.
/// </remarks>
sealed class CallbackOnDispose : IDisposable
{
Action action;
public CallbackOnDispose(Action action)
{
Debug.Assert(action != null);
this.action = action;
}
public void Dispose()
{
Action a = Interlocked.Exchange(ref action, null);
if (a != null) {
a();
}
}
}
}

15
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/ExtensionMethods.cs

@ -109,5 +109,20 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -109,5 +109,20 @@ namespace ICSharpCode.AvalonEdit.Utils
{
return segment.Offset + segment.Length;
}
/// <summary>
/// Gets whether the segment contains the offset.
/// </summary>
/// <returns>
/// True, if offset is between segment.Start and segment.End (inclusive); otherwise, false.
/// </returns>
public static bool Contains(this ISegment segment, int offset)
{
if (segment == null)
return false;
int start = segment.Offset;
int end = start + segment.Length;
return offset >= start && offset <= end;
}
}
}

Loading…
Cancel
Save