package org.greenstone.gatherer.util; import javax.swing.*; import javax.swing.tree.*; /** This synchronized TreeModel is comprised of two seperate models. The extended model is used to paint the screen, and can only be updated on the AWTEvent Thread. The second, private, model contains the current actual state of the model with changes made immediately. If such changes occur then a task is queued in the AWTEvent Thread to update the 'painted' model. This model depends on the TreeNodes used having these properties: * TreeNode x_node = new TreeNode("x"); * TreeNode y_node = x_node.cloneNode(); * for(int i = 0; i < x_node.getChildCount(); i++) { * x_node.getChildAt(i) != y_node.getChildAt(i); * x_node.getChildAt(i).equals(y_node.getChildAt(i)); * } * x_node != y_node; * (new TreeNode("x")).equals(new TreeNode("x")); * In other words, any two different instances of the tree node must be equal according to the equals method. * Methods which need data from the tree should also use the model methods (ie model.getChildCount(node) rather than node.getChildCount()) and should not be in the AWTEvent Thread (as such calls will see the possibly out of date 'visual' model, not the actual underlying model). */ public class SynchronizedTreeModel extends DefaultTreeModel implements Runnable { private boolean changed = false; private DefaultTreeModel offscreen; private TreeModelTest test = null; SynchronizedTreeModel(SynchronizedTreeNode root) { super(root); offscreen = new DefaultTreeModel(root.cloneNode()); } SynchronizedTreeModel(SynchronizedTreeNode root, TreeModelTest test) { super(root); offscreen = new DefaultTreeModel(root.cloneNode()); this.test = test; } /** Returns the child of parent at index index in the parent's child array. */ public Object getChild(Object parent, int index) { if(isEventThread()) { return super.getChild(parent, index); } else { return offscreen.getChild(parent, index); } } /** Returns the number of children of parent. */ public int getChildCount(Object parent) { if(isEventThread()) { return super.getChildCount(parent); } else { return offscreen.getChildCount(parent); } } /** Builds the parents of node up to and including the root node, where the original node is the last element in the returned array. This is probably the most 'expensive' method we make synchronized, but it isn't called often so thats ok. */ public TreeNode[] getPathToRoot(TreeNode aNode) { if(isEventThread()) { return super.getPathToRoot(aNode); } else { return offscreen.getPathToRoot(aNode); } } public Object getRoot() { if(isEventThread()) { return super.getRoot(); } else { return offscreen.getRoot(); } } /** Invoked this to insert newChild at location index in parents children. */ public void insertNodeInto(MutableTreeNode newChild, MutableTreeNode parent, int index) { if(isEventThread()) { if(test != null) test.debug("insertNodeInto(" + newChild + ", " + parent + ", " + index + ")"); super.insertNodeInto(newChild, parent, index); } else { if(test != null) test.debug("offscreen.insertNodeInto(" + newChild + ", " + parent + ", " + index + ")"); offscreen.insertNodeInto(newChild, parent, index); queueUpdate(); } } /** Message this to remove node from its parent. */ public void removeNodeFromParent(MutableTreeNode node) { if(isEventThread()) { if(test != null) test.debug("removeNodeFromParent(" + node + ")"); super.removeNodeFromParent(node); } else { if(test != null) test.debug("offscreen.removeNodeFromParent(" + node + ")"); offscreen.removeNodeFromParent(node); queueUpdate(); } } public void run() { synchronized(this) { if(changed) { setRoot(((SynchronizedTreeNode)(offscreen.getRoot())). cloneNode()); nodeChanged(root); changed = false; if(test != null) test.debug("Painted model refreshed."); } else { if(test != null) test.debug("Painted model is current."); } } } /** Determine if this call is happening on the AWTEvent Dispatch Thread. */ private boolean isEventThread() { return SwingUtilities.isEventDispatchThread(); } private synchronized void queueUpdate() { changed = true; SwingUtilities.invokeLater(this); if(test != null) test.debug("Painted model is obsolete."); } }