1 /* 2 * Copyright (c) 1999, 2015, 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 >= 0 and < 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 >= 0 and < 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 >= 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 >= 0. This is 187 * the width inside of the inset area. 188 * @param height the height to lay out against >= 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 >= 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 >= 0 860 * @param y the Y coordinate >= 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 >= 0 874 * @param y the Y coordinate >= 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 >= 0 888 * @param y the Y coordinate >= 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, >= 0 && < 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 }