Editor.cs 42 KB

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