Code Coverage Statistics for Source File
c:\Tools\SD3\src\Libraries\ICSharpCode.TextEditor\Project\Src\Gui\TextView.cs
|
Sequence Point Coverage
N/A
0 of 0
|
Branch Coverage
N/A
0 of 0
|
Lines
1091
|
Highlight:
Uncovered Code
Covered Code
| L | V | Source |
|---|---|---|
1 |
// <file> |
|
2 |
// <copyright see="prj:///doc/copyright.txt"/> |
|
3 |
// <license see="prj:///doc/license.txt"/> |
|
4 |
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/> |
|
5 |
// <version>$Revision: 2740 $</version> |
|
6 |
// </file> |
|
7 |
||
8 |
using System; |
|
9 |
using System.Collections.Generic; |
|
10 |
using System.Diagnostics; |
|
11 |
using System.Drawing; |
|
12 |
using System.Windows.Forms; |
|
13 |
||
14 |
using ICSharpCode.TextEditor.Document; |
|
15 |
||
16 |
namespace ICSharpCode.TextEditor |
|
17 |
{ |
|
18 |
/// <summary> |
|
19 |
/// This class paints the textarea. |
|
20 |
/// </summary> |
|
21 |
public class TextView : AbstractMargin, IDisposable |
|
22 |
{ |
|
23 |
int fontHeight; |
|
24 |
//Hashtable charWitdh = new Hashtable(); |
|
25 |
//StringFormat measureStringFormat = (StringFormat)StringFormat.GenericTypographic.Clone(); |
|
26 |
Highlight highlight; |
|
27 |
int physicalColumn = 0; // used for calculating physical column during paint |
|
28 |
||
29 |
public void Dispose() |
|
30 |
{ |
|
31 |
measureCache.Clear(); |
|
32 |
//measureStringFormat.Dispose(); |
|
33 |
} |
|
34 |
||
35 |
public Highlight Highlight { |
|
36 |
get { |
|
37 |
return highlight; |
|
38 |
} |
|
39 |
set { |
|
40 |
highlight = value; |
|
41 |
} |
|
42 |
} |
|
43 |
||
44 |
public int FirstPhysicalLine { |
|
45 |
get { |
|
46 |
return textArea.VirtualTop.Y / fontHeight; |
|
47 |
} |
|
48 |
} |
|
49 |
public int LineHeightRemainder { |
|
50 |
get { |
|
51 |
return textArea.VirtualTop.Y % fontHeight; |
|
52 |
} |
|
53 |
} |
|
54 |
/// <summary>Gets the first visible <b>logical</b> line.</summary> |
|
55 |
public int FirstVisibleLine { |
|
56 |
get { |
|
57 |
return textArea.Document.GetFirstLogicalLine(textArea.VirtualTop.Y / fontHeight); |
|
58 |
} |
|
59 |
set { |
|
60 |
if (FirstVisibleLine != value) { |
|
61 |
textArea.VirtualTop = new Point(textArea.VirtualTop.X, textArea.Document.GetVisibleLine(value) * fontHeight); |
|
62 |
||
63 |
} |
|
64 |
} |
|
65 |
} |
|
66 |
||
67 |
public int VisibleLineDrawingRemainder { |
|
68 |
get { |
|
69 |
return textArea.VirtualTop.Y % fontHeight; |
|
70 |
} |
|
71 |
} |
|
72 |
||
73 |
public int FontHeight { |
|
74 |
get { |
|
75 |
return fontHeight; |
|
76 |
} |
|
77 |
} |
|
78 |
||
79 |
public int VisibleLineCount { |
|
80 |
get { |
|
81 |
return 1 + DrawingPosition.Height / fontHeight; |
|
82 |
} |
|
83 |
} |
|
84 |
||
85 |
public int VisibleColumnCount { |
|
86 |
get { |
|
87 |
return (int)(DrawingPosition.Width / WideSpaceWidth) - 1; |
|
88 |
} |
|
89 |
} |
|
90 |
||
91 |
public TextView(TextArea textArea) : base(textArea) |
|
92 |
{ |
|
93 |
base.Cursor = Cursors.IBeam; |
|
94 |
OptionsChanged(); |
|
95 |
} |
|
96 |
||
97 |
static int GetFontHeight(Font font) |
|
98 |
{ |
|
99 |
int height1 = TextRenderer.MeasureText("_", font).Height; |
|
100 |
int height2 = (int)Math.Ceiling(font.GetHeight()); |
|
101 |
return Math.Max(height1, height2) + 1; |
|
102 |
} |
|
103 |
||
104 |
int spaceWidth; |
|
105 |
||
106 |
/// <summary> |
|
107 |
/// Gets the width of a space character. |
|
108 |
/// This value can be quite small in some fonts - consider using WideSpaceWidth instead. |
|
109 |
/// </summary> |
|
110 |
public int SpaceWidth { |
|
111 |
get { |
|
112 |
return spaceWidth; |
|
113 |
} |
|
114 |
} |
|
115 |
||
116 |
int wideSpaceWidth; |
|
117 |
||
118 |
/// <summary> |
|
119 |
/// Gets the width of a 'wide space' (=one quarter of a tab, if tab is set to 4 spaces). |
|
120 |
/// On monospaced fonts, this is the same value as spaceWidth. |
|
121 |
/// </summary> |
|
122 |
public int WideSpaceWidth { |
|
123 |
get { |
|
124 |
return wideSpaceWidth; |
|
125 |
} |
|
126 |
} |
|
127 |
||
128 |
Font lastFont; |
|
129 |
||
130 |
public void OptionsChanged() |
|
131 |
{ |
|
132 |
this.lastFont = TextEditorProperties.FontContainer.RegularFont; |
|
133 |
this.fontHeight = GetFontHeight(lastFont); |
|
134 |
// use minimum width - in some fonts, space has no width but kerning is used instead |
|
135 |
// -> DivideByZeroException |
|
136 |
this.spaceWidth = Math.Max(GetWidth(' ', lastFont), 1); |
|
137 |
// tab should have the width of 4*'x' |
|
138 |
this.wideSpaceWidth = Math.Max(spaceWidth, GetWidth('x', lastFont)); |
|
139 |
} |
|
140 |
||
141 |
#region Paint functions |
|
142 |
public override void Paint(Graphics g, Rectangle rect) |
|
143 |
{ |
|
144 |
if (rect.Width <= 0 || rect.Height <= 0) { |
|
145 |
return; |
|
146 |
} |
|
147 |
||
148 |
// Just to ensure that fontHeight and char widths are always correct... |
|
149 |
if (lastFont != TextEditorProperties.FontContainer.RegularFont) { |
|
150 |
OptionsChanged(); |
|
151 |
textArea.Invalidate(); |
|
152 |
} |
|
153 |
||
154 |
int horizontalDelta = textArea.VirtualTop.X; |
|
155 |
if (horizontalDelta > 0) { |
|
156 |
g.SetClip(this.DrawingPosition); |
|
157 |
} |
|
158 |
||
159 |
for (int y = 0; y < (DrawingPosition.Height + VisibleLineDrawingRemainder) / fontHeight + 1; ++y) { |
|
160 |
Rectangle lineRectangle = new Rectangle(DrawingPosition.X - horizontalDelta, |
|
161 |
DrawingPosition.Top + y * fontHeight - VisibleLineDrawingRemainder, |
|
162 |
DrawingPosition.Width + horizontalDelta, |
|
163 |
fontHeight); |
|
164 |
||
165 |
if (rect.IntersectsWith(lineRectangle)) { |
|
166 |
int fvl = textArea.Document.GetVisibleLine(FirstVisibleLine); |
|
167 |
int currentLine = textArea.Document.GetFirstLogicalLine(textArea.Document.GetVisibleLine(FirstVisibleLine) + y); |
|
168 |
PaintDocumentLine(g, currentLine, lineRectangle); |
|
169 |
} |
|
170 |
} |
|
171 |
||
172 |
DrawMarkerDraw(g); |
|
173 |
||
174 |
if (horizontalDelta > 0) { |
|
175 |
g.ResetClip(); |
|
176 |
} |
|
177 |
} |
|
178 |
||
179 |
void PaintDocumentLine(Graphics g, int lineNumber, Rectangle lineRectangle) |
|
180 |
{ |
|
181 |
Debug.Assert(lineNumber >= 0); |
|
182 |
Brush bgColorBrush = GetBgColorBrush(lineNumber); |
|
183 |
Brush backgroundBrush = textArea.Enabled ? bgColorBrush : SystemBrushes.InactiveBorder; |
|
184 |
||
185 |
if (lineNumber >= textArea.Document.TotalNumberOfLines) { |
|
186 |
g.FillRectangle(backgroundBrush, lineRectangle); |
|
187 |
if (TextEditorProperties.ShowInvalidLines) { |
|
188 |
DrawInvalidLineMarker(g, lineRectangle.Left, lineRectangle.Top); |
|
189 |
} |
|
190 |
if (TextEditorProperties.ShowVerticalRuler) { |
|
191 |
DrawVerticalRuler(g, lineRectangle); |
|
192 |
} |
|
193 |
// bgColorBrush.Dispose(); |
|
194 |
return; |
|
195 |
} |
|
196 |
||
197 |
int physicalXPos = lineRectangle.X; |
|
198 |
// there can't be a folding wich starts in an above line and ends here, because the line is a new one, |
|
199 |
// there must be a return before this line. |
|
200 |
int column = 0; |
|
201 |
physicalColumn = 0; |
|
202 |
if (TextEditorProperties.EnableFolding) { |
|
203 |
while (true) { |
|
204 |
List<FoldMarker> starts = textArea.Document.FoldingManager.GetFoldedFoldingsWithStartAfterColumn(lineNumber, column - 1); |
|
205 |
if (starts == null || starts.Count <= 0) { |
|
206 |
if (lineNumber < textArea.Document.TotalNumberOfLines) { |
|
207 |
physicalXPos = PaintLinePart(g, lineNumber, column, textArea.Document.GetLineSegment(lineNumber).Length, lineRectangle, physicalXPos); |
|
208 |
} |
|
209 |
break; |
|
210 |
} |
|
211 |
// search the first starting folding |
|
212 |
FoldMarker firstFolding = (FoldMarker)starts[0]; |
|
213 |
foreach (FoldMarker fm in starts) { |
|
214 |
if (fm.StartColumn < firstFolding.StartColumn) { |
|
215 |
firstFolding = fm; |
|
216 |
} |
|
217 |
} |
|
218 |
starts.Clear(); |
|
219 |
||
220 |
physicalXPos = PaintLinePart(g, lineNumber, column, firstFolding.StartColumn, lineRectangle, physicalXPos); |
|
221 |
column = firstFolding.EndColumn; |
|
222 |
lineNumber = firstFolding.EndLine; |
|
223 |
if (lineNumber >= textArea.Document.TotalNumberOfLines) { |
|
224 |
Debug.Assert(false, "Folding ends after document end"); |
|
225 |
break; |
|
226 |
} |
|
227 |
||
228 |
ColumnRange selectionRange2 = textArea.SelectionManager.GetSelectionAtLine(lineNumber); |
|
229 |
bool drawSelected = ColumnRange.WholeColumn.Equals(selectionRange2) || firstFolding.StartColumn >= selectionRange2.StartColumn && firstFolding.EndColumn <= selectionRange2.EndColumn; |
|
230 |
||
231 |
physicalXPos = PaintFoldingText(g, lineNumber, physicalXPos, lineRectangle, firstFolding.FoldText, drawSelected); |
|
232 |
} |
|
233 |
} else { |
|
234 |
physicalXPos = PaintLinePart(g, lineNumber, 0, textArea.Document.GetLineSegment(lineNumber).Length, lineRectangle, physicalXPos); |
|
235 |
} |
|
236 |
||
237 |
if (lineNumber < textArea.Document.TotalNumberOfLines) { |
|
238 |
// Paint things after end of line |
|
239 |
ColumnRange selectionRange = textArea.SelectionManager.GetSelectionAtLine(lineNumber); |
|
240 |
LineSegment currentLine = textArea.Document.GetLineSegment(lineNumber); |
|
241 |
HighlightColor selectionColor = textArea.Document.HighlightingStrategy.GetColorFor("Selection"); |
|
242 |
||
243 |
bool selectionBeyondEOL = selectionRange.EndColumn > currentLine.Length || ColumnRange.WholeColumn.Equals(selectionRange); |
|
244 |
||
245 |
if (TextEditorProperties.ShowEOLMarker) { |
|
246 |
HighlightColor eolMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("EOLMarkers"); |
|
247 |
physicalXPos += DrawEOLMarker(g, eolMarkerColor.Color, selectionBeyondEOL ? bgColorBrush : backgroundBrush, physicalXPos, lineRectangle.Y); |
|
248 |
} else { |
|
249 |
if (selectionBeyondEOL) { |
|
250 |
g.FillRectangle(BrushRegistry.GetBrush(selectionColor.BackgroundColor), new RectangleF(physicalXPos, lineRectangle.Y, WideSpaceWidth, lineRectangle.Height)); |
|
251 |
physicalXPos += WideSpaceWidth; |
|
252 |
} |
|
253 |
} |
|
254 |
||
255 |
Brush fillBrush = selectionBeyondEOL && TextEditorProperties.AllowCaretBeyondEOL ? bgColorBrush : backgroundBrush; |
|
256 |
g.FillRectangle(fillBrush, |
|
257 |
new RectangleF(physicalXPos, lineRectangle.Y, lineRectangle.Width - physicalXPos + lineRectangle.X, lineRectangle.Height)); |
|
258 |
} |
|
259 |
if (TextEditorProperties.ShowVerticalRuler) { |
|
260 |
DrawVerticalRuler(g, lineRectangle); |
|
261 |
} |
|
262 |
// bgColorBrush.Dispose(); |
|
263 |
} |
|
264 |
||
265 |
bool DrawLineMarkerAtLine(int lineNumber) |
|
266 |
{ |
|
267 |
return lineNumber == base.textArea.Caret.Line && textArea.MotherTextAreaControl.TextEditorProperties.LineViewerStyle == LineViewerStyle.FullRow; |
|
268 |
} |
|
269 |
||
270 |
Brush GetBgColorBrush(int lineNumber) |
|
271 |
{ |
|
272 |
if (DrawLineMarkerAtLine(lineNumber)) { |
|
273 |
HighlightColor caretLine = textArea.Document.HighlightingStrategy.GetColorFor("CaretMarker"); |
|
274 |
return BrushRegistry.GetBrush(caretLine.Color); |
|
275 |
} |
|
276 |
HighlightColor background = textArea.Document.HighlightingStrategy.GetColorFor("Default"); |
|
277 |
Color bgColor = background.BackgroundColor; |
|
278 |
if (textArea.MotherTextAreaControl.TextEditorProperties.UseCustomLine == true) |
|
279 |
{ |
|
280 |
bgColor = textArea.Document.CustomLineManager.GetCustomColor(lineNumber, bgColor); |
|
281 |
} |
|
282 |
return BrushRegistry.GetBrush(bgColor); |
|
283 |
} |
|
284 |
||
285 |
const int additionalFoldTextSize = 1; |
|
286 |
||
287 |
int PaintFoldingText(Graphics g, int lineNumber, int physicalXPos, Rectangle lineRectangle, string text, bool drawSelected) |
|
288 |
{ |
|
289 |
// TODO: get font and color from the highlighting file |
|
290 |
HighlightColor selectionColor = textArea.Document.HighlightingStrategy.GetColorFor("Selection"); |
|
291 |
Brush bgColorBrush = drawSelected ? BrushRegistry.GetBrush(selectionColor.BackgroundColor) : GetBgColorBrush(lineNumber); |
|
292 |
Brush backgroundBrush = textArea.Enabled ? bgColorBrush : SystemBrushes.InactiveBorder; |
|
293 |
||
294 |
Font font = textArea.TextEditorProperties.FontContainer.RegularFont; |
|
295 |
||
296 |
int wordWidth = MeasureStringWidth(g, text, font) + additionalFoldTextSize; |
|
297 |
Rectangle rect = new Rectangle(physicalXPos, lineRectangle.Y, wordWidth, lineRectangle.Height - 1); |
|
298 |
||
299 |
g.FillRectangle(backgroundBrush, rect); |
|
300 |
||
301 |
physicalColumn += text.Length; |
|
302 |
DrawString(g, |
|
303 |
text, |
|
304 |
font, |
|
305 |
drawSelected ? selectionColor.Color : Color.Gray, |
|
306 |
rect.X + 1, rect.Y); |
|
307 |
g.DrawRectangle(BrushRegistry.GetPen(drawSelected ? Color.DarkGray : Color.Gray), rect.X, rect.Y, rect.Width, rect.Height); |
|
308 |
||
309 |
return physicalXPos + wordWidth + 1; |
|
310 |
} |
|
311 |
||
312 |
struct MarkerToDraw { |
|
313 |
internal TextMarker marker; |
|
314 |
internal RectangleF drawingRect; |
|
315 |
||
316 |
public MarkerToDraw(TextMarker marker, RectangleF drawingRect) |
|
317 |
{ |
|
318 |
this.marker = marker; |
|
319 |
this.drawingRect = drawingRect; |
|
320 |
} |
|
321 |
} |
|
322 |
||
323 |
List<MarkerToDraw> markersToDraw = new List<MarkerToDraw>(); |
|
324 |
||
325 |
void DrawMarker(Graphics g, TextMarker marker, RectangleF drawingRect) |
|
326 |
{ |
|
327 |
// draw markers later so they can overdraw the following text |
|
328 |
markersToDraw.Add(new MarkerToDraw(marker, drawingRect)); |
|
329 |
} |
|
330 |
||
331 |
void DrawMarkerDraw(Graphics g) |
|
332 |
{ |
|
333 |
foreach (MarkerToDraw m in markersToDraw) { |
|
334 |
TextMarker marker = m.marker; |
|
335 |
RectangleF drawingRect = m.drawingRect; |
|
336 |
float drawYPos = drawingRect.Bottom - 1; |
|
337 |
switch (marker.TextMarkerType) { |
|
338 |
case TextMarkerType.Underlined: |
|
339 |
g.DrawLine(BrushRegistry.GetPen(marker.Color), drawingRect.X, drawYPos, drawingRect.Right, drawYPos); |
|
340 |
break; |
|
341 |
case TextMarkerType.WaveLine: |
|
342 |
int reminder = ((int)drawingRect.X) % 6; |
|
343 |
for (float i = (int)drawingRect.X - reminder; i < drawingRect.Right; i += 6) { |
|
344 |
g.DrawLine(BrushRegistry.GetPen(marker.Color), i, drawYPos + 3 - 4, i + 3, drawYPos + 1 - 4); |
|
345 |
if (i + 3 < drawingRect.Right) { |
|
346 |
g.DrawLine(BrushRegistry.GetPen(marker.Color), i + 3, drawYPos + 1 - 4, i + 6, drawYPos + 3 - 4); |
|
347 |
} |
|
348 |
} |
|
349 |
break; |
|
350 |
case TextMarkerType.SolidBlock: |
|
351 |
g.FillRectangle(BrushRegistry.GetBrush(marker.Color), drawingRect); |
|
352 |
break; |
|
353 |
} |
|
354 |
} |
|
355 |
markersToDraw.Clear(); |
|
356 |
} |
|
357 |
||
358 |
/// <summary> |
|
359 |
/// Get the marker brush (for solid block markers) at a given position. |
|
360 |
/// </summary> |
|
361 |
/// <param name="offset">The offset.</param> |
|
362 |
/// <param name="length">The length.</param> |
|
363 |
/// <param name="markers">All markers that have been found.</param> |
|
364 |
/// <returns>The Brush or null when no marker was found.</returns> |
|
365 |
Brush GetMarkerBrushAt(int offset, int length, ref Color foreColor, out IList<TextMarker> markers) |
|
366 |
{ |
|
367 |
markers = Document.MarkerStrategy.GetMarkers(offset, length); |
|
368 |
foreach (TextMarker marker in markers) { |
|
369 |
if (marker.TextMarkerType == TextMarkerType.SolidBlock) { |
|
370 |
if (marker.OverrideForeColor) { |
|
371 |
foreColor = marker.ForeColor; |
|
372 |
} |
|
373 |
return BrushRegistry.GetBrush(marker.Color); |
|
374 |
} |
|
375 |
} |
|
376 |
return null; |
|
377 |
} |
|
378 |
||
379 |
int PaintLinePart(Graphics g, int lineNumber, int startColumn, int endColumn, Rectangle lineRectangle, int physicalXPos) |
|
380 |
{ |
|
381 |
bool drawLineMarker = DrawLineMarkerAtLine(lineNumber); |
|
382 |
Brush backgroundBrush = textArea.Enabled ? GetBgColorBrush(lineNumber) : SystemBrushes.InactiveBorder; |
|
383 |
||
384 |
HighlightColor selectionColor = textArea.Document.HighlightingStrategy.GetColorFor("Selection"); |
|
385 |
ColumnRange selectionRange = textArea.SelectionManager.GetSelectionAtLine(lineNumber); |
|
386 |
HighlightColor tabMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("TabMarkers"); |
|
387 |
HighlightColor spaceMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("SpaceMarkers"); |
|
388 |
||
389 |
LineSegment currentLine = textArea.Document.GetLineSegment(lineNumber); |
|
390 |
||
391 |
Brush selectionBackgroundBrush = BrushRegistry.GetBrush(selectionColor.BackgroundColor); |
|
392 |
||
393 |
if (currentLine.Words == null) { |
|
394 |
return physicalXPos; |
|
395 |
} |
|
396 |
||
397 |
int currentWordOffset = 0; // we cannot use currentWord.Offset because it is not set on space words |
|
398 |
||
399 |
TextWord currentWord; |
|
400 |
TextWord nextCurrentWord = null; |
|
401 |
FontContainer fontContainer = TextEditorProperties.FontContainer; |
|
402 |
for (int wordIdx = 0; wordIdx < currentLine.Words.Count; wordIdx++) { |
|
403 |
currentWord = currentLine.Words[wordIdx]; |
|
404 |
if (currentWordOffset < startColumn) { |
|
405 |
// TODO: maybe we need to split at startColumn when we support fold markers |
|
406 |
// inside words |
|
407 |
currentWordOffset += currentWord.Length; |
|
408 |
continue; |
|
409 |
} |
|
410 |
repeatDrawCurrentWord: |
|
411 |
//physicalXPos += 10; // leave room between drawn words - useful for debugging the drawing code |
|
412 |
if (currentWordOffset >= endColumn || physicalXPos >= lineRectangle.Right) { |
|
413 |
break; |
|
414 |
} |
|
415 |
int currentWordEndOffset = currentWordOffset + currentWord.Length - 1; |
|
416 |
TextWordType currentWordType = currentWord.Type; |
|
417 |
||
418 |
IList<TextMarker> markers; |
|
419 |
Color wordForeColor; |
|
420 |
if (currentWordType == TextWordType.Space) |
|
421 |
wordForeColor = spaceMarkerColor.Color; |
|
422 |
else if (currentWordType == TextWordType.Tab) |
|
423 |
wordForeColor = tabMarkerColor.Color; |
|
424 |
else |
|
425 |
wordForeColor = currentWord.Color; |
|
426 |
Brush wordBackBrush = GetMarkerBrushAt(currentLine.Offset + currentWordOffset, currentWord.Length, ref wordForeColor, out markers); |
|
427 |
||
428 |
// It is possible that we have to split the current word because a marker/the selection begins/ends inside it |
|
429 |
if (currentWord.Length > 1) { |
|
430 |
int splitPos = int.MaxValue; |
|
431 |
if (highlight != null) { |
|
432 |
// split both before and after highlight |
|
433 |
if (highlight.OpenBrace.Y == lineNumber) { |
|
434 |
if (highlight.OpenBrace.X >= currentWordOffset && highlight.OpenBrace.X <= currentWordEndOffset) { |
|
435 |
splitPos = Math.Min(splitPos, highlight.OpenBrace.X - currentWordOffset); |
|
436 |
} |
|
437 |
} |
|
438 |
if (highlight.CloseBrace.Y == lineNumber) { |
|
439 |
if (highlight.CloseBrace.X >= currentWordOffset && highlight.CloseBrace.X <= currentWordEndOffset) { |
|
440 |
splitPos = Math.Min(splitPos, highlight.CloseBrace.X - currentWordOffset); |
|
441 |
} |
|
442 |
} |
|
443 |
if (splitPos == 0) { |
|
444 |
splitPos = 1; // split after highlight |
|
445 |
} |
|
446 |
} |
|
447 |
if (endColumn < currentWordEndOffset) { // split when endColumn is reached |
|
448 |
splitPos = Math.Min(splitPos, endColumn - currentWordOffset); |
|
449 |
} |
|
450 |
if (selectionRange.StartColumn > currentWordOffset && selectionRange.StartColumn <= currentWordEndOffset) { |
|
451 |
splitPos = Math.Min(splitPos, selectionRange.StartColumn - currentWordOffset); |
|
452 |
} else if (selectionRange.EndColumn > currentWordOffset && selectionRange.EndColumn <= currentWordEndOffset) { |
|
453 |
splitPos = Math.Min(splitPos, selectionRange.EndColumn - currentWordOffset); |
|
454 |
} |
|
455 |
foreach (TextMarker marker in markers) { |
|
456 |
int markerColumn = marker.Offset - currentLine.Offset; |
|
457 |
int markerEndColumn = marker.EndOffset - currentLine.Offset + 1; // make end offset exclusive |
|
458 |
if (markerColumn > currentWordOffset && markerColumn <= currentWordEndOffset) { |
|
459 |
splitPos = Math.Min(splitPos, markerColumn - currentWordOffset); |
|
460 |
} else if (markerEndColumn > currentWordOffset && markerEndColumn <= currentWordEndOffset) { |
|
461 |
splitPos = Math.Min(splitPos, markerEndColumn - currentWordOffset); |
|
462 |
} |
|
463 |
} |
|
464 |
if (splitPos != int.MaxValue) { |
|
465 |
if (nextCurrentWord != null) |
|
466 |
throw new ApplicationException("split part invalid: first part cannot be splitted further"); |
|
467 |
nextCurrentWord = TextWord.Split(ref currentWord, splitPos); |
|
468 |
goto repeatDrawCurrentWord; // get markers for first word part |
|
469 |
} |
|
470 |
} |
|
471 |
||
472 |
// get colors from selection status: |
|
473 |
if (ColumnRange.WholeColumn.Equals(selectionRange) || (selectionRange.StartColumn <= currentWordOffset |
|
474 |
&& selectionRange.EndColumn > currentWordEndOffset)) |
|
475 |
{ |
|
476 |
// word is completely selected |
|
477 |
wordBackBrush = selectionBackgroundBrush; |
|
478 |
if (selectionColor.HasForeground) { |
|
479 |
wordForeColor = selectionColor.Color; |
|
480 |
} |
|
481 |
} else if (drawLineMarker) { |
|
482 |
wordBackBrush = backgroundBrush; |
|
483 |
} |
|
484 |
||
485 |
if (wordBackBrush == null) { // use default background if no other background is set |
|
486 |
if (currentWord.SyntaxColor != null && currentWord.SyntaxColor.HasBackground) |
|
487 |
wordBackBrush = BrushRegistry.GetBrush(currentWord.SyntaxColor.BackgroundColor); |
|
488 |
else |
|
489 |
wordBackBrush = backgroundBrush; |
|
490 |
} |
|
491 |
||
492 |
RectangleF wordRectangle; |
|
493 |
||
494 |
if (currentWord.Type == TextWordType.Space) { |
|
495 |
++physicalColumn; |
|
496 |
||
497 |
wordRectangle = new RectangleF(physicalXPos, lineRectangle.Y, SpaceWidth, lineRectangle.Height); |
|
498 |
g.FillRectangle(wordBackBrush, wordRectangle); |
|
499 |
||
500 |
if (TextEditorProperties.ShowSpaces) { |
|
501 |
DrawSpaceMarker(g, wordForeColor, physicalXPos, lineRectangle.Y); |
|
502 |
} |
|
503 |
physicalXPos += SpaceWidth; |
|
504 |
} else if (currentWord.Type == TextWordType.Tab) { |
|
505 |
||
506 |
physicalColumn += TextEditorProperties.TabIndent; |
|
507 |
physicalColumn = (physicalColumn / TextEditorProperties.TabIndent) * TextEditorProperties.TabIndent; |
|
508 |
// go to next tabstop |
|
509 |
int physicalTabEnd = ((physicalXPos + MinTabWidth - lineRectangle.X) |
|
510 |
/ WideSpaceWidth / TextEditorProperties.TabIndent) |
|
511 |
* WideSpaceWidth * TextEditorProperties.TabIndent + lineRectangle.X; |
|
512 |
physicalTabEnd += WideSpaceWidth * TextEditorProperties.TabIndent; |
|
513 |
||
514 |
wordRectangle = new RectangleF(physicalXPos, lineRectangle.Y, physicalTabEnd - physicalXPos, lineRectangle.Height); |
|
515 |
g.FillRectangle(wordBackBrush, wordRectangle); |
|
516 |
||
517 |
if (TextEditorProperties.ShowTabs) { |
|
518 |
DrawTabMarker(g, wordForeColor, physicalXPos, lineRectangle.Y); |
|
519 |
} |
|
520 |
physicalXPos = physicalTabEnd; |
|
521 |
} else { |
|
522 |
int wordWidth = DrawDocumentWord(g, |
|
523 |
currentWord.Word, |
|
524 |
new Point(physicalXPos, lineRectangle.Y), |
|
525 |
currentWord.GetFont(fontContainer), |
|
526 |
wordForeColor, |
|
527 |
wordBackBrush); |
|
528 |
wordRectangle = new RectangleF(physicalXPos, lineRectangle.Y, wordWidth, lineRectangle.Height); |
|
529 |
physicalXPos += wordWidth; |
|
530 |
} |
|
531 |
foreach (TextMarker marker in markers) { |
|
532 |
if (marker.TextMarkerType != TextMarkerType.SolidBlock) { |
|
533 |
DrawMarker(g, marker, wordRectangle); |
|
534 |
} |
|
535 |
} |
|
536 |
||
537 |
// draw bracket highlight |
|
538 |
if (highlight != null) { |
|
539 |
if (highlight.OpenBrace.Y == lineNumber && highlight.OpenBrace.X == currentWordOffset || |
|
540 |
highlight.CloseBrace.Y == lineNumber && highlight.CloseBrace.X == currentWordOffset) { |
|
541 |
DrawBracketHighlight(g, new Rectangle((int)wordRectangle.X, lineRectangle.Y, (int)wordRectangle.Width - 1, lineRectangle.Height - 1)); |
|
542 |
} |
|
543 |
} |
|
544 |
||
545 |
currentWordOffset += currentWord.Length; |
|
546 |
if (nextCurrentWord != null) { |
|
547 |
currentWord = nextCurrentWord; |
|
548 |
nextCurrentWord = null; |
|
549 |
goto repeatDrawCurrentWord; |
|
550 |
} |
|
551 |
} |
|
552 |
if (physicalXPos < lineRectangle.Right && endColumn >= currentLine.Length) { |
|
553 |
// draw markers at line end |
|
554 |
IList<TextMarker> markers = Document.MarkerStrategy.GetMarkers(currentLine.Offset + currentLine.Length); |
|
555 |
foreach (TextMarker marker in markers) { |
|
556 |
if (marker.TextMarkerType != TextMarkerType.SolidBlock) { |
|
557 |
DrawMarker(g, marker, new RectangleF(physicalXPos, lineRectangle.Y, WideSpaceWidth, lineRectangle.Height)); |
|
558 |
} |
|
559 |
} |
|
560 |
} |
|
561 |
return physicalXPos; |
|
562 |
} |
|
563 |
||
564 |
int DrawDocumentWord(Graphics g, string word, Point position, Font font, Color foreColor, Brush backBrush) |
|
565 |
{ |
|
566 |
if (word == null || word.Length == 0) { |
|
567 |
return 0; |
|
568 |
} |
|
569 |
||
570 |
if (word.Length > MaximumWordLength) { |
|
571 |
int width = 0; |
|
572 |
for (int i = 0; i < word.Length; i += MaximumWordLength) { |
|
573 |
Point pos = position; |
|
574 |
pos.X += width; |
|
575 |
if (i + MaximumWordLength < word.Length) |
|
576 |
width += DrawDocumentWord(g, word.Substring(i, MaximumWordLength), pos, font, foreColor, backBrush); |
|
577 |
else |
|
578 |
width += DrawDocumentWord(g, word.Substring(i, word.Length - i), pos, font, foreColor, backBrush); |
|
579 |
} |
|
580 |
return width; |
|
581 |
} |
|
582 |
||
583 |
int wordWidth = MeasureStringWidth(g, word, font); |
|
584 |
||
585 |
//num = ++num % 3; |
|
586 |
g.FillRectangle(backBrush, //num == 0 ? Brushes.LightBlue : num == 1 ? Brushes.LightGreen : Brushes.Yellow, |
|
587 |
new RectangleF(position.X, position.Y, wordWidth + 1, FontHeight)); |
|
588 |
||
589 |
DrawString(g, |
|
590 |
word, |
|
591 |
font, |
|
592 |
foreColor, |
|
593 |
position.X, |
|
594 |
position.Y); |
|
595 |
return wordWidth; |
|
596 |
} |
|
597 |
||
598 |
struct WordFontPair { |
|
599 |
string word; |
|
600 |
Font font; |
|
601 |
public WordFontPair(string word, Font font) { |
|
602 |
this.word = word; |
|
603 |
this.font = font; |
|
604 |
} |
|
605 |
public override bool Equals(object obj) { |
|
606 |
WordFontPair myWordFontPair = (WordFontPair)obj; |
|
607 |
if (!word.Equals(myWordFontPair.word)) return false; |
|
608 |
return font.Equals(myWordFontPair.font); |
|
609 |
} |
|
610 |
||
611 |
public override int GetHashCode() { |
|
612 |
return word.GetHashCode() ^ font.GetHashCode(); |
|
613 |
} |
|
614 |
} |
|
615 |
||
616 |
Dictionary<WordFontPair, int> measureCache = new Dictionary<WordFontPair, int>(); |
|
617 |
||
618 |
// split words after 1000 characters. Fixes GDI+ crash on very longs words, for example |
|
619 |
// a 100 KB Base64-file without any line breaks. |
|
620 |
const int MaximumWordLength = 1000; |
|
621 |
const int MaximumCacheSize = 2000; |
|
622 |
||
623 |
int MeasureStringWidth(Graphics g, string word, Font font) |
|
624 |
{ |
|
625 |
int width; |
|
626 |
||
627 |
if (word == null || word.Length == 0) |
|
628 |
return 0; |
|
629 |
if (word.Length > MaximumWordLength) { |
|
630 |
width = 0; |
|
631 |
for (int i = 0; i < word.Length; i += MaximumWordLength) { |
|
632 |
if (i + MaximumWordLength < word.Length) |
|
633 |
width += MeasureStringWidth(g, word.Substring(i, MaximumWordLength), font); |
|
634 |
else |
|
635 |
width += MeasureStringWidth(g, word.Substring(i, word.Length - i), font); |
|
636 |
} |
|
637 |
return width; |
|
638 |
} |
|
639 |
if (measureCache.TryGetValue(new WordFontPair(word, font), out width)) { |
|
640 |
return width; |
|
641 |
} |
|
642 |
if (measureCache.Count > MaximumCacheSize) { |
|
643 |
measureCache.Clear(); |
|
644 |
} |
|
645 |
||
646 |
// This code here provides better results than MeasureString! |
|
647 |
// Example line that is measured wrong: |
|
648 |
// txt.GetPositionFromCharIndex(txt.SelectionStart) |
|
649 |
// (Verdana 10, highlighting makes GetP... bold) -> note the space between 'x' and '(' |
|
650 |
// this also fixes "jumping" characters when selecting in non-monospace fonts |
|
651 |
// [...] |
|
652 |
// Replaced GDI+ measurement with GDI measurement: faster and even more exact |
|
653 |
width = TextRenderer.MeasureText(g, word, font, new Size(short.MaxValue, short.MaxValue), textFormatFlags).Width; |
|
654 |
measureCache.Add(new WordFontPair(word, font), width); |
|
655 |
return width; |
|
656 |
} |
|
657 |
||
658 |
// Important: Some flags combinations work on WinXP, but not on Win2000. |
|
659 |
// Make sure to test changes here on all operating systems. |
|
660 |
const TextFormatFlags textFormatFlags = |
|
661 |
TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix | TextFormatFlags.PreserveGraphicsClipping; |
|
662 |
#endregion |
|
663 |
||
664 |
#region Conversion Functions |
|
665 |
Dictionary<Font, Dictionary<char, int>> fontBoundCharWidth = new Dictionary<Font, Dictionary<char, int>>(); |
|
666 |
||
667 |
public int GetWidth(char ch, Font font) |
|
668 |
{ |
|
669 |
if (!fontBoundCharWidth.ContainsKey(font)) { |
|
670 |
fontBoundCharWidth.Add(font, new Dictionary<char, int>()); |
|
671 |
} |
|
672 |
if (!fontBoundCharWidth[font].ContainsKey(ch)) { |
|
673 |
using (Graphics g = textArea.CreateGraphics()) { |
|
674 |
return GetWidth(g, ch, font); |
|
675 |
} |
|
676 |
} |
|
677 |
return fontBoundCharWidth[font][ch]; |
|
678 |
} |
|
679 |
||
680 |
public int GetWidth(Graphics g, char ch, Font font) |
|
681 |
{ |
|
682 |
if (!fontBoundCharWidth.ContainsKey(font)) { |
|
683 |
fontBoundCharWidth.Add(font, new Dictionary<char, int>()); |
|
684 |
} |
|
685 |
if (!fontBoundCharWidth[font].ContainsKey(ch)) { |
|
686 |
//Console.WriteLine("Calculate character width: " + ch); |
|
687 |
fontBoundCharWidth[font].Add(ch, MeasureStringWidth(g, ch.ToString(), font)); |
|
688 |
} |
|
689 |
return fontBoundCharWidth[font][ch]; |
|
690 |
} |
|
691 |
||
692 |
public int GetVisualColumn(int logicalLine, int logicalColumn) |
|
693 |
{ |
|
694 |
int column = 0; |
|
695 |
using (Graphics g = textArea.CreateGraphics()) { |
|
696 |
CountColumns(ref column, 0, logicalColumn, logicalLine, g); |
|
697 |
} |
|
698 |
return column; |
|
699 |
} |
|
700 |
||
701 |
public int GetVisualColumnFast(LineSegment line, int logicalColumn) |
|
702 |
{ |
|
703 |
int lineOffset = line.Offset; |
|
704 |
int tabIndent = Document.TextEditorProperties.TabIndent; |
|
705 |
int guessedColumn = 0; |
|
706 |
for (int i = 0; i < logicalColumn; ++i) { |
|
707 |
char ch; |
|
708 |
if (i >= line.Length) { |
|
709 |
ch = ' '; |
|
710 |
} else { |
|
711 |
ch = Document.GetCharAt(lineOffset + i); |
|
712 |
} |
|
713 |
switch (ch) { |
|
714 |
case '\t': |
|
715 |
guessedColumn += tabIndent; |
|
716 |
guessedColumn = (guessedColumn / tabIndent) * tabIndent; |
|
717 |
break; |
|
718 |
default: |
|
719 |
++guessedColumn; |
|
720 |
break; |
|
721 |
} |
|
722 |
} |
|
723 |
return guessedColumn; |
|
724 |
} |
|
725 |
||
726 |
/// <summary> |
|
727 |
/// returns line/column for a visual point position |
|
728 |
/// </summary> |
|
729 |
public TextLocation GetLogicalPosition(Point mousePosition) |
|
730 |
{ |
|
731 |
FoldMarker dummy; |
|
732 |
return GetLogicalColumn(GetLogicalLine(mousePosition.Y), mousePosition.X, out dummy); |
|
733 |
} |
|
734 |
||
735 |
/// <summary> |
|
736 |
/// returns line/column for a visual point position |
|
737 |
/// </summary> |
|
738 |
public TextLocation GetLogicalPosition(int visualPosX, int visualPosY) |
|
739 |
{ |
|
740 |
FoldMarker dummy; |
|
741 |
return GetLogicalColumn(GetLogicalLine(visualPosY), visualPosX, out dummy); |
|
742 |
} |
|
743 |
||
744 |
/// <summary> |
|
745 |
/// returns line/column for a visual point position |
|
746 |
/// </summary> |
|
747 |
public FoldMarker GetFoldMarkerFromPosition(int visualPosX, int visualPosY) |
|
748 |
{ |
|
749 |
FoldMarker foldMarker; |
|
750 |
GetLogicalColumn(GetLogicalLine(visualPosY), visualPosX, out foldMarker); |
|
751 |
return foldMarker; |
|
752 |
} |
|
753 |
||
754 |
/// <summary> |
|
755 |
/// returns logical line number for a visual point |
|
756 |
/// </summary> |
|
757 |
public int GetLogicalLine(int visualPosY) |
|
758 |
{ |
|
759 |
int clickedVisualLine = Math.Max(0, (visualPosY + this.textArea.VirtualTop.Y) / fontHeight); |
|
760 |
return Document.GetFirstLogicalLine(clickedVisualLine); |
|
761 |
} |
|
762 |
||
763 |
internal TextLocation GetLogicalColumn(int lineNumber, int visualPosX, out FoldMarker inFoldMarker) |
|
764 |
{ |
|
765 |
visualPosX += textArea.VirtualTop.X; |
|
766 |
||
767 |
inFoldMarker = null; |
|
768 |
if (lineNumber >= Document.TotalNumberOfLines) { |
|
769 |
return new TextLocation((int)(visualPosX / WideSpaceWidth), lineNumber); |
|
770 |
} |
|
771 |
if (visualPosX <= 0) { |
|
772 |
return new TextLocation(0, lineNumber); |
|
773 |
} |
|
774 |
||
775 |
int start = 0; // column |
|
776 |
int posX = 0; // visual position |
|
777 |
||
778 |
int result; |
|
779 |
using (Graphics g = textArea.CreateGraphics()) { |
|
780 |
do { |
|
781 |
LineSegment line = Document.GetLineSegment(lineNumber); |
|
782 |
FoldMarker nextFolding = FindNextFoldedFoldingOnLineAfterColumn(lineNumber, start-1); |
|
783 |
int end = nextFolding != null ? nextFolding.StartColumn : int.MaxValue; |
|
784 |
result = GetLogicalColumnInternal(g, line, start, end, ref posX, visualPosX); |
|
785 |
if (result < 0) { |
|
786 |
// reached fold marker |
|
787 |
lineNumber = nextFolding.EndLine; |
|
788 |
start = nextFolding.EndColumn; |
|
789 |
int newPosX = posX + 1 + MeasureStringWidth(g, nextFolding.FoldText, TextEditorProperties.FontContainer.RegularFont); |
|
790 |
if (newPosX >= visualPosX) { |
|
791 |
inFoldMarker = nextFolding; |
|
792 |
if (IsNearerToAThanB(visualPosX, posX, newPosX)) |
|
793 |
return new TextLocation(nextFolding.StartColumn, nextFolding.StartLine); |
|
794 |
else |
|
795 |
return new TextLocation(nextFolding.EndColumn, nextFolding.EndLine); |
|
796 |
} |
|
797 |
posX = newPosX; |
|
798 |
} |
|
799 |
} while (result < 0); |
|
800 |
} |
|
801 |
return new TextLocation(result, lineNumber); |
|
802 |
} |
|
803 |
||
804 |
int GetLogicalColumnInternal(Graphics g, LineSegment line, int start, int end, ref int drawingPos, int targetVisualPosX) |
|
805 |
{ |
|
806 |
if (start == end) |
|
807 |
return -1; |
|
808 |
Debug.Assert(start < end); |
|
809 |
Debug.Assert(drawingPos < targetVisualPosX); |
|
810 |
||
811 |
int tabIndent = Document.TextEditorProperties.TabIndent; |
|
812 |
||
813 |
/*float spaceWidth = SpaceWidth; |
|
814 |
float drawingPos = 0; |
|
815 |
LineSegment currentLine = Document.GetLineSegment(logicalLine); |
|
816 |
List<TextWord> words = currentLine.Words; |
|
817 |
if (words == null) return 0; |
|
818 |
int wordCount = words.Count; |
|
819 |
int wordOffset = 0; |
|
820 |
FontContainer fontContainer = TextEditorProperties.FontContainer; |
|
821 |
*/ |
|
822 |
FontContainer fontContainer = TextEditorProperties.FontContainer; |
|
823 |
||
824 |
List<TextWord> words = line.Words; |
|
825 |
if (words == null) return 0; |
|
826 |
int wordOffset = 0; |
|
827 |
for (int i = 0; i < words.Count; i++) { |
|
828 |
TextWord word = words[i]; |
|
829 |
if (wordOffset >= end) { |
|
830 |
return -1; |
|
831 |
} |
|
832 |
if (wordOffset + word.Length >= start) { |
|
833 |
int newDrawingPos; |
|
834 |
switch (word.Type) { |
|
835 |
case TextWordType.Space: |
|
836 |
newDrawingPos = drawingPos + spaceWidth; |
|
837 |
if (newDrawingPos >= targetVisualPosX) |
|
838 |
return IsNearerToAThanB(targetVisualPosX, drawingPos, newDrawingPos) ? wordOffset : wordOffset+1; |
|
839 |
break; |
|
840 |
case TextWordType.Tab: |
|
841 |
// go to next tab position |
|
842 |
drawingPos = (int)((drawingPos + MinTabWidth) / tabIndent / WideSpaceWidth) * tabIndent * WideSpaceWidth; |
|
843 |
newDrawingPos = drawingPos + tabIndent * WideSpaceWidth; |
|
844 |
if (newDrawingPos >= targetVisualPosX) |
|
845 |
return IsNearerToAThanB(targetVisualPosX, drawingPos, newDrawingPos) ? wordOffset : wordOffset+1; |
|
846 |
break; |
|
847 |
case TextWordType.Word: |
|
848 |
int wordStart = Math.Max(wordOffset, start); |
|
849 |
int wordLength = Math.Min(wordOffset + word.Length, end) - wordStart; |
|
850 |
string text = Document.GetText(line.Offset + wordStart, wordLength); |
|
851 |
Font font = word.GetFont(fontContainer) ?? fontContainer.RegularFont; |
|
852 |
newDrawingPos = drawingPos + MeasureStringWidth(g, text, font); |
|
853 |
if (newDrawingPos >= targetVisualPosX) { |
|
854 |
for (int j = 0; j < text.Length; j++) { |
|
855 |
newDrawingPos = drawingPos + MeasureStringWidth(g, text[j].ToString(), font); |
|
856 |
if (newDrawingPos >= targetVisualPosX) { |
|
857 |
if (IsNearerToAThanB(targetVisualPosX, drawingPos, newDrawingPos)) |
|
858 |
return wordStart + j; |
|
859 |
else |
|
860 |
return wordStart + j + 1; |
|
861 |
} |
|
862 |
drawingPos = newDrawingPos; |
|
863 |
} |
|
864 |
return wordStart + text.Length; |
|
865 |
} |
|
866 |
break; |
|
867 |
default: |
|
868 |
throw new NotSupportedException(); |
|
869 |
} |
|
870 |
drawingPos = newDrawingPos; |
|
871 |
} |
|
872 |
wordOffset += word.Length; |
|
873 |
} |
|
874 |
return wordOffset; |
|
875 |
} |
|
876 |
||
877 |
static bool IsNearerToAThanB(int num, int a, int b) |
|
878 |
{ |
|
879 |
return Math.Abs(a - num) < Math.Abs(b - num); |
|
880 |
} |
|
881 |
||
882 |
FoldMarker FindNextFoldedFoldingOnLineAfterColumn(int lineNumber, int column) |
|
883 |
{ |
|
884 |
List<FoldMarker> list = Document.FoldingManager.GetFoldedFoldingsWithStartAfterColumn(lineNumber, column); |
|
885 |
if (list.Count != 0) |
|
886 |
return list[0]; |
|
887 |
else |
|
888 |
return null; |
|
889 |
} |
|
890 |
||
891 |
const int MinTabWidth = 4; |
|
892 |
||
893 |
float CountColumns(ref int column, int start, int end, int logicalLine, Graphics g) |
|
894 |
{ |
|
895 |
if (start > end) throw new ArgumentException("start > end"); |
|
896 |
if (start == end) return 0; |
|
897 |
float spaceWidth = SpaceWidth; |
|
898 |
float drawingPos = 0; |
|
899 |
int tabIndent = Document.TextEditorProperties.TabIndent; |
|
900 |
LineSegment currentLine = Document.GetLineSegment(logicalLine); |
|
901 |
List<TextWord> words = currentLine.Words; |
|
902 |
if (words == null) return 0; |
|
903 |
int wordCount = words.Count; |
|
904 |
int wordOffset = 0; |
|
905 |
FontContainer fontContainer = TextEditorProperties.FontContainer; |
|
906 |
for (int i = 0; i < wordCount; i++) { |
|
907 |
TextWord word = words[i]; |
|
908 |
if (wordOffset >= end) |
|
909 |
break; |
|
910 |
if (wordOffset + word.Length >= start) { |
|
911 |
switch (word.Type) { |
|
912 |
case TextWordType.Space: |
|
913 |
drawingPos += spaceWidth; |
|
914 |
break; |
|
915 |
case TextWordType.Tab: |
|
916 |
// go to next tab position |
|
917 |
drawingPos = (int)((drawingPos + MinTabWidth) / tabIndent / WideSpaceWidth) * tabIndent * WideSpaceWidth; |
|
918 |
drawingPos += tabIndent * WideSpaceWidth; |
|
919 |
break; |
|
920 |
case TextWordType.Word: |
|
921 |
int wordStart = Math.Max(wordOffset, start); |
|
922 |
int wordLength = Math.Min(wordOffset + word.Length, end) - wordStart; |
|
923 |
string text = Document.GetText(currentLine.Offset + wordStart, wordLength); |
|
924 |
drawingPos += MeasureStringWidth(g, text, word.GetFont(fontContainer) ?? fontContainer.RegularFont); |
|
925 |
break; |
|
926 |
} |
|
927 |
} |
|
928 |
wordOffset += word.Length; |
|
929 |
} |
|
930 |
for (int j = currentLine.Length; j < end; j++) { |
|
931 |
drawingPos += WideSpaceWidth; |
|
932 |
} |
|
933 |
// add one pixel in column calculation to account for floating point calculation errors |
|
934 |
column += (int)((drawingPos + 1) / WideSpaceWidth); |
|
935 |
||
936 |
/* OLD Code (does not work for fonts like Verdana) |
|
937 |
for (int j = start; j < end; ++j) { |
|
938 |
char ch; |
|
939 |
if (j >= line.Length) { |
|
940 |
ch = ' '; |
|
941 |
} else { |
|
942 |
ch = Document.GetCharAt(line.Offset + j); |
|
943 |
} |
|
944 |
||
945 |
switch (ch) { |
|
946 |
case '\t': |
|
947 |
int oldColumn = column; |
|
948 |
column += tabIndent; |
|
949 |
column = (column / tabIndent) * tabIndent; |
|
950 |
drawingPos += (column - oldColumn) * spaceWidth; |
|
951 |
break; |
|
952 |
default: |
|
953 |
++column; |
|
954 |
TextWord word = line.GetWord(j); |
|
955 |
if (word == null || word.Font == null) { |
|
956 |
drawingPos += GetWidth(ch, TextEditorProperties.Font); |
|
957 |
} else { |
|
958 |
drawingPos += GetWidth(ch, word.Font); |
|
959 |
} |
|
960 |
break; |
|
961 |
} |
|
962 |
} |
|
963 |
//*/ |
|
964 |
return drawingPos; |
|
965 |
} |
|
966 |
||
967 |
public int GetDrawingXPos(int logicalLine, int logicalColumn) |
|
968 |
{ |
|
969 |
List<FoldMarker> foldings = Document.FoldingManager.GetTopLevelFoldedFoldings(); |
|
970 |
int i; |
|
971 |
FoldMarker f = null; |
|
972 |
// search the last folding that's interresting |
|
973 |
for (i = foldings.Count - 1; i >= 0; --i) { |
|
974 |
f = foldings[i]; |
|
975 |
if (f.StartLine < logicalLine || f.StartLine == logicalLine && f.StartColumn < logicalColumn) { |
|
976 |
break; |
|
977 |
} |
|
978 |
FoldMarker f2 = foldings[i / 2]; |
|
979 |
if (f2.StartLine > logicalLine || f2.StartLine == logicalLine && f2.StartColumn >= logicalColumn) { |
|
980 |
i /= 2; |
|
981 |
} |
|
982 |
} |
|
983 |
int lastFolding = 0; |
|
984 |
int firstFolding = 0; |
|
985 |
int column = 0; |
|
986 |
int tabIndent = Document.TextEditorProperties.TabIndent; |
|
987 |
float drawingPos; |
|
988 |
Graphics g = textArea.CreateGraphics(); |
|
989 |
// if no folding is interresting |
|
990 |
if (f == null || !(f.StartLine < logicalLine || f.StartLine == logicalLine && f.StartColumn < logicalColumn)) { |
|
991 |
drawingPos = CountColumns(ref column, 0, logicalColumn, logicalLine, g); |
|
992 |
return (int)(drawingPos - textArea.VirtualTop.X); |
|
993 |
} |
|
994 |
||
995 |
// if logicalLine/logicalColumn is in folding |
|
996 |
if (f.EndLine > logicalLine || f.EndLine == logicalLine && f.EndColumn > logicalColumn) { |
|
997 |
logicalColumn = f.StartColumn; |
|
998 |
logicalLine = f.StartLine; |
|
999 |
--i; |
|
1000 |
} |
|
1001 |
lastFolding = i; |
|
1002 |
||
1003 |
// search backwards until a new visible line is reched |
|
1004 |
for (; i >= 0; --i) { |
|
1005 |
f = (FoldMarker)foldings[i]; |
|
1006 |
if (f.EndLine < logicalLine) { // reached the begin of a new visible line |
|
1007 |
break; |
|
1008 |
} |
|
1009 |
} |
|
1010 |
firstFolding = i + 1; |
|
1011 |
||
1012 |
if (lastFolding < firstFolding) { |
|
1013 |
drawingPos = CountColumns(ref column, 0, logicalColumn, logicalLine, g); |
|
1014 |
return (int)(drawingPos - textArea.VirtualTop.X); |
|
1015 |
} |
|
1016 |
||
1017 |
int foldEnd = 0; |
|
1018 |
drawingPos = 0; |
|
1019 |
for (i = firstFolding; i <= lastFolding; ++i) { |
|
1020 |
f = foldings[i]; |
|
1021 |
drawingPos += CountColumns(ref column, foldEnd, f.StartColumn, f.StartLine, g); |
|
1022 |
foldEnd = f.EndColumn; |
|
1023 |
column += f.FoldText.Length; |
|
1024 |
drawingPos += additionalFoldTextSize; |
|
1025 |
drawingPos += MeasureStringWidth(g, f.FoldText, TextEditorProperties.FontContainer.RegularFont); |
|
1026 |
} |
|
1027 |
drawingPos += CountColumns(ref column, foldEnd, logicalColumn, logicalLine, g); |
|
1028 |
g.Dispose(); |
|
1029 |
return (int)(drawingPos - textArea.VirtualTop.X); |
|
1030 |
} |
|
1031 |
#endregion |
|
1032 |
||
1033 |
#region DrawHelper functions |
|
1034 |
void DrawBracketHighlight(Graphics g, Rectangle rect) |
|
1035 |
{ |
|
1036 |
g.FillRectangle(BrushRegistry.GetBrush(Color.FromArgb(50, 0, 0, 255)), rect); |
|
1037 |
g.DrawRectangle(Pens.Blue, rect); |
|
1038 |
} |
|
1039 |
||
1040 |
void DrawString(Graphics g, string text, Font font, Color color, int x, int y) |
|
1041 |
{ |
|
1042 |
TextRenderer.DrawText(g, text, font, new Point(x, y), color, textFormatFlags); |
|
1043 |
} |
|
1044 |
||
1045 |
void DrawInvalidLineMarker(Graphics g, int x, int y) |
|
1046 |
{ |
|
1047 |
HighlightColor invalidLinesColor = textArea.Document.HighlightingStrategy.GetColorFor("InvalidLines"); |
|
1048 |
DrawString(g, "~", invalidLinesColor.GetFont(TextEditorProperties.FontContainer), invalidLinesColor.Color, x, y); |
|
1049 |
} |
|
1050 |
||
1051 |
void DrawSpaceMarker(Graphics g, Color color, int x, int y) |
|
1052 |
{ |
|
1053 |
HighlightColor spaceMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("SpaceMarkers"); |
|
1054 |
DrawString(g, "\u00B7", spaceMarkerColor.GetFont(TextEditorProperties.FontContainer), color, x, y); |
|
1055 |
} |
|
1056 |
||
1057 |
void DrawTabMarker(Graphics g, Color color, int x, int y) |
|
1058 |
{ |
|
1059 |
HighlightColor tabMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("TabMarkers"); |
|
1060 |
DrawString(g, "\u00BB", tabMarkerColor.GetFont(TextEditorProperties.FontContainer), color, x, y); |
|
1061 |
} |
|
1062 |
||
1063 |
int DrawEOLMarker(Graphics g, Color color, Brush backBrush, int x, int y) |
|
1064 |
{ |
|
1065 |
HighlightColor eolMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("EOLMarkers"); |
|
1066 |
||
1067 |
int width = GetWidth('\u00B6', eolMarkerColor.GetFont(TextEditorProperties.FontContainer)); |
|
1068 |
g.FillRectangle(backBrush, |
|
1069 |
new RectangleF(x, y, width, fontHeight)); |
|
1070 |
||
1071 |
DrawString(g, "\u00B6", eolMarkerColor.GetFont(TextEditorProperties.FontContainer), color, x, y); |
|
1072 |
return width; |
|
1073 |
} |
|
1074 |
||
1075 |
void DrawVerticalRuler(Graphics g, Rectangle lineRectangle) |
|
1076 |
{ |
|
1077 |
int xpos = WideSpaceWidth * TextEditorProperties.VerticalRulerRow - textArea.VirtualTop.X; |
|
1078 |
if (xpos <= 0) { |
|
1079 |
return; |
|
1080 |
} |
|
1081 |
HighlightColor vRulerColor = textArea.Document.HighlightingStrategy.GetColorFor("VRuler"); |
|
1082 |
||
1083 |
g.DrawLine(BrushRegistry.GetPen(vRulerColor.Color), |
|
1084 |
drawingPosition.Left + xpos, |
|
1085 |
lineRectangle.Top, |
|
1086 |
drawingPosition.Left + xpos, |
|
1087 |
lineRectangle.Bottom); |
|
1088 |
} |
|
1089 |
#endregion |
|
1090 |
} |
|
1091 |
} |