Source for javax.swing.text.FlowView

   1: /* FlowView.java -- A composite View
   2:    Copyright (C) 2005  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: 
  46: import javax.swing.SizeRequirements;
  47: import javax.swing.event.DocumentEvent;
  48: 
  49: /**
  50:  * A <code>View</code> that can flows it's children into it's layout space.
  51:  *
  52:  * The <code>FlowView</code> manages a set of logical views (that are
  53:  * the children of the {@link #layoutPool} field). These are translated
  54:  * at layout time into a set of physical views. These are the views that
  55:  * are managed as the real child views. Each of these child views represents
  56:  * a row and are laid out within a box using the superclasses behaviour.
  57:  * The concrete implementation of the rows must be provided by subclasses.
  58:  *
  59:  * @author Roman Kennke (roman@kennke.org)
  60:  */
  61: public abstract class FlowView extends BoxView
  62: {
  63:   /**
  64:    * A strategy for translating the logical views of a <code>FlowView</code>
  65:    * into the real views.
  66:    */
  67:   public static class FlowStrategy
  68:   {
  69:     /**
  70:      * Creates a new instance of <code>FlowStragegy</code>.
  71:      */
  72:     public FlowStrategy()
  73:     {
  74:       // Nothing to do here.
  75:     }
  76: 
  77:     /**
  78:      * Receives notification from a <code>FlowView</code> that some content
  79:      * has been inserted into the document at a location that the
  80:      * <code>FlowView</code> is responsible for.
  81:      *
  82:      * The default implementation simply calls {@link #layout}.
  83:      *
  84:      * @param fv the flow view that sends the notification
  85:      * @param e the document event describing the change
  86:      * @param alloc the current allocation of the flow view
  87:      */
  88:     public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
  89:     {
  90:       if (alloc == null)
  91:         {
  92:           fv.layoutChanged(X_AXIS);
  93:           fv.layoutChanged(Y_AXIS);
  94:         }
  95:       else
  96:         {
  97:           Component host = fv.getContainer();
  98:           if (host != null)
  99:             host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
 100:         }
 101:     }
 102: 
 103:     /**
 104:      * Receives notification from a <code>FlowView</code> that some content
 105:      * has been removed from the document at a location that the
 106:      * <code>FlowView</code> is responsible for.
 107:      *
 108:      * The default implementation simply calls {@link #layout}.
 109:      *
 110:      * @param fv the flow view that sends the notification
 111:      * @param e the document event describing the change
 112:      * @param alloc the current allocation of the flow view
 113:      */
 114:     public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
 115:     {
 116:       if (alloc == null)
 117:         {
 118:           fv.layoutChanged(X_AXIS);
 119:           fv.layoutChanged(Y_AXIS);
 120:         }
 121:       else
 122:         {
 123:           Component host = fv.getContainer();
 124:           if (host != null)
 125:             host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
 126:         }
 127:     }
 128: 
 129:     /**
 130:      * Receives notification from a <code>FlowView</code> that some attributes
 131:      * have changed in the document at a location that the
 132:      * <code>FlowView</code> is responsible for.
 133:      *
 134:      * The default implementation simply calls {@link #layout}.
 135:      *
 136:      * @param fv the flow view that sends the notification
 137:      * @param e the document event describing the change
 138:      * @param alloc the current allocation of the flow view
 139:      */
 140:     public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
 141:     {
 142:       if (alloc == null)
 143:         {
 144:           fv.layoutChanged(X_AXIS);
 145:           fv.layoutChanged(Y_AXIS);
 146:         }
 147:       else
 148:         {
 149:           Component host = fv.getContainer();
 150:           if (host != null)
 151:             host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
 152:         }
 153:     }
 154: 
 155:     /**
 156:      * Returns the logical view of the managed <code>FlowView</code>.
 157:      *
 158:      * @param fv the flow view for which to return the logical view
 159:      *
 160:      * @return the logical view of the managed <code>FlowView</code>
 161:      */
 162:     protected View getLogicalView(FlowView fv)
 163:     {
 164:       return fv.layoutPool;
 165:     }
 166: 
 167:     /**
 168:      * Performs the layout for the whole view. By default this rebuilds
 169:      * all the physical views from the logical views of the managed FlowView.
 170:      *
 171:      * This is called by {@link FlowView#layout} to update the layout of
 172:      * the view.
 173:      *
 174:      * @param fv the flow view for which we perform the layout
 175:      */
 176:     public void layout(FlowView fv)
 177:     {
 178:       int start = fv.getStartOffset();
 179:       int end = fv.getEndOffset();
 180: 
 181:       // Preserve the views from the logical view from beeing removed.
 182:       View lv = getLogicalView(fv);
 183:       int viewCount = lv.getViewCount();
 184:       for (int i = 0; i < viewCount; i++)
 185:         {
 186:           View v = lv.getView(i);
 187:           v.setParent(lv);
 188:         }
 189: 
 190:       // Then remove all views from the flow view.
 191:       fv.removeAll();
 192: 
 193:       for (int rowIndex = 0; start < end; rowIndex++)
 194:         {
 195:           View row = fv.createRow();
 196:           fv.append(row);
 197:           int next = layoutRow(fv, rowIndex, start);
 198:           if (row.getViewCount() == 0)
 199:             {
 200:               row.append(createView(fv, start, Integer.MAX_VALUE, rowIndex));
 201:               next = row.getEndOffset();
 202:             }
 203:           if (start < next)
 204:             start = next;
 205:           else
 206:             assert false: "May not happen";
 207:         }
 208:     }
 209: 
 210:     /**
 211:      * Lays out one row of the flow view. This is called by {@link #layout} to
 212:      * fill one row with child views until the available span is exhausted. The
 213:      * default implementation fills the row by calling
 214:      * {@link #createView(FlowView, int, int, int)} until the available space is
 215:      * exhausted, a forced break is encountered or there are no more views in
 216:      * the logical view. If the available space is exhausted,
 217:      * {@link #adjustRow(FlowView, int, int, int)} is called to fit the row into
 218:      * the available span.
 219:      * 
 220:      * @param fv the flow view for which we perform the layout
 221:      * @param rowIndex the index of the row
 222:      * @param pos the model position for the beginning of the row
 223:      * @return the start position of the next row
 224:      */
 225:     protected int layoutRow(FlowView fv, int rowIndex, int pos)
 226:     {
 227:       View row = fv.getView(rowIndex);
 228:       int axis = fv.getFlowAxis();
 229:       int span = fv.getFlowSpan(rowIndex);
 230:       int x = fv.getFlowStart(rowIndex);
 231:       int end = fv.getEndOffset();
 232: 
 233:       // Needed for adjusting indentation in adjustRow().
 234:       int preX = x;
 235:       int availableSpan = span;
 236: 
 237:       TabExpander tabExp = fv instanceof TabExpander ? (TabExpander) fv : null;
 238: 
 239:       boolean forcedBreak = false;
 240:       while (pos < end && span >= 0)
 241:         {
 242:           View view = createView(fv, pos, span, rowIndex);
 243:           if (view == null
 244:               || (span == 0 && view.getPreferredSpan(axis) > 0))
 245:             break;
 246: 
 247:           int viewSpan;
 248:           if (axis == X_AXIS && view instanceof TabableView)
 249:             viewSpan = (int) ((TabableView) view).getTabbedSpan(x, tabExp);
 250:           else
 251:             viewSpan = (int) view.getPreferredSpan(axis);
 252: 
 253:           // Break if the line if the view does not fit in this row or the
 254:           // line just must be broken.
 255:           int breakWeight = view.getBreakWeight(axis, pos, span);
 256:           if (breakWeight >= ForcedBreakWeight)
 257:             {
 258:               int rowViewCount = row.getViewCount();
 259:               if (rowViewCount > 0)
 260:                 {
 261:                   view = view.breakView(axis, pos, x, span);
 262:                   if (view != null)
 263:                     {
 264:                       if (axis == X_AXIS && view instanceof TabableView)
 265:                         viewSpan =
 266:                           (int) ((TabableView) view).getTabbedSpan(x, tabExp);
 267:                       else
 268:                         viewSpan = (int) view.getPreferredSpan(axis);
 269:                     }
 270:                   else
 271:                     viewSpan = 0;
 272:                 }
 273:               forcedBreak = true;
 274:             }
 275:           span -= viewSpan;
 276:           x += viewSpan;
 277:           if (view != null)
 278:             {
 279:               row.append(view);
 280:               pos = view.getEndOffset();
 281:             }
 282:           if (forcedBreak)
 283:             break;
 284:         }
 285: 
 286:       if (span < 0)
 287:         adjustRow(fv, rowIndex, availableSpan, preX);
 288:       else if (row.getViewCount() == 0)
 289:         {
 290:           View view = createView(fv, pos, Integer.MAX_VALUE, rowIndex);
 291:           row.append(view);
 292:         }
 293:       return row.getEndOffset();
 294:     }
 295: 
 296:     /**
 297:      * Creates physical views that form the rows of the flow view. This
 298:      * can be an entire view from the logical view (if it fits within the
 299:      * available span), a fragment of such a view (if it doesn't fit in the
 300:      * available span and can be broken down) or <code>null</code> (if it does
 301:      * not fit in the available span and also cannot be broken down).
 302:      *
 303:      * The default implementation fetches the logical view at the specified
 304:      * <code>startOffset</code>. If that view has a different startOffset than
 305:      * specified in the argument, a fragment is created using
 306:      * {@link View#createFragment(int, int)} that has the correct startOffset
 307:      * and the logical view's endOffset.
 308:      *
 309:      * @param fv the flow view
 310:      * @param startOffset the start offset for the view to be created
 311:      * @param spanLeft the available span
 312:      * @param rowIndex the index of the row
 313:      *
 314:      * @return a view to fill the row with, or <code>null</code> if there
 315:      *         is no view or view fragment that fits in the available span
 316:      */
 317:     protected View createView(FlowView fv, int startOffset, int spanLeft,
 318:                               int rowIndex)
 319:     {
 320:        View logicalView = getLogicalView(fv);
 321:        int index = logicalView.getViewIndex(startOffset,
 322:                                             Position.Bias.Forward);
 323:        View retVal = logicalView.getView(index);
 324:        if (retVal.getStartOffset() != startOffset)
 325:          retVal = retVal.createFragment(startOffset, retVal.getEndOffset());
 326:        return retVal;
 327:     }
 328: 
 329:     /**
 330:      * Tries to adjust the specified row to fit within the desired span. The
 331:      * default implementation iterates through the children of the specified
 332:      * row to find the view that has the highest break weight and - if there
 333:      * is more than one view with such a break weight - which is nearest to
 334:      * the end of the row. If there is such a view that has a break weight >
 335:      * {@link View#BadBreakWeight}, this view is broken using the
 336:      * {@link View#breakView(int, int, float, float)} method and this view and
 337:      * all views after the now broken view are replaced by the broken view.
 338:      *
 339:      * @param fv the flow view
 340:      * @param rowIndex the index of the row to be adjusted
 341:      * @param desiredSpan the layout span
 342:      * @param x the X location at which the row starts
 343:      */
 344:     protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) {
 345:       // Determine the last view that has the highest break weight.
 346:       int axis = fv.getFlowAxis();
 347:       View row = fv.getView(rowIndex);
 348:       int count = row.getViewCount();
 349:       int breakIndex = -1;
 350:       int breakWeight = BadBreakWeight;
 351:       int breakSpan = 0;
 352:       int currentSpan = 0;
 353:       for (int i = 0; i < count; ++i)
 354:         {
 355:           View view = row.getView(i);
 356:           int spanLeft = desiredSpan - currentSpan;
 357:           int weight = view.getBreakWeight(axis, x + currentSpan, spanLeft);
 358:           if (weight >= breakWeight && weight > BadBreakWeight)
 359:             {
 360:               breakIndex = i;
 361:               breakSpan = currentSpan;
 362:               breakWeight = weight;
 363:               if (weight >= ForcedBreakWeight)
 364:                 // Don't search further.
 365:                 break;
 366:             }
 367:           currentSpan += view.getPreferredSpan(axis);
 368:         }
 369: 
 370:       // If there is a potential break location found, break the row at
 371:       // this location.
 372:       if (breakIndex >= 0)
 373:         {
 374:           int spanLeft = desiredSpan - breakSpan;
 375:           View toBeBroken = row.getView(breakIndex);
 376:           View brokenView = toBeBroken.breakView(axis,
 377:                                                  toBeBroken.getStartOffset(),
 378:                                                  x + breakSpan, spanLeft);
 379:           View lv = getLogicalView(fv);
 380:           for (int i = breakIndex; i < count; i++)
 381:             {
 382:               View tmp = row.getView(i);
 383:               if (contains(lv, tmp))
 384:                 tmp.setParent(lv);
 385:               else if (tmp.getViewCount() > 0)
 386:                 reparent(tmp, lv);
 387:             }
 388:           row.replace(breakIndex, count - breakIndex,
 389:                       new View[]{ brokenView });
 390:         }
 391: 
 392:     }
 393: 
 394:     /**
 395:      * Helper method to determine if one view contains another as child.
 396:      */
 397:     private boolean contains(View view, View child)
 398:     {
 399:       boolean ret = false;
 400:       int n  = view.getViewCount();
 401:       for (int i = 0; i < n && ret == false; i++)
 402:         {
 403:           if (view.getView(i) == child)
 404:             ret = true;
 405:         }
 406:       return ret;
 407:     }
 408: 
 409:     /**
 410:      * Helper method that reparents the <code>view</code> and all of its
 411:      * decendents to the <code>parent</code> (the logical view).
 412:      *
 413:      * @param view the view to reparent
 414:      * @param parent the new parent
 415:      */
 416:     private void reparent(View view, View parent)
 417:     {
 418:       int n = view.getViewCount();
 419:       for (int i = 0; i < n; i++)
 420:         {
 421:           View tmp = view.getView(i);
 422:           if (contains(parent, tmp))
 423:             tmp.setParent(parent);
 424:           else
 425:             reparent(tmp, parent);
 426:         }
 427:     }
 428:   }
 429: 
 430:   /**
 431:    * This special subclass of <code>View</code> is used to represent
 432:    * the logical representation of this view. It does not support any
 433:    * visual representation, this is handled by the physical view implemented
 434:    * in the <code>FlowView</code>.
 435:    */
 436:   class LogicalView extends CompositeView
 437:   {
 438:     /**
 439:      * Creates a new LogicalView instance.
 440:      */
 441:     LogicalView(Element el)
 442:     {
 443:       super(el);
 444:     }
 445: 
 446:     /**
 447:      * Overridden to return the attributes of the parent
 448:      * (== the FlowView instance).
 449:      */
 450:     public AttributeSet getAttributes()
 451:     {
 452:       View p = getParent();
 453:       return p != null ? p.getAttributes() : null;
 454:     }
 455: 
 456:     protected void childAllocation(int index, Rectangle a)
 457:     {
 458:       // Nothing to do here (not visual).
 459:     }
 460: 
 461:     protected View getViewAtPoint(int x, int y, Rectangle r)
 462:     {
 463:       // Nothing to do here (not visual).
 464:       return null;
 465:     }
 466: 
 467:     protected boolean isAfter(int x, int y, Rectangle r)
 468:     {
 469:       // Nothing to do here (not visual).
 470:       return false;
 471:     }
 472: 
 473:     protected boolean isBefore(int x, int y, Rectangle r)
 474:     {
 475:       // Nothing to do here (not visual).
 476:       return false;
 477:     }
 478: 
 479:     public float getPreferredSpan(int axis)
 480:     {
 481:       float max = 0;
 482:       float pref = 0;
 483:       int n = getViewCount();
 484:       for (int i = 0; i < n; i++)
 485:         {
 486:           View v = getView(i);
 487:           pref += v.getPreferredSpan(axis);
 488:           if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE)
 489:               >= ForcedBreakWeight)
 490:             {
 491:               max = Math.max(max, pref);
 492:               pref = 0;
 493:             }
 494:         }
 495:       max = Math.max(max, pref);
 496:       return max;
 497:     }
 498: 
 499:     public float getMinimumSpan(int axis)
 500:     {
 501:       float max = 0;
 502:       float min = 0;
 503:       boolean wrap = true;
 504:       int n = getViewCount();
 505:       for (int i = 0; i < n; i++)
 506:         {
 507:           View v = getView(i);
 508:           if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE)
 509:               == BadBreakWeight)
 510:             {
 511:               min += v.getPreferredSpan(axis);
 512:               wrap = false;
 513:             }
 514:           else if (! wrap)
 515:             {
 516:               max = Math.max(min, max);
 517:               wrap = true;
 518:               min = 0;
 519:             }
 520:         }
 521:       max = Math.max(max, min);
 522:       return max;
 523:     }
 524: 
 525:     public void paint(Graphics g, Shape s)
 526:     {
 527:       // Nothing to do here (not visual).
 528:     }
 529: 
 530:     /**
 531:      * Overridden to handle possible leaf elements.
 532:      */
 533:     protected void loadChildren(ViewFactory f)
 534:     {
 535:       Element el = getElement();
 536:       if (el.isLeaf())
 537:         {
 538:           View v = new LabelView(el);
 539:           append(v);
 540:         }
 541:       else
 542:         super.loadChildren(f);
 543:     }
 544: 
 545:     /**
 546:      * Overridden to reparent the children to this logical view, in case
 547:      * they have been parented by a row.
 548:      */
 549:     protected void forwardUpdateToView(View v, DocumentEvent e, Shape a,
 550:                                        ViewFactory f)
 551:     {
 552:       v.setParent(this);
 553:       super.forwardUpdateToView(v, e, a, f);
 554:     }
 555: 
 556:     /**
 557:      * Overridden to handle possible leaf element.
 558:      */
 559:     protected int getViewIndexAtPosition(int pos)
 560:     {
 561:       int index = 0;
 562:       if (! getElement().isLeaf())
 563:         index = super.getViewIndexAtPosition(pos);
 564:       return index;
 565:     }
 566:   }
 567: 
 568:   /**
 569:    * The shared instance of FlowStrategy.
 570:    */
 571:   static final FlowStrategy sharedStrategy = new FlowStrategy();
 572: 
 573:   /**
 574:    * The span of the <code>FlowView</code> that should be flowed.
 575:    */
 576:   protected int layoutSpan;
 577: 
 578:   /**
 579:    * Represents the logical child elements of this view, encapsulated within
 580:    * one parent view (an instance of a package private <code>LogicalView</code>
 581:    * class). These will be translated to a set of real views that are then
 582:    * displayed on screen. This translation is performed by the inner class
 583:    * {@link FlowStrategy}.
 584:    */
 585:   protected View layoutPool;
 586: 
 587:   /**
 588:    * The <code>FlowStrategy</code> to use for translating between the
 589:    * logical and physical view.
 590:    */
 591:   protected FlowStrategy strategy;
 592: 
 593:   /**
 594:    * Creates a new <code>FlowView</code> for the given
 595:    * <code>Element</code> and <code>axis</code>.
 596:    *
 597:    * @param element the element that is rendered by this FlowView
 598:    * @param axis the axis along which the view is tiled, either
 599:    *        <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>, the flow
 600:    *        axis is orthogonal to this one
 601:    */
 602:   public FlowView(Element element, int axis)
 603:   {
 604:     super(element, axis);
 605:     strategy = sharedStrategy;
 606:     layoutSpan = Short.MAX_VALUE;
 607:   }
 608: 
 609:   /**
 610:    * Returns the axis along which the view should be flowed. This is
 611:    * orthogonal to the axis along which the boxes are tiled.
 612:    *
 613:    * @return the axis along which the view should be flowed
 614:    */
 615:   public int getFlowAxis()
 616:   {
 617:     int axis = getAxis();
 618:     int flowAxis;
 619:  
 620:     if (axis == X_AXIS)
 621:       flowAxis = Y_AXIS;
 622:     else
 623:       flowAxis = X_AXIS;
 624: 
 625:     return flowAxis;
 626: 
 627:   }
 628: 
 629:   /**
 630:    * Returns the span of the flow for the specified child view. A flow
 631:    * layout can be shaped by providing different span values for different
 632:    * child indices. The default implementation returns the entire available
 633:    * span inside the view.
 634:    *
 635:    * @param index the index of the child for which to return the span
 636:    *
 637:    * @return the span of the flow for the specified child view
 638:    */
 639:   public int getFlowSpan(int index)
 640:   {
 641:     return layoutSpan;
 642:   }
 643: 
 644:   /**
 645:    * Returns the location along the flow axis where the flow span starts
 646:    * given a child view index. The flow can be shaped by providing
 647:    * different values here.
 648:    *
 649:    * @param index the index of the child for which to return the flow location
 650:    *
 651:    * @return the location along the flow axis where the flow span starts
 652:    */
 653:   public int getFlowStart(int index)
 654:   {
 655:     return 0;
 656:   }
 657: 
 658:   /**
 659:    * Creates a new view that represents a row within a flow.
 660:    *
 661:    * @return a view for a new row
 662:    */
 663:   protected abstract View createRow();
 664: 
 665:   /**
 666:    * Loads the children of this view. The <code>FlowView</code> does not
 667:    * directly load its children. Instead it creates a logical view
 668:    * ({@link #layoutPool}) which is filled by the logical child views.
 669:    * The real children are created at layout time and each represent one
 670:    * row.
 671:    *
 672:    * This method is called by {@link View#setParent} in order to initialize
 673:    * the view.
 674:    *
 675:    * @param vf the view factory to use for creating the child views
 676:    */
 677:   protected void loadChildren(ViewFactory vf)
 678:   {
 679:     if (layoutPool == null)
 680:       {
 681:         layoutPool = new LogicalView(getElement());
 682:       }
 683:     layoutPool.setParent(this);
 684:     // Initialize the flow strategy.
 685:     strategy.insertUpdate(this, null, null);
 686:   }
 687: 
 688:   /**
 689:    * Performs the layout of this view. If the span along the flow axis changed,
 690:    * this first calls {@link FlowStrategy#layout} in order to rebuild the
 691:    * rows of this view. Then the superclass's behaviour is called to arrange
 692:    * the rows within the box.
 693:    *
 694:    * @param width the width of the view
 695:    * @param height the height of the view
 696:    */
 697:   protected void layout(int width, int height)
 698:   {
 699:     int flowAxis = getFlowAxis();
 700:     int span; 
 701:     if (flowAxis == X_AXIS)
 702:       span = (int) width;
 703:     else
 704:       span = (int) height;
 705: 
 706:     if (layoutSpan != span)
 707:       {
 708:         layoutChanged(flowAxis);
 709:         layoutChanged(getAxis());
 710:         layoutSpan = span;
 711:       }
 712: 
 713:     if (! isLayoutValid(flowAxis))
 714:       {
 715:         int axis = getAxis();
 716:         int oldSpan = axis == X_AXIS ? getWidth() : getHeight();
 717:         strategy.layout(this);
 718:         int newSpan = (int) getPreferredSpan(axis);
 719:         if (oldSpan != newSpan)
 720:           {
 721:             View parent = getParent();
 722:             if (parent != null)
 723:               parent.preferenceChanged(this, axis == X_AXIS, axis == Y_AXIS);
 724:           }
 725:       }
 726: 
 727:     super.layout(width, height);
 728:   }
 729: 
 730:   /**
 731:    * Receice notification that some content has been inserted in the region
 732:    * that this view is responsible for. This calls
 733:    * {@link FlowStrategy#insertUpdate}.
 734:    *
 735:    * @param changes the document event describing the changes
 736:    * @param a the current allocation of the view
 737:    * @param vf the view factory that is used for creating new child views
 738:    */
 739:   public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
 740:   {
 741:     // First we must send the insertUpdate to the logical view so it can
 742:     // be updated accordingly.
 743:     layoutPool.insertUpdate(changes, a, vf);
 744:     strategy.insertUpdate(this, changes, getInsideAllocation(a));
 745:   }
 746: 
 747:   /**
 748:    * Receice notification that some content has been removed from the region
 749:    * that this view is responsible for. This calls
 750:    * {@link FlowStrategy#removeUpdate}.
 751:    *
 752:    * @param changes the document event describing the changes
 753:    * @param a the current allocation of the view
 754:    * @param vf the view factory that is used for creating new child views
 755:    */
 756:   public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
 757:   {
 758:     layoutPool.removeUpdate(changes, a, vf);
 759:     strategy.removeUpdate(this, changes, getInsideAllocation(a));
 760:   }
 761: 
 762:   /**
 763:    * Receice notification that some attributes changed in the region
 764:    * that this view is responsible for. This calls
 765:    * {@link FlowStrategy#changedUpdate}.
 766:    *
 767:    * @param changes the document event describing the changes
 768:    * @param a the current allocation of the view
 769:    * @param vf the view factory that is used for creating new child views
 770:    */
 771:   public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
 772:   {
 773:     layoutPool.changedUpdate(changes, a, vf);
 774:     strategy.changedUpdate(this, changes, getInsideAllocation(a));
 775:   }
 776: 
 777:   /**
 778:    * Returns the index of the child <code>View</code> for the given model
 779:    * position.
 780:    *
 781:    * This is implemented to iterate over the children of this
 782:    * view (the rows) and return the index of the first view that contains
 783:    * the given position.
 784:    *
 785:    * @param pos the model position for whicht the child <code>View</code> is
 786:    *        queried
 787:    *
 788:    * @return the index of the child <code>View</code> for the given model
 789:    *         position
 790:    */
 791:   protected int getViewIndexAtPosition(int pos)
 792:   {
 793:     // First make sure we have a valid layout.
 794:     if (!isAllocationValid())
 795:       layout(getWidth(), getHeight());
 796: 
 797:     int count = getViewCount();
 798:     int result = -1;
 799: 
 800:     for (int i = 0; i < count; ++i)
 801:       {
 802:         View child = getView(i);
 803:         int start = child.getStartOffset();
 804:         int end = child.getEndOffset();
 805:         if (start <= pos && end > pos)
 806:           {
 807:             result = i;
 808:             break;
 809:           }
 810:       }
 811:     return result;
 812:   }
 813: 
 814:   /**
 815:    * Calculates the size requirements of this <code>BoxView</code> along
 816:    * its minor axis, that is the axis opposite to the axis specified in the
 817:    * constructor.
 818:    *
 819:    * This is overridden and forwards the request to the logical view.
 820:    *
 821:    * @param axis the axis that is examined
 822:    * @param r the <code>SizeRequirements</code> object to hold the result,
 823:    *        if <code>null</code>, a new one is created
 824:    *
 825:    * @return the size requirements for this <code>BoxView</code> along
 826:    *         the specified axis
 827:    */
 828:   protected SizeRequirements calculateMinorAxisRequirements(int axis,
 829:                                                             SizeRequirements r)
 830:   {
 831:     SizeRequirements res = r;
 832:     if (res == null)
 833:       res = new SizeRequirements();
 834:     res.minimum = (int) layoutPool.getMinimumSpan(axis);
 835:     res.preferred = Math.max(res.minimum,
 836:                              (int) layoutPool.getPreferredSpan(axis));
 837:     res.maximum = Integer.MAX_VALUE;
 838:     res.alignment = 0.5F;
 839:     return res;
 840:   }
 841: }