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(float left)
 961:   {
 962:     leftInset = left;
 963:   }
 964: 
 965:   /**
 966:    * Returns the right inset.
 967:    *
 968:    * @return the right inset
 969:    */
 970:   public float getRightInset()
 971:   {
 972:     return rightInset;
 973:   }
 974: 
 975:   /**
 976:    * Sets the right inset.
 977:    *
 978:    * @param right the right inset
 979:    */
 980:   public void setRightInset(float right)
 981:   {
 982:     rightInset = right;
 983:   }
 984: 
 985:   /**
 986:    * Loads the child views of this view. This is triggered by
 987:    * {@link #setParent(View)}.
 988:    *
 989:    * @param f the view factory to build child views with
 990:    */
 991:   protected void loadChildren(ViewFactory f)
 992:   {
 993:     Element e = getElement();
 994:     int n = e.getElementCount();
 995:     if (n > 0)
 996:       {
 997:         View[] added = new View[n];
 998:         for (int i = 0; i < n; i++)
 999:           {
1000:             added[i] = f.create(e.getElement(i));
1001:           }
1002:         replace(0, 0, added);
1003:       }
1004:   }
1005: 
1006:   /**
1007:    * Returns the span along an axis that is taken up by the insets.
1008:    *
1009:    * @param axis the axis
1010:    *
1011:    * @return the span along an axis that is taken up by the insets
1012:    *
1013:    * @since 1.4
1014:    */
1015:   protected float getInsetSpan(int axis)
1016:   {
1017:     float span;
1018:     if (axis == X_AXIS)
1019:       span = leftInset + rightInset;
1020:     else
1021:       span = topInset + bottomInset;
1022:     return span;
1023:   }
1024: 
1025:   /**
1026:    * Sets the <code>estimatedMajorSpan</code> property that determines if
1027:    * the major span should be treated as beeing estimated.
1028:    *
1029:    * @param estimated if the major span should be treated as estimated or not
1030:    *
1031:    * @since 1.4
1032:    */
1033:   protected void setEstimatedMajorSpan(boolean estimated)
1034:   {
1035:     estimatedMajorSpan = estimated;
1036:   }
1037: 
1038:   /**
1039:    * Determines whether the major span should be treated as estimated or as
1040:    * beeing accurate.
1041:    *
1042:    * @return <code>true</code> if the major span should be treated as
1043:    *         estimated, <code>false</code> if the major span should be treated
1044:    *         as accurate
1045:    *
1046:    * @since 1.4
1047:    */
1048:   protected boolean getEstimatedMajorSpan()
1049:   {
1050:     return estimatedMajorSpan;
1051:   }
1052: 
1053:   /**
1054:    * Receives notification from the child states that the requirements along
1055:    * the minor axis have changed.
1056:    *
1057:    * @param cs the child state from which this notification is messaged
1058:    */
1059:   protected synchronized void minorRequirementChange(ChildState cs)
1060:   {
1061:     minorChanged = true;
1062:   }
1063: 
1064:   /**
1065:    * Receives notification from the child states that the requirements along
1066:    * the major axis have changed.
1067:    *
1068:    * @param cs the child state from which this notification is messaged
1069:    */
1070:   protected void majorRequirementChange(ChildState cs, float delta)
1071:   {
1072:     if (! estimatedMajorSpan)
1073:       majorSpan += delta;
1074:     majorChanged = true;
1075:   }
1076: 
1077:   /**
1078:    * Sets the parent for this view. This calls loadChildren if
1079:    * <code>parent</code> is not <code>null</code> and there have not been any
1080:    * child views initializes.
1081:    *
1082:    * @param parent the new parent view; <code>null</code> if this view is
1083:    *        removed from the view hierarchy
1084:    *
1085:    * @see View#setParent(View)
1086:    */
1087:   public void setParent(View parent)
1088:   {
1089:     super.setParent(parent);
1090:     if ((parent != null) && (getViewCount() == 0))
1091:       {
1092:         ViewFactory f = getViewFactory();
1093:         loadChildren(f);
1094:       }
1095:   }
1096: 
1097:   /**
1098:    * Sets the size of this view. This is ususally called before {@link #paint}
1099:    * is called to make sure the view has a valid layout.
1100:    *
1101:    * This implementation queues layout requests for every child view if the
1102:    * minor axis span has changed. (The major axis span is requested to never
1103:    * change for this view).
1104:    *
1105:    * @param width the width of the view
1106:    * @param height the height of the view
1107:    */
1108:   public void setSize(float width, float height)
1109:   {
1110:     float targetSpan;
1111:     if (majorAxis == X_AXIS)
1112:       targetSpan = height - getTopInset() - getBottomInset();
1113:     else
1114:       targetSpan = width - getLeftInset() - getRightInset();
1115: 
1116:     if (targetSpan != minorSpan)
1117:       {
1118:         minorSpan = targetSpan;
1119: 
1120:         int n = getViewCount();
1121:         LayoutQueue q = getLayoutQueue();
1122:         for (int i = 0; i < n; i++)
1123:           {
1124:             ChildState cs = getChildState(i);
1125:             cs.childSizeValid = false;
1126:             q.addTask(cs);
1127:           }
1128:         q.addTask(flushTask);
1129:     }
1130:   }
1131: 
1132:   /**
1133:    * Replaces child views with new child views.
1134:    *
1135:    * This creates ChildState objects for all the new views and adds layout
1136:    * requests for them to the layout queue.
1137:    *
1138:    * @param offset the offset at which to remove/insert
1139:    * @param length the number of child views to remove
1140:    * @param views the new child views to insert
1141:    */
1142:   public void replace(int offset, int length, View[] views)
1143:   {
1144:     synchronized(childStates)
1145:       {
1146:         LayoutQueue q = getLayoutQueue();
1147:         for (int i = 0; i < length; i++)
1148:           childStates.remove(offset);
1149: 
1150:         for (int i = views.length - 1; i >= 0; i--)
1151:           childStates.add(offset, createChildState(views[i]));
1152: 
1153:         // We need to go through the new child states _after_ they have been
1154:         // added to the childStates list, otherwise the layout tasks may find
1155:         // an incomplete child list. That means we have to loop through
1156:         // them again, but what else can we do?
1157:         if (views.length != 0)
1158:           {
1159:             for (int i = 0; i < views.length; i++)
1160:               {
1161:                 ChildState cs = (ChildState) childStates.get(i + offset);
1162:                 cs.getChildView().setParent(this);
1163:                 q.addTask(cs);
1164:               }
1165:             q.addTask(flushTask);
1166:           }
1167:       }
1168:   }
1169: 
1170:   /**
1171:    * Paints the view. This requests the {@link ChildLocator} to paint the views
1172:    * after setting the allocation on it.
1173:    *
1174:    * @param g the graphics context to use
1175:    * @param s the allocation for this view
1176:    */
1177:   public void paint(Graphics g, Shape s)
1178:   {
1179:     synchronized (locator)
1180:       {
1181:         locator.setAllocation(s);
1182:         locator.paintChildren(g);
1183:       }
1184:   }
1185: 
1186:   /**
1187:    * Returns the preferred span of this view along the specified layout axis.
1188:    *
1189:    * @return the preferred span of this view along the specified layout axis
1190:    */
1191:   public float getPreferredSpan(int axis)
1192:   {
1193:     float retVal;
1194:     if (majorAxis == axis)
1195:       retVal = majorSpan;
1196: 
1197:     else if (prefReq != null)
1198:       {
1199:         View child = prefReq.getChildView();
1200:         retVal = child.getPreferredSpan(axis);
1201:       }
1202: 
1203:     // If we have no layout information yet, then return insets + 30 as
1204:     // an estimation.
1205:     else
1206:       {
1207:         if (axis == X_AXIS)
1208:           retVal = getLeftInset() + getRightInset() + 30;
1209:         else
1210:           retVal = getTopInset() + getBottomInset() + 30;
1211:       }
1212:     return retVal;
1213:   }
1214: 
1215:   /**
1216:    * Maps a model location to view coordinates.
1217:    *
1218:    * @param pos the model location
1219:    * @param a the current allocation of this view
1220:    * @param b the bias
1221:    *
1222:    * @return the view allocation for the specified model location
1223:    */
1224:   public Shape modelToView(int pos, Shape a, Bias b)
1225:     throws BadLocationException
1226:   {
1227:     int index = getViewIndexAtPosition(pos, b);
1228:     Shape ca = locator.getChildAllocation(index, a);
1229: 
1230:     ChildState cs = getChildState(index);
1231:     synchronized (cs)
1232:       {
1233:         View cv = cs.getChildView();
1234:         Shape v = cv.modelToView(pos, ca, b);
1235:         return v;
1236:       }
1237:   }
1238: 
1239:   /**
1240:    * Maps view coordinates to a model location.
1241:    *
1242:    * @param x the x coordinate (relative to <code>a</code>)
1243:    * @param y the y coordinate (relative to <code>a</code>)
1244:    * @param b holds the bias of the model location on method exit
1245:    *
1246:    * @return the model location for the specified view location
1247:    */
1248:   public int viewToModel(float x, float y, Shape a, Bias[] b)
1249:   {
1250:     int pos;
1251:     int index;
1252:     Shape ca;
1253: 
1254:     synchronized (locator)
1255:       {
1256:         index = locator.getViewIndexAtPoint(x, y, a);
1257:         ca = locator.getChildAllocation(index, a);
1258:       }
1259: 
1260:     ChildState cs = getChildState(index);
1261:     synchronized (cs)
1262:       {
1263:         View v = cs.getChildView();
1264:         pos = v.viewToModel(x, y, ca, b);
1265:       }
1266:     return pos;
1267:   }
1268: 
1269:   /**
1270:    * Returns the child allocation for the child view with the specified
1271:    * <code>index</code>.
1272:    *
1273:    * @param index the index of the child view
1274:    * @param a the current allocation of this view
1275:    *
1276:    * @return the allocation of the child view
1277:    */
1278:   public Shape getChildAllocation(int index, Shape a)
1279:   {
1280:     Shape ca = locator.getChildAllocation(index, a);
1281:     return ca;
1282:   }
1283: 
1284:   /**
1285:    * Returns the maximum span of this view along the specified axis.
1286:    * This is implemented to return the <code>preferredSpan</code> for the
1287:    * major axis (that means the box can't be resized along the major axis) and
1288:    * {@link Short#MAX_VALUE} for the minor axis.
1289:    *
1290:    * @param axis the axis
1291:    *
1292:    * @return the maximum span of this view along the specified axis
1293:    */
1294:   public float getMaximumSpan(int axis)
1295:   {
1296:     float max;
1297:     if (axis == majorAxis)
1298:       max = getPreferredSpan(axis);
1299:     else
1300:       max = Short.MAX_VALUE;
1301:     return max;
1302:   }
1303: 
1304:   /**
1305:    * Returns the minimum span along the specified axis.
1306:    */
1307:   public float getMinimumSpan(int axis)
1308:   {
1309:     float min;
1310:     if (axis == majorAxis)
1311:       min = getPreferredSpan(axis);
1312:     else
1313:       {
1314:         if (minReq != null)
1315:           {
1316:             View child = minReq.getChildView();
1317:             min = child.getMinimumSpan(axis);
1318:           }
1319:         else
1320:           {
1321:             // No layout information yet. Return insets + 5 as some kind of
1322:             // estimation.
1323:             if (axis == X_AXIS)
1324:               min = getLeftInset() + getRightInset() + 5;
1325:             else
1326:               min = getTopInset() + getBottomInset() + 5;
1327:           }
1328:       }
1329:     return min;
1330:   }
1331: 
1332:   /**
1333:    * Receives notification that one of the child views has changed its
1334:    * layout preferences along one or both axis.
1335:    *
1336:    * This queues a layout request for that child view if necessary.
1337:    *
1338:    * @param view the view that has changed its preferences
1339:    * @param width <code>true</code> if the width preference has changed
1340:    * @param height <code>true</code> if the height preference has changed
1341:    */
1342:   public synchronized void preferenceChanged(View view, boolean width,
1343:                                              boolean height)
1344:   {
1345:     if (view == null)
1346:       getParent().preferenceChanged(this, width, height);
1347:     else
1348:       {
1349:         if (changing != null)
1350:           {
1351:             View cv = changing.getChildView();
1352:             if (cv == view)
1353:               {
1354:                 changing.preferenceChanged(width, height);
1355:                 return;
1356:               }
1357:           }
1358:         int index = getViewIndexAtPosition(view.getStartOffset(),
1359:                                            Position.Bias.Forward);
1360:         ChildState cs = getChildState(index);
1361:         cs.preferenceChanged(width, height);
1362:         LayoutQueue q = getLayoutQueue();
1363:         q.addTask(cs);
1364:         q.addTask(flushTask);
1365:       }
1366:   }
1367: 
1368:   /**
1369:    * Updates the layout for this view. This is implemented to trigger
1370:    * {@link ChildLocator#childChanged} for the changed view, if there is
1371:    * any.
1372:    *
1373:    * @param ec the element change, may be <code>null</code> if there were
1374:    *        no changes to the element of this view
1375:    * @param e the document event
1376:    * @param a the current allocation of this view
1377:    */
1378:   protected void updateLayout(DocumentEvent.ElementChange ec,
1379:                               DocumentEvent e, Shape a)
1380:   {
1381:     if (ec != null)
1382:       {
1383:         int index = Math.max(ec.getIndex() - 1, 0);
1384:         ChildState cs = getChildState(index);
1385:         locator.childChanged(cs);
1386:       }
1387:   }
1388: 
1389: 
1390:   /**
1391:    * Returns the <code>ChildState</code> object associated with the child view
1392:    * at the specified <code>index</code>.
1393:    *
1394:    * @param index the index of the child view for which to query the state
1395:    *
1396:    * @return the child state for the specified child view
1397:    */
1398:   protected ChildState getChildState(int index) {
1399:     synchronized (childStates)
1400:       {
1401:         return (ChildState) childStates.get(index);
1402:       }
1403:   }
1404: 
1405:   /**
1406:    * Returns the <code>LayoutQueue</code> used for layouting the box view.
1407:    * This simply returns {@link LayoutQueue#getDefaultQueue()}.
1408:    *
1409:    * @return the <code>LayoutQueue</code> used for layouting the box view
1410:    */
1411:   protected LayoutQueue getLayoutQueue()
1412:   {
1413:     return LayoutQueue.getDefaultQueue();
1414:   }
1415: 
1416:   /**
1417:    * Returns the child view index of the view that represents the specified
1418:    * position in the document model.
1419:    *
1420:    * @param pos the position in the model
1421:    * @param b the bias
1422:    *
1423:    * @return the child view index of the view that represents the specified
1424:    *         position in the document model
1425:    */
1426:   protected synchronized int getViewIndexAtPosition(int pos, Position.Bias b)
1427:   {
1428:     if (b == Position.Bias.Backward)
1429:       pos = Math.max(0, pos - 1);
1430:     Element elem = getElement();
1431:     return elem.getElementIndex(pos);
1432:   }
1433: 
1434:   /**
1435:    * Creates a <code>ChildState</code> object for the specified view.
1436:    *
1437:    * @param v the view for which to create a child state object
1438:    *
1439:    * @return the created child state
1440:    */
1441:   protected ChildState createChildState(View v)
1442:   {
1443:     return new ChildState(v);
1444:   }
1445: 
1446:   /**
1447:    * Flushes the requirements changes upwards to the parent view. This is
1448:    * called from the layout thread.
1449:    */
1450:   protected synchronized void flushRequirementChanges()
1451:   {
1452:     if (majorChanged || minorChanged)
1453:       {
1454:         View p = getParent();
1455:         if (p != null)
1456:           {
1457:             boolean horizontal;
1458:             boolean vertical;
1459:             if (majorAxis == X_AXIS)
1460:               {
1461:                 horizontal = majorChanged;
1462:                 vertical = minorChanged;
1463:               }
1464:             else
1465:               {
1466:                 vertical = majorChanged;
1467:                 horizontal = minorChanged;
1468:               }
1469: 
1470:             p.preferenceChanged(this, horizontal, vertical);
1471:             majorChanged = false;
1472:             minorChanged = false;
1473: 
1474:             Component c = getContainer();
1475:             if (c != null)
1476:               c.repaint();
1477:           }
1478:       }
1479:   }
1480: }