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