GNU Classpath (0.19) | ||
Frames | No Frames |
1: /* WrappedPlainView.java -- 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.Color; 42: import java.awt.Container; 43: import java.awt.FontMetrics; 44: import java.awt.Graphics; 45: import java.awt.Rectangle; 46: import java.awt.Shape; 47: 48: import javax.swing.event.DocumentEvent; 49: import javax.swing.text.Position.Bias; 50: 51: /** 52: * @author abalkiss 53: * 54: */ 55: public class WrappedPlainView extends BoxView implements TabExpander 56: { 57: /** The color for selected text **/ 58: Color selectedColor; 59: 60: /** The color for unselected text **/ 61: Color unselectedColor; 62: 63: /** The color for disabled components **/ 64: Color disabledColor; 65: 66: /** Stores the font metrics **/ 67: protected FontMetrics metrics; 68: 69: /** Whether or not to wrap on word boundaries **/ 70: boolean wordWrap; 71: 72: /** A ViewFactory that creates WrappedLines **/ 73: ViewFactory viewFactory = new WrappedLineCreator(); 74: 75: /** 76: * The instance returned by {@link #getLineBuffer()}. 77: */ 78: private transient Segment lineBuffer; 79: 80: public WrappedPlainView (Element elem) 81: { 82: this (elem, false); 83: } 84: 85: public WrappedPlainView (Element elem, boolean wordWrap) 86: { 87: super (elem, Y_AXIS); 88: this.wordWrap = wordWrap; 89: } 90: 91: /** 92: * Provides access to the Segment used for retrievals from the Document. 93: * @return the Segment. 94: */ 95: protected final Segment getLineBuffer() 96: { 97: if (lineBuffer == null) 98: lineBuffer = new Segment(); 99: return lineBuffer; 100: } 101: 102: /** 103: * Returns the next tab stop position after a given reference position. 104: * 105: * This implementation ignores the <code>tabStop</code> argument. 106: * 107: * @param x the current x position in pixels 108: * @param tabStop the position within the text stream that the tab occured at 109: */ 110: public float nextTabStop(float x, int tabStop) 111: { 112: JTextComponent host = (JTextComponent)getContainer(); 113: float tabSizePixels = getTabSize() 114: * host.getFontMetrics(host.getFont()).charWidth('m'); 115: return (float) (Math.floor(x / tabSizePixels) + 1) * tabSizePixels; 116: } 117: 118: /** 119: * Returns the tab size for the Document based on 120: * PlainDocument.tabSizeAttribute, defaulting to 8 if this property is 121: * not defined 122: * 123: * @return the tab size. 124: */ 125: protected int getTabSize() 126: { 127: Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute); 128: if (tabSize == null) 129: return 8; 130: return ((Integer)tabSize).intValue(); 131: } 132: 133: /** 134: * Draws a line of text, suppressing white space at the end and expanding 135: * tabs. Calls drawSelectedText and drawUnselectedText. 136: * @param p0 starting document position to use 137: * @param p1 ending document position to use 138: * @param g graphics context 139: * @param x starting x position 140: * @param y starting y position 141: */ 142: protected void drawLine(int p0, int p1, Graphics g, int x, int y) 143: { 144: try 145: { 146: drawUnselectedText(g, x, y, p0, p1); 147: } 148: catch (BadLocationException ble) 149: { 150: // shouldn't happen 151: } 152: } 153: 154: /** 155: * Renders the range of text as selected text. Just paints the text 156: * in the color specified by the host component. Assumes the highlighter 157: * will render the selected background. 158: * @param g the graphics context 159: * @param x the starting X coordinate 160: * @param y the starting Y coordinate 161: * @param p0 the starting model location 162: * @param p1 the ending model location 163: * @return the X coordinate of the end of the text 164: * @throws BadLocationException if the given range is invalid 165: */ 166: protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) 167: throws BadLocationException 168: { 169: g.setColor(selectedColor); 170: Segment segment = getLineBuffer(); 171: getDocument().getText(p0, p1 - p0, segment); 172: return Utilities.drawTabbedText(segment, x, y, g, this, 0); 173: } 174: 175: /** 176: * Renders the range of text as normal unhighlighted text. 177: * @param g the graphics context 178: * @param x the starting X coordinate 179: * @param y the starting Y coordinate 180: * @param p0 the starting model location 181: * @param p1 the end model location 182: * @return the X location of the end off the range 183: * @throws BadLocationException if the range given is invalid 184: */ 185: protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) 186: throws BadLocationException 187: { 188: JTextComponent textComponent = (JTextComponent) getContainer(); 189: if (textComponent.isEnabled()) 190: g.setColor(unselectedColor); 191: else 192: g.setColor(disabledColor); 193: 194: Segment segment = getLineBuffer(); 195: getDocument().getText(p0, p1 - p0, segment); 196: return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset); 197: } 198: 199: /** 200: * Loads the children to initiate the view. Called by setParent. 201: * Creates a WrappedLine for each child Element. 202: */ 203: protected void loadChildren (ViewFactory f) 204: { 205: Element root = getElement(); 206: int numChildren = root.getElementCount(); 207: if (numChildren == 0) 208: return; 209: 210: View[] children = new View[numChildren]; 211: for (int i = 0; i < numChildren; i++) 212: children[i] = new WrappedLine(root.getElement(i)); 213: replace(0, 0, children); 214: } 215: 216: /** 217: * Calculates the break position for the text between model positions 218: * p0 and p1. Will break on word boundaries or character boundaries 219: * depending on the break argument given in construction of this 220: * WrappedPlainView. Used by the nested WrappedLine class to determine 221: * when to start the next logical line. 222: * @param p0 the start model position 223: * @param p1 the end model position 224: * @return the model position at which to break the text 225: */ 226: protected int calculateBreakPosition(int p0, int p1) 227: { 228: Container c = getContainer(); 229: Rectangle alloc = c.isValid() ? c.getBounds() 230: : new Rectangle(c.getPreferredSize()); 231: updateMetrics(); 232: try 233: { 234: getDocument().getText(p0, p1 - p0, getLineBuffer()); 235: } 236: catch (BadLocationException ble) 237: { 238: // this shouldn't happen 239: } 240: // FIXME: Should we account for the insets of the container? 241: if (wordWrap) 242: return p0 243: + Utilities.getBreakLocation(lineBuffer, metrics, alloc.x, 244: alloc.x + alloc.width, this, 0); 245: else 246: { 247: return p0 248: + Utilities.getTabbedTextOffset(lineBuffer, metrics, alloc.x, 249: alloc.x + alloc.width, this, 0); 250: } 251: } 252: 253: void updateMetrics() 254: { 255: Container component = getContainer(); 256: metrics = component.getFontMetrics(component.getFont()); 257: } 258: 259: /** 260: * Determines the preferred span along the given axis. Implemented to 261: * cache the font metrics and then call the super classes method. 262: */ 263: public float getPreferredSpan (int axis) 264: { 265: updateMetrics(); 266: return super.getPreferredSpan(axis); 267: } 268: 269: /** 270: * Called when something was inserted. Overridden so that 271: * the view factory creates WrappedLine views. 272: */ 273: public void insertUpdate (DocumentEvent e, Shape a, ViewFactory f) 274: { 275: super.insertUpdate(e, a, viewFactory); 276: // FIXME: could improve performance by repainting only the necessary area 277: getContainer().repaint(); 278: } 279: 280: /** 281: * Called when something is removed. Overridden so that 282: * the view factory creates WrappedLine views. 283: */ 284: public void removeUpdate (DocumentEvent e, Shape a, ViewFactory f) 285: { 286: super.removeUpdate(e, a, viewFactory); 287: // FIXME: could improve performance by repainting only the necessary area 288: getContainer().repaint(); 289: } 290: 291: /** 292: * Called when the portion of the Document that this View is responsible 293: * for changes. Overridden so that the view factory creates 294: * WrappedLine views. 295: */ 296: public void changedUpdate (DocumentEvent e, Shape a, ViewFactory f) 297: { 298: super.changedUpdate(e, a, viewFactory); 299: // FIXME: could improve performance by repainting only the necessary area 300: getContainer().repaint(); 301: } 302: 303: class WrappedLineCreator implements ViewFactory 304: { 305: // Creates a new WrappedLine 306: public View create(Element elem) 307: { 308: return new WrappedLine(elem); 309: } 310: } 311: 312: /** 313: * Renders the <code>Element</code> that is associated with this 314: * <code>View</code>. Caches the metrics and then calls 315: * super.paint to paint all the child views. 316: * 317: * @param g the <code>Graphics</code> context to render to 318: * @param a the allocated region for the <code>Element</code> 319: */ 320: public void paint(Graphics g, Shape a) 321: { 322: updateMetrics(); 323: super.paint(g, a); 324: } 325: 326: /** 327: * Sets the size of the View. Implemented to update the metrics 328: * and then call super method. 329: */ 330: public void setSize (float width, float height) 331: { 332: updateMetrics(); 333: super.setSize(width, height); 334: } 335: 336: class WrappedLine extends View 337: { 338: /** Used to cache the number of lines for this View **/ 339: int numLines; 340: 341: public WrappedLine(Element elem) 342: { 343: super(elem); 344: determineNumLines(); 345: } 346: 347: /** 348: * Renders this (possibly wrapped) line using the given Graphics object 349: * and on the given rendering surface. 350: */ 351: public void paint(Graphics g, Shape s) 352: { 353: // Ensure metrics are up-to-date. 354: updateMetrics(); 355: JTextComponent textComponent = (JTextComponent) getContainer(); 356: 357: g.setFont(textComponent.getFont()); 358: selectedColor = textComponent.getSelectedTextColor(); 359: unselectedColor = textComponent.getForeground(); 360: disabledColor = textComponent.getDisabledTextColor(); 361: 362: Rectangle rect = s.getBounds(); 363: int lineHeight = metrics.getHeight(); 364: 365: int end = getEndOffset(); 366: int currStart = getStartOffset(); 367: int currEnd; 368: while (currStart < end) 369: { 370: currEnd = calculateBreakPosition(currStart, end); 371: drawLine(currStart, currEnd, g, rect.x, rect.y); 372: rect.y += lineHeight; 373: if (currEnd == currStart) 374: currStart ++; 375: else 376: currStart = currEnd; 377: } 378: } 379: 380: /** 381: * Determines the number of logical lines that the Element 382: * needs to be displayed 383: * @return the number of lines needed to display the Element 384: */ 385: int determineNumLines() 386: { 387: int end = getEndOffset(); 388: if (end == 0) 389: return 0; 390: 391: numLines = 0; 392: int breakPoint; 393: for (int i = getStartOffset(); i < end;) 394: { 395: numLines ++; 396: // careful: check that there's no off-by-one problem here 397: // depending on which position calculateBreakPosition returns 398: breakPoint = calculateBreakPosition(i, end); 399: if (breakPoint == i) 400: i ++; 401: else 402: i = breakPoint; 403: } 404: return numLines; 405: } 406: 407: /** 408: * Determines the preferred span for this view along the given axis. 409: * 410: * @param axis the axis (either X_AXIS or Y_AXIS) 411: * 412: * @return the preferred span along the given axis. 413: * @throws IllegalArgumentException if axis is not X_AXIS or Y_AXIS 414: */ 415: public float getPreferredSpan(int axis) 416: { 417: if (axis == X_AXIS) 418: return getWidth(); 419: else if (axis == Y_AXIS) 420: return numLines * metrics.getHeight(); 421: 422: throw new IllegalArgumentException("Invalid axis for getPreferredSpan: " 423: + axis); 424: } 425: 426: /** 427: * Provides a mapping from model space to view space. 428: * 429: * @param pos the position in the model 430: * @param a the region into which the view is rendered 431: * @param b the position bias (forward or backward) 432: * 433: * @return a box in view space that represents the given position 434: * in model space 435: * @throws BadLocationException if the given model position is invalid 436: */ 437: public Shape modelToView(int pos, Shape a, Bias b) throws BadLocationException 438: { 439: Segment s = getLineBuffer(); 440: int lineHeight = metrics.getHeight(); 441: Rectangle rect = a.getBounds(); 442: 443: // Return a rectangle with width 1 and height equal to the height 444: // of the text 445: rect.height = lineHeight; 446: rect.width = 1; 447: 448: int currLineStart = getStartOffset(); 449: int end = getEndOffset(); 450: 451: if (pos < currLineStart || pos >= end) 452: throw new BadLocationException("invalid offset", pos); 453: 454: while (true) 455: { 456: int currLineEnd = calculateBreakPosition(currLineStart, end); 457: // If pos is between currLineStart and currLineEnd then just find 458: // the width of the text from currLineStart to pos and add that 459: // to rect.x 460: if (pos >= currLineStart && pos < currLineEnd || pos == end - 1) 461: { 462: try 463: { 464: getDocument().getText(currLineStart, pos - currLineStart, s); 465: } 466: catch (BadLocationException ble) 467: { 468: // Shouldn't happen 469: } 470: rect.x += Utilities.getTabbedTextWidth(s, metrics, rect.x, WrappedPlainView.this, currLineStart); 471: return rect; 472: } 473: // Increment rect.y so we're checking the next logical line 474: rect.y += lineHeight; 475: 476: // Increment currLineStart to the model position of the start 477: // of the next logical line 478: if (currLineEnd == currLineStart) 479: currLineStart = end; 480: else 481: currLineStart = currLineEnd; 482: } 483: 484: } 485: 486: /** 487: * Provides a mapping from view space to model space. 488: * 489: * @param x the x coordinate in view space 490: * @param y the y coordinate in view space 491: * @param a the region into which the view is rendered 492: * @param b the position bias (forward or backward) 493: * 494: * @return the location in the model that best represents the 495: * given point in view space 496: */ 497: public int viewToModel(float x, float y, Shape a, Bias[] b) 498: { 499: Segment s = getLineBuffer(); 500: Rectangle rect = a.getBounds(); 501: int currLineStart = getStartOffset(); 502: int end = getEndOffset(); 503: int lineHeight = metrics.getHeight(); 504: if (y < rect.y) 505: return currLineStart; 506: if (y > rect.y + rect.height) 507: return end - 1; 508: 509: while (true) 510: { 511: int currLineEnd = calculateBreakPosition(currLineStart, end); 512: // If we're at the right y-position that means we're on the right 513: // logical line and we should look for the character 514: if (y >= rect.y && y < rect.y + lineHeight) 515: { 516: // Check if the x position is to the left or right of the text 517: if (x < rect.x) 518: return currLineStart; 519: if (x > rect.x + rect.width) 520: return currLineEnd - 1; 521: 522: try 523: { 524: getDocument().getText(currLineStart, end - currLineStart, s); 525: } 526: catch (BadLocationException ble) 527: { 528: // Shouldn't happen 529: } 530: int mark = Utilities.getTabbedTextOffset(s, metrics, rect.x, 531: (int) x, 532: WrappedPlainView.this, 533: currLineStart); 534: return currLineStart + mark; 535: } 536: // Increment rect.y so we're checking the next logical line 537: rect.y += lineHeight; 538: 539: // Increment currLineStart to the model position of the start 540: // of the next logical line 541: if (currLineEnd == currLineStart) 542: currLineStart = end; 543: else 544: currLineStart = currLineEnd; 545: } 546: } 547: } 548: }
GNU Classpath (0.19) |