package rescuecore2.misc.gui; import java.awt.Component; import java.awt.BorderLayout; import java.awt.Color; import java.awt.event.MouseEvent; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import javax.swing.JTree; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.ToolTipManager; import javax.swing.AbstractCellEditor; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreeCellEditor; import javax.swing.tree.TreePath; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import java.util.List; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.HashMap; import java.util.EventObject; import rescuecore2.config.Config; import rescuecore2.config.ConfigConstraint; /** A JTree that knows how to display and edit Config objects. */ public class ConfigTree extends JTree { private Config config; /** Create a ConfigTree that will display a particular Config. @param config The Config to display. */ public ConfigTree(Config config) { this.config = config; DefaultMutableTreeNode root = new DefaultMutableTreeNode("Config"); List<String> keys = new ArrayList<String>(config.getAllKeys()); Collections.sort(keys); buildModel(root, keys); setModel(new DefaultTreeModel(root)); setEditable(true); setCellRenderer(new ConfigEntryCellRenderer()); setCellEditor(new ConfigEntryCellEditor()); setInvokesStopCellEditing(true); ToolTipManager.sharedInstance().registerComponent(this); } private void buildModel(DefaultMutableTreeNode root, Collection<String> keys) { Map<String, DefaultMutableTreeNode> branches = new HashMap<String, DefaultMutableTreeNode>(); for (String next : keys) { String[] branchNames = next.split("\\."); // Create all parent branches if required DefaultMutableTreeNode parent = root; StringBuilder branchName = new StringBuilder(); for (int i = 0; i < branchNames.length - 1; ++i) { branchName.append(branchNames[i]); String name = branchName.toString(); DefaultMutableTreeNode nextParent = branches.get(name); if (nextParent == null) { nextParent = new DefaultMutableTreeNode(new ConfigCategoryNode(name)); branches.put(name, nextParent); parent.add(nextParent); } branchName.append("."); parent = nextParent; } // Create the leaf node DefaultMutableTreeNode leaf = new DefaultMutableTreeNode(new ConfigEntryNode(next, config.getValue(next))); parent.add(leaf); } } private static final class ConfigCategoryNode { private String prefix; private ConfigCategoryNode(String prefix) { this.prefix = prefix; } @Override public String toString() { return prefix; } } private static final class ConfigEntryNode { private String key; private String value; private ConfigEntryNode(String key, String value) { this.key = key; this.value = value; } String getKey() { return key; } String getValue() { return value; } @Override public String toString() { return key + ": " + value; } } private final class ConfigEntryCellRenderer extends JLabel implements TreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { DefaultMutableTreeNode node = ((DefaultMutableTreeNode)value); Object o = node.getUserObject(); setText(o.toString()); setOpaque(false); setToolTipText(null); if (o instanceof ConfigEntryNode) { ConfigEntryNode entry = (ConfigEntryNode)o; String key = entry.getKey(); StringBuilder problems = new StringBuilder(); for (ConfigConstraint constraint : config.getViolatedConstraints()) { if (constraint.getKeys().contains(key)) { if (problems.length() != 0) { problems.append("\n"); } problems.append(constraint.getDescription()); } } if (problems.length() != 0) { setBackground(Color.RED); setOpaque(true); setToolTipText(problems.toString()); } } return this; } } private final class ConfigEntryCellEditor extends AbstractCellEditor implements TreeCellEditor, ActionListener { private JPanel panel; private JLabel label; private JTextField field; private DefaultMutableTreeNode node; private String key; private ConfigEntryCellEditor() { panel = new JPanel(new BorderLayout()); label = new JLabel(); field = new JTextField(); panel.add(label, BorderLayout.CENTER); panel.add(field, BorderLayout.EAST); field.addActionListener(this); node = null; } @Override public boolean isCellEditable(EventObject o) { if (o instanceof MouseEvent && o.getSource() instanceof JTree) { JTree tree = (JTree)o.getSource(); MouseEvent e = (MouseEvent)o; if (e.getClickCount() > 1) { TreePath path = tree.getPathForLocation(e.getX(), e.getY()); Object leaf = path.getLastPathComponent(); if (leaf instanceof DefaultMutableTreeNode) { Object content = ((DefaultMutableTreeNode)leaf).getUserObject(); return content instanceof ConfigEntryNode; } } } return false; } @Override public Component getTreeCellEditorComponent(JTree tree, Object data, boolean selected, boolean expanded, boolean leaf, int row) { node = ((DefaultMutableTreeNode)data); ConfigEntryNode entry = (ConfigEntryNode)node.getUserObject(); key = entry.getKey(); label.setText(key + ": "); field.setText(entry.getValue()); return panel; } @Override public Object getCellEditorValue() { return new ConfigEntryNode(key, field.getText()); } @Override public boolean stopCellEditing() { if (node != null) { String value = field.getText(); node.setUserObject(new ConfigEntryNode(key, value)); config.setValue(key, value); } fireEditingStopped(); return true; } @Override public void actionPerformed(ActionEvent e) { stopCellEditing(); } } }