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