All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.swing.outline.TreePathSupport Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.netbeans.swing.outline;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.AbstractLayoutCache;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

/** Manages expanded/collapsed paths for the Outline.  Provides services similar
 * to those JTree implements inside its own class body.  Propagates changes
 * in expanded state to the layout cache.
 * 

* Principally what this class does is manage the state of expanded paths which * are not visible, or whose parents have been closed/opened. Whereas the * layout cache retains information only about what is visibly expanded, this * class manages information about any path that has been expanded at some * point in the lifetime of an Outline, so that for example, if A contains B * contains C, and A and B and C are expanded, then the user collapses A, * and later re-expands A, B and C will retain their expanded state and * appear as they did the last time A was expanded. *

* When nodes are removed, the OutlineModel must call removePath() for any * defunct paths to avoid memory leaks by the TreePathSupport holding * references to defunct nodes and not allowing them to be garbage collected. *

* Its addTreeWillExpandListener code supports * ExtTreeWillExpandListener, so such a listener may be notified * if some other listener vetoes a pending expansion event. * * @author Tim Boudreau */ public final class TreePathSupport { private List eListeners = new ArrayList(); private List weListeners = new ArrayList(); private AbstractLayoutCache layout; /** Creates a new instance of TreePathSupport */ public TreePathSupport(OutlineModel mdl, AbstractLayoutCache layout) { this.layout = layout; } /** Clear all expanded path data. This is called if the tree model fires * a structural change, and any or all of the nodes it contains may no * longer be present. */ public void clear() { } /** Expand a path. Notifies the layout cache of the change, * stores the expanded path info (so re-expanding a parent node also re-expands * this path if a parent node containing it is later collapsed). Fires * TreeWillExpand and TreeExpansion events. * @param path The tree path to expand */ public void expandPath (TreePath path) { assert SwingUtilities.isEventDispatchThread(); if (layout.isExpanded(path)) { //It's already expanded, don't waste cycles firing bogus events return; } TreePath parentPath = path.getParentPath(); if (parentPath != null) { expandPath(parentPath); } TreeExpansionEvent e = new TreeExpansionEvent (this, path); try { fireTreeWillExpand(e, true); layout.setExpandedState(path, true); fireTreeExpansion(e, true); } catch (ExpandVetoException eve) { fireTreeExpansionVetoed (e, eve); } } /** Collapse a path. Notifies the layout cache of the change, * stores the expanded path info (so re-expanding a parent node also re-expands * this path if a parent node containing it is later collapsed). Fires * TreeWillExpand and TreeExpansion events. * @param path The tree path to collapse */ public void collapsePath (TreePath path) { assert SwingUtilities.isEventDispatchThread(); if (!layout.isExpanded(path)) { //It's already collapsed, don't waste cycles firing bogus events return; } TreeExpansionEvent e = new TreeExpansionEvent (this, path); try { fireTreeWillExpand(e, false); layout.setExpandedState(path, false); fireTreeExpansion(e, false); } catch (ExpandVetoException eve) { fireTreeExpansionVetoed (e, eve); } } /** Remove a path's data from the list of known paths. Called when * a tree model deletion event occurs * @param path The tree path to remove */ public void removePath (TreePath path) { } private void fireTreeExpansion (TreeExpansionEvent e, boolean expanded) { int size = eListeners.size(); TreeExpansionListener[] listeners = new TreeExpansionListener[size]; synchronized (this) { listeners = eListeners.toArray(listeners); } for (int i=0; i < listeners.length; i++) { if (expanded) { listeners[i].treeExpanded(e); } else { listeners[i].treeCollapsed(e); } } } private void fireTreeWillExpand (TreeExpansionEvent e, boolean expanded) throws ExpandVetoException { int size = weListeners.size(); TreeWillExpandListener[] listeners = new TreeWillExpandListener[size]; synchronized (this) { listeners = weListeners.toArray(listeners); } for (int i=0; i < listeners.length; i++) { if (expanded) { listeners[i].treeWillExpand(e); } else { listeners[i].treeWillCollapse(e); } } } private void fireTreeExpansionVetoed (TreeExpansionEvent e, ExpandVetoException ex) { int size = weListeners.size(); TreeWillExpandListener[] listeners = new TreeWillExpandListener[size]; synchronized (this) { listeners = weListeners.toArray(listeners); } for (int i=0; i < listeners.length; i++) { if (listeners[i] instanceof ExtTreeWillExpandListener) { ((ExtTreeWillExpandListener) listeners[i]).treeExpansionVetoed(e, ex); } } } /** * Test if the tree path is expanded. * @param path The tree path to test * @return true if the path is expanded, false otherwise. */ public boolean hasBeenExpanded(TreePath path) { assert SwingUtilities.isEventDispatchThread(); return (path != null && layout.isExpanded(path)); } /** * Returns true if the node identified by the path is currently expanded, * * @param path the TreePath specifying the node to check * @return false if any of the nodes in the node's path are collapsed, * true if all nodes in the path are expanded */ public boolean isExpanded(TreePath path) { assert SwingUtilities.isEventDispatchThread(); if(path == null) return false; if (!layout.isRootVisible() && path.getParentPath() == null) { return true; // Invisible root is always expanded } // Is this node expanded? boolean nodeExpanded = layout.isExpanded(path); if (!nodeExpanded) { return false; } // It is, make sure its parent is also expanded. TreePath parentPath = path.getParentPath(); if(parentPath != null) return isExpanded(parentPath); return true; } /** * Test if the tree path is visible (the parent path is expanded). * @param path The tree path to test * @return true if the path is visible, false otherwise. */ public boolean isVisible(TreePath path) { if(path != null) { TreePath parentPath = path.getParentPath(); if(parentPath != null) { return isExpanded(parentPath); } // Root. return true; } return false; } /** * Get all expanded descendants of the given tree path. * @param parent Tree path to find the expanded descendants for * @return All expanded descendants. */ public TreePath[] getExpandedDescendants(TreePath parent) { assert SwingUtilities.isEventDispatchThread(); TreePath[] result = new TreePath[0]; if(isExpanded(parent)) { TreePath path; List results = null; Enumeration tpe = layout.getVisiblePathsFrom(parent); if (tpe != null) { while (tpe.hasMoreElements()) { path = tpe.nextElement(); // Add the path if it is expanded, a descendant of parent, // and it is visible (all parents expanded). This is rather // expensive! if (path != parent && layout.isExpanded(path) && parent.isDescendant(path)) { if (results == null) { results = new ArrayList(); } results.add (path); } } if (results != null) { result = results.toArray(result); } } } return result; } /** Add a TreeExpansionListener. If the TreeWillExpandListener implements * ExtTreeExpansionListener, it will be notified if another * TreeWillExpandListener vetoes the expansion event * @param l The tree expansion listener */ public synchronized void addTreeExpansionListener (TreeExpansionListener l) { eListeners.add(l); } /** * Remove a TreeExpansionListener. * @param l The tree expansion listener */ public synchronized void removeTreeExpansionListener (TreeExpansionListener l) { eListeners.remove(l); } /** * Add a TreeWillExpandListener. * @param l The tree will expand listener */ public synchronized void addTreeWillExpandListener (TreeWillExpandListener l) { weListeners.add(l); } /** * Remove a TreeWillExpandListener. * @param l The tree will expand listener */ public synchronized void removeTreeWillExpandListener (TreeWillExpandListener l) { weListeners.remove(l); } /** * We need to copy the expansion state to the structurally changed tree. * The structural change of the tree will have the effect of collapsing * all expanded paths. * This method takes care of dumping the layout expansion state and * re-expand the originally expanded nodes. * @param e */ void treeStructureChanged(TreeModelEvent event) { TreePath path = event.getTreePath(); TreeModel model = layout.getModel(); if ((path == null) && (model != null)) { Object root = model.getRoot(); if (root != null) { path = new TreePath(root); } } TreePath[] expandedDescendants = getExpandedDescendants(path); layout.treeStructureChanged(event); for (TreePath tp : expandedDescendants) { layout.setExpandedState(tp, true); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy