/XPathVisualizer/CustomControls/RichTextBoxEx.cs

# · C# · 856 lines · 595 code · 127 blank · 134 comment · 70 complexity · 5a725bd477413e1de08e8bdfb729342c MD5 · raw file

  1. // RichTextBoxEx.cs
  2. // ------------------------------------------------------------------
  3. //
  4. // An extended RichTextBox that provides a few extra capabilities:
  5. //
  6. // 1. line numbering, fast and easy. It numbers the lines "as
  7. // displayed" or according to the hard newlines in the text. The UI
  8. // of the line numbers is configurable: color, font, width, leading
  9. // zeros or not, etc. One limitation: the line #'s are always
  10. // displayed to the left.
  11. //
  12. // 2. Programmatic scrolling
  13. //
  14. // 3. BeginUpdate/EndUpdate and other bells and whistles. Theres also
  15. // BeginUpdateAndSateState()/EndUpdateAndRestoreState(), to keep the
  16. // cursor in place across select/updates.
  17. //
  18. // 4. properties: FirstVisibleLine / NumberOfVisibleLines - in support of
  19. // line numbering.
  20. //
  21. //
  22. // Copyright (c) 2010 Dino Chiesa.
  23. // All rights reserved.
  24. //
  25. // This file is part of the source code disribution for Ionic's
  26. // XPath Visualizer Tool.
  27. //
  28. // ------------------------------------------------------------------
  29. //
  30. // This code is licensed under the Microsoft Public License.
  31. // See the file License.rtf or License.txt for the license details.
  32. // More info on: http://XPathVisualizer.codeplex.com
  33. //
  34. // ------------------------------------------------------------------
  35. //
  36. using System;
  37. using System.Collections.Generic;
  38. using System.ComponentModel;
  39. using System.Windows.Forms;
  40. using System.Runtime.InteropServices;
  41. using System.Drawing;
  42. using System.Drawing.Drawing2D;
  43. namespace Ionic.WinForms
  44. {
  45. /// <summary>
  46. /// Defines methods for performing operations on RichTextBox.
  47. /// </summary>
  48. ///
  49. /// <remarks>
  50. /// <para>
  51. /// The methods in this class could be defined as "extension methods" but
  52. /// for efficiency I'd like to retain some state between calls - for
  53. /// example the handle on the richtextbox or the buffer and structure for
  54. /// the EM_SETCHARFORMAT message, which can be called many times in quick
  55. /// succession.
  56. /// </para>
  57. ///
  58. /// <para>
  59. /// We define these in a separate class for speed and efficiency. For the
  60. /// RichTextBox, in order to make a change in format of some portion of
  61. /// the text, the app must select the text. When the RTB has focus, it
  62. /// will scroll when the selection is updated. If we want to retain state
  63. /// while highlighting text then, we'll have to restore the scroll state
  64. /// after a highlight is applied. But this will produce an ugly UI effect
  65. /// where the scroll jumps forward and back repeatedly. To avoid that, we
  66. /// need to suppress updates to the RTB, using the WM_SETREDRAW message.
  67. /// </para>
  68. ///
  69. /// <para>
  70. /// As a complement to that, we also have some speedy methods to get and
  71. /// set the scroll state, and the selection state.
  72. /// </para>
  73. ///
  74. /// </remarks>
  75. [ToolboxBitmap(typeof(RichTextBox))]
  76. public class RichTextBoxEx : RichTextBox
  77. {
  78. private User32.CHARFORMAT charFormat;
  79. private IntPtr lParam1;
  80. private int _savedScrollLine;
  81. private int _savedSelectionStart;
  82. private int _savedSelectionEnd;
  83. private Pen _borderPen;
  84. private System.Drawing.StringFormat _stringDrawingFormat;
  85. private System.Security.Cryptography.HashAlgorithm alg; // used for comparing text values
  86. public RichTextBoxEx()
  87. {
  88. charFormat = new User32.CHARFORMAT()
  89. {
  90. cbSize = Marshal.SizeOf(typeof(User32.CHARFORMAT)),
  91. szFaceName= new char[32]
  92. };
  93. lParam1= Marshal.AllocCoTaskMem( charFormat.cbSize );
  94. // defaults
  95. NumberFont= new System.Drawing.Font("Consolas",
  96. 9.75F,
  97. System.Drawing.FontStyle.Regular,
  98. System.Drawing.GraphicsUnit.Point, ((byte)(0)));
  99. NumberColor = Color.FromName("DarkGray");
  100. NumberLineCounting = LineCounting.CRLF;
  101. NumberAlignment = StringAlignment.Center;
  102. NumberBorder = SystemColors.ControlDark;
  103. NumberBorderThickness = 1;
  104. NumberPadding = 2;
  105. NumberBackground1 = SystemColors.ControlLight;
  106. NumberBackground2= SystemColors.Window;
  107. SetStringDrawingFormat();
  108. alg = System.Security.Cryptography.SHA1.Create();
  109. }
  110. ~RichTextBoxEx()
  111. {
  112. // Free the allocated memory
  113. Marshal.FreeCoTaskMem(lParam1);
  114. }
  115. private void SetStringDrawingFormat()
  116. {
  117. _stringDrawingFormat = new System.Drawing.StringFormat
  118. {
  119. Alignment = StringAlignment.Center,
  120. LineAlignment = NumberAlignment,
  121. Trimming = StringTrimming.None,
  122. };
  123. }
  124. protected override void OnTextChanged(EventArgs e)
  125. {
  126. NeedRecomputeOfLineNumbers();
  127. base.OnTextChanged(e);
  128. }
  129. public void BeginUpdate()
  130. {
  131. User32.SendMessage(this.Handle, (int)User32.Msgs.WM_SETREDRAW, 0, IntPtr.Zero);
  132. }
  133. public void EndUpdate()
  134. {
  135. User32.SendMessage(this.Handle, (int)User32.Msgs.WM_SETREDRAW, 1, IntPtr.Zero);
  136. }
  137. public IntPtr BeginUpdateAndSuspendEvents()
  138. {
  139. // Stop redrawing:
  140. User32.SendMessage(this.Handle, (int) User32.Msgs.WM_SETREDRAW, 0, IntPtr.Zero);
  141. // Stop sending of events:
  142. IntPtr eventMask = User32.SendMessage(this.Handle, User32.Msgs.EM_GETEVENTMASK, 0, IntPtr.Zero);
  143. return eventMask;
  144. }
  145. public void EndUpdateAndResumeEvents(IntPtr eventMask)
  146. {
  147. // turn on events
  148. User32.SendMessage(this.Handle, User32.Msgs.EM_SETEVENTMASK, 0, eventMask);
  149. // turn on redrawing
  150. User32.SendMessage(this.Handle, User32.Msgs.WM_SETREDRAW, 1, IntPtr.Zero);
  151. NeedRecomputeOfLineNumbers();
  152. this.Invalidate();
  153. }
  154. public void GetSelection(out int start, out int end)
  155. {
  156. User32.SendMessageRef(this.Handle, (int)User32.Msgs.EM_GETSEL, out start, out end);
  157. }
  158. public void SetSelection(int start, int end)
  159. {
  160. User32.SendMessage(this.Handle, (int)User32.Msgs.EM_SETSEL, start, end);
  161. }
  162. public void BeginUpdateAndSaveState()
  163. {
  164. User32.SendMessage(this.Handle, (int)User32.Msgs.WM_SETREDRAW, 0, IntPtr.Zero);
  165. // save scroll position
  166. _savedScrollLine = FirstVisibleDisplayLine;
  167. // save selection
  168. GetSelection(out _savedSelectionStart, out _savedSelectionEnd);
  169. }
  170. public void EndUpdateAndRestoreState()
  171. {
  172. // restore scroll position
  173. int Line1 = FirstVisibleDisplayLine;
  174. Scroll(_savedScrollLine - Line1);
  175. // restore the selection/caret
  176. SetSelection(_savedSelectionStart, _savedSelectionEnd);
  177. // allow redraw
  178. User32.SendMessage(this.Handle, (int)User32.Msgs.WM_SETREDRAW, 1, IntPtr.Zero);
  179. // explicitly ask for a redraw?
  180. Refresh();
  181. }
  182. private String _sformat;
  183. private int _ndigits;
  184. private int _lnw = -1;
  185. private int LineNumberWidth
  186. {
  187. get
  188. {
  189. if (_lnw > 0) return _lnw;
  190. if (NumberLineCounting == LineCounting.CRLF)
  191. {
  192. _ndigits = (CharIndexForTextLine.Length == 0)
  193. ? 1
  194. : (int)(1 + Math.Log((double)CharIndexForTextLine.Length, 10));
  195. }
  196. else
  197. {
  198. int n = GetDisplayLineCount();
  199. _ndigits = (n == 0)
  200. ? 1
  201. : (int)(1 + Math.Log((double)n, 10));
  202. }
  203. var s = new String('0', _ndigits);
  204. var b = new Bitmap(400,400); // in pixels
  205. var g = Graphics.FromImage(b);
  206. SizeF size = g.MeasureString(s, NumberFont);
  207. g.Dispose();
  208. _lnw = NumberPadding * 2 + 4 + (int) (size.Width + 0.5 + NumberBorderThickness);
  209. _sformat = "{0:D" + _ndigits + "}";
  210. return _lnw;
  211. }
  212. }
  213. public bool _lineNumbers;
  214. public bool ShowLineNumbers
  215. {
  216. get
  217. {
  218. return _lineNumbers;
  219. }
  220. set
  221. {
  222. if (value == _lineNumbers) return;
  223. SetLeftMargin(value ? LineNumberWidth + Margin.Left : Margin.Left);
  224. _lineNumbers = value;
  225. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  226. }
  227. }
  228. private void NeedRecomputeOfLineNumbers()
  229. {
  230. //System.Console.WriteLine("Need Recompute of line numbers...");
  231. _CharIndexForTextLine = null;
  232. _Text2 = null;
  233. _lnw = -1;
  234. if (_paintingDisabled) return;
  235. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  236. }
  237. private Font _NumberFont;
  238. public Font NumberFont
  239. {
  240. get { return _NumberFont; }
  241. set
  242. {
  243. if (_NumberFont == value) return;
  244. _lnw = -1;
  245. _NumberFont = value;
  246. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  247. }
  248. }
  249. private LineCounting _NumberLineCounting;
  250. public LineCounting NumberLineCounting
  251. {
  252. get { return _NumberLineCounting; }
  253. set
  254. {
  255. if (_NumberLineCounting == value) return;
  256. _lnw = -1;
  257. _NumberLineCounting = value;
  258. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  259. }
  260. }
  261. private StringAlignment _NumberAlignment;
  262. public StringAlignment NumberAlignment
  263. {
  264. get { return _NumberAlignment; }
  265. set
  266. {
  267. if (_NumberAlignment == value) return;
  268. _NumberAlignment = value;
  269. SetStringDrawingFormat();
  270. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  271. }
  272. }
  273. private Color _NumberColor;
  274. public Color NumberColor
  275. {
  276. get { return _NumberColor; }
  277. set
  278. {
  279. if (_NumberColor.ToArgb() == value.ToArgb()) return;
  280. _NumberColor = value;
  281. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  282. }
  283. }
  284. private bool _NumberLeadingZeroes;
  285. public bool NumberLeadingZeroes
  286. {
  287. get { return _NumberLeadingZeroes; }
  288. set
  289. {
  290. if (_NumberLeadingZeroes == value) return;
  291. _NumberLeadingZeroes = value;
  292. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  293. }
  294. }
  295. private Color _NumberBorder;
  296. public Color NumberBorder
  297. {
  298. get { return _NumberBorder; }
  299. set
  300. {
  301. if (_NumberBorder.ToArgb() == value.ToArgb()) return;
  302. _NumberBorder = value;
  303. NewBorderPen();
  304. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  305. }
  306. }
  307. private int _NumberPadding;
  308. public int NumberPadding
  309. {
  310. get { return _NumberPadding; }
  311. set
  312. {
  313. if (_NumberPadding == value) return;
  314. _lnw = -1;
  315. _NumberPadding = value;
  316. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  317. }
  318. }
  319. public Single _NumberBorderThickness;
  320. public Single NumberBorderThickness
  321. {
  322. get { return _NumberBorderThickness; }
  323. set
  324. {
  325. if (_NumberBorderThickness == value) return;
  326. _lnw = -1;
  327. _NumberBorderThickness = value;
  328. NewBorderPen();
  329. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  330. }
  331. }
  332. private Color _NumberBackground1;
  333. public Color NumberBackground1
  334. {
  335. get { return _NumberBackground1; }
  336. set
  337. {
  338. if (_NumberBackground1.ToArgb() == value.ToArgb()) return;
  339. _NumberBackground1 = value;
  340. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  341. }
  342. }
  343. private Color _NumberBackground2;
  344. public Color NumberBackground2
  345. {
  346. get { return _NumberBackground2; }
  347. set
  348. {
  349. if (_NumberBackground2.ToArgb() == value.ToArgb()) return;
  350. _NumberBackground2 = value;
  351. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  352. }
  353. }
  354. private bool _paintingDisabled;
  355. public void SuspendLineNumberPainting()
  356. {
  357. _paintingDisabled = true;
  358. }
  359. public void ResumeLineNumberPainting()
  360. {
  361. _paintingDisabled = false;
  362. }
  363. private void NewBorderPen()
  364. {
  365. _borderPen = new Pen(NumberBorder);
  366. _borderPen.Width = NumberBorderThickness;
  367. _borderPen.SetLineCap(LineCap.Round, LineCap.Round, DashCap.Round);
  368. }
  369. private DateTime _lastMsgRecd = new DateTime(1901,1,1);
  370. protected override void WndProc(ref Message m)
  371. {
  372. bool handled = false;
  373. switch (m.Msg)
  374. {
  375. case (int)User32.Msgs.WM_PAINT:
  376. //System.Console.WriteLine("{0}", User32.Mnemonic(m.Msg));
  377. //System.Console.Write(".");
  378. if (_paintingDisabled) return;
  379. if (_lineNumbers)
  380. {
  381. base.WndProc(ref m);
  382. this.PaintLineNumbers();
  383. handled = true;
  384. }
  385. break;
  386. case (int)User32.Msgs.WM_CHAR:
  387. // the text is being modified
  388. NeedRecomputeOfLineNumbers();
  389. break;
  390. // case (int)User32.Msgs.EM_POSFROMCHAR:
  391. // case (int)User32.Msgs.WM_GETDLGCODE:
  392. // case (int)User32.Msgs.WM_ERASEBKGND:
  393. // case (int)User32.Msgs.OCM_COMMAND:
  394. // case (int)User32.Msgs.OCM_NOTIFY:
  395. // case (int)User32.Msgs.EM_CHARFROMPOS:
  396. // case (int)User32.Msgs.EM_LINEINDEX:
  397. // case (int)User32.Msgs.WM_NCHITTEST:
  398. // case (int)User32.Msgs.WM_SETCURSOR:
  399. // case (int)User32.Msgs.WM_KEYUP:
  400. // case (int)User32.Msgs.WM_KEYDOWN:
  401. // case (int)User32.Msgs.WM_MOUSEMOVE:
  402. // case (int)User32.Msgs.WM_MOUSEACTIVATE:
  403. // case (int)User32.Msgs.WM_NCMOUSEMOVE:
  404. // case (int)User32.Msgs.WM_NCMOUSEHOVER:
  405. // case (int)User32.Msgs.WM_NCMOUSELEAVE:
  406. // case (int)User32.Msgs.WM_NCLBUTTONDOWN:
  407. // break;
  408. //
  409. // default:
  410. // // divider
  411. // var now = DateTime.Now;
  412. // if ((now - _lastMsgRecd) > TimeSpan.FromMilliseconds(850))
  413. // System.Console.WriteLine("------------ {0}", now.ToString("G"));
  414. // _lastMsgRecd = now;
  415. //
  416. // System.Console.WriteLine("{0}", User32.Mnemonic(m.Msg));
  417. // break;
  418. }
  419. if (!handled)
  420. base.WndProc(ref m);
  421. }
  422. int _lastWidth = 0;
  423. private void PaintLineNumbers()
  424. {
  425. //System.Console.WriteLine(">> PaintLineNumbers");
  426. // To reduce flicker, double-buffer the output
  427. if (_paintingDisabled) return;
  428. int w = LineNumberWidth;
  429. if (w!=_lastWidth)
  430. {
  431. //System.Console.WriteLine(" WIDTH change {0} != {1}", _lastWidth, w);
  432. SetLeftMargin(w + Margin.Left);
  433. _lastWidth = w;
  434. // Don't bother painting line numbers - the margin isn't wide enough currently.
  435. // Ask for a new paint, and paint them next time round.
  436. User32.SendMessage(this.Handle, User32.Msgs.WM_PAINT, 0, 0);
  437. return;
  438. }
  439. Bitmap buffer = new Bitmap(w, this.Bounds.Height);
  440. Graphics g = Graphics.FromImage(buffer);
  441. Brush forebrush = new SolidBrush(NumberColor);
  442. var rect = new Rectangle (0, 0, w, this.Bounds.Height);
  443. bool wantDivider = NumberBackground1.ToArgb() == NumberBackground2.ToArgb();
  444. Brush backBrush = (wantDivider)
  445. ? (Brush) new SolidBrush(NumberBackground2)
  446. : SystemBrushes.Window;
  447. g.FillRectangle(backBrush, rect);
  448. int n = (NumberLineCounting == LineCounting.CRLF)
  449. ? NumberOfVisibleTextLines
  450. : NumberOfVisibleDisplayLines;
  451. int first = (NumberLineCounting == LineCounting.CRLF)
  452. ? FirstVisibleTextLine
  453. : FirstVisibleDisplayLine+1;
  454. int py = 0;
  455. int w2 = w - 2 - (int)NumberBorderThickness;
  456. LinearGradientBrush brush;
  457. Pen dividerPen = new Pen(NumberColor);
  458. for (int i=0; i <= n; i++)
  459. {
  460. int ix = first + i;
  461. int c = (NumberLineCounting == LineCounting.CRLF)
  462. ? GetCharIndexForTextLine(ix)
  463. : GetCharIndexForDisplayLine(ix)-1;
  464. var p = GetPosFromCharIndex(c+1);
  465. Rectangle r4 = Rectangle.Empty;
  466. if (i==n) // last line?
  467. {
  468. if (this.Bounds.Height <= py) continue;
  469. r4 = new Rectangle (1, py, w2, this.Bounds.Height-py);
  470. }
  471. else
  472. {
  473. if (p.Y <= py) continue;
  474. r4 = new Rectangle (1, py, w2, p.Y-py);
  475. }
  476. if (wantDivider)
  477. {
  478. if (i!=n)
  479. g.DrawLine(dividerPen, 1, p.Y+1, w2, p.Y+1); // divider line
  480. }
  481. else
  482. {
  483. // new brush each time for gradient across variable rect sizes
  484. brush = new LinearGradientBrush( r4,
  485. NumberBackground1,
  486. NumberBackground2,
  487. LinearGradientMode.Vertical);
  488. g.FillRectangle(brush, r4);
  489. }
  490. if (NumberLineCounting == LineCounting.CRLF) ix++;
  491. // conditionally slide down
  492. if (NumberAlignment == StringAlignment.Near)
  493. rect.Offset(0, 3);
  494. var s = (NumberLeadingZeroes) ? String.Format(_sformat, ix) : ix.ToString();
  495. g.DrawString(s, NumberFont, forebrush, r4, _stringDrawingFormat);
  496. py = p.Y;
  497. }
  498. if (NumberBorderThickness != 0.0)
  499. {
  500. int t = (int)(w-(NumberBorderThickness+0.5)/2) - 1;
  501. g.DrawLine(_borderPen, t, 0, t, this.Bounds.Height);
  502. //g.DrawLine(_borderPen, w-2, 0, w-2, this.Bounds.Height);
  503. }
  504. // paint that buffer to the screen
  505. Graphics g1 = this.CreateGraphics();
  506. g1.DrawImage(buffer, new Point(0,0));
  507. g1.Dispose();
  508. g.Dispose();
  509. }
  510. private int GetCharIndexFromPos(int x, int y)
  511. {
  512. var p = new User32.POINTL { X= x, Y = y };
  513. int rawSize = Marshal.SizeOf( typeof(User32.POINTL) );
  514. IntPtr lParam = Marshal.AllocHGlobal( rawSize );
  515. Marshal.StructureToPtr(p, lParam, false);
  516. int r = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_CHARFROMPOS, 0, lParam);
  517. Marshal.FreeHGlobal( lParam );
  518. return r;
  519. }
  520. private Point GetPosFromCharIndex(int ix)
  521. {
  522. int rawSize = Marshal.SizeOf( typeof(User32.POINTL) );
  523. IntPtr wParam = Marshal.AllocHGlobal( rawSize );
  524. int r = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_POSFROMCHAR, (int)wParam, ix);
  525. User32.POINTL p1 = (User32.POINTL) Marshal.PtrToStructure(wParam, typeof(User32.POINTL));
  526. Marshal.FreeHGlobal( wParam );
  527. var p = new Point { X= p1.X, Y = p1.Y };
  528. return p;
  529. }
  530. private int GetLengthOfLineContainingChar(int charIndex)
  531. {
  532. int r = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINELENGTH, 0,0);
  533. return r;
  534. }
  535. private int GetLineFromChar(int charIndex)
  536. {
  537. return User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINEFROMCHAR, charIndex, 0);
  538. }
  539. private int GetCharIndexForDisplayLine(int line)
  540. {
  541. return User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINEINDEX, line, 0);
  542. }
  543. private int GetDisplayLineCount()
  544. {
  545. return User32.SendMessage(this.Handle, (int)User32.Msgs.EM_GETLINECOUNT, 0, 0);
  546. }
  547. /// <summary>
  548. /// Sets the color of the characters in the given range.
  549. /// </summary>
  550. ///
  551. /// <remarks>
  552. /// Calling this is equivalent to calling
  553. /// <code>
  554. /// richTextBox.Select(start, end-start);
  555. /// this.richTextBox1.SelectionColor = color;
  556. /// </code>
  557. /// ...but without the error and bounds checking.
  558. /// </remarks>
  559. ///
  560. public void SetSelectionColor(int start, int end, System.Drawing.Color color)
  561. {
  562. User32.SendMessage(this.Handle, (int)User32.Msgs.EM_SETSEL, start, end);
  563. charFormat.dwMask = 0x40000000;
  564. charFormat.dwEffects = 0;
  565. charFormat.crTextColor = System.Drawing.ColorTranslator.ToWin32(color);
  566. Marshal.StructureToPtr(charFormat, lParam1, false);
  567. User32.SendMessage(this.Handle, (int)User32.Msgs.EM_SETCHARFORMAT, User32.SCF_SELECTION, lParam1);
  568. }
  569. private void SetLeftMargin(int widthInPixels)
  570. {
  571. User32.SendMessage(this.Handle, (int)User32.Msgs.EM_SETMARGINS, User32.EC_LEFTMARGIN,
  572. widthInPixels);
  573. }
  574. public Tuple<int,int> GetMargins()
  575. {
  576. int r = User32.SendMessage(this.Handle, (int)User32.Msgs.EM_GETMARGINS, 0,0);
  577. return Tuple.New(r & 0x0000FFFF, (int)((r>>16) & 0x0000FFFF));
  578. }
  579. public void Scroll(int delta)
  580. {
  581. User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINESCROLL, 0, delta);
  582. }
  583. private int FirstVisibleDisplayLine
  584. {
  585. get
  586. {
  587. return User32.SendMessage(this.Handle, (int)User32.Msgs.EM_GETFIRSTVISIBLELINE, 0, 0);
  588. }
  589. set
  590. {
  591. // scroll
  592. int current = FirstVisibleDisplayLine;
  593. int delta = value - current;
  594. User32.SendMessage(this.Handle, (int)User32.Msgs.EM_LINESCROLL, 0, delta);
  595. }
  596. }
  597. private int NumberOfVisibleDisplayLines
  598. {
  599. get
  600. {
  601. int topIndex = this.GetCharIndexFromPosition(new System.Drawing.Point(1, 1));
  602. int bottomIndex = this.GetCharIndexFromPosition(new System.Drawing.Point(1, this.Height - 1));
  603. int topLine = this.GetLineFromCharIndex(topIndex);
  604. int bottomLine = this.GetLineFromCharIndex(bottomIndex);
  605. int n = bottomLine - topLine + 1;
  606. return n;
  607. }
  608. }
  609. private int FirstVisibleTextLine
  610. {
  611. get
  612. {
  613. int c = GetCharIndexFromPos(1,1);
  614. for (int i=0; i < CharIndexForTextLine.Length; i++)
  615. {
  616. if (c < CharIndexForTextLine[i]) return i;
  617. }
  618. return CharIndexForTextLine.Length;
  619. }
  620. }
  621. private int LastVisibleTextLine
  622. {
  623. get
  624. {
  625. int c = GetCharIndexFromPos(1,this.Bounds.Y+this.Bounds.Height);
  626. for (int i=0; i < CharIndexForTextLine.Length; i++)
  627. {
  628. if (c < CharIndexForTextLine[i]) return i;
  629. }
  630. return CharIndexForTextLine.Length;
  631. }
  632. }
  633. private int NumberOfVisibleTextLines
  634. {
  635. get
  636. {
  637. return LastVisibleTextLine - FirstVisibleTextLine;
  638. }
  639. }
  640. public int FirstVisibleLine
  641. {
  642. get
  643. {
  644. if (this.NumberLineCounting == LineCounting.CRLF)
  645. return FirstVisibleTextLine;
  646. else
  647. return FirstVisibleDisplayLine;
  648. }
  649. }
  650. public int NumberOfVisibleLines
  651. {
  652. get
  653. {
  654. if (this.NumberLineCounting == LineCounting.CRLF)
  655. return NumberOfVisibleTextLines;
  656. else
  657. return NumberOfVisibleDisplayLines;
  658. }
  659. }
  660. private int GetCharIndexForTextLine(int ix)
  661. {
  662. if (ix >= CharIndexForTextLine.Length) return 0;
  663. if (ix < 0) return 0;
  664. return CharIndexForTextLine[ix];
  665. }
  666. // The char index is expensive to compute.
  667. private int[] _CharIndexForTextLine;
  668. private int[] CharIndexForTextLine
  669. {
  670. get
  671. {
  672. if (_CharIndexForTextLine == null)
  673. {
  674. var list = new List<int>();
  675. int ix = 0;
  676. foreach( var c in Text2 )
  677. {
  678. if ( c == '\n' ) list.Add(ix);
  679. ix++;
  680. }
  681. _CharIndexForTextLine = list.ToArray();
  682. }
  683. return _CharIndexForTextLine;
  684. }
  685. }
  686. private String _Text2;
  687. private String Text2
  688. {
  689. get
  690. {
  691. if (_Text2 == null)
  692. _Text2 = this.Text;
  693. return _Text2;
  694. }
  695. }
  696. private bool CompareHashes(byte[] a, byte[] b)
  697. {
  698. if (a.Length != b.Length) return false;
  699. for (int i=0; i < a.Length; i++)
  700. {
  701. if (a[i]!=b[i]) return false;
  702. }
  703. return true; // they are equal
  704. }
  705. public enum LineCounting
  706. {
  707. CRLF,
  708. AsDisplayed
  709. }
  710. }
  711. public static class Tuple
  712. {
  713. // Allows Tuple.New(1, "2") instead of new Tuple<int, string>(1, "2")
  714. public static Tuple<T1, T2> New<T1, T2>(T1 v1, T2 v2)
  715. {
  716. return new Tuple<T1, T2>(v1, v2);
  717. }
  718. }
  719. public class Tuple<T1, T2>
  720. {
  721. public Tuple(T1 v1, T2 v2)
  722. {
  723. V1 = v1;
  724. V2 = v2;
  725. }
  726. public T1 V1 { get; set; }
  727. public T2 V2 { get; set; }
  728. }
  729. }