306 lines
7.7 KiB
C#
306 lines
7.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
|
|
using BizHawk.Client.EmuHawk.CustomControls;
|
|
using BizHawk.Common;
|
|
using BizHawk.Common.NumberExtensions;
|
|
|
|
namespace BizHawk.Client.EmuHawk
|
|
{
|
|
public class HexView : Control
|
|
{
|
|
private readonly IControlRenderer _renderer;
|
|
private readonly Font _font;
|
|
private readonly VScrollBar _vBar = new VScrollBar { Visible = false };
|
|
private Size _charSize;
|
|
private readonly int _cellPadding;
|
|
private long _rowSize = 16;
|
|
private long _arraySize;
|
|
|
|
private int CellMargin => _charSize.Width + _cellPadding;
|
|
|
|
private int NumDigits => DataSize * 2;
|
|
private int NumAddressDigits => ArrayLength.NumHexDigits();
|
|
private int ValueBarStart => (Padding.Left * 2) + (ArrayLength.NumHexDigits() * _charSize.Width) + CellMargin;
|
|
|
|
private int NumValueDigits => (16 / DataSize) * (NumDigits + 1);
|
|
private int CharBarStart => ValueBarStart + (NumValueDigits * _charSize.Width) + _cellPadding;
|
|
|
|
private int CellWidth => ((NumDigits + 1) * _charSize.Width) + _cellPadding;
|
|
private int CellHeight => _charSize.Height + Padding.Top + Padding.Bottom;
|
|
private int VisibleRows => (Height / CellHeight) - 1;
|
|
private long TotalRows => ArrayLength / 16;
|
|
private int FirstVisibleRow => _vBar.Value;
|
|
private int LastVisibleRow => FirstVisibleRow + VisibleRows;
|
|
|
|
public HexView()
|
|
{
|
|
_font = new Font(FontFamily.GenericMonospace, 9); // Only support fixed width
|
|
|
|
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
|
|
SetStyle(ControlStyles.UserPaint, true);
|
|
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
|
|
SetStyle(ControlStyles.Opaque, true);
|
|
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
|
|
|
if (OSTailoredCode.CurrentOS == OSTailoredCode.DistinctOS.Windows)
|
|
{
|
|
//_renderer = new GdiRenderer(); // TODO
|
|
_renderer = new GdiPlusRenderer();
|
|
}
|
|
else
|
|
{
|
|
_renderer = new GdiPlusRenderer();
|
|
}
|
|
|
|
using (var g = CreateGraphics())
|
|
using (_renderer.LockGraphics(g, Width, Height))
|
|
{
|
|
// Measure the font. There seems to be some extra horizontal padding on the first
|
|
// character so we'll see how much the width increases on the second character.
|
|
var s1 = _renderer.MeasureString("0", _font);
|
|
var s2 = _renderer.MeasureString("00", _font);
|
|
_charSize = new Size(s2.Width - s1.Width, s1.Height);
|
|
_cellPadding = s1.Width * 2 - s2.Width; // The padding applied to the first digit;
|
|
}
|
|
|
|
_vBar.Anchor = AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
|
|
_vBar.Location = new Point(0 - _vBar.Width, 0);
|
|
_vBar.Height = Height;
|
|
_vBar.SmallChange = 1;
|
|
_vBar.LargeChange = 16;
|
|
Controls.Add(_vBar);
|
|
_vBar.ValueChanged += VerticalBar_ValueChanged;
|
|
RecalculateScrollBars();
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
_renderer.Dispose();
|
|
_font.Dispose();
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
#region Paint
|
|
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
{
|
|
using (_renderer.LockGraphics(e.Graphics, Width, Height))
|
|
{
|
|
// White Background
|
|
_renderer.SetBrush(SystemColors.Control);
|
|
_renderer.SetSolidPen(SystemColors.Control);
|
|
_renderer.FillRectangle(0, 0, Width, Height);
|
|
|
|
_renderer.SetBrush(ForeColor);
|
|
_renderer.SetSolidPen(BorderColor);
|
|
_renderer.PrepDrawString(_font, ForeColor);
|
|
|
|
DrawLines();
|
|
DrawHeader();
|
|
DrawAddressBar();
|
|
DrawValues();
|
|
}
|
|
}
|
|
|
|
private void DrawLines()
|
|
{
|
|
_renderer.Line(ValueBarStart, CellHeight + Padding.Top, ValueBarStart, Height);
|
|
_renderer.Line(ValueBarStart, CellHeight + Padding.Top, Width, CellHeight + Padding.Top);
|
|
_renderer.Line(CharBarStart + Padding.Left, CellHeight + Padding.Top, CharBarStart + Padding.Left, Height);
|
|
}
|
|
|
|
private void DrawHeader()
|
|
{
|
|
var x = ValueBarStart + CellMargin;
|
|
var y = Padding.Top;
|
|
var sb = new StringBuilder();
|
|
for (int i = 0; i < _rowSize; i += DataSize)
|
|
{
|
|
sb
|
|
.Append(i.ToHexString(NumDigits))
|
|
.Append(" ");
|
|
}
|
|
|
|
_renderer.DrawString(sb.ToString(), new Point(x, y));
|
|
}
|
|
|
|
private void DrawAddressBar()
|
|
{
|
|
for (int i = 0; i <= VisibleRows; i++)
|
|
{
|
|
var addr = ((FirstVisibleRow + i) * 16);
|
|
if (addr <= ArrayLength - 16)
|
|
{
|
|
int x = Padding.Left;
|
|
int y = (i * CellHeight) + CellHeight + Padding.Top;
|
|
var str = addr.ToHexString(NumAddressDigits);
|
|
_renderer.DrawString(str, new Point(x, y));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawValues()
|
|
{
|
|
if (QueryIndexValue != null)
|
|
{
|
|
for (int i = 0; i <= VisibleRows; i++)
|
|
{
|
|
var values = new List<long>();
|
|
long baseAddr = (FirstVisibleRow + i) * 16;
|
|
for (int j = 0; j < 16; j += DataSize)
|
|
{
|
|
long addr = baseAddr + j;
|
|
|
|
if (addr < ArrayLength)
|
|
{
|
|
// ReSharper disable once PossibleNullReferenceException
|
|
QueryIndexValue(addr, DataSize, out long value);
|
|
values.Add(value);
|
|
}
|
|
}
|
|
|
|
DrawDataRow(values, i);
|
|
}
|
|
|
|
for (int i = 0; i <= VisibleRows; i++)
|
|
{
|
|
var values = new List<byte>();
|
|
long baseAddr = (FirstVisibleRow + i) * 16;
|
|
for (int j = 0; j < 16; j++)
|
|
{
|
|
long addr = baseAddr + j;
|
|
|
|
if (addr < ArrayLength)
|
|
{
|
|
// ReSharper disable once PossibleNullReferenceException
|
|
QueryIndexValue(addr, 1, out long value);
|
|
values.Add((byte)value);
|
|
}
|
|
}
|
|
|
|
DrawCharRow(values, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawDataRow(IList<long> values, int index)
|
|
{
|
|
var x = ValueBarStart + CellMargin;
|
|
var y = CellHeight + Padding.Top + (CellHeight * index);
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
for (int j = 0; j < values.Count; j++)
|
|
{
|
|
sb
|
|
.Append(values[j].ToHexString(NumDigits))
|
|
.Append(" ");
|
|
}
|
|
|
|
_renderer.DrawString(sb.ToString(), new Point(x, y));
|
|
}
|
|
|
|
private void DrawCharRow(IList<byte> values, int index)
|
|
{
|
|
var x = CharBarStart + CellMargin;
|
|
var y = CellHeight + Padding.Top + (CellHeight * index);
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
foreach (var v in values)
|
|
{
|
|
sb.Append(ToChar(v));
|
|
}
|
|
|
|
_renderer.DrawString(sb.ToString(), new Point(x, y));
|
|
}
|
|
|
|
private char ToChar(byte val)
|
|
{
|
|
if (val >= 0x7F || val < ' ')
|
|
{
|
|
return '.';
|
|
}
|
|
|
|
char? c = null;
|
|
QueryCharacterOverride?.Invoke(val, out c);
|
|
if (c.HasValue)
|
|
{
|
|
return c.Value;
|
|
}
|
|
|
|
return (char)val;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets or sets the border color of the control.
|
|
/// </summary>
|
|
[Category("CatAppearance")]
|
|
public Color BorderColor { get; set; } = SystemColors.ControlLight;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the total length of the data set to edit
|
|
/// </summary>
|
|
[Category("Behavior")]
|
|
public long ArrayLength
|
|
{
|
|
get => _arraySize;
|
|
set
|
|
{
|
|
_arraySize = value;
|
|
RecalculateScrollBars();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the number of bytes each cell represents
|
|
/// </summary>
|
|
[Category("Behavior")]
|
|
[DefaultValue(1)]
|
|
public int DataSize { get; set; } = 1;
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
[Category("Virtual")]
|
|
public event QueryIndexValueHandler QueryIndexValue;
|
|
|
|
public delegate void QueryIndexValueHandler(long index, int dataSize, out long value);
|
|
|
|
/// <summary>
|
|
/// Defines a character display override, intended to use with custom character tables
|
|
/// </summary>
|
|
[Category("Virtual")]
|
|
public event QueryCharacterOverrideHandler QueryCharacterOverride;
|
|
|
|
public delegate void QueryCharacterOverrideHandler(byte value, out char? character);
|
|
|
|
#endregion
|
|
|
|
private void RecalculateScrollBars()
|
|
{
|
|
_vBar.Visible = TotalRows > VisibleRows;
|
|
_vBar.Minimum = 0;
|
|
_vBar.Maximum = (int)TotalRows - 1;
|
|
_vBar.Refresh();
|
|
}
|
|
|
|
private void VerticalBar_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|