diff --git a/GUI.NET/Debugger/Controls/ctrlHexViewer.Designer.cs b/GUI.NET/Debugger/Controls/ctrlHexViewer.Designer.cs index 0caf4413..09a5af7b 100644 --- a/GUI.NET/Debugger/Controls/ctrlHexViewer.Designer.cs +++ b/GUI.NET/Debugger/Controls/ctrlHexViewer.Designer.cs @@ -29,51 +29,55 @@ { this.components = new System.ComponentModel.Container(); this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); - this.ctrlDataViewer = new Mesen.GUI.Debugger.ctrlScrollableTextbox(); this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); this.lblNumberOfColumns = new System.Windows.Forms.Label(); this.cboNumberColumns = new System.Windows.Forms.ComboBox(); + this.panelSearch = new System.Windows.Forms.Panel(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.picCloseSearch = new System.Windows.Forms.PictureBox(); + this.picSearchNext = new System.Windows.Forms.PictureBox(); + this.picSearchPrevious = new System.Windows.Forms.PictureBox(); + this.cboSearch = new System.Windows.Forms.ComboBox(); + this.lblSearchWarning = new System.Windows.Forms.Label(); + this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); + this.chkTextSearch = new System.Windows.Forms.CheckBox(); + this.chkMatchCase = new System.Windows.Forms.CheckBox(); this.toolTip = new System.Windows.Forms.ToolTip(this.components); + this.ctrlHexBox = new Be.Windows.Forms.HexBox(); this.tableLayoutPanel1.SuspendLayout(); this.flowLayoutPanel1.SuspendLayout(); + this.panelSearch.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picCloseSearch)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.picSearchNext)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.picSearchPrevious)).BeginInit(); + this.flowLayoutPanel2.SuspendLayout(); this.SuspendLayout(); // // tableLayoutPanel1 // this.tableLayoutPanel1.ColumnCount = 1; this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel1.Controls.Add(this.ctrlDataViewer, 0, 1); this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.panelSearch, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.ctrlHexBox, 0, 1); this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; - this.tableLayoutPanel1.RowCount = 2; + this.tableLayoutPanel1.RowCount = 3; this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel1.Size = new System.Drawing.Size(191, 109); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(543, 309); this.tableLayoutPanel1.TabIndex = 0; // - // ctrlDataViewer - // - this.ctrlDataViewer.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.ctrlDataViewer.Dock = System.Windows.Forms.DockStyle.Fill; - this.ctrlDataViewer.FontSize = 13F; - this.ctrlDataViewer.Location = new System.Drawing.Point(3, 30); - this.ctrlDataViewer.Name = "ctrlDataViewer"; - this.ctrlDataViewer.ShowContentNotes = false; - this.ctrlDataViewer.ShowLineNumberNotes = false; - this.ctrlDataViewer.Size = new System.Drawing.Size(185, 76); - this.ctrlDataViewer.TabIndex = 0; - this.ctrlDataViewer.MouseMove += new System.Windows.Forms.MouseEventHandler(this.ctrlDataViewer_MouseMove); - this.ctrlDataViewer.FontSizeChanged += new System.EventHandler(this.ctrlDataViewer_FontSizeChanged); - // // flowLayoutPanel1 // this.flowLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.flowLayoutPanel1.AutoSize = true; this.flowLayoutPanel1.Controls.Add(this.lblNumberOfColumns); this.flowLayoutPanel1.Controls.Add(this.cboNumberColumns); - this.flowLayoutPanel1.Location = new System.Drawing.Point(27, 0); + this.flowLayoutPanel1.Location = new System.Drawing.Point(379, 0); this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0); this.flowLayoutPanel1.Name = "flowLayoutPanel1"; this.flowLayoutPanel1.Size = new System.Drawing.Size(164, 27); @@ -105,17 +109,175 @@ this.cboNumberColumns.TabIndex = 1; this.cboNumberColumns.SelectedIndexChanged += new System.EventHandler(this.cboNumberColumns_SelectedIndexChanged); // + // panelSearch + // + this.panelSearch.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.panelSearch.Controls.Add(this.tableLayoutPanel2); + this.panelSearch.Dock = System.Windows.Forms.DockStyle.Fill; + this.panelSearch.Location = new System.Drawing.Point(3, 281); + this.panelSearch.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0); + this.panelSearch.Name = "panelSearch"; + this.panelSearch.Size = new System.Drawing.Size(537, 28); + this.panelSearch.TabIndex = 3; + this.panelSearch.Visible = false; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.ColumnCount = 6; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 250F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Controls.Add(this.picCloseSearch, 3, 0); + this.tableLayoutPanel2.Controls.Add(this.picSearchNext, 2, 0); + this.tableLayoutPanel2.Controls.Add(this.picSearchPrevious, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.cboSearch, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.lblSearchWarning, 4, 0); + this.tableLayoutPanel2.Controls.Add(this.flowLayoutPanel2, 5, 0); + this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 2; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(535, 26); + this.tableLayoutPanel2.TabIndex = 0; + // + // picCloseSearch + // + this.picCloseSearch.Anchor = System.Windows.Forms.AnchorStyles.None; + this.picCloseSearch.Cursor = System.Windows.Forms.Cursors.Hand; + this.picCloseSearch.Image = global::Mesen.GUI.Properties.Resources.Close; + this.picCloseSearch.Location = new System.Drawing.Point(297, 5); + this.picCloseSearch.Name = "picCloseSearch"; + this.picCloseSearch.Size = new System.Drawing.Size(16, 16); + this.picCloseSearch.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; + this.picCloseSearch.TabIndex = 3; + this.picCloseSearch.TabStop = false; + this.picCloseSearch.Click += new System.EventHandler(this.picCloseSearch_Click); + // + // picSearchNext + // + this.picSearchNext.Anchor = System.Windows.Forms.AnchorStyles.None; + this.picSearchNext.Cursor = System.Windows.Forms.Cursors.Hand; + this.picSearchNext.Image = global::Mesen.GUI.Properties.Resources.NextArrow; + this.picSearchNext.Location = new System.Drawing.Point(275, 5); + this.picSearchNext.Name = "picSearchNext"; + this.picSearchNext.Size = new System.Drawing.Size(16, 16); + this.picSearchNext.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; + this.picSearchNext.TabIndex = 2; + this.picSearchNext.TabStop = false; + this.picSearchNext.MouseUp += new System.Windows.Forms.MouseEventHandler(this.picSearchNext_MouseUp); + // + // picSearchPrevious + // + this.picSearchPrevious.Anchor = System.Windows.Forms.AnchorStyles.None; + this.picSearchPrevious.Cursor = System.Windows.Forms.Cursors.Hand; + this.picSearchPrevious.Image = global::Mesen.GUI.Properties.Resources.PreviousArrow; + this.picSearchPrevious.Location = new System.Drawing.Point(253, 5); + this.picSearchPrevious.Name = "picSearchPrevious"; + this.picSearchPrevious.Size = new System.Drawing.Size(16, 16); + this.picSearchPrevious.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize; + this.picSearchPrevious.TabIndex = 1; + this.picSearchPrevious.TabStop = false; + this.picSearchPrevious.MouseUp += new System.Windows.Forms.MouseEventHandler(this.picSearchPrevious_MouseUp); + // + // cboSearch + // + this.cboSearch.Dock = System.Windows.Forms.DockStyle.Fill; + this.cboSearch.FormattingEnabled = true; + this.cboSearch.Location = new System.Drawing.Point(3, 3); + this.cboSearch.Name = "cboSearch"; + this.cboSearch.Size = new System.Drawing.Size(244, 21); + this.cboSearch.TabIndex = 4; + this.cboSearch.TextUpdate += new System.EventHandler(this.cboSearch_TextUpdate); + this.cboSearch.KeyDown += new System.Windows.Forms.KeyEventHandler(this.cboSearch_KeyDown); + // + // lblSearchWarning + // + this.lblSearchWarning.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblSearchWarning.AutoSize = true; + this.lblSearchWarning.ForeColor = System.Drawing.Color.Red; + this.lblSearchWarning.Location = new System.Drawing.Point(319, 7); + this.lblSearchWarning.Name = "lblSearchWarning"; + this.lblSearchWarning.Size = new System.Drawing.Size(0, 13); + this.lblSearchWarning.TabIndex = 6; + // + // flowLayoutPanel2 + // + this.flowLayoutPanel2.Controls.Add(this.chkTextSearch); + this.flowLayoutPanel2.Controls.Add(this.chkMatchCase); + this.flowLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutPanel2.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft; + this.flowLayoutPanel2.Location = new System.Drawing.Point(322, 2); + this.flowLayoutPanel2.Margin = new System.Windows.Forms.Padding(0, 2, 0, 0); + this.flowLayoutPanel2.Name = "flowLayoutPanel2"; + this.flowLayoutPanel2.Size = new System.Drawing.Size(213, 25); + this.flowLayoutPanel2.TabIndex = 7; + // + // chkTextSearch + // + this.chkTextSearch.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.chkTextSearch.AutoSize = true; + this.chkTextSearch.Location = new System.Drawing.Point(128, 3); + this.chkTextSearch.Name = "chkTextSearch"; + this.chkTextSearch.Size = new System.Drawing.Size(82, 17); + this.chkTextSearch.TabIndex = 5; + this.chkTextSearch.Text = "Text search"; + this.chkTextSearch.UseVisualStyleBackColor = true; + this.chkTextSearch.CheckedChanged += new System.EventHandler(this.chkTextSearch_CheckedChanged); + // + // chkMatchCase + // + this.chkMatchCase.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.chkMatchCase.AutoSize = true; + this.chkMatchCase.Location = new System.Drawing.Point(39, 3); + this.chkMatchCase.Name = "chkMatchCase"; + this.chkMatchCase.Size = new System.Drawing.Size(83, 17); + this.chkMatchCase.TabIndex = 6; + this.chkMatchCase.Text = "Match Case"; + this.chkMatchCase.UseVisualStyleBackColor = true; + // + // ctrlDataViewer + // + this.ctrlHexBox.ColumnInfoVisible = true; + this.ctrlHexBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.ctrlHexBox.Font = new System.Drawing.Font("Consolas", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ctrlHexBox.InfoBackColor = System.Drawing.Color.DarkGray; + this.ctrlHexBox.LineInfoVisible = true; + this.ctrlHexBox.Location = new System.Drawing.Point(3, 30); + this.ctrlHexBox.Name = "ctrlHexBox"; + this.ctrlHexBox.ReadOnly = true; + this.ctrlHexBox.SelectionBackColor = System.Drawing.Color.RoyalBlue; + this.ctrlHexBox.ShadowSelectionColor = System.Drawing.Color.Orange; + this.ctrlHexBox.Size = new System.Drawing.Size(537, 248); + this.ctrlHexBox.StringViewVisible = true; + this.ctrlHexBox.TabIndex = 2; + this.ctrlHexBox.UseFixedBytesPerLine = true; + this.ctrlHexBox.VScrollBarVisible = true; + // // ctrlHexViewer // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.tableLayoutPanel1); this.Name = "ctrlHexViewer"; - this.Size = new System.Drawing.Size(191, 109); + this.Size = new System.Drawing.Size(543, 309); this.tableLayoutPanel1.ResumeLayout(false); this.tableLayoutPanel1.PerformLayout(); this.flowLayoutPanel1.ResumeLayout(false); this.flowLayoutPanel1.PerformLayout(); + this.panelSearch.ResumeLayout(false); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.picCloseSearch)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.picSearchNext)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.picSearchPrevious)).EndInit(); + this.flowLayoutPanel2.ResumeLayout(false); + this.flowLayoutPanel2.PerformLayout(); this.ResumeLayout(false); } @@ -123,10 +285,20 @@ #endregion private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; - private ctrlScrollableTextbox ctrlDataViewer; private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; private System.Windows.Forms.Label lblNumberOfColumns; private System.Windows.Forms.ComboBox cboNumberColumns; private System.Windows.Forms.ToolTip toolTip; + private Be.Windows.Forms.HexBox ctrlHexBox; + private System.Windows.Forms.Panel panelSearch; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.PictureBox picCloseSearch; + private System.Windows.Forms.PictureBox picSearchNext; + private System.Windows.Forms.PictureBox picSearchPrevious; + private System.Windows.Forms.ComboBox cboSearch; + private System.Windows.Forms.CheckBox chkTextSearch; + private System.Windows.Forms.Label lblSearchWarning; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel2; + private System.Windows.Forms.CheckBox chkMatchCase; } } diff --git a/GUI.NET/Debugger/Controls/ctrlHexViewer.cs b/GUI.NET/Debugger/Controls/ctrlHexViewer.cs index 6ee3ef22..363e4629 100644 --- a/GUI.NET/Debugger/Controls/ctrlHexViewer.cs +++ b/GUI.NET/Debugger/Controls/ctrlHexViewer.cs @@ -8,61 +8,58 @@ using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Mesen.GUI.Config; +using Be.Windows.Forms; +using Mesen.GUI.Controls; namespace Mesen.GUI.Debugger.Controls { - public partial class ctrlHexViewer : BaseScrollableTextboxUserControl + public partial class ctrlHexViewer : UserControl { - public event EventHandler ColumnCountChanged; - - private int _currentColumnCount; - private byte[] _data; + private FindOptions _findOptions; + private StaticByteProvider _byteProvider; public ctrlHexViewer() { InitializeComponent(); + this.ctrlHexBox.Font = new Font(BaseControl.MonospaceFontFamily, 10, FontStyle.Regular); + this.ctrlHexBox.SelectionForeColor = Color.White; + this.ctrlHexBox.SelectionBackColor = Color.FromArgb(31, 123, 205); + this.ctrlHexBox.ChangeColor = Color.Red; + this.ctrlHexBox.SelectionChangeColor = Color.FromArgb(255, 125, 125); + this.ctrlHexBox.ShadowSelectionColor = Color.FromArgb(100, 60, 128, 200); + this.ctrlHexBox.InfoBackColor = Color.FromArgb(235, 235, 235); + this.ctrlHexBox.InfoForeColor = Color.Gray; + if(LicenseManager.UsageMode != LicenseUsageMode.Designtime) { this.cboNumberColumns.SelectedIndex = ConfigManager.Config.DebugInfo.RamColumnCount; } } - protected override ctrlScrollableTextbox ScrollableTextbox + public byte[] GetData() { - get { return this.ctrlDataViewer; } + return this._byteProvider != null ? this._byteProvider.Bytes.ToArray() : new byte[0]; } - - [Browsable(false)] - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public byte[] Data + + public void SetData(byte[] data, bool clearHistory) { - get { return this._data; } - set - { - if(value != null) { - if(_currentColumnCount != this.ColumnCount) { - _currentColumnCount = this.ColumnCount; + if(data != null) { + bool changed = true; + if(this._byteProvider != null && data.Length == this._byteProvider.Bytes.Count) { + changed = false; + for(int i = 0; i < this._byteProvider.Bytes.Count; i++) { + if(this._byteProvider.Bytes[i] != data[i]) { + changed = true; + break; + } } + } - string[] hexContent, previousHexContent = null; - int[] lineNumbers, previousLineNumbers; - if(this._data != null && this._data.Length == value.Length) { - this.ArrayToHex(this._data, out previousHexContent, out previousLineNumbers); - } - this.ArrayToHex(value, out hexContent, out lineNumbers); - this.ctrlDataViewer.CompareLines = previousHexContent; - this.ctrlDataViewer.TextLines = hexContent; - - this._data = value; - this.ctrlDataViewer.LineNumbers = lineNumbers; - if(this.ColumnCount == 16) { - this.ctrlDataViewer.Header = "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"; - } else if(this.ColumnCount == 32) { - this.ctrlDataViewer.Header = "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F"; - } else if(this.ColumnCount == 64) { - this.ctrlDataViewer.Header = "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F"; - } else { - this.ctrlDataViewer.Header = null; + if(changed) { + _byteProvider = new StaticByteProvider(data); + this.ctrlHexBox.ByteProvider = _byteProvider; + if(clearHistory) { + this.ctrlHexBox.ClearHistory(); } } } @@ -73,101 +70,215 @@ namespace Mesen.GUI.Debugger.Controls get { return Int32.Parse(this.cboNumberColumns.Text); } } - public int IdealWidth + public int RequiredWidth { - get - { - return 145 + this.ColumnCount * 30; - } + get { return this.ctrlHexBox.RequiredWidth; } } - - private string[] _lookupTable = new string[] { - "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", - "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F", - "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", - "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", - "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", - "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", - "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", - "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", - "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F", - "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", - "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF", - "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", - "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF", - "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", - "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", - "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF", - }; - - private void ArrayToHex(byte[] data, out string[] hexContent, out int[] lineNumbers) - { - int columnCount = this.ColumnCount; - int lineNumber = 0; - - int rowCount = data.Length / columnCount; - if(data.Length % columnCount != 0) { - rowCount++; - } - - hexContent = new string[rowCount]; - lineNumbers = new int[rowCount]; - - StringBuilder sb = new StringBuilder(columnCount * 4); - int columnCounter = 0; - int lineCounter = 0; - foreach(byte b in data) { - sb.Append(_lookupTable[b]); - sb.Append(" "); - columnCounter++; - if(columnCounter == columnCount) { - hexContent[lineCounter] = sb.ToString(); - lineNumbers[lineCounter] = lineNumber; - sb.Clear(); - columnCounter = 0; - lineCounter++; - lineNumber += columnCount; - } - } - if(sb.Length > 0) { - hexContent[lineCounter] = sb.ToString(); - lineNumbers[lineCounter] = lineNumber; - } - } - private void cboNumberColumns_SelectedIndexChanged(object sender, EventArgs e) { - if(this.ColumnCountChanged != null) { - this.ColumnCountChanged(sender, e); - } - this.Data = _data; - this.ctrlDataViewer.Focus(); + this.ctrlHexBox.Focus(); + + this.ctrlHexBox.BytesPerLine = this.ColumnCount; + this.ctrlHexBox.UseFixedBytesPerLine = true; ConfigManager.Config.DebugInfo.RamColumnCount = this.cboNumberColumns.SelectedIndex; ConfigManager.ApplyChanges(); } - Point _previousLocation; - private void ctrlDataViewer_MouseMove(object sender, MouseEventArgs e) + public Font HexFont { - if(_previousLocation != e.Location) { - string currentWord = this.GetWordUnderLocation(e.Location, false); - string originalWord = this.GetWordUnderLocation(e.Location, true); + get { return this.ctrlHexBox.Font; } + } - if(currentWord != originalWord) { - this.toolTip.Show("Previous Value: $" + originalWord + Environment.NewLine + "Current Value: $" + currentWord, this.ctrlDataViewer, e.Location.X + 5, e.Location.Y - 40, 3000); - } else { - this.toolTip.Hide(this.ctrlDataViewer); - } - _previousLocation = e.Location; + public void IncreaseFontSize() + { + this.SetFontSize(Math.Min(40, this.ctrlHexBox.Font.Size + 1)); + } + + public void DecreaseFontSize() + { + this.SetFontSize(Math.Max(6, this.ctrlHexBox.Font.Size - 1)); + } + + public void SetFontSize(float size) + { + this.ctrlHexBox.Font = new Font(BaseControl.MonospaceFontFamily, size); + } + + public void ResetFontSize() + { + this.SetFontSize(BaseControl.DefaultFontSize); + } + + public void GoToAddress() + { + GoToAddress address = new GoToAddress(); + + int currentAddr = (int)(this.ctrlHexBox.CurrentLine - 1) * this.ctrlHexBox.BytesPerLine; + address.Address = (UInt32)currentAddr; + + frmGoToLine frm = new frmGoToLine(address); + frm.StartPosition = FormStartPosition.Manual; + Point topLeft = this.PointToScreen(new Point(0, 0)); + frm.Location = new Point(topLeft.X + (this.Width - frm.Width) / 2, topLeft.Y + (this.Height - frm.Height) / 2); + if(frm.ShowDialog() == DialogResult.OK) { + this.ctrlHexBox.ScrollByteIntoView((int)address.Address); + this.ctrlHexBox.Focus(); } } - private void ctrlDataViewer_FontSizeChanged(object sender, EventArgs e) + public void OpenSearchBox(bool forceFocus = false) { - ConfigManager.Config.DebugInfo.RamFontSize = this.ctrlDataViewer.FontSize; - ConfigManager.ApplyChanges(); + this._findOptions = new Be.Windows.Forms.FindOptions(); + this._findOptions.Type = chkTextSearch.Checked ? FindType.Text : FindType.Hex; + this._findOptions.MatchCase = false; + this._findOptions.Text = this.cboSearch.Text; + this._findOptions.WrapSearch = true; + + bool focus = !this.panelSearch.Visible; + this.panelSearch.Visible = true; + if(focus || forceFocus) { + this.cboSearch.Focus(); + this.cboSearch.SelectAll(); + } + } + + private void CloseSearchBox() + { + this.panelSearch.Visible = false; + this.Focus(); + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + switch(keyData) { + case Keys.Control | Keys.F: this.OpenSearchBox(true); return true; + case Keys.Escape: this.CloseSearchBox(); return true; + case Keys.Control | Keys.Oemplus: this.IncreaseFontSize(); return true; + case Keys.Control | Keys.OemMinus: this.DecreaseFontSize(); return true; + case Keys.Control | Keys.D0: this.ResetFontSize(); return true; + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + public void FindNext() + { + this.OpenSearchBox(); + if(this.UpdateSearchOptions()) { + if(this.ctrlHexBox.Find(this._findOptions, HexBox.eSearchDirection.Next) == -1) { + this.lblSearchWarning.Text = "No matches found!"; + } + } + } + + public void FindPrevious() + { + this.OpenSearchBox(); + if(this.UpdateSearchOptions()) { + if(this.ctrlHexBox.Find(this._findOptions, HexBox.eSearchDirection.Previous) == -1) { + this.lblSearchWarning.Text = "No matches found!"; + } + } + } + + private void picCloseSearch_Click(object sender, EventArgs e) + { + this.CloseSearchBox(); + } + + private void picSearchPrevious_MouseUp(object sender, MouseEventArgs e) + { + this.FindPrevious(); + } + + private void picSearchNext_MouseUp(object sender, MouseEventArgs e) + { + this.FindNext(); + } + + private byte[] GetByteArray(string hexText, ref bool hasWildcard) + { + hexText = hexText.Replace(" ", ""); + + try { + List bytes = new List(hexText.Length/2); + for(int i = 0; i < hexText.Length; i+=2) { + if(i == hexText.Length - 1) { + bytes.Add((byte)(Convert.ToByte(hexText.Substring(i, 1), 16) << 4)); + hasWildcard = true; + } else { + bytes.Add(Convert.ToByte(hexText.Substring(i, 2), 16)); + } + } + return bytes.ToArray(); + } catch { + return new byte[0]; + } + } + + private bool UpdateSearchOptions() + { + bool invalidSearchString = false; + + this._findOptions.MatchCase = this.chkMatchCase.Checked; + + if(this.chkTextSearch.Checked) { + this._findOptions.Type = FindType.Text; + this._findOptions.Text = this.cboSearch.Text; + this._findOptions.HasWildcard = false; + } else { + this._findOptions.Type = FindType.Hex; + bool hasWildcard = false; + this._findOptions.Hex = this.GetByteArray(this.cboSearch.Text, ref hasWildcard); + this._findOptions.HasWildcard = hasWildcard; + invalidSearchString = this._findOptions.Hex.Length == 0 && this.cboSearch.Text.Trim().Length > 0; + } + + this.lblSearchWarning.Text = ""; + + bool emptySearch = this._findOptions.Text.Length == 0 || (!this.chkTextSearch.Checked && (this._findOptions.Hex == null || this._findOptions.Hex.Length == 0)); + if(invalidSearchString) { + this.lblSearchWarning.Text = "Invalid search string"; + } else if(!emptySearch) { + return true; + } + return false; + } + + private void cboSearch_TextUpdate(object sender, EventArgs e) + { + if(this.UpdateSearchOptions()) { + if(this.ctrlHexBox.Find(this._findOptions, HexBox.eSearchDirection.Incremental) == -1) { + this.lblSearchWarning.Text = "No matches found!"; + } + } + } + + private void cboSearch_KeyDown(object sender, KeyEventArgs e) + { + if(e.KeyCode == Keys.Enter) { + this.FindNext(); + if(this.cboSearch.Items.Contains(this.cboSearch.Text)) { + this.cboSearch.Items.Remove(this.cboSearch.Text); + } + this.cboSearch.Items.Insert(0, this.cboSearch.Text); + + e.Handled = true; + e.SuppressKeyPress = true; + } + } + + public event EventHandler RequiredWidthChanged + { + add { this.ctrlHexBox.RequiredWidthChanged += value; } + remove { this.ctrlHexBox.RequiredWidthChanged -= value; } + } + + private void chkTextSearch_CheckedChanged(object sender, EventArgs e) + { + this.UpdateSearchOptions(); } } } diff --git a/GUI.NET/Debugger/HexBox/BuiltInContextMenu.cs b/GUI.NET/Debugger/HexBox/BuiltInContextMenu.cs new file mode 100644 index 00000000..429b3e20 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/BuiltInContextMenu.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.ComponentModel; +using System.Windows.Forms; + +namespace Be.Windows.Forms +{ + /// + /// Defines a build-in ContextMenuStrip manager for HexBox control to show Copy, Cut, Paste menu in contextmenu of the control. + /// + [TypeConverterAttribute(typeof(ExpandableObjectConverter))] + public sealed class BuiltInContextMenu : Component + { + /// + /// Contains the HexBox control. + /// + HexBox _hexBox; + /// + /// Contains the ContextMenuStrip control. + /// + ContextMenuStrip _contextMenuStrip; + /// + /// Contains the "Cut"-ToolStripMenuItem object. + /// + ToolStripMenuItem _cutToolStripMenuItem; + /// + /// Contains the "Copy"-ToolStripMenuItem object. + /// + ToolStripMenuItem _copyToolStripMenuItem; + /// + /// Contains the "Paste"-ToolStripMenuItem object. + /// + ToolStripMenuItem _pasteToolStripMenuItem; + /// + /// Contains the "Select All"-ToolStripMenuItem object. + /// + ToolStripMenuItem _selectAllToolStripMenuItem; + /// + /// Initializes a new instance of BuildInContextMenu class. + /// + /// the HexBox control + internal BuiltInContextMenu(HexBox hexBox) + { + _hexBox = hexBox; + _hexBox.ByteProviderChanged += new EventHandler(HexBox_ByteProviderChanged); + } + /// + /// If ByteProvider + /// + /// the sender object + /// the event data + void HexBox_ByteProviderChanged(object sender, EventArgs e) + { + CheckBuiltInContextMenu(); + } + /// + /// Assigns the ContextMenuStrip control to the HexBox control. + /// + void CheckBuiltInContextMenu() + { + if (Util.DesignMode) + return; + + if (this._contextMenuStrip == null) + { + ContextMenuStrip cms = new ContextMenuStrip(); + _cutToolStripMenuItem = new ToolStripMenuItem(CutMenuItemTextInternal, CutMenuItemImage, new EventHandler(CutMenuItem_Click)); + cms.Items.Add(_cutToolStripMenuItem); + _copyToolStripMenuItem = new ToolStripMenuItem(CopyMenuItemTextInternal, CopyMenuItemImage, new EventHandler(CopyMenuItem_Click)); + cms.Items.Add(_copyToolStripMenuItem); + _pasteToolStripMenuItem = new ToolStripMenuItem(PasteMenuItemTextInternal, PasteMenuItemImage, new EventHandler(PasteMenuItem_Click)); + cms.Items.Add(_pasteToolStripMenuItem); + + cms.Items.Add(new ToolStripSeparator()); + + _selectAllToolStripMenuItem = new ToolStripMenuItem(SelectAllMenuItemTextInternal, SelectAllMenuItemImage, new EventHandler(SelectAllMenuItem_Click)); + cms.Items.Add(_selectAllToolStripMenuItem); + cms.Opening += new CancelEventHandler(BuildInContextMenuStrip_Opening); + + _contextMenuStrip = cms; + } + + if (this._hexBox.ByteProvider == null && this._hexBox.ContextMenuStrip == this._contextMenuStrip) + this._hexBox.ContextMenuStrip = null; + else if (this._hexBox.ByteProvider != null && this._hexBox.ContextMenuStrip == null) + this._hexBox.ContextMenuStrip = _contextMenuStrip; + } + /// + /// Before opening the ContextMenuStrip, we manage the availability of the items. + /// + /// the sender object + /// the event data + void BuildInContextMenuStrip_Opening(object sender, CancelEventArgs e) + { + _cutToolStripMenuItem.Enabled = this._hexBox.CanCut(); + _copyToolStripMenuItem.Enabled = this._hexBox.CanCopy(); + _pasteToolStripMenuItem.Enabled = this._hexBox.CanPaste(); + _selectAllToolStripMenuItem.Enabled = this._hexBox.CanSelectAll(); + } + /// + /// The handler for the "Cut"-Click event + /// + /// the sender object + /// the event data + void CutMenuItem_Click(object sender, EventArgs e) { this._hexBox.Cut(); } + /// + /// The handler for the "Copy"-Click event + /// + /// the sender object + /// the event data + void CopyMenuItem_Click(object sender, EventArgs e) { this._hexBox.Copy(); } + /// + /// The handler for the "Paste"-Click event + /// + /// the sender object + /// the event data + void PasteMenuItem_Click(object sender, EventArgs e) { this._hexBox.Paste(); } + /// + /// The handler for the "Select All"-Click event + /// + /// the sender object + /// the event data + void SelectAllMenuItem_Click(object sender, EventArgs e) { this._hexBox.SelectAll(); } + /// + /// Gets or sets the custom text of the "Copy" ContextMenuStrip item. + /// + [Category("BuiltIn-ContextMenu"), DefaultValue(null), Localizable(true)] + public string CopyMenuItemText + { + get { return _copyMenuItemText; } + set { _copyMenuItemText = value; } + } string _copyMenuItemText; + + /// + /// Gets or sets the custom text of the "Cut" ContextMenuStrip item. + /// + [Category("BuiltIn-ContextMenu"), DefaultValue(null), Localizable(true)] + public string CutMenuItemText + { + get { return _cutMenuItemText; } + set { _cutMenuItemText = value; } + } string _cutMenuItemText; + + /// + /// Gets or sets the custom text of the "Paste" ContextMenuStrip item. + /// + [Category("BuiltIn-ContextMenu"), DefaultValue(null), Localizable(true)] + public string PasteMenuItemText + { + get { return _pasteMenuItemText; } + set { _pasteMenuItemText = value; } + } string _pasteMenuItemText; + + /// + /// Gets or sets the custom text of the "Select All" ContextMenuStrip item. + /// + [Category("BuiltIn-ContextMenu"), DefaultValue(null), Localizable(true)] + public string SelectAllMenuItemText + { + get { return _selectAllMenuItemText; } + set { _selectAllMenuItemText = value; } + } string _selectAllMenuItemText = null; + + /// + /// Gets the text of the "Cut" ContextMenuStrip item. + /// + internal string CutMenuItemTextInternal { get { return !string.IsNullOrEmpty(CutMenuItemText) ? CutMenuItemText : "Cut"; } } + /// + /// Gets the text of the "Copy" ContextMenuStrip item. + /// + internal string CopyMenuItemTextInternal { get { return !string.IsNullOrEmpty(CopyMenuItemText) ? CopyMenuItemText : "Copy"; } } + /// + /// Gets the text of the "Paste" ContextMenuStrip item. + /// + internal string PasteMenuItemTextInternal { get { return !string.IsNullOrEmpty(PasteMenuItemText) ? PasteMenuItemText : "Paste"; } } + /// + /// Gets the text of the "Select All" ContextMenuStrip item. + /// + internal string SelectAllMenuItemTextInternal { get { return !string.IsNullOrEmpty(SelectAllMenuItemText) ? SelectAllMenuItemText : "Select All"; } } + + /// + /// Gets or sets the image of the "Cut" ContextMenuStrip item. + /// + [Category("BuiltIn-ContextMenu"), DefaultValue(null)] + public Image CutMenuItemImage + { + get { return _cutMenuItemImage; } + set { _cutMenuItemImage = value; } + } Image _cutMenuItemImage = null; + /// + /// Gets or sets the image of the "Copy" ContextMenuStrip item. + /// + [Category("BuiltIn-ContextMenu"), DefaultValue(null)] + public Image CopyMenuItemImage + { + get { return _copyMenuItemImage; } + set { _copyMenuItemImage = value; } + } Image _copyMenuItemImage = null; + /// + /// Gets or sets the image of the "Paste" ContextMenuStrip item. + /// + [Category("BuiltIn-ContextMenu"), DefaultValue(null)] + public Image PasteMenuItemImage + { + get { return _pasteMenuItemImage; } + set { _pasteMenuItemImage = value; } + } Image _pasteMenuItemImage = null; + /// + /// Gets or sets the image of the "Select All" ContextMenuStrip item. + /// + [Category("BuiltIn-ContextMenu"), DefaultValue(null)] + public Image SelectAllMenuItemImage + { + get { return _selectAllMenuItemImage; } + set { _selectAllMenuItemImage = value; } + } Image _selectAllMenuItemImage = null; + } +} diff --git a/GUI.NET/Debugger/HexBox/ByteCharConverters.cs b/GUI.NET/Debugger/HexBox/ByteCharConverters.cs new file mode 100644 index 00000000..1d480997 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/ByteCharConverters.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Be.Windows.Forms +{ + /// + /// The interface for objects that can translate between characters and bytes. + /// + public interface IByteCharConverter + { + /// + /// Returns the character to display for the byte passed across. + /// + /// + /// + char ToChar(byte b); + + /// + /// Returns the byte to use when the character passed across is entered during editing. + /// + /// + /// + byte ToByte(char c); + } + + /// + /// The default implementation. + /// + public class DefaultByteCharConverter : IByteCharConverter + { + /// + /// Returns the character to display for the byte passed across. + /// + /// + /// + public virtual char ToChar(byte b) + { + return b > 0x1F && !(b > 0x7E && b < 0xA0) ? (char)b : '.'; + } + + /// + /// Returns the byte to use for the character passed across. + /// + /// + /// + public virtual byte ToByte(char c) + { + return (byte)c; + } + + /// + /// Returns a description of the byte char provider. + /// + /// + public override string ToString() + { + return "ANSI (Default)"; + } + } + + /// + /// A byte char provider that can translate bytes encoded in codepage 500 EBCDIC + /// + public class EbcdicByteCharProvider : IByteCharConverter + { + /// + /// The IBM EBCDIC code page 500 encoding. Note that this is not always supported by .NET, + /// the underlying platform has to provide support for it. + /// + private Encoding _ebcdicEncoding = Encoding.GetEncoding(500); + + /// + /// Returns the EBCDIC character corresponding to the byte passed across. + /// + /// + /// + public virtual char ToChar(byte b) + { + string encoded = _ebcdicEncoding.GetString(new byte[] { b }); + return encoded.Length > 0 ? encoded[0] : '.'; + } + + /// + /// Returns the byte corresponding to the EBCDIC character passed across. + /// + /// + /// + public virtual byte ToByte(char c) + { + byte[] decoded = _ebcdicEncoding.GetBytes(new char[] { c }); + return decoded.Length > 0 ? decoded[0] : (byte)0; + } + + /// + /// Returns a description of the byte char provider. + /// + /// + public override string ToString() + { + return "EBCDIC (Code Page 500)"; + } + } +} diff --git a/GUI.NET/Debugger/HexBox/ByteCollection.cs b/GUI.NET/Debugger/HexBox/ByteCollection.cs new file mode 100644 index 00000000..c7d2d37b --- /dev/null +++ b/GUI.NET/Debugger/HexBox/ByteCollection.cs @@ -0,0 +1,127 @@ +using System; + +using System.Collections; + +namespace Be.Windows.Forms +{ + /// + /// Represents a collection of bytes. + /// + public class ByteCollection : CollectionBase + { + /// + /// Initializes a new instance of ByteCollection class. + /// + public ByteCollection() { } + + /// + /// Initializes a new instance of ByteCollection class. + /// + /// an array of bytes to add to collection + public ByteCollection(byte[] bs) + { AddRange(bs); } + + /// + /// Gets or sets the value of a byte + /// + public byte this[int index] + { + get { return (byte)List[index]; } + set { List[index] = value; } + } + + /// + /// Adds a byte into the collection. + /// + /// the byte to add + public void Add(byte b) + { List.Add(b); } + + /// + /// Adds a range of bytes to the collection. + /// + /// the bytes to add + public void AddRange(byte[] bs) + { InnerList.AddRange(bs); } + + /// + /// Removes a byte from the collection. + /// + /// the byte to remove + public void Remove(byte b) + { List.Remove(b); } + + /// + /// Removes a range of bytes from the collection. + /// + /// the index of the start byte + /// the count of the bytes to remove + public void RemoveRange(int index, int count) + { InnerList.RemoveRange(index, count); } + + /// + /// Inserts a range of bytes to the collection. + /// + /// the index of start byte + /// an array of bytes to insert + public void InsertRange(int index, byte[] bs) + { InnerList.InsertRange(index, bs); } + + /// + /// Gets all bytes in the array + /// + /// an array of bytes. + public byte[] GetBytes() + { + byte[] bytes = new byte[Count]; + InnerList.CopyTo(0, bytes, 0, bytes.Length); + return bytes; + } + + /// + /// Inserts a byte to the collection. + /// + /// the index + /// a byte to insert + public void Insert(int index, byte b) + { + InnerList.Insert(index, b); + } + + /// + /// Returns the index of the given byte. + /// + public int IndexOf(byte b) + { + return InnerList.IndexOf(b); + } + + /// + /// Returns true, if the byte exists in the collection. + /// + public bool Contains(byte b) + { + return InnerList.Contains(b); + } + + /// + /// Copies the content of the collection into the given array. + /// + public void CopyTo(byte[] bs, int index) + { + InnerList.CopyTo(bs, index); + } + + /// + /// Copies the content of the collection into an array. + /// + /// the array containing all bytes. + public byte[] ToArray() + { + byte[] data = new byte[this.Count]; + this.CopyTo(data, 0); + return data; + } + + } +} diff --git a/GUI.NET/Debugger/HexBox/BytePositionInfo.cs b/GUI.NET/Debugger/HexBox/BytePositionInfo.cs new file mode 100644 index 00000000..9302376e --- /dev/null +++ b/GUI.NET/Debugger/HexBox/BytePositionInfo.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Be.Windows.Forms +{ + /// + /// Represents a position in the HexBox control + /// + struct BytePositionInfo + { + public BytePositionInfo(long index, int characterPosition) + { + _index = index; + _characterPosition = characterPosition; + } + + public int CharacterPosition + { + get { return _characterPosition; } + } int _characterPosition; + + public long Index + { + get { return _index; } + } long _index; + } +} diff --git a/GUI.NET/Debugger/HexBox/DataBlock.cs b/GUI.NET/Debugger/HexBox/DataBlock.cs new file mode 100644 index 00000000..e5ea58f7 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/DataBlock.cs @@ -0,0 +1,42 @@ +using System; + +namespace Be.Windows.Forms +{ + internal abstract class DataBlock + { + internal DataMap _map; + internal DataBlock _nextBlock; + internal DataBlock _previousBlock; + + public abstract long Length + { + get; + } + + public DataMap Map + { + get + { + return _map; + } + } + + public DataBlock NextBlock + { + get + { + return _nextBlock; + } + } + + public DataBlock PreviousBlock + { + get + { + return _previousBlock; + } + } + + public abstract void RemoveBytes(long position, long count); + } +} diff --git a/GUI.NET/Debugger/HexBox/DataMap.cs b/GUI.NET/Debugger/HexBox/DataMap.cs new file mode 100644 index 00000000..2876b086 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/DataMap.cs @@ -0,0 +1,318 @@ +using System; +using System.Collections; +using System.Text; + +namespace Be.Windows.Forms +{ + internal class DataMap : ICollection, IEnumerable + { + readonly object _syncRoot = new object(); + internal int _count; + internal DataBlock _firstBlock; + internal int _version; + + public DataMap() + { + } + + public DataMap(IEnumerable collection) + { + if (collection == null) + { + throw new ArgumentNullException("collection"); + } + + foreach (DataBlock item in collection) + { + AddLast(item); + } + } + + public DataBlock FirstBlock + { + get + { + return _firstBlock; + } + } + + public void AddAfter(DataBlock block, DataBlock newBlock) + { + AddAfterInternal(block, newBlock); + } + + public void AddBefore(DataBlock block, DataBlock newBlock) + { + AddBeforeInternal(block, newBlock); + } + + public void AddFirst(DataBlock block) + { + if (_firstBlock == null) + { + AddBlockToEmptyMap(block); + } + else + { + AddBeforeInternal(_firstBlock, block); + } + } + + public void AddLast(DataBlock block) + { + if (_firstBlock == null) + { + AddBlockToEmptyMap(block); + } + else + { + AddAfterInternal(GetLastBlock(), block); + } + } + + public void Remove(DataBlock block) + { + RemoveInternal(block); + } + + public void RemoveFirst() + { + if (_firstBlock == null) + { + throw new InvalidOperationException("The collection is empty."); + } + RemoveInternal(_firstBlock); + } + + public void RemoveLast() + { + if (_firstBlock == null) + { + throw new InvalidOperationException("The collection is empty."); + } + RemoveInternal(GetLastBlock()); + } + + public DataBlock Replace(DataBlock block, DataBlock newBlock) + { + AddAfterInternal(block, newBlock); + RemoveInternal(block); + return newBlock; + } + + public void Clear() + { + DataBlock block = FirstBlock; + while (block != null) + { + DataBlock nextBlock = block.NextBlock; + InvalidateBlock(block); + block = nextBlock; + } + _firstBlock = null; + _count = 0; + _version++; + } + + void AddAfterInternal(DataBlock block, DataBlock newBlock) + { + newBlock._previousBlock = block; + newBlock._nextBlock = block._nextBlock; + newBlock._map = this; + + if (block._nextBlock != null) + { + block._nextBlock._previousBlock = newBlock; + } + block._nextBlock = newBlock; + + this._version++; + this._count++; + } + + void AddBeforeInternal(DataBlock block, DataBlock newBlock) + { + newBlock._nextBlock = block; + newBlock._previousBlock = block._previousBlock; + newBlock._map = this; + + if (block._previousBlock != null) + { + block._previousBlock._nextBlock = newBlock; + } + block._previousBlock = newBlock; + + if (_firstBlock == block) + { + _firstBlock = newBlock; + } + this._version++; + this._count++; + } + + void RemoveInternal(DataBlock block) + { + DataBlock previousBlock = block._previousBlock; + DataBlock nextBlock = block._nextBlock; + + if (previousBlock != null) + { + previousBlock._nextBlock = nextBlock; + } + + if (nextBlock != null) + { + nextBlock._previousBlock = previousBlock; + } + + if (_firstBlock == block) + { + _firstBlock = nextBlock; + } + + InvalidateBlock(block); + + _count--; + _version++; + } + + DataBlock GetLastBlock() + { + DataBlock lastBlock = null; + for (DataBlock block = FirstBlock; block != null; block = block.NextBlock) + { + lastBlock = block; + } + return lastBlock; + } + + void InvalidateBlock(DataBlock block) + { + block._map = null; + block._nextBlock = null; + block._previousBlock = null; + } + + void AddBlockToEmptyMap(DataBlock block) + { + block._map = this; + block._nextBlock = null; + block._previousBlock = null; + + _firstBlock = block; + _version++; + _count++; + } + + #region ICollection Members + public void CopyTo(Array array, int index) + { + DataBlock[] blockArray = array as DataBlock[]; + for (DataBlock block = FirstBlock; block != null; block = block.NextBlock) + { + blockArray[index++] = block; + } + } + + public int Count + { + get + { + return _count; + } + } + + public bool IsSynchronized + { + get + { + return false; + } + } + + public object SyncRoot + { + get + { + return _syncRoot; + } + } + #endregion + + #region IEnumerable Members + public IEnumerator GetEnumerator() + { + return new Enumerator(this); + } + #endregion + + #region Enumerator Nested Type + internal class Enumerator : IEnumerator, IDisposable + { + DataMap _map; + DataBlock _current; + int _index; + int _version; + + internal Enumerator(DataMap map) + { + _map = map; + _version = map._version; + _current = null; + _index = -1; + } + + object IEnumerator.Current + { + get + { + if (_index < 0 || _index > _map.Count) + { + throw new InvalidOperationException("Enumerator is positioned before the first element or after the last element of the collection."); + } + return _current; + } + } + + public bool MoveNext() + { + if (this._version != _map._version) + { + throw new InvalidOperationException("Collection was modified after the enumerator was instantiated."); + } + + if (_index >= _map.Count) + { + return false; + } + + if (++_index == 0) + { + _current = _map.FirstBlock; + } + else + { + _current = _current.NextBlock; + } + + return (_index < _map.Count); + } + + void IEnumerator.Reset() + { + if (this._version != this._map._version) + { + throw new InvalidOperationException("Collection was modified after the enumerator was instantiated."); + } + + this._index = -1; + this._current = null; + } + + public void Dispose() + { + } + } + #endregion + } +} diff --git a/GUI.NET/Debugger/HexBox/DynamicByteProvider.cs b/GUI.NET/Debugger/HexBox/DynamicByteProvider.cs new file mode 100644 index 00000000..e14f2445 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/DynamicByteProvider.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; + +namespace Be.Windows.Forms +{ + /// + /// Byte provider for a small amount of data. + /// + public class DynamicByteProvider : IByteProvider + { + /// + /// Contains information about changes. + /// + bool _hasChanges; + /// + /// Contains a byte collection. + /// + List _bytes; + + /// + /// Initializes a new instance of the DynamicByteProvider class. + /// + /// + public DynamicByteProvider(byte[] data) : this(new List(data)) + { + } + + /// + /// Initializes a new instance of the DynamicByteProvider class. + /// + /// + public DynamicByteProvider(List bytes) + { + _bytes = bytes; + } + + /// + /// Raises the Changed event. + /// + void OnChanged(EventArgs e) + { + _hasChanges = true; + + if(Changed != null) + Changed(this, e); + } + + /// + /// Raises the LengthChanged event. + /// + void OnLengthChanged(EventArgs e) + { + if(LengthChanged != null) + LengthChanged(this, e); + } + + /// + /// Gets the byte collection. + /// + public List Bytes + { + get { return _bytes; } + } + + #region IByteProvider Members + /// + /// True, when changes are done. + /// + public bool HasChanges() + { + return _hasChanges; + } + + /// + /// Applies changes. + /// + public void ApplyChanges() + { + _hasChanges = false; + } + + /// + /// Occurs, when the write buffer contains new changes. + /// + public event EventHandler Changed; + + /// + /// Occurs, when InsertBytes or DeleteBytes method is called. + /// + public event EventHandler LengthChanged; + + public event EventHandler ByteChanged; + + + /// + /// Reads a byte from the byte collection. + /// + /// the index of the byte to read + /// the byte + public byte ReadByte(long index) + { return _bytes[(int)index]; } + + /// + /// Write a byte into the byte collection. + /// + /// the index of the byte to write. + /// the byte + public void WriteByte(long index, byte value) + { + if(_bytes[(int)index] != value) { + _bytes[(int)index] = value; + ByteChanged?.Invoke((int)index, EventArgs.Empty); + OnChanged(EventArgs.Empty); + } + } + + /// + /// Deletes bytes from the byte collection. + /// + /// the start index of the bytes to delete. + /// the length of bytes to delete. + public void DeleteBytes(long index, long length) + { + int internal_index = (int)Math.Max(0, index); + int internal_length = (int)Math.Min((int)Length, length); + _bytes.RemoveRange(internal_index, internal_length); + + OnLengthChanged(EventArgs.Empty); + OnChanged(EventArgs.Empty); + } + + /// + /// Inserts byte into the byte collection. + /// + /// the start index of the bytes in the byte collection + /// the byte array to insert + public void InsertBytes(long index, byte[] bs) + { + _bytes.InsertRange((int)index, bs); + + OnLengthChanged(EventArgs.Empty); + OnChanged(EventArgs.Empty); + } + + /// + /// Gets the length of the bytes in the byte collection. + /// + public long Length + { + get + { + return _bytes.Count; + } + } + + /// + /// Returns true + /// + public virtual bool SupportsWriteByte() + { + return true; + } + + /// + /// Returns true + /// + public virtual bool SupportsInsertBytes() + { + return true; + } + + /// + /// Returns true + /// + public virtual bool SupportsDeleteBytes() + { + return true; + } + #endregion + + } +} diff --git a/GUI.NET/Debugger/HexBox/DynamicFileByteProvider.cs b/GUI.NET/Debugger/HexBox/DynamicFileByteProvider.cs new file mode 100644 index 00000000..a68e2752 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/DynamicFileByteProvider.cs @@ -0,0 +1,569 @@ +using System; +using System.Text; +using System.IO; + +namespace Be.Windows.Forms +{ + /// + /// Implements a fully editable byte provider for file data of any size. + /// + /// + /// Only changes to the file are stored in memory with reads from the + /// original data occurring as required. + /// + public sealed class DynamicFileByteProvider : IByteProvider, IDisposable + { + const int COPY_BLOCK_SIZE = 4096; + + string _fileName; + Stream _stream; + DataMap _dataMap; + long _totalLength; + bool _readOnly; + + /// + /// Constructs a new instance. + /// + /// The name of the file from which bytes should be provided. + public DynamicFileByteProvider(string fileName) : this(fileName, false) { } + + /// + /// Constructs a new instance. + /// + /// The name of the file from which bytes should be provided. + /// True, opens the file in read-only mode. + public DynamicFileByteProvider(string fileName, bool readOnly) + { + _fileName = fileName; + + if (!readOnly) + { + _stream = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); + } + else + { + _stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + } + + _readOnly = readOnly; + + ReInitialize(); + } + + /// + /// Constructs a new instance. + /// + /// the stream containing the data. + /// + /// The stream must supported seek operations. + /// + public DynamicFileByteProvider(Stream stream) + { + if (stream == null) + throw new ArgumentNullException("stream"); + if (!stream.CanSeek) + throw new ArgumentException("stream must supported seek operations(CanSeek)"); + + _stream = stream; + _readOnly = !stream.CanWrite; + ReInitialize(); + } + + #region IByteProvider Members + /// + /// See for more information. + /// + public event EventHandler LengthChanged; + + /// + /// See for more information. + /// + public event EventHandler Changed; + + /// + /// See for more information. + /// + public byte ReadByte(long index) + { + long blockOffset; + DataBlock block = GetDataBlock(index, out blockOffset); + FileDataBlock fileBlock = block as FileDataBlock; + if (fileBlock != null) + { + return ReadByteFromFile(fileBlock.FileOffset + index - blockOffset); + } + else + { + MemoryDataBlock memoryBlock = (MemoryDataBlock)block; + return memoryBlock.Data[index - blockOffset]; + } + } + + /// + /// See for more information. + /// + public void WriteByte(long index, byte value) + { + try + { + // Find the block affected. + long blockOffset; + DataBlock block = GetDataBlock(index, out blockOffset); + + // If the byte is already in a memory block, modify it. + MemoryDataBlock memoryBlock = block as MemoryDataBlock; + if (memoryBlock != null) + { + memoryBlock.Data[index - blockOffset] = value; + return; + } + + FileDataBlock fileBlock = (FileDataBlock)block; + + // If the byte changing is the first byte in the block and the previous block is a memory block, extend that. + if (blockOffset == index && block.PreviousBlock != null) + { + MemoryDataBlock previousMemoryBlock = block.PreviousBlock as MemoryDataBlock; + if (previousMemoryBlock != null) + { + previousMemoryBlock.AddByteToEnd(value); + fileBlock.RemoveBytesFromStart(1); + if (fileBlock.Length == 0) + { + _dataMap.Remove(fileBlock); + } + return; + } + } + + // If the byte changing is the last byte in the block and the next block is a memory block, extend that. + if (blockOffset + fileBlock.Length - 1 == index && block.NextBlock != null) + { + MemoryDataBlock nextMemoryBlock = block.NextBlock as MemoryDataBlock; + if (nextMemoryBlock != null) + { + nextMemoryBlock.AddByteToStart(value); + fileBlock.RemoveBytesFromEnd(1); + if (fileBlock.Length == 0) + { + _dataMap.Remove(fileBlock); + } + return; + } + } + + // Split the block into a prefix and a suffix and place a memory block in-between. + FileDataBlock prefixBlock = null; + if (index > blockOffset) + { + prefixBlock = new FileDataBlock(fileBlock.FileOffset, index - blockOffset); + } + + FileDataBlock suffixBlock = null; + if (index < blockOffset + fileBlock.Length - 1) + { + suffixBlock = new FileDataBlock( + fileBlock.FileOffset + index - blockOffset + 1, + fileBlock.Length - (index - blockOffset + 1)); + } + + block = _dataMap.Replace(block, new MemoryDataBlock(value)); + + if (prefixBlock != null) + { + _dataMap.AddBefore(block, prefixBlock); + } + + if (suffixBlock != null) + { + _dataMap.AddAfter(block, suffixBlock); + } + } + finally + { + OnChanged(EventArgs.Empty); + } + } + + /// + /// See for more information. + /// + public void InsertBytes(long index, byte[] bs) + { + try + { + // Find the block affected. + long blockOffset; + DataBlock block = GetDataBlock(index, out blockOffset); + + // If the insertion point is in a memory block, just insert it. + MemoryDataBlock memoryBlock = block as MemoryDataBlock; + if (memoryBlock != null) + { + memoryBlock.InsertBytes(index - blockOffset, bs); + return; + } + + FileDataBlock fileBlock = (FileDataBlock)block; + + // If the insertion point is at the start of a file block, and the previous block is a memory block, append it to that block. + if (blockOffset == index && block.PreviousBlock != null) + { + MemoryDataBlock previousMemoryBlock = block.PreviousBlock as MemoryDataBlock; + if (previousMemoryBlock != null) + { + previousMemoryBlock.InsertBytes(previousMemoryBlock.Length, bs); + return; + } + } + + // Split the block into a prefix and a suffix and place a memory block in-between. + FileDataBlock prefixBlock = null; + if (index > blockOffset) + { + prefixBlock = new FileDataBlock(fileBlock.FileOffset, index - blockOffset); + } + + FileDataBlock suffixBlock = null; + if (index < blockOffset + fileBlock.Length) + { + suffixBlock = new FileDataBlock( + fileBlock.FileOffset + index - blockOffset, + fileBlock.Length - (index - blockOffset)); + } + + block = _dataMap.Replace(block, new MemoryDataBlock(bs)); + + if (prefixBlock != null) + { + _dataMap.AddBefore(block, prefixBlock); + } + + if (suffixBlock != null) + { + _dataMap.AddAfter(block, suffixBlock); + } + } + finally + { + _totalLength += bs.Length; + OnLengthChanged(EventArgs.Empty); + OnChanged(EventArgs.Empty); + } + } + + /// + /// See for more information. + /// + public void DeleteBytes(long index, long length) + { + try + { + long bytesToDelete = length; + + // Find the first block affected. + long blockOffset; + DataBlock block = GetDataBlock(index, out blockOffset); + + // Truncate or remove each block as necessary. + while ((bytesToDelete > 0) && (block != null)) + { + long blockLength = block.Length; + DataBlock nextBlock = block.NextBlock; + + // Delete the appropriate section from the block (this may result in two blocks or a zero length block). + long count = Math.Min(bytesToDelete, blockLength - (index - blockOffset)); + block.RemoveBytes(index - blockOffset, count); + + if (block.Length == 0) + { + _dataMap.Remove(block); + if (_dataMap.FirstBlock == null) + { + _dataMap.AddFirst(new MemoryDataBlock(new byte[0])); + } + } + + bytesToDelete -= count; + blockOffset += block.Length; + block = (bytesToDelete > 0) ? nextBlock : null; + } + } + finally + { + _totalLength -= length; + OnLengthChanged(EventArgs.Empty); + OnChanged(EventArgs.Empty); + } + } + + /// + /// See for more information. + /// + public long Length + { + get + { + return _totalLength; + } + } + + /// + /// See for more information. + /// + public bool HasChanges() + { + if (_readOnly) + return false; + + if (_totalLength != _stream.Length) + { + return true; + } + + long offset = 0; + for (DataBlock block = _dataMap.FirstBlock; block != null; block = block.NextBlock) + { + FileDataBlock fileBlock = block as FileDataBlock; + if (fileBlock == null) + { + return true; + } + + if (fileBlock.FileOffset != offset) + { + return true; + } + + offset += fileBlock.Length; + } + return (offset != _stream.Length); + } + + /// + /// See for more information. + /// + public void ApplyChanges() + { + if (_readOnly) + throw new OperationCanceledException("File is in read-only mode"); + + // This method is implemented to efficiently save the changes to the same file stream opened for reading. + // Saving to a separate file would be a much simpler implementation. + + // Firstly, extend the file length (if necessary) to ensure that there is enough disk space. + if (_totalLength > _stream.Length) + { + _stream.SetLength(_totalLength); + } + + // Secondly, shift around any file sections that have moved. + long dataOffset = 0; + for (DataBlock block = _dataMap.FirstBlock; block != null; block = block.NextBlock) + { + FileDataBlock fileBlock = block as FileDataBlock; + if (fileBlock != null && fileBlock.FileOffset != dataOffset) + { + MoveFileBlock(fileBlock, dataOffset); + } + dataOffset += block.Length; + } + + // Next, write in-memory changes. + dataOffset = 0; + for (DataBlock block = _dataMap.FirstBlock; block != null; block = block.NextBlock) + { + MemoryDataBlock memoryBlock = block as MemoryDataBlock; + if (memoryBlock != null) + { + _stream.Position = dataOffset; + for (int memoryOffset = 0; memoryOffset < memoryBlock.Length; memoryOffset += COPY_BLOCK_SIZE) + { + _stream.Write(memoryBlock.Data, memoryOffset, (int)Math.Min(COPY_BLOCK_SIZE, memoryBlock.Length - memoryOffset)); + } + } + dataOffset += block.Length; + } + + // Finally, if the file has shortened, truncate the stream. + _stream.SetLength(_totalLength); + ReInitialize(); + } + + /// + /// See for more information. + /// + public bool SupportsWriteByte() + { + return !_readOnly; + } + + /// + /// See for more information. + /// + public bool SupportsInsertBytes() + { + return !_readOnly; + } + + /// + /// See for more information. + /// + public bool SupportsDeleteBytes() + { + return !_readOnly; + } + #endregion + + #region IDisposable Members + /// + /// See for more information. + /// + ~DynamicFileByteProvider() + { + Dispose(); + } + + /// + /// See for more information. + /// + public void Dispose() + { + if (_stream != null) + { + _stream.Close(); + _stream = null; + } + _fileName = null; + _dataMap = null; + GC.SuppressFinalize(this); + } + #endregion + + /// + /// Gets a value, if the file is opened in read-only mode. + /// + public bool ReadOnly + { + get { return _readOnly; } + } + + void OnLengthChanged(EventArgs e) + { + if (LengthChanged != null) + LengthChanged(this, e); + } + + void OnChanged(EventArgs e) + { + if (Changed != null) + { + Changed(this, e); + } + } + + DataBlock GetDataBlock(long findOffset, out long blockOffset) + { + if (findOffset < 0 || findOffset > _totalLength) + { + throw new ArgumentOutOfRangeException("findOffset"); + } + + // Iterate over the blocks until the block containing the required offset is encountered. + blockOffset = 0; + for (DataBlock block = _dataMap.FirstBlock; block != null; block = block.NextBlock) + { + if ((blockOffset <= findOffset && blockOffset + block.Length > findOffset) || block.NextBlock == null) + { + return block; + } + blockOffset += block.Length; + } + return null; + } + + FileDataBlock GetNextFileDataBlock(DataBlock block, long dataOffset, out long nextDataOffset) + { + // Iterate over the remaining blocks until a file block is encountered. + nextDataOffset = dataOffset + block.Length; + block = block.NextBlock; + while (block != null) + { + FileDataBlock fileBlock = block as FileDataBlock; + if (fileBlock != null) + { + return fileBlock; + } + nextDataOffset += block.Length; + block = block.NextBlock; + } + return null; + } + + byte ReadByteFromFile(long fileOffset) + { + // Move to the correct position and read the byte. + if (_stream.Position != fileOffset) + { + _stream.Position = fileOffset; + } + return (byte)_stream.ReadByte(); + } + + void MoveFileBlock(FileDataBlock fileBlock, long dataOffset) + { + // First, determine whether the next file block needs to move before this one. + long nextDataOffset; + FileDataBlock nextFileBlock = GetNextFileDataBlock(fileBlock, dataOffset, out nextDataOffset); + if (nextFileBlock != null && dataOffset + fileBlock.Length > nextFileBlock.FileOffset) + { + // The next block needs to move first, so do that now. + MoveFileBlock(nextFileBlock, nextDataOffset); + } + + // Now, move the block. + if (fileBlock.FileOffset > dataOffset) + { + // Move the section to earlier in the file stream (done in chunks starting at the beginning of the section). + byte[] buffer = new byte[COPY_BLOCK_SIZE]; + for (long relativeOffset = 0; relativeOffset < fileBlock.Length; relativeOffset += buffer.Length) + { + long readOffset = fileBlock.FileOffset + relativeOffset; + int bytesToRead = (int)Math.Min(buffer.Length, fileBlock.Length - relativeOffset); + _stream.Position = readOffset; + _stream.Read(buffer, 0, bytesToRead); + + long writeOffset = dataOffset + relativeOffset; + _stream.Position = writeOffset; + _stream.Write(buffer, 0, bytesToRead); + } + } + else + { + // Move the section to later in the file stream (done in chunks starting at the end of the section). + byte[] buffer = new byte[COPY_BLOCK_SIZE]; + for (long relativeOffset = 0; relativeOffset < fileBlock.Length; relativeOffset += buffer.Length) + { + int bytesToRead = (int)Math.Min(buffer.Length, fileBlock.Length - relativeOffset); + long readOffset = fileBlock.FileOffset + fileBlock.Length - relativeOffset - bytesToRead; + _stream.Position = readOffset; + _stream.Read(buffer, 0, bytesToRead); + + long writeOffset = dataOffset + fileBlock.Length - relativeOffset - bytesToRead; + _stream.Position = writeOffset; + _stream.Write(buffer, 0, bytesToRead); + } + } + + // This block now points to a different position in the file. + fileBlock.SetFileOffset(dataOffset); + } + + void ReInitialize() + { + _dataMap = new DataMap(); + _dataMap.AddFirst(new FileDataBlock(0, _stream.Length)); + _totalLength = _stream.Length; + } + } +} diff --git a/GUI.NET/Debugger/HexBox/FileByteProvider.cs b/GUI.NET/Debugger/HexBox/FileByteProvider.cs new file mode 100644 index 00000000..b2bb8b9d --- /dev/null +++ b/GUI.NET/Debugger/HexBox/FileByteProvider.cs @@ -0,0 +1,273 @@ +using System; +using System.IO; +using System.Collections; + +namespace Be.Windows.Forms +{ + /// + /// Byte provider for (big) files. + /// + public class FileByteProvider : IByteProvider, IDisposable + { + #region WriteCollection class + /// + /// Represents the write buffer class + /// + class WriteCollection : DictionaryBase + { + /// + /// Gets or sets a byte in the collection + /// + public byte this[long index] + { + get { return (byte)this.Dictionary[index]; } + set { Dictionary[index] = value; } + } + + /// + /// Adds a byte into the collection + /// + /// the index of the byte + /// the value of the byte + public void Add(long index, byte value) + { Dictionary.Add(index, value); } + + /// + /// Determines if a byte with the given index exists. + /// + /// the index of the byte + /// true, if the is in the collection + public bool Contains(long index) + { return Dictionary.Contains(index); } + + } + #endregion + + /// + /// Occurs, when the write buffer contains new changes. + /// + public event EventHandler Changed; + + /// + /// Contains all changes + /// + WriteCollection _writes = new WriteCollection(); + + /// + /// Contains the file name. + /// + string _fileName; + /// + /// Contains the file stream. + /// + FileStream _fileStream; + /// + /// Read-only access. + /// + bool _readOnly; + + /// + /// Initializes a new instance of the FileByteProvider class. + /// + /// + public FileByteProvider(string fileName) + { + _fileName = fileName; + + try + { + // try to open in write mode + _fileStream = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); + } + catch + { + // write mode failed, try to open in read-only and fileshare friendly mode. + try + { + _fileStream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + _readOnly = true; + } + catch + { + throw; + } + } + } + + /// + /// Terminates the instance of the FileByteProvider class. + /// + ~FileByteProvider() + { + Dispose(); + } + + /// + /// Raises the Changed event. + /// + /// Never used. + void OnChanged(EventArgs e) + { + if(Changed != null) + Changed(this, e); + } + + /// + /// Gets the name of the file the byte provider is using. + /// + public string FileName + { + get { return _fileName; } + } + + /// + /// Returns a value if there are some changes. + /// + /// true, if there are some changes + public bool HasChanges() + { + return (_writes.Count > 0); + } + + /// + /// Updates the file with all changes the write buffer contains. + /// + public void ApplyChanges() + { + if (this._readOnly) + { + throw new Exception("File is in read-only mode."); + } + + if(!HasChanges()) + return; + + IDictionaryEnumerator en = _writes.GetEnumerator(); + while(en.MoveNext()) + { + long index = (long)en.Key; + byte value = (byte)en.Value; + if(_fileStream.Position != index) + _fileStream.Position = index; + _fileStream.Write(new byte[] { value }, 0, 1); + } + _writes.Clear(); + } + + /// + /// Clears the write buffer and reject all changes made. + /// + public void RejectChanges() + { + _writes.Clear(); + } + + #region IByteProvider Members + /// + /// Never used. + /// + #pragma warning disable 0067 + public event EventHandler LengthChanged; + #pragma warning restore 0067 + + /// + /// Reads a byte from the file. + /// + /// the index of the byte to read + /// the byte + public byte ReadByte(long index) + { + if(_writes.Contains(index)) + return _writes[index]; + + if(_fileStream.Position != index) + _fileStream.Position = index; + + byte res = (byte)_fileStream.ReadByte(); + return res; + } + + /// + /// Gets the length of the file. + /// + public long Length + { + get + { + return _fileStream.Length; + } + } + + /// + /// Writes a byte into write buffer + /// + public void WriteByte(long index, byte value) + { + if(_writes.Contains(index)) + _writes[index] = value; + else + _writes.Add(index, value); + + OnChanged(EventArgs.Empty); + } + + /// + /// Not supported + /// + public void DeleteBytes(long index, long length) + { + throw new NotSupportedException("FileByteProvider.DeleteBytes"); + } + + /// + /// Not supported + /// + public void InsertBytes(long index, byte[] bs) + { + throw new NotSupportedException("FileByteProvider.InsertBytes"); + } + + /// + /// Returns true + /// + public bool SupportsWriteByte() + { + return !_readOnly; + } + + /// + /// Returns false + /// + public bool SupportsInsertBytes() + { + return false; + } + + /// + /// Returns false + /// + public bool SupportsDeleteBytes() + { + return false; + } + #endregion + + #region IDisposable Members + /// + /// Releases the file handle used by the FileByteProvider. + /// + public void Dispose() + { + if(_fileStream != null) + { + _fileName = null; + + _fileStream.Close(); + _fileStream = null; + } + + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/GUI.NET/Debugger/HexBox/FileDataBlock.cs b/GUI.NET/Debugger/HexBox/FileDataBlock.cs new file mode 100644 index 00000000..40e0b0eb --- /dev/null +++ b/GUI.NET/Debugger/HexBox/FileDataBlock.cs @@ -0,0 +1,96 @@ +using System; + +namespace Be.Windows.Forms +{ + internal sealed class FileDataBlock : DataBlock + { + long _length; + long _fileOffset; + + public FileDataBlock(long fileOffset, long length) + { + _fileOffset = fileOffset; + _length = length; + } + + public long FileOffset + { + get + { + return _fileOffset; + } + } + + public override long Length + { + get + { + return _length; + } + } + + public void SetFileOffset(long value) + { + _fileOffset = value; + } + + public void RemoveBytesFromEnd(long count) + { + if (count > _length) + { + throw new ArgumentOutOfRangeException("count"); + } + + _length -= count; + } + + public void RemoveBytesFromStart(long count) + { + if (count > _length) + { + throw new ArgumentOutOfRangeException("count"); + } + + _fileOffset += count; + _length -= count; + } + + public override void RemoveBytes(long position, long count) + { + if (position > _length) + { + throw new ArgumentOutOfRangeException("position"); + } + + if (position + count > _length) + { + throw new ArgumentOutOfRangeException("count"); + } + + long prefixLength = position; + long prefixFileOffset = _fileOffset; + + long suffixLength = _length - count - prefixLength; + long suffixFileOffset = _fileOffset + position + count; + + if (prefixLength > 0 && suffixLength > 0) + { + _fileOffset = prefixFileOffset; + _length = prefixLength; + _map.AddAfter(this, new FileDataBlock(suffixFileOffset, suffixLength)); + return; + } + + if (prefixLength > 0) + { + _fileOffset = prefixFileOffset; + _length = prefixLength; + } + else + { + _fileOffset = suffixFileOffset; + _length = suffixLength; + } + } + } +} diff --git a/GUI.NET/Debugger/HexBox/FindOptions.cs b/GUI.NET/Debugger/HexBox/FindOptions.cs new file mode 100644 index 00000000..b0ca4de8 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/FindOptions.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Be.Windows.Forms +{ + /// + /// Defines the type of the Find operation. + /// + public enum FindType + { + /// + /// Used for Text Find operations + /// + Text, + /// + /// Used for Hex Find operations + /// + Hex + } + + /// + /// Defines all state information nee + /// + public class FindOptions + { + public bool WrapSearch { get; set; } + public bool HasWildcard { get; set; } + + /// + /// Gets or sets whether the Find options are valid + /// + public bool IsValid { get; set; } + /// + /// Gets the Find buffer used for case insensitive Find operations. This is the binary representation of Text. + /// + internal byte[] FindBuffer { get; private set; } + /// + /// Gets the Find buffer used for case sensitive Find operations. This is the binary representation of Text in lower case format. + /// + internal byte[] FindBufferLowerCase { get; private set; } + /// + /// Gets the Find buffer used for case sensitive Find operations. This is the binary representation of Text in upper case format. + /// + internal byte[] FindBufferUpperCase { get; private set; } + /// + /// Contains the MatchCase value + /// + bool _matchCase; + /// + /// Gets or sets the value, whether the Find operation is case sensitive or not. + /// + public bool MatchCase + { + get { return _matchCase; } + set + { + _matchCase = value; + UpdateFindBuffer(); + } + } + /// + /// Contains the text that should be found. + /// + string _text; + /// + /// Gets or sets the text that should be found. Only used, when Type is FindType.Hex. + /// + public string Text + { + get { return _text; } + set + { + _text = value; + UpdateFindBuffer(); + } + } + /// + /// Gets or sets the hex buffer that should be found. Only used, when Type is FindType.Hex. + /// + public byte[] Hex { get; set; } + /// + /// Gets or sets the type what should be searched. + /// + public FindType Type { get; set; } + /// + /// Updates the find buffer. + /// + void UpdateFindBuffer() + { + string text = this.Text != null ? this.Text : string.Empty; + FindBuffer = ASCIIEncoding.ASCII.GetBytes(text); + FindBufferLowerCase = ASCIIEncoding.ASCII.GetBytes(text.ToLower()); + FindBufferUpperCase = ASCIIEncoding.ASCII.GetBytes(text.ToUpper()); + } +} +} diff --git a/GUI.NET/Debugger/HexBox/HexBox.cs b/GUI.NET/Debugger/HexBox/HexBox.cs new file mode 100644 index 00000000..18c97586 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/HexBox.cs @@ -0,0 +1,4120 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using System.Runtime.InteropServices; +using System.ComponentModel; +using System.Security.Permissions; +using System.Windows.Forms.VisualStyles; +using System.Text; +using System.Collections.Generic; + +namespace Be.Windows.Forms +{ + /// + /// Represents a hex box control. + /// + [ToolboxBitmap(typeof(HexBox), "HexBox.bmp")] + public class HexBox : Control + { + #region IKeyInterpreter interface + /// + /// Defines a user input handler such as for mouse and keyboard input + /// + interface IKeyInterpreter + { + /// + /// Activates mouse events + /// + void Activate(); + /// + /// Deactivate mouse events + /// + void Deactivate(); + /// + /// Preprocesses WM_KEYUP window message. + /// + /// the Message object to process. + /// True, if the message was processed. + bool PreProcessWmKeyUp(ref Message m); + /// + /// Preprocesses WM_CHAR window message. + /// + /// the Message object to process. + /// True, if the message was processed. + bool PreProcessWmChar(ref Message m); + /// + /// Preprocesses WM_KEYDOWN window message. + /// + /// the Message object to process. + /// True, if the message was processed. + bool PreProcessWmKeyDown(ref Message m); + /// + /// Gives some information about where to place the caret. + /// + /// the index of the byte + /// the position where the caret is to place. + PointF GetCaretPointF(long byteIndex); + } + #endregion + + #region EmptyKeyInterpreter class + /// + /// Represents an empty input handler without any functionality. + /// If is set ByteProvider to null, then this interpreter is used. + /// + class EmptyKeyInterpreter : IKeyInterpreter + { + HexBox _hexBox; + + public EmptyKeyInterpreter(HexBox hexBox) + { + _hexBox = hexBox; + } + + #region IKeyInterpreter Members + public void Activate() { } + public void Deactivate() { } + + public bool PreProcessWmKeyUp(ref Message m) + { return _hexBox.BasePreProcessMessage(ref m); } + + public bool PreProcessWmChar(ref Message m) + { return _hexBox.BasePreProcessMessage(ref m); } + + public bool PreProcessWmKeyDown(ref Message m) + { return _hexBox.BasePreProcessMessage(ref m); } + + public PointF GetCaretPointF(long byteIndex) + { return new PointF(); } + + #endregion + } + #endregion + + #region KeyInterpreter class + /// + /// Handles user input such as mouse and keyboard input during hex view edit + /// + class KeyInterpreter : IKeyInterpreter + { + /// + /// Delegate for key-down processing. + /// + /// the message object contains key data information + /// True, if the message was processed + delegate bool MessageDelegate(ref Message m); + + #region Fields + /// + /// Contains the parent HexBox control + /// + protected HexBox _hexBox; + + /// + /// Contains True, if shift key is down + /// + protected bool _shiftDown; + /// + /// Contains True, if mouse is down + /// + bool _mouseDown; + /// + /// Contains the selection start position info + /// + BytePositionInfo _bpiStart; + /// + /// Contains the current mouse selection position info + /// + BytePositionInfo _bpi; + /// + /// Contains all message handlers of key interpreter key down message + /// + Dictionary _messageHandlers; + #endregion + + #region Ctors + public KeyInterpreter(HexBox hexBox) + { + _hexBox = hexBox; + } + #endregion + + #region Activate, Deactive methods + public virtual void Activate() + { + _hexBox.MouseDown += new MouseEventHandler(BeginMouseSelection); + _hexBox.MouseMove += new MouseEventHandler(UpdateMouseSelection); + _hexBox.MouseUp += new MouseEventHandler(EndMouseSelection); + } + + public virtual void Deactivate() + { + _hexBox.MouseDown -= new MouseEventHandler(BeginMouseSelection); + _hexBox.MouseMove -= new MouseEventHandler(UpdateMouseSelection); + _hexBox.MouseUp -= new MouseEventHandler(EndMouseSelection); + } + #endregion + + #region Mouse selection methods + void BeginMouseSelection(object sender, MouseEventArgs e) + { + System.Diagnostics.Debug.WriteLine("BeginMouseSelection()", "KeyInterpreter"); + + if (e.Button != MouseButtons.Left) + return; + + _mouseDown = true; + + if (!_shiftDown) + { + _bpiStart = new BytePositionInfo(_hexBox._bytePos, _hexBox._byteCharacterPos); + _hexBox.ReleaseSelection(); + } + else + { + UpdateMouseSelection(this, e); + } + } + + void UpdateMouseSelection(object sender, MouseEventArgs e) + { + if (!_mouseDown) + return; + + _bpi = GetBytePositionInfo(new Point(e.X, e.Y)); + long selEnd = _bpi.Index; + long realselStart; + long realselLength; + + if (selEnd < _bpiStart.Index) + { + realselStart = selEnd; + realselLength = _bpiStart.Index - selEnd; + } + else if (selEnd > _bpiStart.Index) + { + realselStart = _bpiStart.Index; + realselLength = selEnd - realselStart; + } + else + { + realselStart = _hexBox._bytePos; + realselLength = 0; + } + + if (realselStart != _hexBox._bytePos || realselLength != _hexBox._selectionLength) + { + _hexBox.InternalSelect(realselStart, realselLength); + _hexBox.ScrollByteIntoView(_bpi.Index); + } + } + + void EndMouseSelection(object sender, MouseEventArgs e) + { + _mouseDown = false; + } + #endregion + + #region PrePrcessWmKeyDown methods + public virtual bool PreProcessWmKeyDown(ref Message m) + { + System.Diagnostics.Debug.WriteLine("PreProcessWmKeyDown(ref Message m)", "KeyInterpreter"); + + Keys vc = (Keys)m.WParam.ToInt32(); + + Keys keyData = vc | Control.ModifierKeys; + + // detect whether key down event should be raised + var hasMessageHandler = this.MessageHandlers.ContainsKey(keyData); + if (hasMessageHandler && RaiseKeyDown(keyData)) + return true; + + MessageDelegate messageHandler = hasMessageHandler + ? this.MessageHandlers[keyData] + : messageHandler = new MessageDelegate(PreProcessWmKeyDown_Default); + + return messageHandler(ref m); + } + + protected bool PreProcessWmKeyDown_Default(ref Message m) + { + _hexBox.ScrollByteIntoView(); + return _hexBox.BasePreProcessMessage(ref m); + } + + protected bool RaiseKeyDown(Keys keyData) + { + KeyEventArgs e = new KeyEventArgs(keyData); + _hexBox.OnKeyDown(e); + return e.Handled; + } + + protected virtual bool PreProcessWmKeyDown_Left(ref Message m) + { + return PerformPosMoveLeft(); + } + + protected virtual bool PreProcessWmKeyDown_Up(ref Message m) + { + long pos = _hexBox._bytePos; + int cp = _hexBox._byteCharacterPos; + + if (!(pos == 0 && cp == 0)) + { + pos = Math.Max(-1, pos - _hexBox._iHexMaxHBytes); + if (pos == -1) + return true; + + _hexBox.SetPosition(pos); + + if (pos < _hexBox._startByte) + { + _hexBox.PerformScrollLineUp(); + } + + _hexBox.UpdateCaret(); + _hexBox.Invalidate(); + } + + _hexBox.ScrollByteIntoView(); + _hexBox.ReleaseSelection(); + + return true; + } + + protected virtual bool PreProcessWmKeyDown_Right(ref Message m) + { + return PerformPosMoveRight(); + } + + protected virtual bool PreProcessWmKeyDown_Down(ref Message m) + { + long pos = _hexBox._bytePos; + int cp = _hexBox._byteCharacterPos; + + if (pos == _hexBox._byteProvider.Length && cp == 0) + return true; + + pos = Math.Min(_hexBox._byteProvider.Length, pos + _hexBox._iHexMaxHBytes); + + if (pos == _hexBox._byteProvider.Length) + cp = 0; + + _hexBox.SetPosition(pos, cp); + + if (pos > _hexBox._endByte - 1) + { + _hexBox.PerformScrollLineDown(); + } + + _hexBox.UpdateCaret(); + _hexBox.ScrollByteIntoView(); + _hexBox.ReleaseSelection(); + _hexBox.Invalidate(); + + return true; + } + + protected virtual bool PreProcessWmKeyDown_PageUp(ref Message m) + { + long pos = _hexBox._bytePos; + int cp = _hexBox._byteCharacterPos; + + if (pos == 0 && cp == 0) + return true; + + pos = Math.Max(0, pos - _hexBox._iHexMaxBytes); + if (pos == 0) + return true; + + _hexBox.SetPosition(pos); + + if (pos < _hexBox._startByte) + { + _hexBox.PerformScrollPageUp(); + } + + _hexBox.ReleaseSelection(); + _hexBox.UpdateCaret(); + _hexBox.Invalidate(); + return true; + } + + protected virtual bool PreProcessWmKeyDown_PageDown(ref Message m) + { + long pos = _hexBox._bytePos; + int cp = _hexBox._byteCharacterPos; + + if (pos == _hexBox._byteProvider.Length && cp == 0) + return true; + + pos = Math.Min(_hexBox._byteProvider.Length, pos + _hexBox._iHexMaxBytes); + + if (pos == _hexBox._byteProvider.Length) + cp = 0; + + _hexBox.SetPosition(pos, cp); + + if (pos > _hexBox._endByte - 1) + { + _hexBox.PerformScrollPageDown(); + } + + _hexBox.ReleaseSelection(); + _hexBox.UpdateCaret(); + _hexBox.Invalidate(); + + return true; + } + + protected virtual bool PreProcessWmKeyDown_ShiftLeft(ref Message m) + { + long pos = _hexBox._bytePos; + long sel = _hexBox._selectionLength; + + if (pos + sel < 1) + return true; + + if (pos + sel <= _bpiStart.Index) + { + if (pos == 0) + return true; + + pos--; + sel++; + } + else + { + sel = Math.Max(0, sel - 1); + } + + _hexBox.ScrollByteIntoView(); + _hexBox.InternalSelect(pos, sel); + + return true; + } + + protected virtual bool PreProcessWmKeyDown_ShiftUp(ref Message m) + { + long pos = _hexBox._bytePos; + long sel = _hexBox._selectionLength; + + if (pos - _hexBox._iHexMaxHBytes < 0 && pos <= _bpiStart.Index) + return true; + + if (_bpiStart.Index >= pos + sel) + { + pos = pos - _hexBox._iHexMaxHBytes; + sel += _hexBox._iHexMaxHBytes; + _hexBox.InternalSelect(pos, sel); + _hexBox.ScrollByteIntoView(); + } + else + { + sel -= _hexBox._iHexMaxHBytes; + if (sel < 0) + { + pos = _bpiStart.Index + sel; + sel = -sel; + _hexBox.InternalSelect(pos, sel); + _hexBox.ScrollByteIntoView(); + } + else + { + sel -= _hexBox._iHexMaxHBytes; + _hexBox.InternalSelect(pos, sel); + _hexBox.ScrollByteIntoView(pos + sel); + } + } + + return true; + } + + protected virtual bool PreProcessWmKeyDown_ShiftRight(ref Message m) + { + long pos = _hexBox._bytePos; + long sel = _hexBox._selectionLength; + + if (pos + sel >= _hexBox._byteProvider.Length) + return true; + + if (_bpiStart.Index <= pos) + { + sel++; + _hexBox.InternalSelect(pos, sel); + _hexBox.ScrollByteIntoView(pos + sel); + } + else + { + pos++; + sel = Math.Max(0, sel - 1); + _hexBox.InternalSelect(pos, sel); + _hexBox.ScrollByteIntoView(); + } + + return true; + } + + protected virtual bool PreProcessWmKeyDown_ShiftDown(ref Message m) + { + long pos = _hexBox._bytePos; + long sel = _hexBox._selectionLength; + + long max = _hexBox._byteProvider.Length; + + if (pos + sel + _hexBox._iHexMaxHBytes > max) + return true; + + if (_bpiStart.Index <= pos) + { + sel += _hexBox._iHexMaxHBytes; + _hexBox.InternalSelect(pos, sel); + _hexBox.ScrollByteIntoView(pos + sel); + } + else + { + sel -= _hexBox._iHexMaxHBytes; + if (sel < 0) + { + pos = _bpiStart.Index; + sel = -sel; + } + else + { + pos += _hexBox._iHexMaxHBytes; + //sel -= _hexBox._iHexMaxHBytes; + } + + _hexBox.InternalSelect(pos, sel); + _hexBox.ScrollByteIntoView(); + } + + return true; + } + + protected virtual bool PreProcessWmKeyDown_Tab(ref Message m) + { + if (_hexBox._stringViewVisible && _hexBox._keyInterpreter.GetType() == typeof(KeyInterpreter)) + { + _hexBox.ActivateStringKeyInterpreter(); + _hexBox.ScrollByteIntoView(); + _hexBox.ReleaseSelection(); + _hexBox.UpdateCaret(); + _hexBox.Invalidate(); + return true; + } + + if (_hexBox.Parent == null) return true; + _hexBox.Parent.SelectNextControl(_hexBox, true, true, true, true); + return true; + } + + protected virtual bool PreProcessWmKeyDown_ShiftTab(ref Message m) + { + if (_hexBox._keyInterpreter is StringKeyInterpreter) + { + _shiftDown = false; + _hexBox.ActivateKeyInterpreter(); + _hexBox.ScrollByteIntoView(); + _hexBox.ReleaseSelection(); + _hexBox.UpdateCaret(); + _hexBox.Invalidate(); + return true; + } + + if (_hexBox.Parent == null) return true; + _hexBox.Parent.SelectNextControl(_hexBox, false, true, true, true); + return true; + } + + protected virtual bool PreProcessWmKeyDown_Back(ref Message m) + { + if (!_hexBox._byteProvider.SupportsDeleteBytes()) + return true; + + if (_hexBox.ReadOnly) + return true; + + long pos = _hexBox._bytePos; + long sel = _hexBox._selectionLength; + int cp = _hexBox._byteCharacterPos; + + long startDelete = (cp == 0 && sel == 0) ? pos - 1 : pos; + if (startDelete < 0 && sel < 1) + return true; + + long bytesToDelete = (sel > 0) ? sel : 1; + _hexBox._byteProvider.DeleteBytes(Math.Max(0, startDelete), bytesToDelete); + _hexBox.UpdateScrollSize(); + + if (sel == 0) + PerformPosMoveLeftByte(); + + _hexBox.ReleaseSelection(); + _hexBox.Invalidate(); + + return true; + } + + protected virtual bool PreProcessWmKeyDown_Delete(ref Message m) + { + if (!_hexBox._byteProvider.SupportsDeleteBytes()) + return true; + + if (_hexBox.ReadOnly) + return true; + + long pos = _hexBox._bytePos; + long sel = _hexBox._selectionLength; + + if (pos >= _hexBox._byteProvider.Length) + return true; + + long bytesToDelete = (sel > 0) ? sel : 1; + _hexBox._byteProvider.DeleteBytes(pos, bytesToDelete); + + _hexBox.UpdateScrollSize(); + _hexBox.ReleaseSelection(); + _hexBox.Invalidate(); + + return true; + } + + protected virtual bool PreProcessWmKeyDown_Home(ref Message m) + { + long pos = _hexBox._bytePos; + int cp = _hexBox._byteCharacterPos; + + if (pos < 1) + return true; + + pos = 0; + cp = 0; + _hexBox.SetPosition(pos, cp); + + _hexBox.ScrollByteIntoView(); + _hexBox.UpdateCaret(); + _hexBox.ReleaseSelection(); + + return true; + } + + protected virtual bool PreProcessWmKeyDown_End(ref Message m) + { + long pos = _hexBox._bytePos; + int cp = _hexBox._byteCharacterPos; + + if (pos >= _hexBox._byteProvider.Length - 1) + return true; + + pos = _hexBox._byteProvider.Length; + cp = 0; + _hexBox.SetPosition(pos, cp); + + _hexBox.ScrollByteIntoView(); + _hexBox.UpdateCaret(); + _hexBox.ReleaseSelection(); + + return true; + } + + protected virtual bool PreProcessWmKeyDown_ShiftShiftKey(ref Message m) + { + if (_mouseDown) + return true; + if (_shiftDown) + return true; + + _shiftDown = true; + + if (_hexBox._selectionLength > 0) + return true; + + _bpiStart = new BytePositionInfo(_hexBox._bytePos, _hexBox._byteCharacterPos); + + return true; + } + + protected virtual bool PreProcessWmKeyDown_ControlC(ref Message m) + { + _hexBox.Copy(); + return true; + } + + protected virtual bool PreProcessWmKeyDown_ControlX(ref Message m) + { + _hexBox.Cut(); + return true; + } + + protected virtual bool PreProcessWmKeyDown_ControlV(ref Message m) + { + _hexBox.Paste(); + return true; + } + + #endregion + + #region PreProcessWmChar methods + public virtual bool PreProcessWmChar(ref Message m) + { + if (Control.ModifierKeys == Keys.Control) + { + return _hexBox.BasePreProcessMessage(ref m); + } + + bool sw = _hexBox._byteProvider.SupportsWriteByte(); + bool si = _hexBox._byteProvider.SupportsInsertBytes(); + bool sd = _hexBox._byteProvider.SupportsDeleteBytes(); + + long pos = _hexBox._bytePos; + long sel = _hexBox._selectionLength; + int cp = _hexBox._byteCharacterPos; + + if ( + (!sw && pos != _hexBox._byteProvider.Length) || + (!si && pos == _hexBox._byteProvider.Length)) + { + return _hexBox.BasePreProcessMessage(ref m); + } + + char c = (char)m.WParam.ToInt32(); + + if (Uri.IsHexDigit(c)) + { + if (RaiseKeyPress(c)) + return true; + + if (_hexBox.ReadOnly) + return true; + + bool isInsertMode = (pos == _hexBox._byteProvider.Length); + + // do insert when insertActive = true + if (!isInsertMode && si && _hexBox.InsertActive && cp == 0) + isInsertMode = true; + + if (sd && si && sel > 0) + { + _hexBox._byteProvider.DeleteBytes(pos, sel); + isInsertMode = true; + cp = 0; + _hexBox.SetPosition(pos, cp); + } + + _hexBox.ReleaseSelection(); + + byte currentByte; + if (isInsertMode) + currentByte = 0; + else + currentByte = _hexBox._byteProvider.ReadByte(pos); + + string sCb = currentByte.ToString("X", System.Threading.Thread.CurrentThread.CurrentCulture); + if (sCb.Length == 1) + sCb = "0" + sCb; + + string sNewCb = c.ToString(); + if (cp == 0) + sNewCb += sCb.Substring(1, 1); + else + sNewCb = sCb.Substring(0, 1) + sNewCb; + byte newcb = byte.Parse(sNewCb, System.Globalization.NumberStyles.AllowHexSpecifier, System.Threading.Thread.CurrentThread.CurrentCulture); + + if (isInsertMode) + _hexBox._byteProvider.InsertBytes(pos, new byte[] { newcb }); + else + _hexBox._byteProvider.WriteByte(pos, newcb); + + PerformPosMoveRight(); + + _hexBox.Invalidate(); + return true; + } + else + { + return _hexBox.BasePreProcessMessage(ref m); + } + } + + protected bool RaiseKeyPress(char keyChar) + { + KeyPressEventArgs e = new KeyPressEventArgs(keyChar); + _hexBox.OnKeyPress(e); + return e.Handled; + } + #endregion + + #region PreProcessWmKeyUp methods + public virtual bool PreProcessWmKeyUp(ref Message m) + { + System.Diagnostics.Debug.WriteLine("PreProcessWmKeyUp(ref Message m)", "KeyInterpreter"); + + Keys vc = (Keys)m.WParam.ToInt32(); + + Keys keyData = vc | Control.ModifierKeys; + + switch (keyData) + { + case Keys.ShiftKey: + case Keys.Insert: + if (RaiseKeyUp(keyData)) + return true; + break; + } + + switch (keyData) + { + case Keys.ShiftKey: + _shiftDown = false; + return true; + case Keys.Insert: + return PreProcessWmKeyUp_Insert(ref m); + default: + return _hexBox.BasePreProcessMessage(ref m); + } + } + + protected virtual bool PreProcessWmKeyUp_Insert(ref Message m) + { + _hexBox.InsertActive = !_hexBox.InsertActive; + return true; + } + + protected bool RaiseKeyUp(Keys keyData) + { + KeyEventArgs e = new KeyEventArgs(keyData); + _hexBox.OnKeyUp(e); + return e.Handled; + } + #endregion + + #region Misc + Dictionary MessageHandlers + { + get + { + if (_messageHandlers == null) + { + _messageHandlers = new Dictionary(); + _messageHandlers.Add(Keys.Left, new MessageDelegate(PreProcessWmKeyDown_Left)); // move left + _messageHandlers.Add(Keys.Up, new MessageDelegate(PreProcessWmKeyDown_Up)); // move up + _messageHandlers.Add(Keys.Right, new MessageDelegate(PreProcessWmKeyDown_Right)); // move right + _messageHandlers.Add(Keys.Down, new MessageDelegate(PreProcessWmKeyDown_Down)); // move down + _messageHandlers.Add(Keys.PageUp, new MessageDelegate(PreProcessWmKeyDown_PageUp)); // move pageup + _messageHandlers.Add(Keys.PageDown, new MessageDelegate(PreProcessWmKeyDown_PageDown)); // move page down + _messageHandlers.Add(Keys.Left | Keys.Shift, new MessageDelegate(PreProcessWmKeyDown_ShiftLeft)); // move left with selection + _messageHandlers.Add(Keys.Up | Keys.Shift, new MessageDelegate(PreProcessWmKeyDown_ShiftUp)); // move up with selection + _messageHandlers.Add(Keys.Right | Keys.Shift, new MessageDelegate(PreProcessWmKeyDown_ShiftRight)); // move right with selection + _messageHandlers.Add(Keys.Down | Keys.Shift, new MessageDelegate(PreProcessWmKeyDown_ShiftDown)); // move down with selection + _messageHandlers.Add(Keys.Tab, new MessageDelegate(PreProcessWmKeyDown_Tab)); // switch to string view + _messageHandlers.Add(Keys.Back, new MessageDelegate(PreProcessWmKeyDown_Back)); // back + _messageHandlers.Add(Keys.Delete, new MessageDelegate(PreProcessWmKeyDown_Delete)); // delete + _messageHandlers.Add(Keys.Home, new MessageDelegate(PreProcessWmKeyDown_Home)); // move to home + _messageHandlers.Add(Keys.End, new MessageDelegate(PreProcessWmKeyDown_End)); // move to end + _messageHandlers.Add(Keys.ShiftKey | Keys.Shift, new MessageDelegate(PreProcessWmKeyDown_ShiftShiftKey)); // begin selection process + _messageHandlers.Add(Keys.C | Keys.Control, new MessageDelegate(PreProcessWmKeyDown_ControlC)); // copy + _messageHandlers.Add(Keys.X | Keys.Control, new MessageDelegate(PreProcessWmKeyDown_ControlX)); // cut + _messageHandlers.Add(Keys.V | Keys.Control, new MessageDelegate(PreProcessWmKeyDown_ControlV)); // paste + } + return _messageHandlers; + } + } + + protected virtual bool PerformPosMoveLeft() + { + long pos = _hexBox._bytePos; + long sel = _hexBox._selectionLength; + int cp = _hexBox._byteCharacterPos; + + if (sel != 0) + { + cp = 0; + _hexBox.SetPosition(pos, cp); + _hexBox.ReleaseSelection(); + } + else + { + if (pos == 0 && cp == 0) + return true; + + if (cp > 0) + { + cp--; + } + else + { + pos = Math.Max(0, pos - 1); + cp++; + } + + _hexBox.SetPosition(pos, cp); + + if (pos < _hexBox._startByte) + { + _hexBox.PerformScrollLineUp(); + } + _hexBox.UpdateCaret(); + _hexBox.Invalidate(); + } + + _hexBox.ScrollByteIntoView(); + return true; + } + protected virtual bool PerformPosMoveRight() + { + long pos = _hexBox._bytePos; + int cp = _hexBox._byteCharacterPos; + long sel = _hexBox._selectionLength; + + if (sel != 0) + { + pos += sel; + cp = 0; + _hexBox.SetPosition(pos, cp); + _hexBox.ReleaseSelection(); + } + else + { + if (!(pos == _hexBox._byteProvider.Length && cp == 0)) + { + + if (cp > 0) + { + pos = Math.Min(_hexBox._byteProvider.Length, pos + 1); + cp = 0; + } + else + { + cp++; + } + + _hexBox.SetPosition(pos, cp); + + if (pos > _hexBox._endByte - 1) + { + _hexBox.PerformScrollLineDown(); + } + _hexBox.UpdateCaret(); + _hexBox.Invalidate(); + } + } + + _hexBox.ScrollByteIntoView(); + return true; + } + protected virtual bool PerformPosMoveLeftByte() + { + long pos = _hexBox._bytePos; + int cp = _hexBox._byteCharacterPos; + + if (pos == 0) + return true; + + pos = Math.Max(0, pos - 1); + cp = 0; + + _hexBox.SetPosition(pos, cp); + + if (pos < _hexBox._startByte) + { + _hexBox.PerformScrollLineUp(); + } + _hexBox.UpdateCaret(); + _hexBox.ScrollByteIntoView(); + _hexBox.Invalidate(); + + return true; + } + + protected virtual bool PerformPosMoveRightByte() + { + long pos = _hexBox._bytePos; + int cp = _hexBox._byteCharacterPos; + + if (pos == _hexBox._byteProvider.Length) + return true; + + pos = Math.Min(_hexBox._byteProvider.Length, pos + 1); + cp = 0; + + _hexBox.SetPosition(pos, cp); + + if (pos > _hexBox._endByte - 1) + { + _hexBox.PerformScrollLineDown(); + } + _hexBox.UpdateCaret(); + _hexBox.ScrollByteIntoView(); + _hexBox.Invalidate(); + + return true; + } + + + public virtual PointF GetCaretPointF(long byteIndex) + { + System.Diagnostics.Debug.WriteLine("GetCaretPointF()", "KeyInterpreter"); + + return _hexBox.GetBytePointF(byteIndex); + } + + protected virtual BytePositionInfo GetBytePositionInfo(Point p) + { + return _hexBox.GetHexBytePositionInfo(p); + } + #endregion + } + #endregion + + #region StringKeyInterpreter class + /// + /// Handles user input such as mouse and keyboard input during string view edit + /// + class StringKeyInterpreter : KeyInterpreter + { + #region Ctors + public StringKeyInterpreter(HexBox hexBox) + : base(hexBox) + { + _hexBox._byteCharacterPos = 0; + } + #endregion + + #region PreProcessWmKeyDown methods + public override bool PreProcessWmKeyDown(ref Message m) + { + Keys vc = (Keys)m.WParam.ToInt32(); + + Keys keyData = vc | Control.ModifierKeys; + + switch (keyData) + { + case Keys.Tab | Keys.Shift: + case Keys.Tab: + if (RaiseKeyDown(keyData)) + return true; + break; + } + + switch (keyData) + { + case Keys.Tab | Keys.Shift: + return PreProcessWmKeyDown_ShiftTab(ref m); + case Keys.Tab: + return PreProcessWmKeyDown_Tab(ref m); + default: + return base.PreProcessWmKeyDown(ref m); + } + } + + protected override bool PreProcessWmKeyDown_Left(ref Message m) + { + return PerformPosMoveLeftByte(); + } + + protected override bool PreProcessWmKeyDown_Right(ref Message m) + { + return PerformPosMoveRightByte(); + } + + #endregion + + #region PreProcessWmChar methods + public override bool PreProcessWmChar(ref Message m) + { + if (Control.ModifierKeys == Keys.Control) + { + return _hexBox.BasePreProcessMessage(ref m); + } + + bool sw = _hexBox._byteProvider.SupportsWriteByte(); + bool si = _hexBox._byteProvider.SupportsInsertBytes(); + bool sd = _hexBox._byteProvider.SupportsDeleteBytes(); + + long pos = _hexBox._bytePos; + long sel = _hexBox._selectionLength; + int cp = _hexBox._byteCharacterPos; + + if ( + (!sw && pos != _hexBox._byteProvider.Length) || + (!si && pos == _hexBox._byteProvider.Length)) + { + return _hexBox.BasePreProcessMessage(ref m); + } + + char c = (char)m.WParam.ToInt32(); + + if (RaiseKeyPress(c)) + return true; + + if (_hexBox.ReadOnly) + return true; + + bool isInsertMode = (pos == _hexBox._byteProvider.Length); + + // do insert when insertActive = true + if (!isInsertMode && si && _hexBox.InsertActive) + isInsertMode = true; + + if (sd && si && sel > 0) + { + _hexBox._byteProvider.DeleteBytes(pos, sel); + isInsertMode = true; + cp = 0; + _hexBox.SetPosition(pos, cp); + } + + _hexBox.ReleaseSelection(); + + byte b = _hexBox.ByteCharConverter.ToByte(c); + if (isInsertMode) + _hexBox._byteProvider.InsertBytes(pos, new byte[] { b }); + else + _hexBox._byteProvider.WriteByte(pos, b); + + PerformPosMoveRightByte(); + _hexBox.Invalidate(); + + return true; + } + #endregion + + #region Misc + public override PointF GetCaretPointF(long byteIndex) + { + System.Diagnostics.Debug.WriteLine("GetCaretPointF()", "StringKeyInterpreter"); + + Point gp = _hexBox.GetGridBytePoint(byteIndex); + return _hexBox.GetByteStringPointF(gp); + } + + protected override BytePositionInfo GetBytePositionInfo(Point p) + { + return _hexBox.GetStringBytePositionInfo(p); + } + #endregion + } + #endregion + + #region Fields + /// + /// Contains the hole content bounds of all text + /// + Rectangle _recContent; + /// + /// Contains the line info bounds + /// + Rectangle _recLineInfo; + /// + /// Contains the column info header rectangle bounds + /// + Rectangle _recColumnInfo; + /// + /// Contains the hex data bounds + /// + Rectangle _recHex; + /// + /// Contains the string view bounds + /// + Rectangle _recStringView; + + /// + /// Contains string format information for text drawing + /// + StringFormat _stringFormat; + /// + /// Contains the maximum of visible horizontal bytes + /// + int _iHexMaxHBytes; + /// + /// Contains the maximum of visible vertical bytes + /// + int _iHexMaxVBytes; + /// + /// Contains the maximum of visible bytes. + /// + int _iHexMaxBytes; + + /// + /// Contains the scroll bars minimum value + /// + long _scrollVmin; + /// + /// Contains the scroll bars maximum value + /// + long _scrollVmax; + /// + /// Contains the scroll bars current position + /// + long _scrollVpos; + /// + /// Contains a vertical scroll + /// + VScrollBar _vScrollBar; + /// + /// Contains a timer for thumbtrack scrolling + /// + Timer _thumbTrackTimer = new Timer(); + /// + /// Contains the thumbtrack scrolling position + /// + long _thumbTrackPosition; + /// + /// Contains the thumptrack delay for scrolling in milliseconds. + /// + const int THUMPTRACKDELAY = 50; + /// + /// Contains the Enviroment.TickCount of the last refresh + /// + int _lastThumbtrack; + /// + /// Contains the border´s left shift + /// + int _recBorderLeft = SystemInformation.Border3DSize.Width; + /// + /// Contains the border´s right shift + /// + int _recBorderRight = SystemInformation.Border3DSize.Width; + /// + /// Contains the border´s top shift + /// + int _recBorderTop = SystemInformation.Border3DSize.Height; + /// + /// Contains the border bottom shift + /// + int _recBorderBottom = SystemInformation.Border3DSize.Height; + + /// + /// Contains the index of the first visible byte + /// + long _startByte; + /// + /// Contains the index of the last visible byte + /// + long _endByte; + + /// + /// Contains the current byte position + /// + long _bytePos = -1; + /// + /// Contains the current char position in one byte + /// + /// + /// "1A" + /// "1" = char position of 0 + /// "A" = char position of 1 + /// + int _byteCharacterPos; + + /// + /// Contains string format information for hex values + /// + string _hexStringFormat = "X"; + + + /// + /// Contains the current key interpreter + /// + IKeyInterpreter _keyInterpreter; + /// + /// Contains an empty key interpreter without functionality + /// + EmptyKeyInterpreter _eki; + /// + /// Contains the default key interpreter + /// + KeyInterpreter _ki; + /// + /// Contains the string key interpreter + /// + StringKeyInterpreter _ski; + + /// + /// Contains True if caret is visible + /// + bool _caretVisible; + + /// + /// Contains true, if the find (Find method) should be aborted. + /// + bool _abortFind; + /// + /// Contains a value of the current finding position. + /// + long _findingPos; + + /// + /// Contains a state value about Insert or Write mode. When this value is true and the ByteProvider SupportsInsert is true bytes are inserted instead of overridden. + /// + bool _insertActive; + + PointF _caretPos; + + #endregion + + #region Events + /// + /// Occurs, when the value of InsertActive property has changed. + /// + [Description("Occurs, when the value of InsertActive property has changed.")] + public event EventHandler InsertActiveChanged; + /// + /// Occurs, when the value of ReadOnly property has changed. + /// + [Description("Occurs, when the value of ReadOnly property has changed.")] + public event EventHandler ReadOnlyChanged; + /// + /// Occurs, when the value of ByteProvider property has changed. + /// + [Description("Occurs, when the value of ByteProvider property has changed.")] + public event EventHandler ByteProviderChanged; + /// + /// Occurs, when the value of SelectionStart property has changed. + /// + [Description("Occurs, when the value of SelectionStart property has changed.")] + public event EventHandler SelectionStartChanged; + /// + /// Occurs, when the value of SelectionLength property has changed. + /// + [Description("Occurs, when the value of SelectionLength property has changed.")] + public event EventHandler SelectionLengthChanged; + /// + /// Occurs, when the value of LineInfoVisible property has changed. + /// + [Description("Occurs, when the value of LineInfoVisible property has changed.")] + public event EventHandler LineInfoVisibleChanged; + /// + /// Occurs, when the value of ColumnInfoVisibleChanged property has changed. + /// + [Description("Occurs, when the value of ColumnInfoVisibleChanged property has changed.")] + public event EventHandler ColumnInfoVisibleChanged; + /// + /// Occurs, when the value of GroupSeparatorVisibleChanged property has changed. + /// + [Description("Occurs, when the value of GroupSeparatorVisibleChanged property has changed.")] + public event EventHandler GroupSeparatorVisibleChanged; + /// + /// Occurs, when the value of StringViewVisible property has changed. + /// + [Description("Occurs, when the value of StringViewVisible property has changed.")] + public event EventHandler StringViewVisibleChanged; + /// + /// Occurs, when the value of BorderStyle property has changed. + /// + [Description("Occurs, when the value of BorderStyle property has changed.")] + public event EventHandler BorderStyleChanged; + /// + /// Occurs, when the value of ColumnWidth property has changed. + /// + [Description("Occurs, when the value of GroupSize property has changed.")] + public event EventHandler GroupSizeChanged; + /// + /// Occurs, when the value of BytesPerLine property has changed. + /// + [Description("Occurs, when the value of BytesPerLine property has changed.")] + public event EventHandler BytesPerLineChanged; + /// + /// Occurs, when the value of UseFixedBytesPerLine property has changed. + /// + [Description("Occurs, when the value of UseFixedBytesPerLine property has changed.")] + public event EventHandler UseFixedBytesPerLineChanged; + /// + /// Occurs, when the value of VScrollBarVisible property has changed. + /// + [Description("Occurs, when the value of VScrollBarVisible property has changed.")] + public event EventHandler VScrollBarVisibleChanged; + /// + /// Occurs, when the value of HexCasing property has changed. + /// + [Description("Occurs, when the value of HexCasing property has changed.")] + public event EventHandler HexCasingChanged; + /// + /// Occurs, when the value of HorizontalByteCount property has changed. + /// + [Description("Occurs, when the value of HorizontalByteCount property has changed.")] + public event EventHandler HorizontalByteCountChanged; + /// + /// Occurs, when the value of VerticalByteCount property has changed. + /// + [Description("Occurs, when the value of VerticalByteCount property has changed.")] + public event EventHandler VerticalByteCountChanged; + /// + /// Occurs, when the value of CurrentLine property has changed. + /// + [Description("Occurs, when the value of CurrentLine property has changed.")] + public event EventHandler CurrentLineChanged; + /// + /// Occurs, when the value of CurrentPositionInLine property has changed. + /// + [Description("Occurs, when the value of CurrentPositionInLine property has changed.")] + public event EventHandler CurrentPositionInLineChanged; + /// + /// Occurs, when Copy method was invoked and ClipBoardData changed. + /// + [Description("Occurs, when Copy method was invoked and ClipBoardData changed.")] + public event EventHandler Copied; + /// + /// Occurs, when CopyHex method was invoked and ClipBoardData changed. + /// + [Description("Occurs, when CopyHex method was invoked and ClipBoardData changed.")] + public event EventHandler CopiedHex; + /// + /// Occurs, when the CharSize property has changed + /// + [Description("Occurs, when the CharSize property has changed")] + public event EventHandler CharSizeChanged; + /// + /// Occurs, when the RequiredWidth property changes + /// + [Description("Occurs, when the RequiredWidth property changes")] + public event EventHandler RequiredWidthChanged; + #endregion + + #region Ctors + + /// + /// Initializes a new instance of a HexBox class. + /// + public HexBox() + { + this._vScrollBar = new VScrollBar(); + this._vScrollBar.Scroll += new ScrollEventHandler(_vScrollBar_Scroll); + + this._builtInContextMenu = new BuiltInContextMenu(this); + + BackColor = Color.White; + Font = SystemFonts.MessageBoxFont; + _stringFormat = new StringFormat(StringFormat.GenericTypographic); + _stringFormat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces; + + ActivateEmptyKeyInterpreter(); + + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.DoubleBuffer, true); + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.ResizeRedraw, true); + + _thumbTrackTimer.Interval = 50; + _thumbTrackTimer.Tick += new EventHandler(PerformScrollThumbTrack); + } + + #endregion + + #region Scroll methods + void _vScrollBar_Scroll(object sender, ScrollEventArgs e) + { + switch (e.Type) + { + case ScrollEventType.Last: + break; + case ScrollEventType.EndScroll: + break; + case ScrollEventType.SmallIncrement: + PerformScrollLineDown(); + break; + case ScrollEventType.SmallDecrement: + PerformScrollLineUp(); + break; + case ScrollEventType.LargeIncrement: + PerformScrollPageDown(); + break; + case ScrollEventType.LargeDecrement: + PerformScrollPageUp(); + break; + case ScrollEventType.ThumbPosition: + long lPos = FromScrollPos(e.NewValue); + PerformScrollThumpPosition(lPos); + break; + case ScrollEventType.ThumbTrack: + // to avoid performance problems use a refresh delay implemented with a timer + if (_thumbTrackTimer.Enabled) // stop old timer + _thumbTrackTimer.Enabled = false; + + // perform scroll immediately only if last refresh is very old + int currentThumbTrack = System.Environment.TickCount; + if (currentThumbTrack - _lastThumbtrack > THUMPTRACKDELAY) + { + PerformScrollThumbTrack(null, null); + _lastThumbtrack = currentThumbTrack; + break; + } + + // start thumbtrack timer + _thumbTrackPosition = FromScrollPos(e.NewValue); + _thumbTrackTimer.Enabled = true; + break; + case ScrollEventType.First: + break; + default: + break; + } + + e.NewValue = ToScrollPos(_scrollVpos); + } + + /// + /// Performs the thumbtrack scrolling after an delay. + /// + void PerformScrollThumbTrack(object sender, EventArgs e) + { + _thumbTrackTimer.Enabled = false; + PerformScrollThumpPosition(_thumbTrackPosition); + _lastThumbtrack = Environment.TickCount; + } + + void UpdateScrollSize() + { + System.Diagnostics.Debug.WriteLine("UpdateScrollSize()", "HexBox"); + + // calc scroll bar info + if (VScrollBarVisible && _byteProvider != null && _byteProvider.Length > 0 && _iHexMaxHBytes != 0) + { + long scrollmax = (long)Math.Ceiling((double)(_byteProvider.Length + 1) / (double)_iHexMaxHBytes - (double)_iHexMaxVBytes); + scrollmax = Math.Max(0, scrollmax); + + long scrollpos = _startByte / _iHexMaxHBytes; + + if (scrollmax < _scrollVmax) + { + /* Data size has been decreased. */ + if (_scrollVpos == _scrollVmax) + /* Scroll one line up if we at bottom. */ + PerformScrollLineUp(); + } + + if (scrollmax == _scrollVmax && scrollpos == _scrollVpos) + return; + + _scrollVmin = 0; + _scrollVmax = scrollmax; + _scrollVpos = Math.Min(scrollpos, scrollmax); + UpdateVScroll(); + } + else if (VScrollBarVisible) + { + // disable scroll bar + _scrollVmin = 0; + _scrollVmax = 0; + _scrollVpos = 0; + UpdateVScroll(); + } + } + + void UpdateVScroll() + { + System.Diagnostics.Debug.WriteLine("UpdateVScroll()", "HexBox"); + + int max = ToScrollMax(_scrollVmax); + + if (max > 0) + { + _vScrollBar.Minimum = 0; + _vScrollBar.Maximum = max; + _vScrollBar.Value = ToScrollPos(_scrollVpos); + _vScrollBar.Visible = true; + } + else + { + _vScrollBar.Visible = false; + } + } + + int ToScrollPos(long value) + { + int max = 65535; + + if (_scrollVmax < max) + return (int)value; + else + { + double valperc = (double)value / (double)_scrollVmax * (double)100; + int res = (int)Math.Floor((double)max / (double)100 * valperc); + res = (int)Math.Max(_scrollVmin, res); + res = (int)Math.Min(_scrollVmax, res); + return res; + } + } + + long FromScrollPos(int value) + { + int max = 65535; + if (_scrollVmax < max) + { + return (long)value; + } + else + { + double valperc = (double)value / (double)max * (double)100; + long res = (int)Math.Floor((double)_scrollVmax / (double)100 * valperc); + return res; + } + } + + int ToScrollMax(long value) + { + long max = 65535; + if (value > max) + return (int)max; + else + return (int)value; + } + + void PerformScrollToLine(long pos) + { + if (pos < _scrollVmin || pos > _scrollVmax || pos == _scrollVpos) + return; + + _scrollVpos = pos; + + UpdateVScroll(); + UpdateVisibilityBytes(); + UpdateCaret(); + Invalidate(); + } + + void PerformScrollLines(int lines) + { + long pos; + if (lines > 0) + { + pos = Math.Min(_scrollVmax, _scrollVpos + lines); + } + else if (lines < 0) + { + pos = Math.Max(_scrollVmin, _scrollVpos + lines); + } + else + { + return; + } + + PerformScrollToLine(pos); + } + + void PerformScrollLineDown() + { + this.PerformScrollLines(1); + } + + void PerformScrollLineUp() + { + this.PerformScrollLines(-1); + } + + void PerformScrollPageDown() + { + this.PerformScrollLines(_iHexMaxVBytes); + } + + void PerformScrollPageUp() + { + this.PerformScrollLines(-_iHexMaxVBytes); + } + + void PerformScrollThumpPosition(long pos) + { + // Bug fix: Scroll to end, do not scroll to end + int difference = (_scrollVmax > 65535) ? 10 : 9; + + if (ToScrollPos(pos) == ToScrollMax(_scrollVmax) - difference) + pos = _scrollVmax; + // End Bug fix + + + PerformScrollToLine(pos); + } + + /// + /// Scrolls the selection start byte into view + /// + public void ScrollByteIntoView() + { + System.Diagnostics.Debug.WriteLine("ScrollByteIntoView()", "HexBox"); + + ScrollByteIntoView(_bytePos); + } + + /// + /// Scrolls the specific byte into view + /// + /// the index of the byte + public void ScrollByteIntoView(long index) + { + System.Diagnostics.Debug.WriteLine("ScrollByteIntoView(long index)", "HexBox"); + + if (_byteProvider == null || _keyInterpreter == null) + return; + + if (index < _startByte) + { + long line = (long)Math.Floor((double)index / (double)_iHexMaxHBytes); + PerformScrollThumpPosition(line); + } + else if (index > _endByte) + { + long line = (long)Math.Floor((double)index / (double)_iHexMaxHBytes); + line -= _iHexMaxVBytes - 1; + PerformScrollThumpPosition(line); + } + } + #endregion + + #region Selection methods + void ReleaseSelection() + { + System.Diagnostics.Debug.WriteLine("ReleaseSelection()", "HexBox"); + + if (_selectionLength == 0) + return; + _selectionLength = 0; + OnSelectionLengthChanged(EventArgs.Empty); + + if (!_caretVisible) + CreateCaret(); + else + UpdateCaret(); + + Invalidate(); + } + + /// + /// Returns true if Select method could be invoked. + /// + public bool CanSelectAll() + { + if (!this.Enabled) + return false; + if (_byteProvider == null) + return false; + + return true; + } + + /// + /// Selects all bytes. + /// + public void SelectAll() + { + if (this.ByteProvider == null) + return; + this.Select(0, this.ByteProvider.Length); + } + + /// + /// Selects the hex box. + /// + /// the start index of the selection + /// the length of the selection + public void Select(long start, long length) + { + if (this.ByteProvider == null) + return; + if (!this.Enabled) + return; + + InternalSelect(start, length); + ScrollByteIntoView(); + } + + void InternalSelect(long start, long length) + { + long pos = start; + long sel = length; + int cp = 0; + + if (sel > 0 && _caretVisible) + DestroyCaret(); + else if (sel == 0 && !_caretVisible) + CreateCaret(); + + SetPosition(pos, cp); + SetSelectionLength(sel); + + UpdateCaret(); + Invalidate(); + } + #endregion + + #region Key interpreter methods + void ActivateEmptyKeyInterpreter() + { + if (_eki == null) + _eki = new EmptyKeyInterpreter(this); + + if (_eki == _keyInterpreter) + return; + + if (_keyInterpreter != null) + _keyInterpreter.Deactivate(); + + _keyInterpreter = _eki; + _keyInterpreter.Activate(); + } + + void ActivateKeyInterpreter() + { + if (_ki == null) + _ki = new KeyInterpreter(this); + + if (_ki == _keyInterpreter) + return; + + if (_keyInterpreter != null) + _keyInterpreter.Deactivate(); + + _keyInterpreter = _ki; + _keyInterpreter.Activate(); + } + + void ActivateStringKeyInterpreter() + { + if (_ski == null) + _ski = new StringKeyInterpreter(this); + + if (_ski == _keyInterpreter) + return; + + if (_keyInterpreter != null) + _keyInterpreter.Deactivate(); + + _keyInterpreter = _ski; + _keyInterpreter.Activate(); + } + #endregion + + #region Caret methods + void CreateCaret() + { + if (_byteProvider == null || _keyInterpreter == null || _caretVisible || !this.Focused) + return; + + System.Diagnostics.Debug.WriteLine("CreateCaret()", "HexBox"); + + // define the caret width depending on InsertActive mode + int caretWidth = (this.InsertActive) ? 1 : (int)_charSize.Width; + int caretHeight = (int)_charSize.Height; + + UpdateCaret(); + + _caretVisible = true; + } + + void UpdateCaret() + { + if (_byteProvider == null || _keyInterpreter == null) + return; + + System.Diagnostics.Debug.WriteLine("UpdateCaret()", "HexBox"); + + long byteIndex = _bytePos - _startByte; + PointF p = _keyInterpreter.GetCaretPointF(byteIndex); + p.X += _byteCharacterPos * _charSize.Width; + + _caretPos = p; + } + + void DestroyCaret() + { + if (!_caretVisible) + return; + + System.Diagnostics.Debug.WriteLine("DestroyCaret()", "HexBox"); + + _caretVisible = false; + this.Invalidate(); + } + + void SetCaretPosition(Point p) + { + System.Diagnostics.Debug.WriteLine("SetCaretPosition()", "HexBox"); + + if (_byteProvider == null || _keyInterpreter == null) + return; + + long pos = _bytePos; + int cp = _byteCharacterPos; + + if (_recHex.Contains(p)) + { + BytePositionInfo bpi = GetHexBytePositionInfo(p); + pos = bpi.Index; + cp = bpi.CharacterPosition; + + SetPosition(pos, cp); + + ActivateKeyInterpreter(); + UpdateCaret(); + Invalidate(); + } + else if (_recStringView.Contains(p)) + { + BytePositionInfo bpi = GetStringBytePositionInfo(p); + pos = bpi.Index; + cp = bpi.CharacterPosition; + + SetPosition(pos, cp); + + ActivateStringKeyInterpreter(); + UpdateCaret(); + Invalidate(); + } + } + + BytePositionInfo GetHexBytePositionInfo(Point p) + { + System.Diagnostics.Debug.WriteLine("GetHexBytePositionInfo()", "HexBox"); + + long bytePos; + int byteCharaterPos; + + float x = ((float)(p.X - _recHex.X) / _charSize.Width); + float y = ((float)(p.Y - _recHex.Y) / _charSize.Height); + int iX = (int)x; + int iY = (int)y; + + int hPos = (iX / 3 + 1); + + bytePos = Math.Min(_byteProvider.Length, + _startByte + (_iHexMaxHBytes * (iY + 1) - _iHexMaxHBytes) + hPos - 1); + byteCharaterPos = (iX % 3); + if (byteCharaterPos > 1) + byteCharaterPos = 1; + + if (bytePos == _byteProvider.Length) + byteCharaterPos = 0; + + if (bytePos < 0) + return new BytePositionInfo(0, 0); + return new BytePositionInfo(bytePos, byteCharaterPos); + } + + BytePositionInfo GetStringBytePositionInfo(Point p) + { + System.Diagnostics.Debug.WriteLine("GetStringBytePositionInfo()", "HexBox"); + + long bytePos; + int byteCharacterPos; + + float x = ((float)(p.X - _recStringView.X) / _charSize.Width); + float y = ((float)(p.Y - _recStringView.Y) / _charSize.Height); + int iX = (int)x; + int iY = (int)y; + + int hPos = iX + 1; + + bytePos = Math.Min(_byteProvider.Length, + _startByte + (_iHexMaxHBytes * (iY + 1) - _iHexMaxHBytes) + hPos - 1); + byteCharacterPos = 0; + + if (bytePos < 0) + return new BytePositionInfo(0, 0); + return new BytePositionInfo(bytePos, byteCharacterPos); + } + #endregion + + #region PreProcessMessage methods + /// + /// Preprocesses windows messages. + /// + /// the message to process. + /// true, if the message was processed + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true), SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)] + public override bool PreProcessMessage(ref Message m) + { + switch (m.Msg) + { + case NativeMethods.WM_KEYDOWN: + return _keyInterpreter.PreProcessWmKeyDown(ref m); + case NativeMethods.WM_CHAR: + return _keyInterpreter.PreProcessWmChar(ref m); + case NativeMethods.WM_KEYUP: + return _keyInterpreter.PreProcessWmKeyUp(ref m); + default: + return base.PreProcessMessage(ref m); + } + } + + bool BasePreProcessMessage(ref Message m) + { + return base.PreProcessMessage(ref m); + } + #endregion + + public enum eSearchDirection + { + Incremental, + Next, + Previous + } + + #region Find methods + /// + /// Searches the current ByteProvider + /// + /// contains all find options + /// the SelectionStart property value if find was successfull or + /// -1 if there is no match + /// -2 if Find was aborted. + public long Find(FindOptions options, eSearchDirection direction) + { + long startIndex = 0; + switch(direction) { + case eSearchDirection.Previous: startIndex = this.SelectionStart - 1; break; + default: + case eSearchDirection.Incremental: startIndex = this.SelectionStart; break; + case eSearchDirection.Next: startIndex = this.SelectionStart + 1; break; + } + + byte[] buffer1 = null; + byte[] buffer2 = null; + if(options.Type == FindType.Text && options.MatchCase) { + if(options.FindBuffer == null || options.FindBuffer.Length == 0) + throw new ArgumentException("FindBuffer can not be null when Type: Text and MatchCase: false"); + buffer1 = options.FindBuffer; + } else if(options.Type == FindType.Text && !options.MatchCase) { + if(options.FindBufferLowerCase == null || options.FindBufferLowerCase.Length == 0) + throw new ArgumentException("FindBufferLowerCase can not be null when Type is Text and MatchCase is true"); + if(options.FindBufferUpperCase == null || options.FindBufferUpperCase.Length == 0) + throw new ArgumentException("FindBufferUpperCase can not be null when Type is Text and MatchCase is true"); + if(options.FindBufferLowerCase.Length != options.FindBufferUpperCase.Length) + throw new ArgumentException("FindBufferUpperCase and FindBufferUpperCase must have the same size when Type is Text and MatchCase is true"); + buffer1 = options.FindBufferLowerCase; + buffer2 = options.FindBufferUpperCase; + + } else if(options.Type == FindType.Hex) { + if(options.Hex == null || options.Hex.Length == 0) + throw new ArgumentException("Hex can not be null when Type is Hex"); + buffer1 = options.Hex; + } + + + _abortFind = false; + + long result = -1; + if(direction == eSearchDirection.Previous) { + result = FindLoop(buffer1, buffer2, startIndex, 0, options.HasWildcard); + if(result == -1 && options.WrapSearch) { + result = FindLoop(buffer1, buffer2, _byteProvider.Length - 1, Math.Max(0, startIndex - buffer1.Length), options.HasWildcard); + } + } else { + result = FindLoop(buffer1, buffer2, startIndex, _byteProvider.Length, options.HasWildcard); + if(result == -1 && options.WrapSearch) { + result = FindLoop(buffer1, buffer2, 0, Math.Min(_byteProvider.Length, startIndex + buffer1.Length), options.HasWildcard); + } + } + return result; + } + + private long FindLoop(byte[] buffer1, byte[] buffer2, long start, long end, bool hasWildcard) + { + int matchStartIndex = start > end ? buffer1.Length - 1 : 0; + int match = matchStartIndex; + int buffer1Length = buffer1.Length; + + for(long pos = start; pos != end; pos = start > end ? pos-1 : pos+1) { + if(_abortFind) + return -2; + + //if (pos % 1000 == 0) // for performance reasons: DoEvents only 1 times per 1000 loops + //Application.DoEvents(); + + byte compareByte = _byteProvider.ReadByte(pos); + + bool wildcardMatch = false; + if(match == buffer1Length - 1 && hasWildcard) { + if((buffer1[match] >> 4) == (compareByte >> 4)) { + wildcardMatch = true; + } + } + + bool buffer1Match = compareByte == buffer1[match]; + bool hasBuffer2 = buffer2 != null; + bool buffer2Match = hasBuffer2 ? compareByte == buffer2[match] : false; + bool isMatch = buffer1Match || buffer2Match || wildcardMatch; + if(!isMatch) { + pos = pos + (matchStartIndex - match); + match = matchStartIndex; + _findingPos = pos; + continue; + } + + match = (start > end ? match-1: match+1); + + if(match == buffer1Length || match == -1) { + long bytePos = start > end ? pos : (pos - buffer1Length + 1); + Select(bytePos, buffer1Length); + ScrollByteIntoView(_bytePos + _selectionLength); + ScrollByteIntoView(_bytePos); + + return bytePos; + } + } + + return -1; + } + + /// + /// Aborts a working Find method. + /// + public void AbortFind() + { + _abortFind = true; + } + + /// + /// Gets a value that indicates the current position during Find method execution. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public long CurrentFindingPosition + { + get + { + return _findingPos; + } + } + #endregion + + #region Copy, Cut and Paste methods + byte[] GetCopyData() + { + if (!CanCopy()) return new byte[0]; + + // put bytes into buffer + byte[] buffer = new byte[_selectionLength]; + int id = -1; + for (long i = _bytePos; i < _bytePos + _selectionLength; i++) + { + id++; + + buffer[id] = _byteProvider.ReadByte(i); + } + return buffer; + } + /// + /// Copies the current selection in the hex box to the Clipboard. + /// + public void Copy() + { + if (!CanCopy()) return; + + // put bytes into buffer + byte[] buffer = GetCopyData(); + + DataObject da = new DataObject(); + + // set string buffer clipbard data + string hex = BitConverter.ToString(buffer).Replace("-", ""); + StringBuilder sb = new StringBuilder(hex.Length * 2); + for(int i = 0; i < hex.Length; i+=BytesPerLine*2) { + sb.AppendLine(hex.Substring(i, i + BytesPerLine*2 > hex.Length ? hex.Length - i : BytesPerLine*2)); + } + + da.SetData(typeof(string), sb.ToString()); + + //string sBuffer = System.Text.Encoding.ASCII.GetString(buffer, 0, buffer.Length); + //da.SetData(typeof(string), sBuffer); + + //set memorystream (BinaryData) clipboard data + System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer, 0, buffer.Length, false, true); + da.SetData("BinaryData", ms); + + Clipboard.SetDataObject(da, true); + UpdateCaret(); + ScrollByteIntoView(); + Invalidate(); + + OnCopied(EventArgs.Empty); + } + + /// + /// Return true if Copy method could be invoked. + /// + public bool CanCopy() + { + if (_selectionLength < 1 || _byteProvider == null) + return false; + + return true; + } + + /// + /// Moves the current selection in the hex box to the Clipboard. + /// + public void Cut() + { + if (!CanCut()) return; + + Copy(); + + _byteProvider.DeleteBytes(_bytePos, _selectionLength); + _byteCharacterPos = 0; + UpdateCaret(); + ScrollByteIntoView(); + ReleaseSelection(); + Invalidate(); + Refresh(); + } + + /// + /// Return true if Cut method could be invoked. + /// + public bool CanCut() + { + if (ReadOnly || !this.Enabled) + return false; + if (_byteProvider == null) + return false; + if (_selectionLength < 1 || !_byteProvider.SupportsDeleteBytes()) + return false; + + return true; + } + + /// + /// Replaces the current selection in the hex box with the contents of the Clipboard. + /// + public void Paste() + { + if (!CanPaste()) return; + + if (_selectionLength > 0) + _byteProvider.DeleteBytes(_bytePos, _selectionLength); + + byte[] buffer = null; + IDataObject da = Clipboard.GetDataObject(); + if (da.GetDataPresent("BinaryData")) + { + System.IO.MemoryStream ms = (System.IO.MemoryStream)da.GetData("BinaryData"); + buffer = new byte[ms.Length]; + ms.Read(buffer, 0, buffer.Length); + } + else if (da.GetDataPresent(typeof(string))) + { + string sBuffer = (string)da.GetData(typeof(string)); + buffer = System.Text.Encoding.ASCII.GetBytes(sBuffer); + } + else + { + return; + } + + _byteProvider.InsertBytes(_bytePos, buffer); + + SetPosition(_bytePos + buffer.Length, 0); + + ReleaseSelection(); + ScrollByteIntoView(); + UpdateCaret(); + Invalidate(); + } + + /// + /// Return true if Paste method could be invoked. + /// + public bool CanPaste() + { + if (ReadOnly || !this.Enabled) return false; + + if (_byteProvider == null || !_byteProvider.SupportsInsertBytes()) + return false; + + if (!_byteProvider.SupportsDeleteBytes() && _selectionLength > 0) + return false; + + IDataObject da = Clipboard.GetDataObject(); + if (da.GetDataPresent("BinaryData")) + return true; + else if (da.GetDataPresent(typeof(string))) + return true; + else + return false; + } + /// + /// Return true if PasteHex method could be invoked. + /// + public bool CanPasteHex() + { + if (!CanPaste()) return false; + + byte[] buffer = null; + IDataObject da = Clipboard.GetDataObject(); + if (da.GetDataPresent(typeof(string))) + { + string hexString = (string)da.GetData(typeof(string)); + buffer = ConvertHexToBytes(hexString); + return (buffer != null); + } + return false; + } + + /// + /// Replaces the current selection in the hex box with the hex string data of the Clipboard. + /// + public void PasteHex() + { + if (!CanPaste()) return; + + byte[] buffer = null; + IDataObject da = Clipboard.GetDataObject(); + if (da.GetDataPresent(typeof(string))) + { + string hexString = (string)da.GetData(typeof(string)); + buffer = ConvertHexToBytes(hexString); + if (buffer == null) + return; + } + else + { + return; + } + + if (_selectionLength > 0) + _byteProvider.DeleteBytes(_bytePos, _selectionLength); + + _byteProvider.InsertBytes(_bytePos, buffer); + + SetPosition(_bytePos + buffer.Length, 0); + + ReleaseSelection(); + ScrollByteIntoView(); + UpdateCaret(); + Invalidate(); + } + + /// + /// Copies the current selection in the hex box to the Clipboard in hex format. + /// + public void CopyHex() + { + if (!CanCopy()) return; + + // put bytes into buffer + byte[] buffer = GetCopyData(); + + DataObject da = new DataObject(); + + // set string buffer clipbard data + string hexString = ConvertBytesToHex(buffer); ; + da.SetData(typeof(string), hexString); + + //set memorystream (BinaryData) clipboard data + System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer, 0, buffer.Length, false, true); + da.SetData("BinaryData", ms); + + Clipboard.SetDataObject(da, true); + UpdateCaret(); + ScrollByteIntoView(); + Invalidate(); + + OnCopiedHex(EventArgs.Empty); + } + + + #endregion + + #region Paint methods + /// + /// Paints the background. + /// + /// A PaintEventArgs that contains the event data. + protected override void OnPaintBackground(PaintEventArgs e) + { + switch (_borderStyle) + { + case BorderStyle.Fixed3D: + { + if (TextBoxRenderer.IsSupported) + { + VisualStyleElement state = VisualStyleElement.TextBox.TextEdit.Normal; + Color backColor = this.BackColor; + + if (this.Enabled) + { + if (this.ReadOnly) + state = VisualStyleElement.TextBox.TextEdit.ReadOnly; + else if (this.Focused) + state = VisualStyleElement.TextBox.TextEdit.Focused; + } + else + { + state = VisualStyleElement.TextBox.TextEdit.Disabled; + backColor = this.BackColorDisabled; + } + + VisualStyleRenderer vsr = new VisualStyleRenderer(state); + vsr.DrawBackground(e.Graphics, this.ClientRectangle); + + Rectangle rectContent = vsr.GetBackgroundContentRectangle(e.Graphics, this.ClientRectangle); + e.Graphics.FillRectangle(new SolidBrush(backColor), rectContent); + } + else + { + // draw background + e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle); + + // draw default border + ControlPaint.DrawBorder3D(e.Graphics, ClientRectangle, Border3DStyle.Sunken); + } + + break; + } + case BorderStyle.FixedSingle: + { + // draw background + e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle); + + // draw fixed single border + ControlPaint.DrawBorder(e.Graphics, ClientRectangle, Color.Black, ButtonBorderStyle.Solid); + break; + } + default: + { + // draw background + e.Graphics.FillRectangle(new SolidBrush(BackColor), ClientRectangle); + break; + } + } + } + + /// + /// Paints the hex box. + /// + /// A PaintEventArgs that contains the event data. + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + if (_byteProvider == null) + return; + + System.Diagnostics.Debug.WriteLine("OnPaint " + DateTime.Now.ToString(), "HexBox"); + + // draw only in the content rectangle, so exclude the border and the scrollbar. + Region r = new Region(ClientRectangle); + r.Exclude(_recContent); + e.Graphics.ExcludeClip(r); + + UpdateVisibilityBytes(); + + if(_caretVisible && this.Focused) { + int caretWidth = (this.InsertActive) ? 1 : (int)_charSize.Width; + int caretHeight = (int)_charSize.Height; + e.Graphics.FillRectangle(Brushes.Yellow, _caretPos.X - 1, _caretPos.Y, caretWidth, caretHeight); + e.Graphics.DrawRectangle(Pens.Gray, _caretPos.X - 1, _caretPos.Y, caretWidth, caretHeight); + } + + if (_lineInfoVisible) + PaintLineInfo(e.Graphics, _startByte, _endByte); + + if (!_stringViewVisible) + { + PaintHex(e.Graphics, _startByte, _endByte); + } + else + { + PaintHexAndStringView(e.Graphics, _startByte, _endByte); + if (_shadowSelectionVisible) + PaintCurrentBytesSign(e.Graphics); + } + if (_columnInfoVisible) + PaintHeaderRow(e.Graphics); + if (_groupSeparatorVisible) + PaintColumnSeparator(e.Graphics); + } + + + void PaintLineInfo(Graphics g, long startByte, long endByte) + { + // Ensure endByte isn't > length of array. + endByte = Math.Min(_byteProvider.Length - 1, endByte); + + Color lineInfoColor = (this.InfoForeColor != Color.Empty) ? this.InfoForeColor : this.ForeColor; + + using(Brush backBrush = new SolidBrush(this.InfoBackColor)) { + using(Brush brush = new SolidBrush(lineInfoColor)) { + int maxLine = GetGridBytePoint(endByte - startByte).Y + 1; + + g.FillRectangle(backBrush, _recLineInfo.X-4, _recLineInfo.Y, _recLineInfo.Width, _recLineInfo.Height); + + for(int i = 0; i < maxLine; i++) { + long firstLineByte = (startByte + (_iHexMaxHBytes) * i) + _lineInfoOffset; + + PointF bytePointF = GetBytePointF(new Point(0, 0 + i)); + string info = firstLineByte.ToString(_hexStringFormat, System.Threading.Thread.CurrentThread.CurrentCulture); + int nulls = 8 - info.Length; + string formattedInfo; + if(nulls > -1) { + formattedInfo = new string('0', LineInfoCharCount - info.Length) + info; + } else { + formattedInfo = new string('~', LineInfoCharCount); + } + + g.DrawString(formattedInfo, Font, brush, new PointF(_recLineInfo.X, bytePointF.Y), _stringFormat); + } + } + } + } + + void PaintHeaderRow(Graphics g) + { + using(Brush brush = new SolidBrush(this.InfoForeColor)) { + using(Brush backBrush = new SolidBrush(this.InfoBackColor)) { + g.FillRectangle(backBrush, 0, 0, this.ClientRectangle.Width, _recLineInfo.Y); + for(int col = 0; col < _iHexMaxHBytes; col++) { + PaintColumnInfo(g, (byte)col, brush, col); + } + } + } + } + + void PaintColumnSeparator(Graphics g) + { + for (int col = GroupSize; col < _iHexMaxHBytes; col += GroupSize) + { + var pen = new Pen(new SolidBrush(this.InfoForeColor), 1); + PointF headerPointF = GetColumnInfoPointF(col); + headerPointF.X -= _charSize.Width / 2; + g.DrawLine(pen, headerPointF, new PointF(headerPointF.X, headerPointF.Y + _recColumnInfo.Height + _recHex.Height)); + if (StringViewVisible) + { + PointF byteStringPointF = GetByteStringPointF(new Point(col, 0)); + headerPointF.X -= 2; + g.DrawLine(pen, new PointF(byteStringPointF.X, byteStringPointF.Y), new PointF(byteStringPointF.X, byteStringPointF.Y + _recHex.Height)); + } + } + } + + void PaintHex(Graphics g, long startByte, long endByte) + { + Brush brush = new SolidBrush(GetDefaultForeColor()); + Brush selBrush = new SolidBrush(_selectionForeColor); + Brush selBrushBack = new SolidBrush(_selectionBackColor); + + int counter = -1; + long intern_endByte = Math.Min(_byteProvider.Length - 1, endByte + _iHexMaxHBytes); + + bool isKeyInterpreterActive = _keyInterpreter == null || _keyInterpreter.GetType() == typeof(KeyInterpreter); + + for (long i = startByte; i < intern_endByte + 1; i++) + { + counter++; + Point gridPoint = GetGridBytePoint(counter); + byte b = _byteProvider.ReadByte(i); + + bool isSelectedByte = i >= _bytePos && i <= (_bytePos + _selectionLength - 1) && _selectionLength != 0; + + if (isSelectedByte && isKeyInterpreterActive) + { + PaintHexStringSelected(g, b, selBrush, selBrushBack, gridPoint); + } + else + { + PaintHexString(g, b, brush, gridPoint); + } + } + } + + void PaintHexString(Graphics g, byte b, Brush brush, Point gridPoint) + { + PointF bytePointF = GetBytePointF(gridPoint); + + string sB = ConvertByteToHex(b); + + g.DrawString(sB.Substring(0, 1), Font, brush, bytePointF, _stringFormat); + bytePointF.X += _charSize.Width; + g.DrawString(sB.Substring(1, 1), Font, brush, bytePointF, _stringFormat); + } + + void PaintColumnInfo(Graphics g, byte b, Brush brush, int col) + { + PointF headerPointF = GetColumnInfoPointF(col); + + string sB = ConvertByteToHex(b); + + g.DrawString(sB.Substring(0, 1), Font, brush, headerPointF, _stringFormat); + headerPointF.X += _charSize.Width; + g.DrawString(sB.Substring(1, 1), Font, brush, headerPointF, _stringFormat); + } + + void PaintHexStringSelected(Graphics g, byte b, Brush brush, Brush brushBack, Point gridPoint) + { + string sB = b.ToString(_hexStringFormat, System.Threading.Thread.CurrentThread.CurrentCulture); + if (sB.Length == 1) + sB = "0" + sB; + + PointF bytePointF = GetBytePointF(gridPoint); + + bool isLastLineChar = (gridPoint.X + 1 == _iHexMaxHBytes); + float bcWidth = (isLastLineChar) ? _charSize.Width * 2 : _charSize.Width * 3; + + g.FillRectangle(brushBack, bytePointF.X, bytePointF.Y, bcWidth, _charSize.Height); + g.DrawString(sB.Substring(0, 1), Font, brush, bytePointF, _stringFormat); + bytePointF.X += _charSize.Width; + g.DrawString(sB.Substring(1, 1), Font, brush, bytePointF, _stringFormat); + } + + void PaintHexAndStringView(Graphics g, long startByte, long endByte) + { + int counter = -1; + long intern_endByte = Math.Min(_byteProvider.Length - 1, endByte + _iHexMaxHBytes); + + bool isKeyInterpreterActive = _keyInterpreter == null || _keyInterpreter.GetType() == typeof(KeyInterpreter); + bool isStringKeyInterpreterActive = _keyInterpreter != null && _keyInterpreter.GetType() == typeof(StringKeyInterpreter); + + for(long i = startByte; i < intern_endByte + 1; i++) { + counter++; + Point gridPoint = GetGridBytePoint(counter); + PointF byteStringPointF = GetByteStringPointF(gridPoint); + byte b = _byteProvider.ReadByte(i); + bool byteHasChanged = false; + if(_prevByteProvider != null && _prevByteProvider.Length == _byteProvider.Length) { + byteHasChanged = _prevByteProvider.ReadByte(i) != b; + } + + bool isSelectedByte = i >= _bytePos && i <= (_bytePos + _selectionLength - 1) && _selectionLength != 0; + + using(Brush brush = new SolidBrush(GetDefaultForeColor())) { + using(Brush changeBrush = new SolidBrush(_changeColor)) { + using(Brush selectionChangeBrush = new SolidBrush(_selectionChangeColor)) { + using(Brush selBrush = new SolidBrush(_selectionForeColor)) { + using(Brush selBrushBack = new SolidBrush(_selectionBackColor)) { + if(isSelectedByte && isKeyInterpreterActive) { + PaintHexStringSelected(g, b, byteHasChanged ? selectionChangeBrush : selBrush, selBrushBack, gridPoint); + } else { + PaintHexString(g, b, byteHasChanged ? changeBrush : brush, gridPoint); + } + + string s = new String(ByteCharConverter.ToChar(b), 1); + + if(isSelectedByte && isStringKeyInterpreterActive) { + g.FillRectangle(selBrushBack, byteStringPointF.X, byteStringPointF.Y, _charSize.Width, _charSize.Height); + g.DrawString(s, Font, selBrush, byteStringPointF, _stringFormat); + } else { + g.DrawString(s, Font, brush, byteStringPointF, _stringFormat); + } + } + } + } + } + } + } + } + + void PaintCurrentBytesSign(Graphics g) + { + if (_keyInterpreter != null && _bytePos != -1 && Enabled) + { + if (_keyInterpreter.GetType() == typeof(KeyInterpreter)) + { + if (_selectionLength == 0) + { + Point gp = GetGridBytePoint(_bytePos - _startByte); + PointF pf = GetByteStringPointF(gp); + Size s = new Size((int)_charSize.Width, (int)_charSize.Height); + Rectangle r = new Rectangle((int)pf.X, (int)pf.Y, s.Width, s.Height); + if (r.IntersectsWith(_recStringView)) + { + r.Intersect(_recStringView); + PaintCurrentByteSign(g, r); + } + } + else + { + int lineWidth = (int)(_recStringView.Width - _charSize.Width); + + Point startSelGridPoint = GetGridBytePoint(_bytePos - _startByte); + PointF startSelPointF = GetByteStringPointF(startSelGridPoint); + + Point endSelGridPoint = GetGridBytePoint(_bytePos - _startByte + _selectionLength - 1); + PointF endSelPointF = GetByteStringPointF(endSelGridPoint); + + int multiLine = endSelGridPoint.Y - startSelGridPoint.Y; + if (multiLine == 0) + { + + Rectangle singleLine = new Rectangle( + (int)startSelPointF.X, + (int)startSelPointF.Y, + (int)(endSelPointF.X - startSelPointF.X + _charSize.Width), + (int)_charSize.Height); + if (singleLine.IntersectsWith(_recStringView)) + { + singleLine.Intersect(_recStringView); + PaintCurrentByteSign(g, singleLine); + } + } + else + { + Rectangle firstLine = new Rectangle( + (int)startSelPointF.X, + (int)startSelPointF.Y, + (int)(_recStringView.X + lineWidth - startSelPointF.X + _charSize.Width), + (int)_charSize.Height); + if (firstLine.IntersectsWith(_recStringView)) + { + firstLine.Intersect(_recStringView); + PaintCurrentByteSign(g, firstLine); + } + + if (multiLine > 1) + { + Rectangle betweenLines = new Rectangle( + _recStringView.X, + (int)(startSelPointF.Y + _charSize.Height), + (int)(_recStringView.Width), + (int)(_charSize.Height * (multiLine - 1))); + if (betweenLines.IntersectsWith(_recStringView)) + { + betweenLines.Intersect(_recStringView); + PaintCurrentByteSign(g, betweenLines); + } + + } + + Rectangle lastLine = new Rectangle( + _recStringView.X, + (int)endSelPointF.Y, + (int)(endSelPointF.X - _recStringView.X + _charSize.Width), + (int)_charSize.Height); + if (lastLine.IntersectsWith(_recStringView)) + { + lastLine.Intersect(_recStringView); + PaintCurrentByteSign(g, lastLine); + } + } + } + } + else + { + if (_selectionLength == 0) + { + Point gp = GetGridBytePoint(_bytePos - _startByte); + PointF pf = GetBytePointF(gp); + Size s = new Size((int)_charSize.Width * 2, (int)_charSize.Height); + Rectangle r = new Rectangle((int)pf.X, (int)pf.Y, s.Width, s.Height); + PaintCurrentByteSign(g, r); + } + else + { + int lineWidth = (int)(_recHex.Width - _charSize.Width * 5); + + Point startSelGridPoint = GetGridBytePoint(_bytePos - _startByte); + PointF startSelPointF = GetBytePointF(startSelGridPoint); + + Point endSelGridPoint = GetGridBytePoint(_bytePos - _startByte + _selectionLength - 1); + PointF endSelPointF = GetBytePointF(endSelGridPoint); + + int multiLine = endSelGridPoint.Y - startSelGridPoint.Y; + if (multiLine == 0) + { + Rectangle singleLine = new Rectangle( + (int)startSelPointF.X, + (int)startSelPointF.Y, + (int)(endSelPointF.X - startSelPointF.X + _charSize.Width * 2), + (int)_charSize.Height); + if (singleLine.IntersectsWith(_recHex)) + { + singleLine.Intersect(_recHex); + PaintCurrentByteSign(g, singleLine); + } + } + else + { + Rectangle firstLine = new Rectangle( + (int)startSelPointF.X, + (int)startSelPointF.Y, + (int)(_recHex.X + lineWidth - startSelPointF.X + _charSize.Width * 2), + (int)_charSize.Height); + if (firstLine.IntersectsWith(_recHex)) + { + firstLine.Intersect(_recHex); + PaintCurrentByteSign(g, firstLine); + } + + if (multiLine > 1) + { + Rectangle betweenLines = new Rectangle( + _recHex.X, + (int)(startSelPointF.Y + _charSize.Height), + (int)(lineWidth + _charSize.Width * 2), + (int)(_charSize.Height * (multiLine - 1))); + if (betweenLines.IntersectsWith(_recHex)) + { + betweenLines.Intersect(_recHex); + PaintCurrentByteSign(g, betweenLines); + } + + } + + Rectangle lastLine = new Rectangle( + _recHex.X, + (int)endSelPointF.Y, + (int)(endSelPointF.X - _recHex.X + _charSize.Width * 2), + (int)_charSize.Height); + if (lastLine.IntersectsWith(_recHex)) + { + lastLine.Intersect(_recHex); + PaintCurrentByteSign(g, lastLine); + } + } + } + } + } + } + + void PaintCurrentByteSign(Graphics g, Rectangle rec) + { + // stack overflowexception on big files - workaround + if (rec.Top < 0 || rec.Left < 0 || rec.Width <= 0 || rec.Height <= 0) + return; + + Bitmap myBitmap = new Bitmap(rec.Width, rec.Height); + Graphics bitmapGraphics = Graphics.FromImage(myBitmap); + + SolidBrush greenBrush = new SolidBrush(_shadowSelectionColor); + + bitmapGraphics.FillRectangle(greenBrush, 0, + 0, rec.Width, rec.Height); + + g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.GammaCorrected; + + g.DrawImage(myBitmap, rec.Left, rec.Top); + } + + Color GetDefaultForeColor() + { + if (Enabled) + return ForeColor; + else + return Color.Gray; + } + void UpdateVisibilityBytes() + { + if (_byteProvider == null || _byteProvider.Length == 0) + return; + + _startByte = (_scrollVpos + 1) * _iHexMaxHBytes - _iHexMaxHBytes; + _endByte = (long)Math.Min(_byteProvider.Length - 1, _startByte + _iHexMaxBytes); + } + #endregion + + public int LineInfoCharCount + { + get + { + if(this.ByteProvider == null || this.ByteProvider.Length > 0x10000000) { + return 8; + } else if(this.ByteProvider.Length > 0x1000000) { + return 7; + } else if(this.ByteProvider.Length > 0x100000) { + return 6; + } else if(this.ByteProvider.Length > 0x10000) { + return 5; + } else { + return 4; + } + } + } + + #region Positioning methods + void UpdateRectanglePositioning() + { + // calc char size + SizeF charSize; + using (var graphics = this.CreateGraphics()) + { + charSize = this.CreateGraphics().MeasureString("A", Font, 100, _stringFormat); + } + CharSize = new SizeF((float)Math.Ceiling(charSize.Width), (float)Math.Ceiling(charSize.Height)); + + int requiredWidth = 0; + + // calc content bounds + _recContent = ClientRectangle; + _recContent.X += _recBorderLeft; + _recContent.Y += _recBorderTop; + _recContent.Width -= _recBorderRight + _recBorderLeft; + _recContent.Height -= _recBorderBottom + _recBorderTop; + + if (_vScrollBarVisible) + { + _recContent.Width -= _vScrollBar.Width; + _vScrollBar.Left = _recContent.X + _recContent.Width; + _vScrollBar.Top = _recContent.Y; + _vScrollBar.Height = _recContent.Height; + requiredWidth += _vScrollBar.Width; + } + + int marginLeft = 4; + + // calc line info bounds + if (_lineInfoVisible) + { + _recLineInfo = new Rectangle(_recContent.X + marginLeft, + _recContent.Y, + (int)(_charSize.Width * (LineInfoCharCount + 1)), + _recContent.Height); + requiredWidth += _recLineInfo.Width + marginLeft; + } + else + { + _recLineInfo = Rectangle.Empty; + _recLineInfo.X = marginLeft; + requiredWidth += marginLeft; + } + + // calc line info bounds + _recColumnInfo = new Rectangle(_recLineInfo.X + _recLineInfo.Width, _recContent.Y, _recContent.Width - _recLineInfo.Width, (int)charSize.Height + 4); + if (_columnInfoVisible) + { + _recLineInfo.Y += (int)charSize.Height + 4; + _recLineInfo.Height -= (int)charSize.Height + 4; + } + else + { + _recColumnInfo.Height = 0; + } + + // calc hex bounds and grid + _recHex = new Rectangle(_recLineInfo.X + _recLineInfo.Width, + _recLineInfo.Y, + _recContent.Width - _recLineInfo.Width, + _recContent.Height - _recColumnInfo.Height); + + if (UseFixedBytesPerLine) + { + SetHorizontalByteCount(_bytesPerLine); + _recHex.Width = (int)Math.Floor(((double)_iHexMaxHBytes) * _charSize.Width * 3 + (2 * _charSize.Width)); + requiredWidth += _recHex.Width; + } + else + { + int hmax = (int)Math.Floor((double)_recHex.Width / (double)_charSize.Width); + if (_stringViewVisible) + { + hmax -= 2; + if (hmax > 1) + SetHorizontalByteCount((int)Math.Floor((double)hmax / 4)); + else + SetHorizontalByteCount(1); + } + else + { + if (hmax > 1) + SetHorizontalByteCount((int)Math.Floor((double)hmax / 3)); + else + SetHorizontalByteCount(1); + } + _recHex.Width = (int)Math.Floor(((double)_iHexMaxHBytes) * _charSize.Width * 3 + (2 * _charSize.Width)); + requiredWidth += _recHex.Width; + } + + if (_stringViewVisible) + { + _recStringView = new Rectangle(_recHex.X + _recHex.Width, + _recHex.Y, + (int)(_charSize.Width * _iHexMaxHBytes), + _recHex.Height); + requiredWidth += _recStringView.Width; + } + else + { + _recStringView = Rectangle.Empty; + } + + RequiredWidth = requiredWidth; + + int vmax = (int)Math.Floor((double)_recHex.Height / (double)_charSize.Height); + SetVerticalByteCount(vmax); + + _iHexMaxBytes = _iHexMaxHBytes * _iHexMaxVBytes; + + UpdateScrollSize(); + } + + PointF GetBytePointF(long byteIndex) + { + Point gp = GetGridBytePoint(byteIndex); + + return GetBytePointF(gp); + } + + PointF GetBytePointF(Point gp) + { + float x = (3 * _charSize.Width) * gp.X + _recHex.X; + float y = (gp.Y + 1) * _charSize.Height - _charSize.Height + _recHex.Y; + + return new PointF(x, y); + } + PointF GetColumnInfoPointF(int col) + { + Point gp = GetGridBytePoint(col); + float x = (3 * _charSize.Width) * gp.X + _recColumnInfo.X; + float y = _recColumnInfo.Y; + + return new PointF(x, y); + } + + PointF GetByteStringPointF(Point gp) + { + float x = (_charSize.Width) * gp.X + _recStringView.X; + float y = (gp.Y + 1) * _charSize.Height - _charSize.Height + _recStringView.Y; + + return new PointF(x, y); + } + + Point GetGridBytePoint(long byteIndex) + { + int row = (int)Math.Floor((double)byteIndex / (double)_iHexMaxHBytes); + int column = (int)(byteIndex + _iHexMaxHBytes - _iHexMaxHBytes * (row + 1)); + + Point res = new Point(column, row); + return res; + } + #endregion + + #region Overridden properties + /// + /// Gets or sets the background color for the control. + /// + [DefaultValue(typeof(Color), "White")] + public override Color BackColor + { + get + { + return base.BackColor; + } + set + { + base.BackColor = value; + } + } + + /// + /// The font used to display text in the hexbox. + /// + public override Font Font + { + get + { + return base.Font; + } + set + { + if (value == null) + return; + + base.Font = value; + this.UpdateRectanglePositioning(); + this.Invalidate(); + } + } + + /// + /// Not used. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), EditorBrowsable(EditorBrowsableState.Never), Bindable(false)] + public override string Text + { + get + { + return base.Text; + } + set + { + base.Text = value; + } + } + + /// + /// Not used. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), EditorBrowsable(EditorBrowsableState.Never), Bindable(false)] + public override RightToLeft RightToLeft + { + get + { + return base.RightToLeft; + } + set + { + base.RightToLeft = value; + } + } + #endregion + + #region Properties + /// + /// Gets or sets the background color for the disabled control. + /// + [Category("Appearance"), DefaultValue(typeof(Color), "WhiteSmoke")] + public Color BackColorDisabled + { + get + { + return _backColorDisabled; + } + set + { + _backColorDisabled = value; + } + } Color _backColorDisabled = Color.FromName("WhiteSmoke"); + + /// + /// Gets or sets if the count of bytes in one line is fix. + /// + /// + /// When set to True, BytesPerLine property determine the maximum count of bytes in one line. + /// + [DefaultValue(false), Category("Hex"), Description("Gets or sets if the count of bytes in one line is fix.")] + public bool ReadOnly + { + get { return _readOnly; } + set + { + if (_readOnly == value) + return; + + _readOnly = value; + OnReadOnlyChanged(EventArgs.Empty); + Invalidate(); + } + } bool _readOnly; + + /// + /// Gets or sets the maximum count of bytes in one line. + /// + /// + /// UseFixedBytesPerLine property no longer has to be set to true for this to work + /// + [DefaultValue(16), Category("Hex"), Description("Gets or sets the maximum count of bytes in one line.")] + public int BytesPerLine + { + get { return _bytesPerLine; } + set + { + if (_bytesPerLine == value) + return; + + _bytesPerLine = value; + OnBytesPerLineChanged(EventArgs.Empty); + + UpdateRectanglePositioning(); + Invalidate(); + } + } int _bytesPerLine = 16; + + /// + /// Gets or sets the number of bytes in a group. Used to show the group separator line (if GroupSeparatorVisible is true) + /// + /// + /// GroupSeparatorVisible property must set to true + /// + [DefaultValue(4), Category("Hex"), Description("Gets or sets the byte-count between group separators (if visible).")] + public int GroupSize + { + get { return _groupSize; } + set + { + if (_groupSize == value) + return; + + _groupSize = value; + OnGroupSizeChanged(EventArgs.Empty); + + UpdateRectanglePositioning(); + Invalidate(); + } + } int _groupSize = 4; + /// + /// Gets or sets if the count of bytes in one line is fix. + /// + /// + /// When set to True, BytesPerLine property determine the maximum count of bytes in one line. + /// + [DefaultValue(false), Category("Hex"), Description("Gets or sets if the count of bytes in one line is fix.")] + public bool UseFixedBytesPerLine + { + get { return _useFixedBytesPerLine; } + set + { + if (_useFixedBytesPerLine == value) + return; + + _useFixedBytesPerLine = value; + OnUseFixedBytesPerLineChanged(EventArgs.Empty); + + UpdateRectanglePositioning(); + Invalidate(); + } + } bool _useFixedBytesPerLine; + + /// + /// Gets or sets the visibility of a vertical scroll bar. + /// + [DefaultValue(false), Category("Hex"), Description("Gets or sets the visibility of a vertical scroll bar.")] + public bool VScrollBarVisible + { + get { return this._vScrollBarVisible; } + set + { + if (_vScrollBarVisible == value) + return; + + _vScrollBarVisible = value; + + if (_vScrollBarVisible) + Controls.Add(_vScrollBar); + else + Controls.Remove(_vScrollBar); + + UpdateRectanglePositioning(); + UpdateScrollSize(); + + OnVScrollBarVisibleChanged(EventArgs.Empty); + } + } bool _vScrollBarVisible; + + /// + /// Gets or sets the ByteProvider. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IByteProvider ByteProvider + { + get { return _byteProvider; } + set + { + bool sameLength = value != null && _byteProvider != null && _byteProvider.Length == value.Length; + if (_byteProvider == value) + return; + + if (value == null) + ActivateEmptyKeyInterpreter(); + else + ActivateKeyInterpreter(); + + if (_byteProvider != null) + _byteProvider.LengthChanged -= new EventHandler(_byteProvider_LengthChanged); + + _prevByteProvider = _byteProvider; + _byteProvider = value; + if (_byteProvider != null) + _byteProvider.LengthChanged += new EventHandler(_byteProvider_LengthChanged); + + OnByteProviderChanged(EventArgs.Empty); + + if (value == null) // do not raise events if value is null + { + _bytePos = -1; + _byteCharacterPos = 0; + _selectionLength = 0; + + DestroyCaret(); + } + else + { + if(!sameLength) { + SetPosition(0, 0); + SetSelectionLength(0); + } + + if (_caretVisible && Focused) + UpdateCaret(); + else + CreateCaret(); + } + + CheckCurrentLineChanged(); + CheckCurrentPositionInLineChanged(); + + if(!sameLength) { + _scrollVpos = 0; + } + + UpdateVisibilityBytes(); + UpdateRectanglePositioning(); + + Invalidate(); + } + } + + IByteProvider _byteProvider; + IByteProvider _prevByteProvider; + + internal void ClearHistory() + { + this._prevByteProvider = null; + } + + /// + /// Gets or sets the visibility of the group separator. + /// + [DefaultValue(false), Category("Hex"), Description("Gets or sets the visibility of a separator vertical line.")] + public bool GroupSeparatorVisible + { + get { return _groupSeparatorVisible; } + set + { + if (_groupSeparatorVisible == value) + return; + + _groupSeparatorVisible = value; + OnGroupSeparatorVisibleChanged(EventArgs.Empty); + + UpdateRectanglePositioning(); + Invalidate(); + } + } bool _groupSeparatorVisible = false; + + /// + /// Gets or sets the visibility of the column info + /// + [DefaultValue(false), Category("Hex"), Description("Gets or sets the visibility of header row.")] + public bool ColumnInfoVisible + { + get { return _columnInfoVisible; } + set + { + if (_columnInfoVisible == value) + return; + + _columnInfoVisible = value; + OnColumnInfoVisibleChanged(EventArgs.Empty); + + UpdateRectanglePositioning(); + Invalidate(); + } + } bool _columnInfoVisible = false; + + /// + /// Gets or sets the visibility of a line info. + /// + [DefaultValue(false), Category("Hex"), Description("Gets or sets the visibility of a line info.")] + public bool LineInfoVisible + { + get { return _lineInfoVisible; } + set + { + if (_lineInfoVisible == value) + return; + + _lineInfoVisible = value; + OnLineInfoVisibleChanged(EventArgs.Empty); + + UpdateRectanglePositioning(); + Invalidate(); + } + } bool _lineInfoVisible = false; + + /// + /// Gets or sets the offset of a line info. + /// + [DefaultValue((long)0), Category("Hex"), Description("Gets or sets the offset of the line info.")] + public long LineInfoOffset + { + get { return _lineInfoOffset; } + set + { + if (_lineInfoOffset == value) + return; + + _lineInfoOffset = value; + + Invalidate(); + } + } long _lineInfoOffset = 0; + + /// + /// Gets or sets the hex box´s border style. + /// + [DefaultValue(typeof(BorderStyle), "Fixed3D"), Category("Hex"), Description("Gets or sets the hex box´s border style.")] + public BorderStyle BorderStyle + { + get { return _borderStyle; } + set + { + if (_borderStyle == value) + return; + + _borderStyle = value; + switch (_borderStyle) + { + case BorderStyle.None: + _recBorderLeft = _recBorderTop = _recBorderRight = _recBorderBottom = 0; + break; + case BorderStyle.Fixed3D: + _recBorderLeft = _recBorderRight = SystemInformation.Border3DSize.Width; + _recBorderTop = _recBorderBottom = SystemInformation.Border3DSize.Height; + break; + case BorderStyle.FixedSingle: + _recBorderLeft = _recBorderTop = _recBorderRight = _recBorderBottom = 1; + break; + } + + UpdateRectanglePositioning(); + + OnBorderStyleChanged(EventArgs.Empty); + + } + } BorderStyle _borderStyle = BorderStyle.Fixed3D; + + /// + /// Gets or sets the visibility of the string view. + /// + [DefaultValue(false), Category("Hex"), Description("Gets or sets the visibility of the string view.")] + public bool StringViewVisible + { + get { return _stringViewVisible; } + set + { + if (_stringViewVisible == value) + return; + + _stringViewVisible = value; + OnStringViewVisibleChanged(EventArgs.Empty); + + UpdateRectanglePositioning(); + Invalidate(); + } + } bool _stringViewVisible; + + /// + /// Gets or sets whether the HexBox control displays the hex characters in upper or lower case. + /// + [DefaultValue(typeof(HexCasing), "Upper"), Category("Hex"), Description("Gets or sets whether the HexBox control displays the hex characters in upper or lower case.")] + public HexCasing HexCasing + { + get + { + if (_hexStringFormat == "X") + return HexCasing.Upper; + else + return HexCasing.Lower; + } + set + { + string format; + if (value == HexCasing.Upper) + format = "X"; + else + format = "x"; + + if (_hexStringFormat == format) + return; + + _hexStringFormat = format; + OnHexCasingChanged(EventArgs.Empty); + + Invalidate(); + } + } + + /// + /// Gets and sets the starting point of the bytes selected in the hex box. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public long SelectionStart + { + get { return _bytePos; } + set + { + SetPosition(value, 0); + ScrollByteIntoView(); + Invalidate(); + } + } + + /// + /// Gets and sets the number of bytes selected in the hex box. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public long SelectionLength + { + get { return _selectionLength; } + set + { + SetSelectionLength(value); + ScrollByteIntoView(); + Invalidate(); + } + } long _selectionLength; + + + /// + /// Gets or sets the info color used for column info and line info. When this property is null, then ForeColor property is used. + /// + [DefaultValue(typeof(Color), "Gray"), Category("Hex"), Description("Gets or sets the line info color. When this property is null, then ForeColor property is used.")] + public Color InfoForeColor + { + get { return _infoForeColor; } + set { _infoForeColor = value; Invalidate(); } + } Color _infoForeColor = Color.Gray; + + /// + /// Gets or sets the info color used for column info and line info. When this property is null, then ForeColor property is used. + /// + [DefaultValue(typeof(Color), "Gray"), Category("Hex"), Description("Gets or sets the line info color. When this property is null, then ForeColor property is used.")] + public Color InfoBackColor + { + get { return _infoBackColor; } + set { _infoBackColor = value; Invalidate(); } + } + Color _infoBackColor = Color.DarkGray; + + /// + /// Gets or sets the background color for the selected bytes. + /// + [DefaultValue(typeof(Color), "Blue"), Category("Hex"), Description("Gets or sets the background color for the selected bytes.")] + public Color SelectionBackColor + { + get { return _selectionBackColor; } + set { _selectionBackColor = value; Invalidate(); } + } Color _selectionBackColor = Color.Blue; + + /// + /// Gets or sets the foreground color for the selected bytes. + /// + [DefaultValue(typeof(Color), "White"), Category("Hex"), Description("Gets or sets the foreground color for the selected bytes.")] + public Color SelectionForeColor + { + get { return _selectionForeColor; } + set { _selectionForeColor = value; Invalidate(); } + } Color _selectionForeColor = Color.White; + + /// + /// Gets or sets the foreground color for the changed bytes. + /// + [DefaultValue(typeof(Color), "White"), Category("Hex"), Description("Gets or sets the foreground color for the selected bytes.")] + public Color ChangeColor + { + get { return _changeColor; } + set { _changeColor = value; Invalidate(); } + } + Color _changeColor = Color.White; + + /// + /// Gets or sets the foreground color for the selected changed bytes. + /// + [DefaultValue(typeof(Color), "White"), Category("Hex"), Description("Gets or sets the foreground color for the selected bytes.")] + public Color SelectionChangeColor + { + get { return _selectionChangeColor; } + set { _selectionChangeColor = value; Invalidate(); } + } + Color _selectionChangeColor = Color.White; + + + + /// + /// Gets or sets the visibility of a shadow selection. + /// + [DefaultValue(true), Category("Hex"), Description("Gets or sets the visibility of a shadow selection.")] + public bool ShadowSelectionVisible + { + get { return _shadowSelectionVisible; } + set + { + if (_shadowSelectionVisible == value) + return; + _shadowSelectionVisible = value; + Invalidate(); + } + } bool _shadowSelectionVisible = true; + + /// + /// Gets or sets the color of the shadow selection. + /// + /// + /// A alpha component must be given! + /// Default alpha = 100 + /// + [Category("Hex"), Description("Gets or sets the color of the shadow selection.")] + public Color ShadowSelectionColor + { + get { return _shadowSelectionColor; } + set { _shadowSelectionColor = value; Invalidate(); } + } Color _shadowSelectionColor = Color.FromArgb(100, 60, 188, 255); + + /// + /// Contains the size of a single character in pixel + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public SizeF CharSize + { + get { return _charSize; } + private set + { + if (_charSize == value) + return; + _charSize = value; + if (CharSizeChanged != null) + CharSizeChanged(this, EventArgs.Empty); + } + } SizeF _charSize; + + /// + /// Gets the width required for the content + /// + [DefaultValue(0), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int RequiredWidth + { + get { return _requiredWidth; } + private set + { + if (_requiredWidth == value) + return; + _requiredWidth = value; + if (RequiredWidthChanged != null) + RequiredWidthChanged(this, EventArgs.Empty); + } + } int _requiredWidth; + + /// + /// Gets the number bytes drawn horizontally. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int HorizontalByteCount + { + get { return _iHexMaxHBytes; } + } + + /// + /// Gets the number bytes drawn vertically. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int VerticalByteCount + { + get { return _iHexMaxVBytes; } + } + + /// + /// Gets the current line + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public long CurrentLine + { + get { return _currentLine; } + } long _currentLine; + + /// + /// Gets the current position in the current line + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public long CurrentPositionInLine + { + get { return _currentPositionInLine; } + } int _currentPositionInLine; + + /// + /// Gets the a value if insertion mode is active or not. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool InsertActive + { + get { return _insertActive; } + set + { + if (_insertActive == value) + return; + + _insertActive = value; + + // recreate caret + DestroyCaret(); + CreateCaret(); + + // raise change event + OnInsertActiveChanged(EventArgs.Empty); + } + } + + /// + /// Gets or sets the built-in context menu. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public BuiltInContextMenu BuiltInContextMenu + { + get { return _builtInContextMenu; } + } BuiltInContextMenu _builtInContextMenu; + + + /// + /// Gets or sets the converter that will translate between byte and character values. + /// + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IByteCharConverter ByteCharConverter + { + get + { + if (_byteCharConverter == null) + _byteCharConverter = new DefaultByteCharConverter(); + return _byteCharConverter; + } + set + { + if (value != null && value != _byteCharConverter) + { + _byteCharConverter = value; + Invalidate(); + } + } + } IByteCharConverter _byteCharConverter; + + #endregion + + #region Misc + /// + /// Converts a byte array to a hex string. For example: {10,11} = "0A 0B" + /// + /// the byte array + /// the hex string + string ConvertBytesToHex(byte[] data) + { + StringBuilder sb = new StringBuilder(); + foreach (byte b in data) + { + string hex = ConvertByteToHex(b); + sb.Append(hex); + sb.Append(" "); + } + if (sb.Length > 0) + sb.Remove(sb.Length - 1, 1); + string result = sb.ToString(); + return result; + } + /// + /// Converts the byte to a hex string. For example: "10" = "0A"; + /// + /// the byte to format + /// the hex string + string ConvertByteToHex(byte b) + { + string sB = b.ToString(_hexStringFormat, System.Threading.Thread.CurrentThread.CurrentCulture); + if (sB.Length == 1) + sB = "0" + sB; + return sB; + } + /// + /// Converts the hex string to an byte array. The hex string must be separated by a space char ' '. If there is any invalid hex information in the string the result will be null. + /// + /// the hex string separated by ' '. For example: "0A 0B 0C" + /// the byte array. null if hex is invalid or empty + byte[] ConvertHexToBytes(string hex) + { + if (string.IsNullOrEmpty(hex)) + return null; + hex = hex.Trim(); + var hexArray = hex.Split(' '); + var byteArray = new byte[hexArray.Length]; + + for (int i = 0; i < hexArray.Length; i++) + { + var hexValue = hexArray[i]; + + byte b; + var isByte = ConvertHexToByte(hexValue, out b); + if (!isByte) + return null; + byteArray[i] = b; + } + + return byteArray; + } + + bool ConvertHexToByte(string hex, out byte b) + { + bool isByte = byte.TryParse(hex, System.Globalization.NumberStyles.HexNumber, System.Threading.Thread.CurrentThread.CurrentCulture, out b); + return isByte; + } + + void SetPosition(long bytePos) + { + SetPosition(bytePos, _byteCharacterPos); + } + + void SetPosition(long bytePos, int byteCharacterPos) + { + if (_byteCharacterPos != byteCharacterPos) + { + _byteCharacterPos = byteCharacterPos; + } + + if (bytePos != _bytePos) + { + _bytePos = bytePos; + CheckCurrentLineChanged(); + CheckCurrentPositionInLineChanged(); + + OnSelectionStartChanged(EventArgs.Empty); + } + } + + void SetSelectionLength(long selectionLength) + { + if (selectionLength != _selectionLength) + { + _selectionLength = selectionLength; + OnSelectionLengthChanged(EventArgs.Empty); + } + } + + void SetHorizontalByteCount(int value) + { + if (_iHexMaxHBytes == value) + return; + + _iHexMaxHBytes = value; + OnHorizontalByteCountChanged(EventArgs.Empty); + } + + void SetVerticalByteCount(int value) + { + if (_iHexMaxVBytes == value) + return; + + _iHexMaxVBytes = value; + OnVerticalByteCountChanged(EventArgs.Empty); + } + + void CheckCurrentLineChanged() + { + long currentLine = (long)Math.Floor((double)_bytePos / (double)_iHexMaxHBytes) + 1; + + if (_byteProvider == null && _currentLine != 0) + { + _currentLine = 0; + OnCurrentLineChanged(EventArgs.Empty); + } + else if (currentLine != _currentLine) + { + _currentLine = currentLine; + OnCurrentLineChanged(EventArgs.Empty); + } + } + + void CheckCurrentPositionInLineChanged() + { + Point gb = GetGridBytePoint(_bytePos); + int currentPositionInLine = gb.X + 1; + + if (_byteProvider == null && _currentPositionInLine != 0) + { + _currentPositionInLine = 0; + OnCurrentPositionInLineChanged(EventArgs.Empty); + } + else if (currentPositionInLine != _currentPositionInLine) + { + _currentPositionInLine = currentPositionInLine; + OnCurrentPositionInLineChanged(EventArgs.Empty); + } + } + + /// + /// Raises the InsertActiveChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnInsertActiveChanged(EventArgs e) + { + if (InsertActiveChanged != null) + InsertActiveChanged(this, e); + } + + /// + /// Raises the ReadOnlyChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnReadOnlyChanged(EventArgs e) + { + if (ReadOnlyChanged != null) + ReadOnlyChanged(this, e); + } + + /// + /// Raises the ByteProviderChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnByteProviderChanged(EventArgs e) + { + if (ByteProviderChanged != null) + ByteProviderChanged(this, e); + } + + /// + /// Raises the SelectionStartChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnSelectionStartChanged(EventArgs e) + { + if (SelectionStartChanged != null) + SelectionStartChanged(this, e); + } + + /// + /// Raises the SelectionLengthChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnSelectionLengthChanged(EventArgs e) + { + if (SelectionLengthChanged != null) + SelectionLengthChanged(this, e); + } + + /// + /// Raises the LineInfoVisibleChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnLineInfoVisibleChanged(EventArgs e) + { + if (LineInfoVisibleChanged != null) + LineInfoVisibleChanged(this, e); + } + + /// + /// Raises the OnColumnInfoVisibleChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnColumnInfoVisibleChanged(EventArgs e) + { + if (ColumnInfoVisibleChanged != null) + ColumnInfoVisibleChanged(this, e); + } + + /// + /// Raises the ColumnSeparatorVisibleChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnGroupSeparatorVisibleChanged(EventArgs e) + { + if (GroupSeparatorVisibleChanged != null) + GroupSeparatorVisibleChanged(this, e); + } + + /// + /// Raises the StringViewVisibleChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnStringViewVisibleChanged(EventArgs e) + { + if (StringViewVisibleChanged != null) + StringViewVisibleChanged(this, e); + } + + /// + /// Raises the BorderStyleChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnBorderStyleChanged(EventArgs e) + { + if (BorderStyleChanged != null) + BorderStyleChanged(this, e); + } + + /// + /// Raises the UseFixedBytesPerLineChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnUseFixedBytesPerLineChanged(EventArgs e) + { + if (UseFixedBytesPerLineChanged != null) + UseFixedBytesPerLineChanged(this, e); + } + + /// + /// Raises the GroupSizeChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnGroupSizeChanged(EventArgs e) + { + if (GroupSizeChanged != null) + GroupSizeChanged(this, e); + } + + /// + /// Raises the BytesPerLineChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnBytesPerLineChanged(EventArgs e) + { + if (BytesPerLineChanged != null) + BytesPerLineChanged(this, e); + } + + /// + /// Raises the VScrollBarVisibleChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnVScrollBarVisibleChanged(EventArgs e) + { + if (VScrollBarVisibleChanged != null) + VScrollBarVisibleChanged(this, e); + } + + /// + /// Raises the HexCasingChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnHexCasingChanged(EventArgs e) + { + if (HexCasingChanged != null) + HexCasingChanged(this, e); + } + + /// + /// Raises the HorizontalByteCountChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnHorizontalByteCountChanged(EventArgs e) + { + if (HorizontalByteCountChanged != null) + HorizontalByteCountChanged(this, e); + } + + /// + /// Raises the VerticalByteCountChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnVerticalByteCountChanged(EventArgs e) + { + if (VerticalByteCountChanged != null) + VerticalByteCountChanged(this, e); + } + + /// + /// Raises the CurrentLineChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnCurrentLineChanged(EventArgs e) + { + if (CurrentLineChanged != null) + CurrentLineChanged(this, e); + } + + /// + /// Raises the CurrentPositionInLineChanged event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnCurrentPositionInLineChanged(EventArgs e) + { + if (CurrentPositionInLineChanged != null) + CurrentPositionInLineChanged(this, e); + } + + + /// + /// Raises the Copied event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnCopied(EventArgs e) + { + if (Copied != null) + Copied(this, e); + } + + /// + /// Raises the CopiedHex event. + /// + /// An EventArgs that contains the event data. + protected virtual void OnCopiedHex(EventArgs e) + { + if (CopiedHex != null) + CopiedHex(this, e); + } + + /// + /// Raises the MouseDown event. + /// + /// An EventArgs that contains the event data. + protected override void OnMouseDown(MouseEventArgs e) + { + System.Diagnostics.Debug.WriteLine("OnMouseDown()", "HexBox"); + + if (!Focused) + Focus(); + + if (e.Button == MouseButtons.Left) + SetCaretPosition(new Point(e.X, e.Y)); + + base.OnMouseDown(e); + } + + /// + /// Raises the MouseWhell event + /// + /// An EventArgs that contains the event data. + protected override void OnMouseWheel(MouseEventArgs e) + { + int linesToScroll = -(e.Delta * SystemInformation.MouseWheelScrollLines / 120); + this.PerformScrollLines(linesToScroll); + + base.OnMouseWheel(e); + } + + + /// + /// Raises the Resize event. + /// + /// An EventArgs that contains the event data. + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + UpdateRectanglePositioning(); + } + + /// + /// Raises the GotFocus event. + /// + /// An EventArgs that contains the event data. + protected override void OnGotFocus(EventArgs e) + { + System.Diagnostics.Debug.WriteLine("OnGotFocus()", "HexBox"); + + base.OnGotFocus(e); + + CreateCaret(); + } + + /// + /// Raises the LostFocus event. + /// + /// An EventArgs that contains the event data. + protected override void OnLostFocus(EventArgs e) + { + System.Diagnostics.Debug.WriteLine("OnLostFocus()", "HexBox"); + + base.OnLostFocus(e); + + DestroyCaret(); + } + + void _byteProvider_LengthChanged(object sender, EventArgs e) + { + UpdateScrollSize(); + } + #endregion + + #region Scaling Support for High DPI resolution screens + /// + /// For high resolution screen support + /// + /// the factor + /// bounds + protected override void ScaleControl(SizeF factor, BoundsSpecified specified) + { + base.ScaleControl(factor, specified); + + this.BeginInvoke(new MethodInvoker(() => + { + this.UpdateRectanglePositioning(); + if (_caretVisible) + { + DestroyCaret(); + CreateCaret(); + } + this.Invalidate(); + })); + } + #endregion + } +} diff --git a/GUI.NET/Debugger/HexBox/HexBox.resx b/GUI.NET/Debugger/HexBox/HexBox.resx new file mode 100644 index 00000000..90f8d2d9 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/HexBox.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + False + + \ No newline at end of file diff --git a/GUI.NET/Debugger/HexBox/HexCasing.cs b/GUI.NET/Debugger/HexBox/HexCasing.cs new file mode 100644 index 00000000..bf007ee4 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/HexCasing.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Be.Windows.Forms +{ + /// + /// Specifies the case of hex characters in the HexBox control + /// + public enum HexCasing + { + /// + /// Converts all characters to uppercase. + /// + Upper = 0, + /// + /// Converts all characters to lowercase. + /// + Lower = 1 + } +} diff --git a/GUI.NET/Debugger/HexBox/IByteProvider.cs b/GUI.NET/Debugger/HexBox/IByteProvider.cs new file mode 100644 index 00000000..7847ae5d --- /dev/null +++ b/GUI.NET/Debugger/HexBox/IByteProvider.cs @@ -0,0 +1,75 @@ +using System; + +namespace Be.Windows.Forms +{ + /// + /// Defines a byte provider for HexBox control + /// + public interface IByteProvider + { + /// + /// Reads a byte from the provider + /// + /// the index of the byte to read + /// the byte to read + byte ReadByte(long index); + /// + /// Writes a byte into the provider + /// + /// the index of the byte to write + /// the byte to write + void WriteByte(long index, byte value); + /// + /// Inserts bytes into the provider + /// + /// + /// + /// This method must raise the LengthChanged event. + void InsertBytes(long index, byte[] bs); + /// + /// Deletes bytes from the provider + /// + /// the start index of the bytes to delete + /// the length of the bytes to delete + /// This method must raise the LengthChanged event. + void DeleteBytes(long index, long length); + + /// + /// Returns the total length of bytes the byte provider is providing. + /// + long Length { get; } + /// + /// Occurs, when the Length property changed. + /// + event EventHandler LengthChanged; + + /// + /// True, when changes are done. + /// + bool HasChanges(); + /// + /// Applies changes. + /// + void ApplyChanges(); + /// + /// Occurs, when bytes are changed. + /// + event EventHandler Changed; + + /// + /// Returns a value if the WriteByte methods is supported by the provider. + /// + /// True, when it´s supported. + bool SupportsWriteByte(); + /// + /// Returns a value if the InsertBytes methods is supported by the provider. + /// + /// True, when it´s supported. + bool SupportsInsertBytes(); + /// + /// Returns a value if the DeleteBytes methods is supported by the provider. + /// + /// True, when it´s supported. + bool SupportsDeleteBytes(); + } +} diff --git a/GUI.NET/Debugger/HexBox/MemoryDataBlock.cs b/GUI.NET/Debugger/HexBox/MemoryDataBlock.cs new file mode 100644 index 00000000..97292670 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/MemoryDataBlock.cs @@ -0,0 +1,87 @@ +using System; + +namespace Be.Windows.Forms +{ + internal sealed class MemoryDataBlock : DataBlock + { + byte[] _data; + + public MemoryDataBlock(byte data) + { + _data = new byte[] { data }; + } + + public MemoryDataBlock(byte[] data) + { + if (data == null) + { + throw new ArgumentNullException("data"); + } + + _data = (byte[])data.Clone(); + } + + public override long Length + { + get + { + return _data.LongLength; + } + } + + public byte[] Data + { + get + { + return _data; + } + } + + public void AddByteToEnd(byte value) + { + byte[] newData = new byte[_data.LongLength + 1]; + _data.CopyTo(newData, 0); + newData[newData.LongLength - 1] = value; + _data = newData; + } + + public void AddByteToStart(byte value) + { + byte[] newData = new byte[_data.LongLength + 1]; + newData[0] = value; + _data.CopyTo(newData, 1); + _data = newData; + } + + public void InsertBytes(long position, byte[] data) + { + byte[] newData = new byte[_data.LongLength + data.LongLength]; + if (position > 0) + { + Array.Copy(_data, 0, newData, 0, position); + } + Array.Copy(data, 0, newData, position, data.LongLength); + if (position < _data.LongLength) + { + Array.Copy(_data, position, newData, position + data.LongLength, _data.LongLength - position); + } + _data = newData; + } + + public override void RemoveBytes(long position, long count) + { + byte[] newData = new byte[_data.LongLength - count]; + + if (position > 0) + { + Array.Copy(_data, 0, newData, 0, position); + } + if (position + count < _data.LongLength) + { + Array.Copy(_data, position + count, newData, position, newData.LongLength - position); + } + + _data = newData; + } + } +} diff --git a/GUI.NET/Debugger/HexBox/NativeMethods.cs b/GUI.NET/Debugger/HexBox/NativeMethods.cs new file mode 100644 index 00000000..b73f82cc --- /dev/null +++ b/GUI.NET/Debugger/HexBox/NativeMethods.cs @@ -0,0 +1,14 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace Be.Windows.Forms +{ + internal static class NativeMethods + { + // Key definitions + public const int WM_KEYDOWN = 0x100; + public const int WM_KEYUP = 0x101; + public const int WM_CHAR = 0x102; + } +} diff --git a/GUI.NET/Debugger/HexBox/StaticByteProvider.cs b/GUI.NET/Debugger/HexBox/StaticByteProvider.cs new file mode 100644 index 00000000..31843048 --- /dev/null +++ b/GUI.NET/Debugger/HexBox/StaticByteProvider.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Be.Windows.Forms +{ + public class StaticByteProvider : DynamicByteProvider + { + public StaticByteProvider(byte[] data) : base(data) + { + } + + public override bool SupportsInsertBytes() + { + return false; + } + + public override bool SupportsDeleteBytes() + { + return false; + } + } +} diff --git a/GUI.NET/Debugger/HexBox/Util.cs b/GUI.NET/Debugger/HexBox/Util.cs new file mode 100644 index 00000000..3e6207fd --- /dev/null +++ b/GUI.NET/Debugger/HexBox/Util.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Be.Windows.Forms +{ + static class Util + { + /// + /// Contains true, if we are in design mode of Visual Studio + /// + private static bool _designMode; + + /// + /// Initializes an instance of Util class + /// + static Util() + { + // design mode is true if host process is: Visual Studio, Visual Studio Express Versions (C#, VB, C++) or SharpDevelop + var designerHosts = new List() { "devenv", "vcsexpress", "vbexpress", "vcexpress", "sharpdevelop" }; + using (var process = System.Diagnostics.Process.GetCurrentProcess()) + { + var processName = process.ProcessName.ToLower(); + _designMode = designerHosts.Contains(processName); + } + } + + /// + /// Gets true, if we are in design mode of Visual Studio + /// + /// + /// In Visual Studio 2008 SP1 the designer is crashing sometimes on windows forms. + /// The DesignMode property of Control class is buggy and cannot be used, so use our own implementation instead. + /// + public static bool DesignMode + { + get + { + return _designMode; + } + } + + } +} diff --git a/GUI.NET/Debugger/frmMemoryViewer.Designer.cs b/GUI.NET/Debugger/frmMemoryViewer.Designer.cs index 97eb0b00..ddc37a57 100644 --- a/GUI.NET/Debugger/frmMemoryViewer.Designer.cs +++ b/GUI.NET/Debugger/frmMemoryViewer.Designer.cs @@ -81,7 +81,7 @@ this.ctrlHexViewer.Name = "ctrlHexViewer"; this.ctrlHexViewer.Size = new System.Drawing.Size(665, 346); this.ctrlHexViewer.TabIndex = 0; - this.ctrlHexViewer.ColumnCountChanged += new System.EventHandler(this.ctrlHexViewer_ColumnCountChanged); + this.ctrlHexViewer.RequiredWidthChanged += new System.EventHandler(this.ctrlHexViewer_RequiredWidthChanged); // // flowLayoutPanel1 // diff --git a/GUI.NET/Debugger/frmMemoryViewer.cs b/GUI.NET/Debugger/frmMemoryViewer.cs index 947e2e5f..433946f3 100644 --- a/GUI.NET/Debugger/frmMemoryViewer.cs +++ b/GUI.NET/Debugger/frmMemoryViewer.cs @@ -29,12 +29,11 @@ namespace Mesen.GUI.Debugger base.OnLoad(e); this.mnuAutoRefresh.Checked = ConfigManager.Config.DebugInfo.RamAutoRefresh; - this.ctrlHexViewer.FontSize = ConfigManager.Config.DebugInfo.RamFontSize; + this.ctrlHexViewer.SetFontSize((int)ConfigManager.Config.DebugInfo.RamFontSize); UpdateImportButton(); this.cboMemoryType.SelectedIndex = 0; - this.Size = new Size(this.ctrlHexViewer.IdealWidth, this.Height); _notifListener = new InteropEmu.NotificationListener(); _notifListener.OnNotification += _notifListener_OnNotification; @@ -52,7 +51,6 @@ namespace Mesen.GUI.Debugger this._memoryType = (DebugMemoryType)this.cboMemoryType.SelectedIndex; this.UpdateImportButton(); this.RefreshData(); - this.ctrlHexViewer.ScrollToTop(); } private void mnuRefresh_Click(object sender, EventArgs e) @@ -60,20 +58,17 @@ namespace Mesen.GUI.Debugger this.RefreshData(); } + int _previousIndex = -1; private void RefreshData() { if(this.tabMain.SelectedTab == this.tpgAccessCounters) { this.ctrlMemoryAccessCounters.RefreshData(); } else if(this.tabMain.SelectedTab == this.tpgMemoryViewer) { - this.ctrlHexViewer.Data = InteropEmu.DebugGetMemoryState((DebugMemoryType)this.cboMemoryType.SelectedIndex); + this.ctrlHexViewer.SetData(InteropEmu.DebugGetMemoryState((DebugMemoryType)this.cboMemoryType.SelectedIndex), this.cboMemoryType.SelectedIndex != _previousIndex); + this._previousIndex = this.cboMemoryType.SelectedIndex; } } - private void ctrlHexViewer_ColumnCountChanged(object sender, EventArgs e) - { - this.Size = new Size(this.ctrlHexViewer.IdealWidth, this.Height); - } - private void mnuFind_Click(object sender, EventArgs e) { this.ctrlHexViewer.OpenSearchBox(); @@ -96,19 +91,19 @@ namespace Mesen.GUI.Debugger private void mnuIncreaseFontSize_Click(object sender, EventArgs e) { - this.ctrlHexViewer.FontSize++; + this.ctrlHexViewer.IncreaseFontSize(); this.UpdateConfig(); } private void mnuDecreaseFontSize_Click(object sender, EventArgs e) { - this.ctrlHexViewer.FontSize--; + this.ctrlHexViewer.DecreaseFontSize(); this.UpdateConfig(); } private void mnuResetFontSize_Click(object sender, EventArgs e) { - this.ctrlHexViewer.FontSize = BaseControl.DefaultFontSize; + this.ctrlHexViewer.ResetFontSize(); this.UpdateConfig(); } @@ -120,7 +115,7 @@ namespace Mesen.GUI.Debugger private void UpdateConfig() { ConfigManager.Config.DebugInfo.RamAutoRefresh = this.mnuAutoRefresh.Checked; - ConfigManager.Config.DebugInfo.RamFontSize = this.ctrlHexViewer.FontSize; + ConfigManager.Config.DebugInfo.RamFontSize = this.ctrlHexViewer.HexFont.Size; ConfigManager.ApplyChanges(); } @@ -166,7 +161,7 @@ namespace Mesen.GUI.Debugger sfd.InitialDirectory = ConfigManager.DebuggerFolder; sfd.FileName = InteropEmu.GetRomInfo().GetRomName() + " - " + cboMemoryType.SelectedItem.ToString() + ".dmp"; if(sfd.ShowDialog() == DialogResult.OK) { - File.WriteAllBytes(sfd.FileName, this.ctrlHexViewer.Data); + File.WriteAllBytes(sfd.FileName, this.ctrlHexViewer.GetData()); } } @@ -185,5 +180,10 @@ namespace Mesen.GUI.Debugger this.ctrlProfiler.RefreshData(); } } + + private void ctrlHexViewer_RequiredWidthChanged(object sender, EventArgs e) + { + this.Size = new Size(this.ctrlHexViewer.RequiredWidth + this.Width - this.ctrlHexViewer.Width + 30, this.Height); + } } } diff --git a/GUI.NET/GUI.NET.csproj b/GUI.NET/GUI.NET.csproj index 5b498c7b..1eea3cea 100644 --- a/GUI.NET/GUI.NET.csproj +++ b/GUI.NET/GUI.NET.csproj @@ -434,6 +434,28 @@ frmTraceLogger.cs + + Component + + + + + + + + + + + + + Component + + + + + + + @@ -719,6 +741,7 @@ frmTraceLogger.cs + BaseConfigForm.cs