I implemented the javax.swing.text.AsyncBoxView.

This is really a piece of code that actually works better than with the
latest JDK. JDK1.5.0_06 seems to have a bug which triggers an NPE with
this class. I would guess that since that view is not used at all in one
of the standard text components, there sneaked in a bug that nobody
noticed. Good for us.

2006-02-13  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/text/AsyncBoxView.java: New file.

/Roman
Index: javax/swing/text/AsyncBoxView.java
===================================================================
RCS file: javax/swing/text/AsyncBoxView.java
diff -N javax/swing/text/AsyncBoxView.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ javax/swing/text/AsyncBoxView.java	13 Feb 2006 13:43:12 -0000
@@ -0,0 +1,1486 @@
+/* AsyncBoxView.java -- A box view that performs layout asynchronously
+   Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+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.text;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.util.ArrayList;
+
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.Position.Bias;
+
+/**
+ * A [EMAIL PROTECTED] View} implementation that lays out its child views in a box, either
+ * vertically or horizontally. The difference to [EMAIL PROTECTED] BoxView} is that the
+ * layout is performed in an asynchronous manner. This helps to keep the
+ * eventqueue free from non-GUI related tasks.
+ *
+ * This view is currently not used in standard text components. In order to
+ * use it you would have to implement a special [EMAIL PROTECTED] EditorKit} with a
+ * [EMAIL PROTECTED] ViewFactory} that returns this view. For example:
+ *
+ * <pre>
+ * static class AsyncEditorKit extends StyledEditorKit implements ViewFactory
+ * {
+ *   public View create(Element el)
+ *   {
+ *     if (el.getName().equals(AbstractDocument.SectionElementName))
+ *       return new AsyncBoxView(el, View.Y_AXIS);
+ *     return super.getViewFactory().create(el);
+ *   }
+ *   public ViewFactory getViewFactory() {
+ *     return this;
+ *   }
+ * }
+ * </pre>
+ *
+ * @author Roman Kennke ([EMAIL PROTECTED])
+ *
+ * @since 1.3
+ */
+public class AsyncBoxView
+  extends View
+{
+
+  /**
+   * Manages the effective position of child views. That keeps the visible
+   * layout stable while the AsyncBoxView might be changing until the layout
+   * thread decides to publish the new layout.
+   */
+  public class ChildLocator
+  {
+
+    /**
+     * The last valid location.
+     */
+    protected ChildState lastValidOffset;
+
+    /**
+     * The last allocation.
+     */
+    protected Rectangle lastAlloc;
+
+    /**
+     * A Rectangle used for child allocation calculation to avoid creation
+     * of lots of garbage Rectangle objects.
+     */
+    protected Rectangle childAlloc;
+
+    /**
+     * Creates a new ChildLocator.
+     */
+    public ChildLocator()
+    {
+      lastAlloc = new Rectangle();
+      childAlloc = new Rectangle();
+    }
+
+    /**
+     * Receives notification that a child has changed. This is called by
+     * child state objects that have changed it's major span.
+     *
+     * This sets the [EMAIL PROTECTED] #lastValidOffset} field to <code>cs</code> if
+     * the new child state's view start offset is smaller than the start offset
+     * of the current child state's view or when <code>lastValidOffset</code>
+     * is <code>null</code>.
+     *
+     * @param cs the child state object that has changed
+     */
+    public synchronized void childChanged(ChildState cs)
+    {
+      if (lastValidOffset == null
+          || cs.getChildView().getStartOffset()
+             < lastValidOffset.getChildView().getStartOffset())
+        {
+          lastValidOffset = cs;
+        }
+    }
+
+    /**
+     * Returns the view index of the view that occupies the specified area, or
+     * <code>-1</code> if there is no such child view.
+     *
+     * @param x the x coordinate (relative to <code>a</code>)
+     * @param y the y coordinate (relative to <code>a</code>)
+     * @param a the current allocation of this view
+     *
+     * @return the view index of the view that occupies the specified area, or
+     *         <code>-1</code> if there is no such child view
+     */
+    public int getViewIndexAtPoint(float x, float y, Shape a)
+    {
+      setAllocation(a);
+      float targetOffset = (getMajorAxis() == X_AXIS) ? x - lastAlloc.x
+                                                      : y - lastAlloc.y;
+      int index = getViewIndexAtVisualOffset(targetOffset);
+      return index;
+    }
+
+    /**
+     * Returns the current allocation for a child view. This updates the
+     * offsets for all children <em>before</em> the requested child view.
+     *
+     * @param index the index of the child view
+     * @param a the current allocation of this view
+     * 
+     * @return the current allocation for a child view
+     */
+    public synchronized Shape getChildAllocation(int index, Shape a)
+    {
+      if (a == null)
+        return null;
+      setAllocation(a);
+      ChildState cs = getChildState(index);
+      if (cs.getChildView().getStartOffset()
+          > lastValidOffset.getChildView().getStartOffset())
+        {
+          updateChildOffsetsToIndex(index);
+        }
+      Shape ca = getChildAllocation(index);
+      return ca;
+    }
+
+    /**
+     * Paints all child views.
+     *
+     * @param g the graphics context to use
+     */
+    public synchronized void paintChildren(Graphics g)
+    {
+      Rectangle clip = g.getClipBounds();
+      float targetOffset = (getMajorAxis() == X_AXIS) ? clip.x - lastAlloc.x
+                                                      : clip.y - lastAlloc.y;
+      int index = getViewIndexAtVisualOffset(targetOffset);
+      int n = getViewCount();
+      float offs = getChildState(index).getMajorOffset();
+      for (int i = index; i < n; i++)
+        {
+          ChildState cs = getChildState(i);
+          cs.setMajorOffset(offs);
+          Shape ca = getChildAllocation(i);
+          if (ca.intersects(clip))
+            {
+              synchronized (cs)
+                {
+                  View v = cs.getChildView();
+                  v.paint(g, ca);
+                }
+            }
+          else
+            {
+              // done painting intersection
+              break;
+            }
+          offs += cs.getMajorSpan();
+        }
+    }
+
+    /**
+     * Returns the current allocation of the child view with the specified
+     * index. Note that this will <em>not</em> update any location information.
+     * 
+     * @param index the index of the requested child view
+     *
+     * @return the current allocation of the child view with the specified
+     *         index
+     */
+    protected Shape getChildAllocation(int index)
+    {
+      ChildState cs = getChildState(index);
+      if (! cs.isLayoutValid())
+          cs.run();
+
+      if (getMajorAxis() == X_AXIS)
+        {
+          childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset();
+          childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset();
+          childAlloc.width = (int) cs.getMajorSpan();
+          childAlloc.height = (int) cs.getMinorSpan();
+        }
+      else
+        {
+          childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset();
+          childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset();
+          childAlloc.height = (int) cs.getMajorSpan();
+          childAlloc.width = (int) cs.getMinorSpan();
+        }
+      return childAlloc;
+    }
+
+    /**
+     * Sets the current allocation for this view.
+     *
+     * @param a the allocation to set
+     */
+    protected void setAllocation(Shape a)
+    {
+      if (a instanceof Rectangle)
+        lastAlloc.setBounds((Rectangle) a);
+      else
+        lastAlloc.setBounds(a.getBounds());
+
+      setSize(lastAlloc.width, lastAlloc.height);
+    }
+
+    /**
+     * Returns the index of the view at the specified offset along the major
+     * layout axis.
+     *
+     * @param targetOffset the requested offset
+     *
+     * @return the index of the view at the specified offset along the major
+     * layout axis
+     */
+    protected int getViewIndexAtVisualOffset(float targetOffset)
+    {
+      int n = getViewCount();
+      if (n > 0)
+        {
+          if (lastValidOffset == null)
+            lastValidOffset = getChildState(0);
+          if (targetOffset > majorSpan)
+            return 0;
+          else if (targetOffset > lastValidOffset.getMajorOffset())
+            return updateChildOffsets(targetOffset);
+          else
+            {
+              float offs = 0f;
+              for (int i = 0; i < n; i++)
+                {
+                  ChildState cs = getChildState(i);
+                  float nextOffs = offs + cs.getMajorSpan();
+                  if (targetOffset < nextOffs)
+                    return i;
+                  offs = nextOffs;
+                }
+            }
+        }
+      return n - 1;
+    }
+
+    /**
+     * Updates all the child view offsets up to the specified targetOffset.
+     *
+     * @param targetOffset the offset up to which the child view offsets are
+     *        updated
+     *
+     * @return the index of the view at the specified offset
+     */
+    private int updateChildOffsets(float targetOffset)
+    {
+      int n = getViewCount();
+      int targetIndex = n - 1;;
+      int pos = lastValidOffset.getChildView().getStartOffset();
+      int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
+      float start = lastValidOffset.getMajorOffset();
+      float lastOffset = start;
+      for (int i = startIndex; i < n; i++)
+        {
+          ChildState cs = getChildState(i);
+          cs.setMajorOffset(lastOffset);
+          lastOffset += cs.getMajorSpan();
+          if (targetOffset < lastOffset)
+            {
+              targetIndex = i;
+              lastValidOffset = cs;
+              break;
+            }
+        }
+      return targetIndex;
+    }
+
+    /**
+     * Updates the offsets of the child views up to the specified index.
+     *
+     * @param index the index up to which the offsets are updated
+     */
+    private void updateChildOffsetsToIndex(int index)
+    {
+      int pos = lastValidOffset.getChildView().getStartOffset();
+      int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
+      float lastOffset = lastValidOffset.getMajorOffset();
+      for (int i = startIndex; i <= index; i++)
+        {
+          ChildState cs = getChildState(i);
+          cs.setMajorOffset(lastOffset);
+          lastOffset += cs.getMajorSpan();
+        }
+    }
+  }
+
+  /**
+   * Represents the layout state of a child view.
+   */
+  public class ChildState
+    implements Runnable
+  {
+
+    /**
+     * The child view for this state record.
+     */
+    private View childView;
+
+    /**
+     * The locator for the child view.
+     */
+    private ChildLocator locator;
+
+    /**
+     * Indicates if the minor axis requirements of this child view are valid
+     * or not.
+     */
+    private boolean minorValid;
+
+    /**
+     * Indicates if the major axis requirements of this child view are valid
+     * or not.
+     */
+    private boolean majorValid;
+
+    /**
+     * Indicates if the current child size is valid. This is package private
+     * to avoid synthetic accessor method.
+     */
+    boolean childSizeValid;
+
+    /**
+     * The child views minimumSpan. This is package private to avoid accessor
+     * method.
+     */
+    float minimum;
+
+    /**
+     * The child views preferredSpan. This is package private to avoid accessor
+     * method.
+     */
+    float preferred;
+
+    /**
+     * The current span of the child view along the major axis.
+     */
+    private float majorSpan;
+
+    /**
+     * The current offset of the child view along the major axis.
+     */
+    private float majorOffset;
+
+    /**
+     * The current span of the child view along the minor axis.
+     */
+    private float minorSpan;
+
+    /**
+     * The current offset of the child view along the major axis.
+     */
+    private float minorOffset;
+
+    /**
+     * The child views maximumSpan.
+     */
+    private float maximum;
+
+    /**
+     * Creates a new <code>ChildState</code> object for the specified child
+     * view.
+     *
+     * @param view the child view for which to create the state record
+     */
+    public ChildState(View view)
+    {
+      childView = view;
+      locator = new ChildLocator();
+    }
+
+    /**
+     * Returns the child view for which this <code>ChildState</code> represents
+     * the layout state.
+     *
+     * @return the child view for this child state object 
+     */
+    public View getChildView()
+    {
+      return childView;
+    }
+
+    /**
+     * Returns <code>true</code> if the current layout information is valid,
+     * <code>false</code> otherwise.
+     *
+     * @return <code>true</code> if the current layout information is valid,
+     *         <code>false</code> otherwise
+     */
+    public boolean isLayoutValid()
+    {
+      return minorValid && majorValid && childSizeValid;
+    }
+
+    /**
+     * Performs the layout update for the child view managed by this
+     * <code>ChildState</code>.
+     */
+    public void run()
+    {
+      Document doc = getDocument();
+      if (doc instanceof AbstractDocument)
+        {
+          AbstractDocument abstractDoc = (AbstractDocument) doc;
+          abstractDoc.readLock();
+        }
+
+      try
+        {
+
+          if (!(minorValid &&  majorValid && childSizeValid)
+              && childView.getParent() == AsyncBoxView.this)
+            {
+              synchronized(AsyncBoxView.this)
+              {
+                changing = this;
+              }
+              update();
+              synchronized(AsyncBoxView.this)
+              {
+                changing = null;
+              }
+              // Changing the major axis may cause the minor axis
+              // requirements to have changed, so we need to do this again.
+              update();
+            }
+        }
+      finally
+        {
+          if (doc instanceof AbstractDocument)
+            {
+              AbstractDocument abstractDoc = (AbstractDocument) doc;
+              abstractDoc.readUnlock();
+            }
+        }
+    }
+
+    /**
+     * Performs the actual update after the run methods has made its checks
+     * and locked the document.
+     */
+    private void update()
+    {
+      int majorAxis = getMajorAxis();
+      boolean minorUpdated = false;
+      synchronized (this)
+        {
+          if (! minorValid)
+            {
+              int minorAxis = getMinorAxis();
+              minimum = childView.getMinimumSpan(minorAxis);
+              preferred = childView.getPreferredSpan(minorAxis);
+              maximum = childView.getMaximumSpan(minorAxis);
+              minorValid = true;
+              minorUpdated = true;
+            }
+        }
+      if (minorUpdated)
+        minorRequirementChange(this);
+
+      boolean majorUpdated = false;
+      float delta = 0.0F;
+      synchronized (this)
+        {
+          if (! majorValid)
+            {
+              float oldSpan = majorSpan;
+              majorSpan = childView.getPreferredSpan(majorAxis);
+              delta = majorSpan - oldSpan;
+              majorValid = true;
+              majorUpdated = true;
+            }
+        }
+      if (majorUpdated)
+        {
+          majorRequirementChange(this, delta);
+          locator.childChanged(this);
+        }
+
+      synchronized (this)
+        {
+          if (! childSizeValid)
+            {
+              float w;
+              float h;
+              if (majorAxis == X_AXIS)
+                {
+                  w = majorSpan;
+                  h = getMinorSpan();
+                }
+              else
+                {
+                  w = getMinorSpan();
+                  h = majorSpan;
+                }
+              childSizeValid = true;
+              childView.setSize(w, h);
+            }
+        }
+    }
+
+    /**
+     * Returns the span of the child view along the minor layout axis.
+     *
+     * @return the span of the child view along the minor layout axis
+     */
+    public float getMinorSpan()
+    {
+      float retVal;
+      if (maximum < minorSpan)
+        retVal = maximum;
+      else
+        retVal = Math.max(minimum, minorSpan);
+      return retVal;
+    }
+
+    /**
+     * Returns the offset of the child view along the minor layout axis.
+     *
+     * @return the offset of the child view along the minor layout axis
+     */
+    public float getMinorOffset()
+    {
+      float retVal;
+      if (maximum < minorSpan)
+        {
+          float align = childView.getAlignment(getMinorAxis());
+          retVal = ((minorSpan - maximum) * align);
+        }
+      else
+        retVal = 0f;
+
+      return retVal;
+    }
+
+    /**
+     * Returns the span of the child view along the major layout axis.
+     *
+     * @return the span of the child view along the major layout axis
+     */
+
+    public float getMajorSpan()
+    {
+      return majorSpan;
+    }
+
+    /**
+     * Returns the offset of the child view along the major layout axis.
+     *
+     * @return the offset of the child view along the major layout axis
+     */
+    public float getMajorOffset()
+    {
+      return majorOffset;
+    }
+
+    /**
+     * Sets the offset of the child view along the major layout axis. This
+     * should only be called by the ChildLocator of that child view.
+     *
+     * @param offset the offset to set
+     */
+    public void setMajorOffset(float offset)
+    {
+      majorOffset = offset;
+    }
+
+    /**
+     * Mark the preferences changed for that child. This forwards to
+     * [EMAIL PROTECTED] AsyncBoxView#preferenceChanged}.
+     *
+     * @param width <code>true</code> if the width preference has changed
+     * @param height <code>true</code> if the height preference has changed
+     */
+    public void preferenceChanged(boolean width, boolean height)
+    {
+      if (getMajorAxis() == X_AXIS)
+        {
+          if (width)
+            majorValid = false;
+          if (height)
+            minorValid = false;
+        }
+      else
+        {
+          if (width)
+            minorValid = false;
+          if (height)
+            majorValid = false;
+        }
+      childSizeValid = false;
+    }
+  }
+
+  /**
+   * Flushes the requirements changes upwards asynchronously.
+   */
+  private class FlushTask implements Runnable
+  {
+    /**
+     * Starts the flush task. This obtains a readLock on the document
+     * and then flushes all the updates using
+     * [EMAIL PROTECTED] AsyncBoxView#flushRequirementChanges()} after updating the
+     * requirements.
+     */
+    public void run()
+    {
+      try
+        {
+          // Acquire a lock on the document.
+          Document doc = getDocument();
+          if (doc instanceof AbstractDocument)
+            {
+              AbstractDocument abstractDoc = (AbstractDocument) doc;
+              abstractDoc.readLock();
+            }
+
+          int n = getViewCount();
+          if (minorChanged && (n > 0))
+            {
+              LayoutQueue q = getLayoutQueue();
+              ChildState min = getChildState(0);
+              ChildState pref = getChildState(0);
+              for (int i = 1; i < n; i++)
+                {
+                  ChildState cs = getChildState(i);
+                  if (cs.minimum > min.minimum)
+                    min = cs;
+                  if (cs.preferred > pref.preferred)
+                    pref = cs;
+                }
+              synchronized (AsyncBoxView.this)
+              {
+                minReq = min;
+                prefReq = pref;
+              }
+            }
+
+          flushRequirementChanges();
+        }
+      finally
+      {
+        // Release the lock on the document.
+        Document doc = getDocument();
+        if (doc instanceof AbstractDocument)
+          {
+            AbstractDocument abstractDoc = (AbstractDocument) doc;
+            abstractDoc.readUnlock();
+          }
+      }
+    }
+
+  }
+
+  /**
+   * The major layout axis.
+   */
+  private int majorAxis;
+
+  /**
+   * The top inset.
+   */
+  private float topInset;
+
+  /**
+   * The bottom inset.
+   */
+  private float bottomInset;
+
+  /**
+   * The left inset.
+   */
+  private float leftInset;
+
+  /**
+   * Indicates if the major span should be treated as beeing estimated or not.
+   */
+  private boolean estimatedMajorSpan;
+
+  /**
+   * The right inset.
+   */
+  private float rightInset;
+
+  /**
+   * The children and their layout statistics.
+   */
+  private ArrayList childStates;
+
+  /**
+   * The currently changing child state. May be null if there is no child state
+   * updating at the moment. This is package private to avoid a synthetic
+   * accessor method inside ChildState.
+   */
+  ChildState changing;
+
+  /**
+   * Represents the minimum requirements. This is used in
+   * [EMAIL PROTECTED] #getMinimumSpan(int)}.
+   */
+  ChildState minReq;
+
+  /**
+   * Represents the minimum requirements. This is used in
+   * [EMAIL PROTECTED] #getPreferredSpan(int)}.
+   */
+  ChildState prefReq;
+
+  /**
+   * Indicates that the major axis requirements have changed.
+   */
+  private boolean majorChanged;
+
+  /**
+   * Indicates that the minor axis requirements have changed. This is package
+   * private to avoid synthetic accessor method.
+   */
+  boolean minorChanged;
+
+  /**
+   * The current span along the major layout axis. This is package private to
+   * avoid synthetic accessor method.
+   */
+  float majorSpan;
+
+  /**
+   * The current span along the minor layout axis. This is package private to
+   * avoid synthetic accessor method.
+   */
+  float minorSpan;
+
+  /**
+   * This tasked is placed on the layout queue to flush updates up to the
+   * parent view.
+   */
+  private Runnable flushTask;
+
+  /**
+   * The child locator for this view.
+   */
+  private ChildLocator locator;
+
+  /**
+   * Creates a new <code>AsyncBoxView</code> that represents the specified
+   * element and layouts its children along the specified axis.
+   *
+   * @param elem the element
+   * @param axis the layout axis
+   */
+  public AsyncBoxView(Element elem, int axis)
+  {
+    super(elem);
+    majorAxis = axis;
+    childStates = new ArrayList();
+    flushTask = new FlushTask();
+    locator = new ChildLocator();
+    minorSpan = Short.MAX_VALUE;
+  }
+
+  /**
+   * Returns the major layout axis.
+   *
+   * @return the major layout axis
+   */
+  public int getMajorAxis()
+  {
+    return majorAxis;
+  }
+
+  /**
+   * Returns the minor layout axis, that is the axis orthogonal to the major
+   * layout axis.
+   *
+   * @return the minor layout axis
+   */
+  public int getMinorAxis()
+  {
+    return majorAxis == X_AXIS ? Y_AXIS : X_AXIS;
+  }
+
+  /**
+   * Returns the view at the specified <code>index</code>.
+   *
+   * @param index the index of the requested child view
+   *
+   * @return the view at the specified <code>index</code>
+   */
+  public View getView(int index)
+  {
+    View view = null;
+    synchronized(childStates)
+      {
+        if ((index >= 0) && (index < childStates.size()))
+          {
+            ChildState cs = (ChildState) childStates.get(index);
+            view = cs.getChildView();
+          }
+      }
+    return view;
+  }
+
+  /**
+   * Returns the number of child views.
+   *
+   * @return the number of child views
+   */
+  public int getViewCount()
+  {
+    synchronized(childStates)
+    {
+      return childStates.size();
+    }
+  }
+
+  /**
+   * Returns the view index of the child view that represents the specified
+   * model position.
+   *
+   * @param pos the model position for which we search the view index
+   * @param bias the bias
+   *
+   * @return the view index of the child view that represents the specified
+   *         model position
+   */
+  public int getViewIndex(int pos, Position.Bias bias)
+  {
+    int retVal = -1;
+
+    if (bias == Position.Bias.Backward)
+      pos = Math.max(0, pos - 1);
+
+    // TODO: A possible optimization would be to implement a binary search
+    // here.
+    int numChildren = childStates.size();
+    if (numChildren > 0)
+      {
+        for (int i = 0; i < numChildren; ++i)
+          {
+            View child = ((ChildState) childStates.get(i)).getChildView();
+            if (child.getStartOffset() <= pos && child.getEndOffset() > pos)
+              {
+                retVal = i;
+                break;
+              }
+          }
+      }
+    return retVal;
+  }
+
+  /**
+   * Returns the top inset.
+   *
+   * @return the top inset
+   */
+  public float getTopInset()
+  {
+    return topInset;
+  }
+
+  /**
+   * Sets the top inset.
+   *
+   * @param top the top inset
+   */
+  public void setTopInset(float top)
+  {
+    topInset = top;
+  }
+
+  /**
+   * Returns the bottom inset.
+   *
+   * @return the bottom inset
+   */
+  public float getBottomInset()
+  {
+    return bottomInset;
+  }
+
+  /**
+   * Sets the bottom inset.
+   *
+   * @param bottom the bottom inset
+   */
+  public void setBottomInset(float bottom)
+  {
+    bottomInset = bottom;
+  }
+
+  /**
+   * Returns the left inset.
+   *
+   * @return the left inset
+   */
+  public float getLeftInset()
+  {
+    return leftInset;
+  }
+
+  /**
+   * Sets the left inset.
+   *
+   * @param left the left inset
+   */
+  public void setLeftInset(float left)
+  {
+    leftInset = left;
+  }
+
+  /**
+   * Returns the right inset.
+   *
+   * @return the right inset
+   */
+  public float getRightInset()
+  {
+    return rightInset;
+  }
+
+  /**
+   * Sets the right inset.
+   *
+   * @param right the right inset
+   */
+  public void setRightInset(float right)
+  {
+    rightInset = right;
+  }
+
+  /**
+   * Loads the child views of this view. This is triggered by
+   * [EMAIL PROTECTED] #setParent(View)}.
+   *
+   * @param f the view factory to build child views with
+   */
+  protected void loadChildren(ViewFactory f)
+  {
+    Element e = getElement();
+    int n = e.getElementCount();
+    if (n > 0)
+      {
+        View[] added = new View[n];
+        for (int i = 0; i < n; i++)
+          {
+            added[i] = f.create(e.getElement(i));
+          }
+        replace(0, 0, added);
+      }
+  }
+  
+  /**
+   * Returns the span along an axis that is taken up by the insets.
+   *
+   * @param axis the axis
+   *
+   * @return the span along an axis that is taken up by the insets
+   *
+   * @since 1.4
+   */
+  protected float getInsetSpan(int axis)
+  {
+    float span;
+    if (axis == X_AXIS)
+      span = leftInset + rightInset;
+    else
+      span = topInset + bottomInset;
+    return span;
+  }
+
+  /**
+   * Sets the <code>estimatedMajorSpan</code> property that determines if
+   * the major span should be treated as beeing estimated.
+   *
+   * @param estimated if the major span should be treated as estimated or not
+   *
+   * @since 1.4
+   */
+  public void setEstimatedMajorSpan(boolean estimated)
+  {
+    estimatedMajorSpan = estimated;
+  }
+
+  /**
+   * Determines whether the major span should be treated as estimated or as
+   * beeing accurate.
+   *
+   * @return <code>true</code> if the major span should be treated as
+   *         estimated, <code>false</code> if the major span should be treated
+   *         as accurate
+   *
+   * @since 1.4
+   */
+  public boolean getEstimatedMajorSpan()
+  {
+    return estimatedMajorSpan;
+  }
+
+  /**
+   * Receives notification from the child states that the requirements along
+   * the minor axis have changed.
+   *
+   * @param cs the child state from which this notification is messaged
+   */
+  protected synchronized void minorRequirementChange(ChildState cs)
+  {
+    minorChanged = true;
+  }
+
+  /**
+   * Receives notification from the child states that the requirements along
+   * the major axis have changed.
+   *
+   * @param cs the child state from which this notification is messaged
+   */
+  protected void majorRequirementChange(ChildState cs, float delta)
+  {
+    if (! estimatedMajorSpan)
+      majorSpan += delta;
+    majorChanged = true;
+  }
+
+  /**
+   * Sets the parent for this view. This calls loadChildren if
+   * <code>parent</code> is not <code>null</code> and there have not been any
+   * child views initializes.
+   *
+   * @param parent the new parent view; <code>null</code> if this view is
+   *        removed from the view hierarchy
+   *
+   * @see View#setParent(View)
+   */
+  public void setParent(View parent)
+  {
+    super.setParent(parent);
+    if ((parent != null) && (getViewCount() == 0))
+      {
+        ViewFactory f = getViewFactory();
+        loadChildren(f);
+      }
+  }
+
+  /**
+   * Sets the size of this view. This is ususally called before [EMAIL PROTECTED] #paint}
+   * is called to make sure the view has a valid layout.
+   *
+   * This implementation queues layout requests for every child view if the
+   * minor axis span has changed. (The major axis span is requested to never
+   * change for this view).
+   *
+   * @param width the width of the view
+   * @param height the height of the view
+   */
+  public void setSize(float width, float height)
+  {
+    float targetSpan;
+    if (majorAxis == X_AXIS)
+      targetSpan = height - getTopInset() - getBottomInset();
+    else
+      targetSpan = width - getLeftInset() - getRightInset();
+
+    if (targetSpan != minorSpan)
+      {
+        minorSpan = targetSpan;
+
+        int n = getViewCount();
+        LayoutQueue q = getLayoutQueue();
+        for (int i = 0; i < n; i++)
+          {
+            ChildState cs = getChildState(i);
+            cs.childSizeValid = false;
+            q.addTask(cs);
+          }
+        q.addTask(flushTask);
+    }
+  }
+
+  /**
+   * Replaces child views with new child views.
+   *
+   * This creates ChildState objects for all the new views and adds layout
+   * requests for them to the layout queue.
+   *
+   * @param offset the offset at which to remove/insert
+   * @param length the number of child views to remove
+   * @param views the new child views to insert
+   */
+  public void replace(int offset, int length, View[] views)
+  {
+    synchronized(childStates)
+      {
+        LayoutQueue q = getLayoutQueue();
+        for (int i = 0; i < length; i++)
+          childStates.remove(offset);
+
+        for (int i = views.length - 1; i >= 0; i--)
+          childStates.add(offset, createChildState(views[i]));
+
+        // We need to go through the new child states _after_ they have been
+        // added to the childStates list, otherwise the layout tasks may find
+        // an incomplete child list. That means we have to loop through
+        // them again, but what else can we do?
+        if (views.length != 0)
+          {
+            for (int i = 0; i < views.length; i++)
+              {
+                ChildState cs = (ChildState) childStates.get(i + offset);
+                cs.getChildView().setParent(this);
+                q.addTask(cs);
+              }
+            q.addTask(flushTask);
+          }
+      }
+  }
+
+  /**
+   * Paints the view. This requests the [EMAIL PROTECTED] ChildLocator} to paint the views
+   * after setting the allocation on it.
+   *
+   * @param g the graphics context to use
+   * @param s the allocation for this view
+   */
+  public void paint(Graphics g, Shape s)
+  {
+    synchronized (locator)
+      {
+        locator.setAllocation(s);
+        locator.paintChildren(g);
+      }
+  }
+
+  /**
+   * Returns the preferred span of this view along the specified layout axis.
+   *
+   * @return the preferred span of this view along the specified layout axis
+   */
+  public float getPreferredSpan(int axis)
+  {
+    float retVal;
+    if (majorAxis == axis)
+      retVal = majorSpan;
+
+    else if (prefReq != null)
+      {
+        View child = prefReq.getChildView();
+        retVal = child.getPreferredSpan(axis);
+      }
+
+    // If we have no layout information yet, then return insets + 30 as
+    // an estimation.
+    else
+      {
+        if (axis == X_AXIS)
+          retVal = getLeftInset() + getRightInset() + 30;
+        else
+          retVal = getTopInset() + getBottomInset() + 30;
+      }
+    return retVal;
+  }
+
+  /**
+   * Maps a model location to view coordinates.
+   *
+   * @param pos the model location
+   * @param a the current allocation of this view
+   * @param b the bias
+   *
+   * @return the view allocation for the specified model location
+   */
+  public Shape modelToView(int pos, Shape a, Bias b)
+    throws BadLocationException
+  {
+    int index = getViewIndexAtPosition(pos, b);
+    Shape ca = locator.getChildAllocation(index, a);
+
+    ChildState cs = getChildState(index);
+    synchronized (cs)
+      {
+        View cv = cs.getChildView();
+        Shape v = cv.modelToView(pos, ca, b);
+        return v;
+      }
+  }
+
+  /**
+   * Maps view coordinates to a model location.
+   *
+   * @param x the x coordinate (relative to <code>a</code>)
+   * @param y the y coordinate (relative to <code>a</code>)
+   * @param b holds the bias of the model location on method exit
+   *
+   * @return the model location for the specified view location
+   */
+  public int viewToModel(float x, float y, Shape a, Bias[] b)
+  {
+    int pos;
+    int index;
+    Shape ca;
+
+    synchronized (locator)
+      {
+        index = locator.getViewIndexAtPoint(x, y, a);
+        ca = locator.getChildAllocation(index, a);
+      }
+
+    ChildState cs = getChildState(index);
+    synchronized (cs)
+      {
+        View v = cs.getChildView();
+        pos = v.viewToModel(x, y, ca, b);
+      }
+    return pos;
+  }
+
+  /**
+   * Returns the child allocation for the child view with the specified
+   * <code>index</code>.
+   *
+   * @param index the index of the child view
+   * @param a the current allocation of this view
+   *
+   * @return the allocation of the child view
+   */
+  public Shape getChildAllocation(int index, Shape a)
+  {
+    Shape ca = locator.getChildAllocation(index, a);
+    return ca;
+  }
+
+  /**
+   * Returns the maximum span of this view along the specified axis.
+   * This is implemented to return the <code>preferredSpan</code> for the
+   * major axis (that means the box can't be resized along the major axis) and
+   * [EMAIL PROTECTED] Short#MAX_VALUE} for the minor axis.
+   *
+   * @param axis the axis
+   *
+   * @return the maximum span of this view along the specified axis
+   */
+  public float getMaximumSpan(int axis)
+  {
+    float max;
+    if (axis == majorAxis)
+      max = getPreferredSpan(axis);
+    else
+      max = Short.MAX_VALUE;
+    return max;
+  }
+
+  /**
+   * Returns the minimum span along the specified axis.
+   */
+  public float getMinimumSpan(int axis)
+  {
+    float min;
+    if (axis == majorAxis)
+      min = getPreferredSpan(axis);
+    else
+      {
+        if (minReq != null)
+          {
+            View child = minReq.getChildView();
+            min = child.getMinimumSpan(axis);
+          }
+        else
+          {
+            // No layout information yet. Return insets + 5 as some kind of
+            // estimation.
+            if (axis == X_AXIS)
+              min = getLeftInset() + getRightInset() + 5;
+            else
+              min = getTopInset() + getBottomInset() + 5;
+          }
+      }
+    return min;
+  }
+
+  /**
+   * Receives notification that one of the child views has changed its
+   * layout preferences along one or both axis.
+   *
+   * This queues a layout request for that child view if necessary.
+   *
+   * @param view the view that has changed its preferences
+   * @param width <code>true</code> if the width preference has changed
+   * @param height <code>true</code> if the height preference has changed
+   */
+  public synchronized void preferenceChanged(View view, boolean width,
+                                             boolean height)
+  {
+    if (view == null)
+      getParent().preferenceChanged(this, width, height);
+    else
+      {
+        if (changing != null)
+          {
+            View cv = changing.getChildView();
+            if (cv == view)
+              {
+                changing.preferenceChanged(width, height);
+                return;
+              }
+          }
+        int index = getViewIndexAtPosition(view.getStartOffset(), 
+                                           Position.Bias.Forward);
+        ChildState cs = getChildState(index);
+        cs.preferenceChanged(width, height);
+        LayoutQueue q = getLayoutQueue();
+        q.addTask(cs);
+        q.addTask(flushTask);
+      }    
+  }
+
+  /**
+   * Updates the layout for this view. This is implemented to trigger
+   * {link ChildLocator#childChanged} for the changed view, if there is
+   * any.
+   *
+   * @param ec the element change, may be <code>null</code> if there were
+   *        no changes to the element of this view
+   * @param e the document event
+   * @param a the current allocation of this view
+   */
+  protected void updateLayout(DocumentEvent.ElementChange ec, 
+                              DocumentEvent e, Shape a)
+  {
+    if (ec != null)
+      {
+        int index = Math.max(ec.getIndex() - 1, 0);
+        ChildState cs = getChildState(index);
+        locator.childChanged(cs);
+      }
+  }
+  
+  
+  /**
+   * Returns the <code>ChildState</code> object associated with the child view
+   * at the specified <code>index</code>.
+   *
+   * @param index the index of the child view for which to query the state
+   *
+   * @return the child state for the specified child view
+   */
+  protected ChildState getChildState(int index) {
+    synchronized (childStates)
+      {
+        return (ChildState) childStates.get(index);
+      }
+  }
+
+  /**
+   * Returns the <code>LayoutQueue</code> used for layouting the box view.
+   * This simply returns [EMAIL PROTECTED] LayoutQueue#getDefaultQueue()}.
+   *
+   * @return the <code>LayoutQueue</code> used for layouting the box view
+   */
+  protected LayoutQueue getLayoutQueue()
+  {
+    return LayoutQueue.getDefaultQueue();
+  }
+
+  /**
+   * Returns the child view index of the view that represents the specified
+   * position in the document model.
+   * 
+   * @param pos the position in the model
+   * @param b the bias
+   *
+   * @return the child view index of the view that represents the specified
+   *         position in the document model
+   */
+  protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b)
+  {
+    if (b == Position.Bias.Backward)
+      pos = Math.max(0, pos - 1);
+    Element elem = getElement();
+    return elem.getElementIndex(pos);
+  }
+
+  /**
+   * Creates a <code>ChildState</code> object for the specified view.
+   *
+   * @param v the view for which to create a child state object
+   *
+   * @return the created child state
+   */
+  protected ChildState createChildState(View v)
+  {
+    return new ChildState(v);
+  }
+
+  /**
+   * Flushes the requirements changes upwards to the parent view. This is
+   * called from the layout thread.
+   */
+  protected synchronized void flushRequirementChanges()
+  {
+    if (majorChanged || minorChanged)
+      {
+        View p = getParent();
+        if (p != null)
+          {
+            boolean horizontal;
+            boolean vertical;
+            if (majorAxis == X_AXIS)
+              {
+                horizontal = majorChanged;
+                vertical = minorChanged;
+              }
+            else
+              {
+                vertical = majorChanged;
+                horizontal = minorChanged;
+              }
+
+            p.preferenceChanged(this, horizontal, vertical);
+            majorChanged = false;
+            minorChanged = false;
+
+            Component c = getContainer();
+            if (c != null)
+              c.repaint();
+          }
+      }
+  }
+}

Reply via email to