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