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