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.Container;
  42: import java.awt.Graphics;
  43: import java.awt.Rectangle;
  44: import java.awt.Shape;
  45: import java.util.Iterator;
  46: import java.util.Vector;
  47: 
  48: import javax.swing.SwingConstants;
  49: import javax.swing.event.DocumentEvent;
  50: 
  51: /**
  52:  * A <code>View</code> that can flows it's children into it's layout space.
  53:  *
  54:  * The <code>FlowView</code> manages a set of logical views (that are
  55:  * the children of the {@link #layoutPool} field). These are translated
  56:  * at layout time into a set of physical views. These are the views that
  57:  * are managed as the real child views. Each of these child views represents
  58:  * a row and are laid out within a box using the superclasses behaviour.
  59:  * The concrete implementation of the rows must be provided by subclasses.
  60:  *
  61:  * @author Roman Kennke (roman@kennke.org)
  62:  */
  63: public abstract class FlowView extends BoxView
  64: {
  65:   /**
  66:    * A strategy for translating the logical views of a <code>FlowView</code>
  67:    * into the real views.
  68:    */
  69:   public static class FlowStrategy
  70:   {
  71:     /**
  72:      * Creates a new instance of <code>FlowStragegy</code>.
  73:      */
  74:     public FlowStrategy()
  75:     {
  76:       // Nothing to do here.
  77:     }
  78: 
  79:     /**
  80:      * Receives notification from a <code>FlowView</code> that some content
  81:      * has been inserted into the document at a location that the
  82:      * <code>FlowView</code> is responsible for.
  83:      *
  84:      * The default implementation simply calls {@link #layout}.
  85:      *
  86:      * @param fv the flow view that sends the notification
  87:      * @param e the document event describing the change
  88:      * @param alloc the current allocation of the flow view
  89:      */
  90:     public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
  91:     {
  92:       layout(fv);
  93:     }
  94: 
  95:     /**
  96:      * Receives notification from a <code>FlowView</code> that some content
  97:      * has been removed from the document at a location that the
  98:      * <code>FlowView</code> is responsible for.
  99:      *
 100:      * The default implementation simply calls {@link #layout}.
 101:      *
 102:      * @param fv the flow view that sends the notification
 103:      * @param e the document event describing the change
 104:      * @param alloc the current allocation of the flow view
 105:      */
 106:     public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
 107:     {
 108:       layout(fv);
 109:     }
 110: 
 111:     /**
 112:      * Receives notification from a <code>FlowView</code> that some attributes
 113:      * have changed in the document at a location that the
 114:      * <code>FlowView</code> is responsible for.
 115:      *
 116:      * The default implementation simply calls {@link #layout}.
 117:      *
 118:      * @param fv the flow view that sends the notification
 119:      * @param e the document event describing the change
 120:      * @param alloc the current allocation of the flow view
 121:      */
 122:     public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
 123:     {
 124:       layout(fv);
 125:     }
 126: 
 127:     /**
 128:      * Returns the logical view of the managed <code>FlowView</code>.
 129:      *
 130:      * @param fv the flow view for which to return the logical view
 131:      *
 132:      * @return the logical view of the managed <code>FlowView</code>
 133:      */
 134:     public View getLogicalView(FlowView fv)
 135:     {
 136:       return fv.layoutPool;
 137:     }
 138: 
 139:     /**
 140:      * Performs the layout for the whole view. By default this rebuilds
 141:      * all the physical views from the logical views of the managed FlowView.
 142:      *
 143:      * This is called by {@link FlowView#layout} to update the layout of
 144:      * the view.
 145:      *
 146:      * @param fv the flow view for which we perform the layout
 147:      */
 148:     public void layout(FlowView fv)
 149:     {
 150:       fv.removeAll();
 151:       Element el = fv.getElement();
 152: 
 153:       int rowStart = el.getStartOffset();
 154:       int end = el.getEndOffset();
 155:       int rowIndex = 0;
 156:       while (rowStart >= 0 && rowStart < end)
 157:         {
 158:           View row = fv.createRow();
 159:           fv.append(row);
 160:           rowStart = layoutRow(fv, rowIndex, rowStart);
 161:           rowIndex++;
 162:         }
 163:     }
 164: 
 165:     /**
 166:      * Lays out one row of the flow view. This is called by {@link #layout}
 167:      * to fill one row with child views until the available span is exhausted.
 168:      *
 169:      * @param fv the flow view for which we perform the layout
 170:      * @param rowIndex the index of the row
 171:      * @param pos the start position for the row
 172:      *
 173:      * @return the start position of the next row
 174:      */
 175:     protected int layoutRow(FlowView fv, int rowIndex, int pos)
 176:     {
 177:       int spanLeft = fv.getFlowSpan(rowIndex);
 178:       if (spanLeft <= 0)
 179:         return -1;
 180: 
 181:       int offset = pos;
 182:       View row = fv.getView(rowIndex);
 183:       int flowAxis = fv.getFlowAxis();
 184: 
 185:       while (spanLeft > 0)
 186:         {
 187:           View child = createView(fv, offset, spanLeft, rowIndex);
 188:           if (child == null)
 189:             {
 190:               offset = -1;
 191:               break;
 192:             }
 193: 
 194:           int span = (int) child.getPreferredSpan(flowAxis);
 195:           if (span > spanLeft)
 196:             {
 197:               offset = -1;
 198:               break;
 199:             }
 200: 
 201:           row.append(child);
 202:           spanLeft -= span;
 203:           offset = child.getEndOffset();
 204:         }
 205:       return offset;
 206:     }
 207: 
 208:     /**
 209:      * Creates physical views that form the rows of the flow view. This
 210:      * can be an entire view from the logical view (if it fits within the
 211:      * available span), a fragment of such a view (if it doesn't fit in the
 212:      * available span and can be broken down) or <code>null</code> (if it does
 213:      * not fit in the available span and also cannot be broken down).
 214:      *
 215:      * @param fv the flow view
 216:      * @param offset the start offset for the view to be created
 217:      * @param spanLeft the available span
 218:      * @param rowIndex the index of the row
 219:      *
 220:      * @return a view to fill the row with, or <code>null</code> if there
 221:      *         is no view or view fragment that fits in the available span
 222:      */
 223:     protected View createView(FlowView fv, int offset, int spanLeft,
 224:                               int rowIndex)
 225:     {
 226:       // Find the logical element for the given offset.
 227:       View logicalView = getLogicalView(fv);
 228: 
 229:       int viewIndex = logicalView.getViewIndex(offset, Position.Bias.Forward);
 230:       if (viewIndex == -1)
 231:         return null;
 232: 
 233:       View child = logicalView.getView(viewIndex);
 234:       int flowAxis = fv.getFlowAxis();
 235:       int span = (int) child.getPreferredSpan(flowAxis);
 236: 
 237:       if (span <= spanLeft)
 238:         return child;
 239:       else if (child.getBreakWeight(flowAxis, offset, spanLeft)
 240:                > BadBreakWeight)
 241:         // FIXME: What to do with the pos parameter here?
 242:         return child.breakView(flowAxis, offset, 0, spanLeft);
 243:       else
 244:         return null;
 245:     }
 246:   }
 247: 
 248:   /**
 249:    * This special subclass of <code>View</code> is used to represent
 250:    * the logical representation of this view. It does not support any
 251:    * visual representation, this is handled by the physical view implemented
 252:    * in the <code>FlowView</code>.
 253:    */
 254:   class LogicalView extends View
 255:   {
 256:     /**
 257:      * The child views of this logical view.
 258:      */
 259:     Vector children;
 260: 
 261:     /**
 262:      * Creates a new LogicalView instance.
 263:      */
 264:     LogicalView(Element el)
 265:     {
 266:       super(el);
 267:       children = new Vector();
 268:     }
 269: 
 270:     /**
 271:      * Returns the container that holds this view. The logical view returns
 272:      * the enclosing FlowView's container here.
 273:      *
 274:      * @return the container that holds this view
 275:      */
 276:     public Container getContainer()
 277:     {
 278:       return FlowView.this.getContainer();
 279:     }
 280: 
 281:     /**
 282:      * Returns the number of child views of this logical view.
 283:      *
 284:      * @return the number of child views of this logical view
 285:      */
 286:     public int getViewCount()
 287:     {
 288:       return children.size();
 289:     }
 290: 
 291:     /**
 292:      * Returns the child view at the specified index.
 293:      *
 294:      * @param index the index
 295:      *
 296:      * @return the child view at the specified index
 297:      */
 298:     public View getView(int index)
 299:     {
 300:       return (View) children.get(index);
 301:     }
 302: 
 303:     /**
 304:      * Replaces some child views with other child views.
 305:      *
 306:      * @param offset the offset at which to replace child views
 307:      * @param length the number of children to remove
 308:      * @param views the views to be inserted
 309:      */
 310:     public void replace(int offset, int length, View[] views)
 311:     {
 312:       if (length > 0)
 313:         {
 314:           for (int count = 0; count < length; ++count)
 315:             children.remove(offset);
 316:         }
 317: 
 318:       int endOffset = offset + views.length;
 319:       for (int i = offset; i < endOffset; ++i)
 320:         {
 321:           children.add(i, views[i - offset]);
 322:           // Set the parent of the child views to the flow view itself so
 323:           // it has something to resolve.
 324:           views[i - offset].setParent(FlowView.this);
 325:         }
 326:     }
 327: 
 328:     /**
 329:      * Returns the index of the child view that contains the specified
 330:      * position in the document model.
 331:      *
 332:      * @param pos the position for which we are searching the child view
 333:      * @param b the bias
 334:      *
 335:      * @return the index of the child view that contains the specified
 336:      *         position in the document model
 337:      */
 338:     public int getViewIndex(int pos, Position.Bias b)
 339:     {
 340:       int index = -1;
 341:       int i = 0;
 342:       for (Iterator it = children.iterator(); it.hasNext(); i++)
 343:         {
 344:           View child = (View) it.next();
 345:           if (child.getStartOffset() >= pos
 346:               && child.getEndOffset() < pos)
 347:             {
 348:               index = i;
 349:               break;
 350:             }
 351:         }
 352:       return index;
 353:     }
 354: 
 355:     /**
 356:      * Throws an AssertionError because it must never be called. LogicalView
 357:      * only serves as a holder for child views and has no visual
 358:      * representation.
 359:      */
 360:     public float getPreferredSpan(int axis)
 361:     {
 362:       throw new AssertionError("This method must not be called in "
 363:                                + "LogicalView.");
 364:     }
 365: 
 366:     /**
 367:      * Throws an AssertionError because it must never be called. LogicalView
 368:      * only serves as a holder for child views and has no visual
 369:      * representation.
 370:      */
 371:     public Shape modelToView(int pos, Shape a, Position.Bias b)
 372:       throws BadLocationException
 373:     {
 374:       throw new AssertionError("This method must not be called in "
 375:                                + "LogicalView.");
 376:     }
 377: 
 378:     /**
 379:      * Throws an AssertionError because it must never be called. LogicalView
 380:      * only serves as a holder for child views and has no visual
 381:      * representation.
 382:      */
 383:     public void paint(Graphics g, Shape s)
 384:     {
 385:       throw new AssertionError("This method must not be called in "
 386:                                + "LogicalView.");
 387:     }
 388: 
 389:     /**
 390:      * Throws an AssertionError because it must never be called. LogicalView
 391:      * only serves as a holder for child views and has no visual
 392:      * representation.
 393:      */
 394:     public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
 395:     {
 396:       throw new AssertionError("This method must not be called in "
 397:                                + "LogicalView.");
 398:     }
 399: 
 400:     /**
 401:      * Returns the document position that is (visually) nearest to the given
 402:      * document position <code>pos</code> in the given direction <code>d</code>.
 403:      *
 404:      * @param c the text component
 405:      * @param pos the document position
 406:      * @param b the bias for <code>pos</code>
 407:      * @param d the direction, must be either {@link SwingConstants#NORTH},
 408:      *        {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or
 409:      *        {@link SwingConstants#EAST}
 410:      * @param biasRet an array of {@link Position.Bias} that can hold at least
 411:      *        one element, which is filled with the bias of the return position
 412:      *        on method exit
 413:      *
 414:      * @return the document position that is (visually) nearest to the given
 415:      *         document position <code>pos</code> in the given direction
 416:      *         <code>d</code>
 417:      *
 418:      * @throws BadLocationException if <code>pos</code> is not a valid offset in
 419:      *         the document model
 420:      */
 421:     public int getNextVisualPositionFrom(JTextComponent c, int pos,
 422:                                          Position.Bias b, int d,
 423:                                          Position.Bias[] biasRet)
 424:       throws BadLocationException
 425:     {
 426:       assert false : "getNextVisualPositionFrom() must not be called in "
 427:         + "LogicalView";
 428:       return 0;
 429:     }
 430:   }
 431: 
 432:   /**
 433:    * The shared instance of FlowStrategy.
 434:    */
 435:   static final FlowStrategy sharedStrategy = new FlowStrategy();
 436: 
 437:   /**
 438:    * The span of the <code>FlowView</code> that should be flowed.
 439:    */
 440:   protected int layoutSpan;
 441: 
 442:   /**
 443:    * Represents the logical child elements of this view, encapsulated within
 444:    * one parent view (an instance of a package private <code>LogicalView</code>
 445:    * class). These will be translated to a set of real views that are then
 446:    * displayed on screen. This translation is performed by the inner class
 447:    * {@link FlowStrategy}.
 448:    */
 449:   protected View layoutPool;
 450: 
 451:   /**
 452:    * The <code>FlowStrategy</code> to use for translating between the
 453:    * logical and physical view.
 454:    */
 455:   protected FlowStrategy strategy;
 456: 
 457:   /**
 458:    * Creates a new <code>FlowView</code> for the given
 459:    * <code>Element</code> and <code>axis</code>.
 460:    *
 461:    * @param element the element that is rendered by this FlowView
 462:    * @param axis the axis along which the view is tiled, either
 463:    *        <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>, the flow
 464:    *        axis is orthogonal to this one
 465:    */
 466:   public FlowView(Element element, int axis)
 467:   {
 468:     super(element, axis);
 469:     strategy = sharedStrategy;
 470:   }
 471: 
 472:   /**
 473:    * Returns the axis along which the view should be flowed. This is
 474:    * orthogonal to the axis along which the boxes are tiled.
 475:    *
 476:    * @return the axis along which the view should be flowed
 477:    */
 478:   public int getFlowAxis()
 479:   {
 480:     int axis = getAxis();
 481:     int flowAxis;
 482:  
 483:     if (axis == X_AXIS)
 484:       flowAxis = Y_AXIS;
 485:     else
 486:       flowAxis = X_AXIS;
 487: 
 488:     return flowAxis;
 489: 
 490:   }
 491: 
 492:   /**
 493:    * Returns the span of the flow for the specified child view. A flow
 494:    * layout can be shaped by providing different span values for different
 495:    * child indices. The default implementation returns the entire available
 496:    * span inside the view.
 497:    *
 498:    * @param index the index of the child for which to return the span
 499:    *
 500:    * @return the span of the flow for the specified child view
 501:    */
 502:   public int getFlowSpan(int index)
 503:   {
 504:     return layoutSpan;
 505:   }
 506: 
 507:   /**
 508:    * Returns the location along the flow axis where the flow span starts
 509:    * given a child view index. The flow can be shaped by providing
 510:    * different values here.
 511:    *
 512:    * @param index the index of the child for which to return the flow location
 513:    *
 514:    * @return the location along the flow axis where the flow span starts
 515:    */
 516:   public int getFlowStart(int index)
 517:   {
 518:     return getLeftInset(); // TODO: Is this correct?
 519:   }
 520: 
 521:   /**
 522:    * Creates a new view that represents a row within a flow.
 523:    *
 524:    * @return a view for a new row
 525:    */
 526:   protected abstract View createRow();
 527: 
 528:   /**
 529:    * Loads the children of this view. The <code>FlowView</code> does not
 530:    * directly load its children. Instead it creates a logical view
 531:    * (@{link #layoutPool}) which is filled by the logical child views.
 532:    * The real children are created at layout time and each represent one
 533:    * row.
 534:    *
 535:    * This method is called by {@link View#setParent} in order to initialize
 536:    * the view.
 537:    *
 538:    * @param vf the view factory to use for creating the child views
 539:    */
 540:   protected void loadChildren(ViewFactory vf)
 541:   {
 542:     if (layoutPool == null)
 543:       {
 544:         layoutPool = new LogicalView(getElement());
 545: 
 546:         Element el = getElement();
 547:         int count = el.getElementCount();
 548:         for (int i = 0; i < count; ++i)
 549:           {
 550:             Element childEl = el.getElement(i);
 551:             View childView = vf.create(childEl);
 552:             layoutPool.append(childView);
 553:           }
 554:       }
 555:   }
 556: 
 557:   /**
 558:    * Performs the layout of this view. If the span along the flow axis changed,
 559:    * this first calls {@link FlowStrategy#layout} in order to rebuild the
 560:    * rows of this view. Then the superclass's behaviour is called to arrange
 561:    * the rows within the box.
 562:    *
 563:    * @param width the width of the view
 564:    * @param height the height of the view
 565:    */
 566:   protected void layout(int width, int height)
 567:   {
 568:     boolean rebuild = false;
 569: 
 570:     int flowAxis = getFlowAxis();
 571:     if (flowAxis == X_AXIS)
 572:       {
 573:         rebuild = !(width == layoutSpan);
 574:         layoutSpan = width;
 575:       }
 576:     else
 577:       {
 578:         rebuild = !(height == layoutSpan);
 579:         layoutSpan = height;
 580:       }
 581: 
 582:     if (rebuild)
 583:       strategy.layout(this);
 584: 
 585:     // TODO: If the span along the box axis has changed in the process of
 586:     // relayouting the rows (that is, if rows have been added or removed),
 587:     // call preferenceChanged in order to throw away cached layout information
 588:     // of the surrounding BoxView.
 589: 
 590:     super.layout(width, height);
 591:   }
 592: 
 593:   /**
 594:    * Receice notification that some content has been inserted in the region
 595:    * that this view is responsible for. This calls
 596:    * {@link FlowStrategy#insertUpdate}.
 597:    *
 598:    * @param changes the document event describing the changes
 599:    * @param a the current allocation of the view
 600:    * @param vf the view factory that is used for creating new child views
 601:    */
 602:   public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
 603:   {
 604:     // First we must send the insertUpdate to the logical view so it can
 605:     // be updated accordingly.
 606:     layoutPool.insertUpdate(changes, a, vf);
 607:     strategy.insertUpdate(this, changes, getInsideAllocation(a));
 608:   }
 609: 
 610:   /**
 611:    * Receice notification that some content has been removed from the region
 612:    * that this view is responsible for. This calls
 613:    * {@link FlowStrategy#removeUpdate}.
 614:    *
 615:    * @param changes the document event describing the changes
 616:    * @param a the current allocation of the view
 617:    * @param vf the view factory that is used for creating new child views
 618:    */
 619:   public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
 620:   {
 621:     strategy.removeUpdate(this, changes, getInsideAllocation(a));
 622:   }
 623: 
 624:   /**
 625:    * Receice notification that some attributes changed in the region
 626:    * that this view is responsible for. This calls
 627:    * {@link FlowStrategy#changedUpdate}.
 628:    *
 629:    * @param changes the document event describing the changes
 630:    * @param a the current allocation of the view
 631:    * @param vf the view factory that is used for creating new child views
 632:    */
 633:   public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
 634:   {
 635:     strategy.changedUpdate(this, changes, getInsideAllocation(a));
 636:   }
 637: 
 638:   /**
 639:    * Returns the index of the child <code>View</code> for the given model
 640:    * position.
 641:    *
 642:    * This is implemented to iterate over the children of this
 643:    * view (the rows) and return the index of the first view that contains
 644:    * the given position.
 645:    *
 646:    * @param pos the model position for whicht the child <code>View</code> is
 647:    *        queried
 648:    *
 649:    * @return the index of the child <code>View</code> for the given model
 650:    *         position
 651:    */
 652:   protected int getViewIndexAtPosition(int pos)
 653:   {
 654:     // First make sure we have a valid layout.
 655:     if (!isAllocationValid())
 656:       layout(getWidth(), getHeight());
 657: 
 658:     int count = getViewCount();
 659:     int result = -1;
 660: 
 661:     for (int i = 0; i < count; ++i)
 662:       {
 663:         View child = getView(i);
 664:         int start = child.getStartOffset();
 665:         int end = child.getEndOffset();
 666:         if (start <= pos && end > pos)
 667:           {
 668:             result = i;
 669:             break;
 670:           }
 671:       }
 672:     return result;
 673:   }
 674: }