1 /* 2 * Copyright (c) 1997, 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.io.PrintStream; 28 import java.util.Vector; 29 import java.awt.*; 30 import javax.swing.event.DocumentEvent; 31 import javax.swing.SizeRequirements; 32 33 /** 34 * A view that arranges its children into a box shape by tiling 35 * its children along an axis. The box is somewhat like that 36 * found in TeX where there is alignment of the 37 * children, flexibility of the children is considered, etc. 38 * This is a building block that might be useful to represent 39 * things like a collection of lines, paragraphs, 40 * lists, columns, pages, etc. The axis along which the children are tiled is 41 * considered the major axis. The orthogonal axis is the minor axis. 42 * <p> 43 * Layout for each axis is handled separately by the methods 44 * <code>layoutMajorAxis</code> and <code>layoutMinorAxis</code>. 45 * Subclasses can change the layout algorithm by 46 * reimplementing these methods. These methods will be called 47 * as necessary depending upon whether or not there is cached 48 * layout information and the cache is considered 49 * valid. These methods are typically called if the given size 50 * along the axis changes, or if <code>layoutChanged</code> is 51 * called to force an updated layout. The <code>layoutChanged</code> 52 * method invalidates cached layout information, if there is any. 53 * The requirements published to the parent view are calculated by 54 * the methods <code>calculateMajorAxisRequirements</code> 55 * and <code>calculateMinorAxisRequirements</code>. 56 * If the layout algorithm is changed, these methods will 57 * likely need to be reimplemented. 58 * 59 * @author Timothy Prinzing 60 */ 61 public class BoxView extends CompositeView { 62 63 /** 64 * Constructs a <code>BoxView</code>. 65 * 66 * @param elem the element this view is responsible for 67 * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code> 68 */ 69 public BoxView(Element elem, int axis) { 70 super(elem); 71 tempRect = new Rectangle(); 72 this.majorAxis = axis; 73 74 majorOffsets = new int[0]; 75 majorSpans = new int[0]; 76 majorReqValid = false; 77 majorAllocValid = false; 78 minorOffsets = new int[0]; 79 minorSpans = new int[0]; 80 minorReqValid = false; 81 minorAllocValid = false; 82 } 83 84 /** 85 * Fetches the tile axis property. This is the axis along which 86 * the child views are tiled. 87 * 88 * @return the major axis of the box, either 89 * <code>View.X_AXIS</code> or <code>View.Y_AXIS</code> 90 * 91 * @since 1.3 92 */ 93 public int getAxis() { 94 return majorAxis; 95 } 96 97 /** 98 * Sets the tile axis property. This is the axis along which 99 * the child views are tiled. 100 * 101 * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code> 102 * 103 * @since 1.3 104 */ 105 public void setAxis(int axis) { 106 boolean axisChanged = (axis != majorAxis); 107 majorAxis = axis; 108 if (axisChanged) { 109 preferenceChanged(null, true, true); 110 } 111 } 112 113 /** 114 * Invalidates the layout along an axis. This happens 115 * automatically if the preferences have changed for 116 * any of the child views. In some cases the layout 117 * may need to be recalculated when the preferences 118 * have not changed. The layout can be marked as 119 * invalid by calling this method. The layout will 120 * be updated the next time the <code>setSize</code> method 121 * is called on this view (typically in paint). 122 * 123 * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code> 124 * 125 * @since 1.3 126 */ 127 public void layoutChanged(int axis) { 128 if (axis == majorAxis) { 129 majorAllocValid = false; 130 } else { 131 minorAllocValid = false; 132 } 133 } 134 135 /** 136 * Determines if the layout is valid along the given axis. 137 * @return if the layout is valid along the given axis 138 * 139 * @param axis either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code> 140 * 141 * @since 1.4 142 */ 143 protected boolean isLayoutValid(int axis) { 144 if (axis == majorAxis) { 145 return majorAllocValid; 146 } else { 147 return minorAllocValid; 148 } 149 } 150 151 /** 152 * Paints a child. By default 153 * that is all it does, but a subclass can use this to paint 154 * things relative to the child. 155 * 156 * @param g the graphics context 157 * @param alloc the allocated region to paint into 158 * @param index the child index, >= 0 && < getViewCount() 159 */ 160 protected void paintChild(Graphics g, Rectangle alloc, int index) { 161 View child = getView(index); 162 child.paint(g, alloc); 163 } 164 165 // --- View methods --------------------------------------------- 166 167 /** 168 * Invalidates the layout and resizes the cache of 169 * requests/allocations. The child allocations can still 170 * be accessed for the old layout, but the new children 171 * will have an offset and span of 0. 172 * 173 * @param index the starting index into the child views to insert 174 * the new views; this should be a value >= 0 and <= getViewCount 175 * @param length the number of existing child views to remove; 176 * This should be a value >= 0 and <= (getViewCount() - offset) 177 * @param elems the child views to add; this value can be 178 * <code>null</code>to indicate no children are being added 179 * (useful to remove) 180 */ 181 public void replace(int index, int length, View[] elems) { 182 super.replace(index, length, elems); 183 184 // invalidate cache 185 int nInserted = (elems != null) ? elems.length : 0; 186 majorOffsets = updateLayoutArray(majorOffsets, index, nInserted); 187 majorSpans = updateLayoutArray(majorSpans, index, nInserted); 188 majorReqValid = false; 189 majorAllocValid = false; 190 minorOffsets = updateLayoutArray(minorOffsets, index, nInserted); 191 minorSpans = updateLayoutArray(minorSpans, index, nInserted); 192 minorReqValid = false; 193 minorAllocValid = false; 194 } 195 196 /** 197 * Resizes the given layout array to match the new number of 198 * child views. The current number of child views are used to 199 * produce the new array. The contents of the old array are 200 * inserted into the new array at the appropriate places so that 201 * the old layout information is transferred to the new array. 202 * 203 * @param oldArray the original layout array 204 * @param offset location where new views will be inserted 205 * @param nInserted the number of child views being inserted; 206 * therefore the number of blank spaces to leave in the 207 * new array at location <code>offset</code> 208 * @return the new layout array 209 */ 210 int[] updateLayoutArray(int[] oldArray, int offset, int nInserted) { 211 int n = getViewCount(); 212 int[] newArray = new int[n]; 213 214 System.arraycopy(oldArray, 0, newArray, 0, offset); 215 System.arraycopy(oldArray, offset, 216 newArray, offset + nInserted, n - nInserted - offset); 217 return newArray; 218 } 219 220 /** 221 * Forwards the given <code>DocumentEvent</code> to the child views 222 * that need to be notified of the change to the model. 223 * If a child changed its requirements and the allocation 224 * was valid prior to forwarding the portion of the box 225 * from the starting child to the end of the box will 226 * be repainted. 227 * 228 * @param ec changes to the element this view is responsible 229 * for (may be <code>null</code> if there were no changes) 230 * @param e the change information from the associated document 231 * @param a the current allocation of the view 232 * @param f the factory to use to rebuild if the view has children 233 * @see #insertUpdate 234 * @see #removeUpdate 235 * @see #changedUpdate 236 * @since 1.3 237 */ 238 protected void forwardUpdate(DocumentEvent.ElementChange ec, 239 DocumentEvent e, Shape a, ViewFactory f) { 240 boolean wasValid = isLayoutValid(majorAxis); 241 super.forwardUpdate(ec, e, a, f); 242 243 // determine if a repaint is needed 244 if (wasValid && (! isLayoutValid(majorAxis))) { 245 // Repaint is needed because one of the tiled children 246 // have changed their span along the major axis. If there 247 // is a hosting component and an allocated shape we repaint. 248 Component c = getContainer(); 249 if ((a != null) && (c != null)) { 250 int pos = e.getOffset(); 251 int index = getViewIndexAtPosition(pos); 252 Rectangle alloc = getInsideAllocation(a); 253 if (majorAxis == X_AXIS) { 254 alloc.x += majorOffsets[index]; 255 alloc.width -= majorOffsets[index]; 256 } else { 257 alloc.y += minorOffsets[index]; 258 alloc.height -= minorOffsets[index]; 259 } 260 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height); 261 } 262 } 263 } 264 265 /** 266 * This is called by a child to indicate its 267 * preferred span has changed. This is implemented to 268 * throw away cached layout information so that new 269 * calculations will be done the next time the children 270 * need an allocation. 271 * 272 * @param child the child view 273 * @param width true if the width preference should change 274 * @param height true if the height preference should change 275 */ 276 public void preferenceChanged(View child, boolean width, boolean height) { 277 boolean majorChanged = (majorAxis == X_AXIS) ? width : height; 278 boolean minorChanged = (majorAxis == X_AXIS) ? height : width; 279 if (majorChanged) { 280 majorReqValid = false; 281 majorAllocValid = false; 282 } 283 if (minorChanged) { 284 minorReqValid = false; 285 minorAllocValid = false; 286 } 287 super.preferenceChanged(child, width, height); 288 } 289 290 /** 291 * Gets the resize weight. A value of 0 or less is not resizable. 292 * 293 * @param axis may be either <code>View.X_AXIS</code> or 294 * <code>View.Y_AXIS</code> 295 * @return the weight 296 * @exception IllegalArgumentException for an invalid axis 297 */ 298 public int getResizeWeight(int axis) { 299 checkRequests(axis); 300 if (axis == majorAxis) { 301 if ((majorRequest.preferred != majorRequest.minimum) || 302 (majorRequest.preferred != majorRequest.maximum)) { 303 return 1; 304 } 305 } else { 306 if ((minorRequest.preferred != minorRequest.minimum) || 307 (minorRequest.preferred != minorRequest.maximum)) { 308 return 1; 309 } 310 } 311 return 0; 312 } 313 314 /** 315 * Sets the size of the view along an axis. This should cause 316 * layout of the view along the given axis. 317 * 318 * @param axis may be either <code>View.X_AXIS</code> or 319 * <code>View.Y_AXIS</code> 320 * @param span the span to layout to >= 0 321 */ 322 void setSpanOnAxis(int axis, float span) { 323 if (axis == majorAxis) { 324 if (majorSpan != (int) span) { 325 majorAllocValid = false; 326 } 327 if (! majorAllocValid) { 328 // layout the major axis 329 majorSpan = (int) span; 330 checkRequests(majorAxis); 331 layoutMajorAxis(majorSpan, axis, majorOffsets, majorSpans); 332 majorAllocValid = true; 333 334 // flush changes to the children 335 updateChildSizes(); 336 } 337 } else { 338 if (((int) span) != minorSpan) { 339 minorAllocValid = false; 340 } 341 if (! minorAllocValid) { 342 // layout the minor axis 343 minorSpan = (int) span; 344 checkRequests(axis); 345 layoutMinorAxis(minorSpan, axis, minorOffsets, minorSpans); 346 minorAllocValid = true; 347 348 // flush changes to the children 349 updateChildSizes(); 350 } 351 } 352 } 353 354 /** 355 * Propagates the current allocations to the child views. 356 */ 357 void updateChildSizes() { 358 int n = getViewCount(); 359 if (majorAxis == X_AXIS) { 360 for (int i = 0; i < n; i++) { 361 View v = getView(i); 362 v.setSize((float) majorSpans[i], (float) minorSpans[i]); 363 } 364 } else { 365 for (int i = 0; i < n; i++) { 366 View v = getView(i); 367 v.setSize((float) minorSpans[i], (float) majorSpans[i]); 368 } 369 } 370 } 371 372 /** 373 * Returns the size of the view along an axis. This is implemented 374 * to return zero. 375 * 376 * @param axis may be either <code>View.X_AXIS</code> or 377 * <code>View.Y_AXIS</code> 378 * @return the current span of the view along the given axis, >= 0 379 */ 380 float getSpanOnAxis(int axis) { 381 if (axis == majorAxis) { 382 return majorSpan; 383 } else { 384 return minorSpan; 385 } 386 } 387 388 /** 389 * Sets the size of the view. This should cause 390 * layout of the view if the view caches any layout 391 * information. This is implemented to call the 392 * layout method with the sizes inside of the insets. 393 * 394 * @param width the width >= 0 395 * @param height the height >= 0 396 */ 397 public void setSize(float width, float height) { 398 layout(Math.max(0, (int)(width - getLeftInset() - getRightInset())), 399 Math.max(0, (int)(height - getTopInset() - getBottomInset()))); 400 } 401 402 /** 403 * Renders the <code>BoxView</code> using the given 404 * rendering surface and area 405 * on that surface. Only the children that intersect 406 * the clip bounds of the given <code>Graphics</code> 407 * will be rendered. 408 * 409 * @param g the rendering surface to use 410 * @param allocation the allocated region to render into 411 * @see View#paint 412 */ 413 public void paint(Graphics g, Shape allocation) { 414 Rectangle alloc = (allocation instanceof Rectangle) ? 415 (Rectangle)allocation : allocation.getBounds(); 416 int n = getViewCount(); 417 int x = alloc.x + getLeftInset(); 418 int y = alloc.y + getTopInset(); 419 Rectangle clip = g.getClipBounds(); 420 for (int i = 0; i < n; i++) { 421 tempRect.x = x + getOffset(X_AXIS, i); 422 tempRect.y = y + getOffset(Y_AXIS, i); 423 tempRect.width = getSpan(X_AXIS, i); 424 tempRect.height = getSpan(Y_AXIS, i); 425 int trx0 = tempRect.x, trx1 = trx0 + tempRect.width; 426 int try0 = tempRect.y, try1 = try0 + tempRect.height; 427 int crx0 = clip.x, crx1 = crx0 + clip.width; 428 int cry0 = clip.y, cry1 = cry0 + clip.height; 429 // We should paint views that intersect with clipping region 430 // even if the intersection has no inside points (is a line). 431 // This is needed for supporting views that have zero width, like 432 // views that contain only combining marks. 433 if ((trx1 >= crx0) && (try1 >= cry0) && (crx1 >= trx0) && (cry1 >= try0)) { 434 paintChild(g, tempRect, i); 435 } 436 } 437 } 438 439 /** 440 * Fetches the allocation for the given child view. 441 * This enables finding out where various views 442 * are located. This is implemented to return 443 * <code>null</code> if the layout is invalid, 444 * otherwise the superclass behavior is executed. 445 * 446 * @param index the index of the child, >= 0 && > getViewCount() 447 * @param a the allocation to this view 448 * @return the allocation to the child; or <code>null</code> 449 * if <code>a</code> is <code>null</code>; 450 * or <code>null</code> if the layout is invalid 451 */ 452 public Shape getChildAllocation(int index, Shape a) { 453 if (a != null) { 454 Shape ca = super.getChildAllocation(index, a); 455 if ((ca != null) && (! isAllocationValid())) { 456 // The child allocation may not have been set yet. 457 Rectangle r = (ca instanceof Rectangle) ? 458 (Rectangle) ca : ca.getBounds(); 459 if ((r.width == 0) && (r.height == 0)) { 460 return null; 461 } 462 } 463 return ca; 464 } 465 return null; 466 } 467 468 /** 469 * Provides a mapping from the document model coordinate space 470 * to the coordinate space of the view mapped to it. This makes 471 * sure the allocation is valid before calling the superclass. 472 * 473 * @param pos the position to convert >= 0 474 * @param a the allocated region to render into 475 * @return the bounding box of the given position 476 * @exception BadLocationException if the given position does 477 * not represent a valid location in the associated document 478 * @see View#modelToView 479 */ 480 public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { 481 if (! isAllocationValid()) { 482 Rectangle alloc = a.getBounds(); 483 setSize(alloc.width, alloc.height); 484 } 485 return super.modelToView(pos, a, b); 486 } 487 488 /** 489 * Provides a mapping from the view coordinate space to the logical 490 * coordinate space of the model. 491 * 492 * @param x x coordinate of the view location to convert >= 0 493 * @param y y coordinate of the view location to convert >= 0 494 * @param a the allocated region to render into 495 * @return the location within the model that best represents the 496 * given point in the view >= 0 497 * @see View#viewToModel 498 */ 499 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { 500 if (! isAllocationValid()) { 501 Rectangle alloc = a.getBounds(); 502 setSize(alloc.width, alloc.height); 503 } 504 return super.viewToModel(x, y, a, bias); 505 } 506 507 /** 508 * Determines the desired alignment for this view along an 509 * axis. This is implemented to give the total alignment 510 * needed to position the children with the alignment points 511 * lined up along the axis orthogonal to the axis that is 512 * being tiled. The axis being tiled will request to be 513 * centered (i.e. 0.5f). 514 * 515 * @param axis may be either <code>View.X_AXIS</code> 516 * or <code>View.Y_AXIS</code> 517 * @return the desired alignment >= 0.0f && <= 1.0f; this should 518 * be a value between 0.0 and 1.0 where 0 indicates alignment at the 519 * origin and 1.0 indicates alignment to the full span 520 * away from the origin; an alignment of 0.5 would be the 521 * center of the view 522 * @exception IllegalArgumentException for an invalid axis 523 */ 524 public float getAlignment(int axis) { 525 checkRequests(axis); 526 if (axis == majorAxis) { 527 return majorRequest.alignment; 528 } else { 529 return minorRequest.alignment; 530 } 531 } 532 533 /** 534 * Determines the preferred span for this view along an 535 * axis. 536 * 537 * @param axis may be either <code>View.X_AXIS</code> 538 * or <code>View.Y_AXIS</code> 539 * @return the span the view would like to be rendered into >= 0; 540 * typically the view is told to render into the span 541 * that is returned, although there is no guarantee; 542 * the parent may choose to resize or break the view 543 * @exception IllegalArgumentException for an invalid axis type 544 */ 545 public float getPreferredSpan(int axis) { 546 checkRequests(axis); 547 float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() : 548 getTopInset() + getBottomInset(); 549 if (axis == majorAxis) { 550 return ((float)majorRequest.preferred) + marginSpan; 551 } else { 552 return ((float)minorRequest.preferred) + marginSpan; 553 } 554 } 555 556 /** 557 * Determines the minimum span for this view along an 558 * axis. 559 * 560 * @param axis may be either <code>View.X_AXIS</code> 561 * or <code>View.Y_AXIS</code> 562 * @return the span the view would like to be rendered into >= 0; 563 * typically the view is told to render into the span 564 * that is returned, although there is no guarantee; 565 * the parent may choose to resize or break the view 566 * @exception IllegalArgumentException for an invalid axis type 567 */ 568 public float getMinimumSpan(int axis) { 569 checkRequests(axis); 570 float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() : 571 getTopInset() + getBottomInset(); 572 if (axis == majorAxis) { 573 return ((float)majorRequest.minimum) + marginSpan; 574 } else { 575 return ((float)minorRequest.minimum) + marginSpan; 576 } 577 } 578 579 /** 580 * Determines the maximum span for this view along an 581 * axis. 582 * 583 * @param axis may be either <code>View.X_AXIS</code> 584 * or <code>View.Y_AXIS</code> 585 * @return the span the view would like to be rendered into >= 0; 586 * typically the view is told to render into the span 587 * that is returned, although there is no guarantee; 588 * the parent may choose to resize or break the view 589 * @exception IllegalArgumentException for an invalid axis type 590 */ 591 public float getMaximumSpan(int axis) { 592 checkRequests(axis); 593 float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() : 594 getTopInset() + getBottomInset(); 595 if (axis == majorAxis) { 596 return ((float)majorRequest.maximum) + marginSpan; 597 } else { 598 return ((float)minorRequest.maximum) + marginSpan; 599 } 600 } 601 602 // --- local methods ---------------------------------------------------- 603 604 /** 605 * Are the allocations for the children still 606 * valid? 607 * 608 * @return true if allocations still valid 609 */ 610 protected boolean isAllocationValid() { 611 return (majorAllocValid && minorAllocValid); 612 } 613 614 /** 615 * Determines if a point falls before an allocated region. 616 * 617 * @param x the X coordinate >= 0 618 * @param y the Y coordinate >= 0 619 * @param innerAlloc the allocated region; this is the area 620 * inside of the insets 621 * @return true if the point lies before the region else false 622 */ 623 protected boolean isBefore(int x, int y, Rectangle innerAlloc) { 624 if (majorAxis == View.X_AXIS) { 625 return (x < innerAlloc.x); 626 } else { 627 return (y < innerAlloc.y); 628 } 629 } 630 631 /** 632 * Determines if a point falls after an allocated region. 633 * 634 * @param x the X coordinate >= 0 635 * @param y the Y coordinate >= 0 636 * @param innerAlloc the allocated region; this is the area 637 * inside of the insets 638 * @return true if the point lies after the region else false 639 */ 640 protected boolean isAfter(int x, int y, Rectangle innerAlloc) { 641 if (majorAxis == View.X_AXIS) { 642 return (x > (innerAlloc.width + innerAlloc.x)); 643 } else { 644 return (y > (innerAlloc.height + innerAlloc.y)); 645 } 646 } 647 648 /** 649 * Fetches the child view at the given coordinates. 650 * 651 * @param x the X coordinate >= 0 652 * @param y the Y coordinate >= 0 653 * @param alloc the parents inner allocation on entry, which should 654 * be changed to the child's allocation on exit 655 * @return the view 656 */ 657 protected View getViewAtPoint(int x, int y, Rectangle alloc) { 658 int n = getViewCount(); 659 if (majorAxis == View.X_AXIS) { 660 if (x < (alloc.x + majorOffsets[0])) { 661 childAllocation(0, alloc); 662 return getView(0); 663 } 664 for (int i = 0; i < n; i++) { 665 if (x < (alloc.x + majorOffsets[i])) { 666 childAllocation(i - 1, alloc); 667 return getView(i - 1); 668 } 669 } 670 childAllocation(n - 1, alloc); 671 return getView(n - 1); 672 } else { 673 if (y < (alloc.y + majorOffsets[0])) { 674 childAllocation(0, alloc); 675 return getView(0); 676 } 677 for (int i = 0; i < n; i++) { 678 if (y < (alloc.y + majorOffsets[i])) { 679 childAllocation(i - 1, alloc); 680 return getView(i - 1); 681 } 682 } 683 childAllocation(n - 1, alloc); 684 return getView(n - 1); 685 } 686 } 687 688 /** 689 * Allocates a region for a child view. 690 * 691 * @param index the index of the child view to 692 * allocate, >= 0 && < getViewCount() 693 * @param alloc the allocated region 694 */ 695 protected void childAllocation(int index, Rectangle alloc) { 696 alloc.x += getOffset(X_AXIS, index); 697 alloc.y += getOffset(Y_AXIS, index); 698 alloc.width = getSpan(X_AXIS, index); 699 alloc.height = getSpan(Y_AXIS, index); 700 } 701 702 /** 703 * Perform layout on the box 704 * 705 * @param width the width (inside of the insets) >= 0 706 * @param height the height (inside of the insets) >= 0 707 */ 708 protected void layout(int width, int height) { 709 setSpanOnAxis(X_AXIS, width); 710 setSpanOnAxis(Y_AXIS, height); 711 } 712 713 /** 714 * Returns the current width of the box. This is the width that 715 * it was last allocated. 716 * @return the current width of the box 717 */ 718 public int getWidth() { 719 int span; 720 if (majorAxis == X_AXIS) { 721 span = majorSpan; 722 } else { 723 span = minorSpan; 724 } 725 span += getLeftInset() - getRightInset(); 726 return span; 727 } 728 729 /** 730 * Returns the current height of the box. This is the height that 731 * it was last allocated. 732 * @return the current height of the box 733 */ 734 public int getHeight() { 735 int span; 736 if (majorAxis == Y_AXIS) { 737 span = majorSpan; 738 } else { 739 span = minorSpan; 740 } 741 span += getTopInset() - getBottomInset(); 742 return span; 743 } 744 745 /** 746 * Performs layout for the major axis of the box (i.e. the 747 * axis that it represents). The results of the layout (the 748 * offset and span for each children) are placed in the given 749 * arrays which represent the allocations to the children 750 * along the major axis. 751 * 752 * @param targetSpan the total span given to the view, which 753 * would be used to layout the children 754 * @param axis the axis being layed out 755 * @param offsets the offsets from the origin of the view for 756 * each of the child views; this is a return value and is 757 * filled in by the implementation of this method 758 * @param spans the span of each child view; this is a return 759 * value and is filled in by the implementation of this method 760 */ 761 protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { 762 /* 763 * first pass, calculate the preferred sizes 764 * and the flexibility to adjust the sizes. 765 */ 766 long preferred = 0; 767 int n = getViewCount(); 768 for (int i = 0; i < n; i++) { 769 View v = getView(i); 770 spans[i] = (int) v.getPreferredSpan(axis); 771 preferred += spans[i]; 772 } 773 774 /* 775 * Second pass, expand or contract by as much as possible to reach 776 * the target span. 777 */ 778 779 // determine the adjustment to be made 780 long desiredAdjustment = targetSpan - preferred; 781 float adjustmentFactor = 0.0f; 782 int[] diffs = null; 783 784 if (desiredAdjustment != 0) { 785 long totalSpan = 0; 786 diffs = new int[n]; 787 for (int i = 0; i < n; i++) { 788 View v = getView(i); 789 int tmp; 790 if (desiredAdjustment < 0) { 791 tmp = (int)v.getMinimumSpan(axis); 792 diffs[i] = spans[i] - tmp; 793 } else { 794 tmp = (int)v.getMaximumSpan(axis); 795 diffs[i] = tmp - spans[i]; 796 } 797 totalSpan += tmp; 798 } 799 800 float maximumAdjustment = Math.abs(totalSpan - preferred); 801 adjustmentFactor = desiredAdjustment / maximumAdjustment; 802 adjustmentFactor = Math.min(adjustmentFactor, 1.0f); 803 adjustmentFactor = Math.max(adjustmentFactor, -1.0f); 804 } 805 806 // make the adjustments 807 int totalOffset = 0; 808 for (int i = 0; i < n; i++) { 809 offsets[i] = totalOffset; 810 if (desiredAdjustment != 0) { 811 float adjF = adjustmentFactor * diffs[i]; 812 spans[i] += Math.round(adjF); 813 } 814 totalOffset = (int) Math.min((long) totalOffset + (long) spans[i], Integer.MAX_VALUE); 815 } 816 } 817 818 /** 819 * Performs layout for the minor axis of the box (i.e. the 820 * axis orthogonal to the axis that it represents). The results 821 * of the layout (the offset and span for each children) are 822 * placed in the given arrays which represent the allocations to 823 * the children along the minor axis. 824 * 825 * @param targetSpan the total span given to the view, which 826 * would be used to layout the children 827 * @param axis the axis being layed out 828 * @param offsets the offsets from the origin of the view for 829 * each of the child views; this is a return value and is 830 * filled in by the implementation of this method 831 * @param spans the span of each child view; this is a return 832 * value and is filled in by the implementation of this method 833 */ 834 protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { 835 int n = getViewCount(); 836 for (int i = 0; i < n; i++) { 837 View v = getView(i); 838 int max = (int) v.getMaximumSpan(axis); 839 if (max < targetSpan) { 840 // can't make the child this wide, align it 841 float align = v.getAlignment(axis); 842 offsets[i] = (int) ((targetSpan - max) * align); 843 spans[i] = max; 844 } else { 845 // make it the target width, or as small as it can get. 846 int min = (int)v.getMinimumSpan(axis); 847 offsets[i] = 0; 848 spans[i] = Math.max(min, targetSpan); 849 } 850 } 851 } 852 853 /** 854 * Calculates the size requirements for the major axis 855 * <code>axis</code>. 856 * 857 * @param axis the axis being studied 858 * @param r the <code>SizeRequirements</code> object; 859 * if <code>null</code> one will be created 860 * @return the newly initialized <code>SizeRequirements</code> object 861 * @see javax.swing.SizeRequirements 862 */ 863 protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) { 864 // calculate tiled request 865 float min = 0; 866 float pref = 0; 867 float max = 0; 868 869 int n = getViewCount(); 870 for (int i = 0; i < n; i++) { 871 View v = getView(i); 872 min += v.getMinimumSpan(axis); 873 pref += v.getPreferredSpan(axis); 874 max += v.getMaximumSpan(axis); 875 } 876 877 if (r == null) { 878 r = new SizeRequirements(); 879 } 880 r.alignment = 0.5f; 881 r.minimum = (int) min; 882 r.preferred = (int) pref; 883 r.maximum = (int) max; 884 return r; 885 } 886 887 /** 888 * Calculates the size requirements for the minor axis 889 * <code>axis</code>. 890 * 891 * @param axis the axis being studied 892 * @param r the <code>SizeRequirements</code> object; 893 * if <code>null</code> one will be created 894 * @return the newly initialized <code>SizeRequirements</code> object 895 * @see javax.swing.SizeRequirements 896 */ 897 protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { 898 int min = 0; 899 long pref = 0; 900 int max = Integer.MAX_VALUE; 901 int n = getViewCount(); 902 for (int i = 0; i < n; i++) { 903 View v = getView(i); 904 min = Math.max((int) v.getMinimumSpan(axis), min); 905 pref = Math.max((int) v.getPreferredSpan(axis), pref); 906 max = Math.max((int) v.getMaximumSpan(axis), max); 907 } 908 909 if (r == null) { 910 r = new SizeRequirements(); 911 r.alignment = 0.5f; 912 } 913 r.preferred = (int) pref; 914 r.minimum = min; 915 r.maximum = max; 916 return r; 917 } 918 919 /** 920 * Checks the request cache and update if needed. 921 * @param axis the axis being studied 922 * @exception IllegalArgumentException if <code>axis</code> is 923 * neither <code>View.X_AXIS</code> nor <code>View.Y_AXIS</code> 924 */ 925 void checkRequests(int axis) { 926 if ((axis != X_AXIS) && (axis != Y_AXIS)) { 927 throw new IllegalArgumentException("Invalid axis: " + axis); 928 } 929 if (axis == majorAxis) { 930 if (!majorReqValid) { 931 majorRequest = calculateMajorAxisRequirements(axis, 932 majorRequest); 933 majorReqValid = true; 934 } 935 } else if (! minorReqValid) { 936 minorRequest = calculateMinorAxisRequirements(axis, minorRequest); 937 minorReqValid = true; 938 } 939 } 940 941 /** 942 * Computes the location and extent of each child view 943 * in this <code>BoxView</code> given the <code>targetSpan</code>, 944 * which is the width (or height) of the region we have to 945 * work with. 946 * 947 * @param targetSpan the total span given to the view, which 948 * would be used to layout the children 949 * @param axis the axis being studied, either 950 * <code>View.X_AXIS</code> or <code>View.Y_AXIS</code> 951 * @param offsets an empty array filled by this method with 952 * values specifying the location of each child view 953 * @param spans an empty array filled by this method with 954 * values specifying the extent of each child view 955 */ 956 protected void baselineLayout(int targetSpan, int axis, int[] offsets, int[] spans) { 957 int totalAscent = (int)(targetSpan * getAlignment(axis)); 958 int totalDescent = targetSpan - totalAscent; 959 960 int n = getViewCount(); 961 962 for (int i = 0; i < n; i++) { 963 View v = getView(i); 964 float align = v.getAlignment(axis); 965 float viewSpan; 966 967 if (v.getResizeWeight(axis) > 0) { 968 // if resizable then resize to the best fit 969 970 // the smallest span possible 971 float minSpan = v.getMinimumSpan(axis); 972 // the largest span possible 973 float maxSpan = v.getMaximumSpan(axis); 974 975 if (align == 0.0f) { 976 // if the alignment is 0 then we need to fit into the descent 977 viewSpan = Math.max(Math.min(maxSpan, totalDescent), minSpan); 978 } else if (align == 1.0f) { 979 // if the alignment is 1 then we need to fit into the ascent 980 viewSpan = Math.max(Math.min(maxSpan, totalAscent), minSpan); 981 } else { 982 // figure out the span that we must fit into 983 float fitSpan = Math.min(totalAscent / align, 984 totalDescent / (1.0f - align)); 985 // fit into the calculated span 986 viewSpan = Math.max(Math.min(maxSpan, fitSpan), minSpan); 987 } 988 } else { 989 // otherwise use the preferred spans 990 viewSpan = v.getPreferredSpan(axis); 991 } 992 993 offsets[i] = totalAscent - (int)(viewSpan * align); 994 spans[i] = (int)viewSpan; 995 } 996 } 997 998 /** 999 * Calculates the size requirements for this <code>BoxView</code> 1000 * by examining the size of each child view. 1001 * 1002 * @param axis the axis being studied 1003 * @param r the <code>SizeRequirements</code> object; 1004 * if <code>null</code> one will be created 1005 * @return the newly initialized <code>SizeRequirements</code> object 1006 */ 1007 protected SizeRequirements baselineRequirements(int axis, SizeRequirements r) { 1008 SizeRequirements totalAscent = new SizeRequirements(); 1009 SizeRequirements totalDescent = new SizeRequirements(); 1010 1011 if (r == null) { 1012 r = new SizeRequirements(); 1013 } 1014 1015 r.alignment = 0.5f; 1016 1017 int n = getViewCount(); 1018 1019 // loop through all children calculating the max of all their ascents and 1020 // descents at minimum, preferred, and maximum sizes 1021 for (int i = 0; i < n; i++) { 1022 View v = getView(i); 1023 float align = v.getAlignment(axis); 1024 float span; 1025 int ascent; 1026 int descent; 1027 1028 // find the maximum of the preferred ascents and descents 1029 span = v.getPreferredSpan(axis); 1030 ascent = (int)(align * span); 1031 descent = (int)(span - ascent); 1032 totalAscent.preferred = Math.max(ascent, totalAscent.preferred); 1033 totalDescent.preferred = Math.max(descent, totalDescent.preferred); 1034 1035 if (v.getResizeWeight(axis) > 0) { 1036 // if the view is resizable then do the same for the minimum and 1037 // maximum ascents and descents 1038 span = v.getMinimumSpan(axis); 1039 ascent = (int)(align * span); 1040 descent = (int)(span - ascent); 1041 totalAscent.minimum = Math.max(ascent, totalAscent.minimum); 1042 totalDescent.minimum = Math.max(descent, totalDescent.minimum); 1043 1044 span = v.getMaximumSpan(axis); 1045 ascent = (int)(align * span); 1046 descent = (int)(span - ascent); 1047 totalAscent.maximum = Math.max(ascent, totalAscent.maximum); 1048 totalDescent.maximum = Math.max(descent, totalDescent.maximum); 1049 } else { 1050 // otherwise use the preferred 1051 totalAscent.minimum = Math.max(ascent, totalAscent.minimum); 1052 totalDescent.minimum = Math.max(descent, totalDescent.minimum); 1053 totalAscent.maximum = Math.max(ascent, totalAscent.maximum); 1054 totalDescent.maximum = Math.max(descent, totalDescent.maximum); 1055 } 1056 } 1057 1058 // we now have an overall preferred, minimum, and maximum ascent and descent 1059 1060 // calculate the preferred span as the sum of the preferred ascent and preferred descent 1061 r.preferred = (int)Math.min((long)totalAscent.preferred + (long)totalDescent.preferred, 1062 Integer.MAX_VALUE); 1063 1064 // calculate the preferred alignment as the preferred ascent divided by the preferred span 1065 if (r.preferred > 0) { 1066 r.alignment = (float)totalAscent.preferred / r.preferred; 1067 } 1068 1069 1070 if (r.alignment == 0.0f) { 1071 // if the preferred alignment is 0 then the minimum and maximum spans are simply 1072 // the minimum and maximum descents since there's nothing above the baseline 1073 r.minimum = totalDescent.minimum; 1074 r.maximum = totalDescent.maximum; 1075 } else if (r.alignment == 1.0f) { 1076 // if the preferred alignment is 1 then the minimum and maximum spans are simply 1077 // the minimum and maximum ascents since there's nothing below the baseline 1078 r.minimum = totalAscent.minimum; 1079 r.maximum = totalAscent.maximum; 1080 } else { 1081 // we want to honor the preferred alignment so we calculate two possible minimum 1082 // span values using 1) the minimum ascent and the alignment, and 2) the minimum 1083 // descent and the alignment. We'll choose the larger of these two numbers. 1084 r.minimum = Math.round(Math.max(totalAscent.minimum / r.alignment, 1085 totalDescent.minimum / (1.0f - r.alignment))); 1086 // a similar calculation is made for the maximum but we choose the smaller number. 1087 r.maximum = Math.round(Math.min(totalAscent.maximum / r.alignment, 1088 totalDescent.maximum / (1.0f - r.alignment))); 1089 } 1090 1091 return r; 1092 } 1093 1094 /** 1095 * Fetches the offset of a particular child's current layout. 1096 * @param axis the axis being studied 1097 * @param childIndex the index of the requested child 1098 * @return the offset (location) for the specified child 1099 */ 1100 protected int getOffset(int axis, int childIndex) { 1101 int[] offsets = (axis == majorAxis) ? majorOffsets : minorOffsets; 1102 return offsets[childIndex]; 1103 } 1104 1105 /** 1106 * Fetches the span of a particular child's current layout. 1107 * @param axis the axis being studied 1108 * @param childIndex the index of the requested child 1109 * @return the span (width or height) of the specified child 1110 */ 1111 protected int getSpan(int axis, int childIndex) { 1112 int[] spans = (axis == majorAxis) ? majorSpans : minorSpans; 1113 return spans[childIndex]; 1114 } 1115 1116 /** 1117 * Determines in which direction the next view lays. 1118 * Consider the View at index n. Typically the <code>View</code>s 1119 * are layed out from left to right, so that the <code>View</code> 1120 * to the EAST will be at index n + 1, and the <code>View</code> 1121 * to the WEST will be at index n - 1. In certain situations, 1122 * such as with bidirectional text, it is possible 1123 * that the <code>View</code> to EAST is not at index n + 1, 1124 * but rather at index n - 1, or that the <code>View</code> 1125 * to the WEST is not at index n - 1, but index n + 1. 1126 * In this case this method would return true, 1127 * indicating the <code>View</code>s are layed out in 1128 * descending order. Otherwise the method would return false 1129 * indicating the <code>View</code>s are layed out in ascending order. 1130 * <p> 1131 * If the receiver is laying its <code>View</code>s along the 1132 * <code>Y_AXIS</code>, this will return the value from 1133 * invoking the same method on the <code>View</code> 1134 * responsible for rendering <code>position</code> and 1135 * <code>bias</code>. Otherwise this will return false. 1136 * 1137 * @param position position into the model 1138 * @param bias either <code>Position.Bias.Forward</code> or 1139 * <code>Position.Bias.Backward</code> 1140 * @return true if the <code>View</code>s surrounding the 1141 * <code>View</code> responding for rendering 1142 * <code>position</code> and <code>bias</code> 1143 * are layed out in descending order; otherwise false 1144 */ 1145 protected boolean flipEastAndWestAtEnds(int position, 1146 Position.Bias bias) { 1147 if(majorAxis == Y_AXIS) { 1148 int testPos = (bias == Position.Bias.Backward) ? 1149 Math.max(0, position - 1) : position; 1150 int index = getViewIndexAtPosition(testPos); 1151 if(index != -1) { 1152 View v = getView(index); 1153 if(v != null && v instanceof CompositeView) { 1154 return ((CompositeView)v).flipEastAndWestAtEnds(position, 1155 bias); 1156 } 1157 } 1158 } 1159 return false; 1160 } 1161 1162 // --- variables ------------------------------------------------ 1163 1164 int majorAxis; 1165 1166 int majorSpan; 1167 int minorSpan; 1168 1169 /* 1170 * Request cache 1171 */ 1172 boolean majorReqValid; 1173 boolean minorReqValid; 1174 SizeRequirements majorRequest; 1175 SizeRequirements minorRequest; 1176 1177 /* 1178 * Allocation cache 1179 */ 1180 boolean majorAllocValid; 1181 int[] majorOffsets; 1182 int[] majorSpans; 1183 boolean minorAllocValid; 1184 int[] minorOffsets; 1185 int[] minorSpans; 1186 1187 /** used in paint. */ 1188 Rectangle tempRect; 1189 }