aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Folding/FoldingElementGenerator.cs
blob: 76a1da101550cf469734a38ebff9e6246890a853 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// 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.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using Tango.Scripting.Editors.Rendering;
using Tango.Scripting.Editors.Utils;

namespace Tango.Scripting.Editors.Folding
{
	/// <summary>
	/// A <see cref="VisualLineElementGenerator"/> that produces line elements for folded <see cref="FoldingSection"/>s.
	/// </summary>
	public sealed class FoldingElementGenerator : VisualLineElementGenerator, ITextViewConnect
	{
		readonly List<TextView> textViews = new List<TextView>();
		FoldingManager foldingManager;
		
		#region FoldingManager property / connecting with TextView
		/// <summary>
		/// Gets/Sets the folding manager from which the foldings should be shown.
		/// </summary>
		public FoldingManager FoldingManager {
			get {
				return foldingManager;
			}
			set {
				if (foldingManager != value) {
					if (foldingManager != null) {
						foreach (TextView v in textViews)
							foldingManager.RemoveFromTextView(v);
					}
					foldingManager = value;
					if (foldingManager != null) {
						foreach (TextView v in textViews)
							foldingManager.AddToTextView(v);
					}
				}
			}
		}
		
		void ITextViewConnect.AddToTextView(TextView textView)
		{
			textViews.Add(textView);
			if (foldingManager != null)
				foldingManager.AddToTextView(textView);
		}
		
		void ITextViewConnect.RemoveFromTextView(TextView textView)
		{
			textViews.Remove(textView);
			if (foldingManager != null)
				foldingManager.RemoveFromTextView(textView);
		}
		#endregion
		
		/// <inheritdoc/>
		public override void StartGeneration(ITextRunConstructionContext context)
		{
			base.StartGeneration(context);
			if (foldingManager != null) {
				if (!foldingManager.textViews.Contains(context.TextView))
					throw new ArgumentException("Invalid TextView");
				if (context.Document != foldingManager.document)
					throw new ArgumentException("Invalid document");
			}
		}
		
		/// <inheritdoc/>
		public override int GetFirstInterestedOffset(int startOffset)
		{
			if (foldingManager != null) {
				foreach (FoldingSection fs in foldingManager.GetFoldingsContaining(startOffset)) {
					// Test whether we're currently within a folded folding (that didn't just end).
					// If so, create the fold marker immediately.
					// This is necessary if the actual beginning of the fold marker got skipped due to another VisualElementGenerator.
					if (fs.IsFolded && fs.EndOffset > startOffset) {
						//return startOffset;
					}
				}
				return foldingManager.GetNextFoldedFoldingStart(startOffset);
			} else {
				return -1;
			}
		}
		
		/// <inheritdoc/>
		public override VisualLineElement ConstructElement(int offset)
		{
			if (foldingManager == null)
				return null;
			int foldedUntil = -1;
			FoldingSection foldingSection = null;
			foreach (FoldingSection fs in foldingManager.GetFoldingsContaining(offset)) {
				if (fs.IsFolded) {
					if (fs.EndOffset > foldedUntil) {
						foldedUntil = fs.EndOffset;
						foldingSection = fs;
					}
				}
			}
			if (foldedUntil > offset && foldingSection != null) {
				// Handle overlapping foldings: if there's another folded folding
				// (starting within the foldingSection) that continues after the end of the folded section,
				// then we'll extend our fold element to cover that overlapping folding.
				bool foundOverlappingFolding;
				do {
					foundOverlappingFolding = false;
					foreach (FoldingSection fs in FoldingManager.GetFoldingsContaining(foldedUntil)) {
						if (fs.IsFolded && fs.EndOffset > foldedUntil) {
							foldedUntil = fs.EndOffset;
							foundOverlappingFolding = true;
						}
					}
				} while (foundOverlappingFolding);
				
				string title = foldingSection.Title;
				if (string.IsNullOrEmpty(title))
					title = "...";
				var p = new VisualLineElementTextRunProperties(CurrentContext.GlobalTextRunProperties);
				p.SetForegroundBrush(textBrush);
				var textFormatter = TextFormatterFactory.Create(CurrentContext.TextView);
				var text = FormattedTextElement.PrepareText(textFormatter, title, p);
				return new FoldingLineElement(foldingSection, text, foldedUntil - offset) { textBrush = textBrush };
			} else {
				return null;
			}
		}
		
		sealed class FoldingLineElement : FormattedTextElement
		{
			readonly FoldingSection fs;
			
			internal Brush textBrush;
			
			public FoldingLineElement(FoldingSection fs, TextLine text, int documentLength) : base(text, documentLength)
			{
				this.fs = fs;
			}
			
			public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
			{
				return new FoldingLineTextRun(this, this.TextRunProperties) { textBrush = textBrush };
			}
			
			protected internal override void OnMouseDown(MouseButtonEventArgs e)
			{
				if (e.ClickCount == 2 && e.ChangedButton == MouseButton.Left) {
					fs.IsFolded = false;
					e.Handled = true;
				} else {
					base.OnMouseDown(e);
				}
			}
		}
		
		sealed class FoldingLineTextRun : FormattedTextRun
		{
			internal Brush textBrush;
			
			public FoldingLineTextRun(FormattedTextElement element, TextRunProperties properties)
				: base(element, properties)
			{
			}
			
			public override void Draw(DrawingContext drawingContext, Point origin, bool rightToLeft, bool sideways)
			{
				var metrics = Format(double.PositiveInfinity);
				Rect r = new Rect(origin.X, origin.Y - metrics.Baseline, metrics.Width, metrics.Height);
				drawingContext.DrawRectangle(null, new Pen(textBrush, 1), r);
				base.Draw(drawingContext, origin, rightToLeft, sideways);
			}
		}
		
		/// <summary>
		/// Default brush for folding element text. Value: Brushes.Gray
		/// </summary>
		public static readonly Brush DefaultTextBrush = Brushes.Gray;
		
		static Brush textBrush = DefaultTextBrush;
		
		/// <summary>
		/// Gets/sets the brush used for folding element text.
		/// </summary>
		public static Brush TextBrush {
			get { return textBrush; }
			set { textBrush = value; }
		}
	}
}