Debugger: Better tooltip for mouseover on labels/addresses

This commit is contained in:
Souryo 2016-11-24 19:47:59 -05:00
parent 0d535e66a1
commit 271e46b02b
10 changed files with 389 additions and 24 deletions

View file

@ -59,7 +59,7 @@
this.mnuGoToLocation,
this.mnuAddToWatch});
this.contextMenuCode.Name = "contextMenuWatch";
this.contextMenuCode.Size = new System.Drawing.Size(259, 170);
this.contextMenuCode.Size = new System.Drawing.Size(259, 148);
this.contextMenuCode.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuCode_Opening);
//
// mnuShowNextStatement
@ -131,6 +131,7 @@
this.ctrlCodeViewer.ShowLineNumberNotes = false;
this.ctrlCodeViewer.Size = new System.Drawing.Size(379, 218);
this.ctrlCodeViewer.TabIndex = 1;
this.ctrlCodeViewer.ScrollPositionChanged += new System.EventHandler(this.ctrlCodeViewer_ScrollPositionChanged);
this.ctrlCodeViewer.MouseUp += new System.Windows.Forms.MouseEventHandler(this.ctrlCodeViewer_MouseUp);
this.ctrlCodeViewer.MouseMove += new System.Windows.Forms.MouseEventHandler(this.ctrlCodeViewer_MouseMove);
this.ctrlCodeViewer.MouseDown += new System.Windows.Forms.MouseEventHandler(this.ctrlCodeViewer_MouseDown);

View file

@ -15,10 +15,13 @@ namespace Mesen.GUI.Debugger
public partial class ctrlDebuggerCode : BaseScrollableTextboxUserControl
{
public delegate void AddressEventHandler(AddressEventArgs args);
public event AddressEventHandler OnWatchAdded;
public delegate void WatchEventHandler(WatchEventArgs args);
public event WatchEventHandler OnWatchAdded;
public event AddressEventHandler OnSetNextStatement;
private DebugViewInfo _config;
private frmCodeTooltip _codeTooltip = null;
public ctrlDebuggerCode()
{
InitializeComponent();
@ -164,6 +167,30 @@ namespace Mesen.GUI.Debugger
#region Events
private Point _previousLocation;
private bool _preventCloseTooltip = false;
private string _hoverLastWord = "";
private void ShowTooltip(string word, Dictionary<string, string> values)
{
if(_hoverLastWord != word || _codeTooltip == null) {
if(!_preventCloseTooltip && _codeTooltip != null) {
_codeTooltip.Close();
_codeTooltip = null;
}
_codeTooltip = new frmCodeTooltip(values);
_codeTooltip.Width = 0;
_codeTooltip.Height = 0;
_codeTooltip.Visible = false;
_codeTooltip.Show(this);
_codeTooltip.Visible = true;
}
_codeTooltip.Left = Cursor.Position.X + 10;
_codeTooltip.Top = Cursor.Position.Y + 10;
_preventCloseTooltip = true;
_hoverLastWord = word;
}
private void ctrlCodeViewer_MouseMove(object sender, MouseEventArgs e)
{
if(e.Location.X < this.ctrlCodeViewer.CodeMargin / 5) {
@ -173,28 +200,42 @@ namespace Mesen.GUI.Debugger
}
if(_previousLocation != e.Location) {
if(!_preventCloseTooltip && _codeTooltip != null) {
_codeTooltip.Close();
_codeTooltip = null;
}
_preventCloseTooltip = false;
string word = GetWordUnderLocation(e.Location);
if(word.StartsWith("$")) {
try {
UInt32 address = UInt32.Parse(word.Substring(1), System.Globalization.NumberStyles.AllowHexSpecifier);
Byte memoryValue = InteropEmu.DebugGetMemoryValue(address);
string valueText = "$" + memoryValue.ToString("X");
toolTip.Show(valueText, ctrlCodeViewer, e.Location.X + 5, e.Location.Y - 20, 3000);
var values = new Dictionary<string, string>() {
{ "Address", "$" + address.ToString("X4") },
{ "Value", "$" + memoryValue.ToString("X2") },
};
ShowTooltip(word, values);
} catch { }
} else {
CodeLabel label = LabelManager.GetLabel(word);
if(label == null) {
toolTip.Hide(ctrlCodeViewer);
} else {
if(label != null) {
Int32 relativeAddress = InteropEmu.DebugGetRelativeAddress(label.Address, label.AddressType);
Int32 memoryValue = relativeAddress >= 0 ? InteropEmu.DebugGetMemoryValue((UInt32)relativeAddress) : -1;
toolTip.Show(
"Label: " + label.Label + Environment.NewLine +
"Address: $" + InteropEmu.DebugGetRelativeAddress(label.Address, label.AddressType).ToString("X4") + Environment.NewLine +
"Value: " + (memoryValue >= 0 ? ("$" + memoryValue.ToString("X2")) : "n/a") + Environment.NewLine +
"Comment: " + (label.Comment.Contains(Environment.NewLine) ? (Environment.NewLine + label.Comment) : label.Comment)
, ctrlCodeViewer, e.Location.X + 5, e.Location.Y - 60 - label.Comment.Split('\n').Length * 14, 3000);
var values = new Dictionary<string, string>() {
{ "Label", label.Label },
{ "Address", "$" + InteropEmu.DebugGetRelativeAddress(label.Address, label.AddressType).ToString("X4") },
{ "Value", (memoryValue >= 0 ? ("$" + memoryValue.ToString("X2")) : "n/a") },
};
if(!string.IsNullOrWhiteSpace(label.Comment)) {
values["Comment"] = label.Comment;
}
ShowTooltip(word, values);
}
}
_previousLocation = e.Location;
@ -202,11 +243,27 @@ namespace Mesen.GUI.Debugger
}
UInt32 _lastClickedAddress = UInt32.MaxValue;
string _newWatchValue = string.Empty;
private void ctrlCodeViewer_MouseUp(object sender, MouseEventArgs e)
{
string word = GetWordUnderLocation(e.Location);
if(word.StartsWith("$")) {
_lastClickedAddress = UInt32.Parse(word.Substring(1), System.Globalization.NumberStyles.AllowHexSpecifier);
if(word.StartsWith("$") || LabelManager.GetLabel(word) != null) {
if(word.StartsWith("$")) {
_lastClickedAddress = UInt32.Parse(word.Substring(1), System.Globalization.NumberStyles.AllowHexSpecifier);
_newWatchValue = "[$" + _lastClickedAddress.ToString("X") + "]";
} else {
_lastClickedAddress = (UInt32)InteropEmu.DebugGetRelativeAddress(LabelManager.GetLabel(word).Address, LabelManager.GetLabel(word).AddressType);
_newWatchValue = "[" + word + "]";
}
if(e.Button == MouseButtons.Left) {
if(ModifierKeys.HasFlag(Keys.Control)) {
GoToLocation();
} else if(ModifierKeys.HasFlag(Keys.Shift)) {
AddWatch();
}
}
mnuGoToLocation.Enabled = true;
mnuGoToLocation.Text = "Go to Location (" + word + ")";
@ -223,6 +280,11 @@ namespace Mesen.GUI.Debugger
Breakpoint _lineBreakpoint = null;
private void ctrlCodeViewer_MouseDown(object sender, MouseEventArgs e)
{
if(_codeTooltip != null) {
_codeTooltip.Close();
_codeTooltip = null;
}
int address = ctrlCodeViewer.GetLineNumberAtPosition(e.Y);
_lineBreakpoint = BreakpointManager.GetMatchingBreakpoint(address);
@ -239,7 +301,15 @@ namespace Mesen.GUI.Debugger
}
}
}
private void ctrlCodeViewer_ScrollPositionChanged(object sender, EventArgs e)
{
if(_codeTooltip != null) {
_codeTooltip.Close();
_codeTooltip = null;
}
}
private void ctrlCodeViewer_MouseDoubleClick(object sender, MouseEventArgs e)
{
int relativeAddress = ctrlCodeViewer.GetLineNumberAtPosition(e.Y);
@ -313,14 +383,24 @@ namespace Mesen.GUI.Debugger
}
private void mnuGoToLocation_Click(object sender, EventArgs e)
{
GoToLocation();
}
private void GoToLocation()
{
this.ctrlCodeViewer.ScrollToLineNumber((int)_lastClickedAddress);
}
private void mnuAddToWatch_Click(object sender, EventArgs e)
{
AddWatch();
}
private void AddWatch()
{
if(this.OnWatchAdded != null) {
this.OnWatchAdded(new AddressEventArgs() { Address = _lastClickedAddress});
this.OnWatchAdded(new WatchEventArgs() { WatchValue = _newWatchValue });
}
}
@ -341,6 +421,11 @@ namespace Mesen.GUI.Debugger
#endregion
}
public class WatchEventArgs : EventArgs
{
public string WatchValue { get; set; }
}
public class AddressEventArgs : EventArgs
{
public UInt32 Address { get; set; }

View file

@ -12,6 +12,8 @@ namespace Mesen.GUI.Debugger
{
public partial class ctrlScrollableTextbox : UserControl
{
public event EventHandler ScrollPositionChanged;
public new event MouseEventHandler MouseUp
{
add { this.ctrlTextbox.MouseUp += value; }
@ -80,6 +82,8 @@ namespace Mesen.GUI.Debugger
this.vScrollBar.Value = this.ctrlTextbox.ScrollPosition;
this.hScrollBar.Value = this.ctrlTextbox.HorizontalScrollPosition;
UpdateHorizontalScrollbar();
ScrollPositionChanged?.Invoke(null, null);
}
private void UpdateHorizontalScrollbar()

View file

@ -128,10 +128,9 @@ namespace Mesen.GUI.Debugger
}
}
public void AddWatch(UInt32 address)
public void AddWatch(string watchValue)
{
ListViewItem item = lstWatch.Items.Insert(lstWatch.Items.Count - 1, "[$" + address.ToString("X") + "]");
item.Tag = address;
ListViewItem item = lstWatch.Items.Insert(lstWatch.Items.Count - 1, watchValue);
UpdateWatch();
}

View file

@ -0,0 +1,91 @@
namespace Mesen.GUI.Debugger
{
partial class frmCodeTooltip
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if(disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.panel1 = new System.Windows.Forms.Panel();
this.tlpMain = new System.Windows.Forms.TableLayoutPanel();
this.panel1.SuspendLayout();
this.SuspendLayout();
//
// panel1
//
this.panel1.AutoSize = true;
this.panel1.BackColor = System.Drawing.SystemColors.Info;
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel1.Controls.Add(this.tlpMain);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(10, 10);
this.panel1.TabIndex = 0;
//
// tlpMain
//
this.tlpMain.AutoSize = true;
this.tlpMain.ColumnCount = 2;
this.tlpMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tlpMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpMain.Dock = System.Windows.Forms.DockStyle.Fill;
this.tlpMain.Location = new System.Drawing.Point(0, 0);
this.tlpMain.Name = "tlpMain";
this.tlpMain.RowCount = 2;
this.tlpMain.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tlpMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tlpMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
this.tlpMain.Size = new System.Drawing.Size(8, 8);
this.tlpMain.TabIndex = 0;
//
// frmCodeTooltip
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSize = true;
this.ClientSize = new System.Drawing.Size(10, 10);
this.ControlBox = false;
this.Controls.Add(this.panel1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "frmCodeTooltip";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
this.Text = "frmCodeTooltip";
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.TableLayoutPanel tlpMain;
}
}

View file

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Mesen.GUI.Debugger
{
public partial class frmCodeTooltip : Form
{
private Dictionary<string, string> _values;
protected override bool ShowWithoutActivation
{
get { return true; }
}
public frmCodeTooltip(Dictionary<string, string> values)
{
_values = values;
InitializeComponent();
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
int i = 0;
foreach(KeyValuePair<string, string> kvp in _values) {
tlpMain.RowStyles.Insert(1, new RowStyle());
Label lbl = new Label();
lbl.Margin = new Padding(2);
lbl.Text = kvp.Key + ":";
lbl.Font = new Font(lbl.Font, FontStyle.Bold);
lbl.AutoSize = true;
tlpMain.SetRow(lbl, i);
tlpMain.SetColumn(lbl, 0);
tlpMain.Controls.Add(lbl);
lbl = new Label();
lbl.Margin = new Padding(2);
lbl.AutoSize = true;
lbl.Text = kvp.Value;
tlpMain.SetRow(lbl, i);
tlpMain.SetColumn(lbl, 1);
tlpMain.Controls.Add(lbl);
i++;
}
}
}
}

View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -214,7 +214,7 @@
this.ctrlDebuggerCode.Name = "ctrlDebuggerCode";
this.ctrlDebuggerCode.Size = new System.Drawing.Size(510, 381);
this.ctrlDebuggerCode.TabIndex = 2;
this.ctrlDebuggerCode.OnWatchAdded += new Mesen.GUI.Debugger.ctrlDebuggerCode.AddressEventHandler(this.ctrlDebuggerCode_OnWatchAdded);
this.ctrlDebuggerCode.OnWatchAdded += new Mesen.GUI.Debugger.ctrlDebuggerCode.WatchEventHandler(this.ctrlDebuggerCode_OnWatchAdded);
this.ctrlDebuggerCode.OnSetNextStatement += new Mesen.GUI.Debugger.ctrlDebuggerCode.AddressEventHandler(this.ctrlDebuggerCode_OnSetNextStatement);
this.ctrlDebuggerCode.Enter += new System.EventHandler(this.ctrlDebuggerCode_Enter);
//
@ -236,7 +236,7 @@
this.ctrlDebuggerCodeSplit.Size = new System.Drawing.Size(1, 381);
this.ctrlDebuggerCodeSplit.TabIndex = 4;
this.ctrlDebuggerCodeSplit.Visible = false;
this.ctrlDebuggerCodeSplit.OnWatchAdded += new Mesen.GUI.Debugger.ctrlDebuggerCode.AddressEventHandler(this.ctrlDebuggerCode_OnWatchAdded);
this.ctrlDebuggerCodeSplit.OnWatchAdded += new Mesen.GUI.Debugger.ctrlDebuggerCode.WatchEventHandler(this.ctrlDebuggerCode_OnWatchAdded);
this.ctrlDebuggerCodeSplit.OnSetNextStatement += new Mesen.GUI.Debugger.ctrlDebuggerCode.AddressEventHandler(this.ctrlDebuggerCode_OnSetNextStatement);
this.ctrlDebuggerCodeSplit.Enter += new System.EventHandler(this.ctrlDebuggerCodeSplit_Enter);
//

View file

@ -307,9 +307,9 @@ namespace Mesen.GUI.Debugger
InteropEmu.DebugPpuStep(89341);
}
private void ctrlDebuggerCode_OnWatchAdded(AddressEventArgs args)
private void ctrlDebuggerCode_OnWatchAdded(WatchEventArgs args)
{
this.ctrlWatch.AddWatch(args.Address);
this.ctrlWatch.AddWatch(args.WatchValue);
}
private void ctrlDebuggerCode_OnSetNextStatement(AddressEventArgs args)

View file

@ -349,6 +349,12 @@
<Compile Include="Debugger\Controls\ctrlWatch.Designer.cs">
<DependentUpon>ctrlWatch.cs</DependentUpon>
</Compile>
<Compile Include="Debugger\frmCodeTooltip.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Debugger\frmCodeTooltip.Designer.cs">
<DependentUpon>frmCodeTooltip.cs</DependentUpon>
</Compile>
<Compile Include="Debugger\frmEditLabel.cs">
<SubType>Form</SubType>
</Compile>
@ -628,6 +634,9 @@
<EmbeddedResource Include="Debugger\Controls\ctrlWatch.resx">
<DependentUpon>ctrlWatch.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Debugger\frmCodeTooltip.resx">
<DependentUpon>frmCodeTooltip.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Debugger\frmEditLabel.resx">
<DependentUpon>frmEditLabel.cs</DependentUpon>
</EmbeddedResource>