616 lines
22 KiB
Java
616 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 JPanel client; // Client area
|
|
private JLabel errAddress; // Address error text
|
|
private JLabel errCondition; // Condition error text
|
|
private JPanel lstBreakpoints; // Breakpoint list
|
|
private JPanel 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 JPanel(new BorderLayout());
|
|
client.setBackground(SystemColor.control);
|
|
client.setFocusable(true);
|
|
client.setPreferredSize(new Dimension(320, 240));
|
|
|
|
// Configure breakpoint list
|
|
lstBreakpoints = new JPanel(new GridBagLayout()) {
|
|
public void paintComponent(Graphics g) {
|
|
super.paintComponent(g);
|
|
onPaint((Graphics2D) g, getWidth(), getHeight());
|
|
}
|
|
};
|
|
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));
|
|
var scr = new JScrollPane(lstBreakpoints);
|
|
client.add(scr, BorderLayout.CENTER);
|
|
spacer = new JPanel();
|
|
spacer.setOpaque(false);
|
|
lstBreakpoints.add(spacer, SPACER);
|
|
|
|
// Configure properties pane
|
|
var props = new JPanel(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 JPanel(new GridBagLayout());
|
|
var gbc = new GridBagConstraints();
|
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
|
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
|
props.add(buttons, gbc);
|
|
var fill = new JPanel();
|
|
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(JPanel 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(JPanel 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(JPanel 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(JPanel 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(JPanel 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;
|
|
}
|
|
|
|
}
|