Editor.cs 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Threading;
  10. using static UICatalog.Scenarios.DynamicMenuBar;
  11. namespace UICatalog.Scenarios;
  12. [ScenarioMetadata ("Editor", "A Text Editor using the TextView control.")]
  13. [ScenarioCategory ("Controls")]
  14. [ScenarioCategory ("Dialogs")]
  15. [ScenarioCategory ("Text and Formatting")]
  16. [ScenarioCategory ("Arrangement")]
  17. [ScenarioCategory ("Files and IO")]
  18. [ScenarioCategory ("TextView")]
  19. [ScenarioCategory ("Menus")]
  20. public class Editor : Scenario
  21. {
  22. private Window _appWindow;
  23. private List<CultureInfo> _cultureInfos;
  24. private string _fileName = "demo.txt";
  25. private bool _forceMinimumPosToZero = true;
  26. private bool _matchCase;
  27. private bool _matchWholeWord;
  28. private MenuItem _miForceMinimumPosToZero;
  29. private byte [] _originalText;
  30. private bool _saved = true;
  31. private TabView _tabView;
  32. private string _textToFind;
  33. private string _textToReplace;
  34. private TextView _textView;
  35. private FindReplaceWindow _findReplaceWindow;
  36. public override void Main ()
  37. {
  38. // Init
  39. Application.Init ();
  40. // Setup - Create a top-level application window and configure it.
  41. _appWindow = new ()
  42. {
  43. //Title = GetQuitKeyAndName (),
  44. Title = _fileName ?? "Untitled",
  45. BorderStyle = LineStyle.None
  46. };
  47. _cultureInfos = Application.SupportedCultures;
  48. _textView = new ()
  49. {
  50. X = 0,
  51. Y = 1,
  52. Width = Dim.Fill (),
  53. Height = Dim.Fill (1),
  54. };
  55. CreateDemoFile (_fileName);
  56. LoadFile ();
  57. _appWindow.Add (_textView);
  58. var menu = new MenuBar
  59. {
  60. Menus =
  61. [
  62. new (
  63. "_File",
  64. new MenuItem []
  65. {
  66. new ("_New", "", () => New ()),
  67. new ("_Open", "", () => Open ()),
  68. new ("_Save", "", () => Save ()),
  69. new ("_Save As", "", () => SaveAs ()),
  70. new ("_Close", "", () => CloseFile ()),
  71. null,
  72. new ("_Quit", "", () => Quit ())
  73. }
  74. ),
  75. new (
  76. "_Edit",
  77. new MenuItem []
  78. {
  79. new (
  80. "_Copy",
  81. "",
  82. () => Copy (),
  83. null,
  84. null,
  85. KeyCode.CtrlMask | KeyCode.C
  86. ),
  87. new (
  88. "C_ut",
  89. "",
  90. () => Cut (),
  91. null,
  92. null,
  93. KeyCode.CtrlMask | KeyCode.W
  94. ),
  95. new (
  96. "_Paste",
  97. "",
  98. () => Paste (),
  99. null,
  100. null,
  101. KeyCode.CtrlMask | KeyCode.Y
  102. ),
  103. null,
  104. new (
  105. "_Find",
  106. "",
  107. () => Find (),
  108. null,
  109. null,
  110. KeyCode.CtrlMask | KeyCode.S
  111. ),
  112. new (
  113. "Find _Next",
  114. "",
  115. () => FindNext (),
  116. null,
  117. null,
  118. KeyCode.CtrlMask
  119. | KeyCode.ShiftMask
  120. | KeyCode.S
  121. ),
  122. new (
  123. "Find P_revious",
  124. "",
  125. () => FindPrevious (),
  126. null,
  127. null,
  128. KeyCode.CtrlMask
  129. | KeyCode.ShiftMask
  130. | KeyCode.AltMask
  131. | KeyCode.S
  132. ),
  133. new (
  134. "_Replace",
  135. "",
  136. () => Replace (),
  137. null,
  138. null,
  139. KeyCode.CtrlMask | KeyCode.R
  140. ),
  141. new (
  142. "Replace Ne_xt",
  143. "",
  144. () => ReplaceNext (),
  145. null,
  146. null,
  147. KeyCode.CtrlMask
  148. | KeyCode.ShiftMask
  149. | KeyCode.R
  150. ),
  151. new (
  152. "Replace Pre_vious",
  153. "",
  154. () => ReplacePrevious (),
  155. null,
  156. null,
  157. KeyCode.CtrlMask
  158. | KeyCode.ShiftMask
  159. | KeyCode.AltMask
  160. | KeyCode.R
  161. ),
  162. new (
  163. "Replace _All",
  164. "",
  165. () => ReplaceAll (),
  166. null,
  167. null,
  168. KeyCode.CtrlMask
  169. | KeyCode.ShiftMask
  170. | KeyCode.AltMask
  171. | KeyCode.A
  172. ),
  173. null,
  174. new (
  175. "_Select All",
  176. "",
  177. () => SelectAll (),
  178. null,
  179. null,
  180. KeyCode.CtrlMask | KeyCode.T
  181. )
  182. }
  183. ),
  184. new ("_ScrollBarView", CreateKeepChecked ()),
  185. new ("_Cursor", CreateCursorRadio ()),
  186. new (
  187. "Forma_t",
  188. new []
  189. {
  190. CreateWrapChecked (),
  191. CreateAutocomplete (),
  192. CreateAllowsTabChecked (),
  193. CreateReadOnlyChecked (),
  194. CreateUseSameRuneTypeForWords (),
  195. CreateSelectWordOnlyOnDoubleClick (),
  196. new MenuItem (
  197. "Colors",
  198. "",
  199. () => _textView.PromptForColors (),
  200. null,
  201. null,
  202. KeyCode.CtrlMask | KeyCode.L
  203. )
  204. }
  205. ),
  206. new (
  207. "_Responder",
  208. new [] { CreateCanFocusChecked (), CreateEnabledChecked (), CreateVisibleChecked () }
  209. ),
  210. new (
  211. "Conte_xtMenu",
  212. new []
  213. {
  214. _miForceMinimumPosToZero = new (
  215. "ForceMinimumPosTo_Zero",
  216. "",
  217. () =>
  218. {
  219. //_miForceMinimumPosToZero.Checked =
  220. // _forceMinimumPosToZero =
  221. // !_forceMinimumPosToZero;
  222. //_textView.ContextMenu.ForceMinimumPosToZero =
  223. // _forceMinimumPosToZero;
  224. }
  225. )
  226. {
  227. CheckType = MenuItemCheckStyle.Checked,
  228. Checked = _forceMinimumPosToZero
  229. },
  230. new MenuBarItem ("_Languages", GetSupportedCultures ())
  231. }
  232. )
  233. ]
  234. };
  235. _appWindow.Add (menu);
  236. var siCursorPosition = new Shortcut (KeyCode.Null, "", null);
  237. var statusBar = new StatusBar (
  238. new []
  239. {
  240. new (Application.QuitKey, $"Quit", Quit),
  241. new (Key.F2, "Open", Open),
  242. new (Key.F3, "Save", () => Save ()),
  243. new (Key.F4, "Save As", () => SaveAs ()),
  244. new (Key.Empty, $"OS Clipboard IsSupported : {Clipboard.IsSupported}", null),
  245. siCursorPosition,
  246. }
  247. )
  248. {
  249. AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast
  250. };
  251. _textView.VerticalScrollBar.AutoShow = false;
  252. _textView.UnwrappedCursorPosition += (s, e) =>
  253. {
  254. siCursorPosition.Title = $"Ln {e.Y + 1}, Col {e.X + 1}";
  255. };
  256. _appWindow.Add (statusBar);
  257. //_scrollBar = new (_textView, true);
  258. //_scrollBar.ChangedPosition += (s, e) =>
  259. // {
  260. // _textView.TopRow = _scrollBar.Position;
  261. // if (_textView.TopRow != _scrollBar.Position)
  262. // {
  263. // _scrollBar.Position = _textView.TopRow;
  264. // }
  265. // _textView.SetNeedsDraw ();
  266. // };
  267. //_scrollBar.OtherScrollBarView.ChangedPosition += (s, e) =>
  268. // {
  269. // _textView.LeftColumn = _scrollBar.OtherScrollBarView.Position;
  270. // if (_textView.LeftColumn != _scrollBar.OtherScrollBarView.Position)
  271. // {
  272. // _scrollBar.OtherScrollBarView.Position = _textView.LeftColumn;
  273. // }
  274. // _textView.SetNeedsDraw ();
  275. // };
  276. //_textView.DrawingContent += (s, e) =>
  277. // {
  278. // _scrollBar.Size = _textView.Lines;
  279. // _scrollBar.Position = _textView.TopRow;
  280. // if (_scrollBar.OtherScrollBarView != null)
  281. // {
  282. // _scrollBar.OtherScrollBarView.Size = _textView.Maxlength;
  283. // _scrollBar.OtherScrollBarView.Position = _textView.LeftColumn;
  284. // }
  285. // };
  286. _appWindow.Closed += (s, e) => Thread.CurrentThread.CurrentUICulture = new ("en-US");
  287. CreateFindReplace ();
  288. // Run - Start the application.
  289. Application.Run (_appWindow);
  290. _appWindow.Dispose ();
  291. // Shutdown - Calling Application.Shutdown is required.
  292. Application.Shutdown ();
  293. }
  294. private bool CanCloseFile ()
  295. {
  296. if (_textView.Text == Encoding.Unicode.GetString (_originalText))
  297. {
  298. //System.Diagnostics.Debug.Assert (!_textView.IsDirty);
  299. return true;
  300. }
  301. Debug.Assert (_textView.IsDirty);
  302. int r = MessageBox.ErrorQuery (
  303. "Save File",
  304. $"Do you want save changes in {_appWindow.Title}?",
  305. "Yes",
  306. "No",
  307. "Cancel"
  308. );
  309. if (r == 0)
  310. {
  311. return Save ();
  312. }
  313. if (r == 1)
  314. {
  315. return true;
  316. }
  317. return false;
  318. }
  319. private void CloseFile ()
  320. {
  321. if (!CanCloseFile ())
  322. {
  323. return;
  324. }
  325. try
  326. {
  327. _textView.CloseFile ();
  328. New (false);
  329. }
  330. catch (Exception ex)
  331. {
  332. MessageBox.ErrorQuery ("Error", ex.Message, "Ok");
  333. }
  334. }
  335. private void ContinueFind (bool next = true, bool replace = false)
  336. {
  337. if (!replace && string.IsNullOrEmpty (_textToFind))
  338. {
  339. Find ();
  340. return;
  341. }
  342. if (replace
  343. && (string.IsNullOrEmpty (_textToFind)
  344. || (_findReplaceWindow == null && string.IsNullOrEmpty (_textToReplace))))
  345. {
  346. Replace ();
  347. return;
  348. }
  349. bool found;
  350. bool gaveFullTurn;
  351. if (next)
  352. {
  353. if (!replace)
  354. {
  355. found = _textView.FindNextText (
  356. _textToFind,
  357. out gaveFullTurn,
  358. _matchCase,
  359. _matchWholeWord
  360. );
  361. }
  362. else
  363. {
  364. found = _textView.FindNextText (
  365. _textToFind,
  366. out gaveFullTurn,
  367. _matchCase,
  368. _matchWholeWord,
  369. _textToReplace,
  370. true
  371. );
  372. }
  373. }
  374. else
  375. {
  376. if (!replace)
  377. {
  378. found = _textView.FindPreviousText (
  379. _textToFind,
  380. out gaveFullTurn,
  381. _matchCase,
  382. _matchWholeWord
  383. );
  384. }
  385. else
  386. {
  387. found = _textView.FindPreviousText (
  388. _textToFind,
  389. out gaveFullTurn,
  390. _matchCase,
  391. _matchWholeWord,
  392. _textToReplace,
  393. true
  394. );
  395. }
  396. }
  397. if (!found)
  398. {
  399. MessageBox.Query ("Find", $"The following specified text was not found: '{_textToFind}'", "Ok");
  400. }
  401. else if (gaveFullTurn)
  402. {
  403. MessageBox.Query (
  404. "Find",
  405. $"No more occurrences were found for the following specified text: '{_textToFind}'",
  406. "Ok"
  407. );
  408. }
  409. }
  410. private void Copy ()
  411. {
  412. if (_textView != null)
  413. {
  414. _textView.Copy ();
  415. }
  416. }
  417. private MenuItem CreateAllowsTabChecked ()
  418. {
  419. var item = new MenuItem { Title = "Allows Tab" };
  420. item.CheckType |= MenuItemCheckStyle.Checked;
  421. item.Checked = _textView.AllowsTab;
  422. item.Action += () => { _textView.AllowsTab = (bool)(item.Checked = !item.Checked); };
  423. return item;
  424. }
  425. private MenuItem CreateAutocomplete ()
  426. {
  427. var singleWordGenerator = new SingleWordSuggestionGenerator ();
  428. _textView.Autocomplete.SuggestionGenerator = singleWordGenerator;
  429. var auto = new MenuItem ();
  430. auto.Title = "Autocomplete";
  431. auto.CheckType |= MenuItemCheckStyle.Checked;
  432. auto.Checked = false;
  433. auto.Action += () =>
  434. {
  435. if ((bool)(auto.Checked = !auto.Checked))
  436. {
  437. // setup autocomplete with all words currently in the editor
  438. singleWordGenerator.AllSuggestions =
  439. Regex.Matches (_textView.Text, "\\w+")
  440. .Select (s => s.Value)
  441. .Distinct ()
  442. .ToList ();
  443. }
  444. else
  445. {
  446. singleWordGenerator.AllSuggestions.Clear ();
  447. }
  448. };
  449. return auto;
  450. }
  451. private MenuItem CreateCanFocusChecked ()
  452. {
  453. var item = new MenuItem { Title = "CanFocus" };
  454. item.CheckType |= MenuItemCheckStyle.Checked;
  455. item.Checked = _textView.CanFocus;
  456. item.Action += () =>
  457. {
  458. _textView.CanFocus = (bool)(item.Checked = !item.Checked);
  459. if (_textView.CanFocus)
  460. {
  461. _textView.SetFocus ();
  462. }
  463. };
  464. return item;
  465. }
  466. private MenuItem [] CreateCursorRadio ()
  467. {
  468. List<MenuItem> menuItems = new ();
  469. menuItems.Add (
  470. new ("_Invisible", "", () => SetCursor (CursorVisibility.Invisible))
  471. {
  472. CheckType = MenuItemCheckStyle.Radio,
  473. Checked = _textView.CursorVisibility
  474. == CursorVisibility.Invisible
  475. }
  476. );
  477. menuItems.Add (
  478. new ("_Box", "", () => SetCursor (CursorVisibility.Box))
  479. {
  480. CheckType = MenuItemCheckStyle.Radio,
  481. Checked = _textView.CursorVisibility == CursorVisibility.Box
  482. }
  483. );
  484. menuItems.Add (
  485. new ("_Underline", "", () => SetCursor (CursorVisibility.Underline))
  486. {
  487. CheckType = MenuItemCheckStyle.Radio,
  488. Checked = _textView.CursorVisibility
  489. == CursorVisibility.Underline
  490. }
  491. );
  492. menuItems.Add (new ("", "", () => { }, () => false));
  493. menuItems.Add (new ("xTerm :", "", () => { }, () => false));
  494. menuItems.Add (new ("", "", () => { }, () => false));
  495. menuItems.Add (
  496. new (" _Default", "", () => SetCursor (CursorVisibility.Default))
  497. {
  498. CheckType = MenuItemCheckStyle.Radio,
  499. Checked = _textView.CursorVisibility
  500. == CursorVisibility.Default
  501. }
  502. );
  503. menuItems.Add (
  504. new (" _Vertical", "", () => SetCursor (CursorVisibility.Vertical))
  505. {
  506. CheckType = MenuItemCheckStyle.Radio,
  507. Checked = _textView.CursorVisibility
  508. == CursorVisibility.Vertical
  509. }
  510. );
  511. menuItems.Add (
  512. new (" V_ertical Fix", "", () => SetCursor (CursorVisibility.VerticalFix))
  513. {
  514. CheckType = MenuItemCheckStyle.Radio,
  515. Checked = _textView.CursorVisibility == CursorVisibility.VerticalFix
  516. }
  517. );
  518. menuItems.Add (
  519. new (" B_ox Fix", "", () => SetCursor (CursorVisibility.BoxFix))
  520. {
  521. CheckType = MenuItemCheckStyle.Radio,
  522. Checked = _textView.CursorVisibility
  523. == CursorVisibility.BoxFix
  524. }
  525. );
  526. menuItems.Add (
  527. new (" U_nderline Fix", "", () => SetCursor (CursorVisibility.UnderlineFix))
  528. {
  529. CheckType = MenuItemCheckStyle.Radio,
  530. Checked = _textView.CursorVisibility == CursorVisibility.UnderlineFix
  531. }
  532. );
  533. void SetCursor (CursorVisibility visibility)
  534. {
  535. _textView.CursorVisibility = visibility;
  536. var title = "";
  537. switch (visibility)
  538. {
  539. case CursorVisibility.Default:
  540. title = " _Default";
  541. break;
  542. case CursorVisibility.Invisible:
  543. title = "_Invisible";
  544. break;
  545. case CursorVisibility.Underline:
  546. title = "_Underline";
  547. break;
  548. case CursorVisibility.UnderlineFix:
  549. title = " U_nderline Fix";
  550. break;
  551. case CursorVisibility.Vertical:
  552. title = " _Vertical";
  553. break;
  554. case CursorVisibility.VerticalFix:
  555. title = " V_ertical Fix";
  556. break;
  557. case CursorVisibility.Box:
  558. title = "_Box";
  559. break;
  560. case CursorVisibility.BoxFix:
  561. title = " B_ox Fix";
  562. break;
  563. }
  564. foreach (MenuItem menuItem in menuItems)
  565. {
  566. menuItem.Checked = menuItem.Title.Equals (title) && visibility == _textView.CursorVisibility;
  567. }
  568. }
  569. return menuItems.ToArray ();
  570. }
  571. private void CreateDemoFile (string fileName)
  572. {
  573. var sb = new StringBuilder ();
  574. // FIXED: BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
  575. sb.Append ("Hello world.\n");
  576. sb.Append ("This is a test of the Emergency Broadcast System.\n");
  577. for (var i = 0; i < 30; i++)
  578. {
  579. sb.Append (
  580. $"{i} - This is a test with a very long line and many lines to test the ScrollViewBar against the TextView. - {i}\n"
  581. );
  582. }
  583. StreamWriter sw = File.CreateText (fileName);
  584. sw.Write (sb.ToString ());
  585. sw.Close ();
  586. }
  587. private MenuItem CreateEnabledChecked ()
  588. {
  589. var item = new MenuItem { Title = "Enabled" };
  590. item.CheckType |= MenuItemCheckStyle.Checked;
  591. item.Checked = _textView.Enabled;
  592. item.Action += () =>
  593. {
  594. _textView.Enabled = (bool)(item.Checked = !item.Checked);
  595. if (_textView.Enabled)
  596. {
  597. _textView.SetFocus ();
  598. }
  599. };
  600. return item;
  601. }
  602. private class FindReplaceWindow : Window
  603. {
  604. private TextView _textView;
  605. public FindReplaceWindow (TextView textView)
  606. {
  607. Title = "Find and Replace";
  608. _textView = textView;
  609. X = Pos.AnchorEnd () - 1;
  610. Y = 2;
  611. Width = 57;
  612. Height = 11;
  613. Arrangement = ViewArrangement.Movable;
  614. KeyBindings.Add (Key.Esc, Command.Cancel);
  615. AddCommand (Command.Cancel, () =>
  616. {
  617. Visible = false;
  618. return true;
  619. });
  620. VisibleChanged += FindReplaceWindow_VisibleChanged;
  621. Initialized += FindReplaceWindow_Initialized;
  622. //var btnCancel = new Button
  623. //{
  624. // X = Pos.AnchorEnd (),
  625. // Y = Pos.AnchorEnd (),
  626. // Text = "Cancel"
  627. //};
  628. //btnCancel.Accept += (s, e) => { Visible = false; };
  629. //Add (btnCancel);
  630. }
  631. private void FindReplaceWindow_VisibleChanged (object sender, EventArgs e)
  632. {
  633. if (Visible == false)
  634. {
  635. _textView.SetFocus ();
  636. }
  637. else
  638. {
  639. FocusDeepest (NavigationDirection.Forward, null);
  640. }
  641. }
  642. private void FindReplaceWindow_Initialized (object sender, EventArgs e)
  643. {
  644. Border.LineStyle = LineStyle.Dashed;
  645. Border.Thickness = new (0, 1, 0, 0);
  646. }
  647. }
  648. private void ShowFindReplace (bool isFind = true)
  649. {
  650. _findReplaceWindow.Visible = true;
  651. _findReplaceWindow.SuperView.MoveSubViewToStart (_findReplaceWindow);
  652. _tabView.SetFocus ();
  653. _tabView.SelectedTab = isFind ? _tabView.Tabs.ToArray () [0] : _tabView.Tabs.ToArray () [1];
  654. _tabView.SelectedTab.View.FocusDeepest (NavigationDirection.Forward, null);
  655. }
  656. private void CreateFindReplace ()
  657. {
  658. _findReplaceWindow = new (_textView);
  659. _tabView = new ()
  660. {
  661. X = 0, Y = 0,
  662. Width = Dim.Fill (), Height = Dim.Fill (0)
  663. };
  664. _tabView.AddTab (new () { DisplayText = "Find", View = CreateFindTab () }, true);
  665. _tabView.AddTab (new () { DisplayText = "Replace", View = CreateReplaceTab () }, false);
  666. _tabView.SelectedTabChanged += (s, e) => _tabView.SelectedTab.View.FocusDeepest (NavigationDirection.Forward, null);
  667. _findReplaceWindow.Add (_tabView);
  668. // _tabView.SelectedTab.View.FocusLast (null); // Hack to get the first tab to be focused
  669. _findReplaceWindow.Visible = false;
  670. _appWindow.Add (_findReplaceWindow);
  671. }
  672. private MenuItem [] CreateKeepChecked ()
  673. {
  674. var item = new MenuItem ();
  675. item.Title = "Keep Content Always In Viewport";
  676. item.CheckType |= MenuItemCheckStyle.Checked;
  677. item.Checked = true;
  678. //item.Action += () => _scrollBar.KeepContentAlwaysInViewport = (bool)(item.Checked = !item.Checked);
  679. return new [] { item };
  680. }
  681. private MenuItem CreateSelectWordOnlyOnDoubleClick ()
  682. {
  683. var item = new MenuItem { Title = "SelectWordOnlyOnDoubleClick" };
  684. item.CheckType |= MenuItemCheckStyle.Checked;
  685. item.Checked = _textView.SelectWordOnlyOnDoubleClick;
  686. item.Action += () => _textView.SelectWordOnlyOnDoubleClick = (bool)(item.Checked = !item.Checked);
  687. return item;
  688. }
  689. private MenuItem CreateUseSameRuneTypeForWords ()
  690. {
  691. var item = new MenuItem { Title = "UseSameRuneTypeForWords" };
  692. item.CheckType |= MenuItemCheckStyle.Checked;
  693. item.Checked = _textView.UseSameRuneTypeForWords;
  694. item.Action += () => _textView.UseSameRuneTypeForWords = (bool)(item.Checked = !item.Checked);
  695. return item;
  696. }
  697. private MenuItem CreateReadOnlyChecked ()
  698. {
  699. var item = new MenuItem { Title = "Read Only" };
  700. item.CheckType |= MenuItemCheckStyle.Checked;
  701. item.Checked = _textView.ReadOnly;
  702. item.Action += () => _textView.ReadOnly = (bool)(item.Checked = !item.Checked);
  703. return item;
  704. }
  705. private MenuItem CreateVisibleChecked ()
  706. {
  707. var item = new MenuItem { Title = "Visible" };
  708. item.CheckType |= MenuItemCheckStyle.Checked;
  709. item.Checked = _textView.Visible;
  710. item.Action += () =>
  711. {
  712. _textView.Visible = (bool)(item.Checked = !item.Checked);
  713. if (_textView.Visible)
  714. {
  715. _textView.SetFocus ();
  716. }
  717. };
  718. return item;
  719. }
  720. private MenuItem CreateWrapChecked ()
  721. {
  722. var item = new MenuItem { Title = "Word Wrap" };
  723. item.CheckType |= MenuItemCheckStyle.Checked;
  724. item.Checked = _textView.WordWrap;
  725. item.Action += () =>
  726. {
  727. _textView.WordWrap = (bool)(item.Checked = !item.Checked);
  728. if (_textView.WordWrap)
  729. {
  730. //_scrollBar.OtherScrollBarView.ShowScrollIndicator = false;
  731. }
  732. };
  733. return item;
  734. }
  735. private void Cut ()
  736. {
  737. if (_textView != null)
  738. {
  739. _textView.Cut ();
  740. }
  741. }
  742. private void Find () { ShowFindReplace (true); }
  743. private void FindNext () { ContinueFind (); }
  744. private void FindPrevious () { ContinueFind (false); }
  745. private View CreateFindTab ()
  746. {
  747. var d = new View ()
  748. {
  749. Width = Dim.Fill (),
  750. Height = Dim.Fill ()
  751. };
  752. int lblWidth = "Replace:".Length;
  753. var label = new Label
  754. {
  755. Width = lblWidth,
  756. TextAlignment = Alignment.End,
  757. Text = "Find:"
  758. };
  759. d.Add (label);
  760. SetFindText ();
  761. var txtToFind = new TextField
  762. {
  763. X = Pos.Right (label) + 1,
  764. Y = Pos.Top (label),
  765. Width = Dim.Fill (1),
  766. Text = _textToFind
  767. };
  768. txtToFind.HasFocusChanging += (s, e) => txtToFind.Text = _textToFind;
  769. d.Add (txtToFind);
  770. var btnFindNext = new Button
  771. {
  772. X = Pos.Align (Alignment.Center),
  773. Y = Pos.AnchorEnd (),
  774. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  775. IsDefault = true,
  776. Text = "Find _Next"
  777. };
  778. btnFindNext.Accepting += (s, e) => FindNext ();
  779. d.Add (btnFindNext);
  780. var btnFindPrevious = new Button
  781. {
  782. X = Pos.Align (Alignment.Center),
  783. Y = Pos.AnchorEnd (),
  784. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  785. Text = "Find _Previous"
  786. };
  787. btnFindPrevious.Accepting += (s, e) => FindPrevious ();
  788. d.Add (btnFindPrevious);
  789. txtToFind.TextChanged += (s, e) =>
  790. {
  791. _textToFind = txtToFind.Text;
  792. _textView.FindTextChanged ();
  793. btnFindNext.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  794. btnFindPrevious.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  795. };
  796. var ckbMatchCase = new CheckBox
  797. {
  798. X = 0, Y = Pos.Top (txtToFind) + 2, CheckedState = _matchCase ? CheckState.Checked : CheckState.UnChecked, Text = "Match c_ase"
  799. };
  800. ckbMatchCase.CheckedStateChanging += (s, e) => _matchCase = e.Result == CheckState.Checked;
  801. d.Add (ckbMatchCase);
  802. var ckbMatchWholeWord = new CheckBox
  803. {
  804. X = 0, Y = Pos.Top (ckbMatchCase) + 1, CheckedState = _matchWholeWord ? CheckState.Checked : CheckState.UnChecked, Text = "Match _whole word"
  805. };
  806. ckbMatchWholeWord.CheckedStateChanging += (s, e) => _matchWholeWord = e.Result == CheckState.Checked;
  807. d.Add (ckbMatchWholeWord);
  808. return d;
  809. }
  810. private MenuItem [] GetSupportedCultures ()
  811. {
  812. List<MenuItem> supportedCultures = new ();
  813. int index = -1;
  814. foreach (CultureInfo c in _cultureInfos)
  815. {
  816. var culture = new MenuItem { CheckType = MenuItemCheckStyle.Checked };
  817. if (index == -1)
  818. {
  819. culture.Title = "_English";
  820. culture.Help = "en-US";
  821. culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == "en-US";
  822. CreateAction (supportedCultures, culture);
  823. supportedCultures.Add (culture);
  824. index++;
  825. culture = new () { CheckType = MenuItemCheckStyle.Checked };
  826. }
  827. culture.Title = $"_{c.Parent.EnglishName}";
  828. culture.Help = c.Name;
  829. culture.Checked = Thread.CurrentThread.CurrentUICulture.Name == c.Name;
  830. CreateAction (supportedCultures, culture);
  831. supportedCultures.Add (culture);
  832. }
  833. return supportedCultures.ToArray ();
  834. void CreateAction (List<MenuItem> supportedCultures, MenuItem culture)
  835. {
  836. culture.Action += () =>
  837. {
  838. Thread.CurrentThread.CurrentUICulture = new (culture.Help);
  839. culture.Checked = true;
  840. foreach (MenuItem item in supportedCultures)
  841. {
  842. item.Checked = item.Help == Thread.CurrentThread.CurrentUICulture.Name;
  843. }
  844. };
  845. }
  846. }
  847. private void LoadFile ()
  848. {
  849. if (_fileName != null)
  850. {
  851. // FIXED: BUGBUG: #452 TextView.LoadFile keeps file open and provides no way of closing it
  852. _textView.Load (_fileName);
  853. //_textView.Text = System.IO.File.ReadAllText (_fileName);
  854. _originalText = Encoding.Unicode.GetBytes (_textView.Text);
  855. _appWindow.Title = _fileName;
  856. _saved = true;
  857. }
  858. }
  859. private void New (bool checkChanges = true)
  860. {
  861. if (checkChanges && !CanCloseFile ())
  862. {
  863. return;
  864. }
  865. _appWindow.Title = "Untitled.txt";
  866. _fileName = null;
  867. _originalText = new MemoryStream ().ToArray ();
  868. _textView.Text = Encoding.Unicode.GetString (_originalText);
  869. }
  870. private void Open ()
  871. {
  872. if (!CanCloseFile ())
  873. {
  874. return;
  875. }
  876. List<IAllowedType> aTypes = new ()
  877. {
  878. new AllowedType (
  879. "Text",
  880. ".txt;.bin;.xml;.json",
  881. ".txt",
  882. ".bin",
  883. ".xml",
  884. ".json"
  885. ),
  886. new AllowedTypeAny ()
  887. };
  888. var d = new OpenDialog { Title = "Open", AllowedTypes = aTypes, AllowsMultipleSelection = false };
  889. Application.Run (d);
  890. if (!d.Canceled && d.FilePaths.Count > 0)
  891. {
  892. _fileName = d.FilePaths [0];
  893. LoadFile ();
  894. }
  895. d.Dispose ();
  896. }
  897. private void Paste ()
  898. {
  899. if (_textView != null)
  900. {
  901. _textView.Paste ();
  902. }
  903. }
  904. private void Quit ()
  905. {
  906. if (!CanCloseFile ())
  907. {
  908. return;
  909. }
  910. Application.RequestStop ();
  911. }
  912. private void Replace () { ShowFindReplace (false); }
  913. private void ReplaceAll ()
  914. {
  915. if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && _findReplaceWindow == null))
  916. {
  917. Replace ();
  918. return;
  919. }
  920. if (_textView.ReplaceAllText (_textToFind, _matchCase, _matchWholeWord, _textToReplace))
  921. {
  922. MessageBox.Query (
  923. "Replace All",
  924. $"All occurrences were replaced for the following specified text: '{_textToReplace}'",
  925. "Ok"
  926. );
  927. }
  928. else
  929. {
  930. MessageBox.Query (
  931. "Replace All",
  932. $"None of the following specified text was found: '{_textToFind}'",
  933. "Ok"
  934. );
  935. }
  936. }
  937. private void ReplaceNext () { ContinueFind (true, true); }
  938. private void ReplacePrevious () { ContinueFind (false, true); }
  939. private View CreateReplaceTab ()
  940. {
  941. var d = new View ()
  942. {
  943. Width = Dim.Fill (),
  944. Height = Dim.Fill ()
  945. };
  946. int lblWidth = "Replace:".Length;
  947. var label = new Label
  948. {
  949. Width = lblWidth,
  950. TextAlignment = Alignment.End,
  951. Text = "Find:"
  952. };
  953. d.Add (label);
  954. SetFindText ();
  955. var txtToFind = new TextField
  956. {
  957. X = Pos.Right (label) + 1,
  958. Y = Pos.Top (label),
  959. Width = Dim.Fill (1),
  960. Text = _textToFind
  961. };
  962. txtToFind.HasFocusChanging += (s, e) => txtToFind.Text = _textToFind;
  963. d.Add (txtToFind);
  964. var btnFindNext = new Button
  965. {
  966. X = Pos.Align (Alignment.Center),
  967. Y = Pos.AnchorEnd (),
  968. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  969. IsDefault = true,
  970. Text = "Replace _Next"
  971. };
  972. btnFindNext.Accepting += (s, e) => ReplaceNext ();
  973. d.Add (btnFindNext);
  974. label = new ()
  975. {
  976. X = Pos.Left (label),
  977. Y = Pos.Top (label) + 1,
  978. Text = "Replace:"
  979. };
  980. d.Add (label);
  981. SetFindText ();
  982. var txtToReplace = new TextField
  983. {
  984. X = Pos.Right (label) + 1,
  985. Y = Pos.Top (label),
  986. Width = Dim.Fill (1),
  987. Text = _textToReplace
  988. };
  989. txtToReplace.TextChanged += (s, e) => _textToReplace = txtToReplace.Text;
  990. d.Add (txtToReplace);
  991. var btnFindPrevious = new Button
  992. {
  993. X = Pos.Align (Alignment.Center),
  994. Y = Pos.AnchorEnd (),
  995. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  996. Text = "Replace _Previous"
  997. };
  998. btnFindPrevious.Accepting += (s, e) => ReplacePrevious ();
  999. d.Add (btnFindPrevious);
  1000. var btnReplaceAll = new Button
  1001. {
  1002. X = Pos.Align (Alignment.Center),
  1003. Y = Pos.AnchorEnd (),
  1004. Enabled = !string.IsNullOrEmpty (txtToFind.Text),
  1005. Text = "Replace _All"
  1006. };
  1007. btnReplaceAll.Accepting += (s, e) => ReplaceAll ();
  1008. d.Add (btnReplaceAll);
  1009. txtToFind.TextChanged += (s, e) =>
  1010. {
  1011. _textToFind = txtToFind.Text;
  1012. _textView.FindTextChanged ();
  1013. btnFindNext.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  1014. btnFindPrevious.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  1015. btnReplaceAll.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
  1016. };
  1017. var ckbMatchCase = new CheckBox
  1018. {
  1019. X = 0, Y = Pos.Top (txtToFind) + 2, CheckedState = _matchCase ? CheckState.Checked : CheckState.UnChecked, Text = "Match c_ase"
  1020. };
  1021. ckbMatchCase.CheckedStateChanging += (s, e) => _matchCase = e.Result == CheckState.Checked;
  1022. d.Add (ckbMatchCase);
  1023. var ckbMatchWholeWord = new CheckBox
  1024. {
  1025. X = 0, Y = Pos.Top (ckbMatchCase) + 1, CheckedState = _matchWholeWord ? CheckState.Checked : CheckState.UnChecked, Text = "Match _whole word"
  1026. };
  1027. ckbMatchWholeWord.CheckedStateChanging += (s, e) => _matchWholeWord = e.Result == CheckState.Checked;
  1028. d.Add (ckbMatchWholeWord);
  1029. return d;
  1030. }
  1031. private bool Save ()
  1032. {
  1033. if (_fileName != null)
  1034. {
  1035. // FIXED: BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
  1036. // As a result files saved on Windows and then read back will show invalid chars.
  1037. return SaveFile (_appWindow.Title, _fileName);
  1038. }
  1039. return SaveAs ();
  1040. }
  1041. private bool SaveAs ()
  1042. {
  1043. List<IAllowedType> aTypes = new ()
  1044. {
  1045. new AllowedType ("Text Files", ".txt", ".bin", ".xml"), new AllowedTypeAny ()
  1046. };
  1047. var sd = new SaveDialog { Title = "Save file", AllowedTypes = aTypes };
  1048. sd.Path = _appWindow.Title;
  1049. Application.Run (sd);
  1050. bool canceled = sd.Canceled;
  1051. string path = sd.Path;
  1052. string fileName = sd.FileName;
  1053. sd.Dispose ();
  1054. if (!canceled)
  1055. {
  1056. if (File.Exists (path))
  1057. {
  1058. if (MessageBox.Query (
  1059. "Save File",
  1060. "File already exists. Overwrite any way?",
  1061. "No",
  1062. "Ok"
  1063. )
  1064. == 1)
  1065. {
  1066. return SaveFile (fileName, path);
  1067. }
  1068. _saved = false;
  1069. return _saved;
  1070. }
  1071. return SaveFile (fileName, path);
  1072. }
  1073. _saved = false;
  1074. return _saved;
  1075. }
  1076. private bool SaveFile (string title, string file)
  1077. {
  1078. try
  1079. {
  1080. _appWindow.Title = title;
  1081. _fileName = file;
  1082. File.WriteAllText (_fileName, _textView.Text);
  1083. _originalText = Encoding.Unicode.GetBytes (_textView.Text);
  1084. _saved = true;
  1085. _textView.ClearHistoryChanges ();
  1086. MessageBox.Query ("Save File", "File was successfully saved.", "Ok");
  1087. }
  1088. catch (Exception ex)
  1089. {
  1090. MessageBox.ErrorQuery ("Error", ex.Message, "Ok");
  1091. return false;
  1092. }
  1093. return true;
  1094. }
  1095. private void SelectAll () { _textView.SelectAll (); }
  1096. private void SetFindText ()
  1097. {
  1098. _textToFind = !string.IsNullOrEmpty (_textView.SelectedText) ? _textView.SelectedText :
  1099. string.IsNullOrEmpty (_textToFind) ? "" : _textToFind;
  1100. _textToReplace = string.IsNullOrEmpty (_textToReplace) ? "" : _textToReplace;
  1101. }
  1102. }