package org.greenstone.gatherer.util; /** *######################################################################### * * A component of the Gatherer application, part of the Greenstone digital * library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Author: John Thompson, Greenstone Digital Library, University of Waikato * * Copyright (C) 1999 New Zealand Digital Library Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *######################################################################## */ import java.util.Vector; import javax.swing.JTree; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import org.greenstone.gatherer.gui.tree.DragTree; /** My latest diabolical class synchronizes the expansion state of two or more JTrees. Muh-hahahaha. Note that these tree should be based on the same model. * @author John Thompson, Greenstone Digital Library, University of Waikato * @version 2.1 */ final public class TreeSynchronizer extends Vector implements TreeExpansionListener, TreeSelectionListener { /** true if we should temporarily ignore further events, most likely because we know our actions are causing them. */ private boolean ignore; /** A list of tree selection listeners. */ private Vector selection_listeners = new Vector(); /** Add a new tree to the synchronization list of trees to be synchronized. * @param tree The lastest victim, a JTree. */ public void add(JTree tree) { super.add(tree); tree.addTreeExpansionListener(this); //tree.addTreeSelectionListener(this); } /** We allow the Gatherer to add tree listeners to this class, as it persists between collection changes transparently. Thus there is no need to reattach listeners everytime the collection changes. */ public void addTreeSelectionListener(TreeSelectionListener listener) { if(!selection_listeners.contains(listener)) { selection_listeners.add(listener); } } /** Called whenever an item in the tree has been collapsed. * @param event A TreeExpansionEvent containing information about the event. */ public void treeCollapsed(TreeExpansionEvent event) { if(!ignore) { ignore = true; // Collapse that path in all registered trees. JTree tree = (JTree)event.getSource(); TreePath path = event.getPath(); for(int i = size(); i != 0; i--) { JTree sibling = (JTree) get(i - 1); if(!sibling.equals(tree)) { sibling.collapsePath(path); } } ignore = false; } } /** Called whenever an item in the tree has been expanded. * @param event A TreeExpansionEvent containing information about the event. */ public void treeExpanded(TreeExpansionEvent event) { if(!ignore) { ignore = true; // Expand that path in all registered trees. JTree tree = (JTree)event.getSource(); TreePath path = event.getPath(); for(int i = size(); i != 0; i--) { JTree sibling = (JTree) get(i - 1); if(!sibling.equals(tree)) { sibling.expandPath(path); } } ignore = false; } } /** Called whenever the one of the trees selection changes. * @param event A TreeSelectionEvent containing information about the event. */ public void valueChanged(TreeSelectionEvent event) { if(!ignore) { ignore = true; // Recover the tree that is the source. JTree tree = (JTree) event.getSource(); // Brute Force approach. // Extract the currently selected paths. TreePath paths[] = tree.getSelectionPaths(); // Then for every registered tree, that isn't this one, ensure those paths are selected. for(int i = size(); paths != null && i != 0; i--) { JTree sibling = (JTree) get(i - 1); if(!sibling.equals(tree)) { // One last thing to do. If this is actually a DragTree we are dealing with we have to tell it to set the selection values immediately, not wait for the clear until it is sure it is no the pre-cursor to a drag. if(sibling instanceof DragTree) { DragTree gtree = (DragTree)sibling; gtree.setImmediate(true); gtree.setSelectionPaths(paths); // I'm going to ensure that the last selected path is visible. gtree.scrollPathToVisible(paths[paths.length - 1]); gtree.setImmediate(false); } else { sibling.setSelectionPaths(paths); sibling.scrollPathToVisible(paths[paths.length - 1]); } } } // Pass on message to all listeners. for(int i = 0; i < selection_listeners.size(); i++) { ((TreeSelectionListener) selection_listeners.get(i)).valueChanged(event); } ignore = false; } } }