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