Bläddra i källkod

More HexEdit work

miguel 7 år sedan
förälder
incheckning
11b4e45920
2 ändrade filer med 186 tillägg och 32 borttagningar
  1. 185 31
      Terminal.Gui/Views/HexView.cs
  2. 1 1
      Terminal.Gui/Views/TextField.cs

+ 185 - 31
Terminal.Gui/Views/HexView.cs

@@ -2,21 +2,43 @@
 // HexView.cs: A hexadecimal viewer
 //
 // TODO:
-// - Support an operation to switch between hex and values
-// - Tab perhaps to switch?
-// - Support nibble-based navigation
-// - Support editing, perhaps via list of changes?
-// - Support selection with highlighting
-// - Redraw should support just repainted affected region
-// - Process Key needs to just queue affected region for cursor changes (as we repaint the text)
-
+// - Support searching and highlighting of the search result
+// - Support PageUp/PageDown/Home/End
+// 
 using System;
+using System.Collections.Generic;
 using System.IO;
 
 namespace Terminal.Gui {
+	/// <summary>
+	/// An Hex viewer an editor view over a System.IO.Stream
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// This provides a hex editor on top of a seekable stream with the left side showing an hex
+	/// dump of the values in the stream and the right side showing the contents (filterd to 
+	/// non-control sequence ascii characters).    
+	/// </para>
+	/// <para>
+	/// Users can switch from one side to the other by using the tab key.  
+	/// </para>
+	/// <para>
+	/// If you want to enable editing, set the AllowsEdits property, once that is done, the user
+	/// can make changes to the hexadecimal values of the stream.   Any changes done are tracked
+	/// in the Edits property which is a sorted dictionary indicating the position where the 
+	/// change was made and the new value.    A convenience ApplyEdits method can be used to c
+	/// apply the methods to the underlying stream.
+	/// </para>
+	/// <para>
+	/// It is possible to control the first byte shown by setting the DisplayStart property 
+	/// to the offset that you want to start viewing.
+	/// </para>
+	/// </remarks>
 	public class HexView : View {
+		SortedDictionary<long, byte> edits = new SortedDictionary<long, byte> ();
 		Stream source;
 		long displayStart, position;
+		bool firstNibble, leftSide;
 
 		/// <summary>
 		/// Creates and instance of the HexView that will render a seekable stream in hex on the allocated view region.
@@ -27,6 +49,8 @@ namespace Terminal.Gui {
 			Source = source;
 			this.source = source;
 			CanFocus = true;
+			leftSide = true;
+			firstNibble = true;
 		}
 
 		/// <summary>
@@ -86,6 +110,24 @@ namespace Terminal.Gui {
 			}
 		}
 
+		//
+		// This is used to support editing of the buffer on a peer List<>, 
+		// the offset corresponds to an offset relative to DisplayStart, and
+		// the buffer contains the contents of a screenful of data, so the 
+		// offset is relative to the buffer.
+		//
+		// 
+		byte GetData (byte [] buffer, int offset, out bool edited)
+		{
+			var pos = DisplayStart + offset;
+			if (edits.TryGetValue (pos, out byte v)) {
+				edited = true;
+				return v;
+			}
+			edited = false;
+			return buffer [offset];
+		}
+
 		public override void Redraw (Rect region)
 		{
 			Attribute currentAttribute;
@@ -100,7 +142,14 @@ namespace Terminal.Gui {
 			Source.Position = displayStart;
 			var n = source.Read (data, 0, data.Length);
 
+			int activeColor = ColorScheme.HotNormal;
+			int trackingColor = ColorScheme.HotFocus;
+
 			for (int line = 0; line < frame.Height; line++) {
+				var lineRect = new Rect (0, line, frame.Width, 1);
+				if (!region.Contains (lineRect))
+					continue;
+				
 				Move (0, line);
 				Driver.SetAttribute (ColorScheme.HotNormal);
 				Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * 4));
@@ -111,28 +160,30 @@ namespace Terminal.Gui {
 				for (int block = 0; block < nblocks; block++) {
 					for (int b = 0; b < 4; b++) {
 						var offset = (line * nblocks * 4) + block * 4 + b;
-						if (offset + displayStart == position)
-							SetAttribute (ColorScheme.HotNormal);
+						bool edited;
+						var value = GetData (data, offset, out edited);
+						if (offset + displayStart == position || edited)
+							SetAttribute (leftSide ? activeColor : trackingColor);
 						else
 							SetAttribute (ColorScheme.Normal);
 
-						Driver.AddStr (offset >= n ? "   " : string.Format ("{0:x2} ", data [offset]));
+						Driver.AddStr (offset >= n ? "   " : string.Format ("{0:x2}", value));
+						SetAttribute (ColorScheme.Normal);
+						Driver.AddRune (' ');
 					}
 					Driver.AddStr (block + 1 == nblocks ? " " : "| ");
 				}
+
+
 				for (int bitem = 0; bitem < nblocks * 4; bitem++) {
 					var offset = line * nblocks * 4 + bitem;
 
-					if (offset + displayStart == position)
-						SetAttribute (ColorScheme.HotFocus);
-					else
-						SetAttribute (ColorScheme.Normal);
-					
+					bool edited = false;
 					Rune c = ' ';
 					if (offset >= n)
 						c = ' ';
 					else {
-						var b = data [offset];
+						var b = GetData (data, offset, out edited);
 						if (b < 32)
 							c = '.';
 						else if (b > 127)
@@ -140,6 +191,11 @@ namespace Terminal.Gui {
 						else
 							c = b;
 					}
+					if (offset + displayStart == position || edited)
+						SetAttribute (leftSide ? trackingColor : activeColor);
+					else
+						SetAttribute (ColorScheme.Normal);
+					
 					Driver.AddRune (c);
 				}
 			}
@@ -154,6 +210,9 @@ namespace Terminal.Gui {
 
 		}
 
+		/// <summary>
+		/// Positions the cursor based for the hex view
+		/// </summary>
 		public override void PositionCursor ()
 		{
 			var delta = (int)(position - displayStart);
@@ -162,53 +221,148 @@ namespace Terminal.Gui {
 			var block = item / 4;
 			var column = (item % 4) * 3;
 
-			Move (displayWidth + block * 14 + column, line);
+			if (leftSide)
+				Move (displayWidth + block * 14 + column + (firstNibble ? 0 : 1), line);
+			else
+				Move (displayWidth + (bytesPerLine / 4) * 14 + item - 1, line);
+		}
+
+		void RedisplayLine (long pos)
+		{
+			var delta = (int) (pos - DisplayStart);
+			var line = delta / bytesPerLine;
+
+			SetNeedsDisplay (new Rect (0, line, Frame.Width, 1));
+		}
+
+		void CursorRight ()
+		{
+			RedisplayLine (position);
+			if (leftSide) {
+				if (firstNibble) {
+					firstNibble = false;
+					return;
+				} else
+					firstNibble = true;
+			}
+			if (position < source.Length)
+				position++;
+			if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
+				SetDisplayStart (DisplayStart + bytesPerLine);
+				SetNeedsDisplay ();
+			} else
+				RedisplayLine (position);
 		}
 
 		public override bool ProcessKey (KeyEvent keyEvent)
 		{
 			switch (keyEvent.Key) {
 			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;
 			case Key.CursorRight:
-				if (position < source.Length)
-					position++;
-				if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
-					SetDisplayStart (DisplayStart + bytesPerLine);
-					SetNeedsDisplay ();
-				}
+				CursorRight ();
 				break;
 			case Key.CursorDown:
+				RedisplayLine (position);
 				if (position + bytesPerLine < source.Length)
 					position += bytesPerLine;
 				if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
 					SetDisplayStart (DisplayStart + bytesPerLine);
 					SetNeedsDisplay ();
-				}
+				} else 
+					RedisplayLine (position);
 				break;
 			case Key.CursorUp:
+				RedisplayLine (position);
 				position -= bytesPerLine;
 				if (position < 0)
 					position = 0;
 				if (position < DisplayStart) {
 					SetDisplayStart (DisplayStart - bytesPerLine);
 					SetNeedsDisplay ();
-				} 
+				}  else 
+					RedisplayLine (position);
+				break;
+			case Key.Tab:
+				leftSide = !leftSide;
+				RedisplayLine (position);
+				firstNibble = true;
 				break;
+
 			default:
-				return false;
+				if (leftSide) {
+					int value = -1;
+					var k = (char)keyEvent.Key;
+					if (k >= 'A' && k <= 'F')
+						value = k - 'A' + 10;
+					else if (k >= 'a' && k <= 'f')
+						value = k - 'a' + 10;
+					else if (k >= '0' && k <= '9')
+						value = k - '0';
+					else
+						return false;
+
+					byte b;
+					if (!edits.TryGetValue (position, out b)) {
+						source.Position = position;
+						b = (byte)source.ReadByte ();
+					}
+					RedisplayLine (position);
+					if (firstNibble) {
+						firstNibble = false;
+						b = (byte)(b & 0xf | (value << 4));
+						edits [position] = b;
+					} else {
+						b = (byte)(b & 0xf0 | value);
+						edits [position] = b;
+						CursorRight ();
+					}
+					return true;
+				} else
+					return false;
 			}
-			// TODO: just se the NeedDispay for the affected region, not all
-			SetNeedsDisplay ();
 			PositionCursor ();
-			return false;
+			return true;
+		}
+
+		/// <summary>
+		/// Gets or sets a value indicating whether this <see cref="T:Terminal.Gui.HexView"/> allow editing of the contents of the underlying stream.
+		/// </summary>
+		/// <value><c>true</c> if allow edits; otherwise, <c>false</c>.</value>
+		public bool AllowEdits { get; set; }
+
+		/// <summary>
+		/// Gets a list of the edits done to the buffer which is a sorted dictionary with the positions where the edit took place and the value that was set.
+		/// </summary>
+		/// <value>The edits.</value>
+		public IReadOnlyDictionary<long,byte> Edits => edits;
+
+		/// <summary>
+		/// This method applies the edits to the stream and resets the contents of the Edits property
+		/// </summary>
+		public void ApplyEdits ()
+		{
+			foreach (var kv in edits) {
+				source.Position = kv.Key;
+				source.WriteByte (kv.Value);
+			}
+			edits = new SortedDictionary<long, byte> ();
 		}
 	}
 }

+ 1 - 1
Terminal.Gui/Views/TextField.cs

@@ -254,7 +254,7 @@ namespace Terminal.Gui {
 
 			default:
 				// Ignore other control characters.
-					if (kb.Key < Key.Space || kb.Key > Key.CharMask)
+				if (kb.Key < Key.Space || kb.Key > Key.CharMask)
 					return false;
 
 				var kbstr = ustring.Make ((uint)kb.Key);