| GNU Classpath (0.95) | |
| Frames | No Frames |
1: /* AsyncBoxView.java -- A box view that performs layout asynchronously 2: Copyright (C) 2006 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.Component; 42: import java.awt.Graphics; 43: import java.awt.Rectangle; 44: import java.awt.Shape; 45: import java.util.ArrayList; 46: 47: import javax.swing.event.DocumentEvent; 48: import javax.swing.text.Position.Bias; 49: 50: /** 51: * A {@link View} implementation that lays out its child views in a box, either 52: * vertically or horizontally. The difference to {@link BoxView} is that the 53: * layout is performed in an asynchronous manner. This helps to keep the 54: * eventqueue free from non-GUI related tasks. 55: * 56: * This view is currently not used in standard text components. In order to 57: * use it you would have to implement a special {@link EditorKit} with a 58: * {@link ViewFactory} that returns this view. For example: 59: * 60: * <pre> 61: * static class AsyncEditorKit extends StyledEditorKit implements ViewFactory 62: * { 63: * public View create(Element el) 64: * { 65: * if (el.getName().equals(AbstractDocument.SectionElementName)) 66: * return new AsyncBoxView(el, View.Y_AXIS); 67: * return super.getViewFactory().create(el); 68: * } 69: * public ViewFactory getViewFactory() { 70: * return this; 71: * } 72: * } 73: * </pre> 74: * 75: * @author Roman Kennke (kennke@aicas.com) 76: * 77: * @since 1.3 78: */ 79: public class AsyncBoxView 80: extends View 81: { 82: 83: /** 84: * Manages the effective position of child views. That keeps the visible 85: * layout stable while the AsyncBoxView might be changing until the layout 86: * thread decides to publish the new layout. 87: */ 88: public class ChildLocator 89: { 90: 91: /** 92: * The last valid location. 93: */ 94: protected ChildState lastValidOffset; 95: 96: /** 97: * The last allocation. 98: */ 99: protected Rectangle lastAlloc; 100: 101: /** 102: * A Rectangle used for child allocation calculation to avoid creation 103: * of lots of garbage Rectangle objects. 104: */ 105: protected Rectangle childAlloc; 106: 107: /** 108: * Creates a new ChildLocator. 109: */ 110: public ChildLocator() 111: { 112: lastAlloc = new Rectangle(); 113: childAlloc = new Rectangle(); 114: } 115: 116: /** 117: * Receives notification that a child has changed. This is called by 118: * child state objects that have changed it's major span. 119: * 120: * This sets the {@link #lastValidOffset} field to <code>cs</code> if 121: * the new child state's view start offset is smaller than the start offset 122: * of the current child state's view or when <code>lastValidOffset</code> 123: * is <code>null</code>. 124: * 125: * @param cs the child state object that has changed 126: */ 127: public synchronized void childChanged(ChildState cs) 128: { 129: if (lastValidOffset == null 130: || cs.getChildView().getStartOffset() 131: < lastValidOffset.getChildView().getStartOffset()) 132: { 133: lastValidOffset = cs; 134: } 135: } 136: 137: /** 138: * Returns the view index of the view that occupies the specified area, or 139: * <code>-1</code> if there is no such child view. 140: * 141: * @param x the x coordinate (relative to <code>a</code>) 142: * @param y the y coordinate (relative to <code>a</code>) 143: * @param a the current allocation of this view 144: * 145: * @return the view index of the view that occupies the specified area, or 146: * <code>-1</code> if there is no such child view 147: */ 148: public int getViewIndexAtPoint(float x, float y, Shape a) 149: { 150: setAllocation(a); 151: float targetOffset = (getMajorAxis() == X_AXIS) ? x - lastAlloc.x 152: : y - lastAlloc.y; 153: int index = getViewIndexAtVisualOffset(targetOffset); 154: return index; 155: } 156: 157: /** 158: * Returns the current allocation for a child view. This updates the 159: * offsets for all children <em>before</em> the requested child view. 160: * 161: * @param index the index of the child view 162: * @param a the current allocation of this view 163: * 164: * @return the current allocation for a child view 165: */ 166: public synchronized Shape getChildAllocation(int index, Shape a) 167: { 168: if (a == null) 169: return null; 170: setAllocation(a); 171: ChildState cs = getChildState(index); 172: if (cs.getChildView().getStartOffset() 173: > lastValidOffset.getChildView().getStartOffset()) 174: { 175: updateChildOffsetsToIndex(index); 176: } 177: Shape ca = getChildAllocation(index); 178: return ca; 179: } 180: 181: /** 182: * Paints all child views. 183: * 184: * @param g the graphics context to use 185: */ 186: public synchronized void paintChildren(Graphics g) 187: { 188: Rectangle clip = g.getClipBounds(); 189: float targetOffset = (getMajorAxis() == X_AXIS) ? clip.x - lastAlloc.x 190: : clip.y - lastAlloc.y; 191: int index = getViewIndexAtVisualOffset(targetOffset); 192: int n = getViewCount(); 193: float offs = getChildState(index).getMajorOffset(); 194: for (int i = index; i < n; i++) 195: { 196: ChildState cs = getChildState(i); 197: cs.setMajorOffset(offs); 198: Shape ca = getChildAllocation(i); 199: if (ca.intersects(clip)) 200: { 201: synchronized (cs) 202: { 203: View v = cs.getChildView(); 204: v.paint(g, ca); 205: } 206: } 207: else 208: { 209: // done painting intersection 210: break; 211: } 212: offs += cs.getMajorSpan(); 213: } 214: } 215: 216: /** 217: * Returns the current allocation of the child view with the specified 218: * index. Note that this will <em>not</em> update any location information. 219: * 220: * @param index the index of the requested child view 221: * 222: * @return the current allocation of the child view with the specified 223: * index 224: */ 225: protected Shape getChildAllocation(int index) 226: { 227: ChildState cs = getChildState(index); 228: if (! cs.isLayoutValid()) 229: cs.run(); 230: 231: if (getMajorAxis() == X_AXIS) 232: { 233: childAlloc.x = lastAlloc.x + (int) cs.getMajorOffset(); 234: childAlloc.y = lastAlloc.y + (int) cs.getMinorOffset(); 235: childAlloc.width = (int) cs.getMajorSpan(); 236: childAlloc.height = (int) cs.getMinorSpan(); 237: } 238: else 239: { 240: childAlloc.y = lastAlloc.y + (int) cs.getMajorOffset(); 241: childAlloc.x = lastAlloc.x + (int) cs.getMinorOffset(); 242: childAlloc.height = (int) cs.getMajorSpan(); 243: childAlloc.width = (int) cs.getMinorSpan(); 244: } 245: return childAlloc; 246: } 247: 248: /** 249: * Sets the current allocation for this view. 250: * 251: * @param a the allocation to set 252: */ 253: protected void setAllocation(Shape a) 254: { 255: if (a instanceof Rectangle) 256: lastAlloc.setBounds((Rectangle) a); 257: else 258: lastAlloc.setBounds(a.getBounds()); 259: 260: setSize(lastAlloc.width, lastAlloc.height); 261: } 262: 263: /** 264: * Returns the index of the view at the specified offset along the major 265: * layout axis. 266: * 267: * @param targetOffset the requested offset 268: * 269: * @return the index of the view at the specified offset along the major 270: * layout axis 271: */ 272: protected int getViewIndexAtVisualOffset(float targetOffset) 273: { 274: int n = getViewCount(); 275: if (n > 0) 276: { 277: if (lastValidOffset == null) 278: lastValidOffset = getChildState(0); 279: if (targetOffset > majorSpan) 280: return 0; 281: else if (targetOffset > lastValidOffset.getMajorOffset()) 282: return updateChildOffsets(targetOffset); 283: else 284: { 285: float offs = 0f; 286: for (int i = 0; i < n; i++) 287: { 288: ChildState cs = getChildState(i); 289: float nextOffs = offs + cs.getMajorSpan(); 290: if (targetOffset < nextOffs) 291: return i; 292: offs = nextOffs; 293: } 294: } 295: } 296: return n - 1; 297: } 298: 299: /** 300: * Updates all the child view offsets up to the specified targetOffset. 301: * 302: * @param targetOffset the offset up to which the child view offsets are 303: * updated 304: * 305: * @return the index of the view at the specified offset 306: */ 307: private int updateChildOffsets(float targetOffset) 308: { 309: int n = getViewCount(); 310: int targetIndex = n - 1; 311: int pos = lastValidOffset.getChildView().getStartOffset(); 312: int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward); 313: float start = lastValidOffset.getMajorOffset(); 314: float lastOffset = start; 315: for (int i = startIndex; i < n; i++) 316: { 317: ChildState cs = getChildState(i); 318: cs.setMajorOffset(lastOffset); 319: lastOffset += cs.getMajorSpan(); 320: if (targetOffset < lastOffset) 321: { 322: targetIndex = i; 323: lastValidOffset = cs; 324: break; 325: } 326: } 327: return targetIndex; 328: } 329: 330: /** 331: * Updates the offsets of the child views up to the specified index. 332: * 333: * @param index the index up to which the offsets are updated 334: */ 335: private void updateChildOffsetsToIndex(int index) 336: { 337: int pos = lastValidOffset.getChildView().getStartOffset(); 338: int startIndex = getViewIndexAtPosition(pos, Position.Bias.Forward); 339: float lastOffset = lastValidOffset.getMajorOffset(); 340: for (int i = startIndex; i <= index; i++) 341: { 342: ChildState cs = getChildState(i); 343: cs.setMajorOffset(lastOffset); 344: lastOffset += cs.getMajorSpan(); 345: } 346: } 347: } 348: 349: /** 350: * Represents the layout state of a child view. 351: */ 352: public class ChildState 353: implements Runnable 354: { 355: 356: /** 357: * The child view for this state record. 358: */ 359: private View childView; 360: 361: /** 362: * Indicates if the minor axis requirements of this child view are valid 363: * or not. 364: */ 365: private boolean minorValid; 366: 367: /** 368: * Indicates if the major axis requirements of this child view are valid 369: * or not. 370: */ 371: private boolean majorValid; 372: 373: /** 374: * Indicates if the current child size is valid. This is package private 375: * to avoid synthetic accessor method. 376: */ 377: boolean childSizeValid; 378: 379: /** 380: * The child views minimumSpan. This is package private to avoid accessor 381: * method. 382: */ 383: float minimum; 384: 385: /** 386: * The child views preferredSpan. This is package private to avoid accessor 387: * method. 388: */ 389: float preferred; 390: 391: /** 392: * The current span of the child view along the major axis. 393: */ 394: private float majorSpan; 395: 396: /** 397: * The current offset of the child view along the major axis. 398: */ 399: private float majorOffset; 400: 401: /** 402: * The current span of the child view along the minor axis. 403: */ 404: private float minorSpan; 405: 406: /** 407: * The current offset of the child view along the major axis. 408: */ 409: private float minorOffset; 410: 411: /** 412: * The child views maximumSpan. 413: */ 414: private float maximum; 415: 416: /** 417: * Creates a new <code>ChildState</code> object for the specified child 418: * view. 419: * 420: * @param view the child view for which to create the state record 421: */ 422: public ChildState(View view) 423: { 424: childView = view; 425: } 426: 427: /** 428: * Returns the child view for which this <code>ChildState</code> represents 429: * the layout state. 430: * 431: * @return the child view for this child state object 432: */ 433: public View getChildView() 434: { 435: return childView; 436: } 437: 438: /** 439: * Returns <code>true</code> if the current layout information is valid, 440: * <code>false</code> otherwise. 441: * 442: * @return <code>true</code> if the current layout information is valid, 443: * <code>false</code> otherwise 444: */ 445: public boolean isLayoutValid() 446: { 447: return minorValid && majorValid && childSizeValid; 448: } 449: 450: /** 451: * Performs the layout update for the child view managed by this 452: * <code>ChildState</code>. 453: */ 454: public void run() 455: { 456: Document doc = getDocument(); 457: if (doc instanceof AbstractDocument) 458: { 459: AbstractDocument abstractDoc = (AbstractDocument) doc; 460: abstractDoc.readLock(); 461: } 462: 463: try 464: { 465: 466: if (!(minorValid && majorValid && childSizeValid) 467: && childView.getParent() == AsyncBoxView.this) 468: { 469: synchronized(AsyncBoxView.this) 470: { 471: changing = this; 472: } 473: update(); 474: synchronized(AsyncBoxView.this) 475: { 476: changing = null; 477: } 478: // Changing the major axis may cause the minor axis 479: // requirements to have changed, so we need to do this again. 480: update(); 481: } 482: } 483: finally 484: { 485: if (doc instanceof AbstractDocument) 486: { 487: AbstractDocument abstractDoc = (AbstractDocument) doc; 488: abstractDoc.readUnlock(); 489: } 490: } 491: } 492: 493: /** 494: * Performs the actual update after the run methods has made its checks 495: * and locked the document. 496: */ 497: private void update() 498: { 499: int majorAxis = getMajorAxis(); 500: boolean minorUpdated = false; 501: synchronized (this) 502: { 503: if (! minorValid) 504: { 505: int minorAxis = getMinorAxis(); 506: minimum = childView.getMinimumSpan(minorAxis); 507: preferred = childView.getPreferredSpan(minorAxis); 508: maximum = childView.getMaximumSpan(minorAxis); 509: minorValid = true; 510: minorUpdated = true; 511: } 512: } 513: if (minorUpdated) 514: minorRequirementChange(this); 515: 516: boolean majorUpdated = false; 517: float delta = 0.0F; 518: synchronized (this) 519: { 520: if (! majorValid) 521: { 522: float oldSpan = majorSpan; 523: majorSpan = childView.getPreferredSpan(majorAxis); 524: delta = majorSpan - oldSpan; 525: majorValid = true; 526: majorUpdated = true; 527: } 528: } 529: if (majorUpdated) 530: { 531: majorRequirementChange(this, delta); 532: locator.childChanged(this); 533: } 534: 535: synchronized (this) 536: { 537: if (! childSizeValid) 538: { 539: float w; 540: float h; 541: if (majorAxis == X_AXIS) 542: { 543: w = majorSpan; 544: h = getMinorSpan(); 545: } 546: else 547: { 548: w = getMinorSpan(); 549: h = majorSpan; 550: } 551: childSizeValid = true; 552: childView.setSize(w, h); 553: } 554: } 555: } 556: 557: /** 558: * Returns the span of the child view along the minor layout axis. 559: * 560: * @return the span of the child view along the minor layout axis 561: */ 562: public float getMinorSpan() 563: { 564: float retVal; 565: if (maximum < minorSpan) 566: retVal = maximum; 567: else 568: retVal = Math.max(minimum, minorSpan); 569: return retVal; 570: } 571: 572: /** 573: * Returns the offset of the child view along the minor layout axis. 574: * 575: * @return the offset of the child view along the minor layout axis 576: */ 577: public float getMinorOffset() 578: { 579: float retVal; 580: if (maximum < minorSpan) 581: { 582: float align = childView.getAlignment(getMinorAxis()); 583: retVal = ((minorSpan - maximum) * align); 584: } 585: else 586: retVal = 0f; 587: 588: return retVal; 589: } 590: 591: /** 592: * Returns the span of the child view along the major layout axis. 593: * 594: * @return the span of the child view along the major layout axis 595: */ 596: 597: public float getMajorSpan() 598: { 599: return majorSpan; 600: } 601: 602: /** 603: * Returns the offset of the child view along the major layout axis. 604: * 605: * @return the offset of the child view along the major layout axis 606: */ 607: public float getMajorOffset() 608: { 609: return majorOffset; 610: } 611: 612: /** 613: * Sets the offset of the child view along the major layout axis. This 614: * should only be called by the ChildLocator of that child view. 615: * 616: * @param offset the offset to set 617: */ 618: public void setMajorOffset(float offset) 619: { 620: majorOffset = offset; 621: } 622: 623: /** 624: * Mark the preferences changed for that child. This forwards to 625: * {@link AsyncBoxView#preferenceChanged}. 626: * 627: * @param width <code>true</code> if the width preference has changed 628: * @param height <code>true</code> if the height preference has changed 629: */ 630: public void preferenceChanged(boolean width, boolean height) 631: { 632: if (getMajorAxis() == X_AXIS) 633: { 634: if (width) 635: majorValid = false; 636: if (height) 637: minorValid = false; 638: } 639: else 640: { 641: if (width) 642: minorValid = false; 643: if (height) 644: majorValid = false; 645: } 646: childSizeValid = false; 647: } 648: } 649: 650: /** 651: * Flushes the requirements changes upwards asynchronously. 652: */ 653: private class FlushTask implements Runnable 654: { 655: /** 656: * Starts the flush task. This obtains a readLock on the document 657: * and then flushes all the updates using 658: * {@link AsyncBoxView#flushRequirementChanges()} after updating the 659: * requirements. 660: */ 661: public void run() 662: { 663: try 664: { 665: // Acquire a lock on the document. 666: Document doc = getDocument(); 667: if (doc instanceof AbstractDocument) 668: { 669: AbstractDocument abstractDoc = (AbstractDocument) doc; 670: abstractDoc.readLock(); 671: } 672: 673: int n = getViewCount(); 674: if (minorChanged && (n > 0)) 675: { 676: LayoutQueue q = getLayoutQueue(); 677: ChildState min = getChildState(0); 678: ChildState pref = getChildState(0); 679: for (int i = 1; i < n; i++) 680: { 681: ChildState cs = getChildState(i); 682: if (cs.minimum > min.minimum) 683: min = cs; 684: if (cs.preferred > pref.preferred) 685: pref = cs; 686: } 687: synchronized (AsyncBoxView.this) 688: { 689: minReq = min; 690: prefReq = pref; 691: } 692: } 693: 694: flushRequirementChanges(); 695: } 696: finally 697: { 698: // Release the lock on the document. 699: Document doc = getDocument(); 700: if (doc instanceof AbstractDocument) 701: { 702: AbstractDocument abstractDoc = (AbstractDocument) doc; 703: abstractDoc.readUnlock(); 704: } 705: } 706: } 707: 708: } 709: 710: /** 711: * The major layout axis. 712: */ 713: private int majorAxis; 714: 715: /** 716: * The top inset. 717: */ 718: private float topInset; 719: 720: /** 721: * The bottom inset. 722: */ 723: private float bottomInset; 724: 725: /** 726: * The left inset. 727: */ 728: private float leftInset; 729: 730: /** 731: * Indicates if the major span should be treated as beeing estimated or not. 732: */ 733: private boolean estimatedMajorSpan; 734: 735: /** 736: * The right inset. 737: */ 738: private float rightInset; 739: 740: /** 741: * The children and their layout statistics. 742: */ 743: private ArrayList childStates; 744: 745: /** 746: * The currently changing child state. May be null if there is no child state 747: * updating at the moment. This is package private to avoid a synthetic 748: * accessor method inside ChildState. 749: */ 750: ChildState changing; 751: 752: /** 753: * Represents the minimum requirements. This is used in 754: * {@link #getMinimumSpan(int)}. 755: */ 756: ChildState minReq; 757: 758: /** 759: * Represents the minimum requirements. This is used in 760: * {@link #getPreferredSpan(int)}. 761: */ 762: ChildState prefReq; 763: 764: /** 765: * Indicates that the major axis requirements have changed. 766: */ 767: private boolean majorChanged; 768: 769: /** 770: * Indicates that the minor axis requirements have changed. This is package 771: * private to avoid synthetic accessor method. 772: */ 773: boolean minorChanged; 774: 775: /** 776: * The current span along the major layout axis. This is package private to 777: * avoid synthetic accessor method. 778: */ 779: float majorSpan; 780: 781: /** 782: * The current span along the minor layout axis. This is package private to 783: * avoid synthetic accessor method. 784: */ 785: float minorSpan; 786: 787: /** 788: * This tasked is placed on the layout queue to flush updates up to the 789: * parent view. 790: */ 791: private Runnable flushTask; 792: 793: /** 794: * The child locator for this view. 795: */ 796: protected ChildLocator locator; 797: 798: /** 799: * Creates a new <code>AsyncBoxView</code> that represents the specified 800: * element and layouts its children along the specified axis. 801: * 802: * @param elem the element 803: * @param axis the layout axis 804: */ 805: public AsyncBoxView(Element elem, int axis) 806: { 807: super(elem); 808: majorAxis = axis; 809: childStates = new ArrayList(); 810: flushTask = new FlushTask(); 811: locator = new ChildLocator(); 812: minorSpan = Short.MAX_VALUE; 813: } 814: 815: /** 816: * Returns the major layout axis. 817: * 818: * @return the major layout axis 819: */ 820: public int getMajorAxis() 821: { 822: return majorAxis; 823: } 824: 825: /** 826: * Returns the minor layout axis, that is the axis orthogonal to the major 827: * layout axis. 828: * 829: * @return the minor layout axis 830: */ 831: public int getMinorAxis() 832: { 833: return majorAxis == X_AXIS ? Y_AXIS : X_AXIS; 834: } 835: 836: /** 837: * Returns the view at the specified <code>index</code>. 838: * 839: * @param index the index of the requested child view 840: * 841: * @return the view at the specified <code>index</code> 842: */ 843: public View getView(int index) 844: { 845: View view = null; 846: synchronized(childStates) 847: { 848: if ((index >= 0) && (index < childStates.size())) 849: { 850: ChildState cs = (ChildState) childStates.get(index); 851: view = cs.getChildView(); 852: } 853: } 854: return view; 855: } 856: 857: /** 858: * Returns the number of child views. 859: * 860: * @return the number of child views 861: */ 862: public int getViewCount() 863: { 864: synchronized(childStates) 865: { 866: return childStates.size(); 867: } 868: } 869: 870: /** 871: * Returns the view index of the child view that represents the specified 872: * model position. 873: * 874: * @param pos the model position for which we search the view index 875: * @param bias the bias 876: * 877: * @return the view index of the child view that represents the specified 878: * model position 879: */ 880: public int getViewIndex(int pos, Position.Bias bias) 881: { 882: int retVal = -1; 883: 884: if (bias == Position.Bias.Backward) 885: pos = Math.max(0, pos - 1); 886: 887: // TODO: A possible optimization would be to implement a binary search 888: // here. 889: int numChildren = childStates.size(); 890: if (numChildren > 0) 891: { 892: for (int i = 0; i < numChildren; ++i) 893: { 894: View child = ((ChildState) childStates.get(i)).getChildView(); 895: if (child.getStartOffset() <= pos && child.getEndOffset() > pos) 896: { 897: retVal = i; 898: break; 899: } 900: } 901: } 902: return retVal; 903: } 904: 905: /** 906: * Returns the top inset. 907: * 908: * @return the top inset 909: */ 910: public float getTopInset() 911: { 912: return topInset; 913: } 914: 915: /** 916: * Sets the top inset. 917: * 918: * @param top the top inset 919: */ 920: public void setTopInset(float top) 921: { 922: topInset = top; 923: } 924: 925: /** 926: * Returns the bottom inset. 927: * 928: * @return the bottom inset 929: */ 930: public float getBottomInset() 931: { 932: return bottomInset; 933: } 934: 935: /** 936: * Sets the bottom inset. 937: * 938: * @param bottom the bottom inset 939: */ 940: public void setBottomInset(float bottom) 941: { 942: bottomInset = bottom; 943: } 944: 945: /** 946: * Returns the left inset. 947: * 948: * @return the left inset 949: */ 950: public float getLeftInset() 951: { 952: return leftInset; 953: } 954: 955: /** 956: * Sets the left inset. 957: * 958: * @param left the left inset 959: */ 960: public void setLeftInset