pvbemu/src/desktop/app/Register.java

521 lines
18 KiB
Java

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