Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Folding/FoldingMargin.cs @ 12850

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

#2077: created branch and added first version

File size: 13.6 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.Collections.Generic;
21using System.Diagnostics;
22using System.Linq;
23using System.Windows;
24using System.Windows.Controls;
25using System.Windows.Media;
26using System.Windows.Media.TextFormatting;
27using ICSharpCode.AvalonEdit.Editing;
28using ICSharpCode.AvalonEdit.Rendering;
29using ICSharpCode.AvalonEdit.Utils;
30
31namespace ICSharpCode.AvalonEdit.Folding
32{
33  /// <summary>
34  /// A margin that shows markers for foldings and allows to expand/collapse the foldings.
35  /// </summary>
36  public class FoldingMargin : AbstractMargin
37  {
38    /// <summary>
39    /// Gets/Sets the folding manager from which the foldings should be shown.
40    /// </summary>
41    public FoldingManager FoldingManager { get; set; }
42   
43    internal const double SizeFactor = Constants.PixelPerPoint;
44   
45    #region Brushes
46    /// <summary>
47    /// FoldingMarkerBrush dependency property.
48    /// </summary>
49    public static readonly DependencyProperty FoldingMarkerBrushProperty =
50      DependencyProperty.RegisterAttached("FoldingMarkerBrush", typeof(Brush), typeof(FoldingMargin),
51                                          new FrameworkPropertyMetadata(Brushes.Gray, FrameworkPropertyMetadataOptions.Inherits, OnUpdateBrushes));
52   
53    /// <summary>
54    /// Gets/sets the Brush used for displaying the lines of folding markers.
55    /// </summary>
56    public Brush FoldingMarkerBrush {
57      get { return (Brush)GetValue(FoldingMarkerBrushProperty); }
58      set { SetValue(FoldingMarkerBrushProperty, value); }
59    }
60   
61    /// <summary>
62    /// FoldingMarkerBackgroundBrush dependency property.
63    /// </summary>
64    public static readonly DependencyProperty FoldingMarkerBackgroundBrushProperty =
65      DependencyProperty.RegisterAttached("FoldingMarkerBackgroundBrush", typeof(Brush), typeof(FoldingMargin),
66                                          new FrameworkPropertyMetadata(Brushes.White, FrameworkPropertyMetadataOptions.Inherits, OnUpdateBrushes));
67   
68    /// <summary>
69    /// Gets/sets the Brush used for displaying the background of folding markers.
70    /// </summary>
71    public Brush FoldingMarkerBackgroundBrush {
72      get { return (Brush)GetValue(FoldingMarkerBackgroundBrushProperty); }
73      set { SetValue(FoldingMarkerBackgroundBrushProperty, value); }
74    }
75   
76    /// <summary>
77    /// SelectedFoldingMarkerBrush dependency property.
78    /// </summary>
79    public static readonly DependencyProperty SelectedFoldingMarkerBrushProperty =
80      DependencyProperty.RegisterAttached("SelectedFoldingMarkerBrush",
81                                          typeof(Brush), typeof(FoldingMargin),
82                                          new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.Inherits, OnUpdateBrushes));
83   
84    /// <summary>
85    /// Gets/sets the Brush used for displaying the lines of selected folding markers.
86    /// </summary>
87    public Brush SelectedFoldingMarkerBrush {
88      get { return (Brush)GetValue(SelectedFoldingMarkerBrushProperty); }
89      set { SetValue(SelectedFoldingMarkerBrushProperty, value); }
90    }
91   
92    /// <summary>
93    /// SelectedFoldingMarkerBackgroundBrush dependency property.
94    /// </summary>
95    public static readonly DependencyProperty SelectedFoldingMarkerBackgroundBrushProperty =
96      DependencyProperty.RegisterAttached("SelectedFoldingMarkerBackgroundBrush",
97                                          typeof(Brush), typeof(FoldingMargin),
98                                          new FrameworkPropertyMetadata(Brushes.White, FrameworkPropertyMetadataOptions.Inherits, OnUpdateBrushes));
99   
100    /// <summary>
101    /// Gets/sets the Brush used for displaying the background of selected folding markers.
102    /// </summary>
103    public Brush SelectedFoldingMarkerBackgroundBrush {
104      get { return (Brush)GetValue(SelectedFoldingMarkerBackgroundBrushProperty); }
105      set { SetValue(SelectedFoldingMarkerBackgroundBrushProperty, value); }
106    }
107   
108    static void OnUpdateBrushes(DependencyObject d, DependencyPropertyChangedEventArgs e)
109    {
110      FoldingMargin m = null;
111      if (d is FoldingMargin)
112        m = (FoldingMargin)d;
113      else if (d is TextEditor)
114        m = ((TextEditor)d).TextArea.LeftMargins.FirstOrDefault(c => c is FoldingMargin) as FoldingMargin;
115      if (m == null) return;
116      if (e.Property.Name == FoldingMarkerBrushProperty.Name)
117        m.foldingControlPen = MakeFrozenPen((Brush)e.NewValue);
118      if (e.Property.Name == SelectedFoldingMarkerBrushProperty.Name)
119        m.selectedFoldingControlPen = MakeFrozenPen((Brush)e.NewValue);
120    }
121    #endregion
122   
123    /// <inheritdoc/>
124    protected override Size MeasureOverride(Size availableSize)
125    {
126      foreach (FoldingMarginMarker m in markers) {
127        m.Measure(availableSize);
128      }
129      double width = SizeFactor * (double)GetValue(TextBlock.FontSizeProperty);
130      return new Size(PixelSnapHelpers.RoundToOdd(width, PixelSnapHelpers.GetPixelSize(this).Width), 0);
131    }
132   
133    /// <inheritdoc/>
134    protected override Size ArrangeOverride(Size finalSize)
135    {
136      Size pixelSize = PixelSnapHelpers.GetPixelSize(this);
137      foreach (FoldingMarginMarker m in markers) {
138        int visualColumn = m.VisualLine.GetVisualColumn(m.FoldingSection.StartOffset - m.VisualLine.FirstDocumentLine.Offset);
139        TextLine textLine = m.VisualLine.GetTextLine(visualColumn);
140        double yPos = m.VisualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextMiddle) - TextView.VerticalOffset;
141        yPos -= m.DesiredSize.Height / 2;
142        double xPos = (finalSize.Width - m.DesiredSize.Width) / 2;
143        m.Arrange(new Rect(PixelSnapHelpers.Round(new Point(xPos, yPos), pixelSize), m.DesiredSize));
144      }
145      return base.ArrangeOverride(finalSize);
146    }
147   
148    /// <inheritdoc/>
149    protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView)
150    {
151      if (oldTextView != null) {
152        oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged;
153      }
154      base.OnTextViewChanged(oldTextView, newTextView);
155      if (newTextView != null) {
156        newTextView.VisualLinesChanged += TextViewVisualLinesChanged;
157      }
158      TextViewVisualLinesChanged(null, null);
159    }
160   
161    List<FoldingMarginMarker> markers = new List<FoldingMarginMarker>();
162   
163    void TextViewVisualLinesChanged(object sender, EventArgs e)
164    {
165      foreach (FoldingMarginMarker m in markers) {
166        RemoveVisualChild(m);
167      }
168      markers.Clear();
169      InvalidateVisual();
170      if (TextView != null && FoldingManager != null && TextView.VisualLinesValid) {
171        foreach (VisualLine line in TextView.VisualLines) {
172          FoldingSection fs = FoldingManager.GetNextFolding(line.FirstDocumentLine.Offset);
173          if (fs == null)
174            continue;
175          if (fs.StartOffset <= line.LastDocumentLine.Offset + line.LastDocumentLine.Length) {
176            FoldingMarginMarker m = new FoldingMarginMarker {
177              IsExpanded = !fs.IsFolded,
178              VisualLine = line,
179              FoldingSection = fs
180            };
181           
182            markers.Add(m);
183            AddVisualChild(m);
184           
185            m.IsMouseDirectlyOverChanged += delegate { InvalidateVisual(); };
186           
187            InvalidateMeasure();
188            continue;
189          }
190        }
191      }
192    }
193   
194    /// <inheritdoc/>
195    protected override int VisualChildrenCount {
196      get { return markers.Count; }
197    }
198   
199    /// <inheritdoc/>
200    protected override Visual GetVisualChild(int index)
201    {
202      return markers[index];
203    }
204   
205    Pen foldingControlPen = MakeFrozenPen((Brush)FoldingMarkerBrushProperty.DefaultMetadata.DefaultValue);
206    Pen selectedFoldingControlPen = MakeFrozenPen((Brush)SelectedFoldingMarkerBrushProperty.DefaultMetadata.DefaultValue);
207   
208    static Pen MakeFrozenPen(Brush brush)
209    {
210      Pen pen = new Pen(brush, 1);
211      pen.Freeze();
212      return pen;
213    }
214   
215    /// <inheritdoc/>
216    protected override void OnRender(DrawingContext drawingContext)
217    {
218      if (TextView == null || !TextView.VisualLinesValid)
219        return;
220      if (TextView.VisualLines.Count == 0 || FoldingManager == null)
221        return;
222     
223      var allTextLines = TextView.VisualLines.SelectMany(vl => vl.TextLines).ToList();
224      Pen[] colors = new Pen[allTextLines.Count + 1];
225      Pen[] endMarker = new Pen[allTextLines.Count];
226     
227      CalculateFoldLinesForFoldingsActiveAtStart(allTextLines, colors, endMarker);
228      CalculateFoldLinesForMarkers(allTextLines, colors, endMarker);
229      DrawFoldLines(drawingContext, colors, endMarker);
230     
231      base.OnRender(drawingContext);
232    }
233
234    /// <summary>
235    /// Calculates fold lines for all folding sections that start in front of the current view
236    /// and run into the current view.
237    /// </summary>
238    void CalculateFoldLinesForFoldingsActiveAtStart(List<TextLine> allTextLines, Pen[] colors, Pen[] endMarker)
239    {
240      int viewStartOffset = TextView.VisualLines[0].FirstDocumentLine.Offset;
241      int viewEndOffset = TextView.VisualLines.Last().LastDocumentLine.EndOffset;
242      var foldings = FoldingManager.GetFoldingsContaining(viewStartOffset);
243      int maxEndOffset = 0;
244      foreach (FoldingSection fs in foldings) {
245        int end = fs.EndOffset;
246        if (end <= viewEndOffset && !fs.IsFolded) {
247          int textLineNr = GetTextLineIndexFromOffset(allTextLines, end);
248          if (textLineNr >= 0) {
249            endMarker[textLineNr] = foldingControlPen;
250          }
251        }
252        if (end > maxEndOffset && fs.StartOffset < viewStartOffset) {
253          maxEndOffset = end;
254        }
255      }
256      if (maxEndOffset > 0) {
257        if (maxEndOffset > viewEndOffset) {
258          for (int i = 0; i < colors.Length; i++) {
259            colors[i] = foldingControlPen;
260          }
261        } else {
262          int maxTextLine = GetTextLineIndexFromOffset(allTextLines, maxEndOffset);
263          for (int i = 0; i <= maxTextLine; i++) {
264            colors[i] = foldingControlPen;
265          }
266        }
267      }
268    }
269   
270    /// <summary>
271    /// Calculates fold lines for all folding sections that start inside the current view
272    /// </summary>
273    void CalculateFoldLinesForMarkers(List<TextLine> allTextLines, Pen[] colors, Pen[] endMarker)
274    {
275      foreach (FoldingMarginMarker marker in markers) {
276        int end = marker.FoldingSection.EndOffset;
277        int endTextLineNr = GetTextLineIndexFromOffset(allTextLines, end);
278        if (!marker.FoldingSection.IsFolded && endTextLineNr >= 0) {
279          if (marker.IsMouseDirectlyOver)
280            endMarker[endTextLineNr] = selectedFoldingControlPen;
281          else if (endMarker[endTextLineNr] == null)
282            endMarker[endTextLineNr] = foldingControlPen;
283        }
284        int startTextLineNr = GetTextLineIndexFromOffset(allTextLines, marker.FoldingSection.StartOffset);
285        if (startTextLineNr >= 0) {
286          for (int i = startTextLineNr + 1; i < colors.Length && i - 1 != endTextLineNr; i++) {
287            if (marker.IsMouseDirectlyOver)
288              colors[i] = selectedFoldingControlPen;
289            else if (colors[i] == null)
290              colors[i] = foldingControlPen;
291          }
292        }
293      }
294    }
295   
296    /// <summary>
297    /// Draws the lines for the folding sections (vertical line with 'color', horizontal lines with 'endMarker')
298    /// Each entry in the input arrays corresponds to one TextLine.
299    /// </summary>
300    void DrawFoldLines(DrawingContext drawingContext, Pen[] colors, Pen[] endMarker)
301    {
302      // Because we are using PenLineCap.Flat (the default), for vertical lines,
303      // Y coordinates must be on pixel boundaries, whereas the X coordinate must be in the
304      // middle of a pixel. (and the other way round for horizontal lines)
305      Size pixelSize = PixelSnapHelpers.GetPixelSize(this);
306      double markerXPos = PixelSnapHelpers.PixelAlign(RenderSize.Width / 2, pixelSize.Width);
307      double startY = 0;
308      Pen currentPen = colors[0];
309      int tlNumber = 0;
310      foreach (VisualLine vl in TextView.VisualLines) {
311        foreach (TextLine tl in vl.TextLines) {
312          if (endMarker[tlNumber] != null) {
313            double visualPos = GetVisualPos(vl, tl, pixelSize.Height);
314            drawingContext.DrawLine(endMarker[tlNumber], new Point(markerXPos - pixelSize.Width / 2, visualPos), new Point(RenderSize.Width, visualPos));
315          }
316          if (colors[tlNumber + 1] != currentPen) {
317            double visualPos = GetVisualPos(vl, tl, pixelSize.Height);
318            if (currentPen != null) {
319              drawingContext.DrawLine(currentPen, new Point(markerXPos, startY + pixelSize.Height / 2), new Point(markerXPos, visualPos - pixelSize.Height / 2));
320            }
321            currentPen = colors[tlNumber + 1];
322            startY = visualPos;
323          }
324          tlNumber++;
325        }
326      }
327      if (currentPen != null) {
328        drawingContext.DrawLine(currentPen, new Point(markerXPos, startY + pixelSize.Height / 2), new Point(markerXPos, RenderSize.Height));
329      }
330    }
331   
332    double GetVisualPos(VisualLine vl, TextLine tl, double pixelHeight)
333    {
334      double pos = vl.GetTextLineVisualYPosition(tl, VisualYPosition.TextMiddle) - TextView.VerticalOffset;
335      return PixelSnapHelpers.PixelAlign(pos, pixelHeight);
336    }
337   
338    int GetTextLineIndexFromOffset(List<TextLine> textLines, int offset)
339    {
340      int lineNumber = TextView.Document.GetLineByOffset(offset).LineNumber;
341      VisualLine vl = TextView.GetVisualLine(lineNumber);
342      if (vl != null) {
343        int relOffset = offset - vl.FirstDocumentLine.Offset;
344        TextLine line = vl.GetTextLine(vl.GetVisualColumn(relOffset));
345        return textLines.IndexOf(line);
346      }
347      return -1;
348    }
349  }
350}
Note: See TracBrowser for help on using the repository browser.