270 lines
8.4 KiB
Java
270 lines
8.4 KiB
Java
|
package app;
|
||
|
|
||
|
// Java imports
|
||
|
import java.awt.*;
|
||
|
import java.awt.event.*;
|
||
|
import java.util.*;
|
||
|
import javax.swing.*;
|
||
|
|
||
|
// Project imports
|
||
|
import util.*;
|
||
|
|
||
|
// Memory viewer and hex editor window
|
||
|
class MemoryWindow extends ChildWindow {
|
||
|
|
||
|
// Private fields
|
||
|
private int address; // Address of top row
|
||
|
private Font font; // Display font
|
||
|
private int fontHeight; // Font line height
|
||
|
private int fontWidth; // Font maximum character width
|
||
|
|
||
|
// UI components
|
||
|
private JPanel client; // Client area
|
||
|
private ArrayList<Row> rows; // Rows of text
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
// Classes //
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// One row of output
|
||
|
private class Row {
|
||
|
JLabel address; // Address (row header)
|
||
|
JLabel[] bytes; // Hexadecimal bytes
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
// Constructors //
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Default constructor
|
||
|
MemoryWindow(MainWindow parent) {
|
||
|
super(parent, "memory.title");
|
||
|
|
||
|
// Configure instance fields
|
||
|
address = 0x00000000;
|
||
|
font = new Font(Util.fontFamily(new String[]
|
||
|
{ "Consolas", Font.MONOSPACED } ), Font.PLAIN, 14);
|
||
|
rows = new ArrayList<Row>();
|
||
|
|
||
|
// Configure client area
|
||
|
client = new JPanel(null);
|
||
|
client.addComponentListener(Util.onResize(e->onResize()));
|
||
|
client.addKeyListener(Util.onKey(e->onKeyDown(e), null));
|
||
|
client.addMouseWheelListener(e->onWheel(e));
|
||
|
client.setBackground(SystemColor.window);
|
||
|
client.setFocusable(true);
|
||
|
client.setPreferredSize(new Dimension(480, 360));
|
||
|
|
||
|
// Configure component
|
||
|
var content = new JPanel(new BorderLayout());
|
||
|
content.setBorder(new JScrollPane().getBorder());
|
||
|
content.add(client, BorderLayout.CENTER);
|
||
|
setContentPane(content);
|
||
|
setFont2(font);
|
||
|
pack();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
// Package Methods //
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Update the display
|
||
|
void refresh() {
|
||
|
|
||
|
// The element is not ready
|
||
|
if (client == null)
|
||
|
return;
|
||
|
|
||
|
// Configure working variables
|
||
|
int height = client.getHeight();
|
||
|
int count = (height + fontHeight - 1) / fontHeight;
|
||
|
var data = new byte[count * 16];
|
||
|
|
||
|
// Retrieve all visible bytes from the emulation context
|
||
|
parent.vue.read(address, data, 0, data.length);
|
||
|
|
||
|
// Update visible rows
|
||
|
for (int x = 0; x < count; x++) {
|
||
|
Row row;
|
||
|
if (x < rows.size())
|
||
|
row = rows.get(x); // Retrieve row from collection
|
||
|
else row = createRow(); // Produce a new row
|
||
|
update(row, x * fontHeight, address + x * 16, data, x * 16);
|
||
|
setVisible(row, true);
|
||
|
}
|
||
|
|
||
|
// Hide any rows that are not visible
|
||
|
for (int x = count; x < rows.size(); x++)
|
||
|
setVisible(rows.get(x), false);
|
||
|
|
||
|
// Finalize layout
|
||
|
client.revalidate();
|
||
|
client.repaint();
|
||
|
}
|
||
|
|
||
|
// Specify a new font
|
||
|
void setFont2(Font font) {
|
||
|
this.font = font;
|
||
|
|
||
|
// Configure the maximum font dimensions
|
||
|
var fontMax = new JLabel("!");
|
||
|
fontMax.setFont(font);
|
||
|
fontHeight = Math.max(1, fontMax.getPreferredSize().height);
|
||
|
fontWidth = -1;
|
||
|
for (int x = 0; x < 16; x++) {
|
||
|
fontMax.setText(Integer.toString(x, 16).toUpperCase());
|
||
|
fontWidth = Math.max(fontWidth, fontMax.getPreferredSize().width);
|
||
|
}
|
||
|
|
||
|
// Configure rows
|
||
|
for (var row : rows) {
|
||
|
row.address.setFont(font);
|
||
|
for (var label : row.bytes)
|
||
|
label.setFont(font);
|
||
|
}
|
||
|
|
||
|
onResize();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
// Event Handlers //
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Key down
|
||
|
private void onKeyDown(KeyEvent e) {
|
||
|
int code = e.getKeyCode();
|
||
|
int mods = e.getModifiersEx();
|
||
|
boolean alt = (mods & InputEvent.ALT_DOWN_MASK ) != 0;
|
||
|
boolean ctrl = (mods & InputEvent.CTRL_DOWN_MASK) != 0;
|
||
|
int tall = Math.max(1, tall(false));
|
||
|
|
||
|
// No Alt combinations
|
||
|
if (alt) return;
|
||
|
|
||
|
// Goto
|
||
|
if (ctrl && code == KeyEvent.VK_G) {
|
||
|
String addr = JOptionPane.showInputDialog(
|
||
|
this, "Goto:", "Goto", JOptionPane.PLAIN_MESSAGE);
|
||
|
if (addr != null && addr.trim().length() != 0) {
|
||
|
try { setAddress((int) Long.parseLong(addr, 16)); }
|
||
|
catch (Exception x) { }
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Seek
|
||
|
switch (code) {
|
||
|
case KeyEvent.VK_UP : setAddress(address - 16); break;
|
||
|
case KeyEvent.VK_DOWN : setAddress(address + 16); break;
|
||
|
case KeyEvent.VK_PAGE_UP : setAddress(address - tall * 16); break;
|
||
|
case KeyEvent.VK_PAGE_DOWN: setAddress(address + tall * 16); break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Client resize
|
||
|
private void onResize() {
|
||
|
refresh();
|
||
|
}
|
||
|
|
||
|
// Mouse wheel
|
||
|
private void onWheel(MouseWheelEvent e) {
|
||
|
int amount = e.getUnitsToScroll();
|
||
|
int mods = e.getModifiersEx();
|
||
|
boolean alt = (mods & InputEvent.ALT_DOWN_MASK ) != 0;
|
||
|
boolean ctrl = (mods & InputEvent.CTRL_DOWN_MASK) != 0;
|
||
|
|
||
|
// No Alt or Ctrl combinations
|
||
|
if (amount == 0 || alt || ctrl)
|
||
|
return;
|
||
|
|
||
|
// Seek
|
||
|
setAddress(address + 16 * amount);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
// Private Methods //
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Add a new row of output
|
||
|
private Row createRow() {
|
||
|
var row = new Row();
|
||
|
|
||
|
// Address label
|
||
|
row.address = new JLabel();
|
||
|
row.address.setFont(font);
|
||
|
row.address.setForeground(SystemColor.windowText);
|
||
|
row.address.setVisible(false);
|
||
|
client.add(row.address);
|
||
|
|
||
|
// Byte labels
|
||
|
row.bytes = new JLabel[16];
|
||
|
for (int x = 0; x < row.bytes.length; x++) {
|
||
|
var label = row.bytes[x] = new JLabel();
|
||
|
label.setFont(font);
|
||
|
label.setForeground(SystemColor.windowText);
|
||
|
label.setHorizontalAlignment(SwingConstants.CENTER);
|
||
|
label.setVisible(false);
|
||
|
client.add(label);
|
||
|
}
|
||
|
|
||
|
// Add the row to the collection
|
||
|
rows.add(row);
|
||
|
return row;
|
||
|
}
|
||
|
|
||
|
// Measure the minimum column widths for a row
|
||
|
private void measure(Row row, int[] widths) {
|
||
|
widths[0] = Math.max(widths[0], row.address.getPreferredSize().width);
|
||
|
for (int x = 0; x < 16; x++)
|
||
|
widths[1] = Math.max(widths[1],
|
||
|
row.bytes[x].getPreferredSize().width);
|
||
|
}
|
||
|
|
||
|
// Specify the address of the top row of output
|
||
|
private void setAddress(int address) {
|
||
|
this.address = address & 0xFFFFFFF0;
|
||
|
refresh();
|
||
|
}
|
||
|
|
||
|
// Show or hide a row
|
||
|
private void setVisible(Row row, boolean visible) {
|
||
|
row.address.setVisible(visible);
|
||
|
for (var label : row.bytes)
|
||
|
label.setVisible(visible);
|
||
|
}
|
||
|
|
||
|
// Determine how many rows of output are visible
|
||
|
private int tall(boolean partial) {
|
||
|
return (client.getHeight() + (partial?fontHeight-1:0)) / fontHeight;
|
||
|
}
|
||
|
|
||
|
// Update the text of a row
|
||
|
private void update(Row row, int y, int address, byte[] data, int offset) {
|
||
|
|
||
|
// Update address
|
||
|
row.address.setBounds(0, y, 8 * fontWidth, fontHeight);
|
||
|
row.address.setText(String.format("%08X", address));
|
||
|
|
||
|
// Update bytes
|
||
|
for (int z = 0, x = 10 * fontWidth; z < 16; z++) {
|
||
|
var label = row.bytes[z];
|
||
|
label.setBounds(x, y, 2 * fontWidth, fontHeight);
|
||
|
label.setText(String.format("%02X", data[offset++] & 0xFF));
|
||
|
x += fontWidth * (z == 7 ? 4 : 3);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|