aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
commit080f1697e97e13461ec6df4d31c8924d01257a1b (patch)
treeb1fe0285de7bc9bc52e9e2195e66fe022bf8f5b3 /Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs
parent1608e69a417bc5e40a607c3958c4a60f19f66f1a (diff)
downloadTango-080f1697e97e13461ec6df4d31c8924d01257a1b.tar.gz
Tango-080f1697e97e13461ec6df4d31c8924d01257a1b.zip
MERGE
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs213
1 files changed, 213 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs
new file mode 100644
index 000000000..9bf872f11
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Snippets/SnippetReplaceableTextElement.cs
@@ -0,0 +1,213 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media;
+using System.Windows.Threading;
+
+using Tango.Scripting.Editors.Document;
+using Tango.Scripting.Editors.Rendering;
+
+namespace Tango.Scripting.Editors.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));
+ }
+
+ /// <inheritdoc/>
+ public override Inline ToTextRun()
+ {
+ return new Italic(base.ToTextRun());
+ }
+ }
+
+ /// <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(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);
+ 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<BoundActiveElement>()) {
+ if (boundElement.targetElement == element) {
+ geoBuilder.AddSegment(textView, boundElement.Segment);
+ geoBuilder.CloseFigure();
+ }
+ }
+ drawingContext.DrawGeometry(null, activeBorderPen, geoBuilder.CreateGeometry());
+ }
+ }
+ }
+ }
+ }
+ }
+}