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)
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)
51 * @author Robert Schuster (robertschuster@fsfe.org)
53 public class Utilities
56 * The length of the char buffer that holds the characters to be drawn.
58 private static final int BUF_LENGTH
= 64;
61 * Creates a new <code>Utilities</code> object.
65 // Nothing to be done here.
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
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.
98 int pixelY
= y
- ascent
;
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')
112 g
.drawChars(buffer
, pos
, len
, pixelX
, pixelY
+ ascent
);
113 pixelX
+= pixelWidth
;
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 ' '.
126 pixelX
= (int) e
.nextTabStop((float) pixelX
,
127 startOffset
+ offset
- s
.offset
);
129 pixelX
+= metrics
.charWidth(' ');
132 // In case we have a newline, we must jump to the next line.
133 pixelY
+= metrics
.getHeight();
138 pixelWidth
+= metrics
.charWidth(buffer
[offset
]);
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
161 public static final int getTabbedTextWidth(Segment s
, FontMetrics metrics
,
162 int x
, TabExpander e
,
165 // This buffers the chars to be drawn.
166 char[] buffer
= s
.array
;
168 // The current x coordinate.
171 // The current maximum width.
174 for (int offset
= s
.offset
; offset
< (s
.offset
+ s
.count
); ++offset
)
176 switch (buffer
[offset
])
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'.
182 pixelX
= (int) e
.nextTabStop((float) pixelX
,
183 startOffset
+ offset
- s
.offset
);
185 pixelX
+= metrics
.charWidth(' ');
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
);
195 // Here we draw the char.
196 pixelX
+= metrics
.charWidth(buffer
[offset
]);
201 // Take the last line into account.
202 maxWidth
= Math
.max(maxWidth
, pixelX
- x
);
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
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
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
235 public static final int getTabbedTextOffset(Segment s
, FontMetrics fm
, int x0
,
236 int x
, TabExpander te
, int p0
,
239 // At the end of the for loop, this holds the requested model location
244 for (pos
= 0; pos
< s
.count
; pos
++)
246 char nextChar
= s
.array
[s
.offset
+pos
];
251 if (nextChar
!= '\t')
252 width
= fm
.charWidth(nextChar
);
256 width
= fm
.charWidth(' ');
258 width
= ((int) te
.nextTabStop(currentX
, pos
)) - currentX
;
263 if (currentX
+ (width
>>1) > x
)
268 if (currentX
+ width
> x
)
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
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
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
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.
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();
326 int last
= wb
.following(offs
);
327 int current
= wb
.next();
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
341 if (Character
.isLetter(cp
)
342 || !Character
.isWhitespace(cp
))
349 throw new BadLocationException("no more word", offs
);
353 * Finds the start of the previous word for the given offset.
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();
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
)))
382 current
= wb
.previous();
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();
403 if (wb
.isBoundary(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();
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
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();
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);
449 int oldHigh
= text
.length() + 1;
452 if (c
.modelToView(high
).y
!= c
.modelToView(offs
).y
)
455 high
= low
+ ((high
+ 1 - low
) / 2);
462 high
+= ((oldHigh
- high
) / 2);
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
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
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();
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
495 if (c
.modelToView(low
).y
!= c
.modelToView(offs
).y
)
498 low
= high
- ((high
+ 1 - low
) / 2);
505 low
-= ((low
- oldLow
) / 2);
513 * Determine where to break the text in the given Segment, attempting to find
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
,
528 int mark
= Utilities
.getTabbedTextOffset(s
, metrics
, x0
, x
, e
, startOffset
, false);
529 BreakIterator breaker
= BreakIterator
.getWordInstance();
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
)
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
);
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
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();
565 if (doc
instanceof StyledDocument
)
567 StyledDocument styledDoc
= (StyledDocument
) doc
;
568 par
= styledDoc
.getParagraphElement(offset
);
572 Element root
= c
.getDocument().getDefaultRootElement();
573 int parIndex
= root
.getElementIndex(offset
);
574 par
= root
.getElement(parIndex
);
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
);
600 // Effectively calculates the y value of the previous line.
601 Point pt
= c
.modelToView(offs
-1).getLocation();
605 // Calculate a simple fitting offset.
606 offs
= c
.viewToModel(pt
);
608 // Find out the real x positions of the calculated character and its
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
))
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
);
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
)
660 // Calculate a simple fitting offset.
661 offs
= c
.viewToModel(pt
);
663 if (offs
== c
.getDocument().getLength())
666 // Find out the real x positions of the calculated character and its
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
))