1 /* VariableHeightLayoutCache.java --
2 Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package javax.swing.tree;
40 import gnu.javax.swing.tree.GnuPath;
42 import java.awt.Rectangle;
43 import java.util.Enumeration;
44 import java.util.HashSet;
45 import java.util.Hashtable;
46 import java.util.LinkedList;
48 import java.util.Vector;
50 import javax.swing.event.TreeModelEvent;
53 * The fixed height tree layout. This class requires the NodeDimensions to be
54 * set and ignores the row height property.
56 * @specnote the methods, of this class, returning TreePath, actually returns
57 * the derived class GnuPath that provides additional information for optimized
58 * painting. See the GnuPath code for details.
60 * @author Audrius Meskauskas
62 public class VariableHeightLayoutCache
63 extends AbstractLayoutCache
66 * The cached node record.
70 NodeRecord(int aRow, int aDepth, Object aNode, Object aParent)
77 isExpanded = expanded.contains(aNode);
81 * The row, where the tree node is displayed.
91 * The parent of the given node, null for the root node.
101 * True for the expanded nodes. The value is calculated in constructor.
102 * Using this field saves one hashtable access operation.
104 final boolean isExpanded;
107 * The cached bounds of the tree row.
112 * The path from the tree top to the given node (computed under first
115 private TreePath path;
118 * Get the path for this node. The derived class is returned, making check
119 * for the last child of some parent easier.
125 boolean lastChild = false;
128 int nc = treeModel.getChildCount(parent);
131 int n = treeModel.getIndexOfChild(parent, node);
137 LinkedList lpath = new LinkedList();
138 NodeRecord rp = this;
141 lpath.addFirst(rp.node);
142 if (rp.parent != null)
144 Object parent = rp.parent;
145 rp = (NodeRecord) nodes.get(parent);
146 // Add the root node, even if it is not visible.
148 lpath.addFirst(parent);
153 path = new GnuPath(lpath.toArray(), lastChild);
159 * Get the rectangle bounds (compute, if required).
161 Rectangle getBounds()
163 // This method may be called in the context when the tree rectangle is
164 // not known. To work around this, it is assumed near infinitely large.
166 bounds = getNodeDimensions(node, row, depth, isExpanded,
173 * The set of all expanded tree nodes.
175 Set expanded = new HashSet();
178 * Maps nodes to the row numbers.
180 Hashtable nodes = new Hashtable();
183 * Maps row numbers to nodes.
185 Hashtable row2node = new Hashtable();
188 * If true, the row map must be recomputed before using.
193 * The cumulative height of all rows.
203 * Creates the unitialised instance. Before using the class, the row height
204 * must be set with the {@link #setRowHeight(int)} and the model must be set
205 * with {@link #setModel(TreeModel)}. The node dimensions may not be set.
207 public VariableHeightLayoutCache()
209 // Nothing to do here.
213 * Get the total number of rows in the tree. Every displayed node occupies the
214 * single row. The root node row is included if the root node is set as
215 * visible (false by default).
217 * @return int the number of the displayed rows.
219 public int getRowCount()
222 return row2node.size();
226 * Refresh the row map.
228 private final void update()
233 totalHeight = maximalWidth = 0;
235 if (treeModel == null)
238 Object root = treeModel.getRoot();
242 countRows(root, null, 0);
246 int sc = treeModel.getChildCount(root);
247 for (int i = 0; i < sc; i++)
249 Object child = treeModel.getChild(root, i);
250 countRows(child, root, 0);
257 * Recursively counts all rows in the tree.
259 private final void countRows(Object node, Object parent, int depth)
261 Integer n = new Integer(row2node.size());
262 row2node.put(n, node);
264 NodeRecord nr = new NodeRecord(n.intValue(), depth, node, parent);
267 // For expanded nodes
268 if (expanded.contains(node))
270 int sc = treeModel.getChildCount(node);
271 int deeper = depth + 1;
272 for (int i = 0; i < sc; i++)
274 Object child = treeModel.getChild(node, i);
275 countRows(child, node, deeper);
281 * Discard the bound information for the given path.
283 * @param path the path, for that the bound information must be recomputed.
285 public void invalidatePathBounds(TreePath path)
287 NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent());
293 * Mark all cached information as invalid.
295 public void invalidateSizes()
301 * Set the expanded state of the given path. The expansion states must be
302 * always updated when expanding and colapsing the tree nodes. Otherwise
303 * other methods will not work correctly after the nodes are collapsed or
306 * @param path the tree path, for that the state is being set.
307 * @param isExpanded the expanded state of the given path.
309 public void setExpandedState(TreePath path, boolean isExpanded)
312 expanded.add(path.getLastPathComponent());
314 expanded.remove(path.getLastPathComponent());
320 * Get the expanded state for the given tree path.
322 * @return true if the given path is expanded, false otherwise.
324 public boolean isExpanded(TreePath path)
326 return expanded.contains(path.getLastPathComponent());
330 * Get bounds for the given tree path.
332 * @param path the tree path
333 * @param rect the rectangle that will be reused to return the result.
334 * @return Rectangle the bounds of the last line, defined by the given path.
336 public Rectangle getBounds(TreePath path, Rectangle rect)
342 Object last = path.getLastPathComponent();
343 NodeRecord r = (NodeRecord) nodes.get(last);
345 // This node is not visible.
347 rect.x = rect.y = rect.width = rect.height = 0;
351 if (r.bounds == null)
353 Rectangle dim = getNodeDimensions(last, r.row, r.depth,
358 rect.setRect(r.bounds);
364 * Get the path, the last element of that is displayed in the given row.
367 * @return TreePath the path
369 public TreePath getPathForRow(int row)
373 Object last = row2node.get(new Integer(row));
378 NodeRecord r = (NodeRecord) nodes.get(last);
384 * Get the row, displaying the last node of the given path.
386 * @param path the path
387 * @return int the row number or -1 if the end of the path is not visible.
389 public int getRowForPath(TreePath path)
395 NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent());
403 * Get the path, closest to the given point.
405 * @param x the point x coordinate
406 * @param y the point y coordinate
407 * @return the tree path, closest to the the given point
409 public TreePath getPathClosestTo(int x, int y)
414 // As the rows have arbitrary height, we need to iterate.
415 NodeRecord best = null;
417 Enumeration en = nodes.elements();
419 int dist = Integer.MAX_VALUE;
421 while (en.hasMoreElements() && dist > 0)
423 r = (NodeRecord) en.nextElement();
427 dist = distance(r.getBounds(), x, y);
431 int rr = distance(r.getBounds(), x, y);
443 return best.getPath();
447 * Get the closest distance from this point till the given rectangle. Only
448 * vertical distance is taken into consideration.
450 int distance(Rectangle r, int x, int y)
454 else if (y > r.y + r.height)
455 return y - (r.y + r.height);
461 * Get the number of the visible childs for the given tree path. If the node
462 * is not expanded, 0 is returned. Otherwise, the number of children is
463 * obtained from the model as the number of children for the last path
466 * @param path the tree path
467 * @return int the number of the visible childs (for row).
469 public int getVisibleChildCount(TreePath path)
471 if (isExpanded(path))
474 return treeModel.getChildCount(path.getLastPathComponent());
478 * Get the enumeration over all visible pathes that start from the given
481 * @param parentPath the parent path
482 * @return the enumeration over pathes
484 public Enumeration getVisiblePathsFrom(TreePath parentPath)
488 Vector p = new Vector(parentPath.getPathCount());
492 for (int i = 0; i < parentPath.getPathCount(); i++)
494 node = parentPath.getPathComponent(i);
495 nr = (NodeRecord) nodes.get(node);
503 * Return the expansion state of the given tree path. The expansion state
504 * must be previously set with the
505 * {@link #setExpandedState(TreePath, boolean)}
507 * @param path the path being checked
508 * @return true if the last node of the path is expanded, false otherwise.
510 public boolean getExpandedState(TreePath path)
512 return expanded.contains(path.getLastPathComponent());
516 * The listener method, called when the tree nodes are changed.
518 * @param event the change event
520 public void treeNodesChanged(TreeModelEvent event)
526 * The listener method, called when the tree nodes are inserted.
528 * @param event the change event
530 public void treeNodesInserted(TreeModelEvent event)
536 * The listener method, called when the tree nodes are removed.
538 * @param event the change event
540 public void treeNodesRemoved(TreeModelEvent event)
546 * Called when the tree structure has been changed.
548 * @param event the change event
550 public void treeStructureChanged(TreeModelEvent event)
556 * Set the tree model that will provide the data.
558 public void setModel(TreeModel newModel)
560 treeModel = newModel;
561 // We need to clear the table and update the layout,
562 // so that we don't end up with wrong data in the tables.
565 if (treeModel != null)
567 // The root node is expanded by default.
568 expanded.add(treeModel.getRoot());
574 * Inform the instance if the tree root node is visible. If this method
575 * is not called, it is assumed that the tree root node is not visible.
577 * @param visible true if the tree root node is visible, false
580 public void setRootVisible(boolean visible)
582 rootVisible = visible;
587 * Get the sum of heights for all rows.
589 public int getPreferredHeight()
594 Enumeration en = nodes.elements();
595 while (en.hasMoreElements())
597 NodeRecord nr = (NodeRecord) en.nextElement();
598 Rectangle r = nr.getBounds();
599 totalHeight += r.height;
605 * Get the maximal width.
607 public int getPreferredWidth(Rectangle value)
613 Enumeration en = nodes.elements();
614 while (en.hasMoreElements())
616 NodeRecord nr = (NodeRecord) en.nextElement();
617 Rectangle r = nr.getBounds();
618 if (r.x + r.width > maximalWidth)
619 maximalWidth = r.x + r.width;