249 lines
7.9 KiB
Java
249 lines
7.9 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
|
|
|
|
// 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;
|
|
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);
|
|
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 lineHeight = parent.app.fntMono.metrics.getHeight();
|
|
int count = (height + lineHeight - 1) / lineHeight;
|
|
var data = new byte[count * 16];
|
|
|
|
// Retrieve all visible bytes from the emulation context
|
|
parent.vue.readBytes(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 * lineHeight, 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();
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// 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 text = JOptionPane.showInputDialog(
|
|
this, "Goto:", "Goto", JOptionPane.PLAIN_MESSAGE);
|
|
Object eval = parent.vue.evaluate(text);
|
|
int addr = 0;
|
|
if (eval instanceof Integer)
|
|
addr = (Integer) eval;
|
|
else if (eval instanceof Long)
|
|
addr = (int) (long) (Long) eval;
|
|
else return;
|
|
setAddress(addr);
|
|
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 font = parent.app.fntMono;
|
|
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) {
|
|
int lineHeight = parent.app.fntMono.metrics.getHeight();
|
|
return (client.getHeight() + (partial?lineHeight-1:0)) / lineHeight;
|
|
}
|
|
|
|
// Update the text of a row
|
|
private void update(Row row, int y, int address, byte[] data, int offset) {
|
|
int hexDigitWidth = parent.app.hexDigitWidth;
|
|
int lineHeight = parent.app.fntMono.metrics.getHeight();
|
|
|
|
// Update address
|
|
row.address.setBounds(0, y, 8 * hexDigitWidth, lineHeight);
|
|
row.address.setText(String.format("%08X", address));
|
|
|
|
// Update bytes
|
|
for (int z = 0, x = 10 * hexDigitWidth; z < 16; z++) {
|
|
var label = row.bytes[z];
|
|
label.setBounds(x, y, 2 * hexDigitWidth, lineHeight);
|
|
label.setText(String.format("%02X", data[offset++] & 0xFF));
|
|
x += hexDigitWidth * (z == 7 ? 4 : 3);
|
|
}
|
|
|
|
}
|
|
|
|
}
|