Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.CodeEditor/3.4/TextMarkerService.cs @ 16724

Last change on this file since 16724 was 16565, checked in by gkronber, 6 years ago

#2520: merged changes from PersistenceOverhaul branch (r16451:16564) into trunk

File size: 12.5 KB
Line 
1#region License Information
2// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5// software and associated documentation files (the "Software"), to deal in the Software
6// without restriction, including without limitation the rights to use, copy, modify, merge,
7// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8// to whom the Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all copies or
11// substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18// DEALINGS IN THE SOFTWARE.
19
20/* HeuristicLab
21 * Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
22 *
23 * This file is part of HeuristicLab.
24 *
25 * HeuristicLab is free software: you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation, either version 3 of the License, or
28 * (at your option) any later version.
29 *
30 * HeuristicLab is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
33 * GNU General Public License for more details.
34 *
35 * You should have received a copy of the GNU General Public License
36 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
37 */
38#endregion
39
40using System;
41using System.Collections.Generic;
42using System.Diagnostics;
43using System.Linq;
44using System.Windows;
45using System.Windows.Media;
46using System.Windows.Threading;
47using ICSharpCode.AvalonEdit.Document;
48using ICSharpCode.AvalonEdit.Rendering;
49using ICSharpCode.NRefactory.Editor;
50using ICSharpCode.SharpDevelop.Editor;
51
52namespace ICSharpCode.AvalonEdit.AddIn {
53  /// <summary>
54  /// Handles the text markers for a code editor.
55  /// </summary>
56  public sealed class TextMarkerService : DocumentColorizingTransformer, IBackgroundRenderer, ITextMarkerService, ITextViewConnect {
57    TextSegmentCollection<TextMarker> markers;
58    TextDocument document;
59
60    public TextMarkerService(TextDocument document) {
61      if (document == null)
62        throw new ArgumentNullException("document");
63      this.document = document;
64      this.markers = new TextSegmentCollection<TextMarker>(document);
65    }
66
67    #region ITextMarkerService
68    public ITextMarker Create(int startOffset, int length) {
69      if (markers == null)
70        throw new InvalidOperationException("Cannot create a marker when not attached to a document");
71
72      int textLength = document.TextLength;
73      if (startOffset < 0 || startOffset > textLength)
74        throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between 0 and " + textLength);
75      if (length < 0 || startOffset + length > textLength)
76        throw new ArgumentOutOfRangeException("length", length, "length must not be negative and startOffset+length must not be after the end of the document");
77
78      TextMarker m = new TextMarker(this, startOffset, length);
79      markers.Add(m);
80      // no need to mark segment for redraw: the text marker is invisible until a property is set
81      return m;
82    }
83
84    public IEnumerable<ITextMarker> GetMarkersAtOffset(int offset) {
85      if (markers == null)
86        return Enumerable.Empty<ITextMarker>();
87      else
88        return markers.FindSegmentsContaining(offset);
89    }
90
91    public IEnumerable<ITextMarker> TextMarkers {
92      get { return markers ?? Enumerable.Empty<ITextMarker>(); }
93    }
94
95    public void RemoveAll(Predicate<ITextMarker> predicate) {
96      if (predicate == null)
97        throw new ArgumentNullException("predicate");
98      if (markers != null) {
99        foreach (TextMarker m in markers.ToArray()) {
100          if (predicate(m))
101            Remove(m);
102        }
103      }
104    }
105
106    public void Remove(ITextMarker marker) {
107      if (marker == null)
108        throw new ArgumentNullException("marker");
109      TextMarker m = marker as TextMarker;
110      if (markers != null && markers.Remove(m)) {
111        Redraw(m);
112        m.OnDeleted();
113      }
114    }
115
116    /// <summary>
117    /// Redraws the specified text segment.
118    /// </summary>
119    internal void Redraw(ISegment segment) {
120      foreach (var view in textViews) {
121        view.Redraw(segment, DispatcherPriority.Normal);
122      }
123      if (RedrawRequested != null)
124        RedrawRequested(this, EventArgs.Empty);
125    }
126
127    public event EventHandler RedrawRequested;
128    #endregion
129
130    #region DocumentColorizingTransformer
131    protected override void ColorizeLine(DocumentLine line) {
132      if (markers == null)
133        return;
134      int lineStart = line.Offset;
135      int lineEnd = lineStart + line.Length;
136      foreach (TextMarker marker in markers.FindOverlappingSegments(lineStart, line.Length)) {
137        Brush foregroundBrush = null;
138        if (marker.ForegroundColor != null) {
139          foregroundBrush = new SolidColorBrush(marker.ForegroundColor.Value);
140          foregroundBrush.Freeze();
141        }
142        ChangeLinePart(
143          Math.Max(marker.StartOffset, lineStart),
144          Math.Min(marker.EndOffset, lineEnd),
145          element => {
146            if (foregroundBrush != null) {
147              element.TextRunProperties.SetForegroundBrush(foregroundBrush);
148            }
149            Typeface tf = element.TextRunProperties.Typeface;
150            element.TextRunProperties.SetTypeface(new Typeface(
151              tf.FontFamily,
152              marker.FontStyle ?? tf.Style,
153              marker.FontWeight ?? tf.Weight,
154              tf.Stretch
155            ));
156          }
157        );
158      }
159    }
160    #endregion
161
162    #region IBackgroundRenderer
163    public KnownLayer Layer {
164      get {
165        // draw behind selection
166        return KnownLayer.Selection;
167      }
168    }
169
170    public void Draw(TextView textView, DrawingContext drawingContext) {
171      if (textView == null)
172        throw new ArgumentNullException("textView");
173      if (drawingContext == null)
174        throw new ArgumentNullException("drawingContext");
175      if (markers == null || !textView.VisualLinesValid)
176        return;
177      var visualLines = textView.VisualLines;
178      if (visualLines.Count == 0)
179        return;
180      int viewStart = visualLines.First().FirstDocumentLine.Offset;
181      int viewEnd = visualLines.Last().LastDocumentLine.EndOffset;
182      foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) {
183        if (marker.BackgroundColor != null) {
184          BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
185          geoBuilder.AlignToWholePixels = true;
186          geoBuilder.CornerRadius = 3;
187          geoBuilder.AddSegment(textView, marker);
188          Geometry geometry = geoBuilder.CreateGeometry();
189          if (geometry != null) {
190            Color color = marker.BackgroundColor.Value;
191            SolidColorBrush brush = new SolidColorBrush(color);
192            brush.Freeze();
193            drawingContext.DrawGeometry(brush, null, geometry);
194          }
195        }
196        var underlineMarkerTypes = TextMarkerTypes.SquigglyUnderline | TextMarkerTypes.NormalUnderline | TextMarkerTypes.DottedUnderline;
197        if ((marker.MarkerTypes & underlineMarkerTypes) != 0) {
198          foreach (Rect r in BackgroundGeometryBuilder.GetRectsForSegment(textView, marker)) {
199            Point startPoint = r.BottomLeft;
200            Point endPoint = r.BottomRight;
201
202            Brush usedBrush = new SolidColorBrush(marker.MarkerColor);
203            usedBrush.Freeze();
204            if ((marker.MarkerTypes & TextMarkerTypes.SquigglyUnderline) != 0) {
205              double offset = 2.5;
206
207              int count = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4);
208
209              StreamGeometry geometry = new StreamGeometry();
210
211              using (StreamGeometryContext ctx = geometry.Open()) {
212                ctx.BeginFigure(startPoint, false, false);
213                ctx.PolyLineTo(CreatePoints(startPoint, endPoint, offset, count).ToArray(), true, false);
214              }
215
216              geometry.Freeze();
217
218              Pen usedPen = new Pen(usedBrush, 1);
219              usedPen.Freeze();
220              drawingContext.DrawGeometry(Brushes.Transparent, usedPen, geometry);
221            }
222            if ((marker.MarkerTypes & TextMarkerTypes.NormalUnderline) != 0) {
223              Pen usedPen = new Pen(usedBrush, 1);
224              usedPen.Freeze();
225              drawingContext.DrawLine(usedPen, startPoint, endPoint);
226            }
227            if ((marker.MarkerTypes & TextMarkerTypes.DottedUnderline) != 0) {
228              Pen usedPen = new Pen(usedBrush, 1);
229              usedPen.DashStyle = DashStyles.Dot;
230              usedPen.Freeze();
231              drawingContext.DrawLine(usedPen, startPoint, endPoint);
232            }
233          }
234        }
235      }
236    }
237
238    IEnumerable<Point> CreatePoints(Point start, Point end, double offset, int count) {
239      for (int i = 0; i < count; i++)
240        yield return new Point(start.X + i * offset, start.Y - ((i + 1) % 2 == 0 ? offset : 0));
241    }
242    #endregion
243
244    #region ITextViewConnect
245    readonly List<TextView> textViews = new List<TextView>();
246
247    void ITextViewConnect.AddToTextView(TextView textView) {
248      if (textView != null && !textViews.Contains(textView)) {
249        Debug.Assert(textView.Document == document);
250        textViews.Add(textView);
251      }
252    }
253
254    void ITextViewConnect.RemoveFromTextView(TextView textView) {
255      if (textView != null) {
256        Debug.Assert(textView.Document == document);
257        textViews.Remove(textView);
258      }
259    }
260    #endregion
261  }
262
263  public sealed class TextMarker : TextSegment, ITextMarker {
264    readonly TextMarkerService service;
265
266    public TextMarker(TextMarkerService service, int startOffset, int length) {
267      if (service == null)
268        throw new ArgumentNullException("service");
269      this.service = service;
270      this.StartOffset = startOffset;
271      this.Length = length;
272      this.markerTypes = TextMarkerTypes.None;
273    }
274
275    public event EventHandler Deleted;
276
277    public bool IsDeleted {
278      get { return !this.IsConnectedToCollection; }
279    }
280
281    public void Delete() {
282      service.Remove(this);
283    }
284
285    internal void OnDeleted() {
286      if (Deleted != null)
287        Deleted(this, EventArgs.Empty);
288    }
289
290    void Redraw() {
291      service.Redraw(this);
292    }
293
294    Color? backgroundColor;
295
296    public Color? BackgroundColor {
297      get { return backgroundColor; }
298      set {
299        if (backgroundColor != value) {
300          backgroundColor = value;
301          Redraw();
302        }
303      }
304    }
305
306    Color? foregroundColor;
307
308    public Color? ForegroundColor {
309      get { return foregroundColor; }
310      set {
311        if (foregroundColor != value) {
312          foregroundColor = value;
313          Redraw();
314        }
315      }
316    }
317
318    FontWeight? fontWeight;
319
320    public FontWeight? FontWeight {
321      get { return fontWeight; }
322      set {
323        if (fontWeight != value) {
324          fontWeight = value;
325          Redraw();
326        }
327      }
328    }
329
330    FontStyle? fontStyle;
331
332    public FontStyle? FontStyle {
333      get { return fontStyle; }
334      set {
335        if (fontStyle != value) {
336          fontStyle = value;
337          Redraw();
338        }
339      }
340    }
341
342    public object Tag { get; set; }
343
344    TextMarkerTypes markerTypes;
345
346    public TextMarkerTypes MarkerTypes {
347      get { return markerTypes; }
348      set {
349        if (markerTypes != value) {
350          markerTypes = value;
351          Redraw();
352        }
353      }
354    }
355
356    Color markerColor;
357
358    public Color MarkerColor {
359      get { return markerColor; }
360      set {
361        if (markerColor != value) {
362          markerColor = value;
363          Redraw();
364        }
365      }
366    }
367
368    public object ToolTip { get; set; }
369  }
370}
Note: See TracBrowser for help on using the repository browser.