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 UPanel 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; rows = new ArrayList(); // Configure client area client = new UPanel(); 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 UPanel(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); } } }