/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/TextMarkerService.cs

http://github.com/icsharpcode/SharpDevelop · C# · 368 lines · 297 code · 46 blank · 25 comment · 74 complexity · 7152e793f6d399e0d994d1a7b648b114 MD5 · raw file

  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. using System;
  19. using System.Collections.Generic;
  20. using System.Diagnostics;
  21. using System.Linq;
  22. using System.Windows;
  23. using System.Windows.Media;
  24. using System.Windows.Threading;
  25. using ICSharpCode.AvalonEdit.Document;
  26. using ICSharpCode.AvalonEdit.Rendering;
  27. using ICSharpCode.NRefactory.Editor;
  28. using ICSharpCode.SharpDevelop;
  29. using ICSharpCode.SharpDevelop.Editor;
  30. namespace ICSharpCode.AvalonEdit.AddIn
  31. {
  32. /// <summary>
  33. /// Handles the text markers for a code editor.
  34. /// </summary>
  35. public sealed class TextMarkerService : DocumentColorizingTransformer, IBackgroundRenderer, ITextMarkerService, ITextViewConnect
  36. {
  37. TextSegmentCollection<TextMarker> markers;
  38. TextDocument document;
  39. public TextMarkerService(TextDocument document)
  40. {
  41. if (document == null)
  42. throw new ArgumentNullException("document");
  43. this.document = document;
  44. this.markers = new TextSegmentCollection<TextMarker>(document);
  45. }
  46. #region ITextMarkerService
  47. public ITextMarker Create(int startOffset, int length)
  48. {
  49. if (markers == null)
  50. throw new InvalidOperationException("Cannot create a marker when not attached to a document");
  51. int textLength = document.TextLength;
  52. if (startOffset < 0 || startOffset > textLength)
  53. throw new ArgumentOutOfRangeException("startOffset", startOffset, "Value must be between 0 and " + textLength);
  54. if (length < 0 || startOffset + length > textLength)
  55. throw new ArgumentOutOfRangeException("length", length, "length must not be negative and startOffset+length must not be after the end of the document");
  56. TextMarker m = new TextMarker(this, startOffset, length);
  57. markers.Add(m);
  58. // no need to mark segment for redraw: the text marker is invisible until a property is set
  59. return m;
  60. }
  61. public IEnumerable<ITextMarker> GetMarkersAtOffset(int offset)
  62. {
  63. if (markers == null)
  64. return Enumerable.Empty<ITextMarker>();
  65. else
  66. return markers.FindSegmentsContaining(offset);
  67. }
  68. public IEnumerable<ITextMarker> TextMarkers {
  69. get { return markers ?? Enumerable.Empty<ITextMarker>(); }
  70. }
  71. public void RemoveAll(Predicate<ITextMarker> predicate)
  72. {
  73. if (predicate == null)
  74. throw new ArgumentNullException("predicate");
  75. if (markers != null) {
  76. foreach (TextMarker m in markers.ToArray()) {
  77. if (predicate(m))
  78. Remove(m);
  79. }
  80. }
  81. }
  82. public void Remove(ITextMarker marker)
  83. {
  84. if (marker == null)
  85. throw new ArgumentNullException("marker");
  86. TextMarker m = marker as TextMarker;
  87. if (markers != null && markers.Remove(m)) {
  88. Redraw(m);
  89. m.OnDeleted();
  90. }
  91. }
  92. /// <summary>
  93. /// Redraws the specified text segment.
  94. /// </summary>
  95. internal void Redraw(ISegment segment)
  96. {
  97. foreach (var view in textViews) {
  98. view.Redraw(segment, DispatcherPriority.Normal);
  99. }
  100. if (RedrawRequested != null)
  101. RedrawRequested(this, EventArgs.Empty);
  102. }
  103. public event EventHandler RedrawRequested;
  104. #endregion
  105. #region DocumentColorizingTransformer
  106. protected override void ColorizeLine(DocumentLine line)
  107. {
  108. if (markers == null)
  109. return;
  110. int lineStart = line.Offset;
  111. int lineEnd = lineStart + line.Length;
  112. foreach (TextMarker marker in markers.FindOverlappingSegments(lineStart, line.Length)) {
  113. Brush foregroundBrush = null;
  114. if (marker.ForegroundColor != null) {
  115. foregroundBrush = new SolidColorBrush(marker.ForegroundColor.Value);
  116. foregroundBrush.Freeze();
  117. }
  118. ChangeLinePart(
  119. Math.Max(marker.StartOffset, lineStart),
  120. Math.Min(marker.EndOffset, lineEnd),
  121. element => {
  122. if (foregroundBrush != null) {
  123. element.TextRunProperties.SetForegroundBrush(foregroundBrush);
  124. }
  125. Typeface tf = element.TextRunProperties.Typeface;
  126. element.TextRunProperties.SetTypeface(new Typeface(
  127. tf.FontFamily,
  128. marker.FontStyle ?? tf.Style,
  129. marker.FontWeight ?? tf.Weight,
  130. tf.Stretch
  131. ));
  132. }
  133. );
  134. }
  135. }
  136. #endregion
  137. #region IBackgroundRenderer
  138. public KnownLayer Layer {
  139. get {
  140. // draw behind selection
  141. return KnownLayer.Selection;
  142. }
  143. }
  144. public void Draw(TextView textView, DrawingContext drawingContext)
  145. {
  146. if (textView == null)
  147. throw new ArgumentNullException("textView");
  148. if (drawingContext == null)
  149. throw new ArgumentNullException("drawingContext");
  150. if (markers == null || !textView.VisualLinesValid)
  151. return;
  152. var visualLines = textView.VisualLines;
  153. if (visualLines.Count == 0)
  154. return;
  155. int viewStart = visualLines.First().FirstDocumentLine.Offset;
  156. int viewEnd = visualLines.Last().LastDocumentLine.EndOffset;
  157. foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart)) {
  158. if (marker.BackgroundColor != null) {
  159. BackgroundGeometryBuilder geoBuilder = new BackgroundGeometryBuilder();
  160. geoBuilder.AlignToWholePixels = true;
  161. geoBuilder.CornerRadius = 3;
  162. geoBuilder.AddSegment(textView, marker);
  163. Geometry geometry = geoBuilder.CreateGeometry();
  164. if (geometry != null) {
  165. Color color = marker.BackgroundColor.Value;
  166. SolidColorBrush brush = new SolidColorBrush(color);
  167. brush.Freeze();
  168. drawingContext.DrawGeometry(brush, null, geometry);
  169. }
  170. }
  171. var underlineMarkerTypes = TextMarkerTypes.SquigglyUnderline | TextMarkerTypes.NormalUnderline | TextMarkerTypes.DottedUnderline;
  172. if ((marker.MarkerTypes & underlineMarkerTypes) != 0) {
  173. foreach (Rect r in BackgroundGeometryBuilder.GetRectsForSegment(textView, marker)) {
  174. Point startPoint = r.BottomLeft;
  175. Point endPoint = r.BottomRight;
  176. Brush usedBrush = new SolidColorBrush(marker.MarkerColor);
  177. usedBrush.Freeze();
  178. if ((marker.MarkerTypes & TextMarkerTypes.SquigglyUnderline) != 0) {
  179. double offset = 2.5;
  180. int count = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4);
  181. StreamGeometry geometry = new StreamGeometry();
  182. using (StreamGeometryContext ctx = geometry.Open()) {
  183. ctx.BeginFigure(startPoint, false, false);
  184. ctx.PolyLineTo(CreatePoints(startPoint, endPoint, offset, count).ToArray(), true, false);
  185. }
  186. geometry.Freeze();
  187. Pen usedPen = new Pen(usedBrush, 1);
  188. usedPen.Freeze();
  189. drawingContext.DrawGeometry(Brushes.Transparent, usedPen, geometry);
  190. }
  191. if ((marker.MarkerTypes & TextMarkerTypes.NormalUnderline) != 0) {
  192. Pen usedPen = new Pen(usedBrush, 1);
  193. usedPen.Freeze();
  194. drawingContext.DrawLine(usedPen, startPoint, endPoint);
  195. }
  196. if ((marker.MarkerTypes & TextMarkerTypes.DottedUnderline) != 0) {
  197. Pen usedPen = new Pen(usedBrush, 1);
  198. usedPen.DashStyle = DashStyles.Dot;
  199. usedPen.Freeze();
  200. drawingContext.DrawLine(usedPen, startPoint, endPoint);
  201. }
  202. }
  203. }
  204. }
  205. }
  206. IEnumerable<Point> CreatePoints(Point start, Point end, double offset, int count)
  207. {
  208. for (int i = 0; i < count; i++)
  209. yield return new Point(start.X + i * offset, start.Y - ((i + 1) % 2 == 0 ? offset : 0));
  210. }
  211. #endregion
  212. #region ITextViewConnect
  213. readonly List<TextView> textViews = new List<TextView>();
  214. void ITextViewConnect.AddToTextView(TextView textView)
  215. {
  216. if (textView != null && !textViews.Contains(textView)) {
  217. Debug.Assert(textView.Document == document);
  218. textViews.Add(textView);
  219. }
  220. }
  221. void ITextViewConnect.RemoveFromTextView(TextView textView)
  222. {
  223. if (textView != null) {
  224. Debug.Assert(textView.Document == document);
  225. textViews.Remove(textView);
  226. }
  227. }
  228. #endregion
  229. }
  230. public sealed class TextMarker : TextSegment, ITextMarker
  231. {
  232. readonly TextMarkerService service;
  233. public TextMarker(TextMarkerService service, int startOffset, int length)
  234. {
  235. if (service == null)
  236. throw new ArgumentNullException("service");
  237. this.service = service;
  238. this.StartOffset = startOffset;
  239. this.Length = length;
  240. this.markerTypes = TextMarkerTypes.None;
  241. }
  242. public event EventHandler Deleted;
  243. public bool IsDeleted {
  244. get { return !this.IsConnectedToCollection; }
  245. }
  246. public void Delete()
  247. {
  248. service.Remove(this);
  249. }
  250. internal void OnDeleted()
  251. {
  252. if (Deleted != null)
  253. Deleted(this, EventArgs.Empty);
  254. }
  255. void Redraw()
  256. {
  257. service.Redraw(this);
  258. }
  259. Color? backgroundColor;
  260. public Color? BackgroundColor {
  261. get { return backgroundColor; }
  262. set {
  263. if (backgroundColor != value) {
  264. backgroundColor = value;
  265. Redraw();
  266. }
  267. }
  268. }
  269. Color? foregroundColor;
  270. public Color? ForegroundColor {
  271. get { return foregroundColor; }
  272. set {
  273. if (foregroundColor != value) {
  274. foregroundColor = value;
  275. Redraw();
  276. }
  277. }
  278. }
  279. FontWeight? fontWeight;
  280. public FontWeight? FontWeight {
  281. get { return fontWeight; }
  282. set {
  283. if (fontWeight != value) {
  284. fontWeight = value;
  285. Redraw();
  286. }
  287. }
  288. }
  289. FontStyle? fontStyle;
  290. public FontStyle? FontStyle {
  291. get { return fontStyle; }
  292. set {
  293. if (fontStyle != value) {
  294. fontStyle = value;
  295. Redraw();
  296. }
  297. }
  298. }
  299. public object Tag { get; set; }
  300. TextMarkerTypes markerTypes;
  301. public TextMarkerTypes MarkerTypes {
  302. get { return markerTypes; }
  303. set {
  304. if (markerTypes != value) {
  305. markerTypes = value;
  306. Redraw();
  307. }
  308. }
  309. }
  310. Color markerColor;
  311. public Color MarkerColor {
  312. get { return markerColor; }
  313. set {
  314. if (markerColor != value) {
  315. markerColor = value;
  316. Redraw();
  317. }
  318. }
  319. }
  320. public object ToolTip { get; set; }
  321. }
  322. }