001/* BoxView.java -- An composite view 002 Copyright (C) 2005, 2006 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.text; 040 041import java.awt.Container; 042import java.awt.Graphics; 043import java.awt.Rectangle; 044import java.awt.Shape; 045 046import javax.swing.SizeRequirements; 047import javax.swing.event.DocumentEvent; 048 049/** 050 * An implementation of {@link CompositeView} that arranges its children in 051 * a box along one axis. This is comparable to how the <code>BoxLayout</code> 052 * works, but for <code>View</code> children. 053 * 054 * @author Roman Kennke (roman@kennke.org) 055 */ 056public class BoxView 057 extends CompositeView 058{ 059 060 /** 061 * The axis along which this <code>BoxView</code> is laid out. 062 */ 063 private int myAxis; 064 065 /** 066 * Indicates if the layout is valid along X_AXIS or Y_AXIS. 067 */ 068 private boolean[] layoutValid = new boolean[2]; 069 070 /** 071 * Indicates if the requirements for an axis are valid. 072 */ 073 private boolean[] requirementsValid = new boolean[2]; 074 075 /** 076 * The spans along the X_AXIS and Y_AXIS. 077 */ 078 private int[][] spans = new int[2][]; 079 080 /** 081 * The offsets of the children along the X_AXIS and Y_AXIS. 082 */ 083 private int[][] offsets = new int[2][]; 084 085 /** 086 * The size requirements along the X_AXIS and Y_AXIS. 087 */ 088 private SizeRequirements[] requirements = new SizeRequirements[2]; 089 090 /** 091 * The current span along X_AXIS or Y_AXIS. 092 */ 093 private int[] span = new int[2]; 094 095 /** 096 * Creates a new <code>BoxView</code> for the given 097 * <code>Element</code> and axis. Valid values for the axis are 098 * {@link View#X_AXIS} and {@link View#Y_AXIS}. 099 * 100 * @param element the element that is rendered by this BoxView 101 * @param axis the axis along which the box is laid out 102 */ 103 public BoxView(Element element, int axis) 104 { 105 super(element); 106 myAxis = axis; 107 layoutValid[0] = false; 108 layoutValid[1] = false; 109 requirementsValid[X_AXIS] = false; 110 requirementsValid[Y_AXIS] = false; 111 span[0] = 0; 112 span[1] = 0; 113 requirements[0] = new SizeRequirements(); 114 requirements[1] = new SizeRequirements(); 115 116 // Initialize the cache arrays. 117 spans[0] = new int[0]; 118 spans[1] = new int[0]; 119 offsets[0] = new int[0]; 120 offsets[1] = new int[0]; 121 } 122 123 /** 124 * Returns the axis along which this <code>BoxView</code> is laid out. 125 * 126 * @return the axis along which this <code>BoxView</code> is laid out 127 * 128 * @since 1.3 129 */ 130 public int getAxis() 131 { 132 return myAxis; 133 } 134 135 /** 136 * Sets the axis along which this <code>BoxView</code> is laid out. 137 * 138 * Valid values for the axis are {@link View#X_AXIS} and 139 * {@link View#Y_AXIS}. 140 * 141 * @param axis the axis along which this <code>BoxView</code> is laid out 142 * 143 * @since 1.3 144 */ 145 public void setAxis(int axis) 146 { 147 boolean changed = axis != myAxis; 148 myAxis = axis; 149 if (changed) 150 preferenceChanged(null, true, true); 151 } 152 153 /** 154 * Marks the layout along the specified axis as invalid. This is triggered 155 * automatically when any of the child view changes its preferences 156 * via {@link #preferenceChanged(View, boolean, boolean)}. 157 * 158 * The layout will be updated the next time when 159 * {@link #setSize(float, float)} is called, typically from within the 160 * {@link #paint(Graphics, Shape)} method. 161 * 162 * Valid values for the axis are {@link View#X_AXIS} and 163 * {@link View#Y_AXIS}. 164 * 165 * @param axis an <code>int</code> value 166 * 167 * @since 1.3 168 */ 169 public void layoutChanged(int axis) 170 { 171 if (axis != X_AXIS && axis != Y_AXIS) 172 throw new IllegalArgumentException("Invalid axis parameter."); 173 layoutValid[axis] = false; 174 } 175 176 /** 177 * Returns <code>true</code> if the layout along the specified 178 * <code>axis</code> is valid, <code>false</code> otherwise. 179 * 180 * Valid values for the axis are {@link View#X_AXIS} and 181 * {@link View#Y_AXIS}. 182 * 183 * @param axis the axis 184 * 185 * @return <code>true</code> if the layout along the specified 186 * <code>axis</code> is valid, <code>false</code> otherwise 187 * 188 * @since 1.4 189 */ 190 protected boolean isLayoutValid(int axis) 191 { 192 if (axis != X_AXIS && axis != Y_AXIS) 193 throw new IllegalArgumentException("Invalid axis parameter."); 194 return layoutValid[axis]; 195 } 196 197 /** 198 * Paints the child <code>View</code> at the specified <code>index</code>. 199 * This method modifies the actual values in <code>alloc</code> so make 200 * sure you have a copy of the original values if you need them. 201 * 202 * @param g the <code>Graphics</code> context to paint to 203 * @param alloc the allocated region for the child to paint into 204 * @param index the index of the child to be painted 205 * 206 * @see #childAllocation(int, Rectangle) 207 */ 208 protected void paintChild(Graphics g, Rectangle alloc, int index) 209 { 210 View child = getView(index); 211 child.paint(g, alloc); 212 } 213 214 /** 215 * Replaces child views by some other child views. If there are no views to 216 * remove (<code>length == 0</code>), the result is a simple insert, if 217 * there are no children to add (<code>view == null</code>) the result 218 * is a simple removal. 219 * 220 * In addition this invalidates the layout and resizes the internal cache 221 * for the child allocations. The old children's cached allocations can 222 * still be accessed (although they are not guaranteed to be valid), and 223 * the new children will have an initial offset and span of 0. 224 * 225 * @param offset the start offset from where to remove children 226 * @param length the number of children to remove 227 * @param views the views that replace the removed children 228 */ 229 public void replace(int offset, int length, View[] views) 230 { 231 // Actually perform the replace. 232 super.replace(offset, length, views); 233 234 // Resize and copy data for cache arrays. 235 int newItems = views != null ? views.length : 0; 236 int minor = 1 - myAxis; 237 offsets[myAxis] = replaceLayoutArray(offsets[myAxis], offset, newItems); 238 spans[myAxis] = replaceLayoutArray(spans[myAxis], offset, newItems); 239 layoutValid[myAxis] = false; 240 requirementsValid[myAxis] = false; 241 offsets[minor] = replaceLayoutArray(offsets[minor], offset, newItems); 242 spans[minor] = replaceLayoutArray(spans[minor], offset, newItems); 243 layoutValid[minor] = false; 244 requirementsValid[minor] = false; 245 } 246 247 /** 248 * Helper method. This updates the layout cache arrays in response 249 * to a call to {@link #replace(int, int, View[])}. 250 * 251 * @param oldArray the old array 252 * 253 * @return the replaced array 254 */ 255 private int[] replaceLayoutArray(int[] oldArray, int offset, int newItems) 256 257 { 258 int num = getViewCount(); 259 int[] newArray = new int[num]; 260 System.arraycopy(oldArray, 0, newArray, 0, offset); 261 System.arraycopy(oldArray, offset, newArray, offset + newItems, 262 num - newItems - offset); 263 return newArray; 264 } 265 266 /** 267 * A Rectangle instance to be reused in the paint() method below. 268 */ 269 private final Rectangle tmpRect = new Rectangle(); 270 271 private Rectangle clipRect = new Rectangle(); 272 273 /** 274 * Renders the <code>Element</code> that is associated with this 275 * <code>View</code>. 276 * 277 * @param g the <code>Graphics</code> context to render to 278 * @param a the allocated region for the <code>Element</code> 279 */ 280 public void paint(Graphics g, Shape a) 281 { 282 // Try to avoid allocation if possible (almost all cases). 283 Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); 284 285 // This returns a cached instance. 286 alloc = getInsideAllocation(alloc); 287 288 int count = getViewCount(); 289 for (int i = 0; i < count; i++) 290 { 291 View child = getView(i); 292 tmpRect.setBounds(alloc); 293 childAllocation(i, tmpRect); 294 if (g.hitClip(tmpRect.x, tmpRect.y, tmpRect.width, tmpRect.height)) 295 paintChild(g, tmpRect, i); 296 } 297 } 298 299 /** 300 * Returns the preferred span of the content managed by this 301 * <code>View</code> along the specified <code>axis</code>. 302 * 303 * @param axis the axis 304 * 305 * @return the preferred span of this <code>View</code>. 306 */ 307 public float getPreferredSpan(int axis) 308 { 309 updateRequirements(axis); 310 // Add margin. 311 float margin; 312 if (axis == X_AXIS) 313 margin = getLeftInset() + getRightInset(); 314 else 315 margin = getTopInset() + getBottomInset(); 316 return requirements[axis].preferred + margin; 317 } 318 319 /** 320 * Returns the maximum span of this view along the specified axis. 321 * This returns <code>Integer.MAX_VALUE</code> for the minor axis 322 * and the preferred span for the major axis. 323 * 324 * @param axis the axis 325 * 326 * @return the maximum span of this view along the specified axis 327 */ 328 public float getMaximumSpan(int axis) 329 { 330 updateRequirements(axis); 331 // Add margin. 332 float margin; 333 if (axis == X_AXIS) 334 margin = getLeftInset() + getRightInset(); 335 else 336 margin = getTopInset() + getBottomInset(); 337 return requirements[axis].maximum + margin; 338 } 339 340 /** 341 * Returns the minimum span of this view along the specified axis. 342 * This calculates the minimum span using 343 * {@link #calculateMajorAxisRequirements} or 344 * {@link #calculateMinorAxisRequirements} (depending on the axis) and 345 * returns the resulting minimum span. 346 * 347 * @param axis the axis 348 * 349 * @return the minimum span of this view along the specified axis 350 */ 351 public float getMinimumSpan(int axis) 352 { 353 updateRequirements(axis); 354 // Add margin. 355 float margin; 356 if (axis == X_AXIS) 357 margin = getLeftInset() + getRightInset(); 358 else 359 margin = getTopInset() + getBottomInset(); 360 return requirements[axis].minimum + margin; 361 } 362 363 /** 364 * Calculates size requirements for a baseline layout. This is not 365 * used by the BoxView itself, but by subclasses that wish to perform 366 * a baseline layout, like the FlowView's rows. 367 * 368 * @param axis the axis that is examined 369 * @param sr the <code>SizeRequirements</code> object to hold the result, 370 * if <code>null</code>, a new one is created 371 * 372 * @return the size requirements for this <code>BoxView</code> along 373 * the specified axis 374 */ 375 protected SizeRequirements baselineRequirements(int axis, 376 SizeRequirements sr) 377 { 378 // Create new instance if sr == null. 379 if (sr == null) 380 sr = new SizeRequirements(); 381 sr.alignment = 0.5F; 382 383 // Calculate overall ascent and descent. 384 int totalAscentMin = 0; 385 int totalAscentPref = 0; 386 int totalAscentMax = 0; 387 int totalDescentMin = 0; 388 int totalDescentPref = 0; 389 int totalDescentMax = 0; 390 391 int count = getViewCount(); 392 for (int i = 0; i < count; i++) 393 { 394 View v = getView(i); 395 float align = v.getAlignment(axis); 396 int span = (int) v.getPreferredSpan(axis); 397 int ascent = (int) (align * span); 398 int descent = span - ascent; 399 400 totalAscentPref = Math.max(ascent, totalAscentPref); 401 totalDescentPref = Math.max(descent, totalDescentPref); 402 if (v.getResizeWeight(axis) > 0) 403 { 404 // If the view is resizable, then use the min and max size 405 // of the view. 406 span = (int) v.getMinimumSpan(axis); 407 ascent = (int) (align * span); 408 descent = span - ascent; 409 totalAscentMin = Math.max(ascent, totalAscentMin); 410 totalDescentMin = Math.max(descent, totalDescentMin); 411 412 span = (int) v.getMaximumSpan(axis); 413 ascent = (int) (align * span); 414 descent = span - ascent; 415 totalAscentMax = Math.max(ascent, totalAscentMax); 416 totalDescentMax = Math.max(descent, totalDescentMax); 417 } 418 else 419 { 420 // If the view is not resizable, use the preferred span. 421 totalAscentMin = Math.max(ascent, totalAscentMin); 422 totalDescentMin = Math.max(descent, totalDescentMin); 423 totalAscentMax = Math.max(ascent, totalAscentMax); 424 totalDescentMax = Math.max(descent, totalDescentMax); 425 } 426 } 427 428 // Preferred overall span is the sum of the preferred ascent and descent. 429 // With overflow check. 430 sr.preferred = (int) Math.min((long) totalAscentPref 431 + (long) totalDescentPref, 432 Integer.MAX_VALUE); 433 434 // Align along the baseline. 435 if (sr.preferred > 0) 436 sr.alignment = (float) totalAscentPref / sr.preferred; 437 438 if (sr.alignment == 0) 439 { 440 // Nothing above the baseline, use the descent. 441 sr.minimum = totalDescentMin; 442 sr.maximum = totalDescentMax; 443 } 444 else if (sr.alignment == 1.0F) 445 { 446 // Nothing below the baseline, use the descent. 447 sr.minimum = totalAscentMin; 448 sr.maximum = totalAscentMax; 449 } 450 else 451 { 452 sr.minimum = Math.max((int) (totalAscentMin / sr.alignment), 453 (int) (totalDescentMin / (1.0F - sr.alignment))); 454 sr.maximum = Math.min((int) (totalAscentMax / sr.alignment), 455 (int) (totalDescentMax / (1.0F - sr.alignment))); 456 } 457 return sr; 458 } 459 460 /** 461 * Calculates the baseline layout of the children of this 462 * <code>BoxView</code> along the specified axis. 463 * 464 * This is not used by the BoxView itself, but by subclasses that wish to 465 * perform a baseline layout, like the FlowView's rows. 466 * 467 * @param span the target span 468 * @param axis the axis that is examined 469 * @param offsets an empty array, filled with the offsets of the children 470 * @param spans an empty array, filled with the spans of the children 471 */ 472 protected void baselineLayout(int span, int axis, int[] offsets, 473 int[] spans) 474 { 475 int totalAscent = (int) (span * getAlignment(axis)); 476 int totalDescent = span - totalAscent; 477 478 int count = getViewCount(); 479 for (int i = 0; i < count; i++) 480 { 481 View v = getView(i); 482 float align = v.getAlignment(axis); 483 int viewSpan; 484 if (v.getResizeWeight(axis) > 0) 485 { 486 // If possible, then resize for best fit. 487 int min = (int) v.getMinimumSpan(axis); 488 int max = (int) v.getMaximumSpan(axis); 489 if (align == 0.0F) 490 viewSpan = Math.max(Math.min(max, totalDescent), min); 491 else if (align == 1.0F) 492 viewSpan = Math.max(Math.min(max, totalAscent), min); 493 else 494 { 495 int fit = (int) Math.min(totalAscent / align, 496 totalDescent / (1.0F - align)); 497 viewSpan = Math.max(Math.min(max, fit), min); 498 } 499 } 500 else 501 viewSpan = (int) v.getPreferredSpan(axis); 502 offsets[i] = totalAscent - (int) (viewSpan * align); 503 spans[i] = viewSpan; 504 } 505 } 506 507 /** 508 * Calculates the size requirements of this <code>BoxView</code> along 509 * its major axis, that is the axis specified in the constructor. 510 * 511 * @param axis the axis that is examined 512 * @param sr the <code>SizeRequirements</code> object to hold the result, 513 * if <code>null</code>, a new one is created 514 * 515 * @return the size requirements for this <code>BoxView</code> along 516 * the specified axis 517 */ 518 protected SizeRequirements calculateMajorAxisRequirements(int axis, 519 SizeRequirements sr) 520 { 521 SizeRequirements res = sr; 522 if (res == null) 523 res = new SizeRequirements(); 524 525 float min = 0; 526 float pref = 0; 527 float max = 0; 528 529 int n = getViewCount(); 530 for (int i = 0; i < n; i++) 531 { 532 View child = getView(i); 533 min += child.getMinimumSpan(axis); 534 pref += child.getPreferredSpan(axis); 535 max += child.getMaximumSpan(axis); 536 } 537 538 res.minimum = (int) min; 539 res.preferred = (int) pref; 540 res.maximum = (int) max; 541 res.alignment = 0.5F; 542 543 return res; 544 } 545 546 /** 547 * Calculates the size requirements of this <code>BoxView</code> along 548 * its minor axis, that is the axis opposite to the axis specified in the 549 * constructor. 550 * 551 * @param axis the axis that is examined 552 * @param sr the <code>SizeRequirements</code> object to hold the result, 553 * if <code>null</code>, a new one is created 554 * 555 * @return the size requirements for this <code>BoxView</code> along 556 * the specified axis 557 */ 558 protected SizeRequirements calculateMinorAxisRequirements(int axis, 559 SizeRequirements sr) 560 { 561 SizeRequirements res = sr; 562 if (res == null) 563 res = new SizeRequirements(); 564 565 res.minimum = 0; 566 res.preferred = 0; 567 res.maximum = Integer.MAX_VALUE; 568 res.alignment = 0.5F; 569 int n = getViewCount(); 570 for (int i = 0; i < n; i++) 571 { 572 View child = getView(i); 573 res.minimum = Math.max((int) child.getMinimumSpan(axis), res.minimum); 574 res.preferred = Math.max((int) child.getPreferredSpan(axis), 575 res.preferred); 576 res.maximum = Math.max((int) child.getMaximumSpan(axis), res.maximum); 577 } 578 579 return res; 580 } 581 582 583 /** 584 * Returns <code>true</code> if the specified point lies before the 585 * given <code>Rectangle</code>, <code>false</code> otherwise. 586 * 587 * "Before" is typically defined as being to the left or above. 588 * 589 * @param x the X coordinate of the point 590 * @param y the Y coordinate of the point 591 * @param r the rectangle to test the point against 592 * 593 * @return <code>true</code> if the specified point lies before the 594 * given <code>Rectangle</code>, <code>false</code> otherwise 595 */ 596 protected boolean isBefore(int x, int y, Rectangle r) 597 { 598 boolean result = false; 599 600 if (myAxis == X_AXIS) 601 result = x < r.x; 602 else 603 result = y < r.y; 604 605 return result; 606 } 607 608 /** 609 * Returns <code>true</code> if the specified point lies after the 610 * given <code>Rectangle</code>, <code>false</code> otherwise. 611 * 612 * "After" is typically defined as being to the right or below. 613 * 614 * @param x the X coordinate of the point 615 * @param y the Y coordinate of the point 616 * @param r the rectangle to test the point against 617 * 618 * @return <code>true</code> if the specified point lies after the 619 * given <code>Rectangle</code>, <code>false</code> otherwise 620 */ 621 protected boolean isAfter(int x, int y, Rectangle r) 622 { 623 boolean result = false; 624 625 if (myAxis == X_AXIS) 626 result = x > r.x + r.width; 627 else 628 result = y > r.y + r.height; 629 630 return result; 631 } 632 633 /** 634 * Returns the child <code>View</code> at the specified location. 635 * 636 * @param x the X coordinate 637 * @param y the Y coordinate 638 * @param r the inner allocation of this <code>BoxView</code> on entry, 639 * the allocation of the found child on exit 640 * 641 * @return the child <code>View</code> at the specified location 642 */ 643 protected View getViewAtPoint(int x, int y, Rectangle r) 644 { 645 View result = null; 646 int count = getViewCount(); 647 if (myAxis == X_AXIS) 648 { 649 // Border case. Requested point is left from the box. 650 if (x < r.x + offsets[X_AXIS][0]) 651 { 652 childAllocation(0, r); 653 result = getView(0); 654 } 655 else 656 { 657 // Search views inside box. 658 for (int i = 0; i < count && result == null; i++) 659 { 660 if (x < r.x + offsets[X_AXIS][i]) 661 { 662 childAllocation(i - 1, r); 663 result = getView(i - 1); 664 } 665 } 666 } 667 } 668 else // Same algorithm for Y_AXIS. 669 { 670 // Border case. Requested point is above the box. 671 if (y < r.y + offsets[Y_AXIS][0]) 672 { 673 childAllocation(0, r); 674 result = getView(0); 675 } 676 else 677 { 678 // Search views inside box. 679 for (int i = 0; i < count && result == null; i++) 680 { 681 if (y < r.y + offsets[Y_AXIS][i]) 682 { 683 childAllocation(i - 1, r); 684 result = getView(i - 1); 685 } 686 } 687 } 688 } 689 // Not found, other border case: point is right from or below the box. 690 if (result == null) 691 { 692 childAllocation(count - 1, r); 693 result = getView(count - 1); 694 } 695 return result; 696 } 697 698 /** 699 * Computes the allocation for a child <code>View</code>. The parameter 700 * <code>a</code> stores the allocation of this <code>CompositeView</code> 701 * and is then adjusted to hold the allocation of the child view. 702 * 703 * @param index 704 * the index of the child <code>View</code> 705 * @param a 706 * the allocation of this <code>CompositeView</code> before the 707 * call, the allocation of the child on exit 708 */ 709 protected void childAllocation(int index, Rectangle a) 710 { 711 a.x += offsets[X_AXIS][index]; 712 a.y += offsets[Y_AXIS][index]; 713 a.width = spans[X_AXIS][index]; 714 a.height = spans[Y_AXIS][index]; 715 } 716 717 /** 718 * Lays out the children of this <code>BoxView</code> with the specified 719 * bounds. 720 * 721 * @param width the width of the allocated region for the children (that 722 * is the inner allocation of this <code>BoxView</code> 723 * @param height the height of the allocated region for the children (that 724 * is the inner allocation of this <code>BoxView</code> 725 */ 726 protected void layout(int width, int height) 727 { 728 layoutAxis(X_AXIS, width); 729 layoutAxis(Y_AXIS, height); 730 } 731 732 private void layoutAxis(int axis, int s) 733 { 734 if (span[axis] != s) 735 layoutValid[axis] = false; 736 if (! layoutValid[axis]) 737 { 738 span[axis] = s; 739 updateRequirements(axis); 740 if (axis == myAxis) 741 layoutMajorAxis(span[axis], axis, offsets[axis], spans[axis]); 742 else 743 layoutMinorAxis(span[axis], axis, offsets[axis], spans[axis]); 744 layoutValid[axis] = true; 745 746 // Push out child layout. 747 int viewCount = getViewCount(); 748 for (int i = 0; i < viewCount; i++) 749 { 750 View v = getView(i); 751 v.setSize(spans[X_AXIS][i], spans[Y_AXIS][i]); 752 } 753 } 754 } 755 756 /** 757 * Performs the layout along the major axis of a <code>BoxView</code>. 758 * 759 * @param targetSpan the (inner) span of the <code>BoxView</code> in which 760 * to layout the children 761 * @param axis the axis along which the layout is performed 762 * @param offsets the array that holds the offsets of the children on exit 763 * @param spans the array that holds the spans of the children on exit 764 */ 765 protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, 766 int[] spans) 767 { 768 // Set the spans to the preferred sizes. Determine the space 769 // that we have to adjust the sizes afterwards. 770 long sumPref = 0; 771 int n = getViewCount(); 772 for (int i = 0; i < n; i++) 773 { 774 View child = getView(i); 775 spans[i] = (int) child.getPreferredSpan(axis); 776 sumPref += spans[i]; 777 } 778 779 // Try to adjust the spans so that we fill the targetSpan. 780 long diff = targetSpan - sumPref; 781 float factor = 0.0F; 782 int[] diffs = null; 783 if (diff != 0) 784 { 785 long total = 0; 786 diffs = new int[n]; 787 for (int i = 0; i < n; i++) 788 { 789 View child = getView(i); 790 int span; 791 if (diff < 0) 792 { 793 span = (int) child.getMinimumSpan(axis); 794 diffs[i] = spans[i] - span; 795 } 796 else 797 { 798 span = (int) child.getMaximumSpan(axis); 799 diffs[i] = span - spans[i]; 800 } 801 total += span; 802 } 803 804 float maxAdjust = Math.abs(total - sumPref); 805 factor = diff / maxAdjust; 806 factor = Math.min(factor, 1.0F); 807 factor = Math.max(factor, -1.0F); 808 } 809 810 // Actually perform adjustments. 811 int totalOffs = 0; 812 for (int i = 0; i < n; i++) 813 { 814 offsets[i] = totalOffs; 815 if (diff != 0) 816 { 817 float adjust = factor * diffs[i]; 818 spans[i] += Math.round(adjust); 819 } 820 // Avoid overflow here. 821 totalOffs = (int) Math.min((long) totalOffs + (long) spans[i], 822 Integer.MAX_VALUE); 823 } 824 } 825 826 /** 827 * Performs the layout along the minor axis of a <code>BoxView</code>. 828 * 829 * @param targetSpan the (inner) span of the <code>BoxView</code> in which 830 * to layout the children 831 * @param axis the axis along which the layout is performed 832 * @param offsets the array that holds the offsets of the children on exit 833 * @param spans the array that holds the spans of the children on exit 834 */ 835 protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, 836 int[] spans) 837 { 838 int count = getViewCount(); 839 for (int i = 0; i < count; i++) 840 { 841 View child = getView(i); 842 int max = (int) child.getMaximumSpan(axis); 843 if (max < targetSpan) 844 { 845 // Align child when it can't be made as wide as the target span. 846 float align = child.getAlignment(axis); 847 offsets[i] = (int) ((targetSpan - max) * align); 848 spans[i] = max; 849 } 850 else 851 { 852 // Expand child to target width if possible. 853 int min = (int) child.getMinimumSpan(axis); 854 offsets[i] = 0; 855 spans[i] = Math.max(min, targetSpan); 856 } 857 } 858 } 859 860 /** 861 * Returns <code>true</code> if the cached allocations for the children 862 * are still valid, <code>false</code> otherwise. 863 * 864 * @return <code>true</code> if the cached allocations for the children 865 * are still valid, <code>false</code> otherwise 866 */ 867 protected boolean isAllocationValid() 868 { 869 return isLayoutValid(X_AXIS) && isLayoutValid(Y_AXIS); 870 } 871 872 /** 873 * Return the current width of the box. This is the last allocated width. 874 * 875 * @return the current width of the box 876 */ 877 public int getWidth() 878 { 879 // The RI returns the following here, however, I'd think that is a bug. 880 // return span[X_AXIS] + getLeftInset() - getRightInset(); 881 return span[X_AXIS] + getLeftInset() + getRightInset(); 882 } 883 884 /** 885 * Return the current height of the box. This is the last allocated height. 886 * 887 * @return the current height of the box 888 */ 889 public int getHeight() 890 { 891 // The RI returns the following here, however, I'd think that is a bug. 892 // return span[Y_AXIS] + getTopInset() - getBottomInset(); 893 return span[Y_AXIS] + getTopInset() + getBottomInset(); 894 } 895 896 /** 897 * Sets the size of the view. If the actual size has changed, the layout 898 * is updated accordingly. 899 * 900 * @param width the new width 901 * @param height the new height 902 */ 903 public void setSize(float width, float height) 904 { 905 layout((int) (width - getLeftInset() - getRightInset()), 906 (int) (height - getTopInset() - getBottomInset())); 907 } 908 909 /** 910 * Returns the span for the child view with the given index for the specified 911 * axis. 912 * 913 * @param axis the axis to examine, either <code>X_AXIS</code> or 914 * <code>Y_AXIS</code> 915 * @param childIndex the index of the child for for which to return the span 916 * 917 * @return the span for the child view with the given index for the specified 918 * axis 919 */ 920 protected int getSpan(int axis, int childIndex) 921 { 922 if (axis != X_AXIS && axis != Y_AXIS) 923 throw new IllegalArgumentException("Illegal axis argument"); 924 return spans[axis][childIndex]; 925 } 926 927 /** 928 * Returns the offset for the child view with the given index for the 929 * specified axis. 930 * 931 * @param axis the axis to examine, either <code>X_AXIS</code> or 932 * <code>Y_AXIS</code> 933 * @param childIndex the index of the child for for which to return the span 934 * 935 * @return the offset for the child view with the given index for the 936 * specified axis 937 */ 938 protected int getOffset(int axis, int childIndex) 939 { 940 if (axis != X_AXIS && axis != Y_AXIS) 941 throw new IllegalArgumentException("Illegal axis argument"); 942 return offsets[axis][childIndex]; 943 } 944 945 /** 946 * Returns the alignment for this box view for the specified axis. The 947 * axis that is tiled (the major axis) will be requested to be aligned 948 * centered (0.5F). The minor axis alignment depends on the child view's 949 * total alignment. 950 * 951 * @param axis the axis which is examined 952 * 953 * @return the alignment for this box view for the specified axis 954 */ 955 public float getAlignment(int axis) 956 { 957 updateRequirements(axis); 958 return requirements[axis].alignment; 959 } 960 961 /** 962 * Called by a child View when its preferred span has changed. 963 * 964 * @param width indicates that the preferred width of the child changed. 965 * @param height indicates that the preferred height of the child changed. 966 * @param child the child View. 967 */ 968 public void preferenceChanged(View child, boolean width, boolean height) 969 { 970 if (width) 971 { 972 layoutValid[X_AXIS] = false; 973 requirementsValid[X_AXIS] = false; 974 } 975 if (height) 976 { 977 layoutValid[Y_AXIS] = false; 978 requirementsValid[Y_AXIS] = false; 979 } 980 super.preferenceChanged(child, width, height); 981 } 982 983 /** 984 * Maps the document model position <code>pos</code> to a Shape 985 * in the view coordinate space. This method overrides CompositeView's 986 * method to make sure the children are allocated properly before 987 * calling the super's behaviour. 988 */ 989 public Shape modelToView(int pos, Shape a, Position.Bias bias) 990 throws BadLocationException 991 { 992 // Make sure everything is allocated properly and then call super 993 if (! isAllocationValid()) 994 { 995 Rectangle bounds = a.getBounds(); 996 setSize(bounds.width, bounds.height); 997 } 998 return super.modelToView(pos, a, bias); 999 } 1000 1001 /** 1002 * Returns the resize weight of this view. A value of <code>0</code> or less 1003 * means this view is not resizeable. Positive values make the view 1004 * resizeable. This implementation returns <code>0</code> for the major 1005 * axis and <code>1</code> for the minor axis of this box view. 1006 * 1007 * @param axis the axis 1008 * 1009 * @return the resizability of this view along the specified axis 1010 * 1011 * @throws IllegalArgumentException if <code>axis</code> is invalid 1012 */ 1013 public int getResizeWeight(int axis) 1014 { 1015 if (axis != X_AXIS && axis != Y_AXIS) 1016 throw new IllegalArgumentException("Illegal axis argument"); 1017 updateRequirements(axis); 1018 int weight = 0; 1019 if ((requirements[axis].preferred != requirements[axis].minimum) 1020 || (requirements[axis].preferred != requirements[axis].maximum)) 1021 weight = 1; 1022 return weight; 1023 } 1024 1025 /** 1026 * Returns the child allocation for the child view with the specified 1027 * <code>index</code>. If the layout is invalid, this returns 1028 * <code>null</code>. 1029 * 1030 * @param index the child view index 1031 * @param a the allocation to this view 1032 * 1033 * @return the child allocation for the child view with the specified 1034 * <code>index</code> or <code>null</code> if the layout is invalid 1035 * or <code>a</code> is null 1036 */ 1037 public Shape getChildAllocation(int index, Shape a) 1038 { 1039 Shape ret = null; 1040 if (isAllocationValid() && a != null) 1041 ret = super.getChildAllocation(index, a); 1042 return ret; 1043 } 1044 1045 protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e, 1046 Shape a, ViewFactory vf) 1047 { 1048 boolean wasValid = isLayoutValid(myAxis); 1049 super.forwardUpdate(ec, e, a, vf); 1050 // Trigger repaint when one of the children changed the major axis. 1051 if (wasValid && ! isLayoutValid(myAxis)) 1052 { 1053 Container c = getContainer(); 1054 if (a != null && c != null) 1055 { 1056 int pos = e.getOffset(); 1057 int index = getViewIndexAtPosition(pos); 1058 Rectangle r = getInsideAllocation(a); 1059 if (myAxis == X_AXIS) 1060 { 1061 r.x += offsets[myAxis][index]; 1062 r.width -= offsets[myAxis][index]; 1063 } 1064 else 1065 { 1066 r.y += offsets[myAxis][index]; 1067 r.height -= offsets[myAxis][index]; 1068 } 1069 c.repaint(r.x, r.y, r.width, r.height); 1070 } 1071 } 1072 } 1073 1074 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) 1075 { 1076 if (! isAllocationValid()) 1077 { 1078 Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); 1079 setSize(r.width, r.height); 1080 } 1081 return super.viewToModel(x, y, a, bias); 1082 } 1083 1084 protected boolean flipEastAndWestAtEnds(int position, Position.Bias bias) 1085 { 1086 // FIXME: What to do here? 1087 return super.flipEastAndWestAtEnds(position, bias); 1088 } 1089 1090 /** 1091 * Updates the view's cached requirements along the specified axis if 1092 * necessary. The requirements are only updated if the layout for the 1093 * specified axis is marked as invalid. 1094 * 1095 * @param axis the axis 1096 */ 1097 private void updateRequirements(int axis) 1098 { 1099 if (axis != Y_AXIS && axis != X_AXIS) 1100 throw new IllegalArgumentException("Illegal axis: " + axis); 1101 if (! requirementsValid[axis]) 1102 { 1103 if (axis == myAxis) 1104 requirements[axis] = calculateMajorAxisRequirements(axis, 1105 requirements[axis]); 1106 else 1107 requirements[axis] = calculateMinorAxisRequirements(axis, 1108 requirements[axis]); 1109 requirementsValid[axis] = true; 1110 } 1111 } 1112}