GNU Classpath (0.20) | |
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: && g.hitClip(copy.x, copy.y, copy.width, copy.height)) 312: paintChild(g, copy, i); 313: } 314: } 315: 316: /** 317: * Returns the preferred span of the content managed by this 318: * <code>View</code> along the specified <code>axis</code>. 319: * 320: * @param axis the axis 321: * 322: * @return the preferred span of this <code>View</code>. 323: */ 324: public float getPreferredSpan(int axis) 325: { 326: SizeRequirements sr = new SizeRequirements(); 327: int pref = baselineRequirements(axis, sr).preferred; 328: return (float) pref; 329: } 330: 331: public float getMaximumSpan(int axis) 332: { 333: if (axis == getAxis()) 334: return getPreferredSpan(axis); 335: else 336: return Integer.MAX_VALUE; 337: } 338: 339: /** 340: * Calculates the size requirements for this <code>BoxView</code> along 341: * the specified axis. 342: * 343: * @param axis the axis that is examined 344: * @param sr the <code>SizeRequirements</code> object to hold the result, 345: * if <code>null</code>, a new one is created 346: * 347: * @return the size requirements for this <code>BoxView</code> along 348: * the specified axis 349: */ 350: protected SizeRequirements baselineRequirements(int axis, 351: SizeRequirements sr) 352: { 353: SizeRequirements result; 354: if (axis == myAxis) 355: result = calculateMajorAxisRequirements(axis, sr); 356: else 357: result = calculateMinorAxisRequirements(axis, sr); 358: return result; 359: } 360: 361: /** 362: * Calculates the layout of the children of this <code>BoxView</code> along 363: * the specified axis. 364: * 365: * @param span the target span 366: * @param axis the axis that is examined 367: * @param offsets an empty array, filled with the offsets of the children 368: * @param spans an empty array, filled with the spans of the children 369: */ 370: protected void baselineLayout(int span, int axis, int[] offsets, 371: int[] spans) 372: { 373: if (axis == myAxis) 374: layoutMajorAxis(span, axis, offsets, spans); 375: else 376: layoutMinorAxis(span, axis, offsets, spans); 377: } 378: 379: /** 380: * Calculates the size requirements of this <code>BoxView</code> along 381: * its major axis, that is the axis specified in the constructor. 382: * 383: * @param axis the axis that is examined 384: * @param sr the <code>SizeRequirements</code> object to hold the result, 385: * if <code>null</code>, a new one is created 386: * 387: * @return the size requirements for this <code>BoxView</code> along 388: * the specified axis 389: */ 390: protected SizeRequirements calculateMajorAxisRequirements(int axis, 391: SizeRequirements sr) 392: { 393: SizeRequirements[] childReqs = getChildRequirements(axis); 394: return SizeRequirements.getTiledSizeRequirements(childReqs); 395: } 396: 397: /** 398: * Calculates the size requirements of this <code>BoxView</code> along 399: * its minor axis, that is the axis opposite to the axis specified in the 400: * constructor. 401: * 402: * @param axis the axis that is examined 403: * @param sr the <code>SizeRequirements</code> object to hold the result, 404: * if <code>null</code>, a new one is created 405: * 406: * @return the size requirements for this <code>BoxView</code> along 407: * the specified axis 408: */ 409: protected SizeRequirements calculateMinorAxisRequirements(int axis, 410: SizeRequirements sr) 411: { 412: SizeRequirements[] childReqs = getChildRequirements(axis); 413: return SizeRequirements.getAlignedSizeRequirements(childReqs); 414: } 415: 416: /** 417: * Returns <code>true</code> if the specified point lies before the 418: * given <code>Rectangle</code>, <code>false</code> otherwise. 419: * 420: * "Before" is typically defined as being to the left or above. 421: * 422: * @param x the X coordinate of the point 423: * @param y the Y coordinate of the point 424: * @param r the rectangle to test the point against 425: * 426: * @return <code>true</code> if the specified point lies before the 427: * given <code>Rectangle</code>, <code>false</code> otherwise 428: */ 429: protected boolean isBefore(int x, int y, Rectangle r) 430: { 431: boolean result = false; 432: 433: if (myAxis == X_AXIS) 434: result = x < r.x; 435: else 436: result = y < r.y; 437: 438: return result; 439: } 440: 441: /** 442: * Returns <code>true</code> if the specified point lies after the 443: * given <code>Rectangle</code>, <code>false</code> otherwise. 444: * 445: * "After" is typically defined as being to the right or below. 446: * 447: * @param x the X coordinate of the point 448: * @param y the Y coordinate of the point 449: * @param r the rectangle to test the point against 450: * 451: * @return <code>true</code> if the specified point lies after the 452: * given <code>Rectangle</code>, <code>false</code> otherwise 453: */ 454: protected boolean isAfter(int x, int y, Rectangle r) 455: { 456: boolean result = false; 457: 458: if (myAxis == X_AXIS) 459: result = x > r.x; 460: else 461: result = y > r.y; 462: 463: return result; 464: } 465: 466: /** 467: * Returns the child <code>View</code> at the specified location. 468: * 469: * @param x the X coordinate 470: * @param y the Y coordinate 471: * @param r the inner allocation of this <code>BoxView</code> on entry, 472: * the allocation of the found child on exit 473: * 474: * @return the child <code>View</code> at the specified location 475: */ 476: protected View getViewAtPoint(int x, int y, Rectangle r) 477: { 478: View result = null; 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: if (result == null && count > 0) 494: return getView(count - 1); 495: return result; 496: } 497: 498: /** 499: * Computes the allocation for a child <code>View</code>. The parameter 500: * <code>a</code> stores the allocation of this <code>CompositeView</code> 501: * and is then adjusted to hold the allocation of the child view. 502: * 503: * @param index 504: * the index of the child <code>View</code> 505: * @param a 506: * the allocation of this <code>CompositeView</code> before the 507: * call, the allocation of the child on exit 508: */ 509: protected void childAllocation(int index, Rectangle a) 510: { 511: if (! isAllocationValid()) 512: layout(a.width, a.height); 513: 514: a.x += offsetsX[index]; 515: a.y += offsetsY[index]; 516: a.width = spansX[index]; 517: a.height = spansY[index]; 518: } 519: 520: /** 521: * Lays out the children of this <code>BoxView</code> with the specified 522: * bounds. 523: * 524: * @param width the width of the allocated region for the children (that 525: * is the inner allocation of this <code>BoxView</code> 526: * @param height the height of the allocated region for the children (that 527: * is the inner allocation of this <code>BoxView</code> 528: */ 529: protected void layout(int width, int height) 530: { 531: baselineLayout(width, X_AXIS, offsetsX, spansX); 532: baselineLayout(height, Y_AXIS, offsetsY, spansY); 533: } 534: 535: /** 536: * Performs the layout along the major axis of a <code>BoxView</code>. 537: * 538: * @param targetSpan the (inner) span of the <code>BoxView</code> in which 539: * to layout the children 540: * @param axis the axis along which the layout is performed 541: * @param offsets the array that holds the offsets of the children on exit 542: * @param spans the array that holds the spans of the children on exit 543: */ 544: protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, 545: int[] spans) 546: { 547: SizeRequirements[] childReqs = getChildRequirements(axis); 548: // Calculate the spans and offsets using the SizeRequirements uility 549: // methods. 550: SizeRequirements.calculateTiledPositions(targetSpan, null, childReqs, 551: offsets, spans); 552: validateLayout(axis); 553: } 554: 555: /** 556: * Performs the layout along the minor axis of a <code>BoxView</code>. 557: * 558: * @param targetSpan the (inner) span of the <code>BoxView</code> in which 559: * to layout the children 560: * @param axis the axis along which the layout is performed 561: * @param offsets the array that holds the offsets of the children on exit 562: * @param spans the array that holds the spans of the children on exit 563: */ 564: protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, 565: int[] spans) 566: { 567: SizeRequirements[] childReqs = getChildRequirements(axis); 568: // Calculate the spans and offsets using the SizeRequirements uility 569: // methods. 570: // TODO: This might be an opportunity for performance optimization. Here 571: // we could use a cached instance of SizeRequirements instead of passing 572: // null to baselineRequirements. However, this would involve rewriting 573: // the baselineRequirements() method to not use the SizeRequirements 574: // utility method, since they cannot reuse a cached instance. 575: SizeRequirements total = baselineRequirements(axis, null); 576: SizeRequirements.calculateAlignedPositions(targetSpan, total, childReqs, 577: offsets, spans); 578: validateLayout(axis); 579: } 580: 581: /** 582: * Returns <code>true</code> if the cached allocations for the children 583: * are still valid, <code>false</code> otherwise. 584: * 585: * @return <code>true</code> if the cached allocations for the children 586: * are still valid, <code>false</code> otherwise 587: */ 588: protected boolean isAllocationValid() 589: { 590: return isLayoutValid(X_AXIS) && isLayoutValid(Y_AXIS); 591: } 592: 593: /** 594: * Return the current width of the box. This is the last allocated width. 595: * 596: * @return the current width of the box 597: */ 598: public int getWidth() 599: { 600: return width; 601: } 602: 603: /** 604: * Return the current height of the box. This is the last allocated height. 605: * 606: * @return the current height of the box 607: */ 608: public int getHeight() 609: { 610: return height; 611: } 612: 613: /** 614: * Sets the size of the view. If the actual size has changed, the layout 615: * is updated accordingly. 616: * 617: * @param width the new width 618: * @param height the new height 619: */ 620: public void setSize(float width, float height) 621: { 622: if (this.width != (int) width) 623: layoutChanged(X_AXIS); 624: if (this.height != (int) height) 625: layoutChanged(Y_AXIS); 626: 627: this.width = (int) width; 628: this.height = (int) height; 629: 630: Rectangle outside = new Rectangle(0, 0, this.width, this.height); 631: Rectangle inside = getInsideAllocation(outside); 632: if (!isAllocationValid()) 633: layout(inside.width, inside.height); 634: } 635: 636: /** 637: * Sets the layout to valid for a specific axis. 638: * 639: * @param axis the axis for which to validate the layout 640: */ 641: void validateLayout(int axis) 642: { 643: if (axis == X_AXIS) 644: xLayoutValid = true; 645: if (axis == Y_AXIS) 646: yLayoutValid = true; 647: } 648: 649: /** 650: * Returns the size requirements of this view's children for the major 651: * axis. 652: * 653: * @return the size requirements of this view's children for the major 654: * axis 655: */ 656: SizeRequirements[] getChildRequirements(int axis) 657: { 658: // Allocate SizeRequirements for each child view. 659: int count = getViewCount(); 660: SizeRequirements[] childReqs = new SizeRequirements[count]; 661: for (int i = 0; i < count; ++i) 662: { 663: View view = getView(i); 664: childReqs[i] = new SizeRequirements((int) view.getMinimumSpan(axis), 665: (int) view.getPreferredSpan(axis), 666: (int) view.getMaximumSpan(axis), 667: view.getAlignment(axis)); 668: } 669: return childReqs; 670: } 671: 672: /** 673: * Returns the span for the child view with the given index for the specified 674: * axis. 675: * 676: * @param axis the axis to examine, either <code>X_AXIS</code> or 677: * <code>Y_AXIS</code> 678: * @param childIndex the index of the child for for which to return the span 679: * 680: * @return the span for the child view with the given index for the specified 681: * axis 682: */ 683: protected int getSpan(int axis, int childIndex) 684: { 685: if (axis == X_AXIS) 686: return spansX[childIndex]; 687: else 688: return spansY[childIndex]; 689: } 690: 691: /** 692: * Returns the offset for the child view with the given index for the 693: * specified axis. 694: * 695: * @param axis the axis to examine, either <code>X_AXIS</code> or 696: * <code>Y_AXIS</code> 697: * @param childIndex the index of the child for for which to return the span 698: * 699: * @return the offset for the child view with the given index for the 700: * specified axis 701: */ 702: protected int getOffset(int axis, int childIndex) 703: { 704: if (axis == X_AXIS) 705: return offsetsX[childIndex]; 706: else 707: return offsetsY[childIndex]; 708: } 709: 710: /** 711: * Returns the alignment for this box view for the specified axis. The 712: * axis that is tiled (the major axis) will be requested to be aligned 713: * centered (0.5F). The minor axis alignment depends on the child view's 714: * total alignment. 715: * 716: * @param axis the axis which is examined 717: * 718: * @return the alignment for this box view for the specified axis 719: */ 720: public float getAlignment(int axis) 721: { 722: if (axis == myAxis) 723: return 0.5F; 724: else 725: return baselineRequirements(axis, null).alignment; 726: } 727: 728: /** 729: * Called by a child View when its preferred span has changed. 730: * 731: * @param width indicates that the preferred width of the child changed. 732: * @param height indicates that the preferred height of the child changed. 733: * @param child the child View. 734: */ 735: public void preferenceChanged (View child, boolean width, boolean height) 736: { 737: if (width) 738: xLayoutValid = false; 739: if (height) 740: yLayoutValid = false; 741: super.preferenceChanged(child, width, height); 742: } 743: 744: /** 745: * Maps the document model position <code>pos</code> to a Shape 746: * in the view coordinate space. This method overrides CompositeView's 747: * method to make sure the children are allocated properly before 748: * calling the super's behaviour. 749: */ 750: public Shape modelToView(int pos, Shape a, Position.Bias bias) 751: throws BadLocationException 752: { 753: // Make sure everything is allocated properly and then call super 754: if (!isAllocationValid()) 755: { 756: Rectangle bounds = a.getBounds(); 757: setSize(bounds.width, bounds.height); 758: } 759: return super.modelToView(pos, a, bias); 760: } 761: }
GNU Classpath (0.20) |