001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: /*
043: * TreePathSupport.java
044: *
045: * Created on January 27, 2004, 7:06 PM
046: */
047:
048: package org.netbeans.swing.outline;
049:
050: import java.util.ArrayList;
051: import java.util.Enumeration;
052: import java.util.HashMap;
053: import java.util.HashSet;
054: import java.util.Iterator;
055: import java.util.List;
056: import java.util.Map;
057: import java.util.Set;
058: import java.util.logging.Level;
059: import java.util.logging.Logger;
060: import javax.swing.event.TreeExpansionEvent;
061: import javax.swing.event.TreeExpansionListener;
062: import javax.swing.event.TreeWillExpandListener;
063: import javax.swing.tree.AbstractLayoutCache;
064: import javax.swing.tree.ExpandVetoException;
065: import javax.swing.tree.TreePath;
066:
067: /** Manages expanded/collapsed paths for the Outline. Provides services similar
068: * to those JTree implements inside its own class body. Propagates changes
069: * in expanded state to the layout cache.
070: * <p>
071: * Principally what this class does is manage the state of expanded paths which
072: * are not visible, or whose parents have been closed/opened. Whereas the
073: * layout cache retains information only about what is visibly expanded, this
074: * class manages information about any path that has been expanded at some
075: * point in the lifetime of an Outline, so that for example, if A contains B
076: * contains C, and A and B and C are expanded, then the user collapses A,
077: * and later reëexpands A, B and C will retain their expanded state and
078: * appear as they did the last time A was expanded.
079: * <p>
080: * When nodes are removed, the OutlineModel must call removePath() for any
081: * defunct paths to avoid memory leaks by the TreePathSupport holding
082: * references to defunct nodes and not allowing them to be garbage collected.
083: * <p>
084: * Its <code>addTreeWillExpandListener</code> code supports
085: * <code>ExtTreeWillExpandListener</code>, so such a listener may be notified
086: * if some other listener vetos a pending expansion event.
087: *
088: * @author Tim Boudreau
089: */
090: public final class TreePathSupport {
091: private OutlineModel mdl;
092: private Map expandedPaths = new HashMap();
093: private List eListeners = new ArrayList();
094: private List weListeners = new ArrayList();
095: private AbstractLayoutCache layout;
096:
097: /** Creates a new instance of TreePathSupport */
098: public TreePathSupport(OutlineModel mdl, AbstractLayoutCache layout) {
099: this .mdl = mdl;
100: this .layout = layout;
101: }
102:
103: /** Clear all expanded path data. This is called if the tree model fires
104: * a structural change, and any or all of the nodes it contains may no
105: * longer be present. */
106: public void clear() {
107: expandedPaths.clear();
108: }
109:
110: /** Expand a path. Notifies the layout cache of the change,
111: * stores the expanded path info (so reexpanding a parent node also reexpands
112: * this path if a parent node containing it is later collapsed). Fires
113: * TreeWillExpand and TreeExpansion events. */
114: public void expandPath(TreePath path) {
115: if (Boolean.TRUE.equals(expandedPaths.get(path))) {
116: //It's already expanded, don't waste cycles firing bogus events
117: return;
118: }
119: TreeExpansionEvent e = new TreeExpansionEvent(this , path);
120: try {
121: fireTreeWillExpand(e, true);
122: expandedPaths.put(path, Boolean.TRUE);
123: layout.setExpandedState(path, true);
124: fireTreeExpansion(e, true);
125: } catch (ExpandVetoException eve) {
126: Logger.getLogger(this .getClass().getName()).log(Level.INFO,
127: eve.getLocalizedMessage(), eve);
128: fireTreeExpansionVetoed(e, eve);
129: }
130: }
131:
132: /** Collapse a path. Notifies the layout cache of the change,
133: * stores the expanded path info (so reexpanding a parent node also reexpands
134: * this path if a parent node containing it is later collapsed). Fires
135: * TreeWillExpand and TreeExpansion events. */
136: public void collapsePath(TreePath path) {
137: if (Boolean.FALSE.equals(expandedPaths.get(path))) {
138: //It's already collapsed, don't waste cycles firing bogus events
139: return;
140: }
141: TreeExpansionEvent e = new TreeExpansionEvent(this , path);
142: try {
143: fireTreeWillExpand(e, false);
144: expandedPaths.put(path, Boolean.FALSE);
145: layout.setExpandedState(path, false);
146: fireTreeExpansion(e, false);
147: } catch (ExpandVetoException eve) {
148: Logger.getLogger(getClass().getName()).log(Level.INFO,
149: eve.getLocalizedMessage(), eve);
150: fireTreeExpansionVetoed(e, eve);
151: }
152: }
153:
154: /** Remove a path's data from the list of known paths. Called when
155: * a tree model deletion event occurs */
156: public void removePath(TreePath path) {
157: expandedPaths.remove(path);
158: }
159:
160: private void fireTreeExpansion(TreeExpansionEvent e,
161: boolean expanded) {
162: int size = eListeners.size();
163:
164: TreeExpansionListener[] listeners = new TreeExpansionListener[size];
165: synchronized (this ) {
166: listeners = (TreeExpansionListener[]) eListeners
167: .toArray(listeners);
168: }
169: for (int i = 0; i < listeners.length; i++) {
170: if (expanded) {
171: listeners[i].treeExpanded(e);
172: } else {
173: listeners[i].treeCollapsed(e);
174: }
175: }
176: }
177:
178: private void fireTreeWillExpand(TreeExpansionEvent e,
179: boolean expanded) throws ExpandVetoException {
180: int size = eListeners.size();
181:
182: TreeWillExpandListener[] listeners = new TreeWillExpandListener[size];
183: synchronized (this ) {
184: listeners = (TreeWillExpandListener[]) weListeners
185: .toArray(listeners);
186: }
187: for (int i = 0; i < listeners.length; i++) {
188: if (expanded) {
189: listeners[i].treeWillExpand(e);
190: } else {
191: listeners[i].treeWillCollapse(e);
192: }
193: }
194: }
195:
196: private void fireTreeExpansionVetoed(TreeExpansionEvent e,
197: ExpandVetoException ex) {
198: int size = eListeners.size();
199:
200: TreeWillExpandListener[] listeners = new TreeWillExpandListener[size];
201: synchronized (this ) {
202: listeners = (TreeWillExpandListener[]) weListeners
203: .toArray(listeners);
204: }
205: for (int i = 0; i < listeners.length; i++) {
206: if (listeners[i] instanceof ExtTreeWillExpandListener) {
207: ((ExtTreeWillExpandListener) listeners[i])
208: .treeExpansionVetoed(e, ex);
209: }
210: }
211: }
212:
213: public boolean hasBeenExpanded(TreePath path) {
214: return (path != null && expandedPaths.get(path) != null);
215: }
216:
217: /**
218: * Returns true if the node identified by the path is currently expanded,
219: *
220: * @param path the <code>TreePath</code> specifying the node to check
221: * @return false if any of the nodes in the node's path are collapsed,
222: * true if all nodes in the path are expanded
223: */
224: public boolean isExpanded(TreePath path) {
225: if (path == null)
226: return false;
227:
228: // Is this node expanded?
229: Object value = expandedPaths.get(path);
230:
231: if (value == null || !((Boolean) value).booleanValue())
232: return false;
233:
234: // It is, make sure its parent is also expanded.
235: TreePath parentPath = path.getParentPath();
236:
237: if (parentPath != null)
238: return isExpanded(parentPath);
239: return true;
240: }
241:
242: protected void removeDescendantToggledPaths(Enumeration toRemove) {
243: if (toRemove != null) {
244: while (toRemove.hasMoreElements()) {
245: TreePath[] descendants = getDescendantToggledPaths((TreePath) toRemove
246: .nextElement());
247: for (int i = 0; i < descendants.length; i++) {
248: expandedPaths.remove(descendants[i]);
249: }
250: }
251: }
252: }
253:
254: protected TreePath[] getDescendantToggledPaths(TreePath parent) {
255: if (parent == null)
256: return null;
257:
258: ArrayList descendants = new ArrayList();
259: Iterator nodes = expandedPaths.keySet().iterator();
260: TreePath path;
261: while (nodes.hasNext()) {
262: path = (TreePath) nodes.next();
263: if (parent.isDescendant(path)) {
264: descendants.add(path);
265: }
266: }
267: TreePath[] result = new TreePath[descendants.size()];
268: return (TreePath[]) descendants.toArray(result);
269: }
270:
271: public boolean isVisible(TreePath path) {
272: if (path != null) {
273: TreePath parentPath = path.getParentPath();
274:
275: if (parentPath != null) {
276: return isExpanded(parentPath);
277: }
278: // Root.
279: return true;
280: }
281: return false;
282: }
283:
284: public TreePath[] getExpandedDescendants(TreePath parent) {
285: TreePath[] result = new TreePath[0];
286: if (isExpanded(parent)) {
287: TreePath path;
288: Object value;
289: List results = null;
290:
291: if (!expandedPaths.isEmpty()) {
292:
293: Iterator i = expandedPaths.keySet().iterator();
294:
295: while (i.hasNext()) {
296: path = (TreePath) i.next();
297: value = expandedPaths.get(path);
298:
299: // Add the path if it is expanded, a descendant of parent,
300: // and it is visible (all parents expanded). This is rather
301: // expensive!
302: if (path != parent && value != null
303: && ((Boolean) value).booleanValue()
304: && parent.isDescendant(path)
305: && isVisible(path)) {
306: if (results == null) {
307: results = new ArrayList();
308: }
309: results.add(path);
310: }
311: }
312: if (results != null) {
313: result = (TreePath[]) results.toArray(result);
314: }
315: }
316: }
317: return result;
318: }
319:
320: /** Add a TreeExpansionListener. If the TreeWillExpandListener implements
321: * ExtTreeExpansionListener, it will be notified if another
322: * TreeWillExpandListener vetoes the expansion event */
323: public synchronized void addTreeExpansionListener(
324: TreeExpansionListener l) {
325: eListeners.add(l);
326: }
327:
328: public synchronized void removeTreeExpansionListener(
329: TreeExpansionListener l) {
330: eListeners.remove(l);
331: }
332:
333: public synchronized void addTreeWillExpandListener(
334: TreeExpansionListener l) {
335: weListeners.add(l);
336: }
337:
338: public synchronized void removeTreeWillExpandListener(
339: TreeExpansionListener l) {
340: weListeners.remove(l);
341: }
342: }
|