Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Snippets/SnippetReplaceableTextElement.cs @ 14243

Last change on this file since 14243 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 7.4 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Linq;
21using System.Windows;
22using System.Windows.Documents;
23using System.Windows.Media;
24using ICSharpCode.NRefactory.Editor;
25using ICSharpCode.AvalonEdit.Document;
26using ICSharpCode.AvalonEdit.Rendering;
27
28namespace ICSharpCode.AvalonEdit.Snippets
29{
30  /// <summary>
31  /// Text element that is supposed to be replaced by the user.
32  /// Will register an <see cref="IReplaceableActiveElement"/>.
33  /// </summary>
34  [Serializable]
35  public class SnippetReplaceableTextElement : SnippetTextElement
36  {
37    /// <inheritdoc/>
38    public override void Insert(InsertionContext context)
39    {
40      int start = context.InsertionPosition;
41      base.Insert(context);
42      int end = context.InsertionPosition;
43      context.RegisterActiveElement(this, new ReplaceableActiveElement(context, start, end));
44    }
45   
46    /// <inheritdoc/>
47    public override Inline ToTextRun()
48    {
49      return new Italic(base.ToTextRun());
50    }
51  }
52 
53  /// <summary>
54  /// Interface for active element registered by <see cref="SnippetReplaceableTextElement"/>.
55  /// </summary>
56  public interface IReplaceableActiveElement : IActiveElement
57  {
58    /// <summary>
59    /// Gets the current text inside the element.
60    /// </summary>
61    string Text { get; }
62   
63    /// <summary>
64    /// Occurs when the text inside the element changes.
65    /// </summary>
66    event EventHandler TextChanged;
67  }
68 
69  sealed class ReplaceableActiveElement : IReplaceableActiveElement, IWeakEventListener
70  {
71    readonly InsertionContext context;
72    readonly int startOffset, endOffset;
73    TextAnchor start, end;
74   
75    public ReplaceableActiveElement(InsertionContext context, int startOffset, int endOffset)
76    {
77      this.context = context;
78      this.startOffset = startOffset;
79      this.endOffset = endOffset;
80    }
81   
82    void AnchorDeleted(object sender, EventArgs e)
83    {
84      context.Deactivate(new SnippetEventArgs(DeactivateReason.Deleted));
85    }
86   
87    public void OnInsertionCompleted()
88    {
89      // anchors must be created in OnInsertionCompleted because they should move only
90      // due to user insertions, not due to insertions of other snippet parts
91      start = context.Document.CreateAnchor(startOffset);
92      start.MovementType = AnchorMovementType.BeforeInsertion;
93      end = context.Document.CreateAnchor(endOffset);
94      end.MovementType = AnchorMovementType.AfterInsertion;
95      start.Deleted += AnchorDeleted;
96      end.Deleted += AnchorDeleted;
97     
98      // Be careful with references from the document to the editing/snippet layer - use weak events
99      // to prevent memory leaks when the text area control gets dropped from the UI while the snippet is active.
100      // The InsertionContext will keep us alive as long as the snippet is in interactive mode.
101      TextDocumentWeakEventManager.TextChanged.AddListener(context.Document, this);
102     
103      background = new Renderer { Layer = KnownLayer.Background, element = this };
104      foreground = new Renderer { Layer = KnownLayer.Text, element = this };
105      context.TextArea.TextView.BackgroundRenderers.Add(background);
106      context.TextArea.TextView.BackgroundRenderers.Add(foreground);
107      context.TextArea.Caret.PositionChanged += Caret_PositionChanged;
108      Caret_PositionChanged(null, null);
109     
110      this.Text = GetText();
111    }
112
113    public void Deactivate(SnippetEventArgs e)
114    {
115      TextDocumentWeakEventManager.TextChanged.RemoveListener(context.Document, this);
116      context.TextArea.TextView.BackgroundRenderers.Remove(background);
117      context.TextArea.TextView.BackgroundRenderers.Remove(foreground);
118      context.TextArea.Caret.PositionChanged -= Caret_PositionChanged;
119    }
120   
121    bool isCaretInside;
122   
123    void Caret_PositionChanged(object sender, EventArgs e)
124    {
125      ISegment s = this.Segment;
126      if (s != null) {
127        bool newIsCaretInside = s.Contains(context.TextArea.Caret.Offset, 0);
128        if (newIsCaretInside != isCaretInside) {
129          isCaretInside = newIsCaretInside;
130          context.TextArea.TextView.InvalidateLayer(foreground.Layer);
131        }
132      }
133    }
134   
135    Renderer background, foreground;
136   
137    public string Text { get; private set; }
138   
139    string GetText()
140    {
141      if (start.IsDeleted || end.IsDeleted)
142        return string.Empty;
143      else
144        return context.Document.GetText(start.Offset, Math.Max(0, end.Offset - start.Offset));
145    }
146   
147    public event EventHandler TextChanged;
148   
149    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
150    {
151      if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) {
152        string newText = GetText();
153        if (this.Text != newText) {
154          this.Text = newText;
155          if (TextChanged != null)
156            TextChanged(this, e);
157        }
158        return true;
159      }
160      return false;
161    }
162   
163    public bool IsEditable {
164      get { return true; }
165    }
166   
167    public ISegment Segment {
168      get {
169        if (start.IsDeleted || end.IsDeleted)
170          return null;
171        else
172          return new SimpleSegment(start.Offset, Math.Max(0, end.Offset - start.Offset));
173      }
174    }
175   
176    sealed class Renderer : IBackgroundRenderer
177    {
178      static readonly Brush backgroundBrush = CreateBackgroundBrush();
179      static readonly Pen activeBorderPen = CreateBorderPen();
180     
181      static Brush CreateBackgroundBrush()
182      {
183        SolidColorBrush b = new SolidColorBrush(Colors.LimeGreen);
184        b.Opacity = 0.4;
185        b.Freeze();
186        return b;
187      }
188     
189      static Pen CreateBorderPen()
190      {
191        Pen p = new Pen(Brushes.Black, 1);
192        p.DashStyle = DashStyles.Dot;
193        p.Freeze();
194        return p;
195      }
196     
197      internal ReplaceableActiveElement element;
198     
199      public KnownLayer Layer { get; set; }
200     
201      public void Draw(TextView textView, System.Windows.Media.DrawingContext drawingContext)
202      {
203        ISegment s = element.Segment;
204        if (s != null) {
205          BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
206          geoBuilder.AlignToMiddleOfPixels = true;
207          if (Layer == KnownLayer.Background) {
208            geoBuilder.AddSegment(textView, s);
209            drawingContext.DrawGeometry(backgroundBrush, null, geoBuilder.CreateGeometry());
210          } else {
211            // draw foreground only if active
212            if (element.isCaretInside) {
213              geoBuilder.AddSegment(textView, s);
214              foreach (BoundActiveElement boundElement in element.context.ActiveElements.OfType<BoundActiveElement>()) {
215                if (boundElement.targetElement == element) {
216                  geoBuilder.AddSegment(textView, boundElement.Segment);
217                  geoBuilder.CloseFigure();
218                }
219              }
220              drawingContext.DrawGeometry(null, activeBorderPen, geoBuilder.CreateGeometry());
221            }
222          }
223        }
224      }
225    }
226  }
227}
Note: See TracBrowser for help on using the repository browser.