Skip to content

Commit

Permalink
Feature: track the current selection in the ImageMapView (#1232)
Browse files Browse the repository at this point in the history
  • Loading branch information
uxmal committed Jan 31, 2023
1 parent 5b57d72 commit 97f596b
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 45 deletions.
22 changes: 20 additions & 2 deletions src/Core/ImageSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,12 @@ public ImageSegment(string name, MemoryArea mem, AccessMode access)
public ulong FileOffset { get; set; }

/// <summary>
/// Size of the segment address space (content may be smaller)
/// Size of the segment address space in address units.
/// </summary>
/// <remarks>
/// The actual content loaded from disk may be smaller, or even zero in the case of
/// .bss segments.
/// </remarks>
public uint Size { get; set; }

public uint ContentSize { get { return (ctSize != 0) ? ctSize : Size; } set { ctSize = value; } }
Expand Down Expand Up @@ -206,12 +210,26 @@ public EndianImageReader CreateImageReader(IProcessorArchitecture arch)
return arch.CreateImageReader(this.MemoryArea, offsetBegin, offsetEnd);
}

/// <summary>
/// Determine whether the specified <paramref name="address" />
/// is located inside the segment.
/// </summary>
/// <param name="address"><see cref="Address"/> to test.</param>
/// <returns>True of the address is inside the segment, false if not.
/// </returns>
public bool IsInRange(Address addr)
{
return IsInRange(addr.ToLinear());
}

public virtual bool IsInRange(ulong linearAddress)
/// <summary>
/// Determine whether the specified <paramref name="linearAddress" />
/// is located inside the segment.
/// </summary>
/// <param name="linearAddress">Linear address to test.</param>
/// <returns>True of the address is inside the segment, false if not.
/// </returns>
public bool IsInRange(ulong linearAddress)
{
ulong linItem = this.Address.ToLinear();
return (linItem <= linearAddress && linearAddress < linItem + Size);
Expand Down
136 changes: 98 additions & 38 deletions src/UserInterfaces/WindowsForms/Controls/ImageMapView.Painter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,26 @@
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;

namespace Reko.UserInterfaces.WindowsForms.Controls
{
public partial class ImageMapView
{
public class Painter
{
private SegmentMap segmentMap;
private ImageMap imageMap;
private const int CxMinSelection = 3;
private static readonly Color FocusedSelectionBgColor = Color.FromArgb(0xFF, 0xFF, 0x30);
private static readonly Color SelectionBgColor = Color.FromArgb(0x80, 0x80, 0x30);

private readonly SegmentMap segmentMap;
private readonly ImageMap imageMap;
private readonly long granularity;
private readonly Address addrMin;
private readonly Address addrMax;
public Rectangle rcClient;
public Rectangle rcBody;
public List<SegmentLayout> segLayouts;
private long granularity;
private Brush brCode;
private Brush brBack;
private Brush brData;
Expand All @@ -49,15 +56,17 @@ public Painter(ImageMapView mapView)
this.imageMap = mapView.ImageMap;
this.segmentMap = mapView.SegmentMap;
this.granularity = mapView.granularity;

var b = mapView.SelectedRange.Begin;
var e = mapView.SelectedRange.End;
this.addrMin = Address.Min(b, e);
this.addrMax = Address.Max(b, e);
segLayouts = new List<SegmentLayout>();
long x = 0;
long cx = 0;
if (imageMap != null && granularity > 0)
{
foreach (var segment in segmentMap.Segments.Values)
{
cx = (segment.Size + granularity - 1) / granularity;
long cx = (segment.Size + granularity - 1) / granularity;
segLayouts.Add(new SegmentLayout
{
Segment = segment,
Expand All @@ -84,9 +93,8 @@ public void Paint(Graphics g, ImageMapView mapView)
this.brData = new SolidBrush(Color.LightBlue);
try
{
RenderSelectionBar(g, mapView.Focused);
RenderScrollControls(g, mapView);
RenderBody(g, mapView.cxOffset);
RenderBody(g, mapView.cxOffset, mapView.Focused);
}
finally
{
Expand All @@ -96,15 +104,23 @@ public void Paint(Graphics g, ImageMapView mapView)
}
}

public void RenderBody(Graphics g, long cxOffset)
public void RenderBody(Graphics g, long cxOffset, bool focused)
{
Font fontSeg = new Font("Arial", 7);
using Font fontSeg = new Font("Arial", 7);
Rectangle rcPaint = rcBody;
rcPaint.Width = 0;
Brush brNew = null;
long? xSelectionBegin = default;
long? xSelectionEnd = default;
var transparentOverlay = Color.FromArgb(0x60, SystemColors.Highlight);
using Brush brOverlay = new SolidBrush(transparentOverlay);
//Debug.Print("== RenderBody ==");
foreach (var sl in segLayouts)
{
if (sl.Segment.IsInRange(addrMin))
xSelectionBegin = sl.AddressToX(addrMin, granularity);
if (sl.Segment.IsInRange(addrMax))
xSelectionEnd = sl.AddressToX(addrMax, granularity);
if (sl.X - cxOffset < rcBody.Width && sl.X + sl.CxWidth - cxOffset >= 0)
{
//Debug.Print("---- Segment {0} ----", sl.Segment.Name);
Expand All @@ -123,28 +139,47 @@ public void RenderBody(Graphics g, long cxOffset)
{
rcPaint.Width = x + CxScroll - rcPaint.X;
g.FillRectangle(brOld, rcPaint);
//Debug.Print("Paint: {0} {1}", rcPaint, brOld);
brOld = brNew;
rcPaint.X = x + CxScroll;
}
else
{
brOld = brNew;
}
brOld = brNew;
}
}
if (brNew != null)
{
rcPaint.Width = xMax + CxScroll - rcPaint.X;
g.FillRectangle(brNew, rcPaint);
}

RenderSelectionOverlay(g, xSelectionBegin, xSelectionEnd, xMin, xMax, rcBody.Y, rcBody.Height, brOverlay);
RenderSegmentName(g, sl, xMin, xMax, fontSeg);
RenderSegmentSeparator(g, CxScroll + sl.X + sl.CxWidth - cxOffset);
}
if (xSelectionBegin.HasValue && xSelectionEnd.HasValue)
{
RenderSelectionBar(g, xSelectionBegin.Value, xSelectionEnd.Value, focused);
}
}
fontSeg.Dispose();
//Debug.Print("== RenderBody ==");
}

private void RenderSelectionOverlay(
Graphics g,
long? xSelectionBegin,
long? xSelectionEnd,
int xMin, int xMax,
int y, int height,
Brush brOverlay)
{
if (!xSelectionBegin.HasValue || !xSelectionEnd.HasValue)
return;
var xBegin = (int) xSelectionBegin.Value;
var xEnd = (int) xSelectionEnd.Value;
Debug.WriteLine($"[{xBegin}-{xEnd}): min: {xMin}, max: {xMax}");
if (xBegin >= xMax || xEnd < xMin)
return;
xBegin = Math.Max(xMin, xBegin);
xEnd = Math.Min(xMax, xEnd);
(xBegin, xEnd) = NormalizeSelection(xBegin, xEnd);
Debug.WriteLine($"Painting overlay: ({xBegin}, {xEnd-xBegin}, {y}, {height})");
g.FillRectangle(brOverlay, CxScroll + xBegin, y, xEnd - xBegin, height);
}

private void RenderSegmentName(Graphics g, SegmentLayout sl, int xMin, int xMax, Font font)
Expand All @@ -154,32 +189,24 @@ private void RenderSegmentName(Graphics g, SegmentLayout sl, int xMin, int xMax,
g.DrawString(sl.Segment.Name, font, SystemBrushes.ControlText, rcText);
}

/// <summary>
/// Returns the layout of the segment that contains the given
/// address <paramref name="addr"/>. A missing address yields
/// a null value.
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public SegmentLayout GetSegment(Address addr)
{
if (addr == null)
return null;
return segLayouts.FirstOrDefault(s => s.Segment.IsInRange(addr));
}

private void RenderSegmentSeparator(Graphics g, long x)
{
var rc = new Rectangle((int)x, rcBody.Top, CxSegmentBorder, rcBody.Height);
g.FillRectangle(Brushes.Black, rc);
g.FillRectangle(SystemBrushes.ControlText, rc);
//Debug.Print("Separator: {0}", rc);
}

private void RenderSelectionBar(Graphics g, bool focused)
private void RenderSelectionBar(Graphics g, long xSelectionBegin, long xSelectionEnd, bool focused)
{
Brush br = new SolidBrush(focused ? Color.FromArgb(0xFF, 0xFF, 0x30) : Color.FromArgb(0x80, 0x80, 0x30));
g.FillRectangle(br, new Rectangle(0, 1 + rcClient.Height - CySelection, rcClient.Width, CySelection - 1));
br.Dispose();
var yTop = 1 + rcClient.Height - CySelection;
var cyHeight = CySelection - 1;
using Brush brBg = new SolidBrush(focused ? FocusedSelectionBgColor : SelectionBgColor);
g.FillRectangle(brBg, new Rectangle(0, yTop, rcClient.Width, cyHeight));

var (xSelBegin, xSelEnd) = NormalizeSelection((int) xSelectionBegin, (int) xSelectionEnd);
var cxSelection = xSelEnd - xSelBegin;
var brSel = focused ? SystemBrushes.Highlight : SystemBrushes.InactiveCaption;
g.FillRectangle(brSel, CxScroll + xSelBegin, yTop, cxSelection, cyHeight);
}

private void RenderScrollControls(Graphics g, ImageMapView mapView)
Expand All @@ -203,6 +230,39 @@ private void RenderScrollControls(Graphics g, ImageMapView mapView)
(mapView.scrollButton == ScrollButton.Left ? ButtonState.Pushed : ButtonState.Normal) | baseState);
}

/// <summary>
/// Returns the layout of the segment that contains the given
/// address <paramref name="addr"/>. A missing address yields
/// a null value.
/// </summary>
/// <param name="addr"></param>
/// <returns></returns>
public SegmentLayout GetSegment(Address addr)
{
if (addr is null)
return null;
return segLayouts.FirstOrDefault(s => s.Segment.IsInRange(addr));
}

/// <summary>
/// Normalize the selection so that it is at least <see cref="CxMinSelection"/> pixels
/// wide.
/// </summary>
/// <param name="xBegin">Start X coordinate of selection.</param>
/// <param name="xEnd">End X coordinate of selection.</param>
/// <returns>A tuple consisting of <paramref name="xBegin"/> and <paramref name="xEnd"/>,
/// possibly adjusted so that the difference between the values is at least <see cref="CxMinSelection"/>.
/// </returns>
private (int, int) NormalizeSelection(int xBegin, int xEnd)
{
if (xEnd - xBegin < CxMinSelection)
{
xBegin -= CxMinSelection / 2;
xEnd = xBegin + CxMinSelection;
}
return (xBegin, xEnd);
}

private Brush GetColorForOffset(ImageSegment seg, long cbOffset)
{
ImageMapItem item;
Expand Down
29 changes: 27 additions & 2 deletions src/UserInterfaces/WindowsForms/Controls/ImageMapView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public class SegmentLayout
public ImageSegment Segment;
public long X;
public long CxWidth;

public long AddressToX(Address addrMin, long granularity)
{
long offset = addrMin - Segment.Address;
return X + (offset + granularity - 1) / granularity;
}
}

public long Granularity { get { return granularity; } set { BoundGranularity(value); BoundOffset(cxOffset); OnGranularityChanged(); } }
Expand Down Expand Up @@ -100,13 +106,32 @@ public Address SelectedAddress
{
get { return selectedAddress; }
set {
selectedAddress = value;
selectedAddress = value;
if (value is { })
{
SelectedRange = new AddressRange(value, value + 1);
}
SelectedAddressChanged?.Invoke(this, EventArgs.Empty);
}
}
public event EventHandler SelectedAddressChanged;
private Address selectedAddress;

[Browsable(false)]
public AddressRange SelectedRange
{
get { return this.selectedRange; }
set
{
if (value == this.selectedRange)
return;
this.selectedRange = value;
Debug.WriteLine($"{nameof(ImageMapView)}: selected range {value}");
Invalidate();
}
}
private AddressRange selectedRange = AddressRange.Empty;

public long Offset { get { return cxOffset; } set { BoundOffset(value); OnOffsetChanged(); } }
public event EventHandler OffsetChanged;
private long cxOffset;
Expand Down Expand Up @@ -267,7 +292,7 @@ private Address MapClientPositionToAddress(int x)

protected override void OnKeyDown(KeyEventArgs e)
{
Debug.Print("KeyDown: {0}", e.KeyData);
Debug.WriteLine($"{nameof(ImageMapView)} KeyDown: {e.KeyData}");
switch (e.KeyData)
{
case Keys.Add:
Expand Down
17 changes: 17 additions & 0 deletions src/UserInterfaces/WindowsForms/Controls/LowLevelView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,27 @@ public LowLevelView()
public IButton ToolbarForwardButton { get { return btnFwdWrapped; } }
public ITextBox ToolBarAddressTextbox { get { return txtAddressWrapped; } }
public IButton ToolBarGoButton { get { return btnGoWrapped; } }

/// <summary>
/// The <see cref="ImageMapView"/> across the top of the voew.
/// </summary>
public ImageMapView ImageMapView { get { return imageMapControl1; } }

/// <summary>
/// The "byte viewer"
/// </summary>
public MemoryControl MemoryView { get { return this.memCtrl; } }

/// <summary>
/// The view of disassembled bytes.
/// </summary>
public DisassemblyControl DisassemblyView { get { return this.dasmCtrl; } }

/// <summary>
/// The visualization of memory.
/// </summary>
public VisualizerControl VisualizerControl { get { return this.visualizerControl; } }

public IComboBox VisualizerList { get { return ddlVisualizerWrapped; } }
IButton INavigableControl<Address>.BackButton { get { return btnBackWrapped; } }
IButton INavigableControl<Address>.ForwardButton { get { return btnFwdWrapped; } }
Expand Down
5 changes: 3 additions & 2 deletions src/UserInterfaces/WindowsForms/Controls/TextViewPainter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,10 @@ private void DrawText(string text, bool selected)
rcTotal.Width, line.Extent.Bottom - rcTotal.Bottom);
}

Color color = selected ? SystemColors.HighlightText : fg;
if (useGdiPlus)
{
var brush = new SolidBrush(selected ? SystemColors.HighlightText : fg);
var brush = new SolidBrush(color);
graphics.DrawString(
text,
this.font,
Expand All @@ -272,7 +273,7 @@ private void DrawText(string text, bool selected)
text,
this.font,
pt,
selected ? SystemColors.HighlightText : fg,
color,
TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix);
}
rcContent.X += rcContent.Width;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ private void SelSvc_SelectionChanged(object sender, EventArgs e)
var ar = selSvc.GetSelectedComponents()
.Cast<AddressRange>()
.FirstOrDefault();
if (ar == null)
if (ar is null)
return;
if (!program.SegmentMap.TryFindSegment(ar.Begin, out var seg))
{
Expand Down
4 changes: 4 additions & 0 deletions src/UserInterfaces/WindowsForms/LowLevelViewInteractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,10 @@ private void MemoryView_SelectionChanged(object sender, SelectionChangedEventArg
if (ignoreAddressChange)
return;
this.ignoreAddressChange = true;

this.Control.ImageMapView.SelectedAddress = e.AddressRange.Begin;
this.control.ImageMapView.SelectedRange = e.AddressRange;

this.Control.DisassemblyView.SelectedObject = e.AddressRange.Begin;
this.Control.DisassemblyView.TopAddress = e.AddressRange.Begin;
this.SelectionChanged?.Invoke(this, e);
Expand Down

0 comments on commit 97f596b

Please sign in to comment.