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