This fixes the harmony testcases for View, CompositeView and BoxView and
applies a small optimization to BoxView and CompositeView. I made these
two classes grow their View and layout arrays more efficiently by not
making a copy on each change, but instead grow the array by doubling its
size, and having a pointer numChildren that points at the array end.
2006-08-28 Roman Kennke <[EMAIL PROTECTED]>
* javax/swing/text/View.java
(height): Removed unneeded field.
(width): Removed unneeded field.
(getBreakWeight): Return GoodBreakWeight when pos is after
the view's span.
(getToolTipText): Check view index more carefully. Avoid
Rectangle creation.
(insertUpdate): Only execute method body if view count > 0.
When updateChildren returns false, clear the ec variable.
(updateChildren): Added null checks.
(viewToModel): Initialize bias array correctly.
* javax/swing/text/CompositeView.java
(children): Made private.
(numChildren): New field.
(loadChildren): Check factory for null. Don't load children
when factory is null.
(replace): Removed null check. Nullify removed children. Made
growing the array more efficient.
(getViewCount): Return numChildren rather then the real array
size.
* javax/swing/text/BoxView.java
(getViewAtPoint): Fixed algorithm for finding the view.
(replace): Made array growing more efficient.
(replaceLayoutArray): New helper method for growing/patching
the layout arrays.
(viewToModel): Make sure we have a valid layout.
/Roman
Index: javax/swing/text/View.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/View.java,v
retrieving revision 1.35
diff -u -1 -2 -r1.35 View.java
--- javax/swing/text/View.java 12 Aug 2006 22:16:12 -0000 1.35
+++ javax/swing/text/View.java 28 Aug 2006 15:47:46 -0000
@@ -48,25 +48,24 @@
import javax.swing.event.DocumentEvent;
public abstract class View implements SwingConstants
{
public static final int BadBreakWeight = 0;
public static final int ExcellentBreakWeight = 2000;
public static final int ForcedBreakWeight = 3000;
public static final int GoodBreakWeight = 1000;
public static final int X_AXIS = 0;
public static final int Y_AXIS = 1;
- private float width, height;
private Element elt;
private View parent;
/**
* Creates a new <code>View</code> instance.
*
* @param elem an <code>Element</code> value
*/
public View(Element elem)
{
elt = elem;
}
@@ -298,52 +297,56 @@
public int getViewIndex(float x, float y, Shape allocation)
{
return -1;
}
/**
* @since 1.4
*/
public String getToolTipText(float x, float y, Shape allocation)
{
int index = getViewIndex(x, y, allocation);
- if (index < -1)
- return null;
-
- Shape childAllocation = getChildAllocation(index, allocation);
-
- if (childAllocation.getBounds().contains(x, y))
- return getView(index).getToolTipText(x, y, childAllocation);
-
- return null;
+ String text = null;
+ if (index >= 0)
+ {
+ allocation = getChildAllocation(index, allocation);
+ Rectangle r = allocation instanceof Rectangle ? (Rectangle) allocation
+ : allocation.getBounds();
+ if (r.contains(x, y))
+ text = getView(index).getToolTipText(x, y, allocation);
+ }
+ return text;
}
/**
* @since 1.3
*/
public Graphics getGraphics()
{
return getContainer().getGraphics();
}
public void preferenceChanged(View child, boolean width, boolean height)
{
if (parent != null)
parent.preferenceChanged(this, width, height);
}
public int getBreakWeight(int axis, float pos, float len)
{
- return BadBreakWeight;
+ int weight = BadBreakWeight;
+ if (len > getPreferredSpan(axis))
+ weight = GoodBreakWeight;
+ return weight;
}
public View breakView(int axis, int offset, float pos, float len)
{
return this;
}
/**
* @since 1.3
*/
public int getViewIndex(int pos, Position.Bias b)
{
@@ -361,30 +364,36 @@
* <li>Call [EMAIL PROTECTED] #forwardUpdate}. This forwards the DocumentEvent to
* the child views.<li>
* <li>Call [EMAIL PROTECTED] #updateLayout}. Gives the view a chance to either
* repair its layout, reschedule layout or do nothing at all.</li>
* </ul>
*
* @param ev the DocumentEvent that describes the change
* @param shape the shape of the view
* @param vf the ViewFactory for creating child views
*/
public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
{
- Element el = getElement();
- DocumentEvent.ElementChange ec = ev.getChange(el);
- if (ec != null)
- updateChildren(ec, ev, vf);
- forwardUpdate(ec, ev, shape, vf);
- updateLayout(ec, ev, shape);
+ if (getViewCount() > 0)
+ {
+ Element el = getElement();
+ DocumentEvent.ElementChange ec = ev.getChange(el);
+ if (ec != null)
+ {
+ if (! updateChildren(ec, ev, vf))
+ ec = null;
+ }
+ forwardUpdate(ec, ev, shape, vf);
+ updateLayout(ec, ev, shape);
+ }
}
/**
* Receive notification about a remove update to the text model.
*
* The default implementation of this method does the following:
* <ul>
* <li>Call [EMAIL PROTECTED] #updateChildren} if the element that this view is
* responsible for has changed. This makes sure that the children can
* correctly represent the model.<li>
* <li>Call [EMAIL PROTECTED] #forwardUpdate}. This forwards the DocumentEvent to
* the child views.<li>
@@ -462,28 +471,33 @@
* that they are responsible for and should then return false.
*
* @since 1.3
*/
protected boolean updateChildren(DocumentEvent.ElementChange ec,
DocumentEvent ev,
ViewFactory vf)
{
Element[] added = ec.getChildrenAdded();
Element[] removed = ec.getChildrenRemoved();
int index = ec.getIndex();
- View[] newChildren = new View[added.length];
- for (int i = 0; i < added.length; ++i)
- newChildren[i] = vf.create(added[i]);
- replace(index, removed.length, newChildren);
+ View[] newChildren = null;
+ if (added != null)
+ {
+ newChildren = new View[added.length];
+ for (int i = 0; i < added.length; ++i)
+ newChildren[i] = vf.create(added[i]);
+ }
+ int numRemoved = removed != null ? removed.length : 0;
+ replace(index, numRemoved, newChildren);
return true;
}
/**
* Forwards the DocumentEvent to child views that need to get notified
* of the change to the model. This calles [EMAIL PROTECTED] #forwardUpdateToView}
* for each View that must be forwarded to.
*
* If <code>ec</code> is not <code>null</code> (this means there have been
* structural changes to the element that this view is responsible for) this
* method should recognize this and don't notify newly added child views.
@@ -747,25 +761,27 @@
* @param x the x coordinate in the view space
* @param y the y coordinate in the view space
* @param a the allocation of this <code>View</code>
*
* @return the position in the document that corresponds to the screen
* coordinates <code>x, y</code>
*
* @deprecated Use [EMAIL PROTECTED] #viewToModel(float, float, Shape, Position.Bias[])}
* instead.
*/
public int viewToModel(float x, float y, Shape a)
{
- return viewToModel(x, y, a, new Position.Bias[0]);
+ Position.Bias[] biasRet = new Position.Bias[1];
+ biasRet[0] = Position.Bias.Forward;
+ return viewToModel(x, y, a, biasRet);
}
/**
* Dumps the complete View hierarchy. This method can be used for debugging
* purposes.
*/
protected void dump()
{
// Climb up the hierarchy to the parent.
View parent = getParent();
if (parent != null)
parent.dump();
Index: javax/swing/text/CompositeView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/CompositeView.java,v
retrieving revision 1.21
diff -u -1 -2 -r1.21 CompositeView.java
--- javax/swing/text/CompositeView.java 25 Aug 2006 21:03:46 -0000 1.21
+++ javax/swing/text/CompositeView.java 28 Aug 2006 15:47:47 -0000
@@ -47,25 +47,30 @@
* An abstract base implementation of [EMAIL PROTECTED] View} that manages child
* <code>View</code>s.
*
* @author Roman Kennke ([EMAIL PROTECTED])
*/
public abstract class CompositeView
extends View
{
/**
* The child views of this <code>CompositeView</code>.
*/
- View[] children;
+ private View[] children;
+
+ /**
+ * The number of child views.
+ */
+ private int numChildren;
/**
* The allocation of this <code>View</code> minus its insets. This is
* initialized in [EMAIL PROTECTED] #getInsideAllocation} and reused and modified in
* [EMAIL PROTECTED] #childAllocation(int, Rectangle)}.
*/
Rectangle insideAllocation;
/**
* The insets of this <code>CompositeView</code>. This is initialized
* in [EMAIL PROTECTED] #setInsets}.
*/
@@ -92,61 +97,64 @@
/**
* Loads the child views of this <code>CompositeView</code>. This method
* is called from [EMAIL PROTECTED] #setParent} to initialize the child views of
* this composite view.
*
* @param f the view factory to use for creating new child views
*
* @see #setParent
*/
protected void loadChildren(ViewFactory f)
{
- Element el = getElement();
- int count = el.getElementCount();
- View[] newChildren = new View[count];
- for (int i = 0; i < count; ++i)
+ if (f != null)
{
- Element child = el.getElement(i);
- View view = f.create(child);
- newChildren[i] = view;
+ Element el = getElement();
+ int count = el.getElementCount();
+ View[] newChildren = new View[count];
+ for (int i = 0; i < count; ++i)
+ {
+ Element child = el.getElement(i);
+ View view = f.create(child);
+ newChildren[i] = view;
+ }
+ // I'd have called replace(0, getViewCount(), newChildren) here
+ // in order to replace all existing views. However according to
+ // Harmony's tests this is not what the RI does.
+ replace(0, 0, newChildren);
}
- // I'd have called replace(0, getViewCount(), newChildren) here
- // in order to replace all existing views. However according to
- // Harmony's tests this is not what the RI does.
- replace(0, 0, newChildren);
}
/**
* Sets the parent of this <code>View</code>.
* In addition to setting the parent, this calls [EMAIL PROTECTED] #loadChildren}, if
* this <code>View</code> does not already have its children initialized.
*
* @param parent the parent to set
*/
public void setParent(View parent)
{
super.setParent(parent);
if (parent != null && ((children == null) || children.length == 0))
loadChildren(getViewFactory());
}
/**
* Returns the number of child views.
*
* @return the number of child views
*/
public int getViewCount()
{
- return children.length;
+ return numChildren;
}
/**
* Returns the child view at index <code>n</code>.
*
* @param n the index of the requested child view
*
* @return the child view at index <code>n</code>
*/
public View getView(int n)
{
return children[n];
@@ -160,45 +168,55 @@
*
* @param offset the start offset from where to remove children
* @param length the number of children to remove
* @param views the views that replace the removed children
*/
public void replace(int offset, int length, View[] views)
{
// Make sure we have an array. The Harmony testsuite indicates that we
// have to do something like this.
if (views == null)
views = new View[0];
- // Check for null views to add.
- for (int i = 0; i < views.length; ++i)
- if (views[i] == null)
- throw new NullPointerException("Added views must not be null");
-
- int endOffset = offset + length;
-
// First we set the parent of the removed children to null.
+ int endOffset = offset + length;
for (int i = offset; i < endOffset; ++i)
{
if (children[i].getParent() == this)
children[i].setParent(null);
+ children[i] = null;
}
- View[] newChildren = new View[children.length - length + views.length];
- System.arraycopy(children, 0, newChildren, 0, offset);
- System.arraycopy(views, 0, newChildren, offset, views.length);
- System.arraycopy(children, offset + length, newChildren,
- offset + views.length,
- children.length - (offset + length));
- children = newChildren;
+ // Update the children array.
+ int delta = views.length - length;
+ int src = offset + length;
+ int numMove = numChildren - src;
+ int dst = src + delta;
+ if (numChildren + delta > children.length)
+ {
+ // Grow array.
+ int newLength = Math.max(2 * children.length, numChildren + delta);
+ View[] newChildren = new View[newLength];
+ System.arraycopy(children, 0, newChildren, 0, offset);
+ System.arraycopy(views, 0, newChildren, offset, views.length);
+ System.arraycopy(children, src, newChildren, dst, numMove);
+ children = newChildren;
+ }
+ else
+ {
+ // Patch existing array.
+ System.arraycopy(children, src, children, dst, numMove);
+ System.arraycopy(views, 0, children, offset, views.length);
+ }
+ numChildren += delta;
// Finally we set the parent of the added children to this.
for (int i = 0; i < views.length; ++i)
views[i].setParent(this);
}
/**
* Returns the allocation for the specified child <code>View</code>.
*
* @param index the index of the child view
* @param a the allocation for this view
*
Index: javax/swing/text/BoxView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/BoxView.java,v
retrieving revision 1.22
diff -u -1 -2 -r1.22 BoxView.java
--- javax/swing/text/BoxView.java 25 Aug 2006 11:20:46 -0000 1.22
+++ javax/swing/text/BoxView.java 28 Aug 2006 15:47:47 -0000
@@ -213,74 +213,84 @@
*
* In addition this invalidates the layout and resizes the internal cache
* for the child allocations. The old children's cached allocations can
* still be accessed (although they are not guaranteed to be valid), and
* the new children will have an initial offset and span of 0.
*
* @param offset the start offset from where to remove children
* @param length the number of children to remove
* @param views the views that replace the removed children
*/
public void replace(int offset, int length, View[] views)
{
- int numViews = 0;
- if (views != null)
- numViews = views.length;
-
- // Resize and copy data for cache arrays.
- // The spansX cache.
- int oldSize = getViewCount();
-
- int[] newSpansX = new int[oldSize - length + numViews];
- System.arraycopy(spans[X_AXIS], 0, newSpansX, 0, offset);
- System.arraycopy(spans[X_AXIS], offset + length, newSpansX,
- offset + numViews,
- oldSize - (offset + length));
- spans[X_AXIS] = newSpansX;
-
- // The spansY cache.
- int[] newSpansY = new int[oldSize - length + numViews];
- System.arraycopy(spans[Y_AXIS], 0, newSpansY, 0, offset);
- System.arraycopy(spans[Y_AXIS], offset + length, newSpansY,
- offset + numViews,
- oldSize - (offset + length));
- spans[Y_AXIS] = newSpansY;
-
- // The offsetsX cache.
- int[] newOffsetsX = new int[oldSize - length + numViews];
- System.arraycopy(offsets[X_AXIS], 0, newOffsetsX, 0, offset);
- System.arraycopy(offsets[X_AXIS], offset + length, newOffsetsX,
- offset + numViews,
- oldSize - (offset + length));
- offsets[X_AXIS] = newOffsetsX;
-
- // The offsetsY cache.
- int[] newOffsetsY = new int[oldSize - length + numViews];
- System.arraycopy(offsets[Y_AXIS], 0, newOffsetsY, 0, offset);
- System.arraycopy(offsets[Y_AXIS], offset + length, newOffsetsY,
- offset + numViews,
- oldSize - (offset + length));
- offsets[Y_AXIS] = newOffsetsY;
+ int oldNumChildren = getViewCount();
// Actually perform the replace.
super.replace(offset, length, views);
+ // Resize and copy data for cache arrays.
+ int newItems = views != null ? views.length : 0;
+ int delta = newItems - length;
+ int src = offset + length;
+ int numMove = oldNumChildren - src;
+ int dst = src + delta;
+ offsets[X_AXIS] = replaceLayoutArray(offsets[X_AXIS], offset,
+ oldNumChildren, delta, src, dst,
+ numMove);
+ spans[X_AXIS] = replaceLayoutArray(spans[X_AXIS], offset,
+ oldNumChildren, delta, src, dst,
+ numMove);
+ offsets[Y_AXIS] = replaceLayoutArray(offsets[Y_AXIS], offset,
+ oldNumChildren, delta, src, dst,
+ numMove);
+ spans[Y_AXIS] = replaceLayoutArray(spans[Y_AXIS], offset,
+ oldNumChildren, delta, src, dst,
+ numMove);
+
// Invalidate layout information.
layoutValid[X_AXIS] = false;
requirementsValid[X_AXIS] = false;
layoutValid[Y_AXIS] = false;
requirementsValid[Y_AXIS] = false;
}
/**
+ * Helper method. This updates the layout cache arrays in response
+ * to a call to [EMAIL PROTECTED] #replace(int, int, View[])}.
+ *
+ * @param oldArray the old array
+ *
+ * @return the replaced array
+ */
+ private int[] replaceLayoutArray(int[] oldArray, int offset, int numChildren,
+ int delta, int src, int dst, int numMove)
+
+ {
+ int[] newArray;
+ if (numChildren + delta > oldArray.length)
+ {
+ int newLength = Math.max(2 * oldArray.length, numChildren + delta);
+ newArray = new int[newLength];
+ System.arraycopy(oldArray, 0, newArray, 0, offset);
+ System.arraycopy(oldArray, src, newArray, dst, numMove);
+ }
+ else
+ {
+ newArray = oldArray;
+ System.arraycopy(newArray, src, newArray, dst, numMove);
+ }
+ return newArray;
+ }
+
+ /**
* Renders the <code>Element</code> that is associated with this
* <code>View</code>.
*
* @param g the <code>Graphics</code> context to render to
* @param a the allocated region for the <code>Element</code>
*/
public void paint(Graphics g, Shape a)
{
Rectangle alloc;
if (a instanceof Rectangle)
alloc = (Rectangle) a;
else
@@ -642,42 +652,72 @@
*
* @param x the X coordinate
* @param y the Y coordinate
* @param r the inner allocation of this <code>BoxView</code> on entry,
* the allocation of the found child on exit
*
* @return the child <code>View</code> at the specified location
*/
protected View getViewAtPoint(int x, int y, Rectangle r)
{
View result = null;
int count = getViewCount();
- Rectangle copy = new Rectangle(r);
-
- for (int i = 0; i < count; ++i)
+ if (myAxis == X_AXIS)
{
- copy.setBounds(r);
- // The next call modifies copy.
- childAllocation(i, copy);
- if (copy.contains(x, y))
- {
- // Modify r on success.
- r.setBounds(copy);
- result = getView(i);
- break;
+ // Border case. Requested point is left from the box.
+ if (x < r.x + offsets[X_AXIS][0])
+ {
+ childAllocation(0, r);
+ result = getView(0);
+ }
+ else
+ {
+ // Search views inside box.
+ for (int i = 0; i < count && result == null; i++)
+ {
+ if (x < r.x + offsets[X_AXIS][i])
+ {
+ childAllocation(i - 1, r);
+ result = getView(i - 1);
+ }
+ }
}
}
-
- if (result == null && count > 0)
- return getView(count - 1);
+ else // Same algorithm for Y_AXIS.
+ {
+ // Border case. Requested point is above the box.
+ if (y < r.y + offsets[Y_AXIS][0])
+ {
+ childAllocation(0, r);
+ result = getView(0);
+ }
+ else
+ {
+ // Search views inside box.
+ for (int i = 0; i < count && result == null; i++)
+ {
+ if (y < r.y + offsets[Y_AXIS][i])
+ {
+ childAllocation(i - 1, r);
+ result = getView(i - 1);
+ }
+ }
+ }
+ }
+ // Not found, other border case: point is right from or below the box.
+ if (result == null)
+ {
+ childAllocation(count - 1, r);
+ result = getView(count - 1);
+ }
return result;
}
/**
* Computes the allocation for a child <code>View</code>. The parameter
* <code>a</code> stores the allocation of this <code>CompositeView</code>
* and is then adjusted to hold the allocation of the child view.
*
* @param index
* the index of the child <code>View</code>
* @param a
* the allocation of this <code>CompositeView</code> before the
@@ -1030,25 +1070,29 @@
return ret;
}
protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e,
Shape a, ViewFactory vf)
{
// FIXME: What to do here?
super.forwardUpdate(ec, e, a, vf);
}
public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
{
- // FIXME: What to do here?
+ if (! isAllocationValid())
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ setSize(r.width, r.height);
+ }
return super.viewToModel(x, y, a, bias);
}
protected boolean flipEastAndWestAtEnds(int position, Position.Bias bias)
{
// FIXME: What to do here?
return super.flipEastAndWestAtEnds(position, bias);
}
/**
* Updates the view's cached requirements along the specified axis if
* necessary. The requirements are only updated if the layout for the