Created
December 8, 2015 18:36
-
-
Save aterai/66c51860e28dacea52f1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//package example; | |
//-*- mode:java; encoding:utf-8 -*- | |
// vim:set fileencoding=utf-8: | |
//http://java-swing-tips.blogspot.jp/2012/02/jcheckbox-node-jtree.html | |
import java.awt.*; | |
import java.awt.event.*; | |
import java.util.*; | |
import javax.swing.*; | |
import javax.swing.event.*; | |
import javax.swing.tree.*; | |
public class TreeModelListenerTest { | |
private JComponent makeUI() { | |
JTree tree = new JTree() { | |
@Override public void updateUI() { | |
setCellRenderer(null); | |
setCellEditor(null); | |
super.updateUI(); | |
setCellRenderer(new CheckBoxNodeRenderer()); | |
setCellEditor(new CheckBoxNodeEditor()); | |
} | |
}; | |
TreeModel model = tree.getModel(); | |
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot(); | |
Enumeration e = root.breadthFirstEnumeration(); | |
while (e.hasMoreElements()) { | |
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement(); | |
node.setUserObject(new CheckBoxNode(Objects.toString(node.getUserObject(), ""), Status.DESELECTED)); | |
} | |
model.addTreeModelListener(new CheckBoxStatusUpdateListener()); | |
tree.setEditable(true); | |
tree.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); | |
tree.expandRow(0); | |
JPanel p = new JPanel(new BorderLayout()); | |
p.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); | |
p.add(new JScrollPane(tree)); | |
return p; | |
} | |
public static void main(String... args) { | |
EventQueue.invokeLater(new Runnable() { | |
@Override public void run() { | |
createAndShowGUI(); | |
} | |
}); | |
} | |
public static void createAndShowGUI() { | |
JFrame f = new JFrame(); | |
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); | |
f.getContentPane().add(new TreeModelListenerTest().makeUI()); | |
f.setSize(320, 240); | |
f.setLocationRelativeTo(null); | |
f.setVisible(true); | |
} | |
} | |
class TriStateCheckBox extends JCheckBox { | |
@Override public void updateUI() { | |
Icon currentIcon = getIcon(); | |
setIcon(null); | |
super.updateUI(); | |
if (Objects.nonNull(currentIcon)) { | |
setIcon(new IndeterminateIcon()); | |
} | |
setOpaque(false); | |
} | |
} | |
class IndeterminateIcon implements Icon { | |
private static final Color FOREGROUND = new Color(50, 20, 255, 200); | |
private static final int SIDE_MARGIN = 4; | |
private static final int HEIGHT = 2; | |
private final Icon icon = UIManager.getIcon("CheckBox.icon"); | |
@Override public void paintIcon(Component c, Graphics g, int x, int y) { | |
icon.paintIcon(c, g, x, y); | |
int w = getIconWidth(); | |
int h = getIconHeight(); | |
Graphics2D g2 = (Graphics2D) g.create(); | |
g2.setPaint(FOREGROUND); | |
g2.translate(x, y); | |
g2.fillRect(SIDE_MARGIN, (h - HEIGHT) / 2, w - SIDE_MARGIN - SIDE_MARGIN, HEIGHT); | |
g2.dispose(); | |
} | |
@Override public int getIconWidth() { | |
return icon.getIconWidth(); | |
} | |
@Override public int getIconHeight() { | |
return icon.getIconHeight(); | |
} | |
} | |
enum Status { SELECTED, DESELECTED, INDETERMINATE } | |
class CheckBoxNode { | |
public final String label; | |
public final Status status; | |
protected CheckBoxNode(String label) { | |
this.label = label; | |
status = Status.INDETERMINATE; | |
} | |
protected CheckBoxNode(String label, Status status) { | |
this.label = label; | |
this.status = status; | |
} | |
@Override public String toString() { | |
return label; | |
} | |
} | |
class CheckBoxStatusUpdateListener implements TreeModelListener { | |
private boolean adjusting; | |
@Override public void treeNodesChanged(TreeModelEvent e) { | |
if (adjusting) { | |
return; | |
} | |
adjusting = true; | |
Object[] children = e.getChildren(); | |
DefaultTreeModel model = (DefaultTreeModel) e.getSource(); | |
DefaultMutableTreeNode node; | |
CheckBoxNode c; // = (CheckBoxNode) node.getUserObject(); | |
if (Objects.nonNull(children) && children.length == 1) { | |
node = (DefaultMutableTreeNode) children[0]; | |
c = (CheckBoxNode) node.getUserObject(); | |
System.out.println(node + ": " + c.status); | |
TreePath parent = e.getTreePath(); | |
DefaultMutableTreeNode n = (DefaultMutableTreeNode) parent.getLastPathComponent(); | |
while (Objects.nonNull(n)) { | |
updateParentUserObject(n); | |
DefaultMutableTreeNode tmp = (DefaultMutableTreeNode) n.getParent(); | |
if (Objects.nonNull(tmp)) { | |
n = tmp; | |
} else { | |
break; | |
} | |
} | |
model.nodeChanged(n); | |
} else { | |
node = (DefaultMutableTreeNode) model.getRoot(); | |
c = (CheckBoxNode) node.getUserObject(); | |
} | |
updateAllChildrenUserObject(node, c.status); | |
model.nodeChanged(node); | |
adjusting = false; | |
} | |
private void updateParentUserObject(DefaultMutableTreeNode parent) { | |
int selectedCount = 0; | |
Enumeration children = parent.children(); | |
while (children.hasMoreElements()) { | |
DefaultMutableTreeNode node = (DefaultMutableTreeNode) children.nextElement(); | |
CheckBoxNode check = (CheckBoxNode) node.getUserObject(); | |
if (check.status == Status.INDETERMINATE) { | |
selectedCount = -1; | |
break; | |
} | |
if (check.status == Status.SELECTED) { | |
selectedCount++; | |
} | |
} | |
String label = ((CheckBoxNode) parent.getUserObject()).label; | |
if (selectedCount == 0) { | |
parent.setUserObject(new CheckBoxNode(label, Status.DESELECTED)); | |
} else if (selectedCount == parent.getChildCount()) { | |
parent.setUserObject(new CheckBoxNode(label, Status.SELECTED)); | |
} else { | |
parent.setUserObject(new CheckBoxNode(label)); | |
} | |
} | |
private void updateAllChildrenUserObject(DefaultMutableTreeNode root, Status status) { | |
Enumeration breadth = root.breadthFirstEnumeration(); | |
while (breadth.hasMoreElements()) { | |
DefaultMutableTreeNode node = (DefaultMutableTreeNode) breadth.nextElement(); | |
if (Objects.equals(root, node)) { | |
continue; | |
} | |
CheckBoxNode check = (CheckBoxNode) node.getUserObject(); | |
node.setUserObject(new CheckBoxNode(check.label, status)); | |
} | |
} | |
@Override public void treeNodesInserted(TreeModelEvent e) { | |
/* not needed */ | |
} | |
@Override public void treeNodesRemoved(TreeModelEvent e) { | |
/* not needed */ | |
} | |
@Override public void treeStructureChanged(TreeModelEvent e) { | |
/* not needed */ | |
} | |
} | |
class CheckBoxNodeRenderer implements TreeCellRenderer { | |
private final JPanel panel = new JPanel(new BorderLayout()); | |
private final DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); | |
private final TriStateCheckBox checkBox = new TriStateCheckBox(); | |
protected CheckBoxNodeRenderer() { | |
super(); | |
panel.setFocusable(false); | |
panel.setRequestFocusEnabled(false); | |
panel.setOpaque(false); | |
panel.add(checkBox, BorderLayout.WEST); | |
checkBox.setOpaque(false); | |
} | |
@Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { | |
JLabel l = (JLabel) renderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); | |
l.setFont(tree.getFont()); | |
if (value instanceof DefaultMutableTreeNode) { | |
checkBox.setEnabled(tree.isEnabled()); | |
checkBox.setFont(tree.getFont()); | |
Object userObject = ((DefaultMutableTreeNode) value).getUserObject(); | |
if (userObject instanceof CheckBoxNode) { | |
CheckBoxNode node = (CheckBoxNode) userObject; | |
if (node.status == Status.INDETERMINATE) { | |
checkBox.setIcon(new IndeterminateIcon()); | |
} else { | |
checkBox.setIcon(null); | |
} | |
l.setText(node.label); | |
checkBox.setSelected(node.status == Status.SELECTED); | |
} | |
panel.add(l); | |
return panel; | |
} | |
return l; | |
} | |
} | |
class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor { | |
private final JPanel panel = new JPanel(new BorderLayout()); | |
private final DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); | |
private final TriStateCheckBox checkBox = new TriStateCheckBox(); | |
private String str; | |
protected CheckBoxNodeEditor() { | |
super(); | |
checkBox.setOpaque(false); | |
checkBox.setFocusable(false); | |
checkBox.addActionListener(new ActionListener() { | |
@Override public void actionPerformed(ActionEvent e) { | |
stopCellEditing(); | |
} | |
}); | |
panel.setFocusable(false); | |
panel.setRequestFocusEnabled(false); | |
panel.setOpaque(false); | |
panel.add(checkBox, BorderLayout.WEST); | |
} | |
@Override public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { | |
JLabel l = (JLabel) renderer.getTreeCellRendererComponent(tree, value, true, expanded, leaf, row, true); | |
l.setFont(tree.getFont()); | |
if (value instanceof DefaultMutableTreeNode) { | |
checkBox.setEnabled(tree.isEnabled()); | |
checkBox.setFont(tree.getFont()); | |
Object userObject = ((DefaultMutableTreeNode) value).getUserObject(); | |
if (userObject instanceof CheckBoxNode) { | |
CheckBoxNode node = (CheckBoxNode) userObject; | |
if (node.status == Status.INDETERMINATE) { | |
checkBox.setIcon(new IndeterminateIcon()); | |
} else { | |
checkBox.setIcon(null); | |
} | |
l.setText(node.label); | |
checkBox.setSelected(node.status == Status.SELECTED); | |
str = node.label; | |
} | |
panel.add(l); | |
return panel; | |
} | |
return l; | |
} | |
@Override public Object getCellEditorValue() { | |
return new CheckBoxNode(str, checkBox.isSelected() ? Status.SELECTED : Status.DESELECTED); | |
} | |
@Override public boolean isCellEditable(EventObject e) { | |
if (e instanceof MouseEvent && e.getSource() instanceof JTree) { | |
MouseEvent me = (MouseEvent) e; | |
JTree tree = (JTree) e.getSource(); | |
TreePath path = tree.getPathForLocation(me.getX(), me.getY()); | |
Rectangle r = tree.getPathBounds(path); | |
if (Objects.isNull(r)) { | |
return false; | |
} | |
Dimension d = checkBox.getPreferredSize(); | |
r.setSize(new Dimension(d.width, r.height)); | |
if (r.contains(me.getX(), me.getY())) { | |
return true; | |
} | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment