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
}