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