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 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(); // 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); } } }