diff --git a/Terminal.Gui/Application/ApplicationNavigation.cs b/Terminal.Gui/Application/ApplicationNavigation.cs
index 2523709d0..23cb2ba11 100644
--- a/Terminal.Gui/Application/ApplicationNavigation.cs
+++ b/Terminal.Gui/Application/ApplicationNavigation.cs
@@ -77,6 +77,7 @@ public class ApplicationNavigation
{
return;
}
+ Debug.Assert (value is null or { CanFocus: true, HasFocus: true });
_focused = value;
diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs
index 02b737896..01cf01beb 100644
--- a/Terminal.Gui/View/View.Hierarchy.cs
+++ b/Terminal.Gui/View/View.Hierarchy.cs
@@ -169,6 +169,13 @@ public partial class View // SuperView/SubView hierarchy management (SuperView,
Debug.Assert (!view.HasFocus);
_subviews.Remove (view);
+
+ // Clean up focus stuff
+ _previouslyFocused = null;
+ if (view._superView is { } && view._superView._previouslyFocused == this)
+ {
+ view._superView._previouslyFocused = null;
+ }
view._superView = null;
SetNeedsLayout ();
diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs
index a8a2022aa..0141f5f87 100644
--- a/Terminal.Gui/View/View.Navigation.cs
+++ b/Terminal.Gui/View/View.Navigation.cs
@@ -180,7 +180,8 @@ public partial class View // Focus and cross-view navigation management (TabStop
if (!_canFocus && HasFocus)
{
// If CanFocus is set to false and this view has focus, make it leave focus
- HasFocus = false;
+ // Set traverssingdown so we don't go back up the hierachy...
+ SetHasFocusFalse (null, traversingDown: false);
}
if (_canFocus && !HasFocus && Visible && SuperView is { Focused: null })
@@ -296,7 +297,12 @@ public partial class View // Focus and cross-view navigation management (TabStop
if (Focused is null && _previouslyFocused is { } && indicies.Contains (_previouslyFocused))
{
- return _previouslyFocused.SetFocus ();
+ if (_previouslyFocused.SetFocus ())
+ {
+ return true;
+ }
+
+ _previouslyFocused = null;
}
return false;
@@ -412,14 +418,14 @@ public partial class View // Focus and cross-view navigation management (TabStop
/// other methods that
/// set or remove focus from a view.
///
- ///
- /// The previously focused view. If there is no previously focused
+ ///
+ /// The currently focused view. If there is no previously focused
/// view.
///
///
/// if was changed to .
///
- private (bool focusSet, bool cancelled) SetHasFocusTrue (View? previousFocusedView, bool traversingUp = false)
+ private (bool focusSet, bool cancelled) SetHasFocusTrue (View? currentFocusedView, bool traversingUp = false)
{
Debug.Assert (SuperView is null || ApplicationNavigation.IsInHierarchy (SuperView, this));
@@ -429,6 +435,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
return (false, false);
}
+ if (currentFocusedView is { HasFocus: false })
+ {
+ throw new ArgumentException ("SetHasFocusTrue: currentFocusedView must HasFocus.");
+ }
+
var thisAsAdornment = this as Adornment;
View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
@@ -451,7 +462,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
bool previousValue = HasFocus;
- bool cancelled = NotifyFocusChanging (false, true, previousFocusedView, this);
+ bool cancelled = NotifyFocusChanging (false, true, currentFocusedView, this);
if (cancelled)
{
@@ -462,7 +473,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
// Any of them may cancel gaining focus. In which case we need to back out.
if (superViewOrParent is { HasFocus: false } sv)
{
- (bool focusSet, bool svCancelled) = sv.SetHasFocusTrue (previousFocusedView, true);
+ (bool focusSet, bool svCancelled) = sv.SetHasFocusTrue (currentFocusedView, true);
if (!focusSet)
{
@@ -488,31 +499,33 @@ public partial class View // Focus and cross-view navigation management (TabStop
if (!traversingUp)
{
- // Restore focus to the previously focused subview
+ // Restore focus to the previously focused subview, if any
if (!RestoreFocus ())
{
- // Couldn't restore focus, so use Advance to navigate to the next focusable subview
- if (!AdvanceFocus (NavigationDirection.Forward, null))
- {
- // Couldn't advance, so we're the most focused view in the application
- Application.Navigation?.SetFocused (this);
- }
+ Debug.Assert (_previouslyFocused is null);
+ // Couldn't restore focus, so use Advance to navigate to the next focusable subview, if any
+ AdvanceFocus (NavigationDirection.Forward, null);
}
}
- if (previousFocusedView is { HasFocus: true } && GetFocusChain (NavigationDirection.Forward, TabStop).Contains (previousFocusedView))
+ // Now make sure the old focused view loses focus
+ if (currentFocusedView is { HasFocus: true } && GetFocusChain (NavigationDirection.Forward, TabStop).Contains (currentFocusedView))
{
- previousFocusedView.SetHasFocusFalse (this);
+ currentFocusedView.SetHasFocusFalse (this);
}
- _previouslyFocused = null;
+ if (_previouslyFocused is { })
+ {
+ _previouslyFocused = null;
+ }
if (Arrangement.HasFlag (ViewArrangement.Overlapped))
{
SuperView?.MoveSubviewToEnd (this);
}
- NotifyFocusChanged (HasFocus, previousFocusedView, this);
+ // Focus work is done. Notify.
+ NotifyFocusChanged (HasFocus, currentFocusedView, this);
SetNeedsDisplay ();
@@ -527,6 +540,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
private bool NotifyFocusChanging (bool currentHasFocus, bool newHasFocus, View? currentFocused, View? newFocused)
{
+ Debug.Assert (currentFocused is null || currentFocused is { HasFocus: true });
+ Debug.Assert (newFocused is null || newFocused is { CanFocus: true });
+
// Call the virtual method
if (OnHasFocusChanging (currentHasFocus, newHasFocus, currentFocused, newFocused))
{
@@ -543,6 +559,13 @@ public partial class View // Focus and cross-view navigation management (TabStop
return true;
}
+ View? appFocused = Application.Navigation?.GetFocused ();
+
+ if (appFocused == currentFocused)
+ {
+ Application.Navigation?.SetFocused (null);
+ }
+
return false;
}
@@ -586,8 +609,8 @@ public partial class View // Focus and cross-view navigation management (TabStop
/// focused.
///
///
- /// Set to true to indicate method is being called recurively, traversing down the focus
- /// chain.
+ /// Set to true to traverse down the focus
+ /// chain only. If false, the method will attempt to AdvanceFocus on the superview or restorefocus on Application.Navigation.GetFocused().
///
///
private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false)
@@ -598,53 +621,60 @@ public partial class View // Focus and cross-view navigation management (TabStop
throw new InvalidOperationException ("SetHasFocusFalse should not be called if the view does not have focus.");
}
+ if (newFocusedView is { HasFocus: false })
+ {
+ throw new InvalidOperationException ("SetHasFocusFalse new focused view does not have focus.");
+ }
+
var thisAsAdornment = this as Adornment;
View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
// If newFocusedVew is null, we need to find the view that should get focus, and SetFocus on it.
if (!traversingDown && newFocusedView is null)
{
- if (superViewOrParent?._previouslyFocused is { })
+ if (superViewOrParent?._previouslyFocused is { CanFocus: true })
{
- if (superViewOrParent._previouslyFocused != this)
+ if (superViewOrParent._previouslyFocused != this && superViewOrParent._previouslyFocused.SetFocus ())
{
- superViewOrParent?._previouslyFocused?.SetFocus ();
-
// The above will cause SetHasFocusFalse, so we can return
+ Debug.Assert (!_hasFocus);
return;
}
}
- if (superViewOrParent is { })
+ if (superViewOrParent is { CanFocus: true })
{
if (superViewOrParent.AdvanceFocus (NavigationDirection.Forward, TabStop))
{
// The above will cause SetHasFocusFalse, so we can return
+ Debug.Assert (!_hasFocus);
return;
}
- newFocusedView = superViewOrParent;
+ if (superViewOrParent is { HasFocus: true, CanFocus: true })
+ {
+ newFocusedView = superViewOrParent;
+ }
}
- if (Application.Navigation is { } && Application.Top is { })
+ if (Application.Navigation is { } && Application.Navigation.GetFocused () is { CanFocus: true })
{
// Temporarily ensure this view can't get focus
bool prevCanFocus = _canFocus;
_canFocus = false;
- bool restoredFocus = Application.Top!.RestoreFocus ();
+ bool restoredFocus = Application.Navigation.GetFocused ()!.RestoreFocus ();
_canFocus = prevCanFocus;
if (restoredFocus)
{
// The above caused SetHasFocusFalse, so we can return
+ Debug.Assert (!_hasFocus);
return;
}
}
-
// No other focusable view to be found. Just "leave" us...
}
-
// Before we can leave focus, we need to make sure that all views down the subview-hierarchy have left focus.
View? mostFocused = MostFocused;
@@ -665,15 +695,7 @@ public partial class View // Focus and cross-view navigation management (TabStop
bottom = bottom.SuperView;
}
- if (bottom == this && bottom.SuperView is Adornment a)
- {
- //a.SetHasFocusFalse (newFocusedView, true);
-
- Debug.Assert (_hasFocus);
- }
-
Debug.Assert (_hasFocus);
-
}
if (superViewOrParent is { })
@@ -684,7 +706,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
bool previousValue = HasFocus;
// Note, can't be cancelled.
- NotifyFocusChanging (HasFocus, !HasFocus, newFocusedView, this);
+ NotifyFocusChanging (HasFocus, !HasFocus, this, newFocusedView);
+
+ Debug.Assert (_hasFocus);
// Get whatever peer has focus, if any so we can update our superview's _previouslyMostFocused
View? focusedPeer = superViewOrParent?.Focused;
@@ -692,16 +716,6 @@ public partial class View // Focus and cross-view navigation management (TabStop
// Set HasFocus false
_hasFocus = false;
- if (Application.Navigation is { })
- {
- View? appFocused = Application.Navigation.GetFocused ();
-
- if (appFocused is { } || appFocused == this)
- {
- Application.Navigation.SetFocused (newFocusedView ?? superViewOrParent);
- }
- }
-
NotifyFocusChanged (HasFocus, this, newFocusedView);
if (_hasFocus)
@@ -721,6 +735,11 @@ public partial class View // Focus and cross-view navigation management (TabStop
private void NotifyFocusChanged (bool newHasFocus, View? previousFocusedView, View? focusedVew)
{
+ if (newHasFocus && focusedVew?.Focused is null)
+ {
+ Application.Navigation?.SetFocused (focusedVew);
+ }
+
// Call the virtual method
OnHasFocusChanged (newHasFocus, previousFocusedView, focusedVew);
diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs
index fb382ab9e..8e3cd335d 100644
--- a/Terminal.Gui/Views/HexView.cs
+++ b/Terminal.Gui/Views/HexView.cs
@@ -1,15 +1,17 @@
#nullable enable
+using System.Diagnostics;
+
//
// HexView.cs: A hexadecimal viewer
//
-// TODO:
-// - Support searching and highlighting of the search result
+// TODO: Support searching and highlighting of the search result
+// TODO: Support growing/shrinking the stream (e.g. del/backspace should work).
//
namespace Terminal.Gui;
-/// An hex viewer and editor over a
+/// An hex viewer and editor over a
///
///
/// provides a hex editor on top of a seekable with the left side
@@ -28,11 +30,12 @@ namespace Terminal.Gui;
///
public class HexView : View, IDesignable
{
- private const int BSIZE = 4;
- private const int DISPLAY_WIDTH = 9;
+ private const int DEFAULT_ADDRESS_WIDTH = 8; // The default value for AddressWidth
+ private const int NUM_BYTES_PER_HEX_COLUMN = 4;
+ private const int HEX_COLUMN_WIDTH = NUM_BYTES_PER_HEX_COLUMN * 3 + 2; // 3 cols per byte + 1 for vert separator + right space
private bool _firstNibble;
- private bool _leftSide;
+ private bool _leftSideHasFocus;
private static readonly Rune _spaceCharRune = new (' ');
private static readonly Rune _periodCharRune = new ('.');
@@ -47,7 +50,7 @@ public class HexView : View, IDesignable
CanFocus = true;
CursorVisibility = CursorVisibility.Default;
- _leftSide = true;
+ _leftSideHasFocus = true;
_firstNibble = true;
// PERF: Closure capture of 'this' creates a lot of overhead.
@@ -66,23 +69,20 @@ public class HexView : View, IDesignable
AddCommand (Command.End, () => MoveEnd ());
AddCommand (Command.LeftStart, () => MoveLeftStart ());
AddCommand (Command.RightEnd, () => MoveEndOfLine ());
- AddCommand (Command.StartOfPage, () => MoveUp (BytesPerLine * ((int)(position - _displayStart) / BytesPerLine)));
+ AddCommand (Command.StartOfPage, () => MoveUp (BytesPerLine * ((int)(Address - _displayStart) / BytesPerLine)));
AddCommand (
Command.EndOfPage,
- () => MoveDown (BytesPerLine * (Frame.Height - 1 - (int)(position - _displayStart) / BytesPerLine))
+ () => MoveDown (BytesPerLine * (Frame.Height - 1 - (int)(Address - _displayStart) / BytesPerLine))
);
- // Default keybindings for this view
KeyBindings.Add (Key.CursorLeft, Command.Left);
KeyBindings.Add (Key.CursorRight, Command.Right);
KeyBindings.Add (Key.CursorDown, Command.Down);
KeyBindings.Add (Key.CursorUp, Command.Up);
- KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
KeyBindings.Add (Key.PageUp, Command.PageUp);
- KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
KeyBindings.Add (Key.PageDown, Command.PageDown);
KeyBindings.Add (Key.Home, Command.Start);
@@ -95,6 +95,9 @@ public class HexView : View, IDesignable
KeyBindings.Add (Key.Tab, Command.Tab);
KeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
+ KeyBindings.Remove (Key.Space);
+ KeyBindings.Remove (Key.Enter);
+
LayoutComplete += HexView_LayoutComplete;
}
@@ -102,30 +105,64 @@ public class HexView : View, IDesignable
public HexView () : this (new MemoryStream ()) { }
///
- /// Gets or sets whether this allow editing of the of the underlying
+ /// Gets or sets whether this allows editing of the of the underlying
/// .
///
- /// true if allow edits; otherwise, false.
+ /// true to allow edits; otherwise, false.
public bool AllowEdits { get; set; } = true;
- /// Gets the current cursor position starting at one for both, line and column.
+ /// Gets the current cursor position.
public Point CursorPosition
{
get
{
- if (!IsInitialized)
+ if (_source is null || BytesPerLine == 0)
{
return Point.Empty;
}
+ var delta = (int)Address;
- var delta = (int)position;
- int line = delta / BytesPerLine + 1;
- int item = delta % BytesPerLine + 1;
+ if (_leftSideHasFocus)
+ {
+ int line = delta / BytesPerLine;
+ int item = delta % BytesPerLine;
- return new (item, line);
+ return new (item, line);
+ }
+ else
+ {
+ int line = delta / BytesPerLine;
+ int item = delta % BytesPerLine;
+
+ return new (item, line);
+ }
}
}
+
+ ///
+ public override Point? PositionCursor ()
+ {
+ var delta = (int)(Address - _displayStart);
+ int line = delta / BytesPerLine;
+ int item = delta % BytesPerLine;
+ int block = item / NUM_BYTES_PER_HEX_COLUMN;
+ int column = item % NUM_BYTES_PER_HEX_COLUMN * 3;
+
+ int x = GetLeftSideStartColumn () + block * HEX_COLUMN_WIDTH + column + (_firstNibble ? 0 : 1);
+ int y = line;
+
+ if (!_leftSideHasFocus)
+ {
+ x = GetLeftSideStartColumn () + BytesPerLine / NUM_BYTES_PER_HEX_COLUMN * HEX_COLUMN_WIDTH + item - 1;
+ }
+
+ Move (x, y);
+
+ return new (x, y);
+ }
+
+
private SortedDictionary _edits = [];
///
@@ -162,9 +199,9 @@ public class HexView : View, IDesignable
DisplayStart = 0;
}
- if (position > _source.Length)
+ if (Address > _source.Length)
{
- position = 0;
+ Address = 0;
}
SetNeedsDisplay ();
@@ -185,18 +222,22 @@ public class HexView : View, IDesignable
}
- private long _pos;
+ private long _address;
- // TODO: Why is this "starting at one"? How does that make sense?
- /// Gets the current character position starting at one, related to the .
- public long Position => position + 1;
-
- private long position
+ /// Gets or sets the current byte position in the .
+ public long Address
{
- get => _pos;
+ get => _address;
set
{
- _pos = value;
+ if (_address == value)
+ {
+ return;
+ }
+
+ //ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual (value, Source!.Length, $"Position");
+
+ _address = value;
RaisePositionChanged ();
}
}
@@ -214,15 +255,39 @@ public class HexView : View, IDesignable
get => _displayStart;
set
{
- position = value;
+ Address = value;
SetDisplayStart (value);
}
}
+ private int _addressWidth = DEFAULT_ADDRESS_WIDTH;
+
+ ///
+ /// Gets or sets the width of the Address column on the left. Set to 0 to hide. The default is 8.
+ ///
+ public int AddressWidth
+ {
+ get => _addressWidth;
+ set
+ {
+ if (_addressWidth == value)
+ {
+ return;
+ }
+ _addressWidth = value;
+ SetNeedsDisplay ();
+ }
+ }
+
+ private int GetLeftSideStartColumn ()
+ {
+ return AddressWidth == 0 ? 0 : AddressWidth + 1;
+ }
+
internal void SetDisplayStart (long value)
{
- if (value > 0 && value >= _source.Length)
+ if (value > 0 && value >= _source?.Length)
{
_displayStart = _source.Length - 1;
}
@@ -306,49 +371,49 @@ public class HexView : View, IDesignable
return true;
}
- if (me.Position.X < DISPLAY_WIDTH)
+ if (me.Position.X < GetLeftSideStartColumn ())
{
return true;
}
- int nblocks = BytesPerLine / BSIZE;
- int blocksSize = nblocks * 14;
- int blocksRightOffset = DISPLAY_WIDTH + blocksSize - 1;
+ int nblocks = BytesPerLine / NUM_BYTES_PER_HEX_COLUMN;
+ int blocksSize = nblocks * HEX_COLUMN_WIDTH;
+ int blocksRightOffset = GetLeftSideStartColumn () + blocksSize - 1;
if (me.Position.X > blocksRightOffset + BytesPerLine - 1)
{
return true;
}
- _leftSide = me.Position.X >= blocksRightOffset;
+ bool clickIsOnLeftSide = me.Position.X >= blocksRightOffset;
long lineStart = me.Position.Y * BytesPerLine + _displayStart;
- int x = me.Position.X - DISPLAY_WIDTH + 1;
- int block = x / 14;
+ int x = me.Position.X - GetLeftSideStartColumn () + 1;
+ int block = x / HEX_COLUMN_WIDTH;
x -= block * 2;
int empty = x % 3;
int item = x / 3;
- if (!_leftSide && item > 0 && (empty == 0 || x == block * 14 + 14 - 1 - block * 2))
+ if (!clickIsOnLeftSide && item > 0 && (empty == 0 || x == block * HEX_COLUMN_WIDTH + HEX_COLUMN_WIDTH - 1 - block * 2))
{
return true;
}
_firstNibble = true;
- if (_leftSide)
+ if (clickIsOnLeftSide)
{
- position = Math.Min (lineStart + me.Position.X - blocksRightOffset, _source.Length);
+ Address = Math.Min (lineStart + me.Position.X - blocksRightOffset, _source.Length - 1);
}
else
{
- position = Math.Min (lineStart + item, _source.Length);
+ Address = Math.Min (lineStart + item, _source.Length - 1);
}
if (me.Flags == MouseFlags.Button1DoubleClicked)
{
- _leftSide = !_leftSide;
+ _leftSideHasFocus = !clickIsOnLeftSide;
- if (_leftSide)
+ if (_leftSideHasFocus)
{
_firstNibble = empty == 1;
}
@@ -376,8 +441,8 @@ public class HexView : View, IDesignable
Driver.SetAttribute (current);
Move (0, 0);
- int nblocks = BytesPerLine / BSIZE;
- var data = new byte [nblocks * BSIZE * viewport.Height];
+ int nblocks = BytesPerLine / NUM_BYTES_PER_HEX_COLUMN;
+ var data = new byte [nblocks * NUM_BYTES_PER_HEX_COLUMN * viewport.Height];
Source.Position = _displayStart;
int n = _source.Read (data, 0, data.Length);
@@ -396,20 +461,26 @@ public class HexView : View, IDesignable
Move (0, line);
currentAttribute = GetHotNormalColor ();
Driver.SetAttribute (currentAttribute);
- Driver.AddStr ($"{_displayStart + line * nblocks * BSIZE:x8} ");
+ string address = $"{_displayStart + line * nblocks * NUM_BYTES_PER_HEX_COLUMN:x8}";
+ Driver.AddStr ($"{address.Substring (8 - AddressWidth)}");
+
+ if (AddressWidth > 0)
+ {
+ Driver.AddStr (" ");
+ }
SetAttribute (GetNormalColor ());
for (var block = 0; block < nblocks; block++)
{
- for (var b = 0; b < BSIZE; b++)
+ for (var b = 0; b < NUM_BYTES_PER_HEX_COLUMN; b++)
{
- int offset = line * nblocks * BSIZE + block * BSIZE + b;
+ int offset = line * nblocks * NUM_BYTES_PER_HEX_COLUMN + block * NUM_BYTES_PER_HEX_COLUMN + b;
byte value = GetData (data, offset, out bool edited);
- if (offset + _displayStart == position || edited)
+ if (offset + _displayStart == Address || edited)
{
- SetAttribute (_leftSide ? activeColor : trackingColor);
+ SetAttribute (_leftSideHasFocus ? activeColor : trackingColor);
}
else
{
@@ -424,9 +495,9 @@ public class HexView : View, IDesignable
Driver.AddStr (block + 1 == nblocks ? " " : "| ");
}
- for (var bitem = 0; bitem < nblocks * BSIZE; bitem++)
+ for (var bitem = 0; bitem < nblocks * NUM_BYTES_PER_HEX_COLUMN; bitem++)
{
- int offset = line * nblocks * BSIZE + bitem;
+ int offset = line * nblocks * NUM_BYTES_PER_HEX_COLUMN + bitem;
byte b = GetData (data, offset, out bool edited);
Rune c;
@@ -450,9 +521,9 @@ public class HexView : View, IDesignable
}
}
- if (offset + _displayStart == position || edited)
+ if (offset + _displayStart == Address || edited)
{
- SetAttribute (_leftSide ? trackingColor : activeColor);
+ SetAttribute (_leftSideHasFocus ? trackingColor : activeColor);
}
else
{
@@ -492,13 +563,13 @@ public class HexView : View, IDesignable
/// Raises the event.
protected void RaisePositionChanged ()
{
- HexViewEventArgs args = new (Position, CursorPosition, BytesPerLine);
+ HexViewEventArgs args = new (Address, CursorPosition, BytesPerLine);
OnPositionChanged (args);
PositionChanged?.Invoke (this, args);
}
///
- /// Called when has changed.
+ /// Called when has changed.
///
protected virtual void OnPositionChanged (HexViewEventArgs e) { }
@@ -519,7 +590,7 @@ public class HexView : View, IDesignable
return false;
}
- if (_leftSide)
+ if (_leftSideHasFocus)
{
int value;
var k = (char)keyEvent.KeyCode;
@@ -543,55 +614,51 @@ public class HexView : View, IDesignable
byte b;
- if (!_edits.TryGetValue (position, out b))
+ if (!_edits.TryGetValue (Address, out b))
{
- _source.Position = position;
+ _source.Position = Address;
b = (byte)_source.ReadByte ();
}
- RedisplayLine (position);
+ // BUGBUG: This makes no sense here.
+ RedisplayLine (Address);
if (_firstNibble)
{
_firstNibble = false;
- b = (byte)((b & 0xf) | (value << BSIZE));
- _edits [position] = b;
- RaiseEdited (new (position, _edits [position]));
+ b = (byte)((b & 0xf) | (value << NUM_BYTES_PER_HEX_COLUMN));
+ _edits [Address] = b;
+ RaiseEdited (new (Address, _edits [Address]));
}
else
{
b = (byte)((b & 0xf0) | value);
- _edits [position] = b;
- RaiseEdited (new (position, _edits [position]));
+ _edits [Address] = b;
+ RaiseEdited (new (Address, _edits [Address]));
MoveRight ();
}
return true;
}
-
- return false;
- }
-
- ///
- public override Point? PositionCursor ()
- {
- var delta = (int)(position - _displayStart);
- int line = delta / BytesPerLine;
- int item = delta % BytesPerLine;
- int block = item / BSIZE;
- int column = item % BSIZE * 3;
-
- int x = DISPLAY_WIDTH + block * 14 + column + (_firstNibble ? 0 : 1);
- int y = line;
-
- if (!_leftSide)
+ else
{
- x = DISPLAY_WIDTH + BytesPerLine / BSIZE * 14 + item - 1;
+ Rune r = keyEvent.AsRune;
+
+ // TODO: Enable entering Tab char - somehow disable Tab for navigation
+
+ _edits [Address] = (byte)(r.Value & 0x00FF);
+ MoveRight ();
+
+ if ((byte)(r.Value & 0xFF00) > 0)
+ {
+ _edits [Address] = (byte)(r.Value & 0xFF00);
+ MoveRight ();
+ }
+
+ //RaiseEdited (new (Address, _edits [Address]));
}
- Move (x, y);
-
- return new (x, y);
+ return false;
}
//
@@ -620,44 +687,44 @@ public class HexView : View, IDesignable
private void HexView_LayoutComplete (object? sender, LayoutEventArgs e)
{
// Small buffers will just show the position, with the bsize field value (4 bytes)
- BytesPerLine = BSIZE;
+ BytesPerLine = NUM_BYTES_PER_HEX_COLUMN;
- if (Viewport.Width - DISPLAY_WIDTH > 17)
+ if (Viewport.Width - GetLeftSideStartColumn () > 17)
{
- BytesPerLine = BSIZE * ((Viewport.Width - DISPLAY_WIDTH) / 18);
+ BytesPerLine = NUM_BYTES_PER_HEX_COLUMN * ((Viewport.Width - GetLeftSideStartColumn ()) / 18);
}
}
private bool MoveDown (int bytes)
{
- RedisplayLine (position);
+ RedisplayLine (Address);
- if (position + bytes < _source.Length)
+ if (Address + bytes < _source.Length)
{
- position += bytes;
+ Address += bytes;
}
else if ((bytes == BytesPerLine * Viewport.Height && _source.Length >= DisplayStart + BytesPerLine * Viewport.Height)
|| (bytes <= BytesPerLine * Viewport.Height - BytesPerLine
&& _source.Length <= DisplayStart + BytesPerLine * Viewport.Height))
{
- long p = position;
+ long p = Address;
while (p + BytesPerLine < _source.Length)
{
p += BytesPerLine;
}
- position = p;
+ Address = p;
}
- if (position >= DisplayStart + BytesPerLine * Viewport.Height)
+ if (Address >= DisplayStart + BytesPerLine * Viewport.Height)
{
SetDisplayStart (DisplayStart + bytes);
SetNeedsDisplay ();
}
else
{
- RedisplayLine (position);
+ RedisplayLine (Address);
}
return true;
@@ -665,16 +732,16 @@ public class HexView : View, IDesignable
private bool MoveEnd ()
{
- position = _source.Length;
+ Address = _source!.Length;
- if (position >= DisplayStart + BytesPerLine * Viewport.Height)
+ if (Address >= DisplayStart + BytesPerLine * Viewport.Height)
{
- SetDisplayStart (position);
+ SetDisplayStart (Address);
SetNeedsDisplay ();
}
else
{
- RedisplayLine (position);
+ RedisplayLine (Address);
}
return true;
@@ -682,7 +749,7 @@ public class HexView : View, IDesignable
private bool MoveEndOfLine ()
{
- position = Math.Min (position / BytesPerLine * BytesPerLine + BytesPerLine - 1, _source.Length);
+ Address = Math.Min (Address / BytesPerLine * BytesPerLine + BytesPerLine - 1, _source!.Length);
SetNeedsDisplay ();
return true;
@@ -698,9 +765,9 @@ public class HexView : View, IDesignable
private bool MoveLeft ()
{
- RedisplayLine (position);
+ RedisplayLine (Address);
- if (_leftSide)
+ if (_leftSideHasFocus)
{
if (!_firstNibble)
{
@@ -712,31 +779,31 @@ public class HexView : View, IDesignable
_firstNibble = false;
}
- if (position == 0)
+ if (Address == 0)
{
return true;
}
- if (position - 1 < DisplayStart)
+ if (Address - 1 < DisplayStart)
{
SetDisplayStart (_displayStart - BytesPerLine);
SetNeedsDisplay ();
}
else
{
- RedisplayLine (position);
+ RedisplayLine (Address);
}
- position--;
+ Address--;
return true;
}
private bool MoveRight ()
{
- RedisplayLine (position);
+ RedisplayLine (Address);
- if (_leftSide)
+ if (_leftSideHasFocus)
{
if (_firstNibble)
{
@@ -748,19 +815,19 @@ public class HexView : View, IDesignable
_firstNibble = true;
}
- if (position < _source.Length)
+ if (Address < _source.Length - 1)
{
- position++;
+ Address++;
}
- if (position >= DisplayStart + BytesPerLine * Viewport.Height)
+ if (Address >= DisplayStart + BytesPerLine * Viewport.Height)
{
SetDisplayStart (DisplayStart + BytesPerLine);
SetNeedsDisplay ();
}
else
{
- RedisplayLine (position);
+ RedisplayLine (Address);
}
return true;
@@ -768,7 +835,7 @@ public class HexView : View, IDesignable
private bool MoveLeftStart ()
{
- position = position / BytesPerLine * BytesPerLine;
+ Address = Address / BytesPerLine * BytesPerLine;
SetNeedsDisplay ();
return true;
@@ -776,21 +843,21 @@ public class HexView : View, IDesignable
private bool MoveUp (int bytes)
{
- RedisplayLine (position);
+ RedisplayLine (Address);
- if (position - bytes > -1)
+ if (Address - bytes > -1)
{
- position -= bytes;
+ Address -= bytes;
}
- if (position < DisplayStart)
+ if (Address < DisplayStart)
{
SetDisplayStart (DisplayStart - bytes);
SetNeedsDisplay ();
}
else
{
- RedisplayLine (position);
+ RedisplayLine (Address);
}
return true;
@@ -814,15 +881,15 @@ public class HexView : View, IDesignable
switch (direction)
{
case NavigationDirection.Forward:
- _leftSide = !_leftSide;
- RedisplayLine (position);
+ _leftSideHasFocus = !_leftSideHasFocus;
+ RedisplayLine (Address);
_firstNibble = true;
return true;
case NavigationDirection.Backward:
- _leftSide = !_leftSide;
- RedisplayLine (position);
+ _leftSideHasFocus = !_leftSideHasFocus;
+ RedisplayLine (Address);
_firstNibble = true;
return true;
diff --git a/Terminal.Gui/Views/HexViewEventArgs.cs b/Terminal.Gui/Views/HexViewEventArgs.cs
index 662d5cc2d..f3f6ddb92 100644
--- a/Terminal.Gui/Views/HexViewEventArgs.cs
+++ b/Terminal.Gui/Views/HexViewEventArgs.cs
@@ -12,12 +12,12 @@ namespace Terminal.Gui;
public class HexViewEventArgs : EventArgs
{
/// Initializes a new instance of
- /// The character position.
+ /// The byte position in the steam.
/// The cursor position.
/// Line bytes length.
- public HexViewEventArgs (long pos, Point cursor, int lineLength)
+ public HexViewEventArgs (long address, Point cursor, int lineLength)
{
- Position = pos;
+ Address = address;
CursorPosition = cursor;
BytesPerLine = lineLength;
}
@@ -28,25 +28,25 @@ public class HexViewEventArgs : EventArgs
/// Gets the current cursor position starting at one for both, line and column.
public Point CursorPosition { get; private set; }
- /// Gets the current character position starting at one, related to the .
- public long Position { get; private set; }
+ /// Gets the byte position in the .
+ public long Address { get; private set; }
}
/// Defines the event arguments for event.
public class HexViewEditEventArgs : EventArgs
{
/// Creates a new instance of the class.
- ///
+ ///
///
- public HexViewEditEventArgs (long position, byte newValue)
+ public HexViewEditEventArgs (long address, byte newValue)
{
- Position = position;
+ Address = address;
NewValue = newValue;
}
- /// Gets the new value for that .
+ /// Gets the new value for that .
public byte NewValue { get; }
- /// Gets the location of the edit.
- public long Position { get; }
+ /// Gets the adress of the edit in the stream.
+ public long Address { get; }
}
diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs
index c1aa85ea4..f23ec23e3 100644
--- a/Terminal.Gui/Views/Shortcut.cs
+++ b/Terminal.Gui/Views/Shortcut.cs
@@ -58,7 +58,7 @@ public class Shortcut : View, IOrientation, IDesignable
///
///
///
- /// The View that will be invoked when user does something that causes the Shortcut's Accept
+ /// The View that will be invoked on when user does something that causes the Shortcut's Accept
/// event to be raised.
///
///
diff --git a/UICatalog/Scenarios/HexEditor.cs b/UICatalog/Scenarios/HexEditor.cs
index 08029a0e7..096c83e6c 100644
--- a/UICatalog/Scenarios/HexEditor.cs
+++ b/UICatalog/Scenarios/HexEditor.cs
@@ -16,27 +16,30 @@ public class HexEditor : Scenario
private HexView _hexView;
private MenuItem _miAllowEdits;
private bool _saved = true;
- private Shortcut _siPositionChanged;
+ private Shortcut _scAddress;
+ private Shortcut _scInfo;
+ private Shortcut _scPosition;
private StatusBar _statusBar;
public override void Main ()
{
Application.Init ();
- Toplevel app = new Toplevel ()
+
+ var app = new Toplevel
{
ColorScheme = Colors.ColorSchemes ["Base"]
};
CreateDemoFile (_fileName);
- _hexView = new HexView (new MemoryStream (Encoding.UTF8.GetBytes ("Demo text.")))
+ _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,
+ BorderStyle = LineStyle.Rounded
};
_hexView.Edited += _hexView_Edited;
_hexView.PositionChanged += _hexView_PositionChanged;
@@ -46,71 +49,97 @@ public class HexEditor : Scenario
{
Menus =
[
- new MenuBarItem (
- "_File",
- new MenuItem []
- {
- new ("_New", "", () => New ()),
- new ("_Open", "", () => Open ()),
- new ("_Save", "", () => Save ()),
- null,
- new ("_Quit", "", () => Quit ())
- }
- ),
- new MenuBarItem (
- "_Edit",
- new MenuItem []
- {
- new ("_Copy", "", () => Copy ()),
- new ("C_ut", "", () => Cut ()),
- new ("_Paste", "", () => Paste ())
- }
- ),
- new MenuBarItem (
- "_Options",
- new []
- {
- _miAllowEdits = new MenuItem (
- "_AllowEdits",
- "",
- () => ToggleAllowEdits ()
- )
- {
- Checked = _hexView.AllowEdits,
- CheckType = MenuItemCheckStyle
- .Checked
- }
- }
- )
+ new (
+ "_File",
+ new MenuItem []
+ {
+ new ("_New", "", () => New ()),
+ new ("_Open", "", () => Open ()),
+ new ("_Save", "", () => Save ()),
+ null,
+ new ("_Quit", "", () => Quit ())
+ }
+ ),
+ new (
+ "_Edit",
+ new MenuItem []
+ {
+ new ("_Copy", "", () => Copy ()),
+ new ("C_ut", "", () => Cut ()),
+ new ("_Paste", "", () => Paste ())
+ }
+ ),
+ new (
+ "_Options",
+ new []
+ {
+ _miAllowEdits = new (
+ "_AllowEdits",
+ "",
+ () => ToggleAllowEdits ()
+ )
+ {
+ Checked = _hexView.AllowEdits,
+ CheckType = MenuItemCheckStyle
+ .Checked
+ }
+ }
+ )
]
};
app.Add (menu);
- _statusBar = new StatusBar (
- new []
- {
- new (Key.F2, "Open", () => Open ()),
- new (Key.F3, "Save", () => Save ()),
- new (
- Application.QuitKey,
- $"Quit",
- () => Quit ()
- ),
- _siPositionChanged = new Shortcut (
- Key.Empty,
- $"Position: {
- _hexView.Position
- } Line: {
- _hexView.CursorPosition.Y
- } Col: {
- _hexView.CursorPosition.X
- } Line length: {
- _hexView.BytesPerLine
- }",
- () => { }
- )
- }
- )
+ var addressWidthUpDown = new NumericUpDown
+ {
+ Value = _hexView.AddressWidth
+ };
+
+ NumericUpDown addressUpDown = new NumericUpDown
+ {
+ 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
};
@@ -119,6 +148,8 @@ public class HexEditor : Scenario
_hexView.Source = LoadFile ();
Application.Run (app);
+ addressUpDown.Dispose ();
+ addressWidthUpDown.Dispose ();
app.Dispose ();
Application.Shutdown ();
}
@@ -127,8 +158,15 @@ public class HexEditor : Scenario
private void _hexView_PositionChanged (object sender, HexViewEventArgs obj)
{
- _siPositionChanged.Title =
- $"Position: {obj.Position} Line: {obj.CursorPosition.Y} Col: {obj.CursorPosition.X} Line length: {obj.BytesPerLine}";
+ _scInfo.Title =
+ $"Bytes: {_hexView.Source!.Length}";
+ _scPosition.Title =
+ $"L: {obj.CursorPosition.Y} C: {obj.CursorPosition.X} Per Line: {obj.BytesPerLine}";
+
+ if (_scAddress.CommandView is NumericUpDown addrNumericUpDown)
+ {
+ addrNumericUpDown.Value = obj.Address;
+ }
}
private void Copy () { MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "Ok"); }
@@ -147,7 +185,7 @@ public class HexEditor : Scenario
private void CreateUnicodeDemoFile (string fileName)
{
var sb = new StringBuilder ();
- sb.Append ("Hello world.\n");
+ 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 ());
@@ -169,8 +207,8 @@ public class HexEditor : Scenario
if (MessageBox.ErrorQuery (
"Save",
"The changes were not saved. Want to open without saving?",
- "Yes",
- "No"
+ "_Yes",
+ "_No"
)
== 1)
{
@@ -190,7 +228,7 @@ public class HexEditor : Scenario
}
else
{
- _hexView.Title = (_fileName ?? "Untitled");
+ _hexView.Title = _fileName ?? "Untitled";
}
return stream;
@@ -213,10 +251,11 @@ public class HexEditor : Scenario
_hexView.Source = LoadFile ();
_hexView.DisplayStart = 0;
}
+
d.Dispose ();
}
- private void Paste () { MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "Ok"); }
+ private void Paste () { MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "_Ok"); }
private void Quit () { Application.RequestStop (); }
private void Save ()
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index 975fcc048..f0b84d866 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -387,6 +387,7 @@ public class UICatalogApp
// 'app' closed cleanly.
foreach (Responder? inst in Responder.Instances)
{
+
Debug.Assert (inst.WasDisposed);
}
diff --git a/UnitTests/Application/Application.NavigationTests.cs b/UnitTests/Application/Application.NavigationTests.cs
index 2f5768ace..26732eaca 100644
--- a/UnitTests/Application/Application.NavigationTests.cs
+++ b/UnitTests/Application/Application.NavigationTests.cs
@@ -68,7 +68,7 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
Application.Navigation.FocusedChanged += ApplicationNavigationOnFocusedChanged;
- Application.Navigation.SetFocused (new ());
+ Application.Navigation.SetFocused (new () { CanFocus = true, HasFocus = true });
Assert.True (raised);
@@ -89,7 +89,7 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
{
Application.Navigation = new ();
- Application.Top = new()
+ Application.Top = new ()
{
Id = "top",
CanFocus = true
@@ -125,7 +125,7 @@ public class ApplicationNavigationTests (ITestOutputHelper output)
{
Application.Navigation = new ();
- Application.Top = new()
+ Application.Top = new ()
{
Id = "top",
CanFocus = true
diff --git a/UnitTests/Views/HexViewTests.cs b/UnitTests/Views/HexViewTests.cs
index 566c4117a..4c5d23081 100644
--- a/UnitTests/Views/HexViewTests.cs
+++ b/UnitTests/Views/HexViewTests.cs
@@ -1,13 +1,42 @@
-using System.Text;
+#nullable enable
+using System.Text;
+using JetBrains.Annotations;
namespace Terminal.Gui.ViewsTests;
public class HexViewTests
{
+ [Theory]
+ [InlineData (0, 4)]
+ [InlineData (9, 4)]
+ [InlineData (20, 4)]
+ [InlineData (24, 4)]
+ [InlineData (30, 4)]
+ [InlineData (50, 4)]
+ public void BytesPerLine_Calculates_Correctly (int width, int expectedBPL)
+ {
+ var hv = new HexView (LoadStream (null, out long _)) { Width = width, Height = 10 };
+ hv.LayoutSubviews ();
+
+ Assert.Equal (expectedBPL, hv.BytesPerLine);
+ }
+
+ [Theory]
+ [InlineData ("01234", 20, 4)]
+ [InlineData ("012345", 20, 4)]
+ public void xuz (string str, int width, int expectedBPL)
+ {
+ var hv = new HexView (LoadStream (str, out long _)) { Width = width, Height = 10 };
+ hv.LayoutSubviews ();
+
+ Assert.Equal (expectedBPL, hv.BytesPerLine);
+ }
+
+
[Fact]
public void AllowEdits_Edits_ApplyEdits ()
{
- var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+ var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
// Needed because HexView relies on LayoutComplete to calc sizes
hv.LayoutSubviews ();
@@ -94,45 +123,50 @@ public class HexViewTests
}
[Fact]
- [AutoInitShutdown]
public void CursorPosition_Encoding_Default ()
{
- var hv = new HexView (LoadStream ()) { Width = Dim.Fill (), Height = Dim.Fill () };
- var top = new Toplevel ();
- top.Add (hv);
- Application.Begin (top);
+ var hv = new HexView (LoadStream (null, out _)) { Width = 100, Height = 100 };
+ Application.Top = new Toplevel ();
+ Application.Top.Add (hv);
- Assert.Equal (new (1, 1), hv.CursorPosition);
+ Application.Top.LayoutSubviews ();
+
+ Assert.Equal (new (0, 0), hv.CursorPosition);
+ Assert.Equal (20, hv.BytesPerLine);
Assert.True (hv.NewKeyDownEvent (Key.Tab));
+ Assert.Equal (new (0, 0), hv.CursorPosition);
+
Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
- Assert.Equal (hv.CursorPosition.X, hv.BytesPerLine);
+ Assert.Equal (hv.CursorPosition.X, hv.BytesPerLine - 1);
Assert.True (hv.NewKeyDownEvent (Key.Home));
Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
- Assert.Equal (new (2, 1), hv.CursorPosition);
+ Assert.Equal (new (1, 0), hv.CursorPosition);
Assert.True (hv.NewKeyDownEvent (Key.CursorDown));
- Assert.Equal (new (2, 2), hv.CursorPosition);
+ Assert.Equal (new (1, 1), hv.CursorPosition);
Assert.True (hv.NewKeyDownEvent (Key.End));
+ Assert.Equal (new (2, 2), hv.CursorPosition);
int col = hv.CursorPosition.X;
int line = hv.CursorPosition.Y;
- int offset = (line - 1) * (hv.BytesPerLine - col);
- Assert.Equal (hv.Position, col * line + offset);
- top.Dispose ();
+ int offset = line * (hv.BytesPerLine - col);
+ Assert.Equal (hv.Address, col * line + offset);
+ Application.Top.Dispose ();
+ Application.ResetState (true);
}
[Fact]
- [AutoInitShutdown]
public void CursorPosition_Encoding_Unicode ()
{
- var hv = new HexView (LoadStream (true)) { Width = Dim.Fill (), Height = Dim.Fill () };
- var top = new Toplevel ();
- top.Add (hv);
- Application.Begin (top);
+ var hv = new HexView (LoadStream (null, out _, true)) { Width = Dim.Fill (), Height = Dim.Fill () };
+ Application.Top = new Toplevel ();
+ Application.Top.Add (hv);
- Assert.Equal (new (1, 1), hv.CursorPosition);
+ hv.LayoutSubviews ();
+
+ Assert.Equal (new (0, 0), hv.CursorPosition);
Assert.True (hv.NewKeyDownEvent (Key.Tab));
Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
@@ -149,14 +183,15 @@ public class HexViewTests
int col = hv.CursorPosition.X;
int line = hv.CursorPosition.Y;
int offset = (line - 1) * (hv.BytesPerLine - col);
- Assert.Equal (hv.Position, col * line + offset);
- top.Dispose ();
+ Assert.Equal (hv.Address, col * line + offset);
+ Application.Top.Dispose ();
+ Application.ResetState (true);
}
[Fact]
public void DiscardEdits_Method ()
{
- var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+ var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
// Needed because HexView relies on LayoutComplete to calc sizes
hv.LayoutSubviews ();
@@ -175,7 +210,7 @@ public class HexViewTests
[Fact]
public void DisplayStart_Source ()
{
- var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+ var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
// Needed because HexView relies on LayoutComplete to calc sizes
hv.LayoutSubviews ();
@@ -196,13 +231,13 @@ public class HexViewTests
[Fact]
public void Edited_Event ()
{
- var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+ var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
// Needed because HexView relies on LayoutComplete to calc sizes
hv.LayoutSubviews ();
KeyValuePair keyValuePair = default;
- hv.Edited += (s, e) => keyValuePair = new (e.Position, e.NewValue);
+ hv.Edited += (s, e) => keyValuePair = new (e.Address, e.NewValue);
Assert.True (hv.NewKeyDownEvent (Key.D4));
Assert.True (hv.NewKeyDownEvent (Key.D6));
@@ -220,143 +255,142 @@ public class HexViewTests
}
[Fact]
- [AutoInitShutdown]
- public void KeyBindings_Command ()
+ public void KeyBindings_Test_Movement_LeftSide ()
{
- var hv = new HexView (LoadStream ()) { Width = 20, Height = 10 };
- var top = new Toplevel ();
- top.Add (hv);
- Application.Begin (top);
+ var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 10 };
+ Application.Top = new Toplevel ();
+ Application.Top.Add (hv);
- Assert.Equal (63, hv.Source.Length);
- Assert.Equal (1, hv.Position);
+ hv.LayoutSubviews ();
+
+ Assert.Equal (MEM_STRING_LENGTH, hv.Source.Length);
+ Assert.Equal (0, hv.Address);
Assert.Equal (4, hv.BytesPerLine);
// right side only needed to press one time
Assert.True (hv.NewKeyDownEvent (Key.Tab));
Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
- Assert.Equal (2, hv.Position);
+ Assert.Equal (1, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
- Assert.Equal (1, hv.Position);
+ Assert.Equal (0, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorDown));
- Assert.Equal (5, hv.Position);
+ Assert.Equal (4, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorUp));
- Assert.Equal (1, hv.Position);
-
- Assert.True (hv.NewKeyDownEvent (Key.V.WithCtrl));
- Assert.Equal (41, hv.Position);
-
- Assert.True (hv.NewKeyDownEvent (new (Key.V.WithAlt)));
- Assert.Equal (1, hv.Position);
+ Assert.Equal (0, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.PageDown));
- Assert.Equal (41, hv.Position);
+ Assert.Equal (40, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.PageUp));
- Assert.Equal (1, hv.Position);
+ Assert.Equal (0, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.End));
- Assert.Equal (64, hv.Position);
+ Assert.Equal (MEM_STRING_LENGTH, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.Home));
- Assert.Equal (1, hv.Position);
+ Assert.Equal (0, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorRight.WithCtrl));
- Assert.Equal (4, hv.Position);
+ Assert.Equal (3, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorLeft.WithCtrl));
- Assert.Equal (1, hv.Position);
+ Assert.Equal (0, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorDown.WithCtrl));
- Assert.Equal (37, hv.Position);
+ Assert.Equal (36, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorUp.WithCtrl));
- Assert.Equal (1, hv.Position);
- top.Dispose ();
+ Assert.Equal (0, hv.Address);
+ Application.Top.Dispose ();
+ Application.ResetState (true);
}
[Fact]
public void Position_Using_Encoding_Default ()
{
- var hv = new HexView (LoadStream ()) { Width = 20, Height = 20 };
+ var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 20 };
+ hv.LayoutSubviews ();
// Needed because HexView relies on LayoutComplete to calc sizes
hv.LayoutSubviews ();
- Assert.Equal (63, hv.Source.Length);
- Assert.Equal (63, hv.Source.Position);
- Assert.Equal (1, hv.Position);
+ Assert.Equal (MEM_STRING_LENGTH, hv.Source.Length);
+ Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+ Assert.Equal (0, hv.Address);
// left side needed to press twice
Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
- Assert.Equal (63, hv.Source.Position);
- Assert.Equal (1, hv.Position);
+ Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+ Assert.Equal (1, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
- Assert.Equal (63, hv.Source.Position);
- Assert.Equal (2, hv.Position);
+ Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+ Assert.Equal (2, hv.Address);
// right side only needed to press one time
Assert.True (hv.NewKeyDownEvent (Key.Tab));
- Assert.Equal (63, hv.Source.Position);
- Assert.Equal (2, hv.Position);
+ Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+ Assert.Equal (2, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
- Assert.Equal (63, hv.Source.Position);
- Assert.Equal (1, hv.Position);
+ Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+ Assert.Equal (1, hv.Address);
// last position is equal to the source length
Assert.True (hv.NewKeyDownEvent (Key.End));
- Assert.Equal (63, hv.Source.Position);
- Assert.Equal (64, hv.Position);
- Assert.Equal (hv.Position - 1, hv.Source.Length);
+ Assert.Equal (MEM_STRING_LENGTH, hv.Source.Position);
+ Assert.Equal (64, hv.Address);
+ Assert.Equal (hv.Address - 1, hv.Source.Length);
}
[Fact]
public void Position_Using_Encoding_Unicode ()
{
- var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+ var hv = new HexView (LoadStream (null, out _, true)) { Width = 20, Height = 20 };
// Needed because HexView relies on LayoutComplete to calc sizes
hv.LayoutSubviews ();
Assert.Equal (126, hv.Source.Length);
Assert.Equal (126, hv.Source.Position);
- Assert.Equal (1, hv.Position);
+ Assert.Equal (1, hv.Address);
// left side needed to press twice
Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
Assert.Equal (126, hv.Source.Position);
- Assert.Equal (1, hv.Position);
+ Assert.Equal (1, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
Assert.Equal (126, hv.Source.Position);
- Assert.Equal (2, hv.Position);
+ Assert.Equal (2, hv.Address);
// right side only needed to press one time
Assert.True (hv.NewKeyDownEvent (Key.Tab));
Assert.Equal (126, hv.Source.Position);
- Assert.Equal (2, hv.Position);
+ Assert.Equal (2, hv.Address);
Assert.True (hv.NewKeyDownEvent (Key.CursorLeft));
Assert.Equal (126, hv.Source.Position);
- Assert.Equal (1, hv.Position);
+ Assert.Equal (1, hv.Address);
// last position is equal to the source length
Assert.True (hv.NewKeyDownEvent (Key.End));
Assert.Equal (126, hv.Source.Position);
- Assert.Equal (127, hv.Position);
- Assert.Equal (hv.Position - 1, hv.Source.Length);
+ Assert.Equal (127, hv.Address);
+ Assert.Equal (hv.Address - 1, hv.Source.Length);
}
[Fact]
- [AutoInitShutdown]
public void PositionChanged_Event ()
{
- var hv = new HexView (LoadStream ()) { Width = Dim.Fill (), Height = Dim.Fill () };
+ var hv = new HexView (LoadStream (null, out _)) { Width = 20, Height = 10 };
+ Application.Top = new Toplevel ();
+ Application.Top.Add (hv);
+
+ Application.Top.LayoutSubviews ();
+
HexViewEventArgs hexViewEventArgs = null;
hv.PositionChanged += (s, e) => hexViewEventArgs = e;
- var top = new Toplevel ();
- top.Add (hv);
- Application.Begin (top);
+
+ Assert.Equal (12, hv.BytesPerLine);
Assert.True (hv.NewKeyDownEvent (Key.CursorRight)); // left side must press twice
Assert.True (hv.NewKeyDownEvent (Key.CursorRight));
@@ -364,87 +398,93 @@ public class HexViewTests
Assert.Equal (12, hexViewEventArgs.BytesPerLine);
Assert.Equal (new (2, 2), hexViewEventArgs.CursorPosition);
- Assert.Equal (14, hexViewEventArgs.Position);
- top.Dispose ();
+ Assert.Equal (14, hexViewEventArgs.Address);
+ Application.Top.Dispose ();
+ Application.ResetState (true);
}
[Fact]
- [AutoInitShutdown]
public void Source_Sets_DisplayStart_And_Position_To_Zero_If_Greater_Than_Source_Length ()
{
- var hv = new HexView (LoadStream ()) { Width = 10, Height = 5 };
- var top = new Toplevel ();
- top.Add (hv);
- Application.Begin (top);
+ var hv = new HexView (LoadStream (null, out _)) { Width = 10, Height = 5 };
+ Application.Top = new Toplevel ();
+ Application.Top.Add (hv);
+
+ hv.LayoutSubviews ();
Assert.True (hv.NewKeyDownEvent (Key.End));
Assert.Equal (62, hv.DisplayStart);
- Assert.Equal (64, hv.Position);
+ Assert.Equal (64, hv.Address);
hv.Source = new MemoryStream ();
Assert.Equal (0, hv.DisplayStart);
- Assert.Equal (0, hv.Position - 1);
+ Assert.Equal (0, hv.Address - 1);
- hv.Source = LoadStream ();
+ hv.Source = LoadStream (null, out _);
hv.Width = Dim.Fill ();
hv.Height = Dim.Fill ();
- top.LayoutSubviews ();
+ Application.Top.LayoutSubviews ();
Assert.Equal (0, hv.DisplayStart);
- Assert.Equal (0, hv.Position - 1);
+ Assert.Equal (0, hv.Address - 1);
Assert.True (hv.NewKeyDownEvent (Key.End));
Assert.Equal (0, hv.DisplayStart);
- Assert.Equal (64, hv.Position);
+ Assert.Equal (64, hv.Address);
hv.Source = new MemoryStream ();
Assert.Equal (0, hv.DisplayStart);
- Assert.Equal (0, hv.Position - 1);
- top.Dispose ();
+ Assert.Equal (0, hv.Address - 1);
+ Application.Top.Dispose ();
+ Application.ResetState (true);
}
- private Stream LoadStream (bool unicode = false)
+ private const string MEM_STRING = "Hello world.\nThis is a test of the Emergency Broadcast System.\n";
+ private const int MEM_STRING_LENGTH = 63;
+
+ private Stream LoadStream (string? memString, out long numBytesInMemString, bool unicode = false)
{
var stream = new MemoryStream ();
byte [] bArray;
- var memString = "Hello world.\nThis is a test of the Emergency Broadcast System.\n";
- Assert.Equal (63, memString.Length);
+ Assert.Equal (MEM_STRING_LENGTH, MEM_STRING.Length);
+
+ if (memString is null)
+ {
+ memString = MEM_STRING;
+ }
if (unicode)
{
bArray = Encoding.Unicode.GetBytes (memString);
- Assert.Equal (126, bArray.Length);
}
else
{
bArray = Encoding.Default.GetBytes (memString);
- Assert.Equal (63, bArray.Length);
}
+ numBytesInMemString = bArray.Length;
stream.Write (bArray);
return stream;
}
- private class NonSeekableStream : Stream
+ private class NonSeekableStream (Stream baseStream) : Stream
{
- private readonly Stream m_stream;
- public NonSeekableStream (Stream baseStream) { m_stream = baseStream; }
- public override bool CanRead => m_stream.CanRead;
+ public override bool CanRead => baseStream.CanRead;
public override bool CanSeek => false;
- public override bool CanWrite => m_stream.CanWrite;
+ public override bool CanWrite => baseStream.CanWrite;
public override long Length => throw new NotSupportedException ();
public override long Position
{
- get => m_stream.Position;
+ get => baseStream.Position;
set => throw new NotSupportedException ();
}
- public override void Flush () { m_stream.Flush (); }
- public override int Read (byte [] buffer, int offset, int count) { return m_stream.Read (buffer, offset, count); }
+ public override void Flush () { baseStream.Flush (); }
+ public override int Read (byte [] buffer, int offset, int count) { return baseStream.Read (buffer, offset, count); }
public override long Seek (long offset, SeekOrigin origin) { throw new NotImplementedException (); }
public override void SetLength (long value) { throw new NotSupportedException (); }
- public override void Write (byte [] buffer, int offset, int count) { m_stream.Write (buffer, offset, count); }
+ public override void Write (byte [] buffer, int offset, int count) { baseStream.Write (buffer, offset, count); }
}
}
diff --git a/UnitTests/Views/ScrollViewTests.cs b/UnitTests/Views/ScrollViewTests.cs
index fd207e28f..149ad2c1d 100644
--- a/UnitTests/Views/ScrollViewTests.cs
+++ b/UnitTests/Views/ScrollViewTests.cs
@@ -874,7 +874,8 @@ public class ScrollViewTests (ITestOutputHelper output)
X = 3,
Y = 3,
Width = 10,
- Height = 10
+ Height = 10,
+ TabStop = TabBehavior.TabStop
};
sv.SetContentSize (new (50, 50));