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