Merge from mainline
[official-gcc.git] / libjava / classpath / javax / swing / text / Utilities.java
blob1adc8ff87e9d97e6f9cecb29d2ec5ad50fb2c378
1 /* Utilities.java --
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)
9 any later version.
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
19 02110-1301 USA.
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.
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.awt.Rectangle;
45 import java.text.BreakIterator;
47 import javax.swing.SwingConstants;
48 import javax.swing.SwingUtilities;
50 /**
51 * A set of utilities to deal with text. This is used by several other classes
52 * inside this package.
54 * @author Roman Kennke (roman@ontographics.com)
56 public class Utilities
58 /**
59 * The length of the char buffer that holds the characters to be drawn.
61 private static final int BUF_LENGTH = 64;
63 /**
64 * Creates a new <code>Utilities</code> object.
66 public Utilities()
68 // Nothing to be done here.
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}.
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.
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;
92 // The current x and y pixel coordinates.
93 int pixelX = x;
94 int pixelY = y;
96 // The font metrics of the current selected font.
97 FontMetrics metrics = g.getFontMetrics();
98 int ascent = metrics.getAscent();
100 int pixelWidth = 0;
101 int pos = s.offset;
102 int len = 0;
104 for (int offset = s.offset; offset < (s.offset + s.count); ++offset)
106 char c = buffer[offset];
107 if (c == '\t' || c == '\n')
109 if (len > 0) {
110 g.drawChars(buffer, pos, len, pixelX, pixelY + ascent);
111 pixelX += pixelWidth;
112 pixelWidth = 0;
114 pos = offset+1;
115 len = 0;
118 switch (c)
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;
141 if (len > 0)
142 g.drawChars(buffer, pos, len, pixelX, pixelY + ascent);
144 return pixelX;
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.
159 public static final int getTabbedTextWidth(Segment s, FontMetrics metrics,
160 int x, TabExpander e,
161 int startOffset)
163 // This buffers the chars to be drawn.
164 char[] buffer = s.array;
166 // The current x coordinate.
167 int pixelX = x;
169 // The current maximum width.
170 int maxWidth = 0;
172 for (int offset = s.offset; offset < (s.offset + s.count); ++offset)
174 switch (buffer[offset])
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;
199 // Take the last line into account.
200 maxWidth = Math.max(maxWidth, pixelX - x);
202 return maxWidth;
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>.
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.
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
230 * @return the model location, so that the resulting fragment fits within the
231 * specified span
233 public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0,
234 int x, TabExpander te, int p0,
235 boolean round)
237 // At the end of the for loop, this holds the requested model location
238 int pos;
239 int currentX = x0;
241 for (pos = p0; pos < s.count; pos++)
243 char nextChar = s.array[s.offset+pos];
244 if (nextChar == 0)
246 if (! round)
247 pos--;
248 break;
250 if (nextChar != '\t')
251 currentX += fm.charWidth(nextChar);
252 else
254 if (te == null)
255 currentX += fm.charWidth(' ');
256 else
257 currentX = (int) te.nextTabStop(currentX, pos);
259 if (currentX > x)
261 if (! round)
262 pos--;
263 break;
266 return pos;
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>.
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.
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
288 * @return the model location, so that the resulting fragment fits within the
289 * specified span
291 public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0,
292 int x, TabExpander te, int p0)
294 return getTabbedTextOffset(s, fm, x0, x, te, p0, true);
298 * Finds the start of the next word for the given offset.
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.
308 public static final int getNextWord(JTextComponent c, int offs)
309 throws BadLocationException
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)
320 for (int i = last; i < current; i++)
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;
327 last = current;
328 current = wb.next();
330 return BreakIterator.DONE;
334 * Finds the start of the previous word for the given offset.
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.
344 public static final int getPreviousWord(JTextComponent c, int offs)
345 throws BadLocationException
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();
355 while (current != BreakIterator.DONE)
357 for (int i = last; i < offs; i++)
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;
364 last = current;
365 current = wb.previous();
367 return 0;
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
377 public static final int getWordStart(JTextComponent c, int offs)
378 throws BadLocationException
380 if (offs < 0 || offs >= c.getText().length())
381 throw new BadLocationException("invalid offset specified", offs);
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);
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
398 public static final int getWordEnd(JTextComponent c, int offs)
399 throws BadLocationException
401 if (offs < 0 || offs >= c.getText().length())
402 throw new BadLocationException("invalid offset specified", offs);
404 String text = c.getText();
405 BreakIterator wb = BreakIterator.getWordInstance();
406 wb.setText(text);
407 return wb.following(offs);
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
420 public static final int getRowEnd(JTextComponent c, int offs)
421 throws BadLocationException
423 String text = c.getText();
424 if (text == null)
425 return -1;
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)
435 if (c.modelToView(high).y != c.modelToView(offs).y)
437 oldHigh = high;
438 high = low + ((high + 1 - low) / 2);
439 if (oldHigh == high)
440 return high - 1;
442 else
444 low = high;
445 high += ((oldHigh - high) / 2);
446 if (low == high)
447 return low;
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.
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
463 public static final int getRowStart(JTextComponent c, int offs)
464 throws BadLocationException
466 String text = c.getText();
467 if (text == null)
468 return -1;
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)
478 if (c.modelToView(low).y != c.modelToView(offs).y)
480 oldLow = low;
481 low = high - ((high + 1 - low) / 2);
482 if (oldLow == low)
483 return low + 1;
485 else
487 high = low;
488 low -= ((low - oldLow) / 2);
489 if (low == high)
490 return low;
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
507 public static final int getBreakLocation(Segment s, FontMetrics metrics,
508 int x0, int x, TabExpander e,
509 int startOffset)
511 int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset);
512 BreakIterator breaker = BreakIterator.getWordInstance();
513 breaker.setText(s.toString());
515 // If mark is equal to the end of the string, just use that position
516 if (mark == s.count)
517 return mark;
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);
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;
532 * Returns the paragraph element in the text component <code>c</code> at
533 * the specified location <code>offset</code>.
535 * @param c the text component
536 * @param offset the offset of the paragraph element to return
538 * @return the paragraph element at <code>offset</code>
540 public static final Element getParagraphElement(JTextComponent c, int offset)
542 Document doc = c.getDocument();
543 Element par = null;
544 if (doc instanceof StyledDocument)
546 StyledDocument styledDoc = (StyledDocument) doc;
547 par = styledDoc.getParagraphElement(offset);
549 else
551 Element root = c.getDocument().getDefaultRootElement();
552 int parIndex = root.getElementIndex(offset);
553 par = root.getElement(parIndex);
555 return par;
559 * Returns the document position that is closest above to the specified x
560 * coordinate in the row containing <code>offset</code>.
562 * @param c the text component
563 * @param offset the offset
564 * @param x the x coordinate
566 * @return the document position that is closest above to the specified x
567 * coordinate in the row containing <code>offset</code>
569 * @throws BadLocationException if <code>offset</code> is not a valid offset
571 public static final int getPositionAbove(JTextComponent c, int offset, int x)
572 throws BadLocationException
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;
586 * Returns the document position that is closest below to the specified x
587 * coordinate in the row containing <code>offset</code>.
589 * @param c the text component
590 * @param offset the offset
591 * @param x the x coordinate
593 * @return the document position that is closest above to the specified x
594 * coordinate in the row containing <code>offset</code>
596 * @throws BadLocationException if <code>offset</code> is not a valid offset
598 public static final int getPositionBelow(JTextComponent c, int offset, int x)
599 throws BadLocationException
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;