GNU Classpath (0.19) | ||
Frames | No Frames |
1: /* BoxView.java -- An composite view 2: Copyright (C) 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.text; 40: 41: import java.awt.Graphics; 42: import java.awt.Rectangle; 43: import java.awt.Shape; 44: 45: import javax.swing.SizeRequirements; 46: 47: /** 48: * An implementation of {@link CompositeView} that arranges its children in 49: * a box along one axis. This is comparable to how the <code>BoxLayout</code> 50: * works, but for <code>View</code> children. 51: * 52: * @author Roman Kennke (roman@kennke.org) 53: */ 54: public class BoxView 55: extends CompositeView 56: { 57: 58: /** 59: * The axis along which this <code>BoxView</code> is laid out. 60: */ 61: int myAxis; 62: 63: /** 64: * Indicates wether the layout in X_AXIS is valid. 65: */ 66: boolean xLayoutValid; 67: 68: /** 69: * Indicates whether the layout in Y_AXIS is valid. 70: */ 71: boolean yLayoutValid; 72: 73: /** 74: * The spans in X direction of the children. 75: */ 76: int[] spansX; 77: 78: /** 79: * The spans in Y direction of the children. 80: */ 81: int[] spansY; 82: 83: /** 84: * The offsets of the children in X direction relative to this BoxView's 85: * inner bounds. 86: */ 87: int[] offsetsX; 88: 89: /** 90: * The offsets of the children in Y direction relative to this BoxView's 91: * inner bounds. 92: */ 93: int[] offsetsY; 94: 95: /** 96: * The current width. 97: */ 98: int width; 99: 100: /** 101: * The current height. 102: */ 103: int height; 104: 105: /** 106: * Creates a new <code>BoxView</code> for the given 107: * <code>Element</code> and axis. Valid values for the axis are 108: * {@link View#X_AXIS} and {@link View#Y_AXIS}. 109: * 110: * @param element the element that is rendered by this BoxView 111: * @param axis the axis along which the box is laid out 112: */ 113: public BoxView(Element element, int axis) 114: { 115: super(element); 116: myAxis = axis; 117: xLayoutValid = false; 118: yLayoutValid = false; 119: 120: // Initialize the cache arrays. 121: spansX = new int[0]; 122: spansY = new int[0]; 123: offsetsX = new int[0]; 124: offsetsY = new int[0]; 125: 126: width = 0; 127: height = 0; 128: } 129: 130: /** 131: * Returns the axis along which this <code>BoxView</code> is laid out. 132: * 133: * @return the axis along which this <code>BoxView</code> is laid out 134: */ 135: public int getAxis() 136: { 137: return myAxis; 138: } 139: 140: /** 141: * Sets the axis along which this <code>BoxView</code> is laid out. 142: * 143: * Valid values for the axis are {@link View#X_AXIS} and 144: * {@link View#Y_AXIS}. 145: * 146: * @param axis the axis along which this <code>BoxView</code> is laid out 147: */ 148: public void setAxis(int axis) 149: { 150: myAxis = axis; 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: public void layoutChanged(int axis) 168: { 169: switch (axis) 170: { 171: case X_AXIS: 172: xLayoutValid = false; 173: break; 174: case Y_AXIS: 175: yLayoutValid = false; 176: break; 177: default: 178: throw new IllegalArgumentException("Invalid axis parameter."); 179: } 180: } 181: 182: /** 183: * Returns <code>true</code> if the layout along the specified 184: * <code>axis</code> is valid, <code>false</code> otherwise. 185: * 186: * Valid values for the axis are {@link View#X_AXIS} and 187: * {@link View#Y_AXIS}. 188: * 189: * @param axis the axis 190: * 191: * @return <code>true</code> if the layout along the specified 192: * <code>axis</code> is valid, <code>false</code> otherwise 193: */ 194: protected boolean isLayoutValid(int axis) 195: { 196: boolean valid = false; 197: switch (axis) 198: { 199: case X_AXIS: 200: valid = xLayoutValid; 201: break; 202: case Y_AXIS: 203: valid = yLayoutValid; 204: break; 205: default: 206: throw new IllegalArgumentException("Invalid axis parameter."); 207: } 208: return valid; 209: } 210: 211: /** 212: * Paints the child <code>View</code> at the specified <code>index</code>. 213: * This method modifies the actual values in <code>alloc</code> so make 214: * sure you have a copy of the original values if you need them. 215: * 216: * @param g the <code>Graphics</code> context to paint to 217: * @param alloc the allocated region for the child to paint into 218: * @param index the index of the child to be painted 219: * 220: * @see #childAllocation(int, Rectangle) 221: */ 222: protected void paintChild(Graphics g, Rectangle alloc, int index) 223: { 224: View child = getView(index); 225: child.paint(g, alloc); 226: } 227: 228: /** 229: * Replaces child views by some other child views. If there are no views to 230: * remove (<code>length == 0</code>), the result is a simple insert, if 231: * there are no children to add (<code>view == null</code>) the result 232: * is a simple removal. 233: * 234: * In addition this invalidates the layout and resizes the internal cache 235: * for the child allocations. The old children's cached allocations can 236: * still be accessed (although they are not guaranteed to be valid), and 237: * the new children will have an initial offset and span of 0. 238: * 239: * @param offset the start offset from where to remove children 240: * @param length the number of children to remove 241: * @param views the views that replace the removed children 242: */ 243: public void replace(int offset, int length, View[] views) 244: { 245: // Resize and copy data for cache arrays. 246: // The spansX cache. 247: int oldSize = getViewCount(); 248: 249: int[] newSpansX = new int[oldSize - length + views.length]; 250: System.arraycopy(spansX, 0, newSpansX, 0, offset); 251: System.arraycopy(spansX, offset + length, newSpansX, 252: offset + views.length, 253: oldSize - (offset + length)); 254: spansX = newSpansX; 255: 256: // The spansY cache. 257: int[] newSpansY = new int[oldSize - length + views.length]; 258: System.arraycopy(spansY, 0, newSpansY, 0, offset); 259: System.arraycopy(spansY, offset + length, newSpansY, 260: offset + views.length, 261: oldSize - (offset + length)); 262: spansY = newSpansY; 263: 264: // The offsetsX cache. 265: int[] newOffsetsX = new int[oldSize - length + views.length]; 266: System.arraycopy(offsetsX, 0, newOffsetsX, 0, offset); 267: System.arraycopy(offsetsX, offset + length, newOffsetsX, 268: offset + views.length, 269: oldSize - (offset + length)); 270: offsetsX = newOffsetsX; 271: 272: // The offsetsY cache. 273: int[] newOffsetsY = new int[oldSize - length + views.length]; 274: System.arraycopy(offsetsY, 0, newOffsetsY, 0, offset); 275: System.arraycopy(offsetsY, offset + length, newOffsetsY, 276: offset + views.length, 277: oldSize - (offset + length)); 278: offsetsY = newOffsetsY; 279: 280: // Actually perform the replace. 281: super.replace(offset, length, views); 282: 283: // Invalidate layout information. 284: layoutChanged(X_AXIS); 285: layoutChanged(Y_AXIS); 286: } 287: 288: /** 289: * Renders the <code>Element</code> that is associated with this 290: * <code>View</code>. 291: * 292: * @param g the <code>Graphics</code> context to render to 293: * @param a the allocated region for the <code>Element</code> 294: */ 295: public void paint(Graphics g, Shape a) 296: { 297: // Adjust size if the size is changed. 298: Rectangle bounds = a.getBounds(); 299: 300: if (bounds.width != getWidth() || bounds.height != getHeight()) 301: setSize(bounds.width, bounds.height); 302: 303: Rectangle inside = getInsideAllocation(a); 304: Rectangle copy = new Rectangle(inside); 305: int count = getViewCount(); 306: for (int i = 0; i < count; ++i) 307: { 308: copy.setBounds(inside); 309: childAllocation(i, copy); 310: if (!copy.isEmpty()) 311: paintChild(g, copy, i); 312: } 313: } 314: 315: /** 316: * Returns the preferred span of the content managed by this 317: * <code>View</code> along the specified <code>axis</code>. 318: * 319: * @param axis the axis 320: * 321: * @return the preferred span of this <code>View</code>. 322: */ 323: public float getPreferredSpan(int axis) 324: { 325: SizeRequirements sr = new SizeRequirements(); 326: int pref = baselineRequirements(axis, sr).preferred; 327: return (float) pref; 328: } 329: 330: public float getMaximumSpan(int axis) 331: { 332: if (axis == getAxis()) 333: return getPreferredSpan(axis); 334: else 335: return Integer.MAX_VALUE; 336: } 337: 338: /** 339: * Calculates the size requirements for this <code>BoxView</code> along 340: * the specified axis. 341: * 342: * @param axis the axis that is examined 343: * @param sr the <code>SizeRequirements</code> object to hold the result, 344: * if <code>null</code>, a new one is created 345: * 346: * @return the size requirements for this <code>BoxView</code> along 347: * the specified axis 348: */ 349: protected SizeRequirements baselineRequirements(int axis, 350: SizeRequirements sr) 351: { 352: SizeRequirements result; 353: if (axis == myAxis) 354: result = calculateMajorAxisRequirements(axis, sr); 355: else 356: result = calculateMinorAxisRequirements(axis, sr); 357: return result; 358: } 359: 360: /** 361: * Calculates the layout of the children of this <code>BoxView</code> along 362: * the specified axis. 363: * 364: * @param span the target span 365: * @param axis the axis that is examined 366: * @param offsets an empty array, filled with the offsets of the children 367: * @param spans an empty array, filled with the spans of the children 368: */ 369: protected void baselineLayout(int span, int axis, int[] offsets, 370: int[] spans) 371: { 372: if (axis == myAxis) 373: layoutMajorAxis(span, axis, offsets, spans); 374: else 375: layoutMinorAxis(span, axis, offsets, spans); 376: } 377: 378: /** 379: * Calculates the size requirements of this <code>BoxView</code> along 380: * its major axis, that is the axis specified in the constructor. 381: * 382: * @param axis the axis that is examined 383: * @param sr the <code>SizeRequirements</code> object to hold the result, 384: * if <code>null</code>, a new one is created 385: * 386: * @return the size requirements for this <code>BoxView</code> along 387: * the specified axis 388: */ 389: protected SizeRequirements calculateMajorAxisRequirements(int axis, 390: SizeRequirements sr) 391: { 392: SizeRequirements[] childReqs = getChildRequirements(axis); 393: return SizeRequirements.getTiledSizeRequirements(childReqs); 394: } 395: 396: /** 397: * Calculates the size requirements of this <code>BoxView</code> along 398: * its minor axis, that is the axis opposite to the axis specified in the 399: * constructor. 400: * 401: * @param axis the axis that is examined 402: * @param sr the <code>SizeRequirements</code> object to hold the result, 403: * if <code>null</code>, a new one is created 404: * 405: * @return the size requirements for this <code>BoxView</code> along 406: * the specified axis 407: */ 408: protected SizeRequirements calculateMinorAxisRequirements(int axis, 409: SizeRequirements sr) 410: { 411: SizeRequirements[] childReqs = getChildRequirements(axis); 412: return SizeRequirements.getAlignedSizeRequirements(childReqs); 413: } 414: 415: /** 416: * Returns <code>true</code> if the specified point lies before the 417: * given <code>Rectangle</code>, <code>false</code> otherwise. 418: * 419: * "Before" is typically defined as being to the left or above. 420: * 421: * @param x the X coordinate of the point 422: * @param y the Y coordinate of the point 423: * @param r the rectangle to test the point against 424: * 425: * @return <code>true</code> if the specified point lies before the 426: * given <code>Rectangle</code>, <code>false</code> otherwise 427: */ 428: protected boolean isBefore(int x, int y, Rectangle r) 429: { 430: boolean result = false; 431: 432: if (myAxis == X_AXIS) 433: result = x < r.x; 434: else 435: result = y < r.y; 436: 437: return result; 438: } 439: 440: /** 441: * Returns <code>true</code> if the specified point lies after the 442: * given <code>Rectangle</code>, <code>false</code> otherwise. 443: * 444: * "After" is typically defined as being to the right or below. 445: * 446: * @param x the X coordinate of the point 447: * @param y the Y coordinate of the point 448: * @param r the rectangle to test the point against 449: * 450: * @return <code>true</code> if the specified point lies after the 451: * given <code>Rectangle</code>, <code>false</code> otherwise 452: */ 453: protected boolean isAfter(int x, int y, Rectangle r) 454: { 455: boolean result = false; 456: 457: if (myAxis == X_AXIS) 458: result = x > r.x; 459: else 460: result = y > r.y; 461: 462: return result; 463: } 464: 465: /** 466: * Returns the child <code>View</code> at the specified location. 467: * 468: * @param x the X coordinate 469: * @param y the Y coordinate 470: * @param r the inner allocation of this <code>BoxView</code> on entry, 471: * the allocation of the found child on exit 472: * 473: * @return the child <code>View</code> at the specified location 474: */ 475: protected View getViewAtPoint(int x, int y, Rectangle r) 476: { 477: View result = null; 478: 479: int count = getViewCount(); 480: Rectangle copy = new Rectangle(r); 481: 482: for (int i = 0; i < count; ++i) 483: { 484: copy.setBounds(r); 485: childAllocation(i, r); 486: if (copy.contains(x, y)) 487: { 488: result = getView(i); 489: break; 490: } 491: } 492: 493: return result; 494: } 495: 496: /** 497: * Computes the allocation for a child <code>View</code>. The parameter 498: * <code>a</code> stores the allocation of this <code>CompositeView</code> 499: * and is then adjusted to hold the allocation of the child view. 500: * 501: * @param index the index of the child <code>View</code> 502: * @param a the allocation of this <code>CompositeView</code> before the 503: * call, the allocation of the child on exit 504: */ 505: protected void childAllocation(int index, Rectangle a) 506: { 507: if (! isAllocationValid()) 508: layout(a.width, a.height); 509: 510: a.x += offsetsX[index]; 511: a.y += offsetsY[index]; 512: a.width = spansX[index]; 513: a.height = spansY[index]; 514: } 515: 516: /** 517: * Lays out the children of this <code>BoxView</code> with the specified 518: * bounds. 519: * 520: * @param width the width of the allocated region for the children (that 521: * is the inner allocation of this <code>BoxView</code> 522: * @param height the height of the allocated region for the children (that 523: * is the inner allocation of this <code>BoxView</code> 524: */ 525: protected void layout(int width, int height) 526: { 527: baselineLayout(width, X_AXIS, offsetsX, spansX); 528: baselineLayout(height, Y_AXIS, offsetsY, spansY); 529: } 530: 531: /** 532: * Performs the layout along the major axis of a <code>BoxView</code>. 533: * 534: * @param targetSpan the (inner) span of the <code>BoxView</code> in which 535: * to layout the children 536: * @param axis the axis along which the layout is performed 537: * @param offsets the array that holds the offsets of the children on exit 538: * @param spans the array that holds the spans of the children on exit 539: */ 540: protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, 541: int[] spans) 542: { 543: SizeRequirements[] childReqs = getChildRequirements(axis); 544: // Calculate the spans and offsets using the SizeRequirements uility 545: // methods. 546: SizeRequirements.calculateTiledPositions(targetSpan, null, childReqs, 547: offsets, spans); 548: validateLayout(axis); 549: } 550: 551: /** 552: * Performs the layout along the minor axis of a <code>BoxView</code>. 553: * 554: * @param targetSpan the (inner) span of the <code>BoxView</code> in which 555: * to layout the children 556: * @param axis the axis along which the layout is performed 557: * @param offsets the array that holds the offsets of the children on exit 558: * @param spans the array that holds the spans of the children on exit 559: */ 560: protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, 561: int[] spans) 562: { 563: SizeRequirements[] childReqs = getChildRequirements(axis); 564: // Calculate the spans and offsets using the SizeRequirements uility 565: // methods. 566: // TODO: This might be an opportunity for performance optimization. Here 567: // we could use a cached instance of SizeRequirements instead of passing 568: // null to baselineRequirements. However, this would involve rewriting 569: // the baselineRequirements() method to not use the SizeRequirements 570: // utility method, since they cannot reuse a cached instance. 571: SizeRequirements total = baselineRequirements(axis, null); 572: SizeRequirements.calculateAlignedPositions(targetSpan, total, childReqs, 573: offsets, spans); 574: validateLayout(axis); 575: } 576: 577: /** 578: * Returns <code>true</code> if the cached allocations for the children 579: * are still valid, <code>false</code> otherwise. 580: * 581: * @return <code>true</code> if the cached allocations for the children 582: * are still valid, <code>false</code> otherwise 583: */ 584: protected boolean isAllocationValid() 585: { 586: return isLayoutValid(X_AXIS) && isLayoutValid(Y_AXIS); 587: } 588: 589: /** 590: * Return the current width of the box. This is the last allocated width. 591: * 592: * @return the current width of the box 593: */ 594: public int getWidth() 595: { 596: return width; 597: } 598: 599: /** 600: * Return the current height of the box. This is the last allocated height. 601: * 602: * @return the current height of the box 603: */ 604: public int getHeight() 605: { 606: return height; 607: } 608: 609: /** 610: * Sets the size of the view. If the actual size has changed, the layout 611: * is updated accordingly. 612: * 613: * @param width the new width 614: * @param height the new height 615: */ 616: public void setSize(float width, float height) 617: { 618: if (this.width != (int) width) 619: layoutChanged(X_AXIS); 620: if (this.height != (int) height) 621: layoutChanged(Y_AXIS); 622: 623: this.width = (int) width; 624: this.height = (int) height; 625: 626: Rectangle outside = new Rectangle(0, 0, this.width, this.height); 627: Rectangle inside = getInsideAllocation(outside); 628: if (!isAllocationValid()) 629: layout(inside.width, inside.height); 630: } 631: 632: /** 633: * Sets the layout to valid for a specific axis. 634: * 635: * @param axis the axis for which to validate the layout 636: */ 637: void validateLayout(int axis) 638: { 639: if (axis == X_AXIS) 640: xLayoutValid = true; 641: if (axis == Y_AXIS) 642: yLayoutValid = true; 643: } 644: 645: /** 646: * Returns the size requirements of this view's children for the major 647: * axis. 648: * 649: * @return the size requirements of this view's children for the major 650: * axis 651: */ 652: SizeRequirements[] getChildRequirements(int axis) 653: { 654: // Allocate SizeRequirements for each child view. 655: int count = getViewCount(); 656: SizeRequirements[] childReqs = new SizeRequirements[count]; 657: for (int i = 0; i < count; ++i) 658: { 659: View view = getView(i); 660: childReqs[i] = new SizeRequirements((int) view.getMinimumSpan(axis), 661: (int) view.getPreferredSpan(axis), 662: (int) view.getMaximumSpan(axis), 663: view.getAlignment(axis)); 664: } 665: return childReqs; 666: } 667: 668: /** 669: * Returns the span for the child view with the given index for the specified 670: * axis. 671: * 672: * @param axis the axis to examine, either <code>X_AXIS</code> or 673: * <code>Y_AXIS</code> 674: * @param childIndex the index of the child for for which to return the span 675: * 676: * @return the span for the child view with the given index for the specified 677: * axis 678: */ 679: protected int getSpan(int axis, int childIndex) 680: { 681: if (axis == X_AXIS) 682: return spansX[childIndex]; 683: else 684: return spansY[childIndex]; 685: } 686: 687: /** 688: * Returns the offset for the child view with the given index for the 689: * specified axis. 690: * 691: * @param axis the axis to examine, either <code>X_AXIS</code> or 692: * <code>Y_AXIS</code> 693: * @param childIndex the index of the child for for which to return the span 694: * 695: * @return the offset for the child view with the given index for the 696: * specified axis 697: */ 698: protected int getOffset(int axis, int childIndex) 699: { 700: if (axis == X_AXIS) 701: return offsetsX[childIndex]; 702: else 703: return offsetsY[childIndex]; 704: } 705: 706: /** 707: * Returns the alignment for this box view for the specified axis. The 708: * axis that is tiled (the major axis) will be requested to be aligned 709: * centered (0.5F). The minor axis alignment depends on the child view's 710: * total alignment. 711: * 712: * @param axis the axis which is examined 713: * 714: * @return the alignment for this box view for the specified axis 715: */ 716: public float getAlignment(int axis) 717: { 718: if (axis == myAxis) 719: return 0.5F; 720: else 721: return baselineRequirements(axis, null).alignment; 722: } 723: 724: /** 725: * Called by a child View when its preferred span has changed. 726: * 727: * @param width indicates that the preferred width of the child changed. 728: * @param height indicates that the preferred height of the child changed. 729: * @param child the child View. 730: */ 731: public void preferenceChanged (View child, boolean width, boolean height) 732: { 733: if (width) 734: xLayoutValid = false; 735: if (height) 736: yLayoutValid = false; 737: super.preferenceChanged(child, width, height); 738: } 739: }
GNU Classpath (0.19) |