001/* BoxView.java -- An composite view
002   Copyright (C) 2005, 2006  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text;
040
041import java.awt.Container;
042import java.awt.Graphics;
043import java.awt.Rectangle;
044import java.awt.Shape;
045
046import javax.swing.SizeRequirements;
047import javax.swing.event.DocumentEvent;
048
049/**
050 * An implementation of {@link CompositeView} that arranges its children in
051 * a box along one axis. This is comparable to how the <code>BoxLayout</code>
052 * works, but for <code>View</code> children.
053 *
054 * @author Roman Kennke (roman@kennke.org)
055 */
056public class BoxView
057  extends CompositeView
058{
059
060  /**
061   * The axis along which this <code>BoxView</code> is laid out.
062   */
063  private int myAxis;
064
065  /**
066   * Indicates if the layout is valid along X_AXIS or Y_AXIS.
067   */
068  private boolean[] layoutValid = new boolean[2];
069
070  /**
071   * Indicates if the requirements for an axis are valid.
072   */
073  private boolean[] requirementsValid = new boolean[2];
074
075  /**
076   * The spans along the X_AXIS and Y_AXIS.
077   */
078  private int[][] spans = new int[2][];
079
080  /**
081   * The offsets of the children along the X_AXIS and Y_AXIS.
082   */
083  private int[][] offsets = new int[2][];
084
085  /**
086   * The size requirements along the X_AXIS and Y_AXIS.
087   */
088  private SizeRequirements[] requirements = new SizeRequirements[2];
089
090  /**
091   * The current span along X_AXIS or Y_AXIS.
092   */
093  private int[] span = new int[2];
094
095  /**
096   * Creates a new <code>BoxView</code> for the given
097   * <code>Element</code> and axis. Valid values for the axis are
098   * {@link View#X_AXIS} and {@link View#Y_AXIS}.
099   *
100   * @param element the element that is rendered by this BoxView
101   * @param axis the axis along which the box is laid out
102   */
103  public BoxView(Element element, int axis)
104  {
105    super(element);
106    myAxis = axis;
107    layoutValid[0] = false;
108    layoutValid[1] = false;
109    requirementsValid[X_AXIS] = false;
110    requirementsValid[Y_AXIS] = false;
111    span[0] = 0;
112    span[1] = 0;
113    requirements[0] = new SizeRequirements();
114    requirements[1] = new SizeRequirements();
115
116    // Initialize the cache arrays.
117    spans[0] = new int[0];
118    spans[1] = new int[0];
119    offsets[0] = new int[0];
120    offsets[1] = new int[0];
121  }
122
123  /**
124   * Returns the axis along which this <code>BoxView</code> is laid out.
125   *
126   * @return the axis along which this <code>BoxView</code> is laid out
127   *
128   * @since 1.3
129   */
130  public int getAxis()
131  {
132    return myAxis;
133  }
134
135  /**
136   * Sets the axis along which this <code>BoxView</code> is laid out.
137   *
138   * Valid values for the axis are {@link View#X_AXIS} and
139   * {@link View#Y_AXIS}.
140   *
141   * @param axis the axis along which this <code>BoxView</code> is laid out
142   *
143   * @since 1.3
144   */
145  public void setAxis(int axis)
146  {
147    boolean changed = axis != myAxis;
148    myAxis = axis;
149    if (changed)
150      preferenceChanged(null, true, true);
151  }
152
153  /**
154   * Marks the layout along the specified axis as invalid. This is triggered
155   * automatically when any of the child view changes its preferences
156   * via {@link #preferenceChanged(View, boolean, boolean)}.
157   *
158   * The layout will be updated the next time when
159   * {@link #setSize(float, float)} is called, typically from within the
160   * {@link #paint(Graphics, Shape)} method.
161   *
162   * Valid values for the axis are {@link View#X_AXIS} and
163   * {@link View#Y_AXIS}.
164   *
165   * @param axis an <code>int</code> value
166   *
167   * @since 1.3
168   */
169  public void layoutChanged(int axis)
170  {
171    if (axis != X_AXIS && axis != Y_AXIS)
172      throw new IllegalArgumentException("Invalid axis parameter.");
173    layoutValid[axis] = false;
174  }
175
176  /**
177   * Returns <code>true</code> if the layout along the specified
178   * <code>axis</code> is valid, <code>false</code> otherwise.
179   *
180   * Valid values for the axis are {@link View#X_AXIS} and
181   * {@link View#Y_AXIS}.
182   *
183   * @param axis the axis
184   *
185   * @return <code>true</code> if the layout along the specified
186   *         <code>axis</code> is valid, <code>false</code> otherwise
187   *
188   * @since 1.4
189   */
190  protected boolean isLayoutValid(int axis)
191  {
192    if (axis != X_AXIS && axis != Y_AXIS)
193      throw new IllegalArgumentException("Invalid axis parameter.");
194    return layoutValid[axis];
195  }
196
197  /**
198   * Paints the child <code>View</code> at the specified <code>index</code>.
199   * This method modifies the actual values in <code>alloc</code> so make
200   * sure you have a copy of the original values if you need them.
201   *
202   * @param g the <code>Graphics</code> context to paint to
203   * @param alloc the allocated region for the child to paint into
204   * @param index the index of the child to be painted
205   *
206   * @see #childAllocation(int, Rectangle)
207   */
208  protected void paintChild(Graphics g, Rectangle alloc, int index)
209  {
210    View child = getView(index);
211    child.paint(g, alloc);
212  }
213
214  /**
215   * Replaces child views by some other child views. If there are no views to
216   * remove (<code>length == 0</code>), the result is a simple insert, if
217   * there are no children to add (<code>view == null</code>) the result
218   * is a simple removal.
219   *
220   * In addition this invalidates the layout and resizes the internal cache
221   * for the child allocations. The old children's cached allocations can
222   * still be accessed (although they are not guaranteed to be valid), and
223   * the new children will have an initial offset and span of 0.
224   *
225   * @param offset the start offset from where to remove children
226   * @param length the number of children to remove
227   * @param views the views that replace the removed children
228   */
229  public void replace(int offset, int length, View[] views)
230  {
231    // Actually perform the replace.
232    super.replace(offset, length, views);
233
234    // Resize and copy data for cache arrays.
235    int newItems = views != null ? views.length : 0;
236    int minor = 1 - myAxis;
237    offsets[myAxis] = replaceLayoutArray(offsets[myAxis], offset, newItems);
238    spans[myAxis] = replaceLayoutArray(spans[myAxis], offset, newItems);
239    layoutValid[myAxis] = false;
240    requirementsValid[myAxis] = false;
241    offsets[minor] = replaceLayoutArray(offsets[minor], offset, newItems);
242    spans[minor] = replaceLayoutArray(spans[minor], offset, newItems);
243    layoutValid[minor] = false;
244    requirementsValid[minor] = false;
245  }
246
247  /**
248   * Helper method. This updates the layout cache arrays in response
249   * to a call to {@link #replace(int, int, View[])}.
250   *
251   * @param oldArray the old array
252   *
253   * @return the replaced array
254   */
255  private int[] replaceLayoutArray(int[] oldArray, int offset, int newItems)
256
257  {
258    int num = getViewCount();
259    int[] newArray = new int[num];
260    System.arraycopy(oldArray, 0, newArray, 0, offset);
261    System.arraycopy(oldArray, offset, newArray, offset + newItems,
262                     num - newItems - offset);
263    return newArray;
264  }
265
266  /**
267   * A Rectangle instance to be reused in the paint() method below.
268   */
269  private final Rectangle tmpRect = new Rectangle();
270
271  private Rectangle clipRect = new Rectangle();
272
273  /**
274   * Renders the <code>Element</code> that is associated with this
275   * <code>View</code>.
276   *
277   * @param g the <code>Graphics</code> context to render to
278   * @param a the allocated region for the <code>Element</code>
279   */
280  public void paint(Graphics g, Shape a)
281  {
282    // Try to avoid allocation if possible (almost all cases).
283    Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
284
285    // This returns a cached instance.
286    alloc = getInsideAllocation(alloc);
287
288    int count = getViewCount();
289    for (int i = 0; i < count; i++)
290      {
291        View child = getView(i);
292        tmpRect.setBounds(alloc);
293        childAllocation(i, tmpRect);
294        if (g.hitClip(tmpRect.x, tmpRect.y, tmpRect.width, tmpRect.height))
295          paintChild(g, tmpRect, i);
296      }
297  }
298
299  /**
300   * Returns the preferred span of the content managed by this
301   * <code>View</code> along the specified <code>axis</code>.
302   *
303   * @param axis the axis
304   *
305   * @return the preferred span of this <code>View</code>.
306   */
307  public float getPreferredSpan(int axis)
308  {
309    updateRequirements(axis);
310    // Add margin.
311    float margin;
312    if (axis == X_AXIS)
313      margin = getLeftInset() + getRightInset();
314    else
315      margin = getTopInset() + getBottomInset();
316    return requirements[axis].preferred + margin;
317  }
318
319  /**
320   * Returns the maximum span of this view along the specified axis.
321   * This returns <code>Integer.MAX_VALUE</code> for the minor axis
322   * and the preferred span for the major axis.
323   *
324   * @param axis the axis
325   *
326   * @return the maximum span of this view along the specified axis
327   */
328  public float getMaximumSpan(int axis)
329  {
330    updateRequirements(axis);
331    // Add margin.
332    float margin;
333    if (axis == X_AXIS)
334      margin = getLeftInset() + getRightInset();
335    else
336      margin = getTopInset() + getBottomInset();
337    return requirements[axis].maximum + margin;
338  }
339
340  /**
341   * Returns the minimum span of this view along the specified axis.
342   * This calculates the minimum span using
343   * {@link #calculateMajorAxisRequirements} or
344   * {@link #calculateMinorAxisRequirements} (depending on the axis) and
345   * returns the resulting minimum span.
346   *
347   * @param axis the axis
348   *
349   * @return the minimum span of this view along the specified axis
350   */
351  public float getMinimumSpan(int axis)
352  {
353    updateRequirements(axis);
354    // Add margin.
355    float margin;
356    if (axis == X_AXIS)
357      margin = getLeftInset() + getRightInset();
358    else
359      margin = getTopInset() + getBottomInset();
360    return requirements[axis].minimum + margin;
361  }
362
363  /**
364   * Calculates size requirements for a baseline layout. This is not
365   * used by the BoxView itself, but by subclasses that wish to perform
366   * a baseline layout, like the FlowView's rows.
367   *
368   * @param axis the axis that is examined
369   * @param sr the <code>SizeRequirements</code> object to hold the result,
370   *        if <code>null</code>, a new one is created
371   *
372   * @return the size requirements for this <code>BoxView</code> along
373   *         the specified axis
374   */
375  protected SizeRequirements baselineRequirements(int axis,
376                                                  SizeRequirements sr)
377  {
378    // Create new instance if sr == null.
379    if (sr == null)
380      sr = new SizeRequirements();
381    sr.alignment = 0.5F;
382
383    // Calculate overall ascent and descent.
384    int totalAscentMin = 0;
385    int totalAscentPref = 0;
386    int totalAscentMax = 0;
387    int totalDescentMin = 0;
388    int totalDescentPref = 0;
389    int totalDescentMax = 0;
390
391    int count = getViewCount();
392    for (int i = 0; i < count; i++)
393      {
394        View v = getView(i);
395        float align = v.getAlignment(axis);
396        int span = (int) v.getPreferredSpan(axis);
397        int ascent = (int) (align * span);
398        int descent = span - ascent;
399
400        totalAscentPref = Math.max(ascent, totalAscentPref);
401        totalDescentPref = Math.max(descent, totalDescentPref);
402        if (v.getResizeWeight(axis) > 0)
403          {
404            // If the view is resizable, then use the min and max size
405            // of the view.
406            span = (int) v.getMinimumSpan(axis);
407            ascent = (int) (align * span);
408            descent = span - ascent;
409            totalAscentMin = Math.max(ascent, totalAscentMin);
410            totalDescentMin = Math.max(descent, totalDescentMin);
411
412            span = (int) v.getMaximumSpan(axis);
413            ascent = (int) (align * span);
414            descent = span - ascent;
415            totalAscentMax = Math.max(ascent, totalAscentMax);
416            totalDescentMax = Math.max(descent, totalDescentMax);
417          }
418        else
419          {
420            // If the view is not resizable, use the preferred span.
421            totalAscentMin = Math.max(ascent, totalAscentMin);
422            totalDescentMin = Math.max(descent, totalDescentMin);
423            totalAscentMax = Math.max(ascent, totalAscentMax);
424            totalDescentMax = Math.max(descent, totalDescentMax);
425          }
426      }
427
428    // Preferred overall span is the sum of the preferred ascent and descent.
429    // With overflow check.
430    sr.preferred = (int) Math.min((long) totalAscentPref
431                                  + (long) totalDescentPref,
432                                  Integer.MAX_VALUE);
433
434    // Align along the baseline.
435    if (sr.preferred > 0)
436      sr.alignment = (float) totalAscentPref / sr.preferred;
437
438    if (sr.alignment == 0)
439      {
440        // Nothing above the baseline, use the descent.
441        sr.minimum = totalDescentMin;
442        sr.maximum = totalDescentMax;
443      }
444    else if (sr.alignment == 1.0F)
445      {
446        // Nothing below the baseline, use the descent.
447        sr.minimum = totalAscentMin;
448        sr.maximum = totalAscentMax;
449      }
450    else
451      {
452        sr.minimum = Math.max((int) (totalAscentMin / sr.alignment),
453                              (int) (totalDescentMin / (1.0F - sr.alignment)));
454        sr.maximum = Math.min((int) (totalAscentMax / sr.alignment),
455                              (int) (totalDescentMax / (1.0F - sr.alignment)));
456      }
457    return sr;
458  }
459
460  /**
461   * Calculates the baseline layout of the children of this
462   * <code>BoxView</code> along the specified axis.
463   *
464   * This is not used by the BoxView itself, but by subclasses that wish to
465   * perform a baseline layout, like the FlowView's rows.
466   *
467   * @param span the target span
468   * @param axis the axis that is examined
469   * @param offsets an empty array, filled with the offsets of the children
470   * @param spans an empty array, filled with the spans of the children
471   */
472  protected void baselineLayout(int span, int axis, int[] offsets,
473                                int[] spans)
474  {
475    int totalAscent = (int) (span * getAlignment(axis));
476    int totalDescent = span - totalAscent;
477
478    int count = getViewCount();
479    for (int i = 0; i < count; i++)
480      {
481        View v = getView(i);
482        float align = v.getAlignment(axis);
483        int viewSpan;
484        if (v.getResizeWeight(axis) > 0)
485          {
486            // If possible, then resize for best fit.
487            int min = (int) v.getMinimumSpan(axis);
488            int max = (int) v.getMaximumSpan(axis);
489            if (align == 0.0F)
490              viewSpan = Math.max(Math.min(max, totalDescent), min);
491            else if (align == 1.0F)
492              viewSpan = Math.max(Math.min(max, totalAscent), min);
493            else
494              {
495                int fit = (int) Math.min(totalAscent / align,
496                                         totalDescent / (1.0F - align));
497                viewSpan = Math.max(Math.min(max, fit), min);
498              }
499          }
500        else
501          viewSpan = (int) v.getPreferredSpan(axis);
502        offsets[i] = totalAscent - (int) (viewSpan * align);
503        spans[i] = viewSpan;
504      }
505  }
506
507  /**
508   * Calculates the size requirements of this <code>BoxView</code> along
509   * its major axis, that is the axis specified in the constructor.
510   *
511   * @param axis the axis that is examined
512   * @param sr the <code>SizeRequirements</code> object to hold the result,
513   *        if <code>null</code>, a new one is created
514   *
515   * @return the size requirements for this <code>BoxView</code> along
516   *         the specified axis
517   */
518  protected SizeRequirements calculateMajorAxisRequirements(int axis,
519                                                           SizeRequirements sr)
520  {
521    SizeRequirements res = sr;
522    if (res == null)
523      res = new SizeRequirements();
524
525    float min = 0;
526    float pref = 0;
527    float max = 0;
528
529    int n = getViewCount();
530    for (int i = 0; i < n; i++)
531      {
532        View child = getView(i);
533        min += child.getMinimumSpan(axis);
534        pref += child.getPreferredSpan(axis);
535        max += child.getMaximumSpan(axis);
536      }
537
538    res.minimum = (int) min;
539    res.preferred = (int) pref;
540    res.maximum = (int) max;
541    res.alignment = 0.5F;
542
543    return res;
544  }
545
546  /**
547   * Calculates the size requirements of this <code>BoxView</code> along
548   * its minor axis, that is the axis opposite to the axis specified in the
549   * constructor.
550   *
551   * @param axis the axis that is examined
552   * @param sr the <code>SizeRequirements</code> object to hold the result,
553   *        if <code>null</code>, a new one is created
554   *
555   * @return the size requirements for this <code>BoxView</code> along
556   *         the specified axis
557   */
558  protected SizeRequirements calculateMinorAxisRequirements(int axis,
559                                                            SizeRequirements sr)
560  {
561    SizeRequirements res = sr;
562    if (res == null)
563      res = new SizeRequirements();
564
565    res.minimum = 0;
566    res.preferred = 0;
567    res.maximum = Integer.MAX_VALUE;
568    res.alignment = 0.5F;
569    int n = getViewCount();
570    for (int i = 0; i < n; i++)
571      {
572        View child = getView(i);
573        res.minimum = Math.max((int) child.getMinimumSpan(axis), res.minimum);
574        res.preferred = Math.max((int) child.getPreferredSpan(axis),
575                                 res.preferred);
576        res.maximum = Math.max((int) child.getMaximumSpan(axis), res.maximum);
577      }
578
579    return res;
580  }
581
582
583  /**
584   * Returns <code>true</code> if the specified point lies before the
585   * given <code>Rectangle</code>, <code>false</code> otherwise.
586   *
587   * &quot;Before&quot; is typically defined as being to the left or above.
588   *
589   * @param x the X coordinate of the point
590   * @param y the Y coordinate of the point
591   * @param r the rectangle to test the point against
592   *
593   * @return <code>true</code> if the specified point lies before the
594   *         given <code>Rectangle</code>, <code>false</code> otherwise
595   */
596  protected boolean isBefore(int x, int y, Rectangle r)
597  {
598    boolean result = false;
599
600    if (myAxis == X_AXIS)
601      result = x < r.x;
602    else
603      result = y < r.y;
604
605    return result;
606  }
607
608  /**
609   * Returns <code>true</code> if the specified point lies after the
610   * given <code>Rectangle</code>, <code>false</code> otherwise.
611   *
612   * &quot;After&quot; is typically defined as being to the right or below.
613   *
614   * @param x the X coordinate of the point
615   * @param y the Y coordinate of the point
616   * @param r the rectangle to test the point against
617   *
618   * @return <code>true</code> if the specified point lies after the
619   *         given <code>Rectangle</code>, <code>false</code> otherwise
620   */
621  protected boolean isAfter(int x, int y, Rectangle r)
622  {
623    boolean result = false;
624
625    if (myAxis == X_AXIS)
626      result = x > r.x + r.width;
627    else
628      result = y > r.y + r.height;
629
630    return result;
631  }
632
633  /**
634   * Returns the child <code>View</code> at the specified location.
635   *
636   * @param x the X coordinate
637   * @param y the Y coordinate
638   * @param r the inner allocation of this <code>BoxView</code> on entry,
639   *        the allocation of the found child on exit
640   *
641   * @return the child <code>View</code> at the specified location
642   */
643  protected View getViewAtPoint(int x, int y, Rectangle r)
644  {
645    View result = null;
646    int count = getViewCount();
647    if (myAxis == X_AXIS)
648      {
649        // Border case. Requested point is left from the box.
650        if (x < r.x + offsets[X_AXIS][0])
651          {
652            childAllocation(0, r);
653            result = getView(0);
654          }
655        else
656          {
657            // Search views inside box.
658            for (int i = 0; i < count && result == null; i++)
659              {
660                if (x < r.x + offsets[X_AXIS][i])
661                  {
662                    childAllocation(i - 1, r);
663                    result = getView(i - 1);
664                  }
665              }
666          }
667      }
668    else // Same algorithm for Y_AXIS.
669      {
670        // Border case. Requested point is above the box.
671        if (y < r.y + offsets[Y_AXIS][0])
672          {
673            childAllocation(0, r);
674            result = getView(0);
675          }
676        else
677          {
678            // Search views inside box.
679            for (int i = 0; i < count && result == null; i++)
680              {
681                if (y < r.y + offsets[Y_AXIS][i])
682                  {
683                    childAllocation(i - 1, r);
684                    result = getView(i - 1);
685                  }
686              }
687          }
688      }
689    // Not found, other border case: point is right from or below the box.
690    if (result == null)
691      {
692        childAllocation(count - 1, r);
693        result = getView(count - 1);
694      }
695    return result;
696  }
697
698  /**
699   * Computes the allocation for a child <code>View</code>. The parameter
700   * <code>a</code> stores the allocation of this <code>CompositeView</code>
701   * and is then adjusted to hold the allocation of the child view.
702   *
703   * @param index
704   *          the index of the child <code>View</code>
705   * @param a
706   *          the allocation of this <code>CompositeView</code> before the
707   *          call, the allocation of the child on exit
708   */
709  protected void childAllocation(int index, Rectangle a)
710  {
711    a.x += offsets[X_AXIS][index];
712    a.y += offsets[Y_AXIS][index];
713    a.width = spans[X_AXIS][index];
714    a.height = spans[Y_AXIS][index];
715  }
716
717  /**
718   * Lays out the children of this <code>BoxView</code> with the specified
719   * bounds.
720   *
721   * @param width the width of the allocated region for the children (that
722   *        is the inner allocation of this <code>BoxView</code>
723   * @param height the height of the allocated region for the children (that
724   *        is the inner allocation of this <code>BoxView</code>
725   */
726  protected void layout(int width, int height)
727  {
728    layoutAxis(X_AXIS, width);
729    layoutAxis(Y_AXIS, height);
730  }
731
732  private void layoutAxis(int axis, int s)
733  {
734    if (span[axis] != s)
735      layoutValid[axis] = false;
736    if (! layoutValid[axis])
737      {
738        span[axis] = s;
739        updateRequirements(axis);
740        if (axis == myAxis)
741          layoutMajorAxis(span[axis], axis, offsets[axis], spans[axis]);
742        else
743          layoutMinorAxis(span[axis], axis, offsets[axis], spans[axis]);
744        layoutValid[axis] = true;
745
746        // Push out child layout.
747        int viewCount = getViewCount();
748        for (int i = 0; i < viewCount; i++)
749          {
750            View v = getView(i);
751            v.setSize(spans[X_AXIS][i], spans[Y_AXIS][i]);
752          }
753      }
754  }
755
756  /**
757   * Performs the layout along the major axis of a <code>BoxView</code>.
758   *
759   * @param targetSpan the (inner) span of the <code>BoxView</code> in which
760   *        to layout the children
761   * @param axis the axis along which the layout is performed
762   * @param offsets the array that holds the offsets of the children on exit
763   * @param spans the array that holds the spans of the children on exit
764   */
765  protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
766                                 int[] spans)
767  {
768    // Set the spans to the preferred sizes. Determine the space
769    // that we have to adjust the sizes afterwards.
770    long sumPref = 0;
771    int n = getViewCount();
772    for (int i = 0; i < n; i++)
773      {
774        View child = getView(i);
775        spans[i] = (int) child.getPreferredSpan(axis);
776        sumPref += spans[i];
777      }
778
779    // Try to adjust the spans so that we fill the targetSpan.
780    long diff = targetSpan - sumPref;
781    float factor = 0.0F;
782    int[] diffs = null;
783    if (diff != 0)
784      {
785        long total = 0;
786        diffs = new int[n];
787        for (int i = 0; i < n; i++)
788          {
789            View child = getView(i);
790            int span;
791            if (diff < 0)
792              {
793                span = (int) child.getMinimumSpan(axis);
794                diffs[i] = spans[i] - span;
795              }
796            else
797              {
798                span = (int) child.getMaximumSpan(axis);
799                diffs[i] = span - spans[i];
800              }
801            total += span;
802          }
803
804        float maxAdjust = Math.abs(total - sumPref);
805        factor = diff / maxAdjust;
806        factor = Math.min(factor, 1.0F);
807        factor = Math.max(factor, -1.0F);
808      }
809
810    // Actually perform adjustments.
811    int totalOffs = 0;
812    for (int i = 0; i < n; i++)
813      {
814        offsets[i] = totalOffs;
815        if (diff != 0)
816          {
817            float adjust = factor * diffs[i];
818            spans[i] += Math.round(adjust);
819          }
820        // Avoid overflow here.
821        totalOffs = (int) Math.min((long) totalOffs + (long) spans[i],
822                                    Integer.MAX_VALUE);
823      }
824  }
825
826  /**
827   * Performs the layout along the minor axis of a <code>BoxView</code>.
828   *
829   * @param targetSpan the (inner) span of the <code>BoxView</code> in which
830   *        to layout the children
831   * @param axis the axis along which the layout is performed
832   * @param offsets the array that holds the offsets of the children on exit
833   * @param spans the array that holds the spans of the children on exit
834   */
835  protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets,
836                                 int[] spans)
837  {
838    int count = getViewCount();
839    for (int i = 0; i < count; i++)
840      {
841        View child = getView(i);
842        int max = (int) child.getMaximumSpan(axis);
843        if (max < targetSpan)
844          {
845            // Align child when it can't be made as wide as the target span.
846            float align = child.getAlignment(axis);
847            offsets[i] = (int) ((targetSpan - max) * align);
848            spans[i] = max;
849          }
850        else
851          {
852            // Expand child to target width if possible.
853            int min = (int) child.getMinimumSpan(axis);
854            offsets[i] = 0;
855            spans[i] = Math.max(min, targetSpan);
856          }
857      }
858  }
859
860  /**
861   * Returns <code>true</code> if the cached allocations for the children
862   * are still valid, <code>false</code> otherwise.
863   *
864   * @return <code>true</code> if the cached allocations for the children
865   *         are still valid, <code>false</code> otherwise
866   */
867  protected boolean isAllocationValid()
868  {
869    return isLayoutValid(X_AXIS) && isLayoutValid(Y_AXIS);
870  }
871
872  /**
873   * Return the current width of the box. This is the last allocated width.
874   *
875   * @return the current width of the box
876   */
877  public int getWidth()
878  {
879    // The RI returns the following here, however, I'd think that is a bug.
880    // return span[X_AXIS] + getLeftInset() - getRightInset();
881    return span[X_AXIS] + getLeftInset() + getRightInset();
882  }
883
884  /**
885   * Return the current height of the box. This is the last allocated height.
886   *
887   * @return the current height of the box
888   */
889  public int getHeight()
890  {
891    // The RI returns the following here, however, I'd think that is a bug.
892    // return span[Y_AXIS] + getTopInset() - getBottomInset();
893    return span[Y_AXIS] + getTopInset() + getBottomInset();
894  }
895
896  /**
897   * Sets the size of the view. If the actual size has changed, the layout
898   * is updated accordingly.
899   *
900   * @param width the new width
901   * @param height the new height
902   */
903  public void setSize(float width, float height)
904  {
905    layout((int) (width - getLeftInset() - getRightInset()),
906           (int) (height - getTopInset() - getBottomInset()));
907  }
908
909  /**
910   * Returns the span for the child view with the given index for the specified
911   * axis.
912   *
913   * @param axis the axis to examine, either <code>X_AXIS</code> or
914   *        <code>Y_AXIS</code>
915   * @param childIndex the index of the child for for which to return the span
916   *
917   * @return the span for the child view with the given index for the specified
918   *         axis
919   */
920  protected int getSpan(int axis, int childIndex)
921  {
922    if (axis != X_AXIS && axis != Y_AXIS)
923      throw new IllegalArgumentException("Illegal axis argument");
924    return spans[axis][childIndex];
925  }
926
927  /**
928   * Returns the offset for the child view with the given index for the
929   * specified axis.
930   *
931   * @param axis the axis to examine, either <code>X_AXIS</code> or
932   *        <code>Y_AXIS</code>
933   * @param childIndex the index of the child for for which to return the span
934   *
935   * @return the offset for the child view with the given index for the
936   *         specified axis
937   */
938  protected int getOffset(int axis, int childIndex)
939  {
940    if (axis != X_AXIS && axis != Y_AXIS)
941      throw new IllegalArgumentException("Illegal axis argument");
942    return offsets[axis][childIndex];
943  }
944
945  /**
946   * Returns the alignment for this box view for the specified axis. The
947   * axis that is tiled (the major axis) will be requested to be aligned
948   * centered (0.5F). The minor axis alignment depends on the child view's
949   * total alignment.
950   *
951   * @param axis the axis which is examined
952   *
953   * @return the alignment for this box view for the specified axis
954   */
955  public float getAlignment(int axis)
956  {
957     updateRequirements(axis);
958     return requirements[axis].alignment;
959  }
960
961  /**
962   * Called by a child View when its preferred span has changed.
963   *
964   * @param width indicates that the preferred width of the child changed.
965   * @param height indicates that the preferred height of the child changed.
966   * @param child the child View.
967   */
968  public void preferenceChanged(View child, boolean width, boolean height)
969  {
970    if (width)
971      {
972        layoutValid[X_AXIS] = false;
973        requirementsValid[X_AXIS] = false;
974      }
975    if (height)
976      {
977        layoutValid[Y_AXIS] = false;
978        requirementsValid[Y_AXIS] = false;
979      }
980    super.preferenceChanged(child, width, height);
981  }
982
983  /**
984   * Maps the document model position <code>pos</code> to a Shape
985   * in the view coordinate space.  This method overrides CompositeView's
986   * method to make sure the children are allocated properly before
987   * calling the super's behaviour.
988   */
989  public Shape modelToView(int pos, Shape a, Position.Bias bias)
990      throws BadLocationException
991  {
992    // Make sure everything is allocated properly and then call super
993    if (! isAllocationValid())
994      {
995        Rectangle bounds = a.getBounds();
996        setSize(bounds.width, bounds.height);
997      }
998    return super.modelToView(pos, a, bias);
999  }
1000
1001  /**
1002   * Returns the resize weight of this view. A value of <code>0</code> or less
1003   * means this view is not resizeable. Positive values make the view
1004   * resizeable. This implementation returns <code>0</code> for the major
1005   * axis and <code>1</code> for the minor axis of this box view.
1006   *
1007   * @param axis the axis
1008   *
1009   * @return the resizability of this view along the specified axis
1010   *
1011   * @throws IllegalArgumentException if <code>axis</code> is invalid
1012   */
1013  public int getResizeWeight(int axis)
1014  {
1015    if (axis != X_AXIS && axis != Y_AXIS)
1016      throw new IllegalArgumentException("Illegal axis argument");
1017    updateRequirements(axis);
1018    int weight = 0;
1019    if ((requirements[axis].preferred != requirements[axis].minimum)
1020        || (requirements[axis].preferred != requirements[axis].maximum))
1021      weight = 1;
1022    return weight;
1023  }
1024
1025  /**
1026   * Returns the child allocation for the child view with the specified
1027   * <code>index</code>. If the layout is invalid, this returns
1028   * <code>null</code>.
1029   *
1030   * @param index the child view index
1031   * @param a the allocation to this view
1032   *
1033   * @return the child allocation for the child view with the specified
1034   *         <code>index</code> or <code>null</code> if the layout is invalid
1035   *         or <code>a</code> is null
1036   */
1037  public Shape getChildAllocation(int index, Shape a)
1038  {
1039    Shape ret = null;
1040    if (isAllocationValid() && a != null)
1041      ret = super.getChildAllocation(index, a);
1042    return ret;
1043  }
1044
1045  protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e,
1046                               Shape a, ViewFactory vf)
1047  {
1048    boolean wasValid = isLayoutValid(myAxis);
1049    super.forwardUpdate(ec, e, a, vf);
1050    // Trigger repaint when one of the children changed the major axis.
1051    if (wasValid && ! isLayoutValid(myAxis))
1052      {
1053        Container c = getContainer();
1054        if (a != null && c != null)
1055          {
1056            int pos = e.getOffset();
1057            int index = getViewIndexAtPosition(pos);
1058            Rectangle r = getInsideAllocation(a);
1059            if (myAxis == X_AXIS)
1060              {
1061                r.x += offsets[myAxis][index];
1062                r.width -= offsets[myAxis][index];
1063              }
1064            else
1065              {
1066                r.y += offsets[myAxis][index];
1067                r.height -= offsets[myAxis][index];
1068              }
1069            c.repaint(r.x, r.y, r.width, r.height);
1070          }
1071      }
1072  }
1073
1074  public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
1075  {
1076    if (! isAllocationValid())
1077      {
1078        Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
1079        setSize(r.width, r.height);
1080      }
1081    return super.viewToModel(x, y, a, bias);
1082  }
1083
1084  protected boolean flipEastAndWestAtEnds(int position, Position.Bias bias)
1085  {
1086    // FIXME: What to do here?
1087    return super.flipEastAndWestAtEnds(position, bias);
1088  }
1089
1090  /**
1091   * Updates the view's cached requirements along the specified axis if
1092   * necessary. The requirements are only updated if the layout for the
1093   * specified axis is marked as invalid.
1094   *
1095   * @param axis the axis
1096   */
1097  private void updateRequirements(int axis)
1098  {
1099    if (axis != Y_AXIS && axis != X_AXIS)
1100      throw new IllegalArgumentException("Illegal axis: " + axis);
1101    if (! requirementsValid[axis])
1102      {
1103        if (axis == myAxis)
1104          requirements[axis] = calculateMajorAxisRequirements(axis,
1105                                                           requirements[axis]);
1106        else
1107          requirements[axis] = calculateMinorAxisRequirements(axis,
1108                                                           requirements[axis]);
1109        requirementsValid[axis] = true;
1110      }
1111  }
1112}