|
@@ -37,27 +37,44 @@ namespace Terminal.Gui {
|
|
public class HexView : View {
|
|
public class HexView : View {
|
|
SortedDictionary<long, byte> edits = new SortedDictionary<long, byte> ();
|
|
SortedDictionary<long, byte> edits = new SortedDictionary<long, byte> ();
|
|
Stream source;
|
|
Stream source;
|
|
- long displayStart, position;
|
|
|
|
|
|
+ long displayStart, pos;
|
|
bool firstNibble, leftSide;
|
|
bool firstNibble, leftSide;
|
|
|
|
|
|
|
|
+ private long position {
|
|
|
|
+ get => pos;
|
|
|
|
+ set {
|
|
|
|
+ pos = value;
|
|
|
|
+ OnPositionChanged ();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
/// <summary>
|
|
/// <summary>
|
|
- /// Initialzies a <see cref="HexView"/> class using <see cref="LayoutStyle.Computed"/> layout.
|
|
|
|
|
|
+ /// Initializes a <see cref="HexView"/> class using <see cref="LayoutStyle.Computed"/> layout.
|
|
/// </summary>
|
|
/// </summary>
|
|
/// <param name="source">The <see cref="Stream"/> to view and edit as hex, this <see cref="Stream"/> must support seeking, or an exception will be thrown.</param>
|
|
/// <param name="source">The <see cref="Stream"/> to view and edit as hex, this <see cref="Stream"/> must support seeking, or an exception will be thrown.</param>
|
|
public HexView (Stream source) : base ()
|
|
public HexView (Stream source) : base ()
|
|
{
|
|
{
|
|
Source = source;
|
|
Source = source;
|
|
- this.source = source;
|
|
|
|
CanFocus = true;
|
|
CanFocus = true;
|
|
leftSide = true;
|
|
leftSide = true;
|
|
firstNibble = true;
|
|
firstNibble = true;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
- /// Initialzies a <see cref="HexView"/> class using <see cref="LayoutStyle.Computed"/> layout.
|
|
|
|
|
|
+ /// Initializes a <see cref="HexView"/> class using <see cref="LayoutStyle.Computed"/> layout.
|
|
/// </summary>
|
|
/// </summary>
|
|
public HexView () : this (source: new MemoryStream ()) { }
|
|
public HexView () : this (source: new MemoryStream ()) { }
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Event to be invoked when an edit is made on the <see cref="Stream"/>.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public event Action<KeyValuePair<long, byte>> Edited;
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Event to be invoked when the position and cursor position changes.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public event Action<HexViewEventArgs> PositionChanged;
|
|
|
|
+
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Sets or gets the <see cref="Stream"/> the <see cref="HexView"/> is operating on; the stream must support seeking (<see cref="Stream.CanSeek"/> == true).
|
|
/// Sets or gets the <see cref="Stream"/> the <see cref="HexView"/> is operating on; the stream must support seeking (<see cref="Stream.CanSeek"/> == true).
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -71,13 +88,17 @@ namespace Terminal.Gui {
|
|
throw new ArgumentException ("The source stream must be seekable (CanSeek property)", "source");
|
|
throw new ArgumentException ("The source stream must be seekable (CanSeek property)", "source");
|
|
source = value;
|
|
source = value;
|
|
|
|
|
|
|
|
+ if (displayStart > source.Length)
|
|
|
|
+ DisplayStart = 0;
|
|
|
|
+ if (position > source.Length)
|
|
|
|
+ position = 0;
|
|
SetNeedsDisplay ();
|
|
SetNeedsDisplay ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal void SetDisplayStart (long value)
|
|
internal void SetDisplayStart (long value)
|
|
{
|
|
{
|
|
- if (value >= source.Length)
|
|
|
|
|
|
+ if (value > 0 && value >= source.Length)
|
|
displayStart = source.Length - 1;
|
|
displayStart = source.Length - 1;
|
|
else if (value < 0)
|
|
else if (value < 0)
|
|
displayStart = 0;
|
|
displayStart = 0;
|
|
@@ -101,7 +122,14 @@ namespace Terminal.Gui {
|
|
|
|
|
|
const int displayWidth = 9;
|
|
const int displayWidth = 9;
|
|
const int bsize = 4;
|
|
const int bsize = 4;
|
|
- int bytesPerLine;
|
|
|
|
|
|
+ int bpl;
|
|
|
|
+ private int bytesPerLine {
|
|
|
|
+ get => bpl;
|
|
|
|
+ set {
|
|
|
|
+ bpl = value;
|
|
|
|
+ OnPositionChanged ();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
/// <inheritdoc/>
|
|
/// <inheritdoc/>
|
|
public override Rect Frame {
|
|
public override Rect Frame {
|
|
@@ -109,10 +137,10 @@ namespace Terminal.Gui {
|
|
set {
|
|
set {
|
|
base.Frame = value;
|
|
base.Frame = value;
|
|
|
|
|
|
- // Small buffers will just show the position, with 4 bytes
|
|
|
|
- bytesPerLine = 4;
|
|
|
|
|
|
+ // Small buffers will just show the position, with the bsize field value (4 bytes)
|
|
|
|
+ bytesPerLine = bsize;
|
|
if (value.Width - displayWidth > 17)
|
|
if (value.Width - displayWidth > 17)
|
|
- bytesPerLine = 4 * ((value.Width - displayWidth) / 18);
|
|
|
|
|
|
+ bytesPerLine = bsize * ((value.Width - displayWidth) / 18);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -144,8 +172,8 @@ namespace Terminal.Gui {
|
|
|
|
|
|
var frame = Frame;
|
|
var frame = Frame;
|
|
|
|
|
|
- var nblocks = bytesPerLine / 4;
|
|
|
|
- var data = new byte [nblocks * 4 * frame.Height];
|
|
|
|
|
|
+ var nblocks = bytesPerLine / bsize;
|
|
|
|
+ var data = new byte [nblocks * bsize * frame.Height];
|
|
Source.Position = displayStart;
|
|
Source.Position = displayStart;
|
|
var n = source.Read (data, 0, data.Length);
|
|
var n = source.Read (data, 0, data.Length);
|
|
|
|
|
|
@@ -159,38 +187,34 @@ namespace Terminal.Gui {
|
|
|
|
|
|
Move (0, line);
|
|
Move (0, line);
|
|
Driver.SetAttribute (ColorScheme.HotNormal);
|
|
Driver.SetAttribute (ColorScheme.HotNormal);
|
|
- Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * 4));
|
|
|
|
|
|
+ Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * bsize));
|
|
|
|
|
|
currentAttribute = ColorScheme.HotNormal;
|
|
currentAttribute = ColorScheme.HotNormal;
|
|
SetAttribute (GetNormalColor ());
|
|
SetAttribute (GetNormalColor ());
|
|
|
|
|
|
for (int block = 0; block < nblocks; block++) {
|
|
for (int block = 0; block < nblocks; block++) {
|
|
- for (int b = 0; b < 4; b++) {
|
|
|
|
- var offset = (line * nblocks * 4) + block * 4 + b;
|
|
|
|
- bool edited;
|
|
|
|
- var value = GetData (data, offset, out edited);
|
|
|
|
|
|
+ for (int b = 0; b < bsize; b++) {
|
|
|
|
+ var offset = (line * nblocks * bsize) + block * bsize + b;
|
|
|
|
+ var value = GetData (data, offset, out bool edited);
|
|
if (offset + displayStart == position || edited)
|
|
if (offset + displayStart == position || edited)
|
|
SetAttribute (leftSide ? activeColor : trackingColor);
|
|
SetAttribute (leftSide ? activeColor : trackingColor);
|
|
else
|
|
else
|
|
SetAttribute (GetNormalColor ());
|
|
SetAttribute (GetNormalColor ());
|
|
|
|
|
|
- Driver.AddStr (offset >= n ? " " : string.Format ("{0:x2}", value));
|
|
|
|
|
|
+ Driver.AddStr (offset >= n && !edited ? " " : string.Format ("{0:x2}", value));
|
|
SetAttribute (GetNormalColor ());
|
|
SetAttribute (GetNormalColor ());
|
|
Driver.AddRune (' ');
|
|
Driver.AddRune (' ');
|
|
}
|
|
}
|
|
Driver.AddStr (block + 1 == nblocks ? " " : "| ");
|
|
Driver.AddStr (block + 1 == nblocks ? " " : "| ");
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
- for (int bitem = 0; bitem < nblocks * 4; bitem++) {
|
|
|
|
- var offset = line * nblocks * 4 + bitem;
|
|
|
|
-
|
|
|
|
- bool edited = false;
|
|
|
|
- Rune c = ' ';
|
|
|
|
- if (offset >= n)
|
|
|
|
|
|
+ for (int bitem = 0; bitem < nblocks * bsize; bitem++) {
|
|
|
|
+ var offset = line * nblocks * bsize + bitem;
|
|
|
|
+ var b = GetData (data, offset, out bool edited);
|
|
|
|
+ Rune c;
|
|
|
|
+ if (offset >= n && !edited)
|
|
c = ' ';
|
|
c = ' ';
|
|
else {
|
|
else {
|
|
- var b = GetData (data, offset, out edited);
|
|
|
|
if (b < 32)
|
|
if (b < 32)
|
|
c = '.';
|
|
c = '.';
|
|
else if (b > 127)
|
|
else if (b > 127)
|
|
@@ -214,7 +238,6 @@ namespace Terminal.Gui {
|
|
Driver.SetAttribute (attribute);
|
|
Driver.SetAttribute (attribute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
}
|
|
}
|
|
|
|
|
|
///<inheritdoc/>
|
|
///<inheritdoc/>
|
|
@@ -223,13 +246,13 @@ namespace Terminal.Gui {
|
|
var delta = (int)(position - displayStart);
|
|
var delta = (int)(position - displayStart);
|
|
var line = delta / bytesPerLine;
|
|
var line = delta / bytesPerLine;
|
|
var item = delta % bytesPerLine;
|
|
var item = delta % bytesPerLine;
|
|
- var block = item / 4;
|
|
|
|
- var column = (item % 4) * 3;
|
|
|
|
|
|
+ var block = item / bsize;
|
|
|
|
+ var column = (item % bsize) * 3;
|
|
|
|
|
|
if (leftSide)
|
|
if (leftSide)
|
|
Move (displayWidth + block * 14 + column + (firstNibble ? 0 : 1), line);
|
|
Move (displayWidth + block * 14 + column + (firstNibble ? 0 : 1), line);
|
|
else
|
|
else
|
|
- Move (displayWidth + (bytesPerLine / 4) * 14 + item - 1, line);
|
|
|
|
|
|
+ Move (displayWidth + (bytesPerLine / bsize) * 14 + item - 1, line);
|
|
}
|
|
}
|
|
|
|
|
|
void RedisplayLine (long pos)
|
|
void RedisplayLine (long pos)
|
|
@@ -240,13 +263,80 @@ namespace Terminal.Gui {
|
|
SetNeedsDisplay (new Rect (0, line, Frame.Width, 1));
|
|
SetNeedsDisplay (new Rect (0, line, Frame.Width, 1));
|
|
}
|
|
}
|
|
|
|
|
|
- void CursorRight ()
|
|
|
|
|
|
+ bool MoveEndOfLine ()
|
|
|
|
+ {
|
|
|
|
+ position = Math.Min ((position / bytesPerLine * bytesPerLine) + bytesPerLine - 1, source.Length);
|
|
|
|
+ SetNeedsDisplay ();
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool MoveStartOfLine ()
|
|
|
|
+ {
|
|
|
|
+ position = position / bytesPerLine * bytesPerLine;
|
|
|
|
+ SetNeedsDisplay ();
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool MoveEnd ()
|
|
|
|
+ {
|
|
|
|
+ position = source.Length;
|
|
|
|
+ if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
|
|
|
|
+ SetDisplayStart (position);
|
|
|
|
+ SetNeedsDisplay ();
|
|
|
|
+ } else
|
|
|
|
+ RedisplayLine (position);
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool MoveHome ()
|
|
|
|
+ {
|
|
|
|
+ DisplayStart = 0;
|
|
|
|
+ SetNeedsDisplay ();
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool ToggleSide ()
|
|
|
|
+ {
|
|
|
|
+ leftSide = !leftSide;
|
|
|
|
+ RedisplayLine (position);
|
|
|
|
+ firstNibble = true;
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool MoveLeft ()
|
|
|
|
+ {
|
|
|
|
+ RedisplayLine (position);
|
|
|
|
+ if (leftSide) {
|
|
|
|
+ if (!firstNibble) {
|
|
|
|
+ firstNibble = true;
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ firstNibble = false;
|
|
|
|
+ }
|
|
|
|
+ if (position == 0)
|
|
|
|
+ return true;
|
|
|
|
+ if (position - 1 < DisplayStart) {
|
|
|
|
+ SetDisplayStart (displayStart - bytesPerLine);
|
|
|
|
+ SetNeedsDisplay ();
|
|
|
|
+ } else
|
|
|
|
+ RedisplayLine (position);
|
|
|
|
+ position--;
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ bool MoveRight ()
|
|
{
|
|
{
|
|
RedisplayLine (position);
|
|
RedisplayLine (position);
|
|
if (leftSide) {
|
|
if (leftSide) {
|
|
if (firstNibble) {
|
|
if (firstNibble) {
|
|
firstNibble = false;
|
|
firstNibble = false;
|
|
- return;
|
|
|
|
|
|
+ return true;
|
|
} else
|
|
} else
|
|
firstNibble = true;
|
|
firstNibble = true;
|
|
}
|
|
}
|
|
@@ -257,32 +347,44 @@ namespace Terminal.Gui {
|
|
SetNeedsDisplay ();
|
|
SetNeedsDisplay ();
|
|
} else
|
|
} else
|
|
RedisplayLine (position);
|
|
RedisplayLine (position);
|
|
|
|
+
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
|
|
- void MoveUp (int bytes)
|
|
|
|
|
|
+ bool MoveUp (int bytes)
|
|
{
|
|
{
|
|
RedisplayLine (position);
|
|
RedisplayLine (position);
|
|
- position -= bytes;
|
|
|
|
- if (position < 0)
|
|
|
|
- position = 0;
|
|
|
|
|
|
+ if (position - bytes > -1)
|
|
|
|
+ position -= bytes;
|
|
if (position < DisplayStart) {
|
|
if (position < DisplayStart) {
|
|
SetDisplayStart (DisplayStart - bytes);
|
|
SetDisplayStart (DisplayStart - bytes);
|
|
SetNeedsDisplay ();
|
|
SetNeedsDisplay ();
|
|
} else
|
|
} else
|
|
RedisplayLine (position);
|
|
RedisplayLine (position);
|
|
|
|
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
|
|
- void MoveDown (int bytes)
|
|
|
|
|
|
+ bool MoveDown (int bytes)
|
|
{
|
|
{
|
|
RedisplayLine (position);
|
|
RedisplayLine (position);
|
|
if (position + bytes < source.Length)
|
|
if (position + bytes < source.Length)
|
|
position += bytes;
|
|
position += bytes;
|
|
|
|
+ else if ((bytes == bytesPerLine * Frame.Height && source.Length >= (DisplayStart + bytesPerLine * Frame.Height))
|
|
|
|
+ || (bytes <= (bytesPerLine * Frame.Height - bytesPerLine) && source.Length <= (DisplayStart + bytesPerLine * Frame.Height))) {
|
|
|
|
+ var p = position;
|
|
|
|
+ while (p + bytesPerLine < source.Length) {
|
|
|
|
+ p += bytesPerLine;
|
|
|
|
+ }
|
|
|
|
+ position = p;
|
|
|
|
+ }
|
|
if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
|
|
if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
|
|
SetDisplayStart (DisplayStart + bytes);
|
|
SetDisplayStart (DisplayStart + bytes);
|
|
SetNeedsDisplay ();
|
|
SetNeedsDisplay ();
|
|
} else
|
|
} else
|
|
RedisplayLine (position);
|
|
RedisplayLine (position);
|
|
|
|
+
|
|
|
|
+ return true;
|
|
}
|
|
}
|
|
|
|
|
|
/// <inheritdoc/>
|
|
/// <inheritdoc/>
|
|
@@ -290,52 +392,43 @@ namespace Terminal.Gui {
|
|
{
|
|
{
|
|
switch (keyEvent.Key) {
|
|
switch (keyEvent.Key) {
|
|
case Key.CursorLeft:
|
|
case Key.CursorLeft:
|
|
- RedisplayLine (position);
|
|
|
|
- if (leftSide) {
|
|
|
|
- if (!firstNibble) {
|
|
|
|
- firstNibble = true;
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
- firstNibble = false;
|
|
|
|
- }
|
|
|
|
- if (position == 0)
|
|
|
|
- return true;
|
|
|
|
- if (position - 1 < DisplayStart) {
|
|
|
|
- SetDisplayStart (displayStart - bytesPerLine);
|
|
|
|
- SetNeedsDisplay ();
|
|
|
|
- } else
|
|
|
|
- RedisplayLine (position);
|
|
|
|
- position--;
|
|
|
|
- break;
|
|
|
|
|
|
+ return MoveLeft ();
|
|
case Key.CursorRight:
|
|
case Key.CursorRight:
|
|
- CursorRight ();
|
|
|
|
- break;
|
|
|
|
|
|
+ return MoveRight ();
|
|
case Key.CursorDown:
|
|
case Key.CursorDown:
|
|
- MoveDown (bytesPerLine);
|
|
|
|
- break;
|
|
|
|
|
|
+ return MoveDown (bytesPerLine);
|
|
case Key.CursorUp:
|
|
case Key.CursorUp:
|
|
- MoveUp (bytesPerLine);
|
|
|
|
- break;
|
|
|
|
|
|
+ return MoveUp (bytesPerLine);
|
|
case Key.Enter:
|
|
case Key.Enter:
|
|
- leftSide = !leftSide;
|
|
|
|
- RedisplayLine (position);
|
|
|
|
- firstNibble = true;
|
|
|
|
- break;
|
|
|
|
|
|
+ return ToggleSide ();
|
|
case ((int)'v' + Key.AltMask):
|
|
case ((int)'v' + Key.AltMask):
|
|
case Key.PageUp:
|
|
case Key.PageUp:
|
|
- MoveUp (bytesPerLine * Frame.Height);
|
|
|
|
- break;
|
|
|
|
|
|
+ return MoveUp (bytesPerLine * Frame.Height);
|
|
case Key.V | Key.CtrlMask:
|
|
case Key.V | Key.CtrlMask:
|
|
case Key.PageDown:
|
|
case Key.PageDown:
|
|
- MoveDown (bytesPerLine * Frame.Height);
|
|
|
|
- break;
|
|
|
|
|
|
+ return MoveDown (bytesPerLine * Frame.Height);
|
|
case Key.Home:
|
|
case Key.Home:
|
|
- DisplayStart = 0;
|
|
|
|
- SetNeedsDisplay ();
|
|
|
|
- break;
|
|
|
|
|
|
+ return MoveHome ();
|
|
|
|
+ case Key.End:
|
|
|
|
+ return MoveEnd ();
|
|
|
|
+ case Key.CursorLeft | Key.CtrlMask:
|
|
|
|
+ return MoveStartOfLine ();
|
|
|
|
+ case Key.CursorRight | Key.CtrlMask:
|
|
|
|
+ return MoveEndOfLine ();
|
|
|
|
+ case Key.CursorUp | Key.CtrlMask:
|
|
|
|
+ return MoveUp (bytesPerLine * ((int)(position - displayStart) / bytesPerLine));
|
|
|
|
+ case Key.CursorDown | Key.CtrlMask:
|
|
|
|
+ return MoveDown (bytesPerLine * (Frame.Height - 1 - ((int)(position - displayStart) / bytesPerLine)));
|
|
default:
|
|
default:
|
|
|
|
+ if (!AllowEdits)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ // Ignore control characters and other special keys
|
|
|
|
+ if (keyEvent.Key < Key.Space || keyEvent.Key > Key.CharMask)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
if (leftSide) {
|
|
if (leftSide) {
|
|
- int value = -1;
|
|
|
|
|
|
+ int value;
|
|
var k = (char)keyEvent.Key;
|
|
var k = (char)keyEvent.Key;
|
|
if (k >= 'A' && k <= 'F')
|
|
if (k >= 'A' && k <= 'F')
|
|
value = k - 'A' + 10;
|
|
value = k - 'A' + 10;
|
|
@@ -354,18 +447,89 @@ namespace Terminal.Gui {
|
|
RedisplayLine (position);
|
|
RedisplayLine (position);
|
|
if (firstNibble) {
|
|
if (firstNibble) {
|
|
firstNibble = false;
|
|
firstNibble = false;
|
|
- b = (byte)(b & 0xf | (value << 4));
|
|
|
|
|
|
+ b = (byte)(b & 0xf | (value << bsize));
|
|
edits [position] = b;
|
|
edits [position] = b;
|
|
|
|
+ OnEdited (new KeyValuePair<long, byte> (position, edits [position]));
|
|
} else {
|
|
} else {
|
|
b = (byte)(b & 0xf0 | value);
|
|
b = (byte)(b & 0xf0 | value);
|
|
edits [position] = b;
|
|
edits [position] = b;
|
|
- CursorRight ();
|
|
|
|
|
|
+ OnEdited (new KeyValuePair<long, byte> (position, edits [position]));
|
|
|
|
+ MoveRight ();
|
|
}
|
|
}
|
|
return true;
|
|
return true;
|
|
} else
|
|
} else
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
- PositionCursor ();
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Method used to invoke the <see cref="Edited"/> event passing the <see cref="KeyValuePair{TKey, TValue}"/>.
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="keyValuePair">The key value pair.</param>
|
|
|
|
+ public virtual void OnEdited (KeyValuePair<long, byte> keyValuePair)
|
|
|
|
+ {
|
|
|
|
+ Edited?.Invoke (keyValuePair);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Method used to invoke the <see cref="PositionChanged"/> event passing the <see cref="HexViewEventArgs"/> arguments.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public virtual void OnPositionChanged ()
|
|
|
|
+ {
|
|
|
|
+ PositionChanged?.Invoke (new HexViewEventArgs (Position, CursorPosition, BytesPerLine));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <inheritdoc/>
|
|
|
|
+ public override bool MouseEvent (MouseEvent me)
|
|
|
|
+ {
|
|
|
|
+ if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
|
|
|
|
+ && !me.Flags.HasFlag (MouseFlags.WheeledDown) && !me.Flags.HasFlag (MouseFlags.WheeledUp))
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ if (!HasFocus)
|
|
|
|
+ SetFocus ();
|
|
|
|
+
|
|
|
|
+ if (me.Flags == MouseFlags.WheeledDown) {
|
|
|
|
+ DisplayStart = Math.Min (DisplayStart + bytesPerLine, source.Length);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (me.Flags == MouseFlags.WheeledUp) {
|
|
|
|
+ DisplayStart = Math.Max (DisplayStart - bytesPerLine, 0);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (me.X < displayWidth)
|
|
|
|
+ return true;
|
|
|
|
+ var nblocks = bytesPerLine / bsize;
|
|
|
|
+ var blocksSize = nblocks * 14;
|
|
|
|
+ var blocksRightOffset = displayWidth + blocksSize - 1;
|
|
|
|
+ if (me.X > blocksRightOffset + bytesPerLine - 1)
|
|
|
|
+ return true;
|
|
|
|
+ leftSide = me.X >= blocksRightOffset;
|
|
|
|
+ var lineStart = (me.Y * bytesPerLine) + displayStart;
|
|
|
|
+ var x = me.X - displayWidth + 1;
|
|
|
|
+ var block = x / 14;
|
|
|
|
+ x -= block * 2;
|
|
|
|
+ var empty = x % 3;
|
|
|
|
+ var item = x / 3;
|
|
|
|
+ if (!leftSide && item > 0 && (empty == 0 || x == (block * 14) + 14 - 1 - (block * 2)))
|
|
|
|
+ return true;
|
|
|
|
+ firstNibble = true;
|
|
|
|
+ if (leftSide)
|
|
|
|
+ position = Math.Min (lineStart + me.X - blocksRightOffset, source.Length);
|
|
|
|
+ else
|
|
|
|
+ position = Math.Min (lineStart + item, source.Length);
|
|
|
|
+
|
|
|
|
+ if (me.Flags == MouseFlags.Button1DoubleClicked) {
|
|
|
|
+ leftSide = !leftSide;
|
|
|
|
+ if (leftSide)
|
|
|
|
+ firstNibble = empty == 1;
|
|
|
|
+ else
|
|
|
|
+ firstNibble = true;
|
|
|
|
+ }
|
|
|
|
+ SetNeedsDisplay ();
|
|
|
|
+
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -374,7 +538,7 @@ namespace Terminal.Gui {
|
|
/// of the underlying <see cref="Stream"/>.
|
|
/// of the underlying <see cref="Stream"/>.
|
|
/// </summary>
|
|
/// </summary>
|
|
/// <value><c>true</c> if allow edits; otherwise, <c>false</c>.</value>
|
|
/// <value><c>true</c> if allow edits; otherwise, <c>false</c>.</value>
|
|
- public bool AllowEdits { get; set; }
|
|
|
|
|
|
+ public bool AllowEdits { get; set; } = true;
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Gets a <see cref="SortedDictionary{TKey, TValue}"/> describing the edits done to the <see cref="HexView"/>.
|
|
/// Gets a <see cref="SortedDictionary{TKey, TValue}"/> describing the edits done to the <see cref="HexView"/>.
|
|
@@ -384,16 +548,56 @@ namespace Terminal.Gui {
|
|
public IReadOnlyDictionary<long, byte> Edits => edits;
|
|
public IReadOnlyDictionary<long, byte> Edits => edits;
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
- /// This method applies andy edits made to the <see cref="Stream"/> and resets the
|
|
|
|
- /// contents of the <see cref="Edits"/> property
|
|
|
|
|
|
+ /// Gets the current character position starting at one, related to the <see cref="Stream"/>.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public long Position => position + 1;
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Gets the current cursor position starting at one for both, line and column.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public Point CursorPosition {
|
|
|
|
+ get {
|
|
|
|
+ var delta = (int)position;
|
|
|
|
+ var line = delta / bytesPerLine + 1;
|
|
|
|
+ var item = delta % bytesPerLine + 1;
|
|
|
|
+
|
|
|
|
+ return new Point (item, line);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// The bytes length per line.
|
|
/// </summary>
|
|
/// </summary>
|
|
- public void ApplyEdits ()
|
|
|
|
|
|
+ public int BytesPerLine => bytesPerLine;
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// This method applies and edits made to the <see cref="Stream"/> and resets the
|
|
|
|
+ /// contents of the <see cref="Edits"/> property.
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="stream">If provided also applies the changes to the passed <see cref="Stream"/></param>.
|
|
|
|
+ public void ApplyEdits (Stream stream = null)
|
|
{
|
|
{
|
|
foreach (var kv in edits) {
|
|
foreach (var kv in edits) {
|
|
source.Position = kv.Key;
|
|
source.Position = kv.Key;
|
|
source.WriteByte (kv.Value);
|
|
source.WriteByte (kv.Value);
|
|
|
|
+ source.Flush ();
|
|
|
|
+ if (stream != null) {
|
|
|
|
+ stream.Position = kv.Key;
|
|
|
|
+ stream.WriteByte (kv.Value);
|
|
|
|
+ stream.Flush ();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
edits = new SortedDictionary<long, byte> ();
|
|
edits = new SortedDictionary<long, byte> ();
|
|
|
|
+ SetNeedsDisplay ();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// This method discards the edits made to the <see cref="Stream"/> by resetting the
|
|
|
|
+ /// contents of the <see cref="Edits"/> property.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public void DiscardEdits ()
|
|
|
|
+ {
|
|
|
|
+ edits = new SortedDictionary<long, byte> ();
|
|
}
|
|
}
|
|
|
|
|
|
private CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
|
|
private CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
|
|
@@ -411,5 +615,45 @@ namespace Terminal.Gui {
|
|
desiredCursorVisibility = value;
|
|
desiredCursorVisibility = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ ///<inheritdoc/>
|
|
|
|
+ public override bool OnEnter (View view)
|
|
|
|
+ {
|
|
|
|
+ Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
|
|
|
|
+
|
|
|
|
+ return base.OnEnter (view);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Defines the event arguments for <see cref="PositionChanged"/> event.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public class HexViewEventArgs : EventArgs {
|
|
|
|
+ /// <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 current cursor position starting at one for both, line and column.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public Point CursorPosition { get; private set; }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// The bytes length per line.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public int BytesPerLine { get; private set; }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Initializes a new instance of <see cref="HexViewEventArgs"/>
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="pos">The character position.</param>
|
|
|
|
+ /// <param name="cursor">The cursor position.</param>
|
|
|
|
+ /// <param name="lineLength">Line bytes length.</param>
|
|
|
|
+ public HexViewEventArgs (long pos, Point cursor, int lineLength)
|
|
|
|
+ {
|
|
|
|
+ Position = pos;
|
|
|
|
+ CursorPosition = cursor;
|
|
|
|
+ BytesPerLine = lineLength;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|