1 /*
   2  * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text;
  26 
  27 import java.awt.*;
  28 import java.util.Vector;
  29 import javax.swing.event.*;
  30 import javax.swing.SizeRequirements;
  31 
  32 /**
  33  * A View that tries to flow it's children into some
  34  * partially constrained space.  This can be used to
  35  * build things like paragraphs, pages, etc.  The
  36  * flow is made up of the following pieces of functionality.
  37  * <ul>
  38  * <li>A logical set of child views, which as used as a
  39  * layout pool from which a physical view is formed.
  40  * <li>A strategy for translating the logical view to
  41  * a physical (flowed) view.
  42  * <li>Constraints for the strategy to work against.
  43  * <li>A physical structure, that represents the flow.
  44  * The children of this view are where the pieces of
  45  * of the logical views are placed to create the flow.
  46  * </ul>
  47  *
  48  * @author  Timothy Prinzing
  49  * @see     View
  50  * @since 1.3
  51  */
  52 public abstract class FlowView extends BoxView {
  53 
  54     /**
  55      * Constructs a FlowView for the given element.
  56      *
  57      * @param elem the element that this view is responsible for
  58      * @param axis may be either View.X_AXIS or View.Y_AXIS
  59      */
  60     public FlowView(Element elem, int axis) {
  61         super(elem, axis);
  62         layoutSpan = Integer.MAX_VALUE;
  63         strategy = new FlowStrategy();
  64     }
  65 
  66     /**
  67      * Fetches the axis along which views should be
  68      * flowed.  By default, this will be the axis
  69      * orthogonal to the axis along which the flow
  70      * rows are tiled (the axis of the default flow
  71      * rows themselves).  This is typically used
  72      * by the <code>FlowStrategy</code>.
  73      * @return the axis along which views should be
  74      * flowed
  75      */
  76     public int getFlowAxis() {
  77         if (getAxis() == Y_AXIS) {
  78             return X_AXIS;
  79         }
  80         return Y_AXIS;
  81     }
  82 
  83     /**
  84      * Fetch the constraining span to flow against for
  85      * the given child index.  This is called by the
  86      * FlowStrategy while it is updating the flow.
  87      * A flow can be shaped by providing different values
  88      * for the row constraints.  By default, the entire
  89      * span inside of the insets along the flow axis
  90      * is returned.
  91      *
  92      * @param index the index of the row being updated.
  93      *   This should be a value &gt;= 0 and &lt; getViewCount().
  94      * @return the constraining span to flow against for
  95      * the given child index
  96      * @see #getFlowStart
  97      */
  98     public int getFlowSpan(int index) {
  99         return layoutSpan;
 100     }
 101 
 102     /**
 103      * Fetch the location along the flow axis that the
 104      * flow span will start at.  This is called by the
 105      * FlowStrategy while it is updating the flow.
 106      * A flow can be shaped by providing different values
 107      * for the row constraints.
 108 
 109      * @param index the index of the row being updated.
 110      *   This should be a value &gt;= 0 and &lt; getViewCount().
 111      * @return the location along the flow axis that the
 112      * flow span will start at
 113      * @see #getFlowSpan
 114      */
 115     public int getFlowStart(int index) {
 116         return 0;
 117     }
 118 
 119     /**
 120      * Create a View that should be used to hold a
 121      * a rows worth of children in a flow.  This is
 122      * called by the FlowStrategy when new children
 123      * are added or removed (i.e. rows are added or
 124      * removed) in the process of updating the flow.
 125      * @return a View that should be used to hold a
 126      * a rows worth of children in a flow
 127      */
 128     protected abstract View createRow();
 129 
 130     // ---- BoxView methods -------------------------------------
 131 
 132     /**
 133      * Loads all of the children to initialize the view.
 134      * This is called by the <code>setParent</code> method.
 135      * This is reimplemented to not load any children directly
 136      * (as they are created in the process of formatting).
 137      * If the layoutPool variable is null, an instance of
 138      * LogicalView is created to represent the logical view
 139      * that is used in the process of formatting.
 140      *
 141      * @param f the view factory
 142      */
 143     protected void loadChildren(ViewFactory f) {
 144         if (layoutPool == null) {
 145             layoutPool = new LogicalView(getElement());
 146         }
 147         layoutPool.setParent(this);
 148 
 149         // This synthetic insertUpdate call gives the strategy a chance
 150         // to initialize.
 151         strategy.insertUpdate(this, null, null);
 152     }
 153 
 154     /**
 155      * Fetches the child view index representing the given position in
 156      * the model.
 157      *
 158      * @param pos the position &gt;= 0
 159      * @return  index of the view representing the given position, or
 160      *   -1 if no view represents that position
 161      */
 162     protected int getViewIndexAtPosition(int pos) {
 163         if (pos >= getStartOffset() && (pos < getEndOffset())) {
 164             for (int counter = 0; counter < getViewCount(); counter++) {
 165                 View v = getView(counter);
 166                 if(pos >= v.getStartOffset() &&
 167                    pos < v.getEndOffset()) {
 168                     return counter;
 169                 }
 170             }
 171         }
 172         return -1;
 173     }
 174 
 175     /**
 176      * Lays out the children.  If the span along the flow
 177      * axis has changed, layout is marked as invalid which
 178      * which will cause the superclass behavior to recalculate
 179      * the layout along the box axis.  The FlowStrategy.layout
 180      * method will be called to rebuild the flow rows as
 181      * appropriate.  If the height of this view changes
 182      * (determined by the preferred size along the box axis),
 183      * a preferenceChanged is called.  Following all of that,
 184      * the normal box layout of the superclass is performed.
 185      *
 186      * @param width  the width to lay out against &gt;= 0.  This is
 187      *   the width inside of the inset area.
 188      * @param height the height to lay out against &gt;= 0 This
 189      *   is the height inside of the inset area.
 190      */
 191     protected void layout(int width, int height) {
 192         final int faxis = getFlowAxis();
 193         int newSpan;
 194         if (faxis == X_AXIS) {
 195             newSpan = width;
 196         } else {
 197             newSpan = height;
 198         }
 199         if (layoutSpan != newSpan) {
 200             layoutChanged(faxis);
 201             layoutChanged(getAxis());
 202             layoutSpan = newSpan;
 203         }
 204 
 205         // repair the flow if necessary
 206         if (! isLayoutValid(faxis)) {
 207             final int heightAxis = getAxis();
 208             int oldFlowHeight = (heightAxis == X_AXIS)? getWidth() : getHeight();
 209             strategy.layout(this);
 210             int newFlowHeight = (int) getPreferredSpan(heightAxis);
 211             if (oldFlowHeight != newFlowHeight) {
 212                 View p = getParent();
 213                 if (p != null) {
 214                     p.preferenceChanged(this, (heightAxis == X_AXIS), (heightAxis == Y_AXIS));
 215                 }
 216 
 217                 // PENDING(shannonh)
 218                 // Temporary fix for 4250847
 219                 // Can be removed when TraversalContext is added
 220                 Component host = getContainer();
 221                 if (host != null) {
 222                     //nb idk 12/12/2001 host should not be equal to null. We need to add assertion here
 223                     host.repaint();
 224                 }
 225             }
 226         }
 227 
 228         super.layout(width, height);
 229     }
 230 
 231     /**
 232      * Calculate requirements along the minor axis.  This
 233      * is implemented to forward the request to the logical
 234      * view by calling getMinimumSpan, getPreferredSpan, and
 235      * getMaximumSpan on it.
 236      */
 237     protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
 238         if (r == null) {
 239             r = new SizeRequirements();
 240         }
 241         float pref = layoutPool.getPreferredSpan(axis);
 242         float min = layoutPool.getMinimumSpan(axis);
 243         // Don't include insets, Box.getXXXSpan will include them.
 244         r.minimum = (int)min;
 245         r.preferred = Math.max(r.minimum, (int) pref);
 246         r.maximum = Integer.MAX_VALUE;
 247         r.alignment = 0.5f;
 248         return r;
 249     }
 250 
 251     // ---- View methods ----------------------------------------------------
 252 
 253     /**
 254      * Gives notification that something was inserted into the document
 255      * in a location that this view is responsible for.
 256      *
 257      * @param changes the change information from the associated document
 258      * @param a the current allocation of the view
 259      * @param f the factory to use to rebuild if the view has children
 260      * @see View#insertUpdate
 261      */
 262     public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
 263         layoutPool.insertUpdate(changes, a, f);
 264         strategy.insertUpdate(this, changes, getInsideAllocation(a));
 265     }
 266 
 267     /**
 268      * Gives notification that something was removed from the document
 269      * in a location that this view is responsible for.
 270      *
 271      * @param changes the change information from the associated document
 272      * @param a the current allocation of the view
 273      * @param f the factory to use to rebuild if the view has children
 274      * @see View#removeUpdate
 275      */
 276     public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
 277         layoutPool.removeUpdate(changes, a, f);
 278         strategy.removeUpdate(this, changes, getInsideAllocation(a));
 279     }
 280 
 281     /**
 282      * Gives notification from the document that attributes were changed
 283      * in a location that this view is responsible for.
 284      *
 285      * @param changes the change information from the associated document
 286      * @param a the current allocation of the view
 287      * @param f the factory to use to rebuild if the view has children
 288      * @see View#changedUpdate
 289      */
 290     public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
 291         layoutPool.changedUpdate(changes, a, f);
 292         strategy.changedUpdate(this, changes, getInsideAllocation(a));
 293     }
 294 
 295     /** {@inheritDoc} */
 296     public void setParent(View parent) {
 297         super.setParent(parent);
 298         if (parent == null
 299                 && layoutPool != null ) {
 300             layoutPool.setParent(null);
 301         }
 302     }
 303 
 304     // --- variables -----------------------------------------------
 305 
 306     /**
 307      * Default constraint against which the flow is
 308      * created against.
 309      */
 310     protected int layoutSpan;
 311 
 312     /**
 313      * These are the views that represent the child elements
 314      * of the element this view represents (The logical view
 315      * to translate to a physical view).  These are not
 316      * directly children of this view.  These are either
 317      * placed into the rows directly or used for the purpose
 318      * of breaking into smaller chunks, to form the physical
 319      * view.
 320      */
 321     protected View layoutPool;
 322 
 323     /**
 324      * The behavior for keeping the flow updated.  By
 325      * default this is a singleton shared by all instances
 326      * of FlowView (FlowStrategy is stateless).  Subclasses
 327      * can create an alternative strategy, which might keep
 328      * state.
 329      */
 330     protected FlowStrategy strategy;
 331 
 332     /**
 333      * Strategy for maintaining the physical form
 334      * of the flow.  The default implementation is
 335      * completely stateless, and recalculates the
 336      * entire flow if the layout is invalid on the
 337      * given FlowView.  Alternative strategies can
 338      * be implemented by subclassing, and might
 339      * perform incremental repair to the layout
 340      * or alternative breaking behavior.
 341      * @since 1.3
 342      */
 343     public static class FlowStrategy {
 344         Position damageStart = null;
 345         Vector<View> viewBuffer;
 346 
 347         void addDamage(FlowView fv, int offset) {
 348             if (offset >= fv.getStartOffset() && offset < fv.getEndOffset()) {
 349                 if (damageStart == null || offset < damageStart.getOffset()) {
 350                     try {
 351                         damageStart = fv.getDocument().createPosition(offset);
 352                     } catch (BadLocationException e) {
 353                         // shouldn't happen since offset is inside view bounds
 354                         assert(false);
 355                     }
 356                 }
 357             }
 358         }
 359 
 360         void unsetDamage() {
 361             damageStart = null;
 362         }
 363 
 364         /**
 365          * Gives notification that something was inserted into the document
 366          * in a location that the given flow view is responsible for.  The
 367          * strategy should update the appropriate changed region (which
 368          * depends upon the strategy used for repair).
 369          *
 370          * @param fv the flow view
 371          * @param e the change information from the associated document
 372          * @param alloc the current allocation of the view inside of the insets.
 373          *   This value will be null if the view has not yet been displayed.
 374          * @see View#insertUpdate
 375          */
 376         public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
 377             // FlowView.loadChildren() makes a synthetic call into this,
 378             // passing null as e
 379             if (e != null) {
 380                 addDamage(fv, e.getOffset());
 381             }
 382 
 383             if (alloc != null) {
 384                 Component host = fv.getContainer();
 385                 if (host != null) {
 386                     host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
 387                 }
 388             } else {
 389                 fv.preferenceChanged(null, true, true);
 390             }
 391         }
 392 
 393         /**
 394          * Gives notification that something was removed from the document
 395          * in a location that the given flow view is responsible for.
 396          *
 397          * @param fv the flow view
 398          * @param e the change information from the associated document
 399          * @param alloc the current allocation of the view inside of the insets.
 400          * @see View#removeUpdate
 401          */
 402         public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
 403             addDamage(fv, e.getOffset());
 404             if (alloc != null) {
 405                 Component host = fv.getContainer();
 406                 if (host != null) {
 407                     host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
 408                 }
 409             } else {
 410                 fv.preferenceChanged(null, true, true);
 411             }
 412         }
 413 
 414         /**
 415          * Gives notification from the document that attributes were changed
 416          * in a location that this view is responsible for.
 417          *
 418          * @param fv     the <code>FlowView</code> containing the changes
 419          * @param e      the <code>DocumentEvent</code> describing the changes
 420          *               done to the Document
 421          * @param alloc  Bounds of the View
 422          * @see View#changedUpdate
 423          */
 424         public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
 425             addDamage(fv, e.getOffset());
 426             if (alloc != null) {
 427                 Component host = fv.getContainer();
 428                 if (host != null) {
 429                     host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
 430                 }
 431             } else {
 432                 fv.preferenceChanged(null, true, true);
 433             }
 434         }
 435 
 436         /**
 437          * This method gives flow strategies access to the logical
 438          * view of the FlowView.
 439          * @param fv the FlowView
 440          * @return the logical view of the FlowView
 441          */
 442         protected View getLogicalView(FlowView fv) {
 443             return fv.layoutPool;
 444         }
 445 
 446         /**
 447          * Update the flow on the given FlowView.  By default, this causes
 448          * all of the rows (child views) to be rebuilt to match the given
 449          * constraints for each row.  This is called by a FlowView.layout
 450          * to update the child views in the flow.
 451          *
 452          * @param fv the view to reflow
 453          */
 454         public void layout(FlowView fv) {
 455             View pool = getLogicalView(fv);
 456             int rowIndex, p0;
 457             int p1 = fv.getEndOffset();
 458 
 459             if (fv.majorAllocValid) {
 460                 if (damageStart == null) {
 461                     return;
 462                 }
 463                 // In some cases there's no view at position damageStart, so
 464                 // step back and search again. See 6452106 for details.
 465                 int offset = damageStart.getOffset();
 466                 while ((rowIndex = fv.getViewIndexAtPosition(offset)) < 0) {
 467                     offset--;
 468                 }
 469                 if (rowIndex > 0) {
 470                     rowIndex--;
 471                 }
 472                 p0 = fv.getView(rowIndex).getStartOffset();
 473             } else {
 474                 rowIndex = 0;
 475                 p0 = fv.getStartOffset();
 476             }
 477             reparentViews(pool, p0);
 478 
 479             viewBuffer = new Vector<View>(10, 10);
 480             int rowCount = fv.getViewCount();
 481             while (p0 < p1) {
 482                 View row;
 483                 if (rowIndex >= rowCount) {
 484                     row = fv.createRow();
 485                     fv.append(row);
 486                 } else {
 487                     row = fv.getView(rowIndex);
 488                 }
 489                 p0 = layoutRow(fv, rowIndex, p0);
 490                 rowIndex++;
 491             }
 492             viewBuffer = null;
 493 
 494             if (rowIndex < rowCount) {
 495                 fv.replace(rowIndex, rowCount - rowIndex, null);
 496             }
 497             unsetDamage();
 498         }
 499 
 500         /**
 501          * Creates a row of views that will fit within the
 502          * layout span of the row.  This is called by the layout method.
 503          * This is implemented to fill the row by repeatedly calling
 504          * the createView method until the available span has been
 505          * exhausted, a forced break was encountered, or the createView
 506          * method returned null.  If the remaining span was exhausted,
 507          * the adjustRow method will be called to perform adjustments
 508          * to the row to try and make it fit into the given span.
 509          *
 510          * @param fv the flow view
 511          * @param rowIndex the index of the row to fill in with views.  The
 512          *   row is assumed to be empty on entry.
 513          * @param pos  The current position in the children of
 514          *   this views element from which to start.
 515          * @return the position to start the next row
 516          */
 517         protected int layoutRow(FlowView fv, int rowIndex, int pos) {
 518             View row = fv.getView(rowIndex);
 519             float x = fv.getFlowStart(rowIndex);
 520             float spanLeft = fv.getFlowSpan(rowIndex);
 521             int end = fv.getEndOffset();
 522             TabExpander te = (fv instanceof TabExpander) ? (TabExpander)fv : null;
 523             final int flowAxis = fv.getFlowAxis();
 524 
 525             int breakWeight = BadBreakWeight;
 526             float breakX = 0f;
 527             float breakSpan = 0f;
 528             int breakIndex = -1;
 529             int n = 0;
 530 
 531             viewBuffer.clear();
 532             while (pos < end && spanLeft >= 0) {
 533                 View v = createView(fv, pos, (int)spanLeft, rowIndex);
 534                 if (v == null) {
 535                     break;
 536                 }
 537 
 538                 int bw = v.getBreakWeight(flowAxis, x, spanLeft);
 539                 if (bw >= ForcedBreakWeight) {
 540                     View w = v.breakView(flowAxis, pos, x, spanLeft);
 541                     if (w != null) {
 542                         viewBuffer.add(w);
 543                     } else if (n == 0) {
 544                         // if the view does not break, and it is the only view
 545                         // in a row, use the whole view
 546                         viewBuffer.add(v);
 547                     }
 548                     break;
 549                 } else if (bw >= breakWeight && bw > BadBreakWeight) {
 550                     breakWeight = bw;
 551                     breakX = x;
 552                     breakSpan = spanLeft;
 553                     breakIndex = n;
 554                 }
 555 
 556                 float chunkSpan;
 557                 if (flowAxis == X_AXIS && v instanceof TabableView) {
 558                     chunkSpan = ((TabableView)v).getTabbedSpan(x, te);
 559                 } else {
 560                     chunkSpan = v.getPreferredSpan(flowAxis);
 561                 }
 562 
 563                 if (chunkSpan > spanLeft && breakIndex >= 0) {
 564                     // row is too long, and we may break
 565                     if (breakIndex < n) {
 566                         v = viewBuffer.get(breakIndex);
 567                     }
 568                     for (int i = n - 1; i >= breakIndex; i--) {
 569                         viewBuffer.remove(i);
 570                     }
 571                     v = v.breakView(flowAxis, v.getStartOffset(), breakX, breakSpan);
 572                 }
 573 
 574                 spanLeft -= chunkSpan;
 575                 x += chunkSpan;
 576                 viewBuffer.add(v);
 577                 pos = v.getEndOffset();
 578                 n++;
 579             }
 580 
 581             View[] views = new View[viewBuffer.size()];
 582             viewBuffer.toArray(views);
 583             row.replace(0, row.getViewCount(), views);
 584             return (views.length > 0 ? row.getEndOffset() : pos);
 585         }
 586 
 587         /**
 588          * Adjusts the given row if possible to fit within the
 589          * layout span.  By default this will try to find the
 590          * highest break weight possible nearest the end of
 591          * the row.  If a forced break is encountered, the
 592          * break will be positioned there.
 593          *
 594          * @param fv the flow view
 595          * @param rowIndex the row to adjust to the current layout
 596          *  span.
 597          * @param desiredSpan the current layout span &gt;= 0
 598          * @param x the location r starts at.
 599          */
 600         protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) {
 601             final int flowAxis = fv.getFlowAxis();
 602             View r = fv.getView(rowIndex);
 603             int n = r.getViewCount();
 604             int span = 0;
 605             int bestWeight = BadBreakWeight;
 606             int bestSpan = 0;
 607             int bestIndex = -1;
 608             View v;
 609             for (int i = 0; i < n; i++) {
 610                 v = r.getView(i);
 611                 int spanLeft = desiredSpan - span;
 612 
 613                 int w = v.getBreakWeight(flowAxis, x + span, spanLeft);
 614                 if ((w >= bestWeight) && (w > BadBreakWeight)) {
 615                     bestWeight = w;
 616                     bestIndex = i;
 617                     bestSpan = span;
 618                     if (w >= ForcedBreakWeight) {
 619                         // it's a forced break, so there is
 620                         // no point in searching further.
 621                         break;
 622                     }
 623                 }
 624                 span += v.getPreferredSpan(flowAxis);
 625             }
 626             if (bestIndex < 0) {
 627                 // there is nothing that can be broken, leave
 628                 // it in it's current state.
 629                 return;
 630             }
 631 
 632             // Break the best candidate view, and patch up the row.
 633             int spanLeft = desiredSpan - bestSpan;
 634             v = r.getView(bestIndex);
 635             v = v.breakView(flowAxis, v.getStartOffset(), x + bestSpan, spanLeft);
 636             View[] va = new View[1];
 637             va[0] = v;
 638             View lv = getLogicalView(fv);
 639             int p0 = r.getView(bestIndex).getStartOffset();
 640             int p1 = r.getEndOffset();
 641             for (int i = 0; i < lv.getViewCount(); i++) {
 642                 View tmpView = lv.getView(i);
 643                 if (tmpView.getEndOffset() > p1) {
 644                     break;
 645                 }
 646                 if (tmpView.getStartOffset() >= p0) {
 647                     tmpView.setParent(lv);
 648                 }
 649             }
 650             r.replace(bestIndex, n - bestIndex, va);
 651         }
 652 
 653         void reparentViews(View pool, int startPos) {
 654             int n = pool.getViewIndex(startPos, Position.Bias.Forward);
 655             if (n >= 0) {
 656                 for (int i = n; i < pool.getViewCount(); i++) {
 657                     pool.getView(i).setParent(pool);
 658                 }
 659             }
 660         }
 661 
 662         /**
 663          * Creates a view that can be used to represent the current piece
 664          * of the flow.  This can be either an entire view from the
 665          * logical view, or a fragment of the logical view.
 666          *
 667          * @param fv the view holding the flow
 668          * @param startOffset the start location for the view being created
 669          * @param spanLeft the about of span left to fill in the row
 670          * @param rowIndex the row the view will be placed into
 671          * @return a view that can be used to represent the current piece
 672          * of the flow
 673          */
 674         protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) {
 675             // Get the child view that contains the given starting position
 676             View lv = getLogicalView(fv);
 677             int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward);
 678             View v = lv.getView(childIndex);
 679             if (startOffset==v.getStartOffset()) {
 680                 // return the entire view
 681                 return v;
 682             }
 683 
 684             // return a fragment.
 685             v = v.createFragment(startOffset, v.getEndOffset());
 686             return v;
 687         }
 688     }
 689 
 690     /**
 691      * This class can be used to represent a logical view for
 692      * a flow.  It keeps the children updated to reflect the state
 693      * of the model, gives the logical child views access to the
 694      * view hierarchy, and calculates a preferred span.  It doesn't
 695      * do any rendering, layout, or model/view translation.
 696      */
 697     static class LogicalView extends CompositeView {
 698 
 699         LogicalView(Element elem) {
 700             super(elem);
 701         }
 702 
 703         protected int getViewIndexAtPosition(int pos) {
 704             Element elem = getElement();
 705             if (elem.isLeaf()) {
 706                 return 0;
 707             }
 708             return super.getViewIndexAtPosition(pos);
 709         }
 710 
 711         protected void loadChildren(ViewFactory f) {
 712             Element elem = getElement();
 713             if (elem.isLeaf()) {
 714                 View v = new LabelView(elem);
 715                 append(v);
 716             } else {
 717                 super.loadChildren(f);
 718             }
 719         }
 720 
 721         /**
 722          * Fetches the attributes to use when rendering.  This view
 723          * isn't directly responsible for an element so it returns
 724          * the outer classes attributes.
 725          */
 726         public AttributeSet getAttributes() {
 727             View p = getParent();
 728             return (p != null) ? p.getAttributes() : null;
 729         }
 730 
 731         /**
 732          * Determines the preferred span for this view along an
 733          * axis.
 734          *
 735          * @param axis may be either View.X_AXIS or View.Y_AXIS
 736          * @return   the span the view would like to be rendered into.
 737          *           Typically the view is told to render into the span
 738          *           that is returned, although there is no guarantee.
 739          *           The parent may choose to resize or break the view.
 740          * @see View#getPreferredSpan
 741          */
 742         public float getPreferredSpan(int axis) {
 743             float maxpref = 0;
 744             float pref = 0;
 745             int n = getViewCount();
 746             for (int i = 0; i < n; i++) {
 747                 View v = getView(i);
 748                 pref += v.getPreferredSpan(axis);
 749                 if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) >= ForcedBreakWeight) {
 750                     maxpref = Math.max(maxpref, pref);
 751                     pref = 0;
 752                 }
 753             }
 754             maxpref = Math.max(maxpref, pref);
 755             return maxpref;
 756         }
 757 
 758         /**
 759          * Determines the minimum span for this view along an
 760          * axis.  The is implemented to find the minimum unbreakable
 761          * span.
 762          *
 763          * @param axis may be either View.X_AXIS or View.Y_AXIS
 764          * @return  the span the view would like to be rendered into.
 765          *           Typically the view is told to render into the span
 766          *           that is returned, although there is no guarantee.
 767          *           The parent may choose to resize or break the view.
 768          * @see View#getPreferredSpan
 769          */
 770         public float getMinimumSpan(int axis) {
 771             float maxmin = 0;
 772             float min = 0;
 773             boolean nowrap = false;
 774             int n = getViewCount();
 775             for (int i = 0; i < n; i++) {
 776                 View v = getView(i);
 777                 if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) == BadBreakWeight) {
 778                     min += v.getPreferredSpan(axis);
 779                     nowrap = true;
 780                 } else if (nowrap) {
 781                     maxmin = Math.max(min, maxmin);
 782                     nowrap = false;
 783                     min = 0;
 784                 }
 785                 if (v instanceof ComponentView) {
 786                     maxmin = Math.max(maxmin, v.getMinimumSpan(axis));
 787                 }
 788             }
 789             maxmin = Math.max(maxmin, min);
 790             return maxmin;
 791         }
 792 
 793         /**
 794          * Forward the DocumentEvent to the given child view.  This
 795          * is implemented to reparent the child to the logical view
 796          * (the children may have been parented by a row in the flow
 797          * if they fit without breaking) and then execute the superclass
 798          * behavior.
 799          *
 800          * @param v the child view to forward the event to.
 801          * @param e the change information from the associated document
 802          * @param a the current allocation of the view
 803          * @param f the factory to use to rebuild if the view has children
 804          * @see #forwardUpdate
 805          * @since 1.3
 806          */
 807         protected void forwardUpdateToView(View v, DocumentEvent e,
 808                                            Shape a, ViewFactory f) {
 809             View parent = v.getParent();
 810             v.setParent(this);
 811             super.forwardUpdateToView(v, e, a, f);
 812             v.setParent(parent);
 813         }
 814 
 815         /** {@inheritDoc} */
 816         @Override
 817         protected void forwardUpdate(DocumentEvent.ElementChange ec,
 818                                           DocumentEvent e, Shape a, ViewFactory f) {
 819             // Update the view responsible for the changed element by invocation of
 820             // super method.
 821             super.forwardUpdate(ec, e, a, f);
 822             // Re-calculate the update indexes and update the views followed by
 823             // the changed place. Note: we update the views only when insertion or
 824             // removal takes place.
 825             DocumentEvent.EventType type = e.getType();
 826             if (type == DocumentEvent.EventType.INSERT ||
 827                 type == DocumentEvent.EventType.REMOVE) {
 828                 firstUpdateIndex = Math.min((lastUpdateIndex + 1), (getViewCount() - 1));
 829                 lastUpdateIndex = Math.max((getViewCount() - 1), 0);
 830                 for (int i = firstUpdateIndex; i <= lastUpdateIndex; i++) {
 831                     View v = getView(i);
 832                     if (v != null) {
 833                         v.updateAfterChange();
 834                     }
 835                 }
 836             }
 837         }
 838 
 839         // The following methods don't do anything useful, they
 840         // simply keep the class from being abstract.
 841 
 842         /**
 843          * Renders using the given rendering surface and area on that
 844          * surface.  This is implemented to do nothing, the logical
 845          * view is never visible.
 846          *
 847          * @param g the rendering surface to use
 848          * @param allocation the allocated region to render into
 849          * @see View#paint
 850          */
 851         public void paint(Graphics g, Shape allocation) {
 852         }
 853 
 854         /**
 855          * Tests whether a point lies before the rectangle range.
 856          * Implemented to return false, as hit detection is not
 857          * performed on the logical view.
 858          *
 859          * @param x the X coordinate &gt;= 0
 860          * @param y the Y coordinate &gt;= 0
 861          * @param alloc the rectangle
 862          * @return true if the point is before the specified range
 863          */
 864         protected boolean isBefore(int x, int y, Rectangle alloc) {
 865             return false;
 866         }
 867 
 868         /**
 869          * Tests whether a point lies after the rectangle range.
 870          * Implemented to return false, as hit detection is not
 871          * performed on the logical view.
 872          *
 873          * @param x the X coordinate &gt;= 0
 874          * @param y the Y coordinate &gt;= 0
 875          * @param alloc the rectangle
 876          * @return true if the point is after the specified range
 877          */
 878         protected boolean isAfter(int x, int y, Rectangle alloc) {
 879             return false;
 880         }
 881 
 882         /**
 883          * Fetches the child view at the given point.
 884          * Implemented to return null, as hit detection is not
 885          * performed on the logical view.
 886          *
 887          * @param x the X coordinate &gt;= 0
 888          * @param y the Y coordinate &gt;= 0
 889          * @param alloc the parent's allocation on entry, which should
 890          *   be changed to the child's allocation on exit
 891          * @return the child view
 892          */
 893         protected View getViewAtPoint(int x, int y, Rectangle alloc) {
 894             return null;
 895         }
 896 
 897         /**
 898          * Returns the allocation for a given child.
 899          * Implemented to do nothing, as the logical view doesn't
 900          * perform layout on the children.
 901          *
 902          * @param index the index of the child, &gt;= 0 &amp;&amp; &lt; getViewCount()
 903          * @param a  the allocation to the interior of the box on entry,
 904          *   and the allocation of the child view at the index on exit.
 905          */
 906         protected void childAllocation(int index, Rectangle a) {
 907         }
 908     }
 909 
 910 
 911 }