Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / javax / swing / text / Utilities.java
blobf154e55aac0987624fc8aae0d7ed70854f632e1d
1 /* Utilities.java --
2 Copyright (C) 2004, 2005, 2006 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.text.BreakIterator;
46 /**
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)
51 * @author Robert Schuster (robertschuster@fsfe.org)
53 public class Utilities
55 /**
56 * The length of the char buffer that holds the characters to be drawn.
58 private static final int BUF_LENGTH = 64;
60 /**
61 * Creates a new <code>Utilities</code> object.
63 public Utilities()
65 // Nothing to be done here.
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}.
74 * The X and Y coordinates denote the start of the <em>baseline</em> where
75 * the text should be drawn.
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.
86 public static final int drawTabbedText(Segment s, int x, int y, Graphics g,
87 TabExpander e, int startOffset)
89 // This buffers the chars to be drawn.
90 char[] buffer = s.array;
92 // The font metrics of the current selected font.
93 FontMetrics metrics = g.getFontMetrics();
94 int ascent = metrics.getAscent();
96 // The current x and y pixel coordinates.
97 int pixelX = x;
98 int pixelY = y - ascent;
100 int pixelWidth = 0;
101 int pos = s.offset;
102 int len = 0;
104 int end = s.offset + s.count;
106 for (int offset = s.offset; offset < end; ++offset)
108 char c = buffer[offset];
109 if (c == '\t' || c == '\n')
111 if (len > 0) {
112 g.drawChars(buffer, pos, len, pixelX, pixelY + ascent);
113 pixelX += pixelWidth;
114 pixelWidth = 0;
116 pos = offset+1;
117 len = 0;
120 switch (c)
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;
143 if (len > 0)
144 g.drawChars(buffer, pos, len, pixelX, pixelY + ascent);
146 return pixelX + pixelWidth;
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.
161 public static final int getTabbedTextWidth(Segment s, FontMetrics metrics,
162 int x, TabExpander e,
163 int startOffset)
165 // This buffers the chars to be drawn.
166 char[] buffer = s.array;
168 // The current x coordinate.
169 int pixelX = x;
171 // The current maximum width.
172 int maxWidth = 0;
174 for (int offset = s.offset; offset < (s.offset + s.count); ++offset)
176 switch (buffer[offset])
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;
201 // Take the last line into account.
202 maxWidth = Math.max(maxWidth, pixelX - x);
204 return maxWidth;
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>.
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.
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
232 * @return the model location, so that the resulting fragment fits within the
233 * specified span
235 public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0,
236 int x, TabExpander te, int p0,
237 boolean round)
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;
244 for (pos = 0; pos < s.count; pos++)
246 char nextChar = s.array[s.offset+pos];
248 if (nextChar == 0)
249 break;
251 if (nextChar != '\t')
252 width = fm.charWidth(nextChar);
253 else
255 if (te == null)
256 width = fm.charWidth(' ');
257 else
258 width = ((int) te.nextTabStop(currentX, pos)) - currentX;
261 if (round)
263 if (currentX + (width>>1) > x)
264 break;
266 else
268 if (currentX + width > x)
269 break;
272 currentX += width;
275 return pos + p0;
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>.
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.
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
297 * @return the model location, so that the resulting fragment fits within the
298 * specified span
300 public static final int getTabbedTextOffset(Segment s, FontMetrics fm, int x0,
301 int x, TabExpander te, int p0)
303 return getTabbedTextOffset(s, fm, x0, x, te, p0, true);
307 * Finds the start of the next word for the given offset.
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.
317 public static final int getNextWord(JTextComponent c, int offs)
318 throws BadLocationException
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);
326 int last = wb.following(offs);
327 int current = wb.next();
328 int cp;
330 while (current != BreakIterator.DONE)
332 for (int i = last; i < current; i++)
334 cp = text.codePointAt(i);
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;
345 last = current;
346 current = wb.next();
349 throw new BadLocationException("no more word", offs);
353 * Finds the start of the previous word for the given offset.
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.
363 public static final int getPreviousWord(JTextComponent c, int offs)
364 throws BadLocationException
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();
374 while (current != BreakIterator.DONE)
376 for (int i = last; i < offs; i++)
378 if (Character.isLetter(text.codePointAt(i)))
379 return last;
381 last = current;
382 current = wb.previous();
384 return 0;
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
394 public static final int getWordStart(JTextComponent c, int offs)
395 throws BadLocationException
397 if (offs < 0 || offs >= c.getText().length())
398 throw new BadLocationException("invalid offset specified", offs);
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);
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
415 public static final int getWordEnd(JTextComponent c, int offs)
416 throws BadLocationException
418 if (offs < 0 || offs >= c.getText().length())
419 throw new BadLocationException("invalid offset specified", offs);
421 String text = c.getText();
422 BreakIterator wb = BreakIterator.getWordInstance();
423 wb.setText(text);
424 return wb.following(offs);
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
437 public static final int getRowEnd(JTextComponent c, int offs)
438 throws BadLocationException
440 String text = c.getText();
441 if (text == null)
442 return -1;
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)
452 if (c.modelToView(high).y != c.modelToView(offs).y)
454 oldHigh = high;
455 high = low + ((high + 1 - low) / 2);
456 if (oldHigh == high)
457 return high - 1;
459 else
461 low = high;
462 high += ((oldHigh - high) / 2);
463 if (low == high)
464 return low;
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.
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
480 public static final int getRowStart(JTextComponent c, int offs)
481 throws BadLocationException
483 String text = c.getText();
484 if (text == null)
485 return -1;
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)
495 if (c.modelToView(low).y != c.modelToView(offs).y)
497 oldLow = low;
498 low = high - ((high + 1 - low) / 2);
499 if (oldLow == low)
500 return low + 1;
502 else
504 high = low;
505 low -= ((low - oldLow) / 2);
506 if (low == high)
507 return low;
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
524 public static final int getBreakLocation(Segment s, FontMetrics metrics,
525 int x0, int x, TabExpander e,
526 int startOffset)
528 int mark = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, false);
529 BreakIterator breaker = BreakIterator.getWordInstance();
530 breaker.setText(s);
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;
536 // If mark is equal to the end of the string, just use that position.
537 if (mark >= shift + s.count)
538 return mark;
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);
544 if (preceding != 0)
545 return preceding + shift;
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;
553 * Returns the paragraph element in the text component <code>c</code> at
554 * the specified location <code>offset</code>.
556 * @param c the text component
557 * @param offset the offset of the paragraph element to return
559 * @return the paragraph element at <code>offset</code>
561 public static final Element getParagraphElement(JTextComponent c, int offset)
563 Document doc = c.getDocument();
564 Element par = null;
565 if (doc instanceof StyledDocument)
567 StyledDocument styledDoc = (StyledDocument) doc;
568 par = styledDoc.getParagraphElement(offset);
570 else
572 Element root = c.getDocument().getDefaultRootElement();
573 int parIndex = root.getElementIndex(offset);
574 par = root.getElement(parIndex);
576 return par;
580 * Returns the document position that is closest above to the specified x
581 * coordinate in the row containing <code>offset</code>.
583 * @param c the text component
584 * @param offset the offset
585 * @param x the x coordinate
587 * @return the document position that is closest above to the specified x
588 * coordinate in the row containing <code>offset</code>
590 * @throws BadLocationException if <code>offset</code> is not a valid offset
592 public static final int getPositionAbove(JTextComponent c, int offset, int x)
593 throws BadLocationException
595 int offs = getRowStart(c, offset);
597 if(offs == -1)
598 return -1;
600 // Effectively calculates the y value of the previous line.
601 Point pt = c.modelToView(offs-1).getLocation();
603 pt.x = x;
605 // Calculate a simple fitting offset.
606 offs = c.viewToModel(pt);
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;
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;
621 * Returns the document position that is closest below to the specified x
622 * coordinate in the row containing <code>offset</code>.
624 * @param c the text component
625 * @param offset the offset
626 * @param x the x coordinate
628 * @return the document position that is closest above to the specified x
629 * coordinate in the row containing <code>offset</code>
631 * @throws BadLocationException if <code>offset</code> is not a valid offset
633 public static final int getPositionBelow(JTextComponent c, int offset, int x)
634 throws BadLocationException
636 int offs = getRowEnd(c, offset);
638 if(offs == -1)
639 return -1;
641 Point pt = null;
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.
650 // Effectively calculates the y value of the next line.
651 pt = c.modelToView(offs+1).getLocation();
653 catch(BadLocationException ble)
655 return offset;
658 pt.x = x;
660 // Calculate a simple fitting offset.
661 offs = c.viewToModel(pt);
663 if (offs == c.getDocument().getLength())
664 return offs;
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;
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;