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