/******************************************************************************* * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com) * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License 3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * ******************************************************************************/ package com.opendoorlogistics.core.utils.ui; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import com.opendoorlogistics.codefromweb.JCheckBoxTree; import com.opendoorlogistics.core.utils.StringIdTreeNode; import com.opendoorlogistics.core.utils.strings.HasStringId; /** * A tree with checkboxes which allows its state to be saved before * the treemodel is replaced and then restored as best possible - so * if nodes are added or removed the existing nodes should stay the same * @author Phil * */ public class JCheckBoxTreeExt extends JCheckBoxTree{ public static interface TreeNodeStateRetriever<TS>{ TS getState(TreePath path); } private void applyExpandedState(StringIdTreeNode<Boolean> expandedState){ for (int i = 0; i < getRowCount(); i++) { TreePath path = getPathForRow(i); if(path.getLastPathComponent() == getModel().getRoot()){ // always expand the root expandPath(path); }else{ Boolean expanded = expandedState.findLeaf(getIdPathExcludeRoot(path)); if(expanded!=null && expanded){ expandPath(path); } } } } public static class CheckBoxTreeState{ private final StringIdTreeNode<Boolean> expandedState; private final StringIdTreeNode<Boolean> checkedState; public StringIdTreeNode<Boolean> getExpandedState() { return expandedState; } public StringIdTreeNode<Boolean> getCheckedState() { return checkedState; } public CheckBoxTreeState(StringIdTreeNode<Boolean> expandedState, StringIdTreeNode<Boolean> checkedState) { super(); this.expandedState = expandedState; this.checkedState = checkedState; } } private void applyCheckedState(final StringIdTreeNode<Boolean> checkedState){ Object root = getModel().getRoot(); if(root==null){ return; } class Recurse{ void recurse(TreePath path){ Boolean checked = checkedState.findLeaf(getIdPathExcludeRoot(path)); if(checked!=null){ if(isChecked(path)!=checked){ toggleCheckState(path); } } Object node = path.getLastPathComponent(); TreeModel model = getModel(); int n = model.getChildCount(node); for(int i =0 ; i<n;i++){ recurse(path.pathByAddingChild(model.getChild(node, i))); } } } new Recurse().recurse(new TreePath(root)); } private String[] getIdPathExcludeRoot(TreePath path){ Object[]objs = path.getPath(); String[] ret = new String[objs.length-1]; for(int i =0 ; i < ret.length ;i++){ ret[i]=getId(objs[i+1]); } return ret; } public CheckBoxTreeState saveState(){ return new CheckBoxTreeState(saveExpandedState(), saveCheckedState()); } public void restoreState(CheckBoxTreeState state){ applyExpandedState(state.getExpandedState()); applyCheckedState(state.getCheckedState()); } private StringIdTreeNode<Boolean> saveExpandedState(){ Object root = getModel().getRoot(); if(root!=null){ return recurseSaveState(null, new TreePath(root), new TreeNodeStateRetriever<Boolean>() { @Override public Boolean getState(TreePath path) { return isExpanded(path); } }); } return null; } public StringIdTreeNode<Boolean> saveCheckedState(){ Object root = getModel().getRoot(); if(root!=null){ return recurseSaveState(null, new TreePath(root), new TreeNodeStateRetriever<Boolean>() { @Override public Boolean getState(TreePath path) { return isChecked(path); } }); } return null; } private <T> StringIdTreeNode<T> recurseSaveState( StringIdTreeNode<T> parent, TreePath path,TreeNodeStateRetriever<T> stateRetriever ){ // TreePath path ; Object treeNode =path.getLastPathComponent(); String id = getId(treeNode); T state = stateRetriever.getState(path); StringIdTreeNode<T> ret; if(parent!=null){ ret = parent.add(id, state); }else{ ret = new StringIdTreeNode<>(id,null); ret.setLeaf(state); } TreeModel model = getModel(); int n = model.getChildCount(treeNode); for(int i =0 ; i<n;i++){ Object child = model.getChild(treeNode, i); recurseSaveState(ret, path.pathByAddingChild(child),stateRetriever); } return ret; } private String getId(Object o){ if(HasStringId.class.isInstance(o)){ return ((HasStringId)o).getId(); } return o.toString(); } public static void main(String args[]) { class TestFrame extends JFrame { private static final long serialVersionUID = 4648172894076113183L; private JCheckBoxTreeExt tree; public TestFrame() { super(); setSize(500, 500); getContentPane().setLayout(new BorderLayout()); tree = new JCheckBoxTreeExt(); getContentPane().add(tree,BorderLayout.CENTER); setDefaultCloseOperation(EXIT_ON_CLOSE); class Tester{ void test(boolean restore){ // save stats StringIdTreeNode<Boolean> checkState = tree.saveCheckedState(); StringIdTreeNode<Boolean> expanded = tree.saveExpandedState(); tree.setModel(JTree.getDefaultTreeModel()); // restore state if(restore){ tree.applyCheckedState(checkState); tree.applyExpandedState(expanded); } tree.initCellRenderer(); } } JToolBar toolBar = new JToolBar(); JButton restore = new JButton("Restore"); restore.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new Tester().test(true); } }); toolBar.add(restore); JButton norestore = new JButton("No restore"); norestore.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new Tester().test(false); } }); toolBar.add(norestore); getContentPane().add(toolBar,BorderLayout.SOUTH); } } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { TestFrame m = new TestFrame(); m.setVisible(true); } }); } }