mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-30 01:38:01 +01:00
Fixed #3786 - Focus
This commit is contained in:
@@ -77,6 +77,7 @@ public class ApplicationNavigation
|
||||
{
|
||||
return;
|
||||
}
|
||||
Debug.Assert (value is null or { CanFocus: true, HasFocus: true });
|
||||
|
||||
_focused = value;
|
||||
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
/// <param name="previousFocusedView">
|
||||
/// The previously focused view. If <see langword="null"/> there is no previously focused
|
||||
/// <param name="currentFocusedView">
|
||||
/// The currently focused view. If <see langword="null"/> there is no previously focused
|
||||
/// view.
|
||||
/// </param>
|
||||
/// <param name="traversingUp"></param>
|
||||
/// <returns><see langword="true"/> if <see cref="HasFocus"/> was changed to <see langword="true"/>.</returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
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.
|
||||
/// </param>
|
||||
/// <param name="traversingDown">
|
||||
/// 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().
|
||||
/// </param>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>An hex viewer and editor <see cref="View"/> over a <see cref="System.IO.Stream"/></summary>
|
||||
/// <summary>An hex viewer and editor <see cref="View"/> over a <see cref="Stream"/></summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="HexView"/> provides a hex editor on top of a seekable <see cref="Stream"/> with the left side
|
||||
@@ -28,11 +30,12 @@ namespace Terminal.Gui;
|
||||
/// </remarks>
|
||||
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 ()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this <see cref="HexView"/> allow editing of the <see cref="Stream"/> of the underlying
|
||||
/// Gets or sets whether this <see cref="HexView"/> allows editing of the <see cref="Stream"/> of the underlying
|
||||
/// <see cref="Stream"/>.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if allow edits; otherwise, <c>false</c>.</value>
|
||||
/// <value><c>true</c> to allow edits; otherwise, <c>false</c>.</value>
|
||||
public bool AllowEdits { get; set; } = true;
|
||||
|
||||
/// <summary>Gets the current cursor position starting at one for both, line and column.</summary>
|
||||
/// <summary>Gets the current cursor position.</summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///<inheritdoc/>
|
||||
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<long, byte> _edits = [];
|
||||
|
||||
/// <summary>
|
||||
@@ -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?
|
||||
/// <summary>Gets the current character position starting at one, related to the <see cref="Stream"/>.</summary>
|
||||
public long Position => position + 1;
|
||||
|
||||
private long position
|
||||
/// <summary>Gets or sets the current byte position in the <see cref="Stream"/>.</summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the Address column on the left. Set to 0 to hide. The default is 8.
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>Raises the <see cref="PositionChanged"/> event.</summary>
|
||||
protected void RaisePositionChanged ()
|
||||
{
|
||||
HexViewEventArgs args = new (Position, CursorPosition, BytesPerLine);
|
||||
HexViewEventArgs args = new (Address, CursorPosition, BytesPerLine);
|
||||
OnPositionChanged (args);
|
||||
PositionChanged?.Invoke (this, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when <see cref="Position"/> has changed.
|
||||
/// Called when <see cref="Address"/> has changed.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
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;
|
||||
|
||||
@@ -12,12 +12,12 @@ namespace Terminal.Gui;
|
||||
public class HexViewEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>Initializes a new instance of <see cref="HexViewEventArgs"/></summary>
|
||||
/// <param name="pos">The character position.</param>
|
||||
/// <param name="address">The byte position in the steam.</param>
|
||||
/// <param name="cursor">The cursor position.</param>
|
||||
/// <param name="lineLength">Line bytes length.</param>
|
||||
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
|
||||
/// <summary>Gets the current cursor position starting at one for both, line and column.</summary>
|
||||
public Point CursorPosition { get; private set; }
|
||||
|
||||
/// <summary>Gets the current character position starting at one, related to the <see cref="Stream"/>.</summary>
|
||||
public long Position { get; private set; }
|
||||
/// <summary>Gets the byte position in the <see cref="Stream"/>.</summary>
|
||||
public long Address { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>Defines the event arguments for <see cref="HexView.Edited"/> event.</summary>
|
||||
public class HexViewEditEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>Creates a new instance of the <see cref="HexViewEditEventArgs"/> class.</summary>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="newValue"></param>
|
||||
public HexViewEditEventArgs (long position, byte newValue)
|
||||
public HexViewEditEventArgs (long address, byte newValue)
|
||||
{
|
||||
Position = position;
|
||||
Address = address;
|
||||
NewValue = newValue;
|
||||
}
|
||||
|
||||
/// <summary>Gets the new value for that <see cref="Position"/>.</summary>
|
||||
/// <summary>Gets the new value for that <see cref="Address"/>.</summary>
|
||||
public byte NewValue { get; }
|
||||
|
||||
/// <summary>Gets the location of the edit.</summary>
|
||||
public long Position { get; }
|
||||
/// <summary>Gets the adress of the edit in the stream.</summary>
|
||||
public long Address { get; }
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class Shortcut : View, IOrientation, IDesignable
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="targetView">
|
||||
/// The View that <paramref name="command"/> will be invoked when user does something that causes the Shortcut's Accept
|
||||
/// The View that <paramref name="command"/> will be invoked on when user does something that causes the Shortcut's Accept
|
||||
/// event to be raised.
|
||||
/// </param>
|
||||
/// <param name="command">
|
||||
|
||||
@@ -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<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
|
||||
};
|
||||
@@ -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<long> 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 ()
|
||||
|
||||
@@ -387,6 +387,7 @@ public class UICatalogApp
|
||||
// 'app' closed cleanly.
|
||||
foreach (Responder? inst in Responder.Instances)
|
||||
{
|
||||
|
||||
Debug.Assert (inst.WasDisposed);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<long, byte> 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); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user