Source for javax.swing.text.AsyncBoxView

   1: /* AsyncBoxView.java -- A box view that performs layout asynchronously
   2:    Copyright (C) 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.Component;
  42: import java.awt.Graphics;
  43: import java.awt.Rectangle;
  44: import java.awt.Shape;
  45: import java.util.ArrayList;
  46: 
  47: import javax.swing.event.DocumentEvent;
  48: import javax.swing.text.Position.Bias;
  49: 
  50: /**
  51:  * A {@link View} implementation that lays out its child views in a box, either
  52:  * vertically or horizontally. The difference to {@link BoxView} is that the
  53:  * layout is performed in an asynchronous manner. This helps to keep the
  54:  * eventqueue free from non-GUI related tasks.
  55:  *
  56:  * This view is currently not used in standard text components. In order to
  57:  * use it you would have to implement a special {@link EditorKit} with a
  58:  * {@link ViewFactory} that returns this view. For example:
  59:  *
  60:  * <pre>
  61:  * static class AsyncEditorKit extends StyledEditorKit implements ViewFactory
  62:  * {
  63:  *   public View create(Element el)
  64:  *   {
  65:  *     if (el.getName().equals(AbstractDocument.SectionElementName))
  66:  *       return new AsyncBoxView(el, View.Y_AXIS);
  67:  *     return super.getViewFactory().create(el);
  68:  *   }
  69:  *   public ViewFactory getViewFactory() {
  70:  *     return this;
  71:  *   }
  72:  * }
  73:  * </pre>
  74:  *
  75:  * @author Roman Kennke (kennke@aicas.com)
  76:  *
  77:  * @since 1.3
  78:  */
  79: public class AsyncBoxView
  80:   extends View
  81: {
  82: 
  83:   /**
  84:    * Manages the effective position of child views. That keeps the visible
  85:    * layout stable while the AsyncBoxView might be changing until the layout
  86:    * thread decides to publish the new layout.
  87:    */
  88:   public class ChildLocator
  89:   {
  90: 
  91:     /**
  92:      * The last valid location.
  93:      */
  94:     protected ChildState lastValidOffset;
  95: 
  96:     /**
  97:      * The last allocation.
  98:      */
  99:     protected Rectangle lastAlloc;
 100: 
 101:     /**
 102:      * A Rectangle used for child allocation calculation to avoid creation
 103:      * of lots of garbage Rectangle objects.
 104:      */
 105:     protected Rectangle childAlloc;
 106: 
 107:     /**
 108:      * Creates a new ChildLocator.
 109:      */
 110:     public ChildLocator()
 111:     {
 112:       lastAlloc = new Rectangle();
 113:       childAlloc = new Rectangle();
 114:     }
 115: 
 116:     /**
 117:      * Receives notification that a child has changed. This is called by
 118:      * child state objects that have changed it's major span.
 119:      *
 120:      * This sets the {@link #lastValidOffset} field to <code>cs</code> if
 121:      * the new child state's view start offset is smaller than the start offset
 122:      * of the current child state's view or when <code>lastValidOffset</code>
 123:      * is <code>null</code>.
 124:      *
 125:      * @param cs the child state object that has changed
 126:      */
 127:     public synchronized void childChanged(ChildState cs)
 128:     {
 129:       if (lastValidOffset == null
 130:           || cs.getChildView().getStartOffset()
 131:              < lastValidOffset.getChildView().getStartOffset())
 132:         {
 133:           lastValidOffset = cs;
 134:         }
 135:     }
 136: 
 137:     /**
 138:      * Returns the view index of the view that occupies the specified area, or
 139:      * <code>-1</code> if there is no such child view.
 140:      *
 141:      * @param x the x coordinate (relative to <code>a</code>)
 142:      * @param y the y coordinate (relative to <code>a</code>)
 143:      * @param a the current allocation of this view
 144:      *
 145:      * @return the view index of the view that occupies the specified area, or
 146:      *         <code>-1</code> if there is no such child view
 147:      */
 148:     public int getViewIndexAtPoint(float x, float y, Shape a)
 149:     {
 150:       setAllocation(a);
 151:       float targetOffset = (getMajorAxis() == X_AXIS) ? x - lastAlloc.x
 152:                                                       : y - lastAlloc.y;
 153:       int index = getViewIndexAtVisualOffset(targetOffset);
 154:       return index;
 155:     }
 156: 
 157:     /**
 158:      * Returns the current allocation for a child view. This updates the
 159:      * offsets for all children <em>before</em> the requested child view.
 160:      *
 161:      * @param index the index of the child view
 162:      * @param a the current allocation of this view
 163:      * 
 164:      * @return the current allocation for a child view
 165:      */
 166:     public synchronized Shape getChildAllocation(int index, Shape a)
 167:     {
 168:       if (a == null)
 169:         return null;
 170:       setAllocation(a);
 171:       ChildState cs = getChildState(index);
 172:       if (cs.getChildView().getStartOffset()
 173:           > lastValidOffset.getChildView().getStartOffset())
 174:         {
 175:           updateChildOffsetsToIndex(index);
 176:         }
 177:       Shape ca = getChildAllocation(index);
 178:       return ca;
 179:     }
 180: 
 181:     /**
 182:      * Paints all child views.
 183:      *
 184:      * @param g the graphics context to use
 185:      */
 186:     public synchronized void paintChildren(Graphics g)
 187:     {
 188:       Rectangle clip = g.getClipBounds();
 189:       float targetOffset = (getMajorAxis() == X_AXIS) ? clip.x - lastAlloc.x
 190:                                                       : clip.y - lastAlloc.y;
 191:       int index = getViewIndexAtVisualOffset(targetOffset);
 192:       int n = getViewCount();
 193:       float offs = getChildState(index).getMajorOffset();
 194:       for (int i = index; i < n; i++)
 195:         {
 196:           ChildState cs = getChildState(i);
 197:           cs.setMajorOffset(offs);
 198:           Shape ca = getChildAllocation(i);
 199:           if (ca.intersects(clip))
 200:             {
 201:               synchronized (cs)
 202:                 {
 203:                   View v = cs.getChildView();
 204:                   v.paint(g, ca);
 205:                 }
 206:             }
 207:           else
 208:             {
 209:               // done painting intersection
 210:               break;
 211:             }
 212:           offs += cs.getMajorSpan();
 213:         }
 214:     }
 215: 
 216:     /**
 217:      * Returns the current allocation of the child view with the specified
 218:      * index. Note that this will <em>not</em> update any location information.
 219:      * 
 220:      * @param index the index of the requested child view
 221:      *
 222:      * @return the current allocation of the child view with the specified
 223:      *         index
 224:      */
 225:     protected Shape getChildAllocation(int index)
 226:     {
 227:       ChildState cs = getChildState(index);
 228:       if (! cs.isLayoutValid())
 229:           cs.run();
 230: 
 231:       if (getMajorAxis() == X_AXIS)
 232:         {
 233:           childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset();
 234:           childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset();
 235:           childAlloc.width = (int) cs.getMajorSpan();
 236:           childAlloc.height = (int) cs.getMinorSpan();
 237:         }
 238:       else
 239:         {
 240:           childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset();
 241:           childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset();
 242:           childAlloc.height = (int) cs.getMajorSpan();
 243:           childAlloc.width = (int) cs.getMinorSpan();
 244:         }
 245:       return childAlloc;
 246:     }
 247: 
 248:     /**
 249:      * Sets the current allocation for this view.
 250:      *
 251:      * @param a the allocation to set
 252:      */
 253:     protected void setAllocation(Shape a)
 254:     {
 255:       if (a instanceof Rectangle)
 256:         lastAlloc.setBounds((Rectangle) a);
 257:       else
 258:         lastAlloc.setBounds(a.getBounds());
 259: 
 260:       setSize(lastAlloc.width, lastAlloc.height);
 261:     }
 262: 
 263:     /**
 264:      * Returns the index of the view at the specified offset along the major
 265:      * layout axis.
 266:      *
 267:      * @param targetOffset the requested offset
 268:      *
 269:      * @return the index of the view at the specified offset along the major
 270:      * layout axis
 271:      */
 272:     protected int getViewIndexAtVisualOffset(float targetOffset)
 273:     {
 274:       int n = getViewCount();
 275:       if (n > 0)
 276:         {
 277:           if (lastValidOffset == null)
 278:             lastValidOffset = getChildState(0);
 279:           if (targetOffset > majorSpan)
 280:             return 0;
 281:           else if (targetOffset > lastValidOffset.getMajorOffset())
 282:             return updateChildOffsets(targetOffset);
 283:           else
 284:             {
 285:               float offs = 0f;
 286:               for (int i = 0; i < n; i++)
 287:                 {
 288:                   ChildState cs = getChildState(i);
 289:                   float nextOffs = offs + cs.getMajorSpan();
 290:                   if (targetOffset < nextOffs)
 291:                     return i;
 292:                   offs = nextOffs;
 293:                 }
 294:             }
 295:         }
 296:       return n - 1;
 297:     }
 298: 
 299:     /**
 300:      * Updates all the child view offsets up to the specified targetOffset.
 301:      *
 302:      * @param targetOffset the offset up to which the child view offsets are
 303:      *        updated
 304:      *
 305:      * @return the index of the view at the specified offset
 306:      */
 307:     private int updateChildOffsets(float targetOffset)
 308:     {
 309:       int n = getViewCount();
 310:       int targetIndex = n - 1;
 311:       int pos = lastValidOffset.getChildView().getStartOffset();
 312:       int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
 313:       float start = lastValidOffset.getMajorOffset();
 314:       float lastOffset = start;
 315:       for (int i = startIndex; i < n; i++)
 316:         {
 317:           ChildState cs = getChildState(i);
 318:           cs.setMajorOffset(lastOffset);
 319:           lastOffset += cs.getMajorSpan();
 320:           if (targetOffset < lastOffset)
 321:             {
 322:               targetIndex = i;
 323:               lastValidOffset = cs;
 324:               break;
 325:             }
 326:         }
 327:       return targetIndex;
 328:     }
 329: 
 330:     /**
 331:      * Updates the offsets of the child views up to the specified index.
 332:      *
 333:      * @param index the index up to which the offsets are updated
 334:      */
 335:     private void updateChildOffsetsToIndex(int index)
 336:     {
 337:       int pos = lastValidOffset.getChildView().getStartOffset();
 338:       int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward);
 339:       float lastOffset = lastValidOffset.getMajorOffset();
 340:       for (int i = startIndex; i <= index; i++)
 341:         {
 342:           ChildState cs = getChildState(i);
 343:           cs.setMajorOffset(lastOffset);
 344:           lastOffset += cs.getMajorSpan();
 345:         }
 346:     }
 347:   }
 348: 
 349:   /**
 350:    * Represents the layout state of a child view.
 351:    */
 352:   public class ChildState
 353:     implements Runnable
 354:   {
 355: 
 356:     /**
 357:      * The child view for this state record.
 358:      */
 359:     private View childView;
 360: 
 361:     /**
 362:      * Indicates if the minor axis requirements of this child view are valid
 363:      * or not.
 364:      */
 365:     private boolean minorValid;
 366: 
 367:     /**
 368:      * Indicates if the major axis requirements of this child view are valid
 369:      * or not.
 370:      */
 371:     private boolean majorValid;
 372: 
 373:     /**
 374:      * Indicates if the current child size is valid. This is package private
 375:      * to avoid synthetic accessor method.
 376:      */
 377:     boolean childSizeValid;
 378: 
 379:     /**
 380:      * The child views minimumSpan. This is package private to avoid accessor
 381:      * method.
 382:      */
 383:     float minimum;
 384: 
 385:     /**
 386:      * The child views preferredSpan. This is package private to avoid accessor
 387:      * method.
 388:      */
 389:     float preferred;
 390: 
 391:     /**
 392:      * The current span of the child view along the major axis.
 393:      */
 394:     private float majorSpan;
 395: 
 396:     /**
 397:      * The current offset of the child view along the major axis.
 398:      */
 399:     private float majorOffset;
 400: 
 401:     /**
 402:      * The current span of the child view along the minor axis.
 403:      */
 404:     private float minorSpan;
 405: 
 406:     /**
 407:      * The current offset of the child view along the major axis.
 408:      */
 409:     private float minorOffset;
 410: 
 411:     /**
 412:      * The child views maximumSpan.
 413:      */
 414:     private float maximum;
 415: 
 416:     /**
 417:      * Creates a new <code>ChildState</code> object for the specified child
 418:      * view.
 419:      *
 420:      * @param view the child view for which to create the state record
 421:      */
 422:     public ChildState(View view)
 423:     {
 424:       childView = view;
 425:     }
 426: 
 427:     /**
 428:      * Returns the child view for which this <code>ChildState</code> represents
 429:      * the layout state.
 430:      *
 431:      * @return the child view for this child state object 
 432:      */
 433:     public View getChildView()
 434:     {
 435:       return childView;
 436:     }
 437: 
 438:     /**
 439:      * Returns <code>true</code> if the current layout information is valid,
 440:      * <code>false</code> otherwise.
 441:      *
 442:      * @return <code>true</code> if the current layout information is valid,
 443:      *         <code>false</code> otherwise
 444:      */
 445:     public boolean isLayoutValid()
 446:     {
 447:       return minorValid && majorValid && childSizeValid;
 448:     }
 449: 
 450:     /**
 451:      * Performs the layout update for the child view managed by this
 452:      * <code>ChildState</code>.
 453:      */
 454:     public void run()
 455:     {
 456:       Document doc = getDocument();
 457:       if (doc instanceof AbstractDocument)
 458:         {
 459:           AbstractDocument abstractDoc = (AbstractDocument) doc;
 460:           abstractDoc.readLock();
 461:         }
 462: 
 463:       try
 464:         {
 465: 
 466:           if (!(minorValid &&  majorValid && childSizeValid)
 467:               && childView.getParent() == AsyncBoxView.this)
 468:             {
 469:               synchronized(AsyncBoxView.this)
 470:               {
 471:                 changing = this;
 472:               }
 473:               update();
 474:               synchronized(AsyncBoxView.this)
 475:               {
 476:                 changing = null;
 477:               }
 478:               // Changing the major axis may cause the minor axis
 479:               // requirements to have changed, so we need to do this again.
 480:               update();
 481:             }
 482:         }
 483:       finally
 484:         {
 485:           if (doc instanceof AbstractDocument)
 486:             {
 487:               AbstractDocument abstractDoc = (AbstractDocument) doc;
 488:               abstractDoc.readUnlock();
 489:             }
 490:         }
 491:     }
 492: 
 493:     /**
 494:      * Performs the actual update after the run methods has made its checks
 495:      * and locked the document.
 496:      */
 497:     private void update()
 498:     {
 499:       int majorAxis = getMajorAxis();
 500:       boolean minorUpdated = false;
 501:       synchronized (this)
 502:         {
 503:           if (! minorValid)
 504:             {
 505:               int minorAxis = getMinorAxis();
 506:               minimum = childView.getMinimumSpan(minorAxis);
 507:               preferred = childView.getPreferredSpan(minorAxis);
 508:               maximum = childView.getMaximumSpan(minorAxis);
 509:               minorValid = true;
 510:               minorUpdated = true;
 511:             }
 512:         }
 513:       if (minorUpdated)
 514:         minorRequirementChange(this);
 515: 
 516:       boolean majorUpdated = false;
 517:       float delta = 0.0F;
 518:       synchronized (this)
 519:         {
 520:           if (! majorValid)
 521:             {
 522:               float oldSpan = majorSpan;
 523:               majorSpan = childView.getPreferredSpan(majorAxis);
 524:               delta = majorSpan - oldSpan;
 525:               majorValid = true;
 526:               majorUpdated = true;
 527:             }
 528:         }
 529:       if (majorUpdated)
 530:         {
 531:           majorRequirementChange(this, delta);
 532:           locator.childChanged(this);
 533:         }
 534: 
 535:       synchronized (this)
 536:         {
 537:           if (! childSizeValid)
 538:             {
 539:               float w;
 540:               float h;
 541:               if (majorAxis == X_AXIS)
 542:                 {
 543:                   w = majorSpan;
 544:                   h = getMinorSpan();
 545:                 }
 546:               else
 547:                 {
 548:                   w = getMinorSpan();
 549:                   h = majorSpan;
 550:                 }
 551:               childSizeValid = true;
 552:               childView.setSize(w, h);
 553:             }
 554:         }
 555:     }
 556: 
 557:     /**
 558:      * Returns the span of the child view along the minor layout axis.
 559:      *
 560:      * @return the span of the child view along the minor layout axis
 561:      */
 562:     public float getMinorSpan()
 563:     {
 564:       float retVal;
 565:       if (maximum < minorSpan)
 566:         retVal = maximum;
 567:       else
 568:         retVal = Math.max(minimum, minorSpan);
 569:       return retVal;
 570:     }
 571: 
 572:     /**
 573:      * Returns the offset of the child view along the minor layout axis.
 574:      *
 575:      * @return the offset of the child view along the minor layout axis
 576:      */
 577:     public float getMinorOffset()
 578:     {
 579:       float retVal;
 580:       if (maximum < minorSpan)
 581:         {
 582:           float align = childView.getAlignment(getMinorAxis());
 583:           retVal = ((minorSpan - maximum) * align);
 584:         }
 585:       else
 586:         retVal = 0f;
 587: 
 588:       return retVal;
 589:     }
 590: 
 591:     /**
 592:      * Returns the span of the child view along the major layout axis.
 593:      *
 594:      * @return the span of the child view along the major layout axis
 595:      */
 596: 
 597:     public float getMajorSpan()
 598:     {
 599:       return majorSpan;
 600:     }
 601: 
 602:     /**
 603:      * Returns the offset of the child view along the major layout axis.
 604:      *
 605:      * @return the offset of the child view along the major layout axis
 606:      */
 607:     public float getMajorOffset()
 608:     {
 609:       return majorOffset;
 610:     }
 611: 
 612:     /**
 613:      * Sets the offset of the child view along the major layout axis. This
 614:      * should only be called by the ChildLocator of that child view.
 615:      *
 616:      * @param offset the offset to set
 617:      */
 618:     public void setMajorOffset(float offset)
 619:     {
 620:       majorOffset = offset;
 621:     }
 622: 
 623:     /**
 624:      * Mark the preferences changed for that child. This forwards to
 625:      * {@link AsyncBoxView#preferenceChanged}.
 626:      *
 627:      * @param width <code>true</code> if the width preference has changed
 628:      * @param height <code>true</code> if the height preference has changed
 629:      */
 630:     public void preferenceChanged(boolean width, boolean height)
 631:     {
 632:       if (getMajorAxis() == X_AXIS)
 633:         {
 634:           if (width)
 635:             majorValid = false;
 636:           if (height)
 637:             minorValid = false;
 638:         }
 639:       else
 640:         {
 641:           if (width)
 642:             minorValid = false;
 643:           if (height)
 644:             majorValid = false;
 645:         }
 646:       childSizeValid = false;
 647:     }
 648:   }
 649: 
 650:   /**
 651:    * Flushes the requirements changes upwards asynchronously.
 652:    */
 653:   private class FlushTask implements Runnable
 654:   {
 655:     /**
 656:      * Starts the flush task. This obtains a readLock on the document
 657:      * and then flushes all the updates using
 658:      * {@link AsyncBoxView#flushRequirementChanges()} after updating the
 659:      * requirements.
 660:      */
 661:     public void run()
 662:     {
 663:       try
 664:         {
 665:           // Acquire a lock on the document.
 666:           Document doc = getDocument();
 667:           if (doc instanceof AbstractDocument)
 668:             {
 669:               AbstractDocument abstractDoc = (AbstractDocument) doc;
 670:               abstractDoc.readLock();
 671:             }
 672: 
 673:           int n = getViewCount();
 674:           if (minorChanged && (n > 0))
 675:             {
 676:               LayoutQueue q = getLayoutQueue();
 677:               ChildState min = getChildState(0);
 678:               ChildState pref = getChildState(0);
 679:               for (int i = 1; i < n; i++)
 680:                 {
 681:                   ChildState cs = getChildState(i);
 682:                   if (cs.minimum > min.minimum)
 683:                     min = cs;
 684:                   if (cs.preferred > pref.preferred)
 685:                     pref = cs;
 686:                 }
 687:               synchronized (AsyncBoxView.this)
 688:               {
 689:                 minReq = min;
 690:                 prefReq = pref;
 691:               }
 692:             }
 693: 
 694:           flushRequirementChanges();
 695:         }
 696:       finally
 697:       {
 698:         // Release the lock on the document.
 699:         Document doc = getDocument();
 700:         if (doc instanceof AbstractDocument)
 701:           {
 702:             AbstractDocument abstractDoc = (AbstractDocument) doc;
 703:             abstractDoc.readUnlock();
 704:           }
 705:       }
 706:     }
 707: 
 708:   }
 709: 
 710:   /**
 711:    * The major layout axis.
 712:    */
 713:   private int majorAxis;
 714: 
 715:   /**
 716:    * The top inset.
 717:    */
 718:   private float topInset;
 719: 
 720:   /**
 721:    * The bottom inset.
 722:    */
 723:   private float bottomInset;
 724: 
 725:   /**
 726:    * The left inset.
 727:    */
 728:   private float leftInset;
 729: 
 730:   /**
 731:    * Indicates if the major span should be treated as beeing estimated or not.
 732:    */
 733:   private boolean estimatedMajorSpan;
 734: 
 735:   /**
 736:    * The right inset.
 737:    */
 738:   private float rightInset;
 739: 
 740:   /**
 741:    * The children and their layout statistics.
 742:    */
 743:   private ArrayList childStates;
 744: 
 745:   /**
 746:    * The currently changing child state. May be null if there is no child state
 747:    * updating at the moment. This is package private to avoid a synthetic
 748:    * accessor method inside ChildState.
 749:    */
 750:   ChildState changing;
 751: 
 752:   /**
 753:    * Represents the minimum requirements. This is used in
 754:    * {@link #getMinimumSpan(int)}.
 755:    */
 756:   ChildState minReq;
 757: 
 758:   /**
 759:    * Represents the minimum requirements. This is used in
 760:    * {@link #getPreferredSpan(int)}.
 761:    */
 762:   ChildState prefReq;
 763: 
 764:   /**
 765:    * Indicates that the major axis requirements have changed.
 766:    */
 767:   private boolean majorChanged;
 768: 
 769:   /**
 770:    * Indicates that the minor axis requirements have changed. This is package
 771:    * private to avoid synthetic accessor method.
 772:    */
 773:   boolean minorChanged;
 774: 
 775:   /**
 776:    * The current span along the major layout axis. This is package private to
 777:    * avoid synthetic accessor method.
 778:    */
 779:   float majorSpan;
 780: 
 781:   /**
 782:    * The current span along the minor layout axis. This is package private to
 783:    * avoid synthetic accessor method.
 784:    */
 785:   float minorSpan;
 786: 
 787:   /**
 788:    * This tasked is placed on the layout queue to flush updates up to the
 789:    * parent view.
 790:    */
 791:   private Runnable flushTask;
 792: 
 793:   /**
 794:    * The child locator for this view.
 795:    */
 796:   protected ChildLocator locator;
 797: 
 798:   /**
 799:    * Creates a new <code>AsyncBoxView</code> that represents the specified
 800:    * element and layouts its children along the specified axis.
 801:    *
 802:    * @param elem the element
 803:    * @param axis the layout axis
 804:    */
 805:   public AsyncBoxView(Element elem, int axis)
 806:   {
 807:     super(elem);
 808:     majorAxis = axis;
 809:     childStates = new ArrayList();
 810:     flushTask = new FlushTask();
 811:     locator = new ChildLocator();
 812:     minorSpan = Short.MAX_VALUE;
 813:   }
 814: 
 815:   /**
 816:    * Returns the major layout axis.
 817:    *
 818:    * @return the major layout axis
 819:    */
 820:   public int getMajorAxis()
 821:   {
 822:     return majorAxis;
 823:   }
 824: 
 825:   /**
 826:    * Returns the minor layout axis, that is the axis orthogonal to the major
 827:    * layout axis.
 828:    *
 829:    * @return the minor layout axis
 830:    */
 831:   public int getMinorAxis()
 832:   {
 833:     return majorAxis == X_AXIS ? Y_AXIS : X_AXIS;
 834:   }
 835: 
 836:   /**
 837:    * Returns the view at the specified <code>index</code>.
 838:    *
 839:    * @param index the index of the requested child view
 840:    *
 841:    * @return the view at the specified <code>index</code>
 842:    */
 843:   public View getView(int index)
 844:   {
 845:     View view = null;
 846:     synchronized(childStates)
 847:       {
 848:         if ((index >= 0) && (index < childStates.size()))
 849:           {
 850:             ChildState cs = (ChildState) childStates.get(index);
 851:             view = cs.getChildView();
 852:           }
 853:       }
 854:     return view;
 855:   }
 856: 
 857:   /**
 858:    * Returns the number of child views.
 859:    *
 860:    * @return the number of child views
 861:    */
 862:   public int getViewCount()
 863:   {
 864:     synchronized(childStates)
 865:     {
 866:       return childStates.size();
 867:     }
 868:   }
 869: 
 870:   /**
 871:    * Returns the view index of the child view that represents the specified
 872:    * model position.
 873:    *
 874:    * @param pos the model position for which we search the view index
 875:    * @param bias the bias
 876:    *
 877:    * @return the view index of the child view that represents the specified
 878:    *         model position
 879:    */
 880:   public int getViewIndex(int pos, Position.Bias bias)
 881:   {
 882:     int retVal = -1;
 883: 
 884:     if (bias == Position.Bias.Backward)
 885:       pos = Math.max(0, pos - 1);
 886: 
 887:     // TODO: A possible optimization would be to implement a binary search
 888:     // here.
 889:     int numChildren = childStates.size();
 890:     if (numChildren > 0)
 891:       {
 892:         for (int i = 0; i < numChildren; ++i)
 893:           {
 894:             View child = ((ChildState) childStates.get(i)).getChildView();
 895:             if (child.getStartOffset() <= pos && child.getEndOffset() > pos)
 896:               {
 897:                 retVal = i;
 898:                 break;
 899:               }
 900:           }
 901:       }
 902:     return retVal;
 903:   }
 904: 
 905:   /**
 906:    * Returns the top inset.
 907:    *
 908:    * @return the top inset
 909:    */
 910:   public float getTopInset()
 911:   {
 912:     return topInset;
 913:   }
 914: 
 915:   /**
 916:    * Sets the top inset.
 917:    *
 918:    * @param top the top inset
 919:    */
 920:   public void setTopInset(float top)
 921:   {
 922:     topInset = top;
 923:   }
 924: 
 925:   /**
 926:    * Returns the bottom inset.
 927:    *
 928:    * @return the bottom inset
 929:    */
 930:   public float getBottomInset()
 931:   {
 932:     return bottomInset;
 933:   }
 934: 
 935:   /**
 936:    * Sets the bottom inset.
 937:    *
 938:    * @param bottom the bottom inset
 939:    */
 940:   public void setBottomInset(float bottom)
 941:   {
 942:     bottomInset = bottom;
 943:   }
 944: 
 945:   /**
 946:    * Returns the left inset.
 947:    *
 948:    * @return the left inset
 949:    */
 950:   public float getLeftInset()
 951:   {
 952:     return leftInset;
 953:   }
 954: 
 955:   /**
 956:    * Sets the left inset.
 957:    *
 958:    * @param left the left inset
 959:    */
 960:   public void setLeftInset