Files
Terminal.Gui/Examples/UICatalog/Scenarios/Editor.cs
Tig 0f72cf8a74 Fixes #4425 - ApplicationImpl internal (#4426)
* Pulled from v2_release

* Refactor migration guide for Terminal.Gui v2

Restructured and expanded the migration guide to provide a comprehensive resource for transitioning from Terminal.Gui v1 to v2. Key updates include:

- Added a Table of Contents for easier navigation.
- Summarized major architectural changes in v2, including the instance-based application model, IRunnable architecture, and 24-bit TrueColor support.
- Updated examples to reflect new patterns, such as initializers replacing constructors and explicit disposal using `IDisposable`.
- Documented changes to the layout system, including the removal of `Absolute`/`Computed` styles and the introduction of `Viewport`.
- Standardized event patterns to use `object sender, EventArgs args`.
- Detailed updates to the Keyboard, Mouse, and Navigation APIs, including configurable key bindings and viewport-relative mouse coordinates.
- Replaced legacy components like `ScrollView` and `ContextMenu` with built-in scrolling and `PopoverMenu`.
- Clarified disposal rules and introduced best practices for resource management.
- Provided a complete migration example and a summary of breaking changes.

This update aims to simplify the migration process by addressing breaking changes, introducing new features, and aligning with modern .NET conventions.

* Refactor to use Application.Instance for lifecycle management

Replaced all occurrences of `ApplicationImpl.Instance` with the new `Application.Instance` property across the codebase to align with the updated application lifecycle model.

Encapsulated the `ApplicationImpl` class by making it `internal`, ensuring it is no longer directly accessible outside its assembly. Introduced the `[Obsolete]` `Application.Instance` property as a backward-compatible singleton for the legacy static `Application` model, while encouraging the use of `Application.Create()` for new code.

Updated `MessageBox` methods to use `Application.Instance` for consistent modal dialog management. Improved documentation to reflect these changes and emphasize the transition to the instance-based application model.

Performed code cleanup in multiple classes to ensure consistency and maintainability. These changes maintain backward compatibility while preparing the codebase for the eventual removal of the legacy `ApplicationImpl` class.

* Fix doc bug

* - Removed obsolete `.cd` class diagram files.
- Introduced `IRunnable` interface for decoupling component execution.
- Added fluent API for running dialogs and retrieving results.
- Enhanced `View` with `App` and `Driver` properties for better decoupling.
- Improved testability with support for mock and real applications.
- Implemented `IDisposable` for proper resource cleanup.
- Replaced `RunnableSessionStack` with `SessionStack` for session management.
- Updated driver architecture to align with the new model.
- Scoped `IKeyboard` to application contexts for modularity.
- Updated documentation with migration strategies and best practices.

These changes modernize the library, improve maintainability, and align with current development practices.
2025-12-01 14:40:31 -07:00

1327 lines
42 KiB
C#

#nullable enable
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace UICatalog.Scenarios;
[ScenarioMetadata ("Editor", "A Text Editor using the TextView control.")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("Dialogs")]
[ScenarioCategory ("Text and Formatting")]
[ScenarioCategory ("Arrangement")]
[ScenarioCategory ("Files and IO")]
[ScenarioCategory ("TextView")]
[ScenarioCategory ("Menus")]
public class Editor : Scenario
{
private Window? _appWindow;
private List<CultureInfo>? _cultureInfos;
private string _fileName = "demo.txt";
private bool _forceMinimumPosToZero = true;
private bool _matchCase;
private bool _matchWholeWord;
private CheckBox? _miForceMinimumPosToZeroCheckBox;
private byte []? _originalText;
private bool _saved = true;
private TabView? _tabView;
private string _textToFind = string.Empty;
private string _textToReplace = string.Empty;
private TextView? _textView;
private FindReplaceWindow? _findReplaceWindow;
public override void Main ()
{
Application.Init ();
_appWindow = new ()
{
Title = _fileName ?? "Untitled",
BorderStyle = LineStyle.None
};
_cultureInfos = Application.SupportedCultures?.ToList ();
_textView = new ()
{
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill (1)
};
CreateDemoFile (_fileName!);
LoadFile ();
_appWindow.Add (_textView);
// MenuBar
MenuBar menu = new ();
menu.Add (
new MenuBarItem (
"_File",
[
new MenuItem { Title = "_New", Action = () => New () },
new MenuItem { Title = "_Open", Action = Open },
new MenuItem { Title = "_Save", Action = () => Save () },
new MenuItem { Title = "_Save As", Action = () => SaveAs () },
new MenuItem { Title = "_Close", Action = CloseFile },
new MenuItem { Title = "_Quit", Action = Quit }
]
)
);
menu.Add (
new MenuBarItem (
"_Edit",
[
new MenuItem { Title = "_Copy", Key = Key.C.WithCtrl, Action = Copy },
new MenuItem { Title = "C_ut", Key = Key.W.WithCtrl, Action = Cut },
new MenuItem { Title = "_Paste", Key = Key.Y.WithCtrl, Action = Paste },
new MenuItem { Title = "_Find", Key = Key.S.WithCtrl, Action = Find },
new MenuItem { Title = "Find _Next", Key = Key.S.WithCtrl.WithShift, Action = FindNext },
new MenuItem { Title = "Find P_revious", Key = Key.S.WithCtrl.WithShift.WithAlt, Action = FindPrevious },
new MenuItem { Title = "_Replace", Key = Key.R.WithCtrl, Action = Replace },
new MenuItem { Title = "Replace Ne_xt", Key = Key.R.WithCtrl.WithShift, Action = ReplaceNext },
new MenuItem { Title = "Replace Pre_vious", Key = Key.R.WithCtrl.WithShift.WithAlt, Action = ReplacePrevious },
new MenuItem { Title = "Replace _All", Key = Key.A.WithCtrl.WithShift.WithAlt, Action = ReplaceAll },
new MenuItem { Title = "_Select All", Key = Key.T.WithCtrl, Action = SelectAll }
]
)
);
menu.Add (new MenuBarItem ("_ScrollBars", CreateScrollBarsMenu ()));
menu.Add (new MenuBarItem ("_Cursor", CreateCursorRadio ()));
menu.Add (
new MenuBarItem (
"Forma_t",
[
CreateWrapChecked (),
CreateAutocomplete (),
CreateAllowsTabChecked (),
CreateReadOnlyChecked (),
CreateUseSameRuneTypeForWords (),
CreateSelectWordOnlyOnDoubleClick (),
new MenuItem { Title = "Colors", Key = Key.L.WithCtrl, Action = () => _textView?.PromptForColors () }
]
)
);
menu.Add (
new MenuBarItem (
"_View",
[CreateCanFocusChecked (), CreateEnabledChecked (), CreateVisibleChecked ()]
)
);
_miForceMinimumPosToZeroCheckBox = new ()
{
Title = "ForceMinimumPosTo_Zero",
CheckedState = _forceMinimumPosToZero ? CheckState.Checked : CheckState.UnChecked
};
_miForceMinimumPosToZeroCheckBox.CheckedStateChanging += (s, e) =>
{
_forceMinimumPosToZero = e.Result == CheckState.Checked;
// Note: PopoverMenu.ForceMinimumPosToZero property doesn't exist in v2
// if (_textView?.ContextMenu is { })
// {
// _textView.ContextMenu.ForceMinimumPosToZero = _forceMinimumPosToZero;
// }
};
menu.Add (
new MenuBarItem (
"Conte_xtMenu",
[
new MenuItem { CommandView = _miForceMinimumPosToZeroCheckBox },
new MenuBarItem ("_Languages", GetSupportedCultures ())
]
)
);
_appWindow.Add (menu);
Shortcut siCursorPosition = new (Key.Empty, "", null);
StatusBar statusBar = new (
[
new (Application.QuitKey, "Quit", Quit),
new (Key.F2, "Open", Open),
new (Key.F3, "Save", () => Save ()),
new (Key.F4, "Save As", () => SaveAs ()),
new (Key.Empty, $"OS Clipboard IsSupported : {Application.Clipboard!.IsSupported}", null),
siCursorPosition
]
)
{
AlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast
};
_textView.VerticalScrollBar.AutoShow = false;
_textView.UnwrappedCursorPosition += (s, e) => { siCursorPosition.Title = $"Ln {e.Y + 1}, Col {e.X + 1}"; };
_appWindow.Add (statusBar);
_appWindow.IsRunningChanged += (s, e) =>
{
if (!e.Value)
{
// BUGBUG: This should restore the original culture info
Thread.CurrentThread.CurrentUICulture = new ("en-US");
}
};
CreateFindReplace ();
Application.Run (_appWindow);
_appWindow.Dispose ();
Application.Shutdown ();
}
private bool CanCloseFile ()
{
if (_textView is null || _originalText is null || _appWindow is null)
{
return true;
}
if (_textView.Text == Encoding.Unicode.GetString (_originalText))
{
return true;
}
Debug.Assert (_textView.IsDirty);
int? r = MessageBox.ErrorQuery (
Application.Instance,
"Save File",
$"Do you want save changes in {_appWindow.Title}?",
"Yes",
"No",
"Cancel"
);
if (r == 0)
{
return Save ();
}
if (r == 1)
{
return true;
}
return false;
}
private void CloseFile ()
{
if (!CanCloseFile () || _textView is null)
{
return;
}
try
{
_textView.CloseFile ();
New (false);
}
catch (Exception ex)
{
MessageBox.ErrorQuery (Application.Instance, "Error", ex.Message, "Ok");
}
}
private void ContinueFind (bool next = true, bool replace = false)
{
if (_textView is null)
{
return;
}
if (!replace && string.IsNullOrEmpty (_textToFind))
{
Find ();
return;
}
if (replace
&& (string.IsNullOrEmpty (_textToFind)
|| (_findReplaceWindow is null && string.IsNullOrEmpty (_textToReplace))))
{
Replace ();
return;
}
bool found;
bool gaveFullTurn;
if (next)
{
if (!replace)
{
found = _textView.FindNextText (
_textToFind,
out gaveFullTurn,
_matchCase,
_matchWholeWord
);
}
else
{
found = _textView.FindNextText (
_textToFind,
out gaveFullTurn,
_matchCase,
_matchWholeWord,
_textToReplace,
true
);
}
}
else
{
if (!replace)
{
found = _textView.FindPreviousText (
_textToFind,
out gaveFullTurn,
_matchCase,
_matchWholeWord
);
}
else
{
found = _textView.FindPreviousText (
_textToFind,
out gaveFullTurn,
_matchCase,
_matchWholeWord,
_textToReplace,
true
);
}
}
if (!found)
{
MessageBox.Query (Application.Instance, "Find", $"The following specified text was not found: '{_textToFind}'", "Ok");
}
else if (gaveFullTurn)
{
MessageBox.Query (Application.Instance,
"Find",
$"No more occurrences were found for the following specified text: '{_textToFind}'",
"Ok"
);
}
}
private void Copy () { _textView?.Copy (); }
private MenuItem [] CreateScrollBarsMenu ()
{
if (_textView is null)
{
return [];
}
List<MenuItem> menuItems = [];
// Vertical ScrollBar AutoShow
CheckBox verticalAutoShowCheckBox = new ()
{
Title = "_Vertical ScrollBar AutoShow",
CheckedState = _textView.VerticalScrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked
};
verticalAutoShowCheckBox.CheckedStateChanged += (s, e) =>
{
_textView.VerticalScrollBar.AutoShow = verticalAutoShowCheckBox.CheckedState == CheckState.Checked;
};
MenuItem verticalItem = new () { CommandView = verticalAutoShowCheckBox };
verticalItem.Accepting += (s, e) =>
{
verticalAutoShowCheckBox.AdvanceCheckState ();
e.Handled = true;
};
menuItems.Add (verticalItem);
// Horizontal ScrollBar AutoShow
CheckBox horizontalAutoShowCheckBox = new ()
{
Title = "_Horizontal ScrollBar AutoShow",
CheckedState = _textView.HorizontalScrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked
};
horizontalAutoShowCheckBox.CheckedStateChanged += (s, e) =>
{
_textView.HorizontalScrollBar.AutoShow = horizontalAutoShowCheckBox.CheckedState == CheckState.Checked;
};
MenuItem horizontalItem = new () { CommandView = horizontalAutoShowCheckBox };
horizontalItem.Accepting += (s, e) =>
{
horizontalAutoShowCheckBox.AdvanceCheckState ();
e.Handled = true;
};
menuItems.Add (horizontalItem);
return [.. menuItems];
}
private MenuItem [] CreateCursorRadio ()
{
if (_textView is null)
{
return [];
}
List<MenuItem> menuItems = [];
List<CheckBox> radioGroup = [];
void AddRadioItem (string title, CursorVisibility visibility)
{
CheckBox checkBox = new ()
{
Title = title,
CheckedState = _textView.CursorVisibility == visibility ? CheckState.Checked : CheckState.UnChecked
};
radioGroup.Add (checkBox);
checkBox.CheckedStateChanging += (s, e) =>
{
if (e.Result == CheckState.Checked)
{
_textView.CursorVisibility = visibility;
foreach (CheckBox cb in radioGroup)
{
if (cb != checkBox)
{
cb.CheckedState = CheckState.UnChecked;
}
}
}
};
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
menuItems.Add (item);
}
AddRadioItem ("_Invisible", CursorVisibility.Invisible);
AddRadioItem ("_Box", CursorVisibility.Box);
AddRadioItem ("_Underline", CursorVisibility.Underline);
menuItems.Add (new () { Title = "" });
menuItems.Add (new () { Title = "xTerm :" });
menuItems.Add (new () { Title = "" });
AddRadioItem (" _Default", CursorVisibility.Default);
AddRadioItem (" _Vertical", CursorVisibility.Vertical);
AddRadioItem (" V_ertical Fix", CursorVisibility.VerticalFix);
AddRadioItem (" B_ox Fix", CursorVisibility.BoxFix);
AddRadioItem (" U_nderline Fix", CursorVisibility.UnderlineFix);
return [.. menuItems];
}
private MenuItem [] GetSupportedCultures ()
{
if (_cultureInfos is null)
{
return [];
}
List<MenuItem> supportedCultures = [];
List<CheckBox> allCheckBoxes = [];
int index = -1;
void CreateCultureMenuItem (string title, string cultureName, bool isChecked)
{
CheckBox checkBox = new ()
{
Title = title,
CheckedState = isChecked ? CheckState.Checked : CheckState.UnChecked
};
allCheckBoxes.Add (checkBox);
checkBox.CheckedStateChanging += (s, e) =>
{
if (e.Result == CheckState.Checked)
{
Thread.CurrentThread.CurrentUICulture = new (cultureName);
foreach (CheckBox cb in allCheckBoxes)
{
cb.CheckedState = cb == checkBox ? CheckState.Checked : CheckState.UnChecked;
}
}
};
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
supportedCultures.Add (item);
}
foreach (CultureInfo c in _cultureInfos)
{
if (index == -1)
{
CreateCultureMenuItem ("_English", "en-US", Thread.CurrentThread.CurrentUICulture.Name == "en-US");
index++;
}
CreateCultureMenuItem ($"_{c.Parent.EnglishName}", c.Name, Thread.CurrentThread.CurrentUICulture.Name == c.Name);
}
return [.. supportedCultures];
}
private MenuItem CreateWrapChecked ()
{
if (_textView is null)
{
return new () { Title = "Word Wrap" };
}
CheckBox checkBox = new ()
{
Title = "Word Wrap",
CheckedState = _textView.WordWrap ? CheckState.Checked : CheckState.UnChecked
};
checkBox.CheckedStateChanged += (s, e) => { _textView.WordWrap = checkBox.CheckedState == CheckState.Checked; };
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
return item;
}
private MenuItem CreateAutocomplete ()
{
if (_textView is null)
{
return new () { Title = "Autocomplete" };
}
SingleWordSuggestionGenerator singleWordGenerator = new ();
_textView.Autocomplete.SuggestionGenerator = singleWordGenerator;
CheckBox checkBox = new ()
{
Title = "Autocomplete",
CheckedState = CheckState.UnChecked
};
checkBox.CheckedStateChanged += (s, e) =>
{
if (checkBox.CheckedState == CheckState.Checked)
{
singleWordGenerator.AllSuggestions =
Regex.Matches (_textView.Text, "\\w+")
.Select (s => s.Value)
.Distinct ()
.ToList ();
}
else
{
singleWordGenerator.AllSuggestions.Clear ();
}
};
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
return item;
}
private MenuItem CreateAllowsTabChecked ()
{
if (_textView is null)
{
return new () { Title = "Allows Tab" };
}
CheckBox checkBox = new ()
{
Title = "Allows Tab",
CheckedState = _textView.AllowsTab ? CheckState.Checked : CheckState.UnChecked
};
checkBox.CheckedStateChanged += (s, e) => { _textView.AllowsTab = checkBox.CheckedState == CheckState.Checked; };
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
return item;
}
private MenuItem CreateReadOnlyChecked ()
{
if (_textView is null)
{
return new () { Title = "Read Only" };
}
CheckBox checkBox = new ()
{
Title = "Read Only",
CheckedState = _textView.ReadOnly ? CheckState.Checked : CheckState.UnChecked
};
checkBox.CheckedStateChanged += (s, e) => { _textView.ReadOnly = checkBox.CheckedState == CheckState.Checked; };
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
return item;
}
private MenuItem CreateUseSameRuneTypeForWords ()
{
if (_textView is null)
{
return new () { Title = "UseSameRuneTypeForWords" };
}
CheckBox checkBox = new ()
{
Title = "UseSameRuneTypeForWords",
CheckedState = _textView.UseSameRuneTypeForWords ? CheckState.Checked : CheckState.UnChecked
};
checkBox.CheckedStateChanged += (s, e) => { _textView.UseSameRuneTypeForWords = checkBox.CheckedState == CheckState.Checked; };
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
return item;
}
private MenuItem CreateSelectWordOnlyOnDoubleClick ()
{
if (_textView is null)
{
return new () { Title = "SelectWordOnlyOnDoubleClick" };
}
CheckBox checkBox = new ()
{
Title = "SelectWordOnlyOnDoubleClick",
CheckedState = _textView.SelectWordOnlyOnDoubleClick ? CheckState.Checked : CheckState.UnChecked
};
checkBox.CheckedStateChanged += (s, e) => { _textView.SelectWordOnlyOnDoubleClick = checkBox.CheckedState == CheckState.Checked; };
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
return item;
}
private MenuItem CreateCanFocusChecked ()
{
if (_textView is null)
{
return new () { Title = "CanFocus" };
}
CheckBox checkBox = new ()
{
Title = "CanFocus",
CheckedState = _textView.CanFocus ? CheckState.Checked : CheckState.UnChecked
};
checkBox.CheckedStateChanged += (s, e) =>
{
_textView.CanFocus = checkBox.CheckedState == CheckState.Checked;
if (_textView.CanFocus)
{
_textView.SetFocus ();
}
};
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
return item;
}
private MenuItem CreateEnabledChecked ()
{
if (_textView is null)
{
return new () { Title = "Enabled" };
}
CheckBox checkBox = new ()
{
Title = "Enabled",
CheckedState = _textView.Enabled ? CheckState.Checked : CheckState.UnChecked
};
checkBox.CheckedStateChanged += (s, e) =>
{
_textView.Enabled = checkBox.CheckedState == CheckState.Checked;
if (_textView.Enabled)
{
_textView.SetFocus ();
}
};
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
return item;
}
private MenuItem CreateVisibleChecked ()
{
if (_textView is null)
{
return new () { Title = "Visible" };
}
CheckBox checkBox = new ()
{
Title = "Visible",
CheckedState = _textView.Visible ? CheckState.Checked : CheckState.UnChecked
};
checkBox.CheckedStateChanged += (s, e) =>
{
_textView.Visible = checkBox.CheckedState == CheckState.Checked;
if (_textView.Visible)
{
_textView.SetFocus ();
}
};
MenuItem item = new () { CommandView = checkBox };
item.Accepting += (s, e) =>
{
checkBox.AdvanceCheckState ();
e.Handled = true;
};
return item;
}
private void CreateDemoFile (string fileName)
{
StringBuilder sb = new ();
sb.Append ("Hello world.\n");
sb.Append ("This is a test of the Emergency Broadcast System.\n");
for (var i = 0; i < 30; i++)
{
sb.Append (
$"{i} - This is a test with a very long line and many lines to test the ScrollViewBar against the TextView. - {i}\n"
);
}
StreamWriter sw = File.CreateText (fileName);
sw.Write (sb.ToString ());
sw.Close ();
}
private void LoadFile ()
{
if (_fileName is null || _textView is null || _appWindow is null)
{
return;
}
_textView.Load (_fileName);
_originalText = Encoding.Unicode.GetBytes (_textView.Text);
_appWindow.Title = _fileName;
_saved = true;
}
private void New (bool checkChanges = true)
{
if (_appWindow is null || _textView is null)
{
return;
}
if (checkChanges && !CanCloseFile ())
{
return;
}
_appWindow.Title = "Untitled.txt";
_fileName = null!;
_originalText = new MemoryStream ().ToArray ();
_textView.Text = Encoding.Unicode.GetString (_originalText);
}
private void Open ()
{
if (!CanCloseFile ())
{
return;
}
List<IAllowedType> aTypes =
[
new AllowedType (
"Text",
".txt;.bin;.xml;.json",
".txt",
".bin",
".xml",
".json"
),
new AllowedTypeAny ()
];
OpenDialog d = new () { Title = "Open", AllowedTypes = aTypes, AllowsMultipleSelection = false };
Application.Run (d);
if (!d.Canceled && d.FilePaths.Count > 0)
{
_fileName = d.FilePaths [0];
LoadFile ();
}
d.Dispose ();
}
private void Paste () { _textView?.Paste (); }
private void Quit ()
{
if (!CanCloseFile ())
{
return;
}
Application.RequestStop ();
}
private void Replace () { ShowFindReplace (false); }
private void ReplaceAll ()
{
if (_textView is null)
{
return;
}
if (string.IsNullOrEmpty (_textToFind) || (string.IsNullOrEmpty (_textToReplace) && _findReplaceWindow is null))
{
Replace ();
return;
}
if (_textView.ReplaceAllText (_textToFind, _matchCase, _matchWholeWord, _textToReplace))
{
MessageBox.Query (Application.Instance,
"Replace All",
$"All occurrences were replaced for the following specified text: '{_textToReplace}'",
"Ok"
);
}
else
{
MessageBox.Query (Application.Instance,
"Replace All",
$"None of the following specified text was found: '{_textToFind}'",
"Ok"
);
}
}
private void ReplaceNext () { ContinueFind (true, true); }
private void ReplacePrevious () { ContinueFind (false, true); }
private View CreateFindTab ()
{
if (_textView is null)
{
return new ();
}
View d = new ()
{
Width = Dim.Fill (),
Height = Dim.Fill ()
};
int lblWidth = "Replace:".Length;
Label label = new ()
{
Width = lblWidth,
TextAlignment = Alignment.End,
Text = "Find:"
};
d.Add (label);
SetFindText ();
TextField txtToFind = new ()
{
X = Pos.Right (label) + 1,
Y = Pos.Top (label),
Width = Dim.Fill (1),
Text = _textToFind
};
txtToFind.HasFocusChanging += (s, e) => { txtToFind.Text = _textToFind; };
d.Add (txtToFind);
Button btnFindNext = new ()
{
X = Pos.Align (Alignment.Center),
Y = Pos.AnchorEnd (),
Enabled = !string.IsNullOrEmpty (txtToFind.Text),
IsDefault = true,
Text = "Find _Next"
};
btnFindNext.Accepting += (s, e) => { FindNext (); };
d.Add (btnFindNext);
Button btnFindPrevious = new ()
{
X = Pos.Align (Alignment.Center),
Y = Pos.AnchorEnd (),
Enabled = !string.IsNullOrEmpty (txtToFind.Text),
Text = "Find _Previous"
};
btnFindPrevious.Accepting += (s, e) => { FindPrevious (); };
d.Add (btnFindPrevious);
txtToFind.TextChanged += (s, e) =>
{
_textToFind = txtToFind.Text;
_textView.FindTextChanged ();
btnFindNext.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
btnFindPrevious.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
};
CheckBox ckbMatchCase = new ()
{
X = 0,
Y = Pos.Top (txtToFind) + 2,
CheckedState = _matchCase ? CheckState.Checked : CheckState.UnChecked,
Text = "Match c_ase"
};
ckbMatchCase.CheckedStateChanging += (s, e) => { _matchCase = e.Result == CheckState.Checked; };
d.Add (ckbMatchCase);
CheckBox ckbMatchWholeWord = new ()
{
X = 0,
Y = Pos.Top (ckbMatchCase) + 1,
CheckedState = _matchWholeWord ? CheckState.Checked : CheckState.UnChecked,
Text = "Match _whole word"
};
ckbMatchWholeWord.CheckedStateChanging += (s, e) => { _matchWholeWord = e.Result == CheckState.Checked; };
d.Add (ckbMatchWholeWord);
return d;
}
private View CreateReplaceTab ()
{
if (_textView is null)
{
return new ();
}
View d = new ()
{
Width = Dim.Fill (),
Height = Dim.Fill ()
};
int lblWidth = "Replace:".Length;
Label label = new ()
{
Width = lblWidth,
TextAlignment = Alignment.End,
Text = "Find:"
};
d.Add (label);
SetFindText ();
TextField txtToFind = new ()
{
X = Pos.Right (label) + 1,
Y = Pos.Top (label),
Width = Dim.Fill (1),
Text = _textToFind
};
txtToFind.HasFocusChanging += (s, e) => { txtToFind.Text = _textToFind; };
d.Add (txtToFind);
Button btnFindNext = new ()
{
X = Pos.Align (Alignment.Center),
Y = Pos.AnchorEnd (),
Enabled = !string.IsNullOrEmpty (txtToFind.Text),
IsDefault = true,
Text = "Replace _Next"
};
btnFindNext.Accepting += (s, e) => { ReplaceNext (); };
d.Add (btnFindNext);
label = new ()
{
X = Pos.Left (label),
Y = Pos.Top (label) + 1,
Text = "Replace:"
};
d.Add (label);
SetFindText ();
TextField txtToReplace = new ()
{
X = Pos.Right (label) + 1,
Y = Pos.Top (label),
Width = Dim.Fill (1),
Text = _textToReplace
};
txtToReplace.TextChanged += (s, e) => { _textToReplace = txtToReplace.Text; };
d.Add (txtToReplace);
Button btnFindPrevious = new ()
{
X = Pos.Align (Alignment.Center),
Y = Pos.AnchorEnd (),
Enabled = !string.IsNullOrEmpty (txtToFind.Text),
Text = "Replace _Previous"
};
btnFindPrevious.Accepting += (s, e) => { ReplacePrevious (); };
d.Add (btnFindPrevious);
Button btnReplaceAll = new ()
{
X = Pos.Align (Alignment.Center),
Y = Pos.AnchorEnd (),
Enabled = !string.IsNullOrEmpty (txtToFind.Text),
Text = "Replace _All"
};
btnReplaceAll.Accepting += (s, e) => { ReplaceAll (); };
d.Add (btnReplaceAll);
txtToFind.TextChanged += (s, e) =>
{
_textToFind = txtToFind.Text;
_textView.FindTextChanged ();
btnFindNext.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
btnFindPrevious.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
btnReplaceAll.Enabled = !string.IsNullOrEmpty (txtToFind.Text);
};
CheckBox ckbMatchCase = new ()
{
X = 0,
Y = Pos.Top (txtToFind) + 2,
CheckedState = _matchCase ? CheckState.Checked : CheckState.UnChecked,
Text = "Match c_ase"
};
ckbMatchCase.CheckedStateChanging += (s, e) => { _matchCase = e.Result == CheckState.Checked; };
d.Add (ckbMatchCase);
CheckBox ckbMatchWholeWord = new ()
{
X = 0,
Y = Pos.Top (ckbMatchCase) + 1,
CheckedState = _matchWholeWord ? CheckState.Checked : CheckState.UnChecked,
Text = "Match _whole word"
};
ckbMatchWholeWord.CheckedStateChanging += (s, e) => { _matchWholeWord = e.Result == CheckState.Checked; };
d.Add (ckbMatchWholeWord);
return d;
}
private bool Save ()
{
if (_fileName is { } && _appWindow is { })
{
return SaveFile (_appWindow.Title, _fileName);
}
return SaveAs ();
}
private bool SaveAs ()
{
if (_appWindow is null)
{
return false;
}
List<IAllowedType> aTypes =
[
new AllowedType ("Text Files", ".txt", ".bin", ".xml"),
new AllowedTypeAny ()
];
SaveDialog sd = new () { Title = "Save file", AllowedTypes = aTypes };
sd.Path = _appWindow.Title;
Application.Run (sd);
bool canceled = sd.Canceled;
string path = sd.Path;
string fileName = sd.FileName;
sd.Dispose ();
if (!canceled)
{
if (File.Exists (path))
{
if (MessageBox.Query (Application.Instance,
"Save File",
"File already exists. Overwrite any way?",
"No",
"Ok"
)
== 1)
{
return SaveFile (fileName, path);
}
_saved = false;
return _saved;
}
return SaveFile (fileName, path);
}
_saved = false;
return _saved;
}
private bool SaveFile (string title, string file)
{
if (_appWindow is null || _textView is null)
{
return false;
}
try
{
_appWindow.Title = title;
_fileName = file;
File.WriteAllText (_fileName, _textView.Text);
_originalText = Encoding.Unicode.GetBytes (_textView.Text);
_saved = true;
_textView.ClearHistoryChanges ();
MessageBox.Query (Application.Instance, "Save File", "File was successfully saved.", "Ok");
}
catch (Exception ex)
{
MessageBox.ErrorQuery (Application.Instance, "Error", ex.Message, "Ok");
return false;
}
return true;
}
private void SelectAll () { _textView?.SelectAll (); }
private void SetFindText ()
{
if (_textView is null)
{
return;
}
_textToFind = !string.IsNullOrEmpty (_textView.SelectedText) ? _textView.SelectedText :
string.IsNullOrEmpty (_textToFind) ? "" : _textToFind;
_textToReplace = string.IsNullOrEmpty (_textToReplace) ? "" : _textToReplace;
}
private void Cut () { _textView?.Cut (); }
private void Find () { ShowFindReplace (); }
private void FindNext () { ContinueFind (); }
private void FindPrevious () { ContinueFind (false); }
private void ShowFindReplace (bool isFind = true)
{
if (_findReplaceWindow is null || _tabView is null)
{
return;
}
_findReplaceWindow.Visible = true;
_findReplaceWindow.SuperView?.MoveSubViewToStart (_findReplaceWindow);
_tabView.SetFocus ();
_tabView.SelectedTab = isFind ? _tabView.Tabs.ToArray () [0] : _tabView.Tabs.ToArray () [1];
_tabView.SelectedTab?.View?.FocusDeepest (NavigationDirection.Forward, null);
}
private void CreateFindReplace ()
{
if (_textView is null || _appWindow is null)
{
return;
}
_findReplaceWindow = new (_textView);
_tabView = new ()
{
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill (0)
};
_tabView.AddTab (new () { DisplayText = "Find", View = CreateFindTab () }, true);
_tabView.AddTab (new () { DisplayText = "Replace", View = CreateReplaceTab () }, false);
_tabView.SelectedTabChanged += (s, e) => { _tabView.SelectedTab?.View?.FocusDeepest (NavigationDirection.Forward, null); };
_findReplaceWindow.Add (_tabView);
_findReplaceWindow.Visible = false;
_appWindow.Add (_findReplaceWindow);
}
private class FindReplaceWindow : Window
{
private readonly TextView _textView;
public FindReplaceWindow (TextView textView)
{
Title = "Find and Replace";
_textView = textView;
X = Pos.AnchorEnd () - 1;
Y = 2;
Width = 57;
Height = 11;
Arrangement = ViewArrangement.Movable;
KeyBindings.Add (Key.Esc, Command.Cancel);
AddCommand (
Command.Cancel,
() =>
{
Visible = false;
return true;
});
VisibleChanged += FindReplaceWindow_VisibleChanged;
Initialized += FindReplaceWindow_Initialized;
}
private void FindReplaceWindow_Initialized (object? sender, EventArgs e)
{
if (Border is { })
{
Border.LineStyle = LineStyle.Dashed;
Border.Thickness = new (0, 1, 0, 0);
}
}
private void FindReplaceWindow_VisibleChanged (object? sender, EventArgs e)
{
if (!Visible)
{
_textView.SetFocus ();
}
else
{
FocusDeepest (NavigationDirection.Forward, null);
}
}
}
}