Editor.cs 42 KB

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