using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; using DevExpress.XtraEditors; using DevExpress.XtraEditors.Drawing; using DevExpress.XtraEditors.ViewInfo; namespace Server.Helper.HexEditor; public class HexEditor : BaseControl { public enum CaseStyle { LowerCase, UpperCase } private object _caretLock = new object(); private object _hexTableLock = new object(); private IKeyMouseEventHandler _handler; private EditView _editView; private ByteCollection _hexTable; private string _lineCountCaps = "X"; private int _nrCharsLineCount = 4; private Caret _caret; private Rectangle _recContent; private Rectangle _recLineCount; private StringFormat _stringFormat; private int _firstByte; private int _lastByte; private int _maxBytesH; private int _maxBytesV; private int _maxBytes; private int _maxVisibleBytesV; private DevExpress.XtraEditors.VScrollBar _vScrollBar; private int _vScrollBarWidth = 20; private int _vScrollPos; private int _vScrollMax; private int _vScrollMin; private int _vScrollSmall; private int _vScrollLarge; private SizeF _charSize; private bool _isVScrollHidden = true; private int _bytesPerLine = 8; private int _entityMargin = 10; private BorderStyle _borderStyle = BorderStyle.Fixed3D; private Color _borderColor = Color.Empty; private Color _selectionBackColor = Color.Blue; private Color _selectionForeColor = Color.White; private CaseStyle _lineCountCaseStyle = CaseStyle.UpperCase; private CaseStyle _hexViewCaseStyle = CaseStyle.UpperCase; private bool _isVScrollVisible; private bool _dragging; public override Font Font { set { base.Font = value; UpdateRectanglePositioning(); Invalidate(); } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public override string Text { get { return base.Text; } set { base.Text = value; } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public byte[] HexTable { get { lock (_hexTableLock) { return _hexTable.ToArray(); } } set { lock (_hexTableLock) { if (value == _hexTable.ToArray()) { return; } _hexTable = new ByteCollection(value); } UpdateRectanglePositioning(); Invalidate(); } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public SizeF CharSize { get { return _charSize; } private set { if (!(_charSize == value)) { _charSize = value; if (this.CharSizeChanged != null) { this.CharSizeChanged(this, EventArgs.Empty); } } } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int MaxBytesV => _maxBytesV; [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int FirstVisibleByte => _firstByte; [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int LastVisibleByte => _lastByte; [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool VScrollBarHidden { get { return _isVScrollHidden; } set { if (_isVScrollHidden != value) { _isVScrollHidden = value; if (!_isVScrollHidden) { base.Controls.Add(_vScrollBar); } else { base.Controls.Remove(_vScrollBar); } UpdateRectanglePositioning(); Invalidate(); } } } [DefaultValue(8)] [Category("Hex")] [Description("Property that specifies the number of bytes to display per line.")] public int BytesPerLine { get { return _bytesPerLine; } set { if (_bytesPerLine != value) { _bytesPerLine = value; UpdateRectanglePositioning(); Invalidate(); } } } [DefaultValue(10)] [Category("Hex")] [Description("Property that specifies the margin between each of the entitys in the control.")] public int EntityMargin { get { return _entityMargin; } set { if (_entityMargin != value) { _entityMargin = value; UpdateRectanglePositioning(); Invalidate(); } } } [DefaultValue(BorderStyle.Fixed3D)] [Category("Appearance")] [Description("Indicates where the control should have a border.")] public new BorderStyle BorderStyle { get { return _borderStyle; } set { if (_borderStyle != value) { if (value != BorderStyle.FixedSingle) { _borderColor = Color.Empty; } _borderStyle = value; UpdateRectanglePositioning(); Invalidate(); } } } [DefaultValue(typeof(Color), "Empty")] [Category("Appearance")] [Description("Indicates the color to be used when displaying a FixedSingle border.")] public Color BorderColor { get { return _borderColor; } set { if (BorderStyle == BorderStyle.FixedSingle && !(_borderColor == value)) { _borderColor = value; Invalidate(); } } } [DefaultValue(typeof(Color), "Blue")] [Category("Hex")] [Description("Property for the background color of the selected text areas.")] public Color SelectionBackColor { get { return _selectionBackColor; } set { if (!(_selectionBackColor == value)) { _selectionBackColor = value; } } } [DefaultValue(typeof(Color), "White")] [Category("Hex")] [Description("Property for the foreground color of the selected text areas.")] public Color SelectionForeColor { get { return _selectionForeColor; } set { if (!(_selectionForeColor == value)) { _selectionForeColor = value; } } } [DefaultValue(CaseStyle.UpperCase)] [Category("Hex")] [Description("Property for the case type to use on the line counter.")] public CaseStyle LineCountCaseStyle { get { return _lineCountCaseStyle; } set { if (_lineCountCaseStyle != value) { _lineCountCaseStyle = value; if (_lineCountCaseStyle == CaseStyle.LowerCase) { _lineCountCaps = "x"; } else { _lineCountCaps = "X"; } Invalidate(); } } } [DefaultValue(CaseStyle.UpperCase)] [Category("Hex")] [Description("Property for the case type to use for the hexadecimal values view.")] public CaseStyle HexViewCaseStyle { get { return _hexViewCaseStyle; } set { if (_hexViewCaseStyle != value) { _hexViewCaseStyle = value; if (_hexViewCaseStyle == CaseStyle.LowerCase) { _editView.SetLowerCase(); } else { _editView.SetUpperCase(); } Invalidate(); } } } [DefaultValue(false)] [Category("Hex")] [Description("Property for the visibility of the vertical scrollbar.")] public bool VScrollBarVisisble { get { return _isVScrollVisible; } set { if (_isVScrollVisible != value) { _isVScrollVisible = value; UpdateRectanglePositioning(); Invalidate(); } } } public int CaretPosX { get { lock (_caretLock) { return _caret.Location.X; } } } public int CaretPosY { get { lock (_caretLock) { return _caret.Location.Y; } } } public int SelectionStart { get { lock (_caretLock) { return _caret.SelectionStart; } } } public int SelectionLength { get { lock (_caretLock) { return _caret.SelectionLength; } } } public int CaretIndex { get { lock (_caretLock) { return _caret.CurrentIndex; } } } public bool CaretFocused { get { lock (_caretLock) { return _caret.Focused; } } } public int HexTableLength { get { lock (_hexTableLock) { return _hexTable.Length; } } } protected override BaseControlPainter Painter => new BaseControlPainter(); protected override BaseControlViewInfo ViewInfo => new BaseControlViewInfo(this); [Description("Event that is triggered whenever the hextable has been changed.")] public event EventHandler HexTableChanged; [Description("Event that is triggered whenever the SelectionStart value is changed.")] public event EventHandler SelectionStartChanged; [Description("Event that is triggered whenever the SelectionLength value is changed.")] public event EventHandler SelectionLengthChanged; [Description("Event that is triggered whenever the size of the char is changed.")] public event EventHandler CharSizeChanged; protected void OnVScrollBarScroll(object sender, ScrollEventArgs e) { switch (e.Type) { case ScrollEventType.SmallIncrement: ScrollLineDown(1); break; case ScrollEventType.SmallDecrement: ScrollLineUp(1); break; case ScrollEventType.LargeIncrement: ScrollLineDown(_vScrollLarge); break; case ScrollEventType.LargeDecrement: ScrollLineUp(_vScrollLarge); break; case ScrollEventType.ThumbTrack: ScrollThumbTrack(e.NewValue - e.OldValue); break; } Invalidate(); } protected void CaretSelectionStartChanged(object sender, EventArgs e) { if (this.SelectionStartChanged != null) { this.SelectionStartChanged(this, e); } } protected void CaretSelectionLengthChanged(object sender, EventArgs e) { if (this.SelectionLengthChanged != null) { this.SelectionLengthChanged(this, e); } } protected override void OnMarginChanged(EventArgs e) { base.OnMarginChanged(e); UpdateRectanglePositioning(); Invalidate(); } protected override void OnGotFocus(EventArgs e) { if (_handler != null) { _handler.OnGotFocus(e); } UpdateRectanglePositioning(); Invalidate(); base.OnGotFocus(e); } protected override void OnLostFocus(EventArgs e) { _dragging = false; DestroyCaret(); base.OnLostFocus(e); } protected override bool IsInputKey(Keys keyData) { if ((uint)(keyData - 37) <= 3u || (uint)(keyData - 65573) <= 3u) { return true; } return base.IsInputKey(keyData); } protected override void OnKeyPress(KeyPressEventArgs e) { if (_handler != null) { _handler.OnKeyPress(e); } base.OnKeyPress(e); } protected override void OnKeyDown(KeyEventArgs e) { if (e.KeyCode == Keys.Next) { if (!_isVScrollHidden) { ScrollLineDown(_vScrollLarge); Invalidate(); } } else if (e.KeyCode == Keys.Prior) { if (!_isVScrollHidden) { ScrollLineUp(_vScrollLarge); Invalidate(); } } else if (_handler != null) { _handler.OnKeyDown(e); } base.OnKeyDown(e); } protected override void OnKeyUp(KeyEventArgs e) { if (_handler != null) { _handler.OnKeyUp(e); } base.OnKeyUp(e); } protected override void OnMouseDown(MouseEventArgs e) { if (Focused) { if (_handler != null) { _handler.OnMouseDown(e); } if (e.Button == MouseButtons.Left) { _dragging = true; Invalidate(); } } else { Focus(); } base.OnMouseDown(e); } protected override void OnMouseMove(MouseEventArgs e) { if (Focused && _dragging) { if (_handler != null) { _handler.OnMouseDragged(e); } Invalidate(); } base.OnMouseMove(e); } protected override void OnMouseUp(MouseEventArgs e) { _dragging = false; if (Focused && _handler != null) { _handler.OnMouseUp(e); } base.OnMouseUp(e); } protected override void OnMouseDoubleClick(MouseEventArgs e) { if (Focused && _handler != null) { _handler.OnMouseDoubleClick(e); } base.OnMouseDoubleClick(e); } public void SetCaretStart(int index, Point location) { location = ScrollToCaret(index, location); lock (_caretLock) { _caret.SetStartIndex(index); _caret.SetCaretLocation(location); } Invalidate(); } public void SetCaretEnd(int index, Point location) { location = ScrollToCaret(index, location); lock (_caretLock) { _caret.SetEndIndex(index); _caret.SetCaretLocation(location); } Invalidate(); } public bool IsSelected(int byteIndex) { lock (_caretLock) { return _caret.IsSelected(byteIndex); } } public void DestroyCaret() { lock (_caretLock) { _caret.Destroy(); } } public void RemoveSelectedBytes() { int selectionStart = SelectionStart; int selectionLength = SelectionLength; if (selectionLength > 0) { lock (_hexTableLock) { _hexTable.RemoveRange(selectionStart, selectionLength); } UpdateRectanglePositioning(); Invalidate(); if (this.HexTableChanged != null) { this.HexTableChanged(this, EventArgs.Empty); } } } public void RemoveByteAt(int index) { lock (_hexTableLock) { _hexTable.RemoveAt(index); } UpdateRectanglePositioning(); Invalidate(); if (this.HexTableChanged != null) { this.HexTableChanged(this, EventArgs.Empty); } } public void AppendByte(byte item) { lock (_hexTableLock) { _hexTable.Add(item); } UpdateRectanglePositioning(); Invalidate(); if (this.HexTableChanged != null) { this.HexTableChanged(this, EventArgs.Empty); } } public void InsertByte(int index, byte item) { lock (_hexTableLock) { _hexTable.Insert(index, item); } UpdateRectanglePositioning(); Invalidate(); if (this.HexTableChanged != null) { this.HexTableChanged(this, EventArgs.Empty); } } public char GetByteAsChar(int index) { lock (_hexTableLock) { return _hexTable.GetCharAt(index); } } public byte GetByte(int index) { lock (_hexTableLock) { return _hexTable.GetAt(index); } } public void SetByte(int index, byte item) { lock (_hexTableLock) { _hexTable.SetAt(index, item); } Invalidate(); if (this.HexTableChanged != null) { this.HexTableChanged(this, EventArgs.Empty); } } public void ScrollLineUp(int lines) { if (_firstByte <= 0) { return; } lines = ((lines > _vScrollPos) ? _vScrollPos : lines); _vScrollPos -= _vScrollSmall * lines; UpdateVisibleByteIndex(); UpdateScrollValues(); if (CaretFocused) { Point caretLocation = new Point(CaretPosX, CaretPosY); caretLocation.Y += _recLineCount.Height * lines; lock (_caretLock) { _caret.SetCaretLocation(caretLocation); } } } public void ScrollLineDown(int lines) { if (_vScrollPos > _vScrollMax - _vScrollLarge) { return; } lines = ((lines + _vScrollPos > _vScrollMax - _vScrollLarge) ? (_vScrollMax - _vScrollLarge - _vScrollPos + 1) : lines); _vScrollPos += _vScrollSmall * lines; UpdateVisibleByteIndex(); UpdateScrollValues(); if (!CaretFocused) { return; } Point caretLocation = new Point(CaretPosX, CaretPosY); caretLocation.Y -= _recLineCount.Height * lines; lock (_caretLock) { _caret.SetCaretLocation(caretLocation); if (caretLocation.Y < _recContent.Y) { _caret.Hide(base.Handle); } } } public void ScrollThumbTrack(int lines) { if (lines != 0) { if (lines < 0) { ScrollLineUp(-1 * lines); } else { ScrollLineDown(lines); } } } public Point ScrollToCaret(int caretIndex, Point position) { if (position.Y < 0) { _vScrollPos -= Math.Abs((position.Y - _recContent.Y) / _recLineCount.Height) * _vScrollSmall; UpdateVisibleByteIndex(); UpdateScrollValues(); if (CaretFocused) { position.Y = _recContent.Y; } } else if (position.Y > _maxVisibleBytesV * _recLineCount.Height) { _vScrollPos += (position.Y / _recLineCount.Height - (_maxVisibleBytesV - 1)) * _vScrollSmall; if (_vScrollPos > _vScrollMax - (_vScrollLarge - 1)) { _vScrollPos = _vScrollMax - (_vScrollLarge - 1); } UpdateVisibleByteIndex(); UpdateScrollValues(); if (CaretFocused) { position.Y = (_maxVisibleBytesV - 1) * _recLineCount.Height + _recContent.Y; } } return position; } private void UpdateRectanglePositioning() { if (base.ClientRectangle.Width != 0) { SizeF sizeF; using (Graphics graphics = CreateGraphics()) { sizeF = graphics.MeasureString("D", Font, 100, _stringFormat); } CharSize = new SizeF((float)Math.Ceiling(sizeF.Width), (float)Math.Ceiling(sizeF.Height)); _recContent = base.ClientRectangle; _recContent.X += base.Margin.Left; _recContent.Y += base.Margin.Top; _recContent.Width -= base.Margin.Right; _recContent.Height -= base.Margin.Bottom; if (BorderStyle == BorderStyle.Fixed3D) { _recContent.X += 2; _recContent.Y += 2; _recContent.Width--; _recContent.Height--; } else if (BorderStyle == BorderStyle.FixedSingle) { _recContent.X++; _recContent.Y++; _recContent.Width--; _recContent.Height--; } if (!VScrollBarHidden) { _recContent.Width -= _vScrollBarWidth; _vScrollBar.Left = _recContent.X + _recContent.Width - base.Margin.Left; _vScrollBar.Top = _recContent.Y - base.Margin.Top; _vScrollBar.Width = _vScrollBarWidth; _vScrollBar.Height = _recContent.Height; } _recLineCount = new Rectangle(_recContent.X, _recContent.Y, (int)(_charSize.Width * 4f), (int)_charSize.Height - 2); _editView.Update(_recLineCount.X + _recLineCount.Width + _entityMargin / 2, _recContent); _maxBytesH = _bytesPerLine; _maxBytesV = (int)Math.Ceiling((float)_recContent.Height / (float)_recLineCount.Height); _maxBytes = _maxBytesH * _maxBytesV; _maxVisibleBytesV = (int)Math.Floor((float)_recContent.Height / (float)_recLineCount.Height); UpdateScrollBarSize(); } } private void UpdateVisibleByteIndex() { if (_hexTable.Length == 0) { _firstByte = 0; _lastByte = 0; } else { _firstByte = _vScrollPos * _maxBytesH; _lastByte = Math.Min(HexTableLength, _firstByte + _maxBytes); } } private void UpdateScrollValues() { if (!_isVScrollHidden) { _vScrollBar.Minimum = _vScrollMin; _vScrollBar.Maximum = _vScrollMax; _vScrollBar.Value = _vScrollPos; _vScrollBar.SmallChange = _vScrollSmall; _vScrollBar.LargeChange = _vScrollLarge; _vScrollBar.Visible = true; } else { _vScrollBar.Visible = false; } } private void UpdateScrollBarSize() { if (VScrollBarVisisble && _maxVisibleBytesV > 0 && _maxBytesH > 0) { int maxVisibleBytesV = _maxVisibleBytesV; int num = 1; int vScrollMin = 0; int num2 = HexTableLength / _maxBytesH; int num3 = _firstByte / _maxBytesH; if (maxVisibleBytesV != _vScrollLarge || num != _vScrollSmall) { _vScrollLarge = maxVisibleBytesV; _vScrollSmall = num; } if (num2 >= maxVisibleBytesV) { if (num2 != _vScrollMax || num3 != _vScrollPos) { _vScrollMin = vScrollMin; _vScrollMax = num2; _vScrollPos = num3; } VScrollBarHidden = false; UpdateScrollValues(); } else { VScrollBarHidden = true; } } else { VScrollBarHidden = true; } } public HexEditor() : this(new ByteCollection()) { } public HexEditor(ByteCollection collection) { _stringFormat = new StringFormat(StringFormat.GenericTypographic); _stringFormat.Alignment = StringAlignment.Center; _stringFormat.LineAlignment = StringAlignment.Center; _hexTable = collection; _vScrollBar = new DevExpress.XtraEditors.VScrollBar(); _vScrollBar.Scroll += OnVScrollBarScroll; SetStyle(ControlStyles.ResizeRedraw, value: true); SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, value: true); SetStyle(ControlStyles.Selectable, value: true); _caret = new Caret(this); _caret.SelectionStartChanged += CaretSelectionStartChanged; _caret.SelectionLengthChanged += CaretSelectionLengthChanged; _editView = new EditView(this); _handler = _editView; Cursor = Cursors.IBeam; } private RectangleF GetLineCountBound(int index) { return new RectangleF(_recLineCount.X, _recLineCount.Y + _recLineCount.Height * index, _recLineCount.Width, _recLineCount.Height); } protected override void OnPaintBackground(PaintEventArgs pevent) { if (BorderStyle == BorderStyle.Fixed3D) { SolidBrush brush = new SolidBrush(BackColor); Rectangle clientRectangle = base.ClientRectangle; pevent.Graphics.FillRectangle(brush, clientRectangle); ControlPaint.DrawBorder3D(pevent.Graphics, base.ClientRectangle, Border3DStyle.Sunken); } else if (BorderStyle == BorderStyle.FixedSingle) { SolidBrush brush2 = new SolidBrush(BackColor); Rectangle clientRectangle2 = base.ClientRectangle; pevent.Graphics.FillRectangle(brush2, clientRectangle2); ControlPaint.DrawBorder(pevent.Graphics, base.ClientRectangle, BorderColor, ButtonBorderStyle.Solid); } else { base.OnPaintBackground(pevent); } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Region region = new Region(base.ClientRectangle); region.Exclude(_recContent); e.Graphics.ExcludeClip(region); UpdateVisibleByteIndex(); PaintLineCount(e.Graphics, _firstByte, _lastByte); _editView.Paint(e.Graphics, _firstByte, _lastByte); } private void PaintLineCount(Graphics g, int startIndex, int lastIndex) { SolidBrush brush = new SolidBrush(ForeColor); for (int i = 0; i * _maxBytesH + startIndex <= lastIndex; i++) { RectangleF lineCountBound = GetLineCountBound(i); string text = (startIndex + i * _maxBytesH).ToString(_lineCountCaps); int num = _nrCharsLineCount - text.Length; string s = ((num <= -1) ? new string('~', _nrCharsLineCount) : (new string('0', num) + text)); g.DrawString(s, Font, brush, lineCountBound, _stringFormat); } } protected override void OnResize(EventArgs e) { base.OnResize(e); UpdateRectanglePositioning(); Invalidate(); } }