This patch contains several fixes, maing the tree editing to work better
with the layout cache.
2006-04-20 Audrius Meskauskas <[EMAIL PROTECTED]>
* javax/swing/plaf/basic/BasicTreeUI.java
(finish): Invalidate path bounds.
(getMaxHeight): Set the row height to the layout cache.
(startEditing): Do not request to recalculated
row height and preferred size.
* javax/swing/tree/DefaultTreeCellEditor.java
(ICON_TEXT_GAP, ICON_TREE_GAP): Removed, replacing
with 0.
* javax/swing/tree/FixedHeightLayoutCache.java:
Rewritten.
Index: javax/swing/plaf/basic/BasicTreeUI.java
===================================================================
RCS file: /sources/classpath/classpath/javax/swing/plaf/basic/BasicTreeUI.java,v
retrieving revision 1.123
diff -u -r1.123 BasicTreeUI.java
--- javax/swing/plaf/basic/BasicTreeUI.java 13 Apr 2006 17:57:48 -0000 1.123
+++ javax/swing/plaf/basic/BasicTreeUI.java 20 Apr 2006 11:42:48 -0000
@@ -100,6 +100,7 @@
import javax.swing.tree.AbstractLayoutCache;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.FixedHeightLayoutCache;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
@@ -668,7 +669,8 @@
maxHeight = Math.max(maxHeight, iconHeight + gap);
}
-
+
+ treeState.setRowHeight(maxHeight);
return maxHeight;
}
@@ -1508,8 +1510,9 @@
{
if (! validCachedPreferredSize)
{
+ Rectangle size = tree.getBounds();
// Add the scrollbar dimensions to the preferred size.
- preferredSize = new Dimension(treeState.getPreferredWidth(null),
+ preferredSize = new Dimension(treeState.getPreferredWidth(size),
treeState.getPreferredHeight());
validCachedPreferredSize = true;
}
@@ -1598,12 +1601,6 @@
*/
protected boolean startEditing(TreePath path, MouseEvent event)
{
- // Force to recalculate the maximal row height.
- maxHeight = 0;
-
- // Force to recalculate the cached preferred size.
- validCachedPreferredSize = false;
-
updateCellEditor();
TreeCellEditor ed = getCellEditor();
@@ -3298,15 +3295,17 @@
*/
void finish()
{
+ treeState.invalidatePathBounds(treeState.getPathForRow(editingRow));
editingPath = null;
editingRow = - 1;
stopEditingInCompleteEditing = false;
isEditing = false;
+ Rectangle bounds = editingComponent.getParent().getBounds();
tree.removeAll();
validCachedPreferredSize = false;
-
// Repaint the region, where was the editing component.
- tree.repaint(editingComponent.getParent().getBounds());
+ tree.repaint(bounds);
editingComponent = null;
+ tree.requestFocus();
}
} // BasicTreeUI
Index: javax/swing/tree/DefaultTreeCellEditor.java
===================================================================
RCS file: /sources/classpath/classpath/javax/swing/tree/DefaultTreeCellEditor.java,v
retrieving revision 1.20
diff -u -r1.20 DefaultTreeCellEditor.java
--- javax/swing/tree/DefaultTreeCellEditor.java 26 Jan 2006 11:26:28 -0000 1.20
+++ javax/swing/tree/DefaultTreeCellEditor.java 20 Apr 2006 11:42:51 -0000
@@ -77,17 +77,6 @@
implements ActionListener, TreeCellEditor, TreeSelectionListener
{
/**
- * The gap between the icon and editing component during editing.
- */
- static int ICON_TEXT_GAP = 3;
-
- /**
- * The left margin of the editing container (the gap between the tree and
- * the editing component of the editing icon.
- */
- static int TREE_ICON_GAP = ICON_TEXT_GAP;
-
- /**
* The number of the fast mouse clicks, required to start the editing
* session.
*/
@@ -141,7 +130,7 @@
{
// From the previous version, the left margin is taken as half
// of the icon width.
- editingIcon.paintIcon(this, g, TREE_ICON_GAP, 0);
+ editingIcon.paintIcon(this, g, 0, 0);
}
super.paint(g);
}
@@ -157,7 +146,7 @@
// Move the component to the left, leaving room for the editing icon:
if (editingIcon != null)
- eOffset = TREE_ICON_GAP + editingIcon.getIconWidth() + ICON_TEXT_GAP;
+ eOffset = editingIcon.getIconWidth();
else
eOffset = 0;
@@ -166,7 +155,7 @@
c.setLocation(eOffset, 0);
// Span the editing component near over all window width.
- c.setSize(bounds.width - eOffset - TREE_ICON_GAP, bounds.height);
+ c.setSize(bounds.width - eOffset, bounds.height);
/*
* @specnote the Sun sets some more narrow editing component width (it is
* not documented how does it is calculated). However as our text field is
@@ -542,7 +531,8 @@
* If the realEditor returns true to this message, prepareForEditing
* is messaged and true is returned.
*
- * @param event - the event the editor should use to consider whether to begin editing or not
+ * @param event - the event the editor should use to consider whether to
+ * begin editing or not
* @return true if editing can be started
*/
public boolean isCellEditable(EventObject event)
Index: javax/swing/tree/FixedHeightLayoutCache.java
===================================================================
RCS file: /sources/classpath/classpath/javax/swing/tree/FixedHeightLayoutCache.java,v
retrieving revision 1.10
diff -u -r1.10 FixedHeightLayoutCache.java
--- javax/swing/tree/FixedHeightLayoutCache.java 13 Apr 2006 16:49:16 -0000 1.10
+++ javax/swing/tree/FixedHeightLayoutCache.java 20 Apr 2006 11:42:52 -0000
@@ -37,6 +37,8 @@
package javax.swing.tree;
+import gnu.javax.swing.tree.GnuPath;
+
import java.awt.Rectangle;
import java.util.Enumeration;
import java.util.HashSet;
@@ -45,22 +47,21 @@
import java.util.Set;
import java.util.Vector;
-import javax.swing.UIManager;
import javax.swing.event.TreeModelEvent;
+
/**
* The fixed height tree layout. This class assumes that all cells in the tree
* have the same fixed height. This may be not the case, for instance, if leaves
* and branches have different height, of if the tree rows may have arbitrary
* variable height. This class will also work if the NodeDimensions are not
- * set. If they are set, the size calculations are just forwarded to the set
- * instance.
+ * set.
*
* @author Audrius Meskauskas
* @author Andrew Selkirk
*/
public class FixedHeightLayoutCache
- extends AbstractLayoutCache
+ extends VariableHeightLayoutCache
{
/**
* The cached node record.
@@ -114,8 +115,23 @@
*/
private TreePath path;
+ /**
+ * Get the path for this node. The derived class is returned,
+ * making check for the last child of some parent easier.
+ */
TreePath getPath()
{
+ boolean lastChild = false;
+ if (parent!=null)
+ {
+ int nc = treeModel.getChildCount(parent);
+ if (nc > 0)
+ {
+ int n = treeModel.getIndexOfChild(parent, node);
+ if (n == nc-1)
+ lastChild = true;
+ }
+ }
if (path == null)
{
LinkedList lpath = new LinkedList();
@@ -128,12 +144,26 @@
else
rp = null;
}
- path = new TreePath(lpath.toArray());
+ 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(Integer.MAX_VALUE/2,
+ Integer.MAX_VALUE/2));
+ return bounds;
+ }
}
-
+
/**
* The set of all expanded tree nodes.
*/
@@ -153,6 +183,16 @@
* 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;
/**
* Creates the unitialised instance. Before using the class, the row height
@@ -184,6 +224,8 @@
{
nodes.clear();
row2node.clear();
+
+ totalHeight = maximalWidth = 0;
Object root = treeModel.getRoot();
@@ -211,8 +253,10 @@
Integer n = new Integer(row2node.size());
row2node.put(n, node);
- nodes.put(node, new NodeRecord(n.intValue(), depth, node, parent));
-
+ NodeRecord nr = new NodeRecord(n.intValue(), depth, node, parent);
+ nodes.put(node, nr);
+
+ // For expanded nodes and for the root node.
if (expanded.contains(node) || parent == null)
{
int sc = treeModel.getChildCount(node);
@@ -226,14 +270,15 @@
}
/**
- * This should invalidate the width of the last path component, but
- * following the JDK 1.4 API it is not cached and the method should return
- * without action.
- * @param path the path being invalidated, ignored.
+ * 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)
{
- // Following JDK 1.4 API, should return without action.
+ NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent());
+ if (r!=null)
+ r.bounds = null;
}
/**
@@ -277,8 +322,7 @@
* Get bounds for the given tree path.
*
* @param path the tree path
- * @param rect the rectangle, specifying the area where the path should be
- * displayed.
+ * @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)
@@ -288,18 +332,22 @@
Object last = path.getLastPathComponent();
NodeRecord r = (NodeRecord) nodes.get(last);
if (r == null)
- // This node is not visible.
- return new Rectangle();
+ // This node is not visible.
+ {
+ rect.x = rect.y = rect.width = rect.height = 0;
+ }
else
{
if (r.bounds == null)
{
- Rectangle dim = getNodeDimensions(last, r.row, r.depth, r.isExpanded,
- rect);
+ Rectangle dim = getNodeDimensions(last, r.row, r.depth,
+ r.isExpanded, rect);
r.bounds = dim;
}
- return r.bounds;
+
+ rect.setRect(r.bounds);
}
+ return rect;
}
/**
@@ -351,28 +399,59 @@
if (dirty)
update();
- // We do not need to iterate because all rows have the same height.
- int row = y / rowHeight;
- if (row < 0)
- row = 0;
- if (row > getRowCount())
- row = getRowCount() - 1;
+ // As the rows have arbitrary height, we need to iterate.
+ NodeRecord best = null;
+ NodeRecord r;
+ Enumeration en = nodes.elements();
- if (row < 0)
- return null; // Empty tree - nothing to return.
-
- Object node = row2node.get(new Integer(row));
- NodeRecord nr = (NodeRecord) nodes.get(node);
- return nr.getPath();
+ int dist = Integer.MAX_VALUE;
+
+ while (en.hasMoreElements() && dist > 0)
+ {
+ r = (NodeRecord) en.nextElement();
+ if (best == null)
+ {
+ best = r;
+ dist = distance(r.getBounds(), x, y);
+ }
+ else
+ {
+ int rr = distance(r.getBounds(), x, y);
+ if (rr < dist)
+ {
+ best = r;
+ dist = rr;
+ }
+ }
+ }
+
+ if (best == null)
+ return null;
+ else
+ return best.getPath();
}
+
+ /**
+ * Get the closest distance from this point till the given rectangle. Only
+ * vertical distance is taken into consideration.
+ */
+ int distance(Rectangle r, int x, int y)
+ {
+ if (y < r.y)
+ return r.y - y;
+ else if (y > r.y + r.height)
+ return y - (r.y + r.height);
+ 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
+ * 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
+ * @param path the tree path
* @return int the number of the visible childs (for row).
*/
public int getVisibleChildCount(TreePath path)
@@ -466,7 +545,7 @@
*/
public void setModel(TreeModel newModel)
{
- super.setModel(newModel);
+ treeModel = newModel;
dirty = true;
}
@@ -484,32 +563,40 @@
}
/**
- * Get the node dimensions. If the NodeDimensions are not set, this method
- * calculates dimensions assuming the fixed row height.
- *
- * @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)
- return nodeDimensions.getNodeDimensions(value, row, depth, expanded,
- bounds);
- else
+ * Get the sum of heights for all rows.
+ */
+ public int getPreferredHeight()
+ {
+ if (dirty)
+ update();
+ totalHeight = 0;
+ Enumeration en = nodes.elements();
+ while (en.hasMoreElements())
{
- Rectangle r = new Rectangle(bounds);
-
- int indent = depth * UIManager.getInt("Tree.rightChildIndent");
+ NodeRecord nr = (NodeRecord) en.nextElement();
+ Rectangle r = nr.getBounds();
+ totalHeight += r.height;
+ }
+ return totalHeight;
+ }
- r.x = indent;
- r.y = row * getRowHeight();
- r.width = bounds.width = r.x;
- r.height = getRowHeight();
- return r;
+ /**
+ * 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;
}
+ return maximalWidth;
}
}