Skip to content

Instantly share code, notes, and snippets.

@aterai
Created December 8, 2015 18:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aterai/66c51860e28dacea52f1 to your computer and use it in GitHub Desktop.
Save aterai/66c51860e28dacea52f1 to your computer and use it in GitHub Desktop.
//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