This fixes the JTree layout, which has been broken for some weeks. These
fixes are driven and backed up by the Harmony testsuite. We now pass all
tests for AbstractLayoutCache and most tests for
VariableHeightLayoutCache (most remaining fails are mostly bogus tests
or weird unrealistic corner cases).
2006-10-12 Roman Kennke <[EMAIL PROTECTED]>
PR 29418
* javax/swing/tree/AbstractLayoutCache.java
(getNodeDimensions): Don't throw InternalError, but instead
return null.
(getRowsForPaths): Check for null here.
(isFixedRowHeight): Returns true when rowHeight > 0.
(setSelectionModel): Set this as the row mapper for the selection
model.
* javax/swing/tree/VariableHeightLayoutCache.java
(NodeRecord.NodeRecord): Initialize bounds field.
(getBounds): Simply return the bounds field.
(row2Node): Changed to be an ArrayList.
(RECT_CACHE): New field. Caches a Rectangle instance.
(countRows): Added y parameter and return value. The method
now takes the current y position as parameter, and returns
the updated y position.
(getBounds): Fixed to return the correct bounds.
(getPathForRow): Replaced by fixed implementation.
(getPreferredHeight): Replaced by more efficient implementation.
This simply fetches the last node record and returns its lower
bounds.
(getPreferredWidth): Added null check.
(getVisibleChildCount): Added null check.
(getVisiblePathsFrom): Added null check.
(setExpandedState): Also expand the ancestors of the node
to be expanded.
(setModel): Set dirty flag rather than updating for real.
(setNodeDimensions): Overridden to set the dirty flag.
(setRowHeight): Overridden to set the dirty flag.
(update): Don't special case the root here, this is done now
in countRows().
/Roman
Index: javax/swing/tree/AbstractLayoutCache.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/tree/AbstractLayoutCache.java,v
retrieving revision 1.12
diff -u -1 -5 -r1.12 AbstractLayoutCache.java
--- javax/swing/tree/AbstractLayoutCache.java 23 May 2006 16:01:38 -0000 1.12
+++ javax/swing/tree/AbstractLayoutCache.java 12 Oct 2006 10:37:32 -0000
@@ -137,33 +137,35 @@
/**
* Get the node dimensions. The NodeDimensions property must be set
* (unless the method is overridden, like if
* [EMAIL PROTECTED] FixedHeightLayoutCache}. If the method is not overridden and
* the property is not set, the InternalError is thrown.
*
* @param value the last node in the path
* @param row the node row
* @param depth the indentation depth
* @param expanded true if this node is expanded, false otherwise
* @param bounds the area where the tree is displayed
*/
protected Rectangle getNodeDimensions(Object value, int row, int depth,
boolean expanded, Rectangle bounds)
{
- if (nodeDimensions == null)
- throw new InternalError("The NodeDimensions are not set");
- return nodeDimensions.getNodeDimensions(value, row, depth, expanded, bounds);
+ Rectangle d = null;
+ if (nodeDimensions != null)
+ d = nodeDimensions.getNodeDimensions(value, row, depth, expanded,
+ bounds);
+ return d;
}
/**
* Sets the model that provides the tree data.
*
* @param model the model
*/
public void setModel(TreeModel model)
{
treeModel = model;
}
/**
* Returns the model that provides the tree data.
*
@@ -212,31 +214,36 @@
*
* @return the row height
*/
public int getRowHeight()
{
return rowHeight;
}
/**
* setSelectionModel
*
* @param model the model
*/
public void setSelectionModel(TreeSelectionModel model)
{
+ if (treeSelectionModel != null)
+ treeSelectionModel.setRowMapper(null);
treeSelectionModel = model;
+ if (treeSelectionModel != null)
+ treeSelectionModel.setRowMapper(this);
+
}
/**
* getSelectionModel
*
* @return the model
*/
public TreeSelectionModel getSelectionModel()
{
return treeSelectionModel;
}
/**
* Get the sum of heights for all rows. This class provides a general not
* optimized implementation that is overridded in derived classes
@@ -413,33 +420,37 @@
*/
public abstract void treeStructureChanged(TreeModelEvent event);
/**
* Get the tree row numbers for the given pathes. This method performs
* the "bulk" conversion that may be faster than mapping pathes one by
* one. To have the benefit from the bulk conversion, the method must be
* overridden in the derived classes. The default method delegates work
* to the [EMAIL PROTECTED] #getRowForPath(TreePath)}.
*
* @param paths the tree paths the array of the tree pathes.
* @return the array of the matching tree rows.
*/
public int[] getRowsForPaths(TreePath[] paths)
{
- int[] rows = new int[paths.length];
- for (int i = 0; i < rows.length; i++)
- rows[i] = getRowForPath(paths[i]);
+ int[] rows = null;
+ if (paths != null)
+ {
+ rows = new int[paths.length];
+ for (int i = 0; i < rows.length; i++)
+ rows[i] = getRowForPath(paths[i]);
+ }
return rows;
}
/**
* Returns true if this layout supposes that all rows have the fixed
* height.
*
* @return boolean true if all rows in the tree must have the fixed
* height (false by default).
*/
protected boolean isFixedRowHeight()
{
- return false;
+ return rowHeight > 0;
}
}
Index: javax/swing/tree/VariableHeightLayoutCache.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/tree/VariableHeightLayoutCache.java,v
retrieving revision 1.18
diff -u -1 -5 -r1.18 VariableHeightLayoutCache.java
--- javax/swing/tree/VariableHeightLayoutCache.java 4 Oct 2006 15:35:35 -0000 1.18
+++ javax/swing/tree/VariableHeightLayoutCache.java 12 Oct 2006 10:37:32 -0000
@@ -28,93 +28,97 @@
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package javax.swing.tree;
import gnu.javax.swing.tree.GnuPath;
import java.awt.Rectangle;
+import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Set;
import java.util.Vector;
import javax.swing.event.TreeModelEvent;
/**
* The fixed height tree layout. This class requires the NodeDimensions to be
* set and ignores the row height property.
*
* @specnote the methods, of this class, returning TreePath, actually returns
* the derived class GnuPath that provides additional information for optimized
* painting. See the GnuPath code for details.
*
* @author Audrius Meskauskas
*/
public class VariableHeightLayoutCache
- extends AbstractLayoutCache
+ extends AbstractLayoutCache
{
+
+ private static final Rectangle RECT_CACHE = new Rectangle();
+
/**
* The cached node record.
*/
class NodeRecord
{
NodeRecord(int aRow, int aDepth, Object aNode, Object aParent)
{
row = aRow;
depth = aDepth;
parent = aParent;
node = aNode;
-
- isExpanded = expanded.contains(aNode);
+ isExpanded = expanded.contains(aNode);
+ bounds = new Rectangle(0, -1, 0, 0);
}
/**
* The row, where the tree node is displayed.
*/
final int row;
/**
* The nesting depth
*/
final int depth;
/**
* The parent of the given node, null for the root node.
*/
final Object parent;
/**
* This node.
*/
final Object node;
/**
* True for the expanded nodes. The value is calculated in constructor.
* Using this field saves one hashtable access operation.
*/
final boolean isExpanded;
-
+
/**
* The cached bounds of the tree row.
*/
Rectangle bounds;
/**
* The path from the tree top to the given node (computed under first
* demand)
*/
private TreePath path;
/**
* Get the path for this node. The derived class is returned, making check
* for the last child of some parent easier.
*/
@@ -148,53 +152,48 @@
lpath.addFirst(parent);
}
else
rp = null;
}
path = new GnuPath(lpath.toArray(), lastChild);
}
return path;
}
/**
* Get the rectangle bounds (compute, if required).
*/
Rectangle getBounds()
{
- // This method may be called in the context when the tree rectangle is
- // not known. To work around this, it is assumed near infinitely large.
- if (bounds == null)
- bounds = getNodeDimensions(node, row, depth, isExpanded,
- new Rectangle());
return bounds;
}
}
/**
* The set of all expanded tree nodes.
*/
Set expanded = new HashSet();
/**
* Maps nodes to the row numbers.
*/
Hashtable nodes = new Hashtable();
/**
* Maps row numbers to nodes.
*/
- Hashtable row2node = new Hashtable();
+ ArrayList row2node = new ArrayList();
/**
* If true, the row map must be recomputed before using.
*/
boolean dirty;
/**
* The cumulative height of all rows.
*/
int totalHeight;
/**
* The maximal width.
*/
int maximalWidth;
@@ -224,69 +223,78 @@
/**
* Refresh the row map.
*/
private final void update()
{
nodes.clear();
row2node.clear();
totalHeight = maximalWidth = 0;
if (treeModel == null)
return;
Object root = treeModel.getRoot();
-
- if (rootVisible)
- {
- countRows(root, null, 0);
- }
- else
- {
- int sc = treeModel.getChildCount(root);
- for (int i = 0; i < sc; i++)
- {
- Object child = treeModel.getChild(root, i);
- countRows(child, root, 0);
- }
- }
+ countRows(root, null, 0, 0);
dirty = false;
}
/**
* Recursively counts all rows in the tree.
*/
- private final void countRows(Object node, Object parent, int depth)
+ private final int countRows(Object node, Object parent, int depth, int y)
{
- Integer n = new Integer(row2node.size());
- row2node.put(n, node);
-
- NodeRecord nr = new NodeRecord(n.intValue(), depth, node, parent);
+ boolean visible = node != treeModel.getRoot() || rootVisible;
+ int row = row2node.size();
+ if (visible)
+ {
+ row2node.add(node);
+ }
+ NodeRecord nr = new NodeRecord(row, depth, node, parent);
+ NodeDimensions d = getNodeDimensions();
+ Rectangle r = RECT_CACHE;
+ if (d != null)
+ r = d.getNodeDimensions(node, row, depth, nr.isExpanded, r);
+ else
+ r.setBounds(0, 0, 0, 0);
+
+ if (! visible)
+ r.y = -1;
+ else
+ r.y = Math.max(0, y);
+
+ if (isFixedRowHeight())
+ r.height = getRowHeight();
+
+ nr.bounds.setBounds(r);
nodes.put(node, nr);
-
- // For expanded nodes
+
+ if (visible)
+ y += r.height;
+
+ int sc = treeModel.getChildCount(node);
+ int deeper = depth + 1;
if (expanded.contains(node))
{
- int sc = treeModel.getChildCount(node);
- int deeper = depth + 1;
for (int i = 0; i < sc; i++)
{
Object child = treeModel.getChild(node, i);
- countRows(child, node, deeper);
+ y = countRows(child, node, deeper, y);
}
}
+ return y;
}
/**
* Discard the bound information for the given path.
*
* @param path the path, for that the bound information must be recomputed.
*/
public void invalidatePathBounds(TreePath path)
{
NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent());
if (r != null)
r.bounds = null;
}
/**
@@ -297,118 +305,117 @@
dirty = true;
}
/**
* Set the expanded state of the given path. The expansion states must be
* always updated when expanding and colapsing the tree nodes. Otherwise
* other methods will not work correctly after the nodes are collapsed or
* expanded.
*
* @param path the tree path, for that the state is being set.
* @param isExpanded the expanded state of the given path.
*/
public void setExpandedState(TreePath path, boolean isExpanded)
{
if (isExpanded)
- expanded.add(path.getLastPathComponent());
+ {
+ int length = path.getPathCount();
+ for (int i = 0; i < length; i++)
+ expanded.add(path.getPathComponent(i));
+ }
else
expanded.remove(path.getLastPathComponent());
-
+
dirty = true;
}
/**
* Get the expanded state for the given tree path.
*
* @return true if the given path is expanded, false otherwise.
*/
public boolean isExpanded(TreePath path)
{
return expanded.contains(path.getLastPathComponent());
}
/**
* Get bounds for the given tree path.
*
* @param path the tree path
* @param rect the rectangle that will be reused to return the result.
* @return Rectangle the bounds of the last line, defined by the given path.
*/
public Rectangle getBounds(TreePath path, Rectangle rect)
{
if (path == null)
return null;
if (dirty)
update();
- // The RI allows null arguments for rect, in which case a new Rectangle
- // is created.
- if (rect == null)
- rect = new Rectangle();
-
Object last = path.getLastPathComponent();
+ Rectangle result = null;
NodeRecord r = (NodeRecord) nodes.get(last);
- if (r == null)
- // This node is not visible.
- {
- rect.x = rect.y = rect.width = rect.height = 0;
- }
- else
+ if (r != null)
{
- if (r.bounds == null)
- {
- Rectangle dim = getNodeDimensions(last, r.row, r.depth,
- r.isExpanded, rect);
- r.bounds = dim;
- }
-
- rect.setRect(r.bounds);
+ // The RI allows null arguments for rect, in which case a new Rectangle
+ // is created.
+ result = rect;
+ if (result == null)
+ result = new Rectangle(r.bounds);
+ else
+ result.setBounds(r.bounds);
}
- return rect;
+ return result;
}
/**
* Get the path, the last element of that is displayed in the given row.
*
* @param row the row
* @return TreePath the path
*/
public TreePath getPathForRow(int row)
{
if (dirty)
update();
- Object last = row2node.get(new Integer(row));
- if (last == null)
- return null;
- else
- {
- NodeRecord r = (NodeRecord) nodes.get(last);
- return r.getPath();
+
+ TreePath path = null;
+ // Search row in the nodes map. TODO: This is inefficient, optimize this.
+ Enumeration nodesEnum = nodes.elements();
+ while (nodesEnum.hasMoreElements() && path == null)
+ {
+ NodeRecord record = (NodeRecord) nodesEnum.nextElement();
+ if (record.row == row)
+ path = record.getPath();
}
+ return path;
}
/**
* Get the row, displaying the last node of the given path.
*
* @param path the path
* @return int the row number or -1 if the end of the path is not visible.
*/
public int getRowForPath(TreePath path)
{
if (path == null)
return -1;
- if (dirty) update();
+
+ if (dirty)
+ update();
NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent());
if (r == null)
return - 1;
else
return r.row;
}
/**
* Get the path, closest to the given point.
*
* @param x the point x coordinate
* @param y the point y coordinate
* @return the tree path, closest to the the given point
*/
@@ -462,56 +469,56 @@
else
return 0;
}
/**
* Get the number of the visible childs for the given tree path. If the node
* is not expanded, 0 is returned. Otherwise, the number of children is
* obtained from the model as the number of children for the last path
* component.
*
* @param path the tree path
* @return int the number of the visible childs (for row).
*/
public int getVisibleChildCount(TreePath path)
{
- if (isExpanded(path))
+ if (! isExpanded(path) || treeModel == null)
return 0;
else
return treeModel.getChildCount(path.getLastPathComponent());
}
/**
* Get the enumeration over all visible pathes that start from the given
* parent path.
*
* @param parentPath the parent path
* @return the enumeration over pathes
*/
public Enumeration getVisiblePathsFrom(TreePath parentPath)
{
if (dirty)
update();
Vector p = new Vector(parentPath.getPathCount());
Object node;
NodeRecord nr;
for (int i = 0; i < parentPath.getPathCount(); i++)
{
node = parentPath.getPathComponent(i);
nr = (NodeRecord) nodes.get(node);
- if (nr.row >= 0)
+ if (nr != null && nr.row >= 0)
p.add(node);
}
return p.elements();
}
/**
* Return the expansion state of the given tree path. The expansion state
* must be previously set with the
* [EMAIL PROTECTED] #setExpandedState(TreePath, boolean)}
*
* @param path the path being checked
* @return true if the last node of the path is expanded, false otherwise.
*/
public boolean getExpandedState(TreePath path)
{
@@ -552,78 +559,99 @@
* Called when the tree structure has been changed.
*
* @param event the change event
*/
public void treeStructureChanged(TreeModelEvent event)
{
dirty = true;
}
/**
* Set the tree model that will provide the data.
*/
public void setModel(TreeModel newModel)
{
treeModel = newModel;
- // We need to clear the table and update the layout,
- // so that we don't end up with wrong data in the tables.
- expanded.clear();
- update();
+ dirty = true;
if (treeModel != null)
{
// The root node is expanded by default.
expanded.add(treeModel.getRoot());
- dirty = true;
}
}
/**
* Inform the instance if the tree root node is visible. If this method
* is not called, it is assumed that the tree root node is not visible.
*
* @param visible true if the tree root node is visible, false
* otherwise.
*/
public void setRootVisible(boolean visible)
{
rootVisible = visible;
dirty = true;
}
/**
* Get the sum of heights for all rows.
*/
public int getPreferredHeight()
{
if (dirty)
update();
- totalHeight = 0;
- Enumeration en = nodes.elements();
- while (en.hasMoreElements())
+ int height = 0;
+ int rowCount = getRowCount();
+ if (rowCount > 0)
{
- NodeRecord nr = (NodeRecord) en.nextElement();
- Rectangle r = nr.getBounds();
- totalHeight += r.height;
+ NodeRecord last = (NodeRecord) nodes.get(row2node.get(rowCount - 1));
+ height = last.bounds.y + last.bounds.height;
}
- return totalHeight;
+ return height;
}
/**
* Get the maximal width.
*/
public int getPreferredWidth(Rectangle value)
{
if (dirty)
update();
maximalWidth = 0;
Enumeration en = nodes.elements();
while (en.hasMoreElements())
{
NodeRecord nr = (NodeRecord) en.nextElement();
- Rectangle r = nr.getBounds();
- if (r.x + r.width > maximalWidth)
- maximalWidth = r.x + r.width;
+ if (nr != null)
+ {
+ Rectangle r = nr.getBounds();
+ int width = r.x + r.width;
+ if (width > maximalWidth)
+ maximalWidth = width;
+ }
}
return maximalWidth;
}
+
+ /**
+ * Sets the node dimensions and invalidates the cached layout.
+ *
+ * @param dim the dimensions to set
+ */
+ public void setNodeDimensions(NodeDimensions dim)
+ {
+ super.setNodeDimensions(dim);
+ dirty = true;
+ }
+
+ /**
+ * Sets the row height and marks the layout as invalid.
+ *
+ * @param height the row height to set
+ */
+ public void setRowHeight(int height)
+ {
+ super.setRowHeight(height);
+ dirty = true;
+ }
}