001    /* BoxView.java -- An composite view
002       Copyright (C) 2005, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.text;
040    
041    import java.awt.Container;
042    import java.awt.Graphics;
043    import java.awt.Rectangle;
044    import java.awt.Shape;
045    
046    import javax.swing.SizeRequirements;
047    import 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     */
056    public 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    }