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)) { 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; 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 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 /** 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 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; 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 ------------------------------------------------ | 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} and {@code layoutMinorAxis}. 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} is 51 * called to force an updated layout. The {@code layoutChanged} 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} 55 * and {@code calculateMinorAxisRequirements}. 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}. 65 * 66 * @param elem the element this view is responsible for 67 * @param axis either {@code View.X_AXIS} or {@code View.Y_AXIS} 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} or {@code View.Y_AXIS} 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} or {@code View.Y_AXIS} 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} method 121 * is called on this view (typically in paint). 122 * 123 * @param axis either {@code View.X_AXIS} or {@code View.Y_AXIS} 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} or {@code View.Y_AXIS} 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} 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} 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} 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} 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)) { 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} or 294 * {@code View.Y_AXIS} 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} or 319 * {@code View.Y_AXIS} 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; 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} or 377 * {@code View.Y_AXIS} 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} 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} 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} 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} 449 * if {@code a} is {@code null}; 450 * or {@code null} 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 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} 516 * or {@code View.Y_AXIS} 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} 538 * or {@code View.Y_AXIS} 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} 561 * or {@code View.Y_AXIS} 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} 584 * or {@code View.Y_AXIS} 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 /** 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}. 856 * 857 * @param axis the axis being studied 858 * @param r the {@code SizeRequirements} object; 859 * if {@code null} one will be created 860 * @return the newly initialized {@code SizeRequirements} 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}. 890 * 891 * @param axis the axis being studied 892 * @param r the {@code SizeRequirements} object; 893 * if {@code null} one will be created 894 * @return the newly initialized {@code SizeRequirements} 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} is 923 * neither {@code View.X_AXIS} nor {@code View.Y_AXIS} 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} given the {@code targetSpan}, 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} or {@code View.Y_AXIS} 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 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} 1000 * by examining the size of each child view. 1001 * 1002 * @param axis the axis being studied 1003 * @param r the {@code SizeRequirements} object; 1004 * if {@code null} one will be created 1005 * @return the newly initialized {@code SizeRequirements} 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; 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}s 1119 * are layed out from left to right, so that the {@code View} 1120 * to the EAST will be at index n + 1, and the {@code View} 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} to EAST is not at index n + 1, 1124 * but rather at index n - 1, or that the {@code View} 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}s are layed out in 1128 * descending order. Otherwise the method would return false 1129 * indicating the {@code View}s are layed out in ascending order. 1130 * <p> 1131 * If the receiver is laying its {@code View}s along the 1132 * {@code Y_AXIS}, this will return the value from 1133 * invoking the same method on the {@code View} 1134 * responsible for rendering {@code position} and 1135 * {@code bias}. Otherwise this will return false. 1136 * 1137 * @param position position into the model 1138 * @param bias either {@code Position.Bias.Forward} or 1139 * {@code Position.Bias.Backward} 1140 * @return true if the {@code View}s surrounding the 1141 * {@code View} responding for rendering 1142 * {@code position} and {@code bias} 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 ------------------------------------------------ |