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:   /**
 402:    * The shared instance of FlowStrategy.
 403:    */
 404:   static final FlowStrategy sharedStrategy = new FlowStrategy();
 405: 
 406:   /**
 407:    * The span of the <code>FlowView</code> that should be flowed.
 408:    */
 409:   protected int layoutSpan;
 410: 
 411:   /**
 412:    * Represents the logical child elements of this view, encapsulated within
 413:    * one parent view (an instance of a package private <code>LogicalView</code>
 414:    * class). These will be translated to a set of real views that are then
 415:    * displayed on screen. This translation is performed by the inner class
 416:    * {@link FlowStrategy}.
 417:    */
 418:   protected View layoutPool;
 419: 
 420:   /**
 421:    * The <code>FlowStrategy</code> to use for translating between the
 422:    * logical and physical view.
 423:    */
 424:   protected FlowStrategy strategy;
 425: 
 426:   /**
 427:    * Creates a new <code>FlowView</code> for the given
 428:    * <code>Element</code> and <code>axis</code>.
 429:    *
 430:    * @param element the element that is rendered by this FlowView
 431:    * @param axis the axis along which the view is tiled, either
 432:    *        <code>View.X_AXIS</code> or <code>View.Y_AXIS</code>, the flow
 433:    *        axis is orthogonal to this one
 434:    */
 435:   public FlowView(Element element, int axis)
 436:   {
 437:     super(element, axis);
 438:     strategy = sharedStrategy;
 439:   }
 440: 
 441:   /**
 442:    * Returns the axis along which the view should be flowed. This is
 443:    * orthogonal to the axis along which the boxes are tiled.
 444:    *
 445:    * @return the axis along which the view should be flowed
 446:    */
 447:   public int getFlowAxis()
 448:   {
 449:     int axis = getAxis();
 450:     int flowAxis;
 451:  
 452:     if (axis == X_AXIS)
 453:       flowAxis = Y_AXIS;
 454:     else
 455:       flowAxis = X_AXIS;
 456: 
 457:     return flowAxis;
 458: 
 459:   }
 460: 
 461:   /**
 462:    * Returns the span of the flow for the specified child view. A flow
 463:    * layout can be shaped by providing different span values for different
 464:    * child indices. The default implementation returns the entire available
 465:    * span inside the view.
 466:    *
 467:    * @param index the index of the child for which to return the span
 468:    *
 469:    * @return the span of the flow for the specified child view
 470:    */
 471:   public int getFlowSpan(int index)
 472:   {
 473:     return layoutSpan;
 474:   }
 475: 
 476:   /**
 477:    * Returns the location along the flow axis where the flow span starts
 478:    * given a child view index. The flow can be shaped by providing
 479:    * different values here.
 480:    *
 481:    * @param index the index of the child for which to return the flow location
 482:    *
 483:    * @return the location along the flow axis where the flow span starts
 484:    */
 485:   public int getFlowStart(int index)
 486:   {
 487:     return getLeftInset(); // TODO: Is this correct?
 488:   }
 489: 
 490:   /**
 491:    * Creates a new view that represents a row within a flow.
 492:    *
 493:    * @return a view for a new row
 494:    */
 495:   protected abstract View createRow();
 496: 
 497:   /**
 498:    * Loads the children of this view. The <code>FlowView</code> does not
 499:    * directly load its children. Instead it creates a logical view
 500:    * (@{link #layoutPool}) which is filled by the logical child views.
 501:    * The real children are created at layout time and each represent one
 502:    * row.
 503:    *
 504:    * This method is called by {@link View#setParent} in order to initialize
 505:    * the view.
 506:    *
 507:    * @param vf the view factory to use for creating the child views
 508:    */
 509:   protected void loadChildren(ViewFactory vf)
 510:   {
 511:     if (layoutPool == null)
 512:       {
 513:         layoutPool = new LogicalView(getElement());
 514: 
 515:         Element el = getElement();
 516:         int count = el.getElementCount();
 517:         for (int i = 0; i < count; ++i)
 518:           {
 519:             Element childEl = el.getElement(i);
 520:             View childView = vf.create(childEl);
 521:             layoutPool.append(childView);
 522:           }
 523:       }
 524:   }
 525: 
 526:   /**
 527:    * Performs the layout of this view. If the span along the flow axis changed,
 528:    * this first calls {@link FlowStrategy#layout} in order to rebuild the
 529:    * rows of this view. Then the superclass's behaviour is called to arrange
 530:    * the rows within the box.
 531:    *
 532:    * @param width the width of the view
 533:    * @param height the height of the view
 534:    */
 535:   protected void layout(int width, int height)
 536:   {
 537:     boolean rebuild = false;
 538: 
 539:     int flowAxis = getFlowAxis();
 540:     if (flowAxis == X_AXIS)
 541:       {
 542:         rebuild = !(width == layoutSpan);
 543:         layoutSpan = width;
 544:       }
 545:     else
 546:       {
 547:         rebuild = !(height == layoutSpan);
 548:         layoutSpan = height;
 549:       }
 550: 
 551:     if (rebuild)
 552:       strategy.layout(this);
 553: 
 554:     // TODO: If the span along the box axis has changed in the process of
 555:     // relayouting the rows (that is, if rows have been added or removed),
 556:     // call preferenceChanged in order to throw away cached layout information
 557:     // of the surrounding BoxView.
 558: 
 559:     super.layout(width, height);
 560:   }
 561: 
 562:   /**
 563:    * Receice notification that some content has been inserted in the region
 564:    * that this view is responsible for. This calls
 565:    * {@link FlowStrategy#insertUpdate}.
 566:    *
 567:    * @param changes the document event describing the changes
 568:    * @param a the current allocation of the view
 569:    * @param vf the view factory that is used for creating new child views
 570:    */
 571:   public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
 572:   {
 573:     // First we must send the insertUpdate to the logical view so it can
 574:     // be updated accordingly.
 575:     layoutPool.insertUpdate(changes, a, vf);
 576:     strategy.insertUpdate(this, changes, getInsideAllocation(a));
 577:   }
 578: 
 579:   /**
 580:    * Receice notification that some content has been removed from the region
 581:    * that this view is responsible for. This calls
 582:    * {@link FlowStrategy#removeUpdate}.
 583:    *
 584:    * @param changes the document event describing the changes
 585:    * @param a the current allocation of the view
 586:    * @param vf the view factory that is used for creating new child views
 587:    */
 588:   public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
 589:   {
 590:     strategy.removeUpdate(this, changes, getInsideAllocation(a));
 591:   }
 592: 
 593:   /**
 594:    * Receice notification that some attributes changed in the region
 595:    * that this view is responsible for. This calls
 596:    * {@link FlowStrategy#changedUpdate}.
 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 changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
 603:   {
 604:     strategy.changedUpdate(this, changes, getInsideAllocation(a));
 605:   }
 606: 
 607:   /**
 608:    * Returns the index of the child <code>View</code> for the given model
 609:    * position.
 610:    *
 611:    * This is implemented to iterate over the children of this
 612:    * view (the rows) and return the index of the first view that contains
 613:    * the given position.
 614:    *
 615:    * @param pos the model position for whicht the child <code>View</code> is
 616:    *        queried
 617:    *
 618:    * @return the index of the child <code>View</code> for the given model
 619:    *         position
 620:    */
 621:   protected int getViewIndexAtPosition(int pos)
 622:   {
 623:     // First make sure we have a valid layout.
 624:     if (!isAllocationValid())
 625:       layout(getWidth(), getHeight());
 626: 
 627:     int count = getViewCount();
 628:     int result = -1;
 629: 
 630:     for (int i = 0; i < count; ++i)
 631:       {
 632:         View child = getView(i);
 633:         int start = child.getStartOffset();
 634:         int end = child.getEndOffset();
 635:         if (start <= pos && end > pos)
 636:           {
 637:             result = i;
 638:             break;
 639:           }
 640:       }
 641:     return result;
 642:   }
 643: }