Editor.cs 42 KB

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