2 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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)
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.
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
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
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. */
39 package javax.swing.text;
41 import java.awt.FontMetrics;
42 import java.awt.Graphics;
43 import java.awt.Point;
44 import java.text.BreakIterator;
47 * A set of utilities to deal with text. This is used by several other classes
48 * inside this package.
50 * @author Roman Kennke (roman@ontographics.com)
52 public class Utilities
55 * The length of the char buffer that holds the characters to be drawn.
57 private static final int BUF_LENGTH = 64;
60 * Creates a new <code>Utilities</code> object.
64 // Nothing to be done here.
68 * Draws the given text segment. Contained tabs and newline characters
69 * are taken into account. Tabs are expanded using the
70 * specified {@link TabExpander}.
73 * The X and Y coordinates denote the start of the <em>baseline</em> where
74 * the text should be drawn.
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
82 * @param startOffset starting offset in the text.
83 * @return the x coordinate at the end of the drawn text.
85 public static final int drawTabbedText(Segment s, int x, int y, Graphics g,
86 TabExpander e, int startOffset)
88 // This buffers the chars to be drawn.
89 char[] buffer = s.array;
91 // The font metrics of the current selected font.
92 FontMetrics metrics = g.getFontMetrics();
93 int ascent = metrics.getAscent();
95 // The current x and y pixel coordinates.
97 int pixelY = y - ascent;
103 for (int offset = s.offset; offset < (s.offset + s.count); ++offset)
105 char c = buffer[offset];
106 if (c == '\t' || c == '\n')
109 g.drawChars(buffer, pos, len, pixelX, pixelY + ascent);
110 pixelX += pixelWidth;
120 // In case we have a tab, we just 'jump' over the tab.
121 // When we have no tab expander we just use the width of ' '.
123 pixelX = (int) e.nextTabStop((float) pixelX,
124 startOffset + offset - s.offset);
126 pixelX += metrics.charWidth(' ');
129 // In case we have a newline, we must jump to the next line.
130 pixelY += metrics.getHeight();
135 pixelWidth += metrics.charWidth(buffer[offset]);
141 g.drawChars(buffer, pos, len, pixelX, pixelY + ascent);
147 * Determines the width, that the given text <code>s</code> would take
148 * if it was printed with the given {@link java.awt.FontMetrics} on the
149 * specified screen position.
150 * @param s the text fragment
151 * @param metrics the font metrics of the font to be used
152 * @param x the x coordinate of the point at which drawing should be done
153 * @param e the {@link TabExpander} to be used
154 * @param startOffset the index in <code>s</code> where to start
155 * @returns the width of the given text s. This takes tabs and newlines
158 public static final int getTabbedTextWidth(Segment s, FontMetrics metrics,
159 int x, TabExpander e,
162 // This buffers the chars to be drawn.
163 char[] buffer = s.array;
165 // The current x coordinate.
168 // The current maximum width.
171 for (int offset = s.offset; offset < (s.offset + s.count); ++offset)
173 switch (buffer[offset])
176 // In case we have a tab, we just 'jump' over the tab.
177 // When we have no tab expander we just use the width of 'm'.
179 pixelX = (int) e.nextTabStop((float) pixelX,
180 startOffset + offset - s.offset);
182 pixelX += metrics.charWidth(' ');
185 // In case we have a newline, we must 'draw'
186 // the buffer and jump on the next line.
187 pixelX += metrics.charWidth(buffer[offset]);
188 maxWidth = Math.max(maxWidth, pixelX - x);
192 // Here we draw the char.
193 pixelX += metrics.charWidth(buffer[offset]);
198 // Take the last line into account.
199 maxWidth = Math.max(maxWidth, pixelX - x);
205 * Provides a facility to map screen coordinates into a model location. For a
206 * given text fragment and start location within this fragment, this method
207 * determines the model location so that the resulting fragment fits best
208 * into the span <code>[x0, x]</code>.
210 * The parameter <code>round</code> controls which model location is returned
211 * if the view coordinates are on a character: If <code>round</code> is
212 * <code>true</code>, then the result is rounded up to the next character, so
213 * that the resulting fragment is the smallest fragment that is larger than
214 * the specified span. If <code>round</code> is <code>false</code>, then the
215 * resulting fragment is the largest fragment that is smaller than the
218 * @param s the text segment
219 * @param fm the font metrics to use
220 * @param x0 the starting screen location
221 * @param x the target screen location at which the requested fragment should
223 * @param te the tab expander to use; if this is <code>null</code>, TABs are
224 * expanded to one space character
225 * @param p0 the starting model location
226 * @param round if <code>true</code> round up to the next location, otherwise
227 * round down to the current location
229 * @return the model location, so that the resulting fragment fits within the
232 public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0,
233 int x, TabExpander te, int p0,
236 // At the end of the for loop, this holds the requested model location
240 for (pos = 0; pos < s.count; pos++)
242 char nextChar = s.array[s.offset+pos];
250 if (nextChar != '\t')
251 currentX += fm.charWidth(nextChar);
255 currentX += fm.charWidth(' ');
257 currentX = (int) te.nextTabStop(currentX, pos);
272 * Provides a facility to map screen coordinates into a model location. For a
273 * given text fragment and start location within this fragment, this method
274 * determines the model location so that the resulting fragment fits best
275 * into the span <code>[x0, x]</code>.
277 * This method rounds up to the next location, so that the resulting fragment
278 * will be the smallest fragment of the text, that is greater than the
281 * @param s the text segment
282 * @param fm the font metrics to use
283 * @param x0 the starting screen location
284 * @param x the target screen location at which the requested fragment should
286 * @param te the tab expander to use; if this is <code>null</code>, TABs are
287 * expanded to one space character
288 * @param p0 the starting model location
290 * @return the model location, so that the resulting fragment fits within the
293 public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0,
294 int x, TabExpander te, int p0)
296 return getTabbedTextOffset(s, fm, x0, x, te, p0, true);
300 * Finds the start of the next word for the given offset.
305 * the offset in the document
306 * @return the location in the model of the start of the next word.
307 * @throws BadLocationException
308 * if the offset is invalid.
310 public static final int getNextWord(JTextComponent c, int offs)
311 throws BadLocationException
313 if (offs < 0 || offs > (c.getText().length() - 1))
314 throw new BadLocationException("invalid offset specified", offs);
315 String text = c.getText();
316 BreakIterator wb = BreakIterator.getWordInstance();
318 int last = wb.following(offs);
319 int current = wb.next();
320 while (current != BreakIterator.DONE)
322 for (int i = last; i < current; i++)
324 // FIXME: Should use isLetter(int) and text.codePointAt(int)
325 // instead, but isLetter(int) isn't implemented yet
326 if (Character.isLetter(text.charAt(i)))
332 return BreakIterator.DONE;
336 * Finds the start of the previous word for the given offset.
341 * the offset in the document
342 * @return the location in the model of the start of the previous word.
343 * @throws BadLocationException
344 * if the offset is invalid.
346 public static final int getPreviousWord(JTextComponent c, int offs)
347 throws BadLocationException
349 if (offs < 0 || offs > (c.getText().length() - 1))
350 throw new BadLocationException("invalid offset specified", offs);
351 String text = c.getText();
352 BreakIterator wb = BreakIterator.getWordInstance();
354 int last = wb.preceding(offs);
355 int current = wb.previous();
357 while (current != BreakIterator.DONE)
359 for (int i = last; i < offs; i++)
361 // FIXME: Should use isLetter(int) and text.codePointAt(int)
362 // instead, but isLetter(int) isn't implemented yet
363 if (Character.isLetter(text.charAt(i)))
367 current = wb.previous();
373 * Finds the start of a word for the given location.
374 * @param c the text component
375 * @param offs the offset location
376 * @return the location of the word beginning
377 * @throws BadLocationException if the offset location is invalid
379 public static final int getWordStart(JTextComponent c, int offs)
380 throws BadLocationException
382 if (offs < 0 || offs >= c.getText().length())
383 throw new BadLocationException("invalid offset specified", offs);
385 String text = c.getText();
386 BreakIterator wb = BreakIterator.getWordInstance();
388 if (wb.isBoundary(offs))
390 return wb.preceding(offs);
394 * Finds the end of a word for the given location.
395 * @param c the text component
396 * @param offs the offset location
397 * @return the location of the word end
398 * @throws BadLocationException if the offset location is invalid
400 public static final int getWordEnd(JTextComponent c, int offs)
401 throws BadLocationException
403 if (offs < 0 || offs >= c.getText().length())
404 throw new BadLocationException("invalid offset specified", offs);
406 String text = c.getText();
407 BreakIterator wb = BreakIterator.getWordInstance();
409 return wb.following(offs);
413 * Get the model position of the end of the row that contains the
414 * specified model position. Return null if the given JTextComponent
415 * does not have a size.
416 * @param c the JTextComponent
417 * @param offs the model position
418 * @return the model position of the end of the row containing the given
420 * @throws BadLocationException if the offset is invalid
422 public static final int getRowEnd(JTextComponent c, int offs)
423 throws BadLocationException
425 String text = c.getText();
429 // Do a binary search for the smallest position X > offs
430 // such that that character at positino X is not on the same
431 // line as the character at position offs
432 int high = offs + ((text.length() - 1 - offs) / 2);
434 int oldHigh = text.length() + 1;
437 if (c.modelToView(high).y != c.modelToView(offs).y)
440 high = low + ((high + 1 - low) / 2);
447 high += ((oldHigh - high) / 2);
455 * Get the model position of the start of the row that contains the specified
456 * model position. Return null if the given JTextComponent does not have a
459 * @param c the JTextComponent
460 * @param offs the model position
461 * @return the model position of the start of the row containing the given
463 * @throws BadLocationException if the offset is invalid
465 public static final int getRowStart(JTextComponent c, int offs)
466 throws BadLocationException
468 String text = c.getText();
472 // Do a binary search for the greatest position X < offs
473 // such that the character at position X is not on the same
474 // row as the character at position offs
480 if (c.modelToView(low).y != c.modelToView(offs).y)
483 low = high - ((high + 1 - low) / 2);
490 low -= ((low - oldLow) / 2);
498 * Determine where to break the text in the given Segment, attempting to find
500 * @param s the Segment that holds the text
501 * @param metrics the font metrics used for calculating the break point
502 * @param x0 starting view location representing the start of the text
503 * @param x the target view location
504 * @param e the TabExpander used for expanding tabs (if this is null tabs
505 * are expanded to 1 space)
506 * @param startOffset the offset in the Document of the start of the text
507 * @return the offset at which we should break the text
509 public static final int getBreakLocation(Segment s, FontMetrics metrics,
510 int x0, int x, TabExpander e,
513 int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset);
514 BreakIterator breaker = BreakIterator.getWordInstance();
517 // If mark is equal to the end of the string, just use that position
518 if (mark == s.count + s.offset)
521 // Try to find a word boundary previous to the mark at which we
522 // can break the text
523 int preceding = breaker.preceding(mark + 1);
528 // If preceding is 0 we couldn't find a suitable word-boundary so
529 // just break it on the character boundary
534 * Returns the paragraph element in the text component <code>c</code> at
535 * the specified location <code>offset</code>.
537 * @param c the text component
538 * @param offset the offset of the paragraph element to return
540 * @return the paragraph element at <code>offset</code>
542 public static final Element getParagraphElement(JTextComponent c, int offset)
544 Document doc = c.getDocument();
546 if (doc instanceof StyledDocument)
548 StyledDocument styledDoc = (StyledDocument) doc;
549 par = styledDoc.getParagraphElement(offset);
553 Element root = c.getDocument().getDefaultRootElement();
554 int parIndex = root.getElementIndex(offset);
555 par = root.getElement(parIndex);
561 * Returns the document position that is closest above to the specified x
562 * coordinate in the row containing <code>offset</code>.
564 * @param c the text component
565 * @param offset the offset
566 * @param x the x coordinate
568 * @return the document position that is closest above to the specified x
569 * coordinate in the row containing <code>offset</code>
571 * @throws BadLocationException if <code>offset</code> is not a valid offset
573 public static final int getPositionAbove(JTextComponent c, int offset, int x)
574 throws BadLocationException
576 int offs = getRowStart(c, offset);
581 // Effectively calculates the y value of the previous line.
582 Point pt = c.modelToView(offs-1).getLocation();
586 // Calculate a simple fitting offset.
587 offs = c.viewToModel(pt);
589 // Find out the real x positions of the calculated character and its
591 int offsX = c.modelToView(offs).getLocation().x;
592 int offsXNext = c.modelToView(offs+1).getLocation().x;
594 // Chose the one which is nearer to us and return its offset.
595 if (Math.abs(offsX-x) <= Math.abs(offsXNext-x))
602 * Returns the document position that is closest below to the specified x
603 * coordinate in the row containing <code>offset</code>.
605 * @param c the text component
606 * @param offset the offset
607 * @param x the x coordinate
609 * @return the document position that is closest above to the specified x
610 * coordinate in the row containing <code>offset</code>
612 * @throws BadLocationException if <code>offset</code> is not a valid offset
614 public static final int getPositionBelow(JTextComponent c, int offset, int x)
615 throws BadLocationException
617 int offs = getRowEnd(c, offset);
622 // Effectively calculates the y value of the previous line.
623 Point pt = c.modelToView(offs+1).getLocation();
627 // Calculate a simple fitting offset.
628 offs = c.viewToModel(pt);
630 if (offs == c.getDocument().getLength())
633 // Find out the real x positions of the calculated character and its
635 int offsX = c.modelToView(offs).getLocation().x;
636 int offsXNext = c.modelToView(offs+1).getLocation().x;
638 // Chose the one which is nearer to us and return its offset.
639 if (Math.abs(offsX-x) <= Math.abs(offsXNext-x))