/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.Debugger.DisassemblerModule; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.DefaultCellEditor; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; public class RegisterTable extends JTable { private static final long serialVersionUID = 1L; private static final Font tableFont = new Font("Courier new", Font.PLAIN, 12); private HashMap<String, Color> highlights; private class Register { public Register(String name) { this.name = name; value = 0; changed = false; highlights = new HashMap<String, Color>(); } public String name; public int value; public boolean changed; @Override public String toString() { return String.format("0x%08X", value); } }; public RegisterTable(String[] regnames) { super(); setFont(tableFont); setDefaultRenderer(String.class, new RegisterNameRenderer()); setDefaultRenderer(Register.class, new RegisterValueRenderer()); setDefaultEditor(Register.class, new RegisterValueEditor()); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); setRegisters(regnames); } public RegisterTable() { super(); setFont(tableFont); setDefaultRenderer(String.class, new RegisterNameRenderer()); setDefaultRenderer(Register.class, new RegisterValueRenderer()); setDefaultEditor(Register.class, new RegisterValueEditor()); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } final public void setRegisters(String[] regnames) { setModel(new RegisterTableModel(regnames)); } public void highlightRegister(String regname, Color color) { // strip leading '$' if (regname.startsWith("$")) { regname = regname.substring(1); } highlights.put(regname.toLowerCase(), color); repaint(); } public void clearRegisterHighlights() { highlights.clear(); repaint(); } @Override public void setModel(TableModel dataModel) { // needed to allow setting model property in NetBeans to null if (dataModel != null) { super.setModel(dataModel); } } public void resetChanges() { ((RegisterTableModel) getModel()).resetChanges(); } public int getAddressAt(int rowIndex) { return ((Register) ((RegisterTableModel) getModel()).getValueAt(rowIndex, 1)).value; } private class RegisterNameRenderer extends JLabel implements TableCellRenderer { private static final long serialVersionUID = 1L; public RegisterNameRenderer() { super(); setFont(tableFont); setBackground(selectionBackground); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { String reg = (String) table.getModel().getValueAt(row, column); setText(reg); reg = reg.toLowerCase(); if (highlights.containsKey(reg)) { // shade the highlight color on selection if (isSelected) { setBackground(highlights.get(reg).darker()); } else { setBackground(highlights.get(reg)); } // always display the colour setOpaque(true); } else { // handle regular selection setBackground(selectionBackground); setOpaque(isSelected); } return this; } } class RegisterValueEditor extends DefaultCellEditor { private static final long serialVersionUID = 1L; public RegisterValueEditor() { super(new JTextField()); final JTextField tf = ((JTextField) getComponent()); tf.setFont(tableFont); } @Override public Object getCellEditorValue() { return ((JTextField) getComponent()).getText(); } @Override public Component getTableCellEditorComponent( final JTable table, final Object value, final boolean isSelected, final int row, final int column) { final JTextField tf = ((JTextField) getComponent()); tf.setText(String.format("0x%X", ((Register) table.getModel().getValueAt(row, column)).value)); // needed for double-click to work, otherwise the second click // is interpreted to position the caret SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // automatically select text after '0x' tf.select(2, tf.getText().length()); } }); return tf; } } private class RegisterValueRenderer extends JLabel implements TableCellRenderer { private static final long serialVersionUID = 1L; public RegisterValueRenderer() { super(); setFont(tableFont); setBackground(selectionBackground); } @Override public Component getTableCellRendererComponent( JTable table, Object color, boolean isSelected, boolean hasFocus, int row, int column) { Register reg = (Register) table.getModel().getValueAt(row, column); setText(String.format("0x%08X", reg.value)); if (reg.changed) { setForeground(Color.RED); setFont(getFont().deriveFont(Font.BOLD)); } else { setForeground(Color.BLACK); setFont(getFont().deriveFont(Font.PLAIN)); } // handle selection highlight setOpaque(isSelected); return this; } } private class RegisterTableModel extends AbstractTableModel { private static final long serialVersionUID = 1L; private List<Register> reginfo; public RegisterTableModel(String[] regnames) { super(); reginfo = new LinkedList<Register>(); for (int i = 0; i < regnames.length; i++) { reginfo.add(new Register(regnames[i])); } } public void resetChanges() { Iterator<Register> it = reginfo.iterator(); while (it.hasNext()) { (it.next()).changed = false; } fireTableDataChanged(); } @Override public Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return String.class; case 1: return Register.class; default: throw new IndexOutOfBoundsException("column index out of range"); } } @Override public String getColumnName(int column) { switch (column) { case 0: return "REG"; case 1: return "HEX"; default: throw new IndexOutOfBoundsException("column index out of range"); } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { // only the values of the registers are editable return (columnIndex == 1); } @Override public int getColumnCount() { // REG, HEX return 2; } @Override public int getRowCount() { return reginfo.size(); } @Override public Object getValueAt(int rowIndex, int columnIndex) { switch (columnIndex) { case 0: return reginfo.get(rowIndex).name; case 1: return reginfo.get(rowIndex); default: throw new IndexOutOfBoundsException("column index out of range"); } } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { int value; if (aValue instanceof Integer) { value = (Integer) aValue; } else if (aValue instanceof String) { try { value = Integer.decode((String) aValue); } catch (NumberFormatException nfe) { // ignore - will revert to old value instead return; } } else { throw new IllegalArgumentException("setValueAt() will only handle Integer and String objects"); } reginfo.get(rowIndex).changed = value != reginfo.get(rowIndex).value; if (reginfo.get(rowIndex).changed) { reginfo.get(rowIndex).value = value; fireTableCellUpdated(rowIndex, columnIndex); } } } }