package edu.princeton.toy.choosers; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import javax.swing.tree.*; import edu.princeton.swing.*; import edu.princeton.toy.lang.*; /** * TExceptionChooserPane manages a set of colors for various indices and allows the user to change the * colors for those indices. It also offers a disabled option which generally tells the index not * to use that color (how that is done will be implementation dependant). * * @author btsang * @version 7.1 */ public class TExceptionChooserPane extends JPanel implements ChangeListener, TreeSelectionListener { private static final String CLASS_STRING = TExceptionChooserPane.class.toString(); /** * The command that causes the TExceptionChooserPane to update its TristateButtonModels due to * a change in one of those models. */ public static final String UPDATE_COMMAND = CLASS_STRING + "#updateCommand"; private static final String rootLabel = "All Toy Exceptional Conditions"; private static final int familyMemberIndices[][] = new int[TExceptionType.FAMILIES.length][]; private static final Border UNFOCUSED_BORDER = new EmptyBorder(1, 1, 1, 1); private static final Border FOCUSED_BORDER = new LineBorder(UIManager.getColor("Tree.selectionBorderColor"), 1); private static final Color SELECTION_FOREGROUND = UIManager.getColor("Tree.selectionForeground"); private static final Color SELECTION_BACKGROUND = UIManager.getColor("Tree.selectionBackground"); private boolean ignoreUpdate; private TristateButtonModel rootModel; private TristateButtonModel familyModels[]; private TristateButtonModel typeModels[]; private PTree exceptionTree; private JTextArea descriptionTextArea; /** * Initialize the familyMemberIndices array. */ static { for (int ctr = 0; ctr < TExceptionType.FAMILIES.length; ctr++) { int memberCount = 0; for (int ctr2 = 0; ctr2 < TExceptionType.TYPES.length; ctr2++) { if (TExceptionType.TYPES[ctr2].getFamily() == ctr) memberCount++; } familyMemberIndices[ctr] = new int[memberCount]; memberCount = 0; for (int ctr2 = 0; ctr2 < TExceptionType.TYPES.length; ctr2++) { if (TExceptionType.TYPES[ctr2].getFamily() == ctr) { familyMemberIndices[ctr][memberCount] = ctr2; memberCount++; } } } } /** * Creates a new TExceptionChooserPane. */ public TExceptionChooserPane() { super(new GridBagLayout()); familyModels = new TristateButtonModel[TExceptionType.FAMILIES.length]; typeModels = new TristateButtonModel[TExceptionType.TYPES.length]; add( new JLabel("Throwable Exceptions"), new GridBagConstraints( 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0 ) ); { // Prepare the tree CheckBoxCell cell = new CheckBoxCell(rootLabel); rootModel = cell.getModel(); rootModel.addChangeListener(this); DefaultMutableTreeNode root = new DefaultMutableTreeNode(cell, true); for (int ctr = 0; ctr < TExceptionType.FAMILIES.length; ctr++) { cell = new CheckBoxCell(TExceptionType.FAMILIES[ctr]); familyModels[ctr] = cell.getModel(); familyModels[ctr].addChangeListener(this); DefaultMutableTreeNode familyNode = new DefaultMutableTreeNode(cell, true); int memberIndices[] = familyMemberIndices[ctr]; for (int ctr2 = 0; ctr2 < memberIndices.length; ctr2++) { int index = memberIndices[ctr2]; cell = new CheckBoxCell(TExceptionType.TYPES[index].getName()); typeModels[index] = cell.getModel(); typeModels[index].addChangeListener(this); familyNode.add(new DefaultMutableTreeNode(cell, false)); } root.add(familyNode); } exceptionTree = new PTree( new DefaultTreeModel( root, true ) ); exceptionTree.setShowsRootHandles(true); exceptionTree.putClientProperty("PTree.lineStyle", "Angled"); exceptionTree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION ); exceptionTree.addTreeSelectionListener(this); exceptionTree.setCellRenderer(new CheckBoxCellRenderer()); exceptionTree.setCellEditor(new CheckBoxCellEditor()); exceptionTree.setEditable(true); for (int ctr = 0; ctr < exceptionTree.getRowCount(); ctr++) exceptionTree.expandRow(ctr); add( new JScrollPane( exceptionTree, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED ), new GridBagConstraints( 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(2, 2, 2, 2), 0, 0 ) ); } add( new JLabel("Description"), new GridBagConstraints( 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0 ) ); { descriptionTextArea = new JTextArea(4, 30); descriptionTextArea.setLineWrap(true); descriptionTextArea.setWrapStyleWord(true); descriptionTextArea.setEnabled(false); descriptionTextArea.setBackground(null); descriptionTextArea.setDisabledTextColor(Color.black); descriptionTextArea.setMinimumSize(new Dimension(100, 70)); descriptionTextArea.setPreferredSize(new Dimension(100, 70)); descriptionTextArea.setMargin(new Insets(2, 2, 2, 2)); descriptionTextArea.setBorder(new EtchedBorder()); add( descriptionTextArea, new GridBagConstraints( 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(2, 2, 2, 2), 0, 0 ) ); } } /** * Returns wheter or not the virtual toy machines should throw all exceptions of the given type. * * @param index The index of the exception type in the TExceptionType.TYPES array. An invalid * value will result in an ArrayIndexOutOfBoundsException. * @return True iff all exceptions of the specified type will be thrown. * @see edu.princeton.toy.lang.TExceptionType#TYPES */ public boolean getWillThrow(int index) { return typeModels[index].isSelected(); } /** * Sets wheter or not the virtual toy machines should throw all exceptions of the given type. * * @param index The index of the exception type in the TExceptionType.TYPES array. An invalid * value will result in an ArrayIndexOutOfBoundsException. * @param willThrow True iff all exceptions of the specified type should be thrown. * @see edu.princeton.toy.lang.TExceptionType#TYPES */ public void setWillThrow(int index, boolean willThrow) { typeModels[index].setSelected(willThrow); } /** * Performs a command based on the argument. * * @param command A string representing the command. Note that pointer equality (not string * equality) is tested here, so it is important to use the string constants defined in this * class. An IllegalArgumentException will be thrown if the argument is invalid. * @return True iff the command was executed sucessfully. */ public synchronized boolean doCommand(String command, Object extraInfo) { //////////////////////////////////////////////////////////////////////////////////////////// if (command == UPDATE_COMMAND) { try { ignoreUpdate = true; if (extraInfo == rootModel) { if (rootModel.isTristate()) return false; boolean selected = rootModel.isSelected(); // Set all of the family checkboxes to the root's value for (int ctr = 0; ctr < TExceptionType.FAMILIES.length; ctr++) familyModels[ctr].setSelected(selected); // Set all of the type checkboxes to the root's value for (int ctr = 0; ctr < TExceptionType.TYPES.length; ctr++) typeModels[ctr].setSelected(selected); exceptionTree.repaint(); return true; } for (int ctr = 0; ctr < TExceptionType.FAMILIES.length; ctr++) { if (familyModels[ctr] == extraInfo) { if (familyModels[ctr].isTristate()) return false; boolean selected = familyModels[ctr].isSelected(); // Set all the family members' values to the family's value int memberIndices[] = familyMemberIndices[ctr]; for (int ctr2 = 0; ctr2 < memberIndices.length; ctr2++) typeModels[memberIndices[ctr2]].setSelected(selected); // Test to see if the root is in tristate boolean tristate = false; for (int ctr2 = 0; ctr2 < TExceptionType.FAMILIES.length && !tristate; ctr2++) { if (familyModels[ctr2].isTristate() || familyModels[ctr2].isSelected() != selected) { tristate = true; } } if (tristate) rootModel.setTristate(true); else rootModel.setSelected(selected); exceptionTree.repaint(); return true; } } for (int ctr = 0; ctr < TExceptionType.TYPES.length; ctr++) { if (typeModels[ctr] == extraInfo) { if (typeModels[ctr].isTristate()) throw new IllegalStateException(); boolean selected = typeModels[ctr].isSelected(); boolean tristate = false; // Test to see if the family is in tristate int family = TExceptionType.TYPES[ctr].getFamily(); int memberIndices[] = familyMemberIndices[family]; for (int ctr2 = 0; ctr2 < memberIndices.length && !tristate; ctr2++) { if (typeModels[memberIndices[ctr2]].isSelected() != selected) tristate = true; } if (tristate) familyModels[family].setTristate(true); else familyModels[family].setSelected(selected); // Test to see of the root is in tristate for (int ctr2 = 0; ctr2 < TExceptionType.FAMILIES.length && !tristate; ctr2++) { if (familyModels[ctr2].isTristate() || familyModels[ctr2].isSelected() != selected) { tristate = true; } } if (tristate) rootModel.setTristate(true); else rootModel.setSelected(selected); exceptionTree.repaint(); return true; } } throw new IllegalArgumentException("Unknown source: " + extraInfo); } finally { ignoreUpdate = false; } } throw new IllegalArgumentException(); } /** * Implement ChangeListener to listen to changes in the checkbox states. */ public void stateChanged(ChangeEvent e) { if (ignoreUpdate) return; doCommand(UPDATE_COMMAND, e.getSource()); } /** * Implement TreeSelectionListener to listen to changes in the tree's selection. */ public void valueChanged(TreeSelectionEvent e) { TreePath path = e.getPath(); if (path == null) { descriptionTextArea.setText(""); return; } DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); CheckBoxCell cell = (CheckBoxCell)node.getUserObject(); TristateButtonModel model = cell.getModel(); if (model == rootModel) { descriptionTextArea.setText(""); return; } for (int ctr = 0; ctr < TExceptionType.FAMILIES.length; ctr++) { if (familyModels[ctr] == model) { descriptionTextArea.setText(""); return; } } for (int ctr = 0; ctr < TExceptionType.TYPES.length; ctr++) { if (typeModels[ctr] == model) { descriptionTextArea.setText(TExceptionType.TYPES[ctr].getDescription()); return; } } throw new IllegalArgumentException("Unknown cell: " + cell); } /** * CheckBoxCell is actually a JComponent containing a checkbox and a label. It is used to * render and edit a cell in a tree. * * @author btsang * @version 7.1 */ protected class CheckBoxCell extends JPanel { private String text; private TristateButtonModel model; private JPanel labelPanel; /** * Constructs a new CheckBoxCell. */ public CheckBoxCell(String text) { super(new GridBagLayout()); if (text == null) throw new NullPointerException(); this.text = text; PTristateCheckBox checkBox = new PTristateCheckBox(); checkBox.setForeground(null); checkBox.setBackground(null); checkBox.setFont(null); model = (TristateButtonModel)checkBox.getModel(); add( checkBox, new GridBagConstraints( 0, 0, 1, 1, 0.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0 ) ); { // Prepare the label panel labelPanel = new JPanel(new BorderLayout()); JLabel label = new JLabel(text); label.setForeground(null); label.setBackground(null); label.setFont(null); labelPanel.add(label, BorderLayout.CENTER); add( labelPanel, new GridBagConstraints( 1, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0 ) ); } setSelectedInTree(false); setFocusedInTree(false); } /** * Returns the model associated with the checkbox. */ public TristateButtonModel getModel() { return model; } /** * Sets wheter or not the CheckBoxCell is selected in the tree. */ public void setSelectedInTree(boolean selectedInTree) { if (selectedInTree) { labelPanel.setForeground(SELECTION_FOREGROUND); labelPanel.setBackground(SELECTION_BACKGROUND); } else { labelPanel.setForeground(null); labelPanel.setBackground(null); } } /** * Sets wheter or not the CheckBoxCell is focused in the tree. */ public void setFocusedInTree(boolean focusedInTree) { if (focusedInTree) { labelPanel.setBorder(FOCUSED_BORDER); } else { labelPanel.setBorder(UNFOCUSED_BORDER); } } /** * Override toString to provide a cleaner string for clipboard ops performed on the tree. */ public String toString() { return text; } } /** * CheckBoxCellEditor provides a Component which edits a CheckBoxCell for the TExceptionChooser. * * @author btsang * @version 7.1 */ protected class CheckBoxCellEditor extends AbstractCellEditor implements TreeCellEditor { private CheckBoxCell currentCell; /** * Creates a new CheckBoxCellEditor. */ public CheckBoxCellEditor() { } /** * Returns the value contained in the editor (that would be the CheckBoxCell we were given * to edit). */ public Object getCellEditorValue() { return currentCell; } /** * Implement TreeCellEditor to extract the CheckBoxCell from a DefaultMutableTreeNode, * configure its display properties, and return that component. */ public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; CheckBoxCell cell = (CheckBoxCell)node.getUserObject(); cell.setForeground(tree.getForeground()); cell.setBackground(tree.getBackground()); cell.setFont(tree.getFont()); cell.setSelectedInTree(true); cell.setFocusedInTree(true); return cell; } } /** * CheckBoxCellRenderer renders a CheckBoxCell for the TExceptionChooser. * * @author btsang * @version 7.1 */ protected class CheckBoxCellRenderer implements TreeCellRenderer { /** * Creates a new CheckBoxCellRenderer. */ public CheckBoxCellRenderer() { } /** * Implement TreeCellRenderer to extract the CheckBoxCell from a DefaultMutableTreeNode, * configure its display properties, and return that component. */ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; CheckBoxCell cell = (CheckBoxCell)node.getUserObject(); cell.setForeground(tree.getForeground()); cell.setBackground(tree.getBackground()); cell.setFont(tree.getFont()); cell.setSelectedInTree(selected); cell.setFocusedInTree(hasFocus); return cell; } } }