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