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 >= 0 and < 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 >= 0 and < 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 >= 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 >= 0. This is 179 * the width inside of the inset area. 180 * @param height the height to lay out against >= 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 >= 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 >= 0 844 * @param y the Y coordinate >= 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 >= 0 858 * @param y the Y coordinate >= 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 >= 0 872 * @param y the Y coordinate >= 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, >= 0 && < 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 }