package net.sf.openrocket.gui.main.componenttree; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.JTree; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.ComponentChangeListener; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.BugException; /** * A TreeModel that implements viewing of the rocket tree structure. * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ public class ComponentTreeModel implements TreeModel, ComponentChangeListener { ArrayList<TreeModelListener> listeners = new ArrayList<TreeModelListener>(); private final RocketComponent root; private final JTree tree; public ComponentTreeModel(RocketComponent root, JTree tree) { this.root = root; this.tree = tree; root.addComponentChangeListener(this); } @Override public Object getChild(Object parent, int index) { RocketComponent component = (RocketComponent) parent; try { return component.getChild(index); } catch (IndexOutOfBoundsException e) { return null; } } @Override public int getChildCount(Object parent) { RocketComponent c = (RocketComponent) parent; return c.getChildCount(); } @Override public int getIndexOfChild(Object parent, Object child) { if (parent == null || child == null) return -1; RocketComponent p = (RocketComponent) parent; RocketComponent c = (RocketComponent) child; return p.getChildPosition(c); } @Override public Object getRoot() { return root; } @Override public boolean isLeaf(Object node) { return !((RocketComponent) node).allowsChildren(); } @Override public void addTreeModelListener(TreeModelListener l) { listeners.add(l); } @Override public void removeTreeModelListener(TreeModelListener l) { listeners.remove(l); } private void fireTreeNodeChanged(RocketComponent node) { TreeModelEvent e = new TreeModelEvent(this, makeTreePath(node), null, null); Object[] l = listeners.toArray(); for (int i = 0; i < l.length; i++) ((TreeModelListener) l[i]).treeNodesChanged(e); } private void fireTreeStructureChanged(RocketComponent source) { Object[] path = { root }; // Get currently expanded path IDs Enumeration<TreePath> enumer = tree.getExpandedDescendants(new TreePath(path)); ArrayList<String> expanded = new ArrayList<String>(); if (enumer != null) { while (enumer.hasMoreElements()) { TreePath p = enumer.nextElement(); expanded.add(((RocketComponent) p.getLastPathComponent()).getID()); } } // Send structure change event TreeModelEvent e = new TreeModelEvent(this, path); Object[] l = listeners.toArray(); for (int i = 0; i < l.length; i++) ((TreeModelListener) l[i]).treeStructureChanged(e); // Re-expand the paths for (String id : expanded) { RocketComponent c = root.findComponent(id); if (c == null) continue; tree.expandPath(makeTreePath(c)); } if (source != null) { TreePath p = makeTreePath(source); tree.makeVisible(p); tree.expandPath(p); } } @Override public void valueForPathChanged(TreePath path, Object newValue) { System.err.println("ERROR: valueForPathChanged called?!"); } @Override public void componentChanged(ComponentChangeEvent e) { if (e.isTreeChange() || e.isUndoChange() || e.isMassChange()) { // Tree must be fully updated also in case of an undo change fireTreeStructureChanged(e.getSource()); if (e.isTreeChange() && e.isUndoChange()) { // If the undo has changed the tree structure, some elements may be hidden // unnecessarily // TODO: LOW: Could this be performed better? expandAll(); } } else if (e.isOtherChange()) { fireTreeNodeChanged(e.getSource()); } } public void expandAll() { Iterator<RocketComponent> iterator = root.iterator(false); while (iterator.hasNext()) { tree.makeVisible(makeTreePath(iterator.next())); } } /** * Return the rocket component that a TreePath object is referring to. * * @param path the TreePath * @return the RocketComponent the path is referring to. * @throws NullPointerException if path is null * @throws BugException if the path does not refer to a RocketComponent */ public static RocketComponent componentFromPath(TreePath path) { Object last = path.getLastPathComponent(); if (!(last instanceof RocketComponent)) { throw new BugException("Tree path does not refer to a RocketComponent: " + path.toString()); } return (RocketComponent) last; } /** * Return a TreePath corresponding to the specified rocket component. * * @param component the rocket component * @return a TreePath corresponding to this RocketComponent */ public static TreePath makeTreePath(RocketComponent component) { if (component == null) { throw new NullPointerException(); } RocketComponent c = component; List<RocketComponent> list = new LinkedList<RocketComponent>(); while (c != null) { list.add(0, c); c = c.getParent(); } return new TreePath(list.toArray()); } /** * Return a string describing the path, using component normal names. * * @param treePath the tree path * @return a string representation */ public static String pathToString(TreePath treePath) { StringBuffer sb = new StringBuffer(); sb.append("["); for (Object o : treePath.getPath()) { if (sb.length() > 1) { sb.append("; "); } if (o instanceof RocketComponent) { sb.append(((RocketComponent) o).getComponentName()); } else { sb.append(o.toString()); } } sb.append("]"); return sb.toString(); } }