Browse Source
git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5064 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
12 changed files with 717 additions and 0 deletions
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Snippets |
||||
{ |
||||
/// <summary>
|
||||
/// Represents an active element that allows the snippet to stay interactive after insertion.
|
||||
/// </summary>
|
||||
public interface IActiveElement |
||||
{ |
||||
/// <summary>
|
||||
/// Called when the all snippet elements have been inserted.
|
||||
/// </summary>
|
||||
void OnInsertionCompleted(); |
||||
|
||||
/// <summary>
|
||||
/// Called when the interactive mode is deactivated.
|
||||
/// </summary>
|
||||
void Deactivate(); |
||||
} |
||||
} |
@ -0,0 +1,178 @@
@@ -0,0 +1,178 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Windows.Input; |
||||
|
||||
using ICSharpCode.AvalonEdit.Document; |
||||
using ICSharpCode.AvalonEdit.Editing; |
||||
using ICSharpCode.AvalonEdit.Utils; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Snippets |
||||
{ |
||||
/// <summary>
|
||||
/// Represents the context of a snippet insertion.
|
||||
/// </summary>
|
||||
public class InsertionContext |
||||
{ |
||||
/// <summary>
|
||||
/// Creates a new InsertionContext instance.
|
||||
/// </summary>
|
||||
public InsertionContext(TextArea textArea, int insertionPosition) |
||||
{ |
||||
if (textArea == null) |
||||
throw new ArgumentNullException("textArea"); |
||||
this.TextArea = textArea; |
||||
this.Document = textArea.Document; |
||||
this.InsertionPosition = insertionPosition; |
||||
|
||||
DocumentLine startLine = this.Document.GetLineByOffset(insertionPosition); |
||||
ISegment indentation = TextUtilities.GetWhitespaceAfter(this.Document, startLine.Offset); |
||||
this.Indentation = Document.GetText(indentation.Offset, Math.Min(indentation.EndOffset, insertionPosition) - indentation.Offset); |
||||
|
||||
this.LineTerminator = NewLineFinder.GetNewLineFromDocument(this.Document, startLine.LineNumber); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the text area.
|
||||
/// </summary>
|
||||
public TextArea TextArea { get; private set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the text document.
|
||||
/// </summary>
|
||||
public TextDocument Document { get; private set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the indentation at the insertion position.
|
||||
/// </summary>
|
||||
public string Indentation { get; private set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the line terminator at the insertion position.
|
||||
/// </summary>
|
||||
public string LineTerminator { get; private set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets the insertion position.
|
||||
/// </summary>
|
||||
public int InsertionPosition { get; set; } |
||||
|
||||
/// <summary>
|
||||
/// Inserts text at the insertion position and advances the insertion position.
|
||||
/// </summary>
|
||||
public void InsertText(string text) |
||||
{ |
||||
if (text == null) |
||||
throw new ArgumentNullException("text"); |
||||
int textOffset = 0; |
||||
SimpleSegment segment; |
||||
while ((segment = NewLineFinder.NextNewLine(text, textOffset)) != SimpleSegment.Invalid) { |
||||
string insertString = text.Substring(textOffset, segment.Offset - textOffset) |
||||
+ this.LineTerminator + this.Indentation; |
||||
this.Document.Insert(InsertionPosition, insertString); |
||||
this.InsertionPosition += insertString.Length; |
||||
textOffset = segment.EndOffset; |
||||
} |
||||
string remainingInsertString = text.Substring(textOffset); |
||||
this.Document.Insert(InsertionPosition, remainingInsertString); |
||||
this.InsertionPosition += remainingInsertString.Length; |
||||
} |
||||
|
||||
Dictionary<SnippetElement, IActiveElement> elementMap = new Dictionary<SnippetElement, IActiveElement>(); |
||||
List<IActiveElement> registeredElements = new List<IActiveElement>(); |
||||
|
||||
/// <summary>
|
||||
/// Registers an active element. Elements should be registered during insertion and will be called back
|
||||
/// when insertion has completed.
|
||||
/// </summary>
|
||||
/// <param name="owner">The snippet element that created the active element.</param>
|
||||
/// <param name="element">The active element.</param>
|
||||
public void RegisterActiveElement(SnippetElement owner, IActiveElement element) |
||||
{ |
||||
if (owner == null) |
||||
throw new ArgumentNullException("owner"); |
||||
if (element == null) |
||||
throw new ArgumentNullException("element"); |
||||
elementMap.Add(owner, element); |
||||
registeredElements.Add(element); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Returns the active element belonging to the specified snippet element, or null if no such active element is found.
|
||||
/// </summary>
|
||||
public IActiveElement GetActiveElement(SnippetElement owner) |
||||
{ |
||||
if (owner == null) |
||||
throw new ArgumentNullException("owner"); |
||||
IActiveElement element; |
||||
if (elementMap.TryGetValue(owner, out element)) |
||||
return element; |
||||
else |
||||
return null; |
||||
} |
||||
|
||||
bool insertionCompleted; |
||||
|
||||
/// <summary>
|
||||
/// Calls the <see cref="IActiveElement.OnInsertionCompleted"/> method on all registered active elements
|
||||
/// and raises the <see cref="InsertionCompleted"/> event.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", |
||||
Justification="There is an event and this method is raising it.")] |
||||
public void RaiseInsertionCompleted() |
||||
{ |
||||
foreach (IActiveElement element in registeredElements) { |
||||
element.OnInsertionCompleted(); |
||||
} |
||||
if (InsertionCompleted != null) |
||||
InsertionCompleted(this, EventArgs.Empty); |
||||
insertionCompleted = true; |
||||
if (registeredElements.Count == 0) { |
||||
// deactivate immediately if there are no interactive elements
|
||||
Deactivate(); |
||||
} else { |
||||
// register Escape key handler
|
||||
TextArea.KeyDown += TextArea_KeyDown; |
||||
} |
||||
} |
||||
|
||||
void TextArea_KeyDown(object sender, KeyEventArgs e) |
||||
{ |
||||
if (e.Key == Key.Escape) { |
||||
Deactivate(); |
||||
e.Handled = true; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Occurs when the all snippet elements have been inserted.
|
||||
/// </summary>
|
||||
public event EventHandler InsertionCompleted; |
||||
|
||||
/// <summary>
|
||||
/// Calls the <see cref="IActiveElement.Deactivate"/> method on all registered active elements.
|
||||
/// </summary>
|
||||
public void Deactivate() |
||||
{ |
||||
if (!insertionCompleted) |
||||
throw new InvalidOperationException("Cannot call Deactivate() until RaiseInsertionCompleted() has finished."); |
||||
TextArea.KeyDown -= TextArea_KeyDown; |
||||
foreach (IActiveElement element in registeredElements) { |
||||
element.Deactivate(); |
||||
} |
||||
if (Deactivated != null) |
||||
Deactivated(this, EventArgs.Empty); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Occurs when the interactive mode is deactivated.
|
||||
/// </summary>
|
||||
public event EventHandler Deactivated; |
||||
} |
||||
} |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ICSharpCode.AvalonEdit.Editing; |
||||
using ICSharpCode.AvalonEdit.Utils; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Snippets |
||||
{ |
||||
/// <summary>
|
||||
/// A code snippet that can be inserted into the text editor.
|
||||
/// </summary>
|
||||
[Serializable] |
||||
public class Snippet : SnippetContainerElement |
||||
{ |
||||
/// <summary>
|
||||
/// Inserts the snippet into the text area.
|
||||
/// </summary>
|
||||
public void Insert(TextArea textArea) |
||||
{ |
||||
if (textArea == null) |
||||
throw new ArgumentNullException("textArea"); |
||||
Freeze(); |
||||
InsertionContext context = new InsertionContext(textArea, textArea.Caret.Offset); |
||||
using (context.Document.RunUpdate()) { |
||||
Insert(context); |
||||
context.RaiseInsertionCompleted(); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Snippets |
||||
{ |
||||
/// <summary>
|
||||
/// An element that binds to a <see cref="SnippetReplaceableTextElement"/> and displays the same text.
|
||||
/// </summary>
|
||||
[Serializable] |
||||
public class SnippetBoundElement : SnippetElement |
||||
{ |
||||
SnippetReplaceableTextElement targetElement; |
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets the target element.
|
||||
/// </summary>
|
||||
public SnippetReplaceableTextElement TargetElement { |
||||
get { return targetElement; } |
||||
set { |
||||
CheckBeforeMutation(); |
||||
targetElement = value; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Converts the text before copying it.
|
||||
/// </summary>
|
||||
public virtual string ConvertText(string input) |
||||
{ |
||||
return input; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Insert(InsertionContext context) |
||||
{ |
||||
if (targetElement != null) { |
||||
int start = context.InsertionPosition; |
||||
string inputText = targetElement.Text; |
||||
if (inputText != null) { |
||||
context.InsertText(ConvertText(inputText)); |
||||
} |
||||
int end = context.InsertionPosition; |
||||
AnchorSegment segment = new AnchorSegment(context.Document, start, end - start); |
||||
context.RegisterActiveElement(this, new BoundActiveElement(context, targetElement, this, segment)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
sealed class BoundActiveElement : IActiveElement |
||||
{ |
||||
InsertionContext context; |
||||
SnippetReplaceableTextElement targetSnippetElement; |
||||
SnippetBoundElement boundElement; |
||||
IReplaceableActiveElement targetElement; |
||||
AnchorSegment segment; |
||||
|
||||
public BoundActiveElement(InsertionContext context, SnippetReplaceableTextElement targetSnippetElement, SnippetBoundElement boundElement, AnchorSegment segment) |
||||
{ |
||||
this.context = context; |
||||
this.targetSnippetElement = targetSnippetElement; |
||||
this.boundElement = boundElement; |
||||
this.segment = segment; |
||||
} |
||||
|
||||
public void OnInsertionCompleted() |
||||
{ |
||||
targetElement = context.GetActiveElement(targetSnippetElement) as IReplaceableActiveElement; |
||||
if (targetElement != null) { |
||||
targetElement.TextChanged += targetElement_TextChanged; |
||||
} |
||||
} |
||||
|
||||
void targetElement_TextChanged(object sender, EventArgs e) |
||||
{ |
||||
int offset = segment.Offset; |
||||
int length = segment.Length; |
||||
string text = boundElement.ConvertText(targetElement.Text); |
||||
context.Document.Replace(offset, length, text); |
||||
if (length == 0) { |
||||
// replacing an empty anchor segment with text won't enlarge it, so we have to recreate it
|
||||
segment = new AnchorSegment(context.Document, offset, text.Length); |
||||
} |
||||
} |
||||
|
||||
public void Deactivate() |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Snippets |
||||
{ |
||||
/// <summary>
|
||||
/// Sets the caret position after interactive mode has finished.
|
||||
/// </summary>
|
||||
public class SnippetCaretElement : SnippetElement |
||||
{ |
||||
/// <inheritdoc/>
|
||||
public override void Insert(InsertionContext context) |
||||
{ |
||||
TextAnchor pos = context.Document.CreateAnchor(context.InsertionPosition); |
||||
pos.SurviveDeletion = true; |
||||
context.Deactivated += delegate { |
||||
context.TextArea.Caret.Offset = pos.Offset; |
||||
}; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ICSharpCode.AvalonEdit.Editing; |
||||
using ICSharpCode.AvalonEdit.Utils; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Snippets |
||||
{ |
||||
/// <summary>
|
||||
/// A snippet element that has sub-elements.
|
||||
/// </summary>
|
||||
[Serializable] |
||||
public class SnippetContainerElement : SnippetElement |
||||
{ |
||||
FreezableNullSafeCollection<SnippetElement> elements = new FreezableNullSafeCollection<SnippetElement>(); |
||||
|
||||
/// <summary>
|
||||
/// Gets the list of child elements.
|
||||
/// </summary>
|
||||
public IList<SnippetElement> Elements { |
||||
get { return elements; } |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void FreezeInternal() |
||||
{ |
||||
elements.Freeze(); |
||||
base.FreezeInternal(); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Insert(InsertionContext context) |
||||
{ |
||||
foreach (SnippetElement e in this.Elements) { |
||||
e.Insert(context); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ICSharpCode.AvalonEdit.Editing; |
||||
using ICSharpCode.AvalonEdit.Utils; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Snippets |
||||
{ |
||||
/// <summary>
|
||||
/// An element inside a snippet.
|
||||
/// </summary>
|
||||
[Serializable] |
||||
public abstract class SnippetElement : IFreezable |
||||
{ |
||||
// TODO: think about removing IFreezable, it may not be required after all
|
||||
|
||||
#region IFreezable infrastructure
|
||||
bool isFrozen; |
||||
|
||||
/// <summary>
|
||||
/// Gets if this instance is frozen. Frozen instances are immutable and thus thread-safe.
|
||||
/// </summary>
|
||||
public bool IsFrozen { |
||||
get { return isFrozen; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Freezes this instance.
|
||||
/// </summary>
|
||||
public void Freeze() |
||||
{ |
||||
if (!isFrozen) { |
||||
FreezeInternal(); |
||||
isFrozen = true; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Override this method to freeze child elements.
|
||||
/// </summary>
|
||||
protected virtual void FreezeInternal() |
||||
{ |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if this instance is frozen.
|
||||
/// </summary>
|
||||
protected void CheckBeforeMutation() |
||||
{ |
||||
if (isFrozen) |
||||
throw new InvalidOperationException("Cannot mutate frozen " + GetType().Name); |
||||
} |
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Performs insertion of the snippet.
|
||||
/// </summary>
|
||||
public abstract void Insert(InsertionContext context); |
||||
} |
||||
} |
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using System.Windows; |
||||
using System.Windows.Threading; |
||||
|
||||
using ICSharpCode.AvalonEdit.Document; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Snippets |
||||
{ |
||||
/// <summary>
|
||||
/// Text element that is supposed to be replaced by the user.
|
||||
/// Will register an <see cref="IReplaceableActiveElement"/>.
|
||||
/// </summary>
|
||||
[Serializable] |
||||
public class SnippetReplaceableTextElement : SnippetTextElement |
||||
{ |
||||
/// <inheritdoc/>
|
||||
public override void Insert(InsertionContext context) |
||||
{ |
||||
int start = context.InsertionPosition; |
||||
base.Insert(context); |
||||
int end = context.InsertionPosition; |
||||
context.RegisterActiveElement(this, new ReplaceableActiveElement(context, start, end)); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Interface for active element registered by <see cref="SnippetReplaceableTextElement"/>.
|
||||
/// </summary>
|
||||
public interface IReplaceableActiveElement : IActiveElement |
||||
{ |
||||
/// <summary>
|
||||
/// Gets the current text inside the element.
|
||||
/// </summary>
|
||||
string Text { get; } |
||||
|
||||
/// <summary>
|
||||
/// Occurs when the text inside the element changes.
|
||||
/// </summary>
|
||||
event EventHandler TextChanged; |
||||
} |
||||
|
||||
sealed class ReplaceableActiveElement : IReplaceableActiveElement, IWeakEventListener |
||||
{ |
||||
readonly InsertionContext context; |
||||
readonly int startOffset, endOffset; |
||||
TextAnchor start, end; |
||||
|
||||
public ReplaceableActiveElement(InsertionContext context, int startOffset, int endOffset) |
||||
{ |
||||
this.context = context; |
||||
this.startOffset = startOffset; |
||||
this.endOffset = endOffset; |
||||
} |
||||
|
||||
void AnchorDeleted(object sender, EventArgs e) |
||||
{ |
||||
context.Deactivate(); |
||||
} |
||||
|
||||
public void OnInsertionCompleted() |
||||
{ |
||||
// anchors must be created in OnInsertionCompleted because they should move only
|
||||
// due to user insertions, not due to insertions of other snippet parts
|
||||
start = context.Document.CreateAnchor(startOffset); |
||||
start.MovementType = AnchorMovementType.BeforeInsertion; |
||||
end = context.Document.CreateAnchor(endOffset); |
||||
end.MovementType = AnchorMovementType.AfterInsertion; |
||||
start.Deleted += AnchorDeleted; |
||||
end.Deleted += AnchorDeleted; |
||||
|
||||
// Be careful with references from the document to the editing/snippet layer - use weak events
|
||||
// to prevent memory leaks when text areas get dropped from the UI while the snippet is active.
|
||||
// The InsertionContext will keep us alive as long as the snippet is in interactive mode.
|
||||
TextDocumentWeakEventManager.TextChanged.AddListener(context.Document, this); |
||||
|
||||
this.Text = GetText(); |
||||
} |
||||
|
||||
public void Deactivate() |
||||
{ |
||||
TextDocumentWeakEventManager.TextChanged.RemoveListener(context.Document, this); |
||||
} |
||||
|
||||
public string Text { get; private set; } |
||||
|
||||
string GetText() |
||||
{ |
||||
if (start.IsDeleted || end.IsDeleted) |
||||
return string.Empty; |
||||
else |
||||
return context.Document.GetText(start.Offset, Math.Max(0, end.Offset - start.Offset)); |
||||
} |
||||
|
||||
public event EventHandler TextChanged; |
||||
|
||||
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e) |
||||
{ |
||||
if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) { |
||||
string newText = GetText(); |
||||
if (this.Text != newText) { |
||||
this.Text = newText; |
||||
if (TextChanged != null) |
||||
TextChanged(this, e); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Snippets |
||||
{ |
||||
/// <summary>
|
||||
/// Represents a text element in a snippet.
|
||||
/// </summary>
|
||||
[Serializable] |
||||
public class SnippetTextElement : SnippetElement |
||||
{ |
||||
string text; |
||||
|
||||
/// <summary>
|
||||
/// The text to be inserted.
|
||||
/// </summary>
|
||||
public string Text { |
||||
get { return text; } |
||||
set { |
||||
CheckBeforeMutation(); |
||||
text = value; |
||||
} |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Insert(InsertionContext context) |
||||
{ |
||||
if (text != null) |
||||
context.InsertText(text); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Daniel Grunwald"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
|
||||
using System; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Utils |
||||
{ |
||||
interface IFreezable |
||||
{ |
||||
/// <summary>
|
||||
/// Gets if this instance is frozen. Frozen instances are immutable and thus thread-safe.
|
||||
/// </summary>
|
||||
bool IsFrozen { get; } |
||||
|
||||
/// <summary>
|
||||
/// Freezes this instance.
|
||||
/// </summary>
|
||||
void Freeze(); |
||||
} |
||||
|
||||
[Serializable] |
||||
sealed class FreezableNullSafeCollection<T> : NullSafeCollection<T>, IFreezable where T : class, IFreezable |
||||
{ |
||||
bool isFrozen; |
||||
|
||||
public bool IsFrozen { get { return isFrozen; } } |
||||
|
||||
public void Freeze() |
||||
{ |
||||
if (!isFrozen) { |
||||
foreach (T item in this) { |
||||
item.Freeze(); |
||||
} |
||||
isFrozen = true; |
||||
} |
||||
} |
||||
|
||||
void CheckBeforeMutation() |
||||
{ |
||||
if (isFrozen) |
||||
throw new InvalidOperationException("Cannot mutate frozen " + GetType().Name); |
||||
} |
||||
|
||||
protected override void ClearItems() |
||||
{ |
||||
this.CheckBeforeMutation(); |
||||
base.ClearItems(); |
||||
} |
||||
|
||||
protected override void InsertItem(int index, T item) |
||||
{ |
||||
this.CheckBeforeMutation(); |
||||
base.InsertItem(index, item); |
||||
} |
||||
|
||||
protected override void RemoveItem(int index) |
||||
{ |
||||
this.CheckBeforeMutation(); |
||||
base.RemoveItem(index); |
||||
} |
||||
|
||||
protected override void SetItem(int index, T item) |
||||
{ |
||||
this.CheckBeforeMutation(); |
||||
base.SetItem(index, item); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue