Files
Terminal.Gui/Examples/UICatalog/Scenarios/HexEditor.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

304 lines
9.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#nullable enable
using System.Text;
namespace UICatalog.Scenarios;
[ScenarioMetadata ("HexEditor", "A binary (hex) editor using the HexView control.")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("Dialogs")]
[ScenarioCategory ("Text and Formatting")]
[ScenarioCategory ("Navigation")]
[ScenarioCategory ("Files and IO")]
public class HexEditor : Scenario
{
private string? _fileName;
private HexView? _hexView;
private MenuItem? _miReadOnly;
private bool _saved = true;
private Shortcut? _scAddress;
private Shortcut? _scInfo;
private Shortcut? _scPosition;
private StatusBar? _statusBar;
public override void Main ()
{
Application.Init ();
var app = new Window ()
{
BorderStyle = LineStyle.None
};
_fileName = "demo.bin";
CreateDemoFile (_fileName);
_hexView = new (new MemoryStream (Encoding.UTF8.GetBytes ("Demo text.")))
{
X = 0,
Y = 1,
Width = Dim.Fill (),
Height = Dim.Fill (1),
Title = _fileName ?? "Untitled",
BorderStyle = LineStyle.Rounded,
};
_hexView.Arrangement = ViewArrangement.Resizable;
_hexView.Edited += _hexView_Edited;
_hexView.PositionChanged += _hexView_PositionChanged;
_hexView.VerticalScrollBar.AutoShow = false;
app.Add (_hexView);
var menu = new MenuBar
{
Menus =
[
new (
"_File",
new MenuItem []
{
new ("_New", "", New),
new ("_Open", "", Open),
new ("_Save", "", Save),
null!, // Passing null automatically creates a separator (a Line object).
new ("_Quit", "", Quit)
}
),
new (
"_Edit",
new MenuItem []
{
new ("_Copy", "", Copy),
new ("C_ut", "", Cut),
new ("_Paste", "", Paste)
}
),
new (
"_Options",
new MenuItem []
{
_miReadOnly = new (
"_Read Only",
"",
ToggleReadOnly
)
{
}
}
)
]
};
CheckBox cb = new CheckBox ()
{
Title = _miReadOnly.Title,
CheckedState = _hexView.ReadOnly ? CheckState.Checked : CheckState.None,
};
_miReadOnly.CommandView = cb;
app.Add (menu);
var addressWidthUpDown = new NumericUpDown
{
Value = _hexView.AddressWidth
};
NumericUpDown<long> addressUpDown = new NumericUpDown<long>
{
Value = _hexView.Address,
Format = $"0x{{0:X{_hexView.AddressWidth}}}"
};
addressWidthUpDown.ValueChanging += (sender, args) =>
{
args.Cancel = args.NewValue is < 0 or > 8;
if (!args.Cancel)
{
_hexView.AddressWidth = args.NewValue;
// ReSharper disable once AccessToDisposedClosure
addressUpDown.Format = $"0x{{0:X{_hexView.AddressWidth}}}";
}
};
addressUpDown.ValueChanging += (sender, args) =>
{
args.Cancel = args.NewValue is < 0;
if (!args.Cancel)
{
_hexView.Address = args.NewValue;
}
};
_statusBar = new (
[
new (Key.F2, "Open", Open),
new (Key.F3, "Save", Save),
new ()
{
CommandView = addressWidthUpDown,
HelpText = "Address Width"
},
_scAddress = new ()
{
CommandView = addressUpDown,
HelpText = "Address:"
},
_scInfo = new (Key.Empty, string.Empty, () => { }),
_scPosition = new (Key.Empty, string.Empty, () => { })
])
{
AlignmentModes = AlignmentModes.IgnoreFirstOrLast
};
app.Add (_statusBar);
_hexView.VerticalScrollBar.AutoShow = true;
_hexView.HorizontalScrollBar.AutoShow = true;
_hexView.Source = LoadFile ();
Application.Run (app);
addressUpDown.Dispose ();
addressWidthUpDown.Dispose ();
app.Dispose ();
Application.Shutdown ();
}
private void _hexView_Edited (object? sender, HexViewEditEventArgs e) { _saved = false; }
private void _hexView_PositionChanged (object? sender, HexViewEventArgs obj)
{
_scInfo!.Title =
$"Bytes: {_hexView!.Source!.Length}";
_scPosition!.Title =
$"L: {obj.Position.Y} C: {obj.Position.X} Per Line: {obj.BytesPerLine}";
if (_scAddress!.CommandView is NumericUpDown<long> addrNumericUpDown)
{
addrNumericUpDown.Value = obj.Address;
}
}
private void Copy () { MessageBox.ErrorQuery (Application.Instance, "Not Implemented", "Functionality not yet implemented.", "Ok"); }
private void CreateDemoFile (string fileName)
{
var sb = new StringBuilder ();
sb.Append ("Hello world.\n");
sb.Append ("This is a test of the Emergency Broadcast System.\n");
StreamWriter sw = File.CreateText (fileName);
sw.Write (sb.ToString ());
sw.Close ();
}
private void CreateUnicodeDemoFile (string fileName)
{
var sb = new StringBuilder ();
sb.Append ("Hello world with wide codepoints: 𝔹A𝔽.\n");
sb.Append ("This is a test of the Emergency Broadcast System.\n");
byte [] buffer = Encoding.Unicode.GetBytes (sb.ToString ());
var ms = new MemoryStream (buffer);
var file = new FileStream (fileName, FileMode.Create, FileAccess.Write);
ms.WriteTo (file);
file.Close ();
ms.Close ();
}
private void Cut () { MessageBox.ErrorQuery (Application.Instance, "Not Implemented", "Functionality not yet implemented.", "Ok"); }
private Stream LoadFile ()
{
var stream = new MemoryStream ();
if (!_saved && _hexView!.Edits.Count > 0 && _hexView.Source is {})
{
if (MessageBox.ErrorQuery (Application.Instance,
"Save",
"The changes were not saved. Want to open without saving?",
"_Yes",
"_No"
)
== 1)
{
return _hexView.Source;
}
_hexView.DiscardEdits ();
_saved = true;
}
if (_fileName is { })
{
byte [] bin = File.ReadAllBytes (_fileName);
stream.Write (bin);
_hexView!.Title = _fileName;
_saved = true;
}
else
{
_hexView!.Title = _fileName ?? "Untitled";
}
return stream;
}
private void New ()
{
_fileName = null;
_hexView!.Source = LoadFile ();
}
private void Open ()
{
var d = new OpenDialog { Title = "Open", AllowsMultipleSelection = false };
Application.Run (d);
if (!d.Canceled)
{
_fileName = d.FilePaths [0];
_hexView!.Source = LoadFile ();
//_hexView.DisplayStart = 0;
}
d.Dispose ();
}
private void Paste () { MessageBox.ErrorQuery (Application.Instance, "Not Implemented", "Functionality not yet implemented.", "_Ok"); }
private void Quit () { Application.RequestStop (); }
private void Save ()
{
if (_fileName != null)
{
using (var fs = new FileStream (_fileName, FileMode.OpenOrCreate))
{
_hexView?.ApplyEdits (fs);
//_hexView.Source.Position = 0;
//_hexView.Source.CopyTo (fs);
//fs.Flush ();
}
_saved = true;
}
else
{
_hexView!.ApplyEdits ();
}
}
private void ToggleReadOnly ()
{
if (_miReadOnly?.CommandView is not CheckBox cb)
{
return;
}
_hexView!.ReadOnly = cb.CheckedState == CheckState.Checked;
}
}