Source for javax.swing.text.BoxView

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