Editor.cs 42 KB

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