// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Linq;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
namespace ICSharpCode.AvalonEdit.Snippets
{
///
/// Text element that is supposed to be replaced by the user.
/// Will register an .
///
[Serializable]
public class SnippetReplaceableTextElement : SnippetTextElement
{
///
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));
}
///
public override Inline ToTextRun()
{
return new Italic(base.ToTextRun());
}
}
///
/// Interface for active element registered by .
///
public interface IReplaceableActiveElement : IActiveElement
{
///
/// Gets the current text inside the element.
///
string Text { get; }
///
/// Occurs when the text inside the element changes.
///
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(new SnippetEventArgs(DeactivateReason.Deleted));
}
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 the text area control gets 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);
background = new Renderer { Layer = KnownLayer.Background, element = this };
foreground = new Renderer { Layer = KnownLayer.Text, element = this };
context.TextArea.TextView.BackgroundRenderers.Add(background);
context.TextArea.TextView.BackgroundRenderers.Add(foreground);
context.TextArea.Caret.PositionChanged += Caret_PositionChanged;
Caret_PositionChanged(null, null);
this.Text = GetText();
}
public void Deactivate(SnippetEventArgs e)
{
TextDocumentWeakEventManager.TextChanged.RemoveListener(context.Document, this);
context.TextArea.TextView.BackgroundRenderers.Remove(background);
context.TextArea.TextView.BackgroundRenderers.Remove(foreground);
context.TextArea.Caret.PositionChanged -= Caret_PositionChanged;
}
bool isCaretInside;
void Caret_PositionChanged(object sender, EventArgs e)
{
ISegment s = this.Segment;
if (s != null) {
bool newIsCaretInside = s.Contains(context.TextArea.Caret.Offset, 0);
if (newIsCaretInside != isCaretInside) {
isCaretInside = newIsCaretInside;
context.TextArea.TextView.InvalidateLayer(foreground.Layer);
}
}
}
Renderer background, foreground;
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;
}
public bool IsEditable {
get { return true; }
}
public ISegment Segment {
get {
if (start.IsDeleted || end.IsDeleted)
return null;
else
return new SimpleSegment(start.Offset, Math.Max(0, end.Offset - start.Offset));
}
}
sealed class Renderer : IBackgroundRenderer
{
static readonly Brush backgroundBrush = CreateBackgroundBrush();
static readonly Pen activeBorderPen = CreateBorderPen();
static Brush CreateBackgroundBrush()
{
SolidColorBrush b = new SolidColorBrush(Colors.LimeGreen);
b.Opacity = 0.4;
b.Freeze();
return b;
}
static Pen CreateBorderPen()
{
Pen p = new Pen(Brushes.Black, 1);
p.DashStyle = DashStyles.Dot;
p.Freeze();
return p;
}
internal ReplaceableActiveElement element;
public KnownLayer Layer { get; set; }
public void Draw(TextView textView, System.Windows.Media.DrawingContext drawingContext)
{
ISegment s = element.Segment;
if (s != null) {
BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
geoBuilder.AlignToMiddleOfPixels = true;
if (Layer == KnownLayer.Background) {
geoBuilder.AddSegment(textView, s);
drawingContext.DrawGeometry(backgroundBrush, null, geoBuilder.CreateGeometry());
} else {
// draw foreground only if active
if (element.isCaretInside) {
geoBuilder.AddSegment(textView, s);
foreach (BoundActiveElement boundElement in element.context.ActiveElements.OfType()) {
if (boundElement.targetElement == element) {
geoBuilder.AddSegment(textView, boundElement.Segment);
geoBuilder.CloseFigure();
}
}
drawingContext.DrawGeometry(null, activeBorderPen, geoBuilder.CreateGeometry());
}
}
}
}
}
}
}