pvbemu/src/desktop/app/BreakpointsWindow.java

614 lines
22 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.*;
// Memory viewer and hex editor window
class BreakpointsWindow extends ChildWindow {
// Private fields
private ArrayList<Item> items; // List items
private int selectedIndex; // Selected list item
// UI components
private JButton btnDelete; // Delete button
private JButton btnNew; // New button
private JCheckBox chkEnabled; // Enabled check box
private JCheckBox chkException; // Exception check box
private JCheckBox chkExecute; // Execute check box
private JCheckBox chkRead; // Read check box
private JCheckBox chkWrite; // Write check box
private UPanel client; // Client area
private JLabel errAddress; // Address error text
private JLabel errCondition; // Condition error text
private UPanel lstBreakpoints; // Breakpoint list
private UPanel spacer; // List termination spacer
private JTextField txtAddress; // Address text box
private JTextField txtCondition; // Condition text box
private JTextField txtName; // Name text box
///////////////////////////////////////////////////////////////////////////
// Constants //
///////////////////////////////////////////////////////////////////////////
// Address error keys
private static final String[] KEYS_ADDRESS = {
null , // NONE
"breakpoints.error.badliteral_a", // BADLITERAL
null , // BADOPERAND
null , // BADTOKEN
"breakpoints.error.earlyeof" , // EARLYEOF
null , // INVALID
null , // NESTING
"breakpoints.error.unexpected" // UNEXPECTED
};
// Condition error keys
private static final String[] KEYS_CONDITION = {
null , // NONE
"breakpoints.error.badliteral_c", // BADLITERAL
"breakpoints.error.badoperand" , // BADOPERAND
"breakpoints.error.badtoken" , // BADTOKEN
"breakpoints.error.earlyeof" , // EARLYEOF
"breakpoints.error.invalid" , // INVALID
"breakpoints.error.nesting" , // NESTING
"breakpoints.error.unexpected" // UNEXPECTED
};
// List constraints
private static final GridBagConstraints ENABLED;
private static final GridBagConstraints NAME;
private static final GridBagConstraints SPACER;
private static final GridBagConstraints STATUS;
// Static initializer
static {
ENABLED = new GridBagConstraints();
ENABLED.anchor = GridBagConstraints.CENTER;
ENABLED.insets = new Insets(1, 2, 1, 2);
NAME = new GridBagConstraints();
NAME.fill = GridBagConstraints.HORIZONTAL;
NAME.gridwidth = GridBagConstraints.REMAINDER;
NAME.insets = new Insets(1, 0, 1, 2);
NAME.weightx = 1;
SPACER = new GridBagConstraints();
SPACER.fill = GridBagConstraints.BOTH;
SPACER.gridheight = GridBagConstraints.REMAINDER;
SPACER.gridwidth = GridBagConstraints.REMAINDER;
SPACER.weighty = 1;
STATUS = new GridBagConstraints();
STATUS.fill = GridBagConstraints.BOTH;
STATUS.insets = new Insets(0, 0, 0, 2);
}
///////////////////////////////////////////////////////////////////////////
// Classes //
///////////////////////////////////////////////////////////////////////////
// Text box commit
private interface Commit {
void run();
}
// List item
private class Item {
Breakpoint breakpoint;
JCheckBox chkEnabled;
JLabel lblName;
JLabel lblStatus;
// Constructor
Item(Breakpoint brk) {
var that = this;
breakpoint = brk;
brk.setFetch(false);
chkEnabled = new JCheckBox();
chkEnabled.setBorder(null);
chkEnabled.setFocusable(false);
chkEnabled.addActionListener(e->onEnabled(that));
parent.app.localizer.add(chkEnabled,"null","breakpoints.enabled");
lblName = new JLabel(brk.getName());
lblStatus = new JLabel("", SwingConstants.CENTER);
refresh(null, null);
}
// Update the display
void refresh(Instruction inst, Access acc) {
if (
breakpoint.getAddressError ().code != Breakpoint.NONE ||
breakpoint.getConditionError().code != Breakpoint.NONE
) lblStatus.setText("\u00d7"); // Times symbol
else if (breakpoint.any() && breakpoint.evaluate(inst, acc))
lblStatus.setText("\u2605"); // Star
else lblStatus.setText(" ");
int lineHeight = parent.app.fntDialog.metrics.getHeight();
lblStatus.setPreferredSize(new Dimension(lineHeight, lineHeight));
}
// Specify whether or not the item has focus
void setFocused(boolean focused) {
var color = focused ? SystemColor.textHighlightText :
SystemColor.windowText;
lblStatus.setForeground(color);
lblName .setForeground(color);
}
}
///////////////////////////////////////////////////////////////////////////
// Constructors //
///////////////////////////////////////////////////////////////////////////
// Default constructor
BreakpointsWindow(MainWindow parent) {
super(parent, "breakpoints.title");
// Configure instance fields
items = new ArrayList<Item>();
selectedIndex = -1;
// Configure client area
client = new UPanel(new BorderLayout());
client.setBackground(SystemColor.control);
client.setFocusable(true);
client.setPreferredSize(new Dimension(320, 240));
// Configure breakpoint list
lstBreakpoints = new UPanel(new GridBagLayout());
lstBreakpoints.setFocusable(true);
lstBreakpoints.setBackground(SystemColor.window);
lstBreakpoints.addFocusListener(Util.onFocus(
e->lstBreakpoints.repaint(), e->lstBreakpoints.repaint()));
lstBreakpoints.addMouseListener(Util.onMouse(e->onMouseDown(e), null));
lstBreakpoints.addPaintListener((g,w,h)->onPaint(g, w, h));
var scr = new JScrollPane(lstBreakpoints);
scr.getVerticalScrollBar().setUnitIncrement(
parent.app.fntDialog.metrics.getHeight());
client.add(scr, BorderLayout.CENTER);
spacer = new UPanel();
spacer.setOpaque(false);
lstBreakpoints.add(spacer, SPACER);
// Configure properties pane
var props = new UPanel(new GridBagLayout());
props.setBackground(SystemColor.control);
props.addMouseListener(
Util.onMouse(e->client.requestFocus(), null));
client.add(props, BorderLayout.SOUTH);
// Configure properties
label(props, "breakpoints.name", 2);
txtName = textBox(props, false, 2, ()->onName());
//label(props, "breakpoints.enabled", 0);
//chkEnabled = checkBox(props, null, true);
chkEnabled = new JCheckBox();
chkEnabled.addActionListener(e->onEnabled(null));
label(props, "breakpoints.type", 0);
chkExecute = checkBox(props, "breakpoints.execute" , false);
chkExecute .addActionListener(e->onType(chkExecute ));
chkRead = checkBox(props, "breakpoints.read" , false);
chkRead .addActionListener(e->onType(chkRead ));
chkWrite = checkBox(props, "breakpoints.write" , false);
chkWrite .addActionListener(e->onType(chkWrite ));
chkException = checkBox(props, "breakpoints.exception", true );
chkException.addActionListener(e->onType(chkException));
label(props, "breakpoints.address", 0);
txtAddress = textBox(props, true, 0, ()->onAddress());
errAddress = error(props);
label(props, "breakpoints.condition", 0);
txtCondition = textBox(props, true, 0, ()->onCondition());
errCondition = error(props);
// Configure buttons
var buttons = new UPanel(new GridBagLayout());
var gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
props.add(buttons, gbc);
var fill = new UPanel();
fill.setOpaque(false);
fill.addMouseListener(Util.onMouse(e->onDebug(e), null));
gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
buttons.add(fill, gbc);
btnDelete = button(buttons, "breakpoints.delete", false);
btnDelete.setEnabled(false);
btnDelete.addActionListener(e->onDelete());
btnNew = button(buttons, "breakpoints.new", true);
btnNew.addActionListener(e->onNew());
// Configure component
setContentPane(client);
pack();
}
///////////////////////////////////////////////////////////////////////////
// Package Methods //
///////////////////////////////////////////////////////////////////////////
// Update the display
void refresh() {
var inst = new Instruction(parent.vue.read(
parent.vue.getRegister(Vue.PC, true), Vue.S32));
var acc = inst.access(parent.vue);
for (var item : items)
item.refresh(inst, acc);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
///////////////////////////////////////////////////////////////////////////
// Event Handlers //
///////////////////////////////////////////////////////////////////////////
// Address text box commit
private void onAddress() {
if (selectedIndex == -1)
return;
var item = items.get(selectedIndex);
item.breakpoint.setAddresses(txtAddress.getText());
checkErrors(item);
item.refresh(null, null);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
// Condition text box commit
private void onCondition() {
if (selectedIndex == -1)
return;
var item = items.get(selectedIndex);
item.breakpoint.setCondition(txtCondition.getText());
checkErrors(item);
item.refresh(null, null);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
// Debug interaction
private void onDebug(MouseEvent e) {
client.requestFocus();
if (
selectedIndex == -1 ||
e.getButton() != MouseEvent.BUTTON1 ||
(e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == 0
) return;
var brk = items.get(selectedIndex).breakpoint;
System.out.println("Address");
System.out.println(brk.getAddresses());
System.out.println(brk.debugRanges());
System.out.println("Condition");
System.out.println(brk.getCondition());
System.out.println(brk.debugTokens());
System.out.println();
}
// Delete button click
private void onDelete() {
var item = items.remove(selectedIndex);
parent.vue.remove(item.breakpoint);
lstBreakpoints.remove(item.chkEnabled);
lstBreakpoints.remove(item.lblName);
lstBreakpoints.remove(item.lblStatus);
lstBreakpoints.revalidate();
selectedIndex = -1;
select(-1);
}
// Enabled check box toggle
private void onEnabled(Item item) {
boolean enabled;
// Clicked from the properties pane
if (item == null) {
enabled = chkEnabled.isSelected();
item = items.get(selectedIndex);
item.chkEnabled.setSelected(enabled);
client.requestFocus();
}
// Clicked from the breakpoints list
else {
enabled = item.chkEnabled.isSelected();
if (items.indexOf(item) == selectedIndex)
chkEnabled.setSelected(enabled);
lstBreakpoints.requestFocus();
}
// Common processing
item.breakpoint.setEnabled(enabled);
item.refresh(null, null);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
// List mouse press
private void onMouseDown(MouseEvent e) {
int count = items.size();
// Left clicking
if (count > 0 && e.getButton() == MouseEvent.BUTTON1) {
int newIndex = e.getY() / items.get(0).lblStatus.getHeight();
if (newIndex < count)
select(newIndex);
}
// Update layout
lstBreakpoints.requestFocus();
lstBreakpoints.repaint();
}
// Name text box commit
private void onName() {
if (selectedIndex == -1)
return;
var item = items.get(selectedIndex);
item.lblName.setText(item.breakpoint.setName(txtName.getText()));
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
item.refresh(null, null);
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
// New button click
private void onNew() {
var brk = parent.vue.breakpoint();
brk.setEnabled(true);
brk.setName (parent.app.localizer.get("breakpoints.default_name"));
var item = new Item(brk);
item.chkEnabled.setSelected(true);
item.lblName .setText(brk.getName());
items.add(item);
int count = items.size();
boolean first = count == 1;
lstBreakpoints.remove(spacer);
lstBreakpoints.add(item.chkEnabled, ENABLED);
lstBreakpoints.add(item.lblStatus , STATUS );
lstBreakpoints.add(item.lblName , NAME );
lstBreakpoints.add(spacer , SPACER );
select(count - 1);
lstBreakpoints.requestFocus();
lstBreakpoints.revalidate();
}
// List paint
private void onPaint(Graphics2D g, int width, int height) {
// There is no selected item
if (selectedIndex == -1)
return;
// Select the display colors
var item = items.get(selectedIndex);
if (lstBreakpoints.isFocusOwner()) {
g.setColor(SystemColor.textHighlight);
item.setFocused(true);
} else {
g.setColor(SystemColor.control);
item.setFocused(false);
}
// Draw the selection background
height = item.lblStatus.getHeight();
g.fillRect(0, selectedIndex * height, width, height);
}
// Type check box toggle
private void onType(JCheckBox chk) {
var item = items.get(selectedIndex);
var brk = item.breakpoint;
boolean selected = chk.isSelected();
// Configure controls
if (chk == chkException)
brk.setException(selected);
else if (chk == chkExecute )
brk.setExecute (selected);
else if (chk == chkRead )
brk.setRead (selected);
else if (chk == chkWrite )
brk.setWrite (selected);
// Common processing
item.refresh(null, null);
client.requestFocus();
lstBreakpoints.revalidate();
lstBreakpoints.repaint();
}
///////////////////////////////////////////////////////////////////////////
// Private Methods //
///////////////////////////////////////////////////////////////////////////
// Add a button to the form
private JButton button(UPanel panel, String key, boolean last) {
var btn = new JButton();
if (key != null)
parent.app.localizer.add(btn, key);
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(0, 0, 2, 2);
if (last)
gbc.gridwidth = GridBagConstraints.REMAINDER;
panel.add(btn, gbc);
return btn;
}
// Add a check box to the form
private JCheckBox checkBox(UPanel panel, String key, boolean last) {
var chk = new JCheckBox();
if (key != null)
parent.app.localizer.add(chk, key);
chk.setBorder(null);
chk.setEnabled(false);
chk.setFocusable(false);
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(0, 0, 2, last ? 2 : 6);
if (last)
gbc.gridwidth = GridBagConstraints.REMAINDER;
panel.add(chk, gbc);
return chk;
}
// Check for errors in the selected breakpoint
private void checkErrors(Item item) {
var brk = item.breakpoint;
var loc = parent.app.localizer;
// Check for address errors
var err = brk.getAddressError();
if (err.code != Breakpoint.NONE) {
loc.add(errAddress, KEYS_ADDRESS[err.code]);
loc.put(errAddress, "err.position",
Integer.toString(err.position));
loc.put(errAddress, "err.text", err.text);
errAddress.setVisible(true);
} else errAddress.setVisible(false);
// Check for condition errors
err = brk.getConditionError();
if (err.code != Breakpoint.NONE) {
loc.add(errCondition, KEYS_CONDITION[err.code]);
loc.put(errCondition, "err.position",
Integer.toString(err.position));
loc.put(errCondition, "err.text", err.text);
if (err.code == Breakpoint.NESTING)
loc.put(errCondition, "err.other",
err.text.equals(")") ? "]" : ")");
errCondition.setVisible(true);
} else errCondition.setVisible(false);
}
// Add an error label to the form
private JLabel error(UPanel panel) {
var lbl = new JLabel();
lbl.setForeground(Color.red);
lbl.setVisible(false);
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.CENTER;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = 1;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(0, 0, 2, 2);
panel.add(lbl, gbc);
return lbl;
}
// Add a label to the form
private void label(UPanel panel, String key, int top) {
var lbl = new JLabel();
if (key != null)
parent.app.localizer.add(lbl, key);
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(top, 2, 2, 4);
panel.add(lbl, gbc);
}
// Select a particular list item
private void select(int newIndex) {
boolean enabled = newIndex != -1;
// The existing selection was de-selected
if (selectedIndex != -1 && newIndex != selectedIndex)
items.get(selectedIndex).setFocused(false);
// Selecting a new list item
if (newIndex != -1)
items.get(newIndex).setFocused(true);
// Configure instance fields
selectedIndex = newIndex;
// Update control properties
if (enabled) {
var brk = items.get(selectedIndex).breakpoint;
chkEnabled .setSelected(brk.isEnabled ());
chkExecute .setSelected(brk.getExecute ());
chkRead .setSelected(brk.getRead ());
chkWrite .setSelected(brk.getWrite ());
chkException.setSelected(brk.getException());
txtAddress .setText (brk.getAddresses());
txtCondition.setText (brk.getCondition());
txtName .setText (brk.getName ());
}
// Clear control properties
else {
chkEnabled .setSelected(false);
chkExecute .setSelected(false);
chkRead .setSelected(false);
chkWrite .setSelected(false);
chkException.setSelected(false);
errAddress .setText(""); errAddress .setVisible(false);
errCondition.setText(""); errCondition.setVisible(false);
txtAddress .setText("");
txtCondition.setText("");
txtName .setText("");
}
// Enable or disable controls
btnDelete .setEnabled(enabled);
chkEnabled .setEnabled(enabled);
chkExecute .setEnabled(enabled);
chkException.setEnabled(enabled);
chkRead .setEnabled(enabled);
chkWrite .setEnabled(enabled);
txtAddress .setEnabled(enabled);
txtCondition.setEnabled(enabled);
txtName .setEnabled(enabled);
// Common processing
if (selectedIndex != -1)
checkErrors(items.get(selectedIndex));
lstBreakpoints.repaint();
}
// Add a text box to the form
private JTextField textBox(UPanel panel, boolean mono, int top,
Commit handler) {
var txt = new JTextField();
txt.setEnabled(false);
if (mono)
txt.setFont(parent.app.fntMono);
txt.addActionListener(e->client.requestFocus());
txt.addFocusListener (Util.onFocus(null, e->handler.run()));
var gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.CENTER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(top, 0, 2, 2);
gbc.weightx = 1;
panel.add(txt, gbc);
return txt;
}
}