1/*
2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package javax.swing.text;
26
27import java.io.PrintStream;
28import java.util.Vector;
29import java.awt.*;
30import javax.swing.event.DocumentEvent;
31import javax.swing.SizeRequirements;
32
33/**
34 * A view that arranges its children into a box shape by tiling
35 * its children along an axis.  The box is somewhat like that
36 * found in TeX where there is alignment of the
37 * children, flexibility of the children is considered, etc.
38 * This is a building block that might be useful to represent
39 * things like a collection of lines, paragraphs,
40 * lists, columns, pages, etc.  The axis along which the children are tiled is
41 * considered the major axis.  The orthogonal axis is the minor axis.
42 * <p>
43 * Layout for each axis is handled separately by the methods
44 * <code>layoutMajorAxis</code> and <code>layoutMinorAxis</code>.
45 * Subclasses can change the layout algorithm by
46 * reimplementing these methods.    These methods will be called
47 * as necessary depending upon whether or not there is cached
48 * layout information and the cache is considered
49 * valid.  These methods are typically called if the given size
50 * along the axis changes, or if <code>layoutChanged</code> is
51 * called to force an updated layout.  The <code>layoutChanged</code>
52 * method invalidates cached layout information, if there is any.
53 * The requirements published to the parent view are calculated by
54 * the methods <code>calculateMajorAxisRequirements</code>
55 * and  <code>calculateMinorAxisRequirements</code>.
56 * If the layout algorithm is changed, these methods will
57 * likely need to be reimplemented.
58 *
59 * @author  Timothy Prinzing
60 */
61public class BoxView extends CompositeView {
62
63    /**
64     * Constructs a <code>BoxView</code>.
65     *
66     * @param elem the element this view is responsible for
67     * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
68     */
69    public BoxView(Element elem, int axis) {
70        super(elem);
71        tempRect = new Rectangle();
72        this.majorAxis = axis;
73
74        majorOffsets = new int[0];
75        majorSpans = new int[0];
76        majorReqValid = false;
77        majorAllocValid = false;
78        minorOffsets = new int[0];
79        minorSpans = new int[0];
80        minorReqValid = false;
81        minorAllocValid = false;
82    }
83
84    /**
85     * Fetches the tile axis property.  This is the axis along which
86     * the child views are tiled.
87     *
88     * @return the major axis of the box, either
89     *  <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
90     *
91     * @since 1.3
92     */
93    public int getAxis() {
94        return majorAxis;
95    }
96
97    /**
98     * Sets the tile axis property.  This is the axis along which
99     * the child views are tiled.
100     *
101     * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
102     *
103     * @since 1.3
104     */
105    public void setAxis(int axis) {
106        boolean axisChanged = (axis != majorAxis);
107        majorAxis = axis;
108        if (axisChanged) {
109            preferenceChanged(null, true, true);
110        }
111    }
112
113    /**
114     * Invalidates the layout along an axis.  This happens
115     * automatically if the preferences have changed for
116     * any of the child views.  In some cases the layout
117     * may need to be recalculated when the preferences
118     * have not changed.  The layout can be marked as
119     * invalid by calling this method.  The layout will
120     * be updated the next time the <code>setSize</code> method
121     * is called on this view (typically in paint).
122     *
123     * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
124     *
125     * @since 1.3
126     */
127    public void layoutChanged(int axis) {
128        if (axis == majorAxis) {
129            majorAllocValid = false;
130        } else {
131            minorAllocValid = false;
132        }
133    }
134
135    /**
136     * Determines if the layout is valid along the given axis.
137     * @return if the layout is valid along the given axis
138     *
139     * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
140     *
141     * @since 1.4
142     */
143    protected boolean isLayoutValid(int axis) {
144        if (axis == majorAxis) {
145            return majorAllocValid;
146        } else {
147            return minorAllocValid;
148        }
149    }
150
151    /**
152     * Paints a child.  By default
153     * that is all it does, but a subclass can use this to paint
154     * things relative to the child.
155     *
156     * @param g the graphics context
157     * @param alloc the allocated region to paint into
158     * @param index the child index, &gt;= 0 &amp;&amp; &lt; getViewCount()
159     */
160    protected void paintChild(Graphics g, Rectangle alloc, int index) {
161        View child = getView(index);
162        child.paint(g, alloc);
163    }
164
165    // --- View methods ---------------------------------------------
166
167    /**
168     * Invalidates the layout and resizes the cache of
169     * requests/allocations.  The child allocations can still
170     * be accessed for the old layout, but the new children
171     * will have an offset and span of 0.
172     *
173     * @param index the starting index into the child views to insert
174     *   the new views; this should be a value &gt;= 0 and &lt;= getViewCount
175     * @param length the number of existing child views to remove;
176     *   This should be a value &gt;= 0 and &lt;= (getViewCount() - offset)
177     * @param elems the child views to add; this value can be
178     *   <code>null</code>to indicate no children are being added
179     *   (useful to remove)
180     */
181    public void replace(int index, int length, View[] elems) {
182        super.replace(index, length, elems);
183
184        // invalidate cache
185        int nInserted = (elems != null) ? elems.length : 0;
186        majorOffsets = updateLayoutArray(majorOffsets, index, nInserted);
187        majorSpans = updateLayoutArray(majorSpans, index, nInserted);
188        majorReqValid = false;
189        majorAllocValid = false;
190        minorOffsets = updateLayoutArray(minorOffsets, index, nInserted);
191        minorSpans = updateLayoutArray(minorSpans, index, nInserted);
192        minorReqValid = false;
193        minorAllocValid = false;
194    }
195
196    /**
197     * Resizes the given layout array to match the new number of
198     * child views.  The current number of child views are used to
199     * produce the new array.  The contents of the old array are
200     * inserted into the new array at the appropriate places so that
201     * the old layout information is transferred to the new array.
202     *
203     * @param oldArray the original layout array
204     * @param offset location where new views will be inserted
205     * @param nInserted the number of child views being inserted;
206     *          therefore the number of blank spaces to leave in the
207     *          new array at location <code>offset</code>
208     * @return the new layout array
209     */
210    int[] updateLayoutArray(int[] oldArray, int offset, int nInserted) {
211        int n = getViewCount();
212        int[] newArray = new int[n];
213
214        System.arraycopy(oldArray, 0, newArray, 0, offset);
215        System.arraycopy(oldArray, offset,
216                         newArray, offset + nInserted, n - nInserted - offset);
217        return newArray;
218    }
219
220    /**
221     * Forwards the given <code>DocumentEvent</code> to the child views
222     * that need to be notified of the change to the model.
223     * If a child changed its requirements and the allocation
224     * was valid prior to forwarding the portion of the box
225     * from the starting child to the end of the box will
226     * be repainted.
227     *
228     * @param ec changes to the element this view is responsible
229     *  for (may be <code>null</code> if there were no changes)
230     * @param e the change information from the associated document
231     * @param a the current allocation of the view
232     * @param f the factory to use to rebuild if the view has children
233     * @see #insertUpdate
234     * @see #removeUpdate
235     * @see #changedUpdate
236     * @since 1.3
237     */
238    protected void forwardUpdate(DocumentEvent.ElementChange ec,
239                                 DocumentEvent e, Shape a, ViewFactory f) {
240        boolean wasValid = isLayoutValid(majorAxis);
241        super.forwardUpdate(ec, e, a, f);
242
243        // determine if a repaint is needed
244        if (wasValid && (! isLayoutValid(majorAxis))) {
245            // Repaint is needed because one of the tiled children
246            // have changed their span along the major axis.  If there
247            // is a hosting component and an allocated shape we repaint.
248            Component c = getContainer();
249            if ((a != null) && (c != null)) {
250                int pos = e.getOffset();
251                int index = getViewIndexAtPosition(pos);
252                Rectangle alloc = getInsideAllocation(a);
253                if (majorAxis == X_AXIS) {
254                    alloc.x += majorOffsets[index];
255                    alloc.width -= majorOffsets[index];
256                } else {
257                    alloc.y += minorOffsets[index];
258                    alloc.height -= minorOffsets[index];
259                }
260                c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
261            }
262        }
263    }
264
265    /**
266     * This is called by a child to indicate its
267     * preferred span has changed.  This is implemented to
268     * throw away cached layout information so that new
269     * calculations will be done the next time the children
270     * need an allocation.
271     *
272     * @param child the child view
273     * @param width true if the width preference should change
274     * @param height true if the height preference should change
275     */
276    public void preferenceChanged(View child, boolean width, boolean height) {
277        boolean majorChanged = (majorAxis == X_AXIS) ? width : height;
278        boolean minorChanged = (majorAxis == X_AXIS) ? height : width;
279        if (majorChanged) {
280            majorReqValid = false;
281            majorAllocValid = false;
282        }
283        if (minorChanged) {
284            minorReqValid = false;
285            minorAllocValid = false;
286        }
287        super.preferenceChanged(child, width, height);
288    }
289
290    /**
291     * Gets the resize weight.  A value of 0 or less is not resizable.
292     *
293     * @param axis may be either <code>View.X_AXIS</code> or
294     *          <code>View.Y_AXIS</code>
295     * @return the weight
296     * @exception IllegalArgumentException for an invalid axis
297     */
298    public int getResizeWeight(int axis) {
299        checkRequests(axis);
300        if (axis == majorAxis) {
301            if ((majorRequest.preferred != majorRequest.minimum) ||
302                (majorRequest.preferred != majorRequest.maximum)) {
303                return 1;
304            }
305        } else {
306            if ((minorRequest.preferred != minorRequest.minimum) ||
307                (minorRequest.preferred != minorRequest.maximum)) {
308                return 1;
309            }
310        }
311        return 0;
312    }
313
314    /**
315     * Sets the size of the view along an axis.  This should cause
316     * layout of the view along the given axis.
317     *
318     * @param axis may be either <code>View.X_AXIS</code> or
319     *          <code>View.Y_AXIS</code>
320     * @param span the span to layout to >= 0
321     */
322    void setSpanOnAxis(int axis, float span) {
323        if (axis == majorAxis) {
324            if (majorSpan != (int) span) {
325                majorAllocValid = false;
326            }
327            if (! majorAllocValid) {
328                // layout the major axis
329                majorSpan = (int) span;
330                checkRequests(majorAxis);
331                layoutMajorAxis(majorSpan, axis, majorOffsets, majorSpans);
332                majorAllocValid = true;
333
334                // flush changes to the children
335                updateChildSizes();
336            }
337        } else {
338            if (((int) span) != minorSpan) {
339                minorAllocValid = false;
340            }
341            if (! minorAllocValid) {
342                // layout the minor axis
343                minorSpan = (int) span;
344                checkRequests(axis);
345                layoutMinorAxis(minorSpan, axis, minorOffsets, minorSpans);
346                minorAllocValid = true;
347
348                // flush changes to the children
349                updateChildSizes();
350            }
351        }
352    }
353
354    /**
355     * Propagates the current allocations to the child views.
356     */
357    void updateChildSizes() {
358        int n = getViewCount();
359        if (majorAxis == X_AXIS) {
360            for (int i = 0; i < n; i++) {
361                View v = getView(i);
362                v.setSize((float) majorSpans[i], (float) minorSpans[i]);
363            }
364        } else {
365            for (int i = 0; i < n; i++) {
366                View v = getView(i);
367                v.setSize((float) minorSpans[i], (float) majorSpans[i]);
368            }
369        }
370    }
371
372    /**
373     * Returns the size of the view along an axis.  This is implemented
374     * to return zero.
375     *
376     * @param axis may be either <code>View.X_AXIS</code> or
377     *          <code>View.Y_AXIS</code>
378     * @return the current span of the view along the given axis, >= 0
379     */
380    float getSpanOnAxis(int axis) {
381        if (axis == majorAxis) {
382            return majorSpan;
383        } else {
384            return minorSpan;
385        }
386    }
387
388    /**
389     * Sets the size of the view.  This should cause
390     * layout of the view if the view caches any layout
391     * information.  This is implemented to call the
392     * layout method with the sizes inside of the insets.
393     *
394     * @param width the width &gt;= 0
395     * @param height the height &gt;= 0
396     */
397    public void setSize(float width, float height) {
398        layout(Math.max(0, (int)(width - getLeftInset() - getRightInset())),
399               Math.max(0, (int)(height - getTopInset() - getBottomInset())));
400    }
401
402    /**
403     * Renders the <code>BoxView</code> using the given
404     * rendering surface and area
405     * on that surface.  Only the children that intersect
406     * the clip bounds of the given <code>Graphics</code>
407     * will be rendered.
408     *
409     * @param g the rendering surface to use
410     * @param allocation the allocated region to render into
411     * @see View#paint
412     */
413    public void paint(Graphics g, Shape allocation) {
414        Rectangle alloc = (allocation instanceof Rectangle) ?
415                           (Rectangle)allocation : allocation.getBounds();
416        int n = getViewCount();
417        int x = alloc.x + getLeftInset();
418        int y = alloc.y + getTopInset();
419        Rectangle clip = g.getClipBounds();
420        for (int i = 0; i < n; i++) {
421            tempRect.x = x + getOffset(X_AXIS, i);
422            tempRect.y = y + getOffset(Y_AXIS, i);
423            tempRect.width = getSpan(X_AXIS, i);
424            tempRect.height = getSpan(Y_AXIS, i);
425            int trx0 = tempRect.x, trx1 = trx0 + tempRect.width;
426            int try0 = tempRect.y, try1 = try0 + tempRect.height;
427            int crx0 = clip.x, crx1 = crx0 + clip.width;
428            int cry0 = clip.y, cry1 = cry0 + clip.height;
429            // We should paint views that intersect with clipping region
430            // even if the intersection has no inside points (is a line).
431            // This is needed for supporting views that have zero width, like
432            // views that contain only combining marks.
433            if ((trx1 >= crx0) && (try1 >= cry0) && (crx1 >= trx0) && (cry1 >= try0)) {
434                paintChild(g, tempRect, i);
435            }
436        }
437    }
438
439    /**
440     * Fetches the allocation for the given child view.
441     * This enables finding out where various views
442     * are located.  This is implemented to return
443     * <code>null</code> if the layout is invalid,
444     * otherwise the superclass behavior is executed.
445     *
446     * @param index the index of the child, &gt;= 0 &amp;&amp; &gt; getViewCount()
447     * @param a  the allocation to this view
448     * @return the allocation to the child; or <code>null</code>
449     *          if <code>a</code> is <code>null</code>;
450     *          or <code>null</code> if the layout is invalid
451     */
452    public Shape getChildAllocation(int index, Shape a) {
453        if (a != null) {
454            Shape ca = super.getChildAllocation(index, a);
455            if ((ca != null) && (! isAllocationValid())) {
456                // The child allocation may not have been set yet.
457                Rectangle r = (ca instanceof Rectangle) ?
458                    (Rectangle) ca : ca.getBounds();
459                if ((r.width == 0) && (r.height == 0)) {
460                    return null;
461                }
462            }
463            return ca;
464        }
465        return null;
466    }
467
468    /**
469     * Provides a mapping from the document model coordinate space
470     * to the coordinate space of the view mapped to it.  This makes
471     * sure the allocation is valid before calling the superclass.
472     *
473     * @param pos the position to convert &gt;= 0
474     * @param a the allocated region to render into
475     * @return the bounding box of the given position
476     * @exception BadLocationException  if the given position does
477     *  not represent a valid location in the associated document
478     * @see View#modelToView
479     */
480    public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
481        if (! isAllocationValid()) {
482            Rectangle alloc = a.getBounds();
483            setSize(alloc.width, alloc.height);
484        }
485        return super.modelToView(pos, a, b);
486    }
487
488    /**
489     * Provides a mapping from the view coordinate space to the logical
490     * coordinate space of the model.
491     *
492     * @param x   x coordinate of the view location to convert &gt;= 0
493     * @param y   y coordinate of the view location to convert &gt;= 0
494     * @param a the allocated region to render into
495     * @return the location within the model that best represents the
496     *  given point in the view &gt;= 0
497     * @see View#viewToModel
498     */
499    public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
500        if (! isAllocationValid()) {
501            Rectangle alloc = a.getBounds();
502            setSize(alloc.width, alloc.height);
503        }
504        return super.viewToModel(x, y, a, bias);
505    }
506
507    /**
508     * Determines the desired alignment for this view along an
509     * axis.  This is implemented to give the total alignment
510     * needed to position the children with the alignment points
511     * lined up along the axis orthogonal to the axis that is
512     * being tiled.  The axis being tiled will request to be
513     * centered (i.e. 0.5f).
514     *
515     * @param axis may be either <code>View.X_AXIS</code>
516     *   or <code>View.Y_AXIS</code>
517     * @return the desired alignment &gt;= 0.0f &amp;&amp; &lt;= 1.0f; this should
518     *   be a value between 0.0 and 1.0 where 0 indicates alignment at the
519     *   origin and 1.0 indicates alignment to the full span
520     *   away from the origin; an alignment of 0.5 would be the
521     *   center of the view
522     * @exception IllegalArgumentException for an invalid axis
523     */
524    public float getAlignment(int axis) {
525        checkRequests(axis);
526        if (axis == majorAxis) {
527            return majorRequest.alignment;
528        } else {
529            return minorRequest.alignment;
530        }
531    }
532
533    /**
534     * Determines the preferred span for this view along an
535     * axis.
536     *
537     * @param axis may be either <code>View.X_AXIS</code>
538     *           or <code>View.Y_AXIS</code>
539     * @return   the span the view would like to be rendered into &gt;= 0;
540     *           typically the view is told to render into the span
541     *           that is returned, although there is no guarantee;
542     *           the parent may choose to resize or break the view
543     * @exception IllegalArgumentException for an invalid axis type
544     */
545    public float getPreferredSpan(int axis) {
546        checkRequests(axis);
547        float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
548            getTopInset() + getBottomInset();
549        if (axis == majorAxis) {
550            return ((float)majorRequest.preferred) + marginSpan;
551        } else {
552            return ((float)minorRequest.preferred) + marginSpan;
553        }
554    }
555
556    /**
557     * Determines the minimum span for this view along an
558     * axis.
559     *
560     * @param axis may be either <code>View.X_AXIS</code>
561     *           or <code>View.Y_AXIS</code>
562     * @return  the span the view would like to be rendered into &gt;= 0;
563     *           typically the view is told to render into the span
564     *           that is returned, although there is no guarantee;
565     *           the parent may choose to resize or break the view
566     * @exception IllegalArgumentException for an invalid axis type
567     */
568    public float getMinimumSpan(int axis) {
569        checkRequests(axis);
570        float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
571            getTopInset() + getBottomInset();
572        if (axis == majorAxis) {
573            return ((float)majorRequest.minimum) + marginSpan;
574        } else {
575            return ((float)minorRequest.minimum) + marginSpan;
576        }
577    }
578
579    /**
580     * Determines the maximum span for this view along an
581     * axis.
582     *
583     * @param axis may be either <code>View.X_AXIS</code>
584     *           or <code>View.Y_AXIS</code>
585     * @return   the span the view would like to be rendered into &gt;= 0;
586     *           typically the view is told to render into the span
587     *           that is returned, although there is no guarantee;
588     *           the parent may choose to resize or break the view
589     * @exception IllegalArgumentException for an invalid axis type
590     */
591    public float getMaximumSpan(int axis) {
592        checkRequests(axis);
593        float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() :
594            getTopInset() + getBottomInset();
595        if (axis == majorAxis) {
596            return ((float)majorRequest.maximum) + marginSpan;
597        } else {
598            return ((float)minorRequest.maximum) + marginSpan;
599        }
600    }
601
602    // --- local methods ----------------------------------------------------
603
604    /**
605     * Are the allocations for the children still
606     * valid?
607     *
608     * @return true if allocations still valid
609     */
610    protected boolean isAllocationValid() {
611        return (majorAllocValid && minorAllocValid);
612    }
613
614    /**
615     * Determines if a point falls before an allocated region.
616     *
617     * @param x the X coordinate &gt;= 0
618     * @param y the Y coordinate &gt;= 0
619     * @param innerAlloc the allocated region; this is the area
620     *   inside of the insets
621     * @return true if the point lies before the region else false
622     */
623    protected boolean isBefore(int x, int y, Rectangle innerAlloc) {
624        if (majorAxis == View.X_AXIS) {
625            return (x < innerAlloc.x);
626        } else {
627            return (y < innerAlloc.y);
628        }
629    }
630
631    /**
632     * Determines if a point falls after an allocated region.
633     *
634     * @param x the X coordinate &gt;= 0
635     * @param y the Y coordinate &gt;= 0
636     * @param innerAlloc the allocated region; this is the area
637     *   inside of the insets
638     * @return true if the point lies after the region else false
639     */
640    protected boolean isAfter(int x, int y, Rectangle innerAlloc) {
641        if (majorAxis == View.X_AXIS) {
642            return (x > (innerAlloc.width + innerAlloc.x));
643        } else {
644            return (y > (innerAlloc.height + innerAlloc.y));
645        }
646    }
647
648    /**
649     * Fetches the child view at the given coordinates.
650     *
651     * @param x the X coordinate &gt;= 0
652     * @param y the Y coordinate &gt;= 0
653     * @param alloc the parents inner allocation on entry, which should
654     *   be changed to the child's allocation on exit
655     * @return the view
656     */
657    protected View getViewAtPoint(int x, int y, Rectangle alloc) {
658        int n = getViewCount();
659        if (majorAxis == View.X_AXIS) {
660            if (x < (alloc.x + majorOffsets[0])) {
661                childAllocation(0, alloc);
662                return getView(0);
663            }
664            for (int i = 0; i < n; i++) {
665                if (x < (alloc.x + majorOffsets[i])) {
666                    childAllocation(i - 1, alloc);
667                    return getView(i - 1);
668                }
669            }
670            childAllocation(n - 1, alloc);
671            return getView(n - 1);
672        } else {
673            if (y < (alloc.y + majorOffsets[0])) {
674                childAllocation(0, alloc);
675                return getView(0);
676            }
677            for (int i = 0; i < n; i++) {
678                if (y < (alloc.y + majorOffsets[i])) {
679                    childAllocation(i - 1, alloc);
680                    return getView(i - 1);
681                }
682            }
683            childAllocation(n - 1, alloc);
684            return getView(n - 1);
685        }
686    }
687
688    /**
689     * Allocates a region for a child view.
690     *
691     * @param index the index of the child view to
692     *   allocate, &gt;= 0 &amp;&amp; &lt; getViewCount()
693     * @param alloc the allocated region
694     */
695    protected void childAllocation(int index, Rectangle alloc) {
696        alloc.x += getOffset(X_AXIS, index);
697        alloc.y += getOffset(Y_AXIS, index);
698        alloc.width = getSpan(X_AXIS, index);
699        alloc.height = getSpan(Y_AXIS, index);
700    }
701
702    /**
703     * Perform layout on the box
704     *
705     * @param width the width (inside of the insets) &gt;= 0
706     * @param height the height (inside of the insets) &gt;= 0
707     */
708    protected void layout(int width, int height) {
709        setSpanOnAxis(X_AXIS, width);
710        setSpanOnAxis(Y_AXIS, height);
711    }
712
713    /**
714     * Returns the current width of the box.  This is the width that
715     * it was last allocated.
716     * @return the current width of the box
717     */
718    public int getWidth() {
719        int span;
720        if (majorAxis == X_AXIS) {
721            span = majorSpan;
722        } else {
723            span = minorSpan;
724        }
725        span += getLeftInset() - getRightInset();
726        return span;
727    }
728
729    /**
730     * Returns the current height of the box.  This is the height that
731     * it was last allocated.
732     * @return the current height of the box
733     */
734    public int getHeight() {
735        int span;
736        if (majorAxis == Y_AXIS) {
737            span = majorSpan;
738        } else {
739            span = minorSpan;
740        }
741        span += getTopInset() - getBottomInset();
742        return span;
743    }
744
745    /**
746     * Performs layout for the major axis of the box (i.e. the
747     * axis that it represents). The results of the layout (the
748     * offset and span for each children) are placed in the given
749     * arrays which represent the allocations to the children
750     * along the major axis.
751     *
752     * @param targetSpan the total span given to the view, which
753     *  would be used to layout the children
754     * @param axis the axis being layed out
755     * @param offsets the offsets from the origin of the view for
756     *  each of the child views; this is a return value and is
757     *  filled in by the implementation of this method
758     * @param spans the span of each child view; this is a return
759     *  value and is filled in by the implementation of this method
760     */
761    protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
762        /*
763         * first pass, calculate the preferred sizes
764         * and the flexibility to adjust the sizes.
765         */
766        long preferred = 0;
767        int n = getViewCount();
768        for (int i = 0; i < n; i++) {
769            View v = getView(i);
770            spans[i] = (int) v.getPreferredSpan(axis);
771            preferred += spans[i];
772        }
773
774        /*
775         * Second pass, expand or contract by as much as possible to reach
776         * the target span.
777         */
778
779        // determine the adjustment to be made
780        long desiredAdjustment = targetSpan - preferred;
781        float adjustmentFactor = 0.0f;
782        int[] diffs = null;
783
784        if (desiredAdjustment != 0) {
785            long totalSpan = 0;
786            diffs = new int[n];
787            for (int i = 0; i < n; i++) {
788                View v = getView(i);
789                int tmp;
790                if (desiredAdjustment < 0) {
791                    tmp = (int)v.getMinimumSpan(axis);
792                    diffs[i] = spans[i] - tmp;
793                } else {
794                    tmp = (int)v.getMaximumSpan(axis);
795                    diffs[i] = tmp - spans[i];
796                }
797                totalSpan += tmp;
798            }
799
800            float maximumAdjustment = Math.abs(totalSpan - preferred);
801                adjustmentFactor = desiredAdjustment / maximumAdjustment;
802                adjustmentFactor = Math.min(adjustmentFactor, 1.0f);
803                adjustmentFactor = Math.max(adjustmentFactor, -1.0f);
804            }
805
806        // make the adjustments
807        int totalOffset = 0;
808        for (int i = 0; i < n; i++) {
809            offsets[i] = totalOffset;
810            if (desiredAdjustment != 0) {
811                float adjF = adjustmentFactor * diffs[i];
812                spans[i] += Math.round(adjF);
813            }
814            totalOffset = (int) Math.min((long) totalOffset + (long) spans[i], Integer.MAX_VALUE);
815        }
816    }
817
818    /**
819     * Performs layout for the minor axis of the box (i.e. the
820     * axis orthogonal to the axis that it represents). The results
821     * of the layout (the offset and span for each children) are
822     * placed in the given arrays which represent the allocations to
823     * the children along the minor axis.
824     *
825     * @param targetSpan the total span given to the view, which
826     *  would be used to layout the children
827     * @param axis the axis being layed out
828     * @param offsets the offsets from the origin of the view for
829     *  each of the child views; this is a return value and is
830     *  filled in by the implementation of this method
831     * @param spans the span of each child view; this is a return
832     *  value and is filled in by the implementation of this method
833     */
834    protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
835        int n = getViewCount();
836        for (int i = 0; i < n; i++) {
837            View v = getView(i);
838            int max = (int) v.getMaximumSpan(axis);
839            if (max < targetSpan) {
840                // can't make the child this wide, align it
841                float align = v.getAlignment(axis);
842                offsets[i] = (int) ((targetSpan - max) * align);
843                spans[i] = max;
844            } else {
845                // make it the target width, or as small as it can get.
846                int min = (int)v.getMinimumSpan(axis);
847                offsets[i] = 0;
848                spans[i] = Math.max(min, targetSpan);
849            }
850        }
851    }
852
853    /**
854     * Calculates the size requirements for the major axis
855     * <code>axis</code>.
856     *
857     * @param axis the axis being studied
858     * @param r the <code>SizeRequirements</code> object;
859     *          if <code>null</code> one will be created
860     * @return the newly initialized <code>SizeRequirements</code> object
861     * @see javax.swing.SizeRequirements
862     */
863    protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
864        // calculate tiled request
865        float min = 0;
866        float pref = 0;
867        float max = 0;
868
869        int n = getViewCount();
870        for (int i = 0; i < n; i++) {
871            View v = getView(i);
872            min += v.getMinimumSpan(axis);
873            pref += v.getPreferredSpan(axis);
874            max += v.getMaximumSpan(axis);
875        }
876
877        if (r == null) {
878            r = new SizeRequirements();
879        }
880        r.alignment = 0.5f;
881        r.minimum = (int) min;
882        r.preferred = (int) pref;
883        r.maximum = (int) max;
884        return r;
885    }
886
887    /**
888     * Calculates the size requirements for the minor axis
889     * <code>axis</code>.
890     *
891     * @param axis the axis being studied
892     * @param r the <code>SizeRequirements</code> object;
893     *          if <code>null</code> one will be created
894     * @return the newly initialized <code>SizeRequirements</code> object
895     * @see javax.swing.SizeRequirements
896     */
897    protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
898        int min = 0;
899        long pref = 0;
900        int max = Integer.MAX_VALUE;
901        int n = getViewCount();
902        for (int i = 0; i < n; i++) {
903            View v = getView(i);
904            min = Math.max((int) v.getMinimumSpan(axis), min);
905            pref = Math.max((int) v.getPreferredSpan(axis), pref);
906            max = Math.max((int) v.getMaximumSpan(axis), max);
907        }
908
909        if (r == null) {
910            r = new SizeRequirements();
911            r.alignment = 0.5f;
912        }
913        r.preferred = (int) pref;
914        r.minimum = min;
915        r.maximum = max;
916        return r;
917    }
918
919    /**
920     * Checks the request cache and update if needed.
921     * @param axis the axis being studied
922     * @exception IllegalArgumentException if <code>axis</code> is
923     *  neither <code>View.X_AXIS</code> nor <code>View.Y_AXIS</code>
924     */
925    void checkRequests(int axis) {
926        if ((axis != X_AXIS) && (axis != Y_AXIS)) {
927            throw new IllegalArgumentException("Invalid axis: " + axis);
928        }
929        if (axis == majorAxis) {
930            if (!majorReqValid) {
931                majorRequest = calculateMajorAxisRequirements(axis,
932                                                              majorRequest);
933                majorReqValid = true;
934            }
935        } else if (! minorReqValid) {
936            minorRequest = calculateMinorAxisRequirements(axis, minorRequest);
937            minorReqValid = true;
938        }
939    }
940
941    /**
942     * Computes the location and extent of each child view
943     * in this <code>BoxView</code> given the <code>targetSpan</code>,
944     * which is the width (or height) of the region we have to
945     * work with.
946     *
947     * @param targetSpan the total span given to the view, which
948     *  would be used to layout the children
949     * @param axis the axis being studied, either
950     *          <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>
951     * @param offsets an empty array filled by this method with
952     *          values specifying the location  of each child view
953     * @param spans  an empty array filled by this method with
954     *          values specifying the extent of each child view
955     */
956    protected void baselineLayout(int targetSpan, int axis, int[] offsets, int[] spans) {
957        int totalAscent = (int)(targetSpan * getAlignment(axis));
958        int totalDescent = targetSpan - totalAscent;
959
960        int n = getViewCount();
961
962        for (int i = 0; i < n; i++) {
963            View v = getView(i);
964            float align = v.getAlignment(axis);
965            float viewSpan;
966
967            if (v.getResizeWeight(axis) > 0) {
968                // if resizable then resize to the best fit
969
970                // the smallest span possible
971                float minSpan = v.getMinimumSpan(axis);
972                // the largest span possible
973                float maxSpan = v.getMaximumSpan(axis);
974
975                if (align == 0.0f) {
976                    // if the alignment is 0 then we need to fit into the descent
977                    viewSpan = Math.max(Math.min(maxSpan, totalDescent), minSpan);
978                } else if (align == 1.0f) {
979                    // if the alignment is 1 then we need to fit into the ascent
980                    viewSpan = Math.max(Math.min(maxSpan, totalAscent), minSpan);
981                } else {
982                    // figure out the span that we must fit into
983                    float fitSpan = Math.min(totalAscent / align,
984                                             totalDescent / (1.0f - align));
985                    // fit into the calculated span
986                    viewSpan = Math.max(Math.min(maxSpan, fitSpan), minSpan);
987                }
988            } else {
989                // otherwise use the preferred spans
990                viewSpan = v.getPreferredSpan(axis);
991            }
992
993            offsets[i] = totalAscent - (int)(viewSpan * align);
994            spans[i] = (int)viewSpan;
995        }
996    }
997
998    /**
999     * Calculates the size requirements for this <code>BoxView</code>
1000     * by examining the size of each child view.
1001     *
1002     * @param axis the axis being studied
1003     * @param r the <code>SizeRequirements</code> object;
1004     *          if <code>null</code> one will be created
1005     * @return the newly initialized <code>SizeRequirements</code> object
1006     */
1007    protected SizeRequirements baselineRequirements(int axis, SizeRequirements r) {
1008        SizeRequirements totalAscent = new SizeRequirements();
1009        SizeRequirements totalDescent = new SizeRequirements();
1010
1011        if (r == null) {
1012            r = new SizeRequirements();
1013        }
1014
1015        r.alignment = 0.5f;
1016
1017        int n = getViewCount();
1018
1019        // loop through all children calculating the max of all their ascents and
1020        // descents at minimum, preferred, and maximum sizes
1021        for (int i = 0; i < n; i++) {
1022            View v = getView(i);
1023            float align = v.getAlignment(axis);
1024            float span;
1025            int ascent;
1026            int descent;
1027
1028            // find the maximum of the preferred ascents and descents
1029            span = v.getPreferredSpan(axis);
1030            ascent = (int)(align * span);
1031            descent = (int)(span - ascent);
1032            totalAscent.preferred = Math.max(ascent, totalAscent.preferred);
1033            totalDescent.preferred = Math.max(descent, totalDescent.preferred);
1034
1035            if (v.getResizeWeight(axis) > 0) {
1036                // if the view is resizable then do the same for the minimum and
1037                // maximum ascents and descents
1038                span = v.getMinimumSpan(axis);
1039                ascent = (int)(align * span);
1040                descent = (int)(span - ascent);
1041                totalAscent.minimum = Math.max(ascent, totalAscent.minimum);
1042                totalDescent.minimum = Math.max(descent, totalDescent.minimum);
1043
1044                span = v.getMaximumSpan(axis);
1045                ascent = (int)(align * span);
1046                descent = (int)(span - ascent);
1047                totalAscent.maximum = Math.max(ascent, totalAscent.maximum);
1048                totalDescent.maximum = Math.max(descent, totalDescent.maximum);
1049            } else {
1050                // otherwise use the preferred
1051                totalAscent.minimum = Math.max(ascent, totalAscent.minimum);
1052                totalDescent.minimum = Math.max(descent, totalDescent.minimum);
1053                totalAscent.maximum = Math.max(ascent, totalAscent.maximum);
1054                totalDescent.maximum = Math.max(descent, totalDescent.maximum);
1055            }
1056        }
1057
1058        // we now have an overall preferred, minimum, and maximum ascent and descent
1059
1060        // calculate the preferred span as the sum of the preferred ascent and preferred descent
1061        r.preferred = (int)Math.min((long)totalAscent.preferred + (long)totalDescent.preferred,
1062                                    Integer.MAX_VALUE);
1063
1064        // calculate the preferred alignment as the preferred ascent divided by the preferred span
1065        if (r.preferred > 0) {
1066            r.alignment = (float)totalAscent.preferred / r.preferred;
1067        }
1068
1069
1070        if (r.alignment == 0.0f) {
1071            // if the preferred alignment is 0 then the minimum and maximum spans are simply
1072            // the minimum and maximum descents since there's nothing above the baseline
1073            r.minimum = totalDescent.minimum;
1074            r.maximum = totalDescent.maximum;
1075        } else if (r.alignment == 1.0f) {
1076            // if the preferred alignment is 1 then the minimum and maximum spans are simply
1077            // the minimum and maximum ascents since there's nothing below the baseline
1078            r.minimum = totalAscent.minimum;
1079            r.maximum = totalAscent.maximum;
1080        } else {
1081            // we want to honor the preferred alignment so we calculate two possible minimum
1082            // span values using 1) the minimum ascent and the alignment, and 2) the minimum
1083            // descent and the alignment. We'll choose the larger of these two numbers.
1084            r.minimum = Math.round(Math.max(totalAscent.minimum / r.alignment,
1085                                          totalDescent.minimum / (1.0f - r.alignment)));
1086            // a similar calculation is made for the maximum but we choose the smaller number.
1087            r.maximum = Math.round(Math.min(totalAscent.maximum / r.alignment,
1088                                          totalDescent.maximum / (1.0f - r.alignment)));
1089        }
1090
1091        return r;
1092    }
1093
1094    /**
1095     * Fetches the offset of a particular child's current layout.
1096     * @param axis the axis being studied
1097     * @param childIndex the index of the requested child
1098     * @return the offset (location) for the specified child
1099     */
1100    protected int getOffset(int axis, int childIndex) {
1101        int[] offsets = (axis == majorAxis) ? majorOffsets : minorOffsets;
1102        return offsets[childIndex];
1103    }
1104
1105    /**
1106     * Fetches the span of a particular child's current layout.
1107     * @param axis the axis being studied
1108     * @param childIndex the index of the requested child
1109     * @return the span (width or height) of the specified child
1110     */
1111    protected int getSpan(int axis, int childIndex) {
1112        int[] spans = (axis == majorAxis) ? majorSpans : minorSpans;
1113        return spans[childIndex];
1114    }
1115
1116    /**
1117     * Determines in which direction the next view lays.
1118     * Consider the View at index n. Typically the <code>View</code>s
1119     * are layed out from left to right, so that the <code>View</code>
1120     * to the EAST will be at index n + 1, and the <code>View</code>
1121     * to the WEST will be at index n - 1. In certain situations,
1122     * such as with bidirectional text, it is possible
1123     * that the <code>View</code> to EAST is not at index n + 1,
1124     * but rather at index n - 1, or that the <code>View</code>
1125     * to the WEST is not at index n - 1, but index n + 1.
1126     * In this case this method would return true,
1127     * indicating the <code>View</code>s are layed out in
1128     * descending order. Otherwise the method would return false
1129     * indicating the <code>View</code>s are layed out in ascending order.
1130     * <p>
1131     * If the receiver is laying its <code>View</code>s along the
1132     * <code>Y_AXIS</code>, this will return the value from
1133     * invoking the same method on the <code>View</code>
1134     * responsible for rendering <code>position</code> and
1135     * <code>bias</code>. Otherwise this will return false.
1136     *
1137     * @param position position into the model
1138     * @param bias either <code>Position.Bias.Forward</code> or
1139     *          <code>Position.Bias.Backward</code>
1140     * @return true if the <code>View</code>s surrounding the
1141     *          <code>View</code> responding for rendering
1142     *          <code>position</code> and <code>bias</code>
1143     *          are layed out in descending order; otherwise false
1144     */
1145    protected boolean flipEastAndWestAtEnds(int position,
1146                                            Position.Bias bias) {
1147        if(majorAxis == Y_AXIS) {
1148            int testPos = (bias == Position.Bias.Backward) ?
1149                          Math.max(0, position - 1) : position;
1150            int index = getViewIndexAtPosition(testPos);
1151            if(index != -1) {
1152                View v = getView(index);
1153                if(v != null && v instanceof CompositeView) {
1154                    return ((CompositeView)v).flipEastAndWestAtEnds(position,
1155                                                                    bias);
1156                }
1157            }
1158        }
1159        return false;
1160    }
1161
1162    // --- variables ------------------------------------------------
1163
1164    int majorAxis;
1165
1166    int majorSpan;
1167    int minorSpan;
1168
1169    /*
1170     * Request cache
1171     */
1172    boolean majorReqValid;
1173    boolean minorReqValid;
1174    SizeRequirements majorRequest;
1175    SizeRequirements minorRequest;
1176
1177    /*
1178     * Allocation cache
1179     */
1180    boolean majorAllocValid;
1181    int[] majorOffsets;
1182    int[] majorSpans;
1183    boolean minorAllocValid;
1184    int[] minorOffsets;
1185    int[] minorSpans;
1186
1187    /** used in paint. */
1188    Rectangle tempRect;
1189}
1190