GNU Classpath (0.18) | ||
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 {@link #setSize()} is 159: * called, typically from within the {@link #paint()} method. 160: * 161: * Valid values for the axis are {@link View#X_AXIS} and 162: * {@link View#Y_AXIS}. 163: * 164: * @param axis an <code>int</code> value 165: */ 166: public void layoutChanged(int axis) 167: { 168: switch (axis) 169: { 170: case X_AXIS: 171: xLayoutValid = false; 172: break; 173: case Y_AXIS: 174: yLayoutValid = false; 175: break; 176: default: 177: throw new IllegalArgumentException("Invalid axis parameter."); 178: } 179: } 180: 181: /** 182: * Returns <code>true</code> if the layout along the specified 183: * <code>axis</code> is valid, <code>false</code> otherwise. 184: * 185: * Valid values for the axis are {@link View#X_AXIS} and 186: * {@link View#Y_AXIS}. 187: * 188: * @param axis the axis 189: * 190: * @return <code>true</code> if the layout along the specified 191: * <code>axis</code> is valid, <code>false</code> otherwise 192: */ 193: protected boolean isLayoutValid(int axis) 194: { 195: boolean valid = false; 196: switch (axis) 197: { 198: case X_AXIS: 199: valid = xLayoutValid; 200: break; 201: case Y_AXIS: 202: valid = yLayoutValid; 203: break; 204: default: 205: throw new IllegalArgumentException("Invalid axis parameter."); 206: } 207: return valid; 208: } 209: 210: /** 211: * Paints the child <code>View</code> at the specified <code>index</code>. 212: * This method modifies the actual values in <code>alloc</code> so make 213: * sure you have a copy of the original values if you need them. 214: * 215: * @param g the <code>Graphics</code> context to paint to 216: * @param alloc the allocated region for the child to paint into 217: * @param index the index of the child to be painted 218: * 219: * @see {@link #childAllocation} 220: */ 221: protected void paintChild(Graphics g, Rectangle alloc, int index) 222: { 223: View child = getView(index); 224: childAllocation(index, alloc); 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: 305: Rectangle copy = new Rectangle(inside); 306: int count = getViewCount(); 307: for (int i = 0; i < count; ++i) 308: { 309: // TODO: Figure out if the parameter to paintChild is meant to 310: // be the child allocation or the allocation of this BoxView. 311: // I assume the second option here. 312: // We pass this method a copy of the inside rectangle here because 313: // it modifies the actual values. 314: copy.setBounds(inside); 315: paintChild(g, copy, i); 316: } 317: } 318: 319: /** 320: * Returns the preferred span of the content managed by this 321: * <code>View</code> along the specified <code>axis</code>. 322: * 323: * @param axis the axis 324: * 325: * @return the preferred span of this <code>View</code>. 326: */ 327: public float getPreferredSpan(int axis) 328: { 329: SizeRequirements sr = new SizeRequirements(); 330: int pref = baselineRequirements(axis, sr).preferred; 331: return (float) pref; 332: } 333: 334: public float getMaximumSpan(int axis) 335: { 336: if (axis == getAxis()) 337: return getPreferredSpan(axis); 338: else 339: return Integer.MAX_VALUE; 340: } 341: 342: /** 343: * Calculates the size requirements for this <code>BoxView</code> along 344: * the specified axis. 345: * 346: * @param axis the axis that is examined 347: * @param sr the <code>SizeRequirements</code> object to hold the result, 348: * if <code>null</code>, a new one is created 349: * 350: * @return the size requirements for this <code>BoxView</code> along 351: * the specified axis 352: */ 353: protected SizeRequirements baselineRequirements(int axis, 354: SizeRequirements sr) 355: { 356: SizeRequirements result; 357: if (axis == myAxis) 358: result = calculateMajorAxisRequirements(axis, sr); 359: else 360: result = calculateMinorAxisRequirements(axis, sr); 361: return result; 362: } 363: 364: /** 365: * Calculates the size requirements of this <code>BoxView</code> along 366: * its major axis, that is the axis specified in the constructor. 367: * 368: * @param axis the axis that is examined 369: * @param sr the <code>SizeRequirements</code> object to hold the result, 370: * if <code>null</code>, a new one is created 371: * 372: * @return the size requirements for this <code>BoxView</code> along 373: * the specified axis 374: */ 375: protected SizeRequirements calculateMajorAxisRequirements(int axis, 376: SizeRequirements sr) 377: { 378: if (sr == null) 379: sr = new SizeRequirements(); 380: else 381: { 382: sr.maximum = 0; 383: sr.minimum = 0; 384: sr.preferred = 0; 385: sr.alignment = 0.5F; 386: } 387: 388: int count = getViewCount(); 389: 390: // Sum up the sizes of the children along the specified axis. 391: for (int i = 0; i < count; ++i) 392: { 393: View child = getView(i); 394: sr.minimum += child.getMinimumSpan(axis); 395: sr.preferred += child.getPreferredSpan(axis); 396: sr.maximum += child.getMaximumSpan(axis); 397: } 398: return sr; 399: } 400: 401: /** 402: * Calculates the size requirements of this <code>BoxView</code> along 403: * its minor axis, that is the axis opposite to the axis specified in the 404: * constructor. 405: * 406: * @param axis the axis that is examined 407: * @param sr the <code>SizeRequirements</code> object to hold the result, 408: * if <code>null</code>, a new one is created 409: * 410: * @return the size requirements for this <code>BoxView</code> along 411: * the specified axis 412: */ 413: protected SizeRequirements calculateMinorAxisRequirements(int axis, 414: SizeRequirements sr) 415: { 416: if (sr == null) 417: sr = new SizeRequirements(); 418: else 419: { 420: sr.maximum = 0; 421: sr.minimum = 0; 422: sr.preferred = 0; 423: sr.alignment = 0.5F; 424: } 425: 426: int count = getViewCount(); 427: 428: int aboveBaseline = 0; 429: int belowBaseline = 0; 430: int aboveBaselineMin = 0; 431: int belowBaselineMin = 0; 432: int aboveBaselineMax = 0; 433: int belowBaselineMax = 0; 434: 435: for (int i = 0; i < count; ++i) 436: { 437: View child = getView(i); 438: float align = child.getAlignment(axis); 439: int pref = (int) child.getPreferredSpan(axis); 440: int min = (int) child.getMinimumSpan(axis); 441: int max = (int) child.getMaximumSpan(axis); 442: aboveBaseline += (int) (align * pref); 443: belowBaseline += (int) ((1.F - align) * pref); 444: aboveBaselineMin += (int) (align * min); 445: belowBaselineMin += (int) ((1.F - align) * min); 446: aboveBaselineMax += (int) (align * max); 447: belowBaselineMax += (int) ((1.F - align) * max); 448: } 449: sr.minimum = aboveBaselineMin + belowBaselineMin; 450: sr.maximum = aboveBaselineMax + belowBaselineMax; 451: sr.preferred = aboveBaseline + belowBaseline; 452: if (aboveBaseline == 0) 453: sr.alignment = 1.0F; 454: else 455: sr.alignment = (float) (sr.preferred / aboveBaseline); 456: 457: return sr; 458: } 459: 460: /** 461: * Returns <code>true</code> if the specified point lies before the 462: * given <code>Rectangle</code>, <code>false</code> otherwise. 463: * 464: * "Before" is typically defined as being to the left or above. 465: * 466: * @param x the X coordinate of the point 467: * @param y the Y coordinate of the point 468: * @param r the rectangle to test the point against 469: * 470: * @return <code>true</code> if the specified point lies before the 471: * given <code>Rectangle</code>, <code>false</code> otherwise 472: */ 473: protected boolean isBefore(int x, int y, Rectangle r) 474: { 475: boolean result = false; 476: 477: if (myAxis == X_AXIS) 478: result = x < r.x; 479: else 480: result = y < r.y; 481: 482: return result; 483: } 484: 485: /** 486: * Returns <code>true</code> if the specified point lies after the 487: * given <code>Rectangle</code>, <code>false</code> otherwise. 488: * 489: * "After" is typically defined as being to the right or below. 490: * 491: * @param x the X coordinate of the point 492: * @param y the Y coordinate of the point 493: * @param r the rectangle to test the point against 494: * 495: * @return <code>true</code> if the specified point lies after the 496: * given <code>Rectangle</code>, <code>false</code> otherwise 497: */ 498: protected boolean isAfter(int x, int y, Rectangle r) 499: { 500: boolean result = false; 501: 502: if (myAxis == X_AXIS) 503: result = x > r.x; 504: else 505: result = y > r.y; 506: 507: return result; 508: } 509: 510: /** 511: * Returns the child <code>View</code> at the specified location. 512: * 513: * @param x the X coordinate 514: * @param y the Y coordinate 515: * @param r the inner allocation of this <code>BoxView</code> on entry, 516: * the allocation of the found child on exit 517: * 518: * @return the child <code>View</code> at the specified location 519: */ 520: protected View getViewAtPoint(int x, int y, Rectangle r) 521: { 522: View result = null; 523: 524: int count = getViewCount(); 525: Rectangle copy = new Rectangle(r); 526: 527: for (int i = 0; i < count; ++i) 528: { 529: copy.setBounds(r); 530: childAllocation(i, r); 531: if (copy.contains(x, y)) 532: { 533: result = getView(i); 534: break; 535: } 536: } 537: 538: return result; 539: } 540: 541: /** 542: * Computes the allocation for a child <code>View</code>. The parameter 543: * <code>a</code> stores the allocation of this <code>CompositeView</code> 544: * and is then adjusted to hold the allocation of the child view. 545: * 546: * @param index the index of the child <code>View</code> 547: * @param a the allocation of this <code>CompositeView</code> before the 548: * call, the allocation of the child on exit 549: */ 550: protected void childAllocation(int index, Rectangle a) 551: { 552: if (! isAllocationValid()) 553: layout(a.width, a.height); 554: 555: a.x += offsetsX[index]; 556: a.y += offsetsY[index]; 557: a.width = spansX[index]; 558: a.height = spansY[index]; 559: } 560: 561: /** 562: * Lays out the children of this <code>BoxView</code> with the specified 563: * bounds. 564: * 565: * @param width the width of the allocated region for the children (that 566: * is the inner allocation of this <code>BoxView</code> 567: * @param height the height of the allocated region for the children (that 568: * is the inner allocation of this <code>BoxView</code> 569: */ 570: protected void layout(int width, int height) 571: { 572: this.width = width; 573: this.height = height; 574: 575: if (myAxis == X_AXIS) 576: { 577: layoutMajorAxis(width, X_AXIS, offsetsX, spansX); 578: layoutMinorAxis(height, Y_AXIS, offsetsY, spansY); 579: } 580: else 581: { 582: layoutMajorAxis(height, Y_AXIS, offsetsY, spansY); 583: layoutMinorAxis(width, X_AXIS, offsetsX, spansX); 584: } 585: } 586: 587: /** 588: * Performs the layout along the major axis of a <code>BoxView</code>. 589: * 590: * @param targetSpan the (inner) span of the <code>BoxView</code> in which 591: * to layout the children 592: * @param axis the axis along which the layout is performed 593: * @param offsets the array that holds the offsets of the children on exit 594: * @param offsets the array that holds the spans of the children on exit 595: */ 596: protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, 597: int[] spans) 598: { 599: // Allocate SizeRequirements for each child view. 600: int count = getViewCount(); 601: SizeRequirements[] childReqs = new SizeRequirements[count]; 602: for (int i = 0; i < count; ++i) 603: { 604: View view = getView(i); 605: childReqs[i] = new SizeRequirements((int) view.getMinimumSpan(axis), 606: (int) view.getPreferredSpan(axis), 607: (int) view.getMaximumSpan(axis), 608: view.getAlignment(axis)); 609: } 610: 611: // Calculate the spans and offsets using the SizeRequirements uility 612: // methods. 613: SizeRequirements.calculateTiledPositions(targetSpan, null, childReqs, 614: offsets, spans); 615: 616: validateLayout(axis); 617: } 618: 619: /** 620: * Performs the layout along the minor axis of a <code>BoxView</code>. 621: * 622: * @param targetSpan the (inner) span of the <code>BoxView</code> in which 623: * to layout the children 624: * @param axis the axis along which the layout is performed 625: * @param offsets the array that holds the offsets of the children on exit 626: * @param offsets the array that holds the spans of the children on exit 627: */ 628: protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, 629: int[] spans) 630: { 631: // Allocate SizeRequirements for each child view. 632: int count = getViewCount(); 633: SizeRequirements[] childReqs = new SizeRequirements[count]; 634: for (int i = 0; i < count; ++i) 635: { 636: View view = getView(i); 637: childReqs[i] = new SizeRequirements((int) view.getMinimumSpan(axis), 638: (int) view.getPreferredSpan(axis), 639: (int) view.getMaximumSpan(axis), 640: view.getAlignment(axis)); 641: } 642: 643: // Calculate the spans and offsets using the SizeRequirements uility 644: // methods. 645: SizeRequirements.calculateAlignedPositions(targetSpan, null, childReqs, 646: offsets, spans); 647: validateLayout(axis); 648: } 649: 650: /** 651: * Returns <code>true</code> if the cached allocations for the children 652: * are still valid, <code>false</code> otherwise. 653: * 654: * @return <code>true</code> if the cached allocations for the children 655: * are still valid, <code>false</code> otherwise 656: */ 657: protected boolean isAllocationValid() 658: { 659: return isLayoutValid(X_AXIS) && isLayoutValid(Y_AXIS); 660: } 661: 662: /** 663: * Return the current width of the box. This is the last allocated width. 664: * 665: * @return the current width of the box 666: */ 667: public int getWidth() 668: { 669: return width; 670: } 671: 672: /** 673: * Return the current height of the box. This is the last allocated height. 674: * 675: * @return the current height of the box 676: */ 677: public int getHeight() 678: { 679: return height; 680: } 681: 682: /** 683: * Sets the size of the view. If the actual size has changed, the layout 684: * is updated accordingly. 685: * 686: * @param width the new width 687: * @param height the new height 688: */ 689: public void setSize(float width, float height) 690: { 691: if (this.width != (int) width) 692: layoutChanged(X_AXIS); 693: if (this.height != (int) height) 694: layoutChanged(Y_AXIS); 695: 696: Rectangle outside = new Rectangle(0, 0, this.width, this.height); 697: Rectangle inside = getInsideAllocation(outside); 698: if (!isAllocationValid()) 699: layout(inside.width, inside.height); 700: } 701: 702: /** 703: * Sets the layout to valid for a specific axis. 704: * 705: * @param axis the axis for which to validate the layout 706: */ 707: void validateLayout(int axis) 708: { 709: if (axis == X_AXIS) 710: xLayoutValid = true; 711: if (axis == Y_AXIS) 712: yLayoutValid = true; 713: } 714: }
GNU Classpath (0.18) |