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      * @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 }