aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLineElement.cs
blob: 7858ac0385aca93042629bf075f07d5e4c7413ad (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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// 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.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;

using Tango.Scripting.Editors.Document;

namespace Tango.Scripting.Editors.Rendering
{
	/// <summary>
	/// Represents a visual element in the document.
	/// </summary>
	public abstract class VisualLineElement
	{
		/// <summary>
		/// Creates a new VisualLineElement.
		/// </summary>
		/// <param name="visualLength">The length of the element in VisualLine coordinates. Must be positive.</param>
		/// <param name="documentLength">The length of the element in the document. Must be non-negative.</param>
		protected VisualLineElement(int visualLength, int documentLength)
		{
			if (visualLength < 1)
				throw new ArgumentOutOfRangeException("visualLength", visualLength, "Value must be at least 1");
			if (documentLength < 0)
				throw new ArgumentOutOfRangeException("documentLength", documentLength, "Value must be at least 0");
			this.VisualLength = visualLength;
			this.DocumentLength = documentLength;
		}
		
		/// <summary>
		/// Gets the length of this element in visual columns.
		/// </summary>
		public int VisualLength { get; private set; }
		
		/// <summary>
		/// Gets the length of this element in the text document.
		/// </summary>
		public int DocumentLength { get; private set; }
		
		/// <summary>
		/// Gets the visual column where this element starts.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
		                                                 Justification = "This property holds the start visual column, use GetVisualColumn to get inner visual columns.")]
		public int VisualColumn { get; internal set; }
		
		/// <summary>
		/// Gets the text offset where this element starts, relative to the start text offset of the visual line.
		/// </summary>
		public int RelativeTextOffset { get; internal set; }
		
		/// <summary>
		/// Gets the text run properties.
		/// A unique <see cref="VisualLineElementTextRunProperties"/> instance is used for each
		/// <see cref="VisualLineElement"/>; colorizing code may assume that modifying the
		/// <see cref="VisualLineElementTextRunProperties"/> will affect only this
		/// <see cref="VisualLineElement"/>.
		/// </summary>
		public VisualLineElementTextRunProperties TextRunProperties { get; private set; }
		
		/// <summary>
		/// Gets/sets the brush used for the background of this <see cref="VisualLineElement" />.
		/// </summary>
		public Brush BackgroundBrush { get; set; }
		
		internal void SetTextRunProperties(VisualLineElementTextRunProperties p)
		{
			this.TextRunProperties = p;
		}
		
		/// <summary>
		/// Creates the TextRun for this line element.
		/// </summary>
		/// <param name="startVisualColumn">
		/// The visual column from which the run should be constructed.
		/// Normally the same value as the <see cref="VisualColumn"/> property is used to construct the full run;
		/// but when word-wrapping is active, partial runs might be created.
		/// </param>
		/// <param name="context">
		/// Context object that contains information relevant for text run creation.
		/// </param>
		public abstract TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context);
		
		/// <summary>
		/// Retrieves the text span immediately before the visual column.
		/// </summary>
		/// <remarks>This method is used for word-wrapping in bidirectional text.</remarks>
		public virtual TextSpan<CultureSpecificCharacterBufferRange> GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context)
		{
			return null;
		}
		
		/// <summary>
		/// Gets if this VisualLineElement can be split.
		/// </summary>
		public virtual bool CanSplit {
			get { return false; }
		}
		
		/// <summary>
		/// Splits the element.
		/// </summary>
		/// <param name="splitVisualColumn">Position inside this element at which it should be broken</param>
		/// <param name="elements">The collection of line elements</param>
		/// <param name="elementIndex">The index at which this element is in the elements list.</param>
		public virtual void Split(int splitVisualColumn, IList<VisualLineElement> elements, int elementIndex)
		{
			throw new NotSupportedException();
		}
		
		/// <summary>
		/// Helper method for splitting this line element into two, correctly updating the
		/// <see cref="VisualLength"/>, <see cref="DocumentLength"/>, <see cref="VisualColumn"/>
		/// and <see cref="RelativeTextOffset"/> properties.
		/// </summary>
		/// <param name="firstPart">The element before the split position.</param>
		/// <param name="secondPart">The element after the split position.</param>
		/// <param name="splitVisualColumn">The split position as visual column.</param>
		/// <param name="splitRelativeTextOffset">The split position as text offset.</param>
		protected void SplitHelper(VisualLineElement firstPart, VisualLineElement secondPart, int splitVisualColumn, int splitRelativeTextOffset)
		{
			if (firstPart == null)
				throw new ArgumentNullException("firstPart");
			if (secondPart == null)
				throw new ArgumentNullException("secondPart");
			int relativeSplitVisualColumn = splitVisualColumn - VisualColumn;
			int relativeSplitRelativeTextOffset = splitRelativeTextOffset - RelativeTextOffset;
			
			if (relativeSplitVisualColumn <= 0 || relativeSplitVisualColumn >= VisualLength)
				throw new ArgumentOutOfRangeException("splitVisualColumn", splitVisualColumn, "Value must be between " + (VisualColumn + 1) + " and " + (VisualColumn + VisualLength - 1));
			if (relativeSplitRelativeTextOffset < 0 || relativeSplitRelativeTextOffset > DocumentLength)
				throw new ArgumentOutOfRangeException("splitRelativeTextOffset", splitRelativeTextOffset, "Value must be between " + (RelativeTextOffset) + " and " + (RelativeTextOffset + DocumentLength));
			int oldVisualLength = VisualLength;
			int oldDocumentLength = DocumentLength;
			int oldVisualColumn = VisualColumn;
			int oldRelativeTextOffset = RelativeTextOffset;
			firstPart.VisualColumn = oldVisualColumn;
			secondPart.VisualColumn = oldVisualColumn + relativeSplitVisualColumn;
			firstPart.RelativeTextOffset = oldRelativeTextOffset;
			secondPart.RelativeTextOffset = oldRelativeTextOffset + relativeSplitRelativeTextOffset;
			firstPart.VisualLength = relativeSplitVisualColumn;
			secondPart.VisualLength = oldVisualLength - relativeSplitVisualColumn;
			firstPart.DocumentLength = relativeSplitRelativeTextOffset;
			secondPart.DocumentLength = oldDocumentLength - relativeSplitRelativeTextOffset;
			if (firstPart.TextRunProperties == null)
				firstPart.TextRunProperties = TextRunProperties.Clone();
			if (secondPart.TextRunProperties == null)
				secondPart.TextRunProperties = TextRunProperties.Clone();
		}
		
		/// <summary>
		/// Gets the visual column of a text location inside this element.
		/// The text offset is given relative to the visual line start.
		/// </summary>
		public virtual int GetVisualColumn(int relativeTextOffset)
		{
			if (relativeTextOffset >= this.RelativeTextOffset + DocumentLength)
				return VisualColumn + VisualLength;
			else
				return VisualColumn;
		}
		
		/// <summary>
		/// Gets the text offset of a visual column inside this element.
		/// </summary>
		/// <returns>A text offset relative to the visual line start.</returns>
		public virtual int GetRelativeOffset(int visualColumn)
		{
			if (visualColumn >= this.VisualColumn + VisualLength)
				return RelativeTextOffset + DocumentLength;
			else
				return RelativeTextOffset;
		}
		
		/// <summary>
		/// Gets the next caret position inside this element.
		/// </summary>
		/// <param name="visualColumn">The visual column from which the search should be started.</param>
		/// <param name="direction">The search direction (forwards or backwards).</param>
		/// <param name="mode">Whether to stop only at word borders.</param>
		/// <returns>The visual column of the next caret position, or -1 if there is no next caret position.</returns>
		/// <remarks>
		/// In the space between two line elements, it is sufficient that one of them contains a caret position;
		/// though in many cases, both of them contain one.
		/// </remarks>
		public virtual int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
		{
			int stop1 = this.VisualColumn;
			int stop2 = this.VisualColumn + this.VisualLength;
			if (direction == LogicalDirection.Backward) {
				if (visualColumn > stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol)
					return stop2;
				else if (visualColumn > stop1)
					return stop1;
			} else {
				if (visualColumn < stop1)
					return stop1;
				else if (visualColumn < stop2 && mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol)
					return stop2;
			}
			return -1;
		}
		
		/// <summary>
		/// Gets whether the specified offset in this element is considered whitespace.
		/// </summary>
		public virtual bool IsWhitespace(int visualColumn)
		{
			return false;
		}
		
		/// <summary>
		/// Gets whether the <see cref="GetNextCaretPosition"/> implementation handles line borders.
		/// If this property returns false, the caller of GetNextCaretPosition should handle the line
		/// borders (i.e. place caret stops at the start and end of the line).
		/// This property has an effect only for VisualLineElements that are at the start or end of a
		/// <see cref="VisualLine"/>.
		/// </summary>
		public virtual bool HandlesLineBorders {
			get { return false; }
		}
		
		/// <summary>
		/// Queries the cursor over the visual line element.
		/// </summary>
		protected internal virtual void OnQueryCursor(QueryCursorEventArgs e)
		{
		}
		
		/// <summary>
		/// Allows the visual line element to handle a mouse event.
		/// </summary>
		protected internal virtual void OnMouseDown(MouseButtonEventArgs e)
		{
		}
		
		/// <summary>
		/// Allows the visual line element to handle a mouse event.
		/// </summary>
		protected internal virtual void OnMouseUp(MouseButtonEventArgs e)
		{
		}
	}
}