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
.awt
.Rectangle
;
45 import java
.text
.BreakIterator
;
47 import javax
.swing
.SwingConstants
;
48 import javax
.swing
.SwingUtilities
;
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
59 * The length of the char buffer that holds the characters to be drawn.
61 private static final int BUF_LENGTH
= 64;
64 * Creates a new <code>Utilities</code> object.
68 // Nothing to be done here.
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
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.
96 // The font metrics of the current selected font.
97 FontMetrics metrics
= g
.getFontMetrics();
98 int ascent
= metrics
.getAscent();
104 for (int offset
= s
.offset
; offset
< (s
.offset
+ s
.count
); ++offset
)
106 char c
= buffer
[offset
];
107 if (c
== '\t' || c
== '\n')
110 g
.drawChars(buffer
, pos
, len
, pixelX
, pixelY
+ ascent
);
111 pixelX
+= pixelWidth
;
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 ' '.
124 pixelX
= (int) e
.nextTabStop((float) pixelX
,
125 startOffset
+ offset
- s
.offset
);
127 pixelX
+= metrics
.charWidth(' ');
130 // In case we have a newline, we must jump to the next line.
131 pixelY
+= metrics
.getHeight();
136 pixelWidth
+= metrics
.charWidth(buffer
[offset
]);
142 g
.drawChars(buffer
, pos
, len
, pixelX
, pixelY
+ ascent
);
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
159 public static final int getTabbedTextWidth(Segment s
, FontMetrics metrics
,
160 int x
, TabExpander e
,
163 // This buffers the chars to be drawn.
164 char[] buffer
= s
.array
;
166 // The current x coordinate.
169 // The current maximum width.
172 for (int offset
= s
.offset
; offset
< (s
.offset
+ s
.count
); ++offset
)
174 switch (buffer
[offset
])
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'.
180 pixelX
= (int) e
.nextTabStop((float) pixelX
,
181 startOffset
+ offset
- s
.offset
);
183 pixelX
+= metrics
.charWidth(' ');
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
);
193 // Here we draw the char.
194 pixelX
+= metrics
.charWidth(buffer
[offset
]);
199 // Take the last line into account.
200 maxWidth
= Math
.max(maxWidth
, pixelX
- x
);
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
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
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
233 public static final int getTabbedTextOffset(Segment s
, FontMetrics fm
, int x0
,
234 int x
, TabExpander te
, int p0
,
237 // At the end of the for loop, this holds the requested model location
241 for (pos
= p0
; pos
< s
.count
; pos
++)
243 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
);
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
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
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
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.
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();
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
)))
330 return BreakIterator
.DONE
;
334 * Finds the start of the previous word for the given offset.
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();
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
)))
365 current
= wb
.previous();
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();
386 if (wb
.isBoundary(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();
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
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();
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);
432 int oldHigh
= text
.length() + 1;
435 if (c
.modelToView(high
).y
!= c
.modelToView(offs
).y
)
438 high
= low
+ ((high
+ 1 - low
) / 2);
445 high
+= ((oldHigh
- high
) / 2);
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
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
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();
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
478 if (c
.modelToView(low
).y
!= c
.modelToView(offs
).y
)
481 low
= high
- ((high
+ 1 - low
) / 2);
488 low
-= ((low
- oldLow
) / 2);
496 * Determine where to break the text in the given Segment, attempting to find
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
,
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
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);
526 // If preceding is 0 we couldn't find a suitable word-boundary so
527 // just break it on the character boundary
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();
544 if (doc
instanceof StyledDocument
)
546 StyledDocument styledDoc
= (StyledDocument
) doc
;
547 par
= styledDoc
.getParagraphElement(offset
);
551 Element root
= c
.getDocument().getDefaultRootElement();
552 int parIndex
= root
.getElementIndex(offset
);
553 par
= root
.getElement(parIndex
);
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]);
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]);