package app; // Java imports import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; // Project imports import util.*; import vue.*; // Register list item class Register { // Instance fields private boolean expandable; // The expansion area can be shown private boolean expanded; // The expanded area is being shown private int index; // Register index private int mode; // Display mode for program registers private String name; // Register name private RegisterList parent; // Containing register list private int type; // Expansion controls type private int value; // Current register value // UI components UPanel expansion; // Expansion container JLabel btnExpand; // Expand button UPanel indent; // Expansion area indentation JLabel lblName; // Register name JTextField txtValue; // Register value ArrayList controls; // Expansion controls /////////////////////////////////////////////////////////////////////////// // Constants // /////////////////////////////////////////////////////////////////////////// // Modes static final int FLOAT = 3; static final int HEX = 0; static final int SIGNED = 1; static final int UNSIGNED = 2; // Types static final int PLAIN = -2; static final int PROGRAM = -3; // Expand button labels static final String COLLAPSE = "-"; static final String EXPAND = "+"; /////////////////////////////////////////////////////////////////////////// // Constructors // /////////////////////////////////////////////////////////////////////////// // Default constructor Register(RegisterList parent, String name, int index, int type) { parent.add(index, this); // Configure instance fields controls = new ArrayList(); expandable = type != PLAIN && index != 0; this.index = index; mode = HEX; this.name = name; this.parent = parent; this.type = type; // Click handler for expand and name controls MouseListener expand = !expandable ? null : Util.onMouse(e->{ if (e.getButton() == 1) setExpanded(!expanded); }, null); // Expand button btnExpand = new JLabel(expandable ? EXPAND : ""); btnExpand.setHorizontalAlignment(SwingConstants.CENTER); if (expandable) btnExpand.addMouseListener(expand); var gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.NORTH; gbc.fill = GridBagConstraints.HORIZONTAL; parent.add(gbc, btnExpand); // Name label lblName = new JLabel(" "); if (expandable) lblName.addMouseListener(expand); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.NORTH; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1; parent.add(gbc, lblName); // Value text box txtValue = new JTextField(); txtValue.setBorder(null); txtValue.setOpaque(false); txtValue.setText("00000000"); gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.insets = new Insets(0, 4, 0, 0); parent.add(gbc, txtValue); // Value changed txtValue.addActionListener(e->parent.requestFocus()); txtValue.addFocusListener(Util.onFocus(null, e->{ String text = txtValue.getText(); int val = value; try { switch (mode) { case HEX : val = (int) Long.parseLong(text, 16); break; case SIGNED : // Fallthrough case UNSIGNED: val = (int) Long.parseLong(text, 10); break; case FLOAT : val = Float.floatToIntBits(Float.parseFloat(text) ); break; }} catch (Exception x) { } setValue(val); })); // Expansion controls switch (type) { case PROGRAM : initProgram(); break; case Vue.CHCW: initCHCW (); break; case Vue.ECR : initECR (); break; case Vue.PC : initPC (); break; case Vue.PIR : initPIR (); break; case Vue.PSW : initPSW (); break; case Vue.TKCW: initTKCW (); break; default: configure(); return; } // Expansion indentation if (index != Vue.PC) { indent = new UPanel(); indent.setOpaque(false); indent.setPreferredSize(new Dimension(0, 0)); indent.setVisible(false); gbc = new GridBagConstraints(); parent.add(gbc, indent); } // Expansion area expansion.setOpaque(false); expansion.setVisible(false); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.WEST; if (index == Vue.PC) gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.insets = new Insets(0, 4, 0, 0); gbc.weightx = 1; parent.add(gbc, expansion); // Handling for PSW if (index == Vue.PSW && type == Vue.PSW) setExpanded(true); // Apply application settings configure(); } /////////////////////////////////////////////////////////////////////////// // Expansion Constructors // /////////////////////////////////////////////////////////////////////////// // Expansion controls for CHCW private void initCHCW() { expansion = new UPanel(new GridBagLayout()); addCheckBox("ICE", 1, false, true); } // Expansion controls for ECR private void initECR() { expansion = new UPanel(new GridBagLayout()); addTextBox("EICC", 0, 16, false, true); addTextBox("FECC", 16, 16, false, true); } // Expansion controls for program registers private void initProgram() { expansion = new UPanel(new GridBagLayout()); var group = new ButtonGroup(); group.add(addRadioButton("cpu.hex" , HEX )); group.add(addRadioButton("cpu.signed" , SIGNED )); group.add(addRadioButton("cpu.unsigned", UNSIGNED)); group.add(addRadioButton("cpu.float" , FLOAT )); } // Expansion controls for PC private void initPC() { expansion = new UPanel(new GridBagLayout()); // Configure controls for (int x = 0; x < 2; x++) { // Indentation indent = new UPanel(); indent.setOpaque(false); indent.setPreferredSize(new Dimension(0, 0)); var gbc = new GridBagConstraints(); gbc.weightx = 1; expansion.add(indent, gbc); // Name label var label = new JLabel(); parent.parent.parent.app.localizer.add(label, x == 0 ? "cpu.jump_from" : "cpu.jump_to" ); gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.NORTHWEST; expansion.add(label, gbc); controls.add(label); // Value text box var txt = new JTextField(); txt.addActionListener(e->parent.requestFocus()); txt.addFocusListener(Util.onFocus(null, e->txt.setText((String) txt.getClientProperty("text")))); txt.putClientProperty("index", x == 0 ? Vue.JUMP_FROM : Vue.JUMP_TO); txt.setBorder(null); txt.setOpaque(false); gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.insets = new Insets(0, 4, 0, 0); expansion.add(txt, gbc); controls.add(txt); } } // Expansion controls for PSW private void initPSW() { expansion = new UPanel(new GridBagLayout()); addCheckBox("Z" , 0, false, false); addCheckBox("FRO", 9, false, true ); addCheckBox("S" , 1, false, false); addCheckBox("FIV", 8, false, true ); addCheckBox("OV" , 2, false, false); addCheckBox("FZD", 7, false, true ); addCheckBox("CY" , 3, false, false); addCheckBox("FOV", 6, false, true ); addCheckBox("EP" , 14, false, false); addCheckBox("FUD", 5, false, true ); addCheckBox("NP" , 15, false, false); addCheckBox("FPR", 4, false, true ); addCheckBox("AE" , 13, false, true ); addCheckBox("ID" , 12, false, false); addTextBox ("I" , 16, 4, false, false); } // Expansion controls for PIR private void initPIR() { expansion = new UPanel(new GridBagLayout()); addTextBox("PT", 0, 16, true, true); } // Expansion controls for TKCW private void initTKCW() { expansion = new UPanel(new GridBagLayout()); addCheckBox("OTM", 8, true, false); addCheckBox("FVT", 5, true, true ); addCheckBox("FIT", 7, true, false); addCheckBox("FUT", 4, true, true ); addCheckBox("FZT", 6, true, false); addCheckBox("FPT", 3, true, true ); addCheckBox("RDI", 2, true, false); addTextBox ("RD", 0, 2, true, false); } /////////////////////////////////////////////////////////////////////////// // Package Methods // /////////////////////////////////////////////////////////////////////////// // Apply configuration settings void configure() { String name = null; // System register if (type != PROGRAM) { name = this.name; if (!Disassembler.systemCaps) name = name.toLowerCase(); } // Program register else { if (Disassembler.programNames) name = this.name; if (name == null) name = "r" + index; if (Disassembler.programCaps) name = name.toUpperCase(); } // Name label lblName.setText(name); // Expand button var size = btnExpand.getPreferredSize(); var metrics = parent.parent.parent.app.fntDialog.metrics; size.width = 4 + Math.max( metrics.stringWidth(EXPAND), metrics.stringWidth(COLLAPSE)); btnExpand.setPreferredSize(size); // Value text box var fntMono = parent.parent.parent.app.fntMono; int hexDigitWidth = parent.parent.parent.app.hexDigitWidth; txtValue.setFont(fntMono); txtValue.setPreferredSize(new Dimension( hexDigitWidth * 8 + 4, fntMono.metrics.getHeight())); // Expansion controls for (var ctrl : controls) { if (!(ctrl instanceof JTextField)) continue; if (type == Vue.PC || (Boolean) ctrl.getClientProperty("hex")) ((JTextField) ctrl).setFont(fntMono); int digits = type == Vue.PC ? 8 : (Integer) ctrl.getClientProperty("digits"); size = ctrl.getPreferredSize(); size.width = digits * hexDigitWidth + 4; ctrl.setPreferredSize(size); } } // Refresh controls void refresh() { var vue = parent.parent.parent.vue; // Value text box value = vue.getRegister(index, type != PROGRAM); txtValue.setText( type != PROGRAM || mode == HEX ? String.format("%08X", value) : mode == SIGNED ? Integer.toString(value) : mode == UNSIGNED ? Long.toString(value & 0xFFFFFFFFL) : Float.toString(Float.intBitsToFloat(value)) ); // Expansion controls for (var control : controls) { // Check box if (control instanceof JCheckBox) { var ctrl = (JCheckBox) control; int bit = (Integer) ctrl.getClientProperty("bit"); ctrl.setSelected((value & 1 << bit) != 0); } // Text box if (control instanceof JTextField) { var ctrl = (JTextField) control; int digits; // Maximum digits that can be displayed boolean hex; // The value is hexadecimal int val; // The value to be displayed // Jump history if (type == Vue.PC) { digits = 8; hex = true; val = vue.getRegister((Integer) ctrl.getClientProperty("index"), true); } // All other values else { int bit = (Integer) ctrl.getClientProperty("bit" ); digits = (Integer) ctrl.getClientProperty("digits"); hex = (Boolean) ctrl.getClientProperty("hex"); int width = (Integer) ctrl.getClientProperty("width"); val = value >> bit & (1 << width) - 1; } // Update the text String text = !hex ? Integer.toString(val) : String.format( "%0" + digits + (Disassembler.hexCaps ? "X" : "x"), val); ctrl.putClientProperty("text", text); ctrl.setText(text); } } } // Specify whether the expansion area is expanded void setExpanded(boolean expanded) { // Error checking if (!expandable) return; // Update controls this.expanded = expanded; btnExpand.setText(expanded ? "-" : "+"); if (type != Vue.PC) indent.setVisible(expanded); expansion.setVisible(expanded); parent.revalidate(); } // Change the display mode of a program register void setMode(int mode) { this.mode = mode; txtValue.setFont(mode!=HEX ? null : parent.parent.parent.app.fntMono); refresh(); } /////////////////////////////////////////////////////////////////////////// // Private Methods // /////////////////////////////////////////////////////////////////////////// // Add a check box to the expansion area private void addCheckBox(String name, int bit, boolean readOnly, boolean last) { int mask = 1 << bit; // Configure control var ctrl = new JCheckBox(name); ctrl.putClientProperty("bit", bit); ctrl.setBorder(null); ctrl.setEnabled(!readOnly); ctrl.setFocusable(false); ctrl.setOpaque(false); controls.add(ctrl); // Event handler ctrl.addItemListener(e->setValue( e.getStateChange() == ItemEvent.SELECTED ? value | mask : value & ~mask )); // Configure expansion area var gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.NORTHWEST; gbc.gridwidth = last ? GridBagConstraints.REMAINDER : 2; gbc.insets = new Insets(0, 4, 0, 4); expansion.add(ctrl, gbc); } // Add a radio button to the expansion area private JRadioButton addRadioButton(String key, int mode) { // Configure control var ctrl = new JRadioButton(); parent.parent.parent.app.localizer.add(ctrl, key); ctrl.setBorder(null); ctrl.setFocusable(false); ctrl.setOpaque(false); ctrl.setSelected(mode == HEX); controls.add(ctrl); // Event handler ctrl.addItemListener(e->{ if (e.getStateChange() == ItemEvent.SELECTED) setMode(mode); }); // Configure expansion area var gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.NORTHWEST; gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.insets = new Insets(0, 4, 0, 0); expansion.add(ctrl, gbc); return ctrl; } // Add a text box to the expansion area private void addTextBox(String name, int bit, int width, boolean readOnly, boolean hex) { int mask = (1 << width) - 1; // Configure control var ctrl = new JTextField(); ctrl.putClientProperty("bit", bit); ctrl.putClientProperty("digits", Integer.toString(mask, hex ? 16 : 10).length()); ctrl.putClientProperty("hex", hex); ctrl.putClientProperty("width", width); ctrl.setBorder(null); ctrl.setEnabled(!readOnly); ctrl.setOpaque(false); controls.add(ctrl); // Event handlers ctrl.addActionListener(e->parent.requestFocus()); ctrl.addFocusListener(Util.onFocus(null, e->{ int val = value >> bit & mask; try { val = Integer.parseInt(ctrl.getText(), hex ? 16 : 10); } catch (Exception x) { } setValue(value & ~(mask << bit) | (val & mask) << bit); })); // Configure expansion area var label = new JLabel(name); label.setEnabled(!readOnly); var gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.NORTHWEST; gbc.insets = new Insets(0, 4, 0, 4); expansion.add(label, gbc); gbc = new GridBagConstraints(); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.insets = new Insets(0, 4, 0, 4); expansion.add(ctrl , gbc); } // Update the register value private void setValue(int value) { parent.parent.parent.vue.setRegister(index, type != PROGRAM, value); refresh(); if (index == Vue.PSW && type == Vue.PSW) parent.registers.get(Vue.PC).refresh(); } }