/*
* Copyright 2014 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.benchmark.impl.aggregator.swingui;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.optaplanner.benchmark.impl.result.SingleBenchmarkResult;
import static org.optaplanner.benchmark.impl.aggregator.swingui.MixedCheckBox.MixedCheckBoxStatus.*;
public class CheckBoxTree extends JTree {
private static final Color TREE_SELECTION_COLOR = UIManager.getColor("Tree.selectionBackground");
private Set<DefaultMutableTreeNode> selectedSingleBenchmarkNodes = new HashSet<>();
public CheckBoxTree(DefaultMutableTreeNode root) {
super(root);
addMouseListener(new CheckBoxTreeMouseListener(this));
setCellRenderer(new CheckBoxTreeCellRenderer());
setToggleClickCount(0);
getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
}
public Set<DefaultMutableTreeNode> getSelectedSingleBenchmarkNodes() {
return selectedSingleBenchmarkNodes;
}
public void setSelectedSingleBenchmarkNodes(Set<DefaultMutableTreeNode> selectedSingleBenchmarkNodes) {
this.selectedSingleBenchmarkNodes = selectedSingleBenchmarkNodes;
}
public void expandNodes() {
expandSubtree(null, true);
}
public void collapseNodes() {
expandSubtree(null, false);
}
private void expandSubtree(TreePath path, boolean expand) {
if (path == null) {
TreePath selectionPath = getSelectionPath();
path = selectionPath == null ? new TreePath(treeModel.getRoot()) : selectionPath;
}
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) path.getLastPathComponent();
Enumeration children = currentNode.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
TreePath expandedPath = path.pathByAddingChild(child);
expandSubtree(expandedPath, expand);
}
if (expand) {
expandPath(path);
} else if (path.getParentPath() != null) {
collapsePath(path);
}
}
public void updateHierarchyCheckBoxStates() {
for (DefaultMutableTreeNode currentNode : selectedSingleBenchmarkNodes) {
resolveNewCheckBoxState(currentNode, CHECKED, MIXED);
}
treeDidChange();
}
private void resolveNewCheckBoxState(DefaultMutableTreeNode currentNode, MixedCheckBox.MixedCheckBoxStatus newStatus,
MixedCheckBox.MixedCheckBoxStatus mixedStatus) {
MixedCheckBox checkBox = (MixedCheckBox) currentNode.getUserObject();
checkBox.setStatus(newStatus);
selectChildren(currentNode, newStatus);
TreeNode[] ancestorNodes = currentNode.getPath();
// examine ancestors, don't lose track of most recent changes - bottom-up approach
for (int i = ancestorNodes.length - 2; i >= 0; i--) {
DefaultMutableTreeNode ancestorNode = (DefaultMutableTreeNode) ancestorNodes[i];
MixedCheckBox ancestorCheckbox = (MixedCheckBox) ancestorNode.getUserObject();
if (checkChildren(ancestorNode, newStatus)) {
ancestorCheckbox.setStatus(newStatus);
} else {
if (mixedStatus == null) {
break;
}
ancestorCheckbox.setStatus(mixedStatus);
}
}
}
private void selectChildren(DefaultMutableTreeNode parent, MixedCheckBox.MixedCheckBoxStatus status) {
MixedCheckBox box = (MixedCheckBox) parent.getUserObject();
if (box.getBenchmarkResult() instanceof SingleBenchmarkResult) {
if (status == CHECKED) {
selectedSingleBenchmarkNodes.add(parent);
} else if (status == UNCHECKED) {
selectedSingleBenchmarkNodes.remove(parent);
}
}
Enumeration children = parent.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
MixedCheckBox childCheckBox = (MixedCheckBox) child.getUserObject();
childCheckBox.setStatus(status);
selectChildren(child, status);
}
}
private boolean checkChildren(DefaultMutableTreeNode parent, MixedCheckBox.MixedCheckBoxStatus status) {
boolean childrenCheck = true;
Enumeration children = parent.children();
while (children.hasMoreElements()) {
DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
MixedCheckBox checkBox = (MixedCheckBox) child.getUserObject();
if (checkBox.getStatus() != status) {
childrenCheck = false;
break;
}
}
return childrenCheck;
}
private class CheckBoxTreeMouseListener extends MouseAdapter {
private CheckBoxTree tree;
private double unlabeledMixedCheckBoxWidth;
public CheckBoxTreeMouseListener(CheckBoxTree tree) {
this.tree = tree;
unlabeledMixedCheckBoxWidth = new MixedCheckBox().getPreferredSize().getWidth();
}
@Override
public void mousePressed(MouseEvent e) {
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if (path != null) {
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) path.getLastPathComponent();
MixedCheckBox checkBox = (MixedCheckBox) currentNode.getUserObject();
// ignore clicks on checkbox's label - enables to select it without changing the state
if (e.getX() - tree.getPathBounds(path).getX() > unlabeledMixedCheckBoxWidth) {
return;
}
switch (checkBox.getStatus()) {
case CHECKED:
resolveNewCheckBoxState(currentNode, UNCHECKED, MIXED);
break;
case UNCHECKED:
resolveNewCheckBoxState(currentNode, CHECKED, MIXED);
break;
case MIXED:
resolveNewCheckBoxState(currentNode, CHECKED, null);
break;
default:
throw new IllegalStateException("The status (" + checkBox.getStatus() + ") is not implemented.");
}
tree.treeDidChange();
}
}
}
private static class CheckBoxTreeCellRenderer implements TreeCellRenderer {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
MixedCheckBox checkBox = (MixedCheckBox) node.getUserObject();
checkBox.setBackground(selected ? TREE_SELECTION_COLOR : Color.WHITE);
return checkBox;
}
}
}