GNU Classpath (0.91) | |
Frames | No Frames |
1: /* Utilities.java -- 2: Copyright (C) 2004, 2005, 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.FontMetrics; 42: import java.awt.Graphics; 43: import java.awt.Point; 44: import java.text.BreakIterator; 45: 46: /** 47: * A set of utilities to deal with text. This is used by several other classes 48: * inside this package. 49: * 50: * @author Roman Kennke (roman@ontographics.com) 51: * @author Robert Schuster (robertschuster@fsfe.org) 52: */ 53: public class Utilities 54: { 55: /** 56: * The length of the char buffer that holds the characters to be drawn. 57: */ 58: private static final int BUF_LENGTH = 64; 59: 60: /** 61: * Creates a new <code>Utilities</code> object. 62: */ 63: public Utilities() 64: { 65: // Nothing to be done here. 66: } 67: 68: /** 69: * Draws the given text segment. Contained tabs and newline characters 70: * are taken into account. Tabs are expanded using the 71: * specified {@link TabExpander}. 72: * 73: * 74: * The X and Y coordinates denote the start of the <em>baseline</em> where 75: * the text should be drawn. 76: * 77: * @param s the text fragment to be drawn. 78: * @param x the x position for drawing. 79: * @param y the y position for drawing. 80: * @param g the {@link Graphics} context for drawing. 81: * @param e the {@link TabExpander} which specifies the Tab-expanding 82: * technique. 83: * @param startOffset starting offset in the text. 84: * @return the x coordinate at the end of the drawn text. 85: */ 86: public static final int drawTabbedText(Segment s, int x, int y, Graphics g, 87: TabExpander e, int startOffset) 88: { 89: // This buffers the chars to be drawn. 90: char[] buffer = s.array; 91: 92: // The font metrics of the current selected font. 93: FontMetrics metrics = g.getFontMetrics(); 94: int ascent = metrics.getAscent(); 95: 96: // The current x and y pixel coordinates. 97: int pixelX = x; 98: int pixelY = y - ascent; 99: 100: int pixelWidth = 0; 101: int pos = s.offset; 102: int len = 0; 103: 104: int end = s.offset + s.count; 105: 106: for (int offset = s.offset; offset < end; ++offset) 107: { 108: char c = buffer[offset]; 109: if (c == '\t' || c == '\n') 110: { 111: if (len > 0) { 112: g.drawChars(buffer, pos, len, pixelX, pixelY + ascent); 113: pixelX += pixelWidth; 114: pixelWidth = 0; 115: } 116: pos = offset+1; 117: len = 0; 118: } 119: 120: switch (c) 121: { 122: case '\t': 123: // In case we have a tab, we just 'jump' over the tab. 124: // When we have no tab expander we just use the width of ' '. 125: if (e != null) 126: pixelX = (int) e.nextTabStop((float) pixelX, 127: startOffset + offset - s.offset); 128: else 129: pixelX += metrics.charWidth(' '); 130: break; 131: case '\n': 132: // In case we have a newline, we must jump to the next line. 133: pixelY += metrics.getHeight(); 134: pixelX = x; 135: break; 136: default: 137: ++len; 138: pixelWidth += metrics.charWidth(buffer[offset]); 139: break; 140: } 141: } 142: 143: if (len > 0) 144: g.drawChars(buffer, pos, len, pixelX, pixelY + ascent); 145: 146: return pixelX + pixelWidth; 147: } 148: 149: /** 150: * Determines the width, that the given text <code>s</code> would take 151: * if it was printed with the given {@link java.awt.FontMetrics} on the 152: * specified screen position. 153: * @param s the text fragment 154: * @param metrics the font metrics of the font to be used 155: * @param x the x coordinate of the point at which drawing should be done 156: * @param e the {@link TabExpander} to be used 157: * @param startOffset the index in <code>s</code> where to start 158: * @returns the width of the given text s. This takes tabs and newlines 159: * into account. 160: */ 161: public static final int getTabbedTextWidth(Segment s, FontMetrics metrics, 162: int x, TabExpander e, 163: int startOffset) 164: { 165: // This buffers the chars to be drawn. 166: char[] buffer = s.array; 167: 168: // The current x coordinate. 169: int pixelX = x; 170: 171: // The current maximum width. 172: int maxWidth = 0; 173: 174: for (int offset = s.offset; offset < (s.offset + s.count); ++offset) 175: { 176: switch (buffer[offset]) 177: { 178: case '\t': 179: // In case we have a tab, we just 'jump' over the tab. 180: // When we have no tab expander we just use the width of 'm'. 181: if (e != null) 182: pixelX = (int) e.nextTabStop((float) pixelX, 183: startOffset + offset - s.offset); 184: else 185: pixelX += metrics.charWidth(' '); 186: break; 187: case '\n': 188: // In case we have a newline, we must 'draw' 189: // the buffer and jump on the next line. 190: pixelX += metrics.charWidth(buffer[offset]); 191: maxWidth = Math.max(maxWidth, pixelX - x); 192: pixelX = x; 193: break; 194: default: 195: // Here we draw the char. 196: pixelX += metrics.charWidth(buffer[offset]); 197: break; 198: } 199: } 200: 201: // Take the last line into account. 202: maxWidth = Math.max(maxWidth, pixelX - x); 203: 204: return maxWidth; 205: } 206: 207: /** 208: * Provides a facility to map screen coordinates into a model location. For a 209: * given text fragment and start location within this fragment, this method 210: * determines the model location so that the resulting fragment fits best 211: * into the span <code>[x0, x]</code>. 212: * 213: * The parameter <code>round</code> controls which model location is returned 214: * if the view coordinates are on a character: If <code>round</code> is 215: * <code>true</code>, then the result is rounded up to the next character, so 216: * that the resulting fragment is the smallest fragment that is larger than 217: * the specified span. If <code>round</code> is <code>false</code>, then the 218: * resulting fragment is the largest fragment that is smaller than the 219: * specified span. 220: * 221: * @param s the text segment 222: * @param fm the font metrics to use 223: * @param x0 the starting screen location 224: * @param x the target screen location at which the requested fragment should 225: * end 226: * @param te the tab expander to use; if this is <code>null</code>, TABs are 227: * expanded to one space character 228: * @param p0 the starting model location 229: * @param round if <code>true</code> round up to the next location, otherwise 230: * round down to the current location 231: * 232: * @return the model location, so that the resulting fragment fits within the 233: * specified span 234: */ 235: public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0, 236: int x, TabExpander te, int p0, 237: boolean round) 238: { 239: // At the end of the for loop, this holds the requested model location 240: int pos; 241: int currentX = x0; 242: int width = 0; 243: 244: for (pos = 0; pos < s.count; pos++) 245: { 246: char nextChar = s.array[s.offset+pos]; 247: 248: if (nextChar == 0) 249: break; 250: 251: if (nextChar != '\t') 252: width = fm.charWidth(nextChar); 253: else 254: { 255: if (te == null) 256: width = fm.charWidth(' '); 257: else 258: width = ((int) te.nextTabStop(currentX, pos)) - currentX; 259: } 260: 261: if (round) 262: { 263: if (currentX + (width>>1) > x) 264: break; 265: } 266: else 267: { 268: if (currentX + width > x) 269: break; 270: } 271: 272: currentX += width; 273: } 274: 275: return pos + p0; 276: } 277: 278: /** 279: * Provides a facility to map screen coordinates into a model location. For a 280: * given text fragment and start location within this fragment, this method 281: * determines the model location so that the resulting fragment fits best 282: * into the span <code>[x0, x]</code>. 283: * 284: * This method rounds up to the next location, so that the resulting fragment 285: * will be the smallest fragment of the text, that is greater than the 286: * specified span. 287: * 288: * @param s the text segment 289: * @param fm the font metrics to use 290: * @param x0 the starting screen location 291: * @param x the target screen location at which the requested fragment should 292: * end 293: * @param te the tab expander to use; if this is <code>null</code>, TABs are 294: * expanded to one space character 295: * @param p0 the starting model location 296: * 297: * @return the model location, so that the resulting fragment fits within the 298: * specified span 299: */ 300: public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0, 301: int x, TabExpander te, int p0) 302: { 303: return getTabbedTextOffset(s, fm, x0, x, te, p0, true); 304: } 305: 306: /** 307: * Finds the start of the next word for the given offset. 308: * 309: * @param c 310: * the text component 311: * @param offs 312: * the offset in the document 313: * @return the location in the model of the start of the next word. 314: * @throws BadLocationException 315: * if the offset is invalid. 316: */ 317: public static final int getNextWord(JTextComponent c, int offs) 318: throws BadLocationException 319: { 320: if (offs < 0 || offs > (c.getText().length() - 1)) 321: throw new BadLocationException("invalid offset specified", offs); 322: String text = c.getText(); 323: BreakIterator wb = BreakIterator.getWordInstance(); 324: wb.setText(text); 325: 326: int last = wb.following(offs); 327: int current = wb.next(); 328: int cp; 329: 330: while (current != BreakIterator.DONE) 331: { 332: for (int i = last; i < current; i++) 333: { 334: cp = text.codePointAt(i); 335: 336: // Return the last found bound if there is a letter at the current 337: // location or is not whitespace (meaning it is a number or 338: // punctuation). The first case means that 'last' denotes the 339: // beginning of a word while the second case means it is the start 340: // of some else. 341: if (Character.isLetter(cp) 342: || !Character.isWhitespace(cp)) 343: return last; 344: } 345: last = current; 346: current = wb.next(); 347: } 348: 349: throw new BadLocationException("no more word", offs); 350: } 351: 352: /** 353: * Finds the start of the previous word for the given offset. 354: * 355: * @param c 356: * the text component 357: * @param offs 358: * the offset in the document 359: * @return the location in the model of the start of the previous word. 360: * @throws BadLocationException 361: * if the offset is invalid. 362: */ 363: public static final int getPreviousWord(JTextComponent c, int offs) 364: throws BadLocationException 365: { 366: if (offs < 0 || offs > (c.getText().length() - 1)) 367: throw new BadLocationException("invalid offset specified", offs); 368: String text = c.getText(); 369: BreakIterator wb = BreakIterator.getWordInstance(); 370: wb.setText(text); 371: int last = wb.preceding(offs); 372: int current = wb.previous(); 373: 374: while (current != BreakIterator.DONE) 375: { 376: for (int i = last; i < offs; i++) 377: { 378: if (Character.isLetter(text.codePointAt(i))) 379: return last; 380: } 381: last = current; 382: current = wb.previous(); 383: } 384: return 0; 385: } 386: 387: /** 388: * Finds the start of a word for the given location. 389: * @param c the text component 390: * @param offs the offset location 391: * @return the location of the word beginning 392: * @throws BadLocationException if the offset location is invalid 393: */ 394: public static final int getWordStart(JTextComponent c, int offs) 395: throws BadLocationException 396: { 397: if (offs < 0 || offs >= c.getText().length()) 398: throw new BadLocationException("invalid offset specified", offs); 399: 400: String text = c.getText(); 401: BreakIterator wb = BreakIterator.getWordInstance(); 402: wb.setText(text); 403: if (wb.isBoundary(offs)) 404: return offs; 405: return wb.preceding(offs); 406: } 407: 408: /** 409: * Finds the end of a word for the given location. 410: * @param c the text component 411: * @param offs the offset location 412: * @return the location of the word end 413: * @throws BadLocationException if the offset location is invalid 414: */ 415: public static final int getWordEnd(JTextComponent c, int offs) 416: throws BadLocationException 417: { 418: if (offs < 0 || offs >= c.getText().length()) 419: throw new BadLocationException("invalid offset specified", offs); 420: 421: String text = c.getText(); 422: BreakIterator wb = BreakIterator.getWordInstance(); 423: wb.setText(text); 424: return wb.following(offs); 425: } 426: 427: /** 428: * Get the model position of the end of the row that contains the 429: * specified model position. Return null if the given JTextComponent 430: * does not have a size. 431: * @param c the JTextComponent 432: * @param offs the model position 433: * @return the model position of the end of the row containing the given 434: * offset 435: * @throws BadLocationException if the offset is invalid 436: */ 437: public static final int getRowEnd(JTextComponent c, int offs) 438: throws BadLocationException 439: { 440: String text = c.getText(); 441: if (text == null) 442: return -1; 443: 444: // Do a binary search for the smallest position X > offs 445: // such that that character at positino X is not on the same 446: // line as the character at position offs 447: int high = offs + ((text.length() - 1 - offs) / 2); 448: int low = offs; 449: int oldHigh = text.length() + 1; 450: while (true) 451: { 452: if (c.modelToView(high).y != c.modelToView(offs).y) 453: { 454: oldHigh = high; 455: high = low + ((high + 1 - low) / 2); 456: if (oldHigh == high) 457: return high - 1; 458: } 459: else 460: { 461: low = high; 462: high += ((oldHigh - high) / 2); 463: if (low == high) 464: return low; 465: } 466: } 467: } 468: 469: /** 470: * Get the model position of the start of the row that contains the specified 471: * model position. Return null if the given JTextComponent does not have a 472: * size. 473: * 474: * @param c the JTextComponent 475: * @param offs the model position 476: * @return the model position of the start of the row containing the given 477: * offset 478: * @throws BadLocationException if the offset is invalid 479: */ 480: public static final int getRowStart(JTextComponent c, int offs) 481: throws BadLocationException 482: { 483: String text = c.getText(); 484: if (text == null) 485: return -1; 486: 487: // Do a binary search for the greatest position X < offs 488: // such that the character at position X is not on the same 489: // row as the character at position offs 490: int high = offs; 491: int low = 0; 492: int oldLow = 0; 493: while (true) 494: { 495: if (c.modelToView(low).y != c.modelToView(offs).y) 496: { 497: oldLow = low; 498: low = high - ((high + 1 - low) / 2); 499: if (oldLow == low) 500: return low + 1; 501: } 502: else 503: { 504: high = low; 505: low -= ((low - oldLow) / 2); 506: if (low == high) 507: return low; 508: } 509: } 510: } 511: 512: /** 513: * Determine where to break the text in the given Segment, attempting to find 514: * a word boundary. 515: * @param s the Segment that holds the text 516: * @param metrics the font metrics used for calculating the break point 517: * @param x0 starting view location representing the start of the text 518: * @param x the target view location 519: * @param e the TabExpander used for expanding tabs (if this is null tabs 520: * are expanded to 1 space) 521: * @param startOffset the offset in the Document of the start of the text 522: * @return the offset at which we should break the text 523: */ 524: public static final int getBreakLocation(Segment s, FontMetrics metrics, 525: int x0, int x, TabExpander e, 526: int startOffset) 527: { 528: int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, false); 529: BreakIterator breaker = BreakIterator.getWordInstance(); 530: breaker.setText(s); 531: 532: // If startOffset and s.offset differ then we need to use 533: // that difference two convert the offset between the two metrics. 534: int shift = startOffset - s.offset; 535: 536: // If mark is equal to the end of the string, just use that position. 537: if (mark >= shift + s.count) 538: return mark; 539: 540: // Try to find a word boundary previous to the mark at which we 541: // can break the text. 542: int preceding = breaker.preceding(mark + 1 - shift); 543: 544: if (preceding != 0) 545: return preceding + shift; 546: 547: // If preceding is 0 we couldn't find a suitable word-boundary so 548: // just break it on the character boundary 549: return mark; 550: } 551: 552: /** 553: * Returns the paragraph element in the text component <code>c</code> at 554: * the specified location <code>offset</code>. 555: * 556: * @param c the text component 557: * @param offset the offset of the paragraph element to return 558: * 559: * @return the paragraph element at <code>offset</code> 560: */ 561: public static final Element getParagraphElement(JTextComponent c, int offset) 562: { 563: Document doc = c.getDocument(); 564: Element par = null; 565: if (doc instanceof StyledDocument) 566: { 567: StyledDocument styledDoc = (StyledDocument) doc; 568: par = styledDoc.getParagraphElement(offset); 569: } 570: else 571: { 572: Element root = c.getDocument().getDefaultRootElement(); 573: int parIndex = root.getElementIndex(offset); 574: par = root.getElement(parIndex); 575: } 576: return par; 577: } 578: 579: /** 580: * Returns the document position that is closest above to the specified x 581: * coordinate in the row containing <code>offset</code>. 582: * 583: * @param c the text component 584: * @param offset the offset 585: * @param x the x coordinate 586: * 587: * @return the document position that is closest above to the specified x 588: * coordinate in the row containing <code>offset</code> 589: * 590: * @throws BadLocationException if <code>offset</code> is not a valid offset 591: */ 592: public static final int getPositionAbove(JTextComponent c, int offset, int x) 593: throws BadLocationException 594: { 595: int offs = getRowStart(c, offset); 596: 597: if(offs == -1) 598: return -1; 599: 600: // Effectively calculates the y value of the previous line. 601: Point pt = c.modelToView(offs-1).getLocation(); 602: 603: pt.x = x; 604: 605: // Calculate a simple fitting offset. 606: offs = c.viewToModel(pt); 607: 608: // Find out the real x positions of the calculated character and its 609: // neighbour. 610: int offsX = c.modelToView(offs).getLocation().x; 611: int offsXNext = c.modelToView(offs+1).getLocation().x; 612: 613: // Chose the one which is nearer to us and return its offset. 614: if (Math.abs(offsX-x) <= Math.abs(offsXNext-x)) 615: return offs; 616: else 617: return offs+1; 618: } 619: 620: /** 621: * Returns the document position that is closest below to the specified x 622: * coordinate in the row containing <code>offset</code>. 623: * 624: * @param c the text component 625: * @param offset the offset 626: * @param x the x coordinate 627: * 628: * @return the document position that is closest above to the specified x 629: * coordinate in the row containing <code>offset</code> 630: * 631: * @throws BadLocationException if <code>offset</code> is not a valid offset 632: */ 633: public static final int getPositionBelow(JTextComponent c, int offset, int x) 634: throws BadLocationException 635: { 636: int offs = getRowEnd(c, offset); 637: 638: if(offs == -1) 639: return -1; 640: 641: Point pt = null; 642: 643: // Note: Some views represent the position after the last 644: // typed character others do not. Converting offset 3 in "a\nb" 645: // in a PlainView will return a valid rectangle while in a 646: // WrappedPlainView this will throw a BadLocationException. 647: // This behavior has been observed in the RI. 648: try 649: { 650: // Effectively calculates the y value of the next line. 651: pt = c.modelToView(offs+1).getLocation(); 652: } 653: catch(BadLocationException ble) 654: { 655: return offset; 656: } 657: 658: pt.x = x; 659: 660: // Calculate a simple fitting offset. 661: offs = c.viewToModel(pt); 662: 663: if (offs == c.getDocument().getLength()) 664: return offs; 665: 666: // Find out the real x positions of the calculated character and its 667: // neighbour. 668: int offsX = c.modelToView(offs).getLocation().x; 669: int offsXNext = c.modelToView(offs+1).getLocation().x; 670: 671: // Chose the one which is nearer to us and return its offset. 672: if (Math.abs(offsX-x) <= Math.abs(offsXNext-x)) 673: return offs; 674: else 675: return offs+1; 676: } 677: }
GNU Classpath (0.91) |