| /* |
| * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package javax.swing.text; |
| |
| import java.lang.reflect.Method; |
| |
| import java.awt.Component; |
| import java.awt.Rectangle; |
| import java.awt.Graphics; |
| import java.awt.FontMetrics; |
| import java.awt.Shape; |
| import java.awt.Toolkit; |
| import java.awt.Graphics2D; |
| import java.awt.font.FontRenderContext; |
| import java.awt.font.TextLayout; |
| import java.awt.font.TextAttribute; |
| |
| import java.text.*; |
| import javax.swing.JComponent; |
| import javax.swing.SwingConstants; |
| import javax.swing.text.ParagraphView.Row; |
| import sun.swing.SwingUtilities2; |
| |
| /** |
| * A collection of methods to deal with various text |
| * related activities. |
| * |
| * @author Timothy Prinzing |
| */ |
| public class Utilities { |
| /** |
| * If <code>view</code>'s container is a <code>JComponent</code> it |
| * is returned, after casting. |
| */ |
| static JComponent getJComponent(View view) { |
| if (view != null) { |
| Component component = view.getContainer(); |
| if (component instanceof JComponent) { |
| return (JComponent)component; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Draws the given text, expanding any tabs that are contained |
| * using the given tab expansion technique. This particular |
| * implementation renders in a 1.1 style coordinate system |
| * where ints are used and 72dpi is assumed. |
| * |
| * @param s the source of the text |
| * @param x the X origin >= 0 |
| * @param y the Y origin >= 0 |
| * @param g the graphics context |
| * @param e how to expand the tabs. If this value is null, |
| * tabs will be expanded as a space character. |
| * @param startOffset starting offset of the text in the document >= 0 |
| * @return the X location at the end of the rendered text |
| */ |
| public static final int drawTabbedText(Segment s, int x, int y, Graphics g, |
| TabExpander e, int startOffset) { |
| return drawTabbedText(null, s, x, y, g, e, startOffset); |
| } |
| |
| /** |
| * Draws the given text, expanding any tabs that are contained |
| * using the given tab expansion technique. This particular |
| * implementation renders in a 1.1 style coordinate system |
| * where ints are used and 72dpi is assumed. |
| * |
| * @param view View requesting rendering, may be null. |
| * @param s the source of the text |
| * @param x the X origin >= 0 |
| * @param y the Y origin >= 0 |
| * @param g the graphics context |
| * @param e how to expand the tabs. If this value is null, |
| * tabs will be expanded as a space character. |
| * @param startOffset starting offset of the text in the document >= 0 |
| * @return the X location at the end of the rendered text |
| */ |
| static final int drawTabbedText(View view, |
| Segment s, int x, int y, Graphics g, |
| TabExpander e, int startOffset) { |
| return drawTabbedText(view, s, x, y, g, e, startOffset, null); |
| } |
| |
| // In addition to the previous method it can extend spaces for |
| // justification. |
| // |
| // all params are the same as in the preious method except the last |
| // one: |
| // @param justificationData justificationData for the row. |
| // if null not justification is needed |
| static final int drawTabbedText(View view, |
| Segment s, int x, int y, Graphics g, |
| TabExpander e, int startOffset, |
| int [] justificationData) { |
| JComponent component = getJComponent(view); |
| FontMetrics metrics = SwingUtilities2.getFontMetrics(component, g); |
| int nextX = x; |
| char[] txt = s.array; |
| int txtOffset = s.offset; |
| int flushLen = 0; |
| int flushIndex = s.offset; |
| int spaceAddon = 0; |
| int spaceAddonLeftoverEnd = -1; |
| int startJustifiableContent = 0; |
| int endJustifiableContent = 0; |
| if (justificationData != null) { |
| int offset = - startOffset + txtOffset; |
| View parent = null; |
| if (view != null |
| && (parent = view.getParent()) != null) { |
| offset += parent.getStartOffset(); |
| } |
| spaceAddon = |
| justificationData[Row.SPACE_ADDON]; |
| spaceAddonLeftoverEnd = |
| justificationData[Row.SPACE_ADDON_LEFTOVER_END] + offset; |
| startJustifiableContent = |
| justificationData[Row.START_JUSTIFIABLE] + offset; |
| endJustifiableContent = |
| justificationData[Row.END_JUSTIFIABLE] + offset; |
| } |
| int n = s.offset + s.count; |
| for (int i = txtOffset; i < n; i++) { |
| if (txt[i] == '\t' |
| || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd) |
| && (txt[i] == ' ') |
| && startJustifiableContent <= i |
| && i <= endJustifiableContent |
| )) { |
| if (flushLen > 0) { |
| nextX = SwingUtilities2.drawChars(component, g, txt, |
| flushIndex, flushLen, x, y); |
| flushLen = 0; |
| } |
| flushIndex = i + 1; |
| if (txt[i] == '\t') { |
| if (e != null) { |
| nextX = (int) e.nextTabStop((float) nextX, startOffset + i - txtOffset); |
| } else { |
| nextX += metrics.charWidth(' '); |
| } |
| } else if (txt[i] == ' ') { |
| nextX += metrics.charWidth(' ') + spaceAddon; |
| if (i <= spaceAddonLeftoverEnd) { |
| nextX++; |
| } |
| } |
| x = nextX; |
| } else if ((txt[i] == '\n') || (txt[i] == '\r')) { |
| if (flushLen > 0) { |
| nextX = SwingUtilities2.drawChars(component, g, txt, |
| flushIndex, flushLen, x, y); |
| flushLen = 0; |
| } |
| flushIndex = i + 1; |
| x = nextX; |
| } else { |
| flushLen += 1; |
| } |
| } |
| if (flushLen > 0) { |
| nextX = SwingUtilities2.drawChars(component, g,txt, flushIndex, |
| flushLen, x, y); |
| } |
| return nextX; |
| } |
| |
| /** |
| * Determines the width of the given segment of text taking tabs |
| * into consideration. This is implemented in a 1.1 style coordinate |
| * system where ints are used and 72dpi is assumed. |
| * |
| * @param s the source of the text |
| * @param metrics the font metrics to use for the calculation |
| * @param x the X origin >= 0 |
| * @param e how to expand the tabs. If this value is null, |
| * tabs will be expanded as a space character. |
| * @param startOffset starting offset of the text in the document >= 0 |
| * @return the width of the text |
| */ |
| public static final int getTabbedTextWidth(Segment s, FontMetrics metrics, int x, |
| TabExpander e, int startOffset) { |
| return getTabbedTextWidth(null, s, metrics, x, e, startOffset, null); |
| } |
| |
| |
| // In addition to the previous method it can extend spaces for |
| // justification. |
| // |
| // all params are the same as in the preious method except the last |
| // one: |
| // @param justificationData justificationData for the row. |
| // if null not justification is needed |
| static final int getTabbedTextWidth(View view, Segment s, FontMetrics metrics, int x, |
| TabExpander e, int startOffset, |
| int[] justificationData) { |
| int nextX = x; |
| char[] txt = s.array; |
| int txtOffset = s.offset; |
| int n = s.offset + s.count; |
| int charCount = 0; |
| int spaceAddon = 0; |
| int spaceAddonLeftoverEnd = -1; |
| int startJustifiableContent = 0; |
| int endJustifiableContent = 0; |
| if (justificationData != null) { |
| int offset = - startOffset + txtOffset; |
| View parent = null; |
| if (view != null |
| && (parent = view.getParent()) != null) { |
| offset += parent.getStartOffset(); |
| } |
| spaceAddon = |
| justificationData[Row.SPACE_ADDON]; |
| spaceAddonLeftoverEnd = |
| justificationData[Row.SPACE_ADDON_LEFTOVER_END] + offset; |
| startJustifiableContent = |
| justificationData[Row.START_JUSTIFIABLE] + offset; |
| endJustifiableContent = |
| justificationData[Row.END_JUSTIFIABLE] + offset; |
| } |
| |
| for (int i = txtOffset; i < n; i++) { |
| if (txt[i] == '\t' |
| || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd) |
| && (txt[i] == ' ') |
| && startJustifiableContent <= i |
| && i <= endJustifiableContent |
| )) { |
| nextX += metrics.charsWidth(txt, i-charCount, charCount); |
| charCount = 0; |
| if (txt[i] == '\t') { |
| if (e != null) { |
| nextX = (int) e.nextTabStop((float) nextX, |
| startOffset + i - txtOffset); |
| } else { |
| nextX += metrics.charWidth(' '); |
| } |
| } else if (txt[i] == ' ') { |
| nextX += metrics.charWidth(' ') + spaceAddon; |
| if (i <= spaceAddonLeftoverEnd) { |
| nextX++; |
| } |
| } |
| } else if(txt[i] == '\n') { |
| // Ignore newlines, they take up space and we shouldn't be |
| // counting them. |
| nextX += metrics.charsWidth(txt, i - charCount, charCount); |
| charCount = 0; |
| } else { |
| charCount++; |
| } |
| } |
| nextX += metrics.charsWidth(txt, n - charCount, charCount); |
| return nextX - x; |
| } |
| |
| /** |
| * Determines the relative offset into the given text that |
| * best represents the given span in the view coordinate |
| * system. This is implemented in a 1.1 style coordinate |
| * system where ints are used and 72dpi is assumed. |
| * |
| * @param s the source of the text |
| * @param metrics the font metrics to use for the calculation |
| * @param x0 the starting view location representing the start |
| * of the given text >= 0. |
| * @param x the target view location to translate to an |
| * offset into the text >= 0. |
| * @param e how to expand the tabs. If this value is null, |
| * tabs will be expanded as a space character. |
| * @param startOffset starting offset of the text in the document >= 0 |
| * @return the offset into the text >= 0 |
| */ |
| public static final int getTabbedTextOffset(Segment s, FontMetrics metrics, |
| int x0, int x, TabExpander e, |
| int startOffset) { |
| return getTabbedTextOffset(s, metrics, x0, x, e, startOffset, true); |
| } |
| |
| static final int getTabbedTextOffset(View view, Segment s, FontMetrics metrics, |
| int x0, int x, TabExpander e, |
| int startOffset, |
| int[] justificationData) { |
| return getTabbedTextOffset(view, s, metrics, x0, x, e, startOffset, true, |
| justificationData); |
| } |
| |
| public static final int getTabbedTextOffset(Segment s, |
| FontMetrics metrics, |
| int x0, int x, TabExpander e, |
| int startOffset, |
| boolean round) { |
| return getTabbedTextOffset(null, s, metrics, x0, x, e, startOffset, round, null); |
| } |
| |
| // In addition to the previous method it can extend spaces for |
| // justification. |
| // |
| // all params are the same as in the preious method except the last |
| // one: |
| // @param justificationData justificationData for the row. |
| // if null not justification is needed |
| static final int getTabbedTextOffset(View view, |
| Segment s, |
| FontMetrics metrics, |
| int x0, int x, TabExpander e, |
| int startOffset, |
| boolean round, |
| int[] justificationData) { |
| if (x0 >= x) { |
| // x before x0, return. |
| return 0; |
| } |
| int nextX = x0; |
| // s may be a shared segment, so it is copied prior to calling |
| // the tab expander |
| char[] txt = s.array; |
| int txtOffset = s.offset; |
| int txtCount = s.count; |
| int spaceAddon = 0 ; |
| int spaceAddonLeftoverEnd = -1; |
| int startJustifiableContent = 0 ; |
| int endJustifiableContent = 0; |
| if (justificationData != null) { |
| int offset = - startOffset + txtOffset; |
| View parent = null; |
| if (view != null |
| && (parent = view.getParent()) != null) { |
| offset += parent.getStartOffset(); |
| } |
| spaceAddon = |
| justificationData[Row.SPACE_ADDON]; |
| spaceAddonLeftoverEnd = |
| justificationData[Row.SPACE_ADDON_LEFTOVER_END] + offset; |
| startJustifiableContent = |
| justificationData[Row.START_JUSTIFIABLE] + offset; |
| endJustifiableContent = |
| justificationData[Row.END_JUSTIFIABLE] + offset; |
| } |
| int n = s.offset + s.count; |
| for (int i = s.offset; i < n; i++) { |
| if (txt[i] == '\t' |
| || ((spaceAddon != 0 || i <= spaceAddonLeftoverEnd) |
| && (txt[i] == ' ') |
| && startJustifiableContent <= i |
| && i <= endJustifiableContent |
| )){ |
| if (txt[i] == '\t') { |
| if (e != null) { |
| nextX = (int) e.nextTabStop((float) nextX, |
| startOffset + i - txtOffset); |
| } else { |
| nextX += metrics.charWidth(' '); |
| } |
| } else if (txt[i] == ' ') { |
| nextX += metrics.charWidth(' ') + spaceAddon; |
| if (i <= spaceAddonLeftoverEnd) { |
| nextX++; |
| } |
| } |
| } else { |
| nextX += metrics.charWidth(txt[i]); |
| } |
| if (x < nextX) { |
| // found the hit position... return the appropriate side |
| int offset; |
| |
| // the length of the string measured as a whole may differ from |
| // the sum of individual character lengths, for example if |
| // fractional metrics are enabled; and we must guard from this. |
| if (round) { |
| offset = i + 1 - txtOffset; |
| |
| int width = metrics.charsWidth(txt, txtOffset, offset); |
| int span = x - x0; |
| |
| if (span < width) { |
| while (offset > 0) { |
| int nextWidth = offset > 1 ? metrics.charsWidth(txt, txtOffset, offset - 1) : 0; |
| |
| if (span >= nextWidth) { |
| if (span - nextWidth < width - span) { |
| offset--; |
| } |
| |
| break; |
| } |
| |
| width = nextWidth; |
| offset--; |
| } |
| } |
| } else { |
| offset = i - txtOffset; |
| |
| while (offset > 0 && metrics.charsWidth(txt, txtOffset, offset) > (x - x0)) { |
| offset--; |
| } |
| } |
| |
| return offset; |
| } |
| } |
| |
| // didn't find, return end offset |
| return txtCount; |
| } |
| |
| /** |
| * Determine where to break the given text to fit |
| * within the given span. This tries to find a word boundary. |
| * @param s the source of the text |
| * @param metrics the font metrics to use for the calculation |
| * @param x0 the starting view location representing the start |
| * of the given text. |
| * @param x the target view location to translate to an |
| * offset into the text. |
| * @param e how to expand the tabs. If this value is null, |
| * tabs will be expanded as a space character. |
| * @param startOffset starting offset in the document of the text |
| * @return the offset into the given text |
| */ |
| public static final int getBreakLocation(Segment s, FontMetrics metrics, |
| int x0, int x, TabExpander e, |
| int startOffset) { |
| char[] txt = s.array; |
| int txtOffset = s.offset; |
| int txtCount = s.count; |
| int index = Utilities.getTabbedTextOffset(s, metrics, x0, x, |
| e, startOffset, false); |
| |
| if (index >= txtCount - 1) { |
| return txtCount; |
| } |
| |
| for (int i = txtOffset + index; i >= txtOffset; i--) { |
| char ch = txt[i]; |
| if (ch < 256) { |
| // break on whitespace |
| if (Character.isWhitespace(ch)) { |
| index = i - txtOffset + 1; |
| break; |
| } |
| } else { |
| // a multibyte char found; use BreakIterator to find line break |
| BreakIterator bit = BreakIterator.getLineInstance(); |
| bit.setText(s); |
| int breakPos = bit.preceding(i + 1); |
| if (breakPos > txtOffset) { |
| index = breakPos - txtOffset; |
| } |
| break; |
| } |
| } |
| return index; |
| } |
| |
| /** |
| * Determines the starting row model position of the row that contains |
| * the specified model position. The component given must have a |
| * size to compute the result. If the component doesn't have a size |
| * a value of -1 will be returned. |
| * |
| * @param c the editor |
| * @param offs the offset in the document >= 0 |
| * @return the position >= 0 if the request can be computed, otherwise |
| * a value of -1 will be returned. |
| * @exception BadLocationException if the offset is out of range |
| */ |
| public static final int getRowStart(JTextComponent c, int offs) throws BadLocationException { |
| Rectangle r = c.modelToView(offs); |
| if (r == null) { |
| return -1; |
| } |
| int lastOffs = offs; |
| int y = r.y; |
| while ((r != null) && (y == r.y)) { |
| // Skip invisible elements |
| if(r.height !=0) { |
| offs = lastOffs; |
| } |
| lastOffs -= 1; |
| r = (lastOffs >= 0) ? c.modelToView(lastOffs) : null; |
| } |
| return offs; |
| } |
| |
| /** |
| * Determines the ending row model position of the row that contains |
| * the specified model position. The component given must have a |
| * size to compute the result. If the component doesn't have a size |
| * a value of -1 will be returned. |
| * |
| * @param c the editor |
| * @param offs the offset in the document >= 0 |
| * @return the position >= 0 if the request can be computed, otherwise |
| * a value of -1 will be returned. |
| * @exception BadLocationException if the offset is out of range |
| */ |
| public static final int getRowEnd(JTextComponent c, int offs) throws BadLocationException { |
| Rectangle r = c.modelToView(offs); |
| if (r == null) { |
| return -1; |
| } |
| int n = c.getDocument().getLength(); |
| int lastOffs = offs; |
| int y = r.y; |
| while ((r != null) && (y == r.y)) { |
| // Skip invisible elements |
| if (r.height !=0) { |
| offs = lastOffs; |
| } |
| lastOffs += 1; |
| r = (lastOffs <= n) ? c.modelToView(lastOffs) : null; |
| } |
| return offs; |
| } |
| |
| /** |
| * Determines the position in the model that is closest to the given |
| * view location in the row above. The component given must have a |
| * size to compute the result. If the component doesn't have a size |
| * a value of -1 will be returned. |
| * |
| * @param c the editor |
| * @param offs the offset in the document >= 0 |
| * @param x the X coordinate >= 0 |
| * @return the position >= 0 if the request can be computed, otherwise |
| * a value of -1 will be returned. |
| * @exception BadLocationException if the offset is out of range |
| */ |
| public static final int getPositionAbove(JTextComponent c, int offs, int x) throws BadLocationException { |
| int lastOffs = getRowStart(c, offs) - 1; |
| if (lastOffs < 0) { |
| return -1; |
| } |
| int bestSpan = Integer.MAX_VALUE; |
| int y = 0; |
| Rectangle r = null; |
| if (lastOffs >= 0) { |
| r = c.modelToView(lastOffs); |
| y = r.y; |
| } |
| while ((r != null) && (y == r.y)) { |
| int span = Math.abs(r.x - x); |
| if (span < bestSpan) { |
| offs = lastOffs; |
| bestSpan = span; |
| } |
| lastOffs -= 1; |
| r = (lastOffs >= 0) ? c.modelToView(lastOffs) : null; |
| } |
| return offs; |
| } |
| |
| /** |
| * Determines the position in the model that is closest to the given |
| * view location in the row below. The component given must have a |
| * size to compute the result. If the component doesn't have a size |
| * a value of -1 will be returned. |
| * |
| * @param c the editor |
| * @param offs the offset in the document >= 0 |
| * @param x the X coordinate >= 0 |
| * @return the position >= 0 if the request can be computed, otherwise |
| * a value of -1 will be returned. |
| * @exception BadLocationException if the offset is out of range |
| */ |
| public static final int getPositionBelow(JTextComponent c, int offs, int x) throws BadLocationException { |
| int lastOffs = getRowEnd(c, offs) + 1; |
| if (lastOffs <= 0) { |
| return -1; |
| } |
| int bestSpan = Integer.MAX_VALUE; |
| int n = c.getDocument().getLength(); |
| int y = 0; |
| Rectangle r = null; |
| if (lastOffs <= n) { |
| r = c.modelToView(lastOffs); |
| y = r.y; |
| } |
| while ((r != null) && (y == r.y)) { |
| int span = Math.abs(x - r.x); |
| if (span < bestSpan) { |
| offs = lastOffs; |
| bestSpan = span; |
| } |
| lastOffs += 1; |
| r = (lastOffs <= n) ? c.modelToView(lastOffs) : null; |
| } |
| return offs; |
| } |
| |
| /** |
| * Determines the start of a word for the given model location. |
| * Uses BreakIterator.getWordInstance() to actually get the words. |
| * |
| * @param c the editor |
| * @param offs the offset in the document >= 0 |
| * @return the location in the model of the word start >= 0 |
| * @exception BadLocationException if the offset is out of range |
| */ |
| public static final int getWordStart(JTextComponent c, int offs) throws BadLocationException { |
| Document doc = c.getDocument(); |
| Element line = getParagraphElement(c, offs); |
| if (line == null) { |
| throw new BadLocationException("No word at " + offs, offs); |
| } |
| int lineStart = line.getStartOffset(); |
| int lineEnd = Math.min(line.getEndOffset(), doc.getLength()); |
| |
| Segment seg = SegmentCache.getSharedSegment(); |
| doc.getText(lineStart, lineEnd - lineStart, seg); |
| if(seg.count > 0) { |
| BreakIterator words = BreakIterator.getWordInstance(c.getLocale()); |
| words.setText(seg); |
| int wordPosition = seg.offset + offs - lineStart; |
| if(wordPosition >= words.last()) { |
| wordPosition = words.last() - 1; |
| } |
| words.following(wordPosition); |
| offs = lineStart + words.previous() - seg.offset; |
| } |
| SegmentCache.releaseSharedSegment(seg); |
| return offs; |
| } |
| |
| /** |
| * Determines the end of a word for the given location. |
| * Uses BreakIterator.getWordInstance() to actually get the words. |
| * |
| * @param c the editor |
| * @param offs the offset in the document >= 0 |
| * @return the location in the model of the word end >= 0 |
| * @exception BadLocationException if the offset is out of range |
| */ |
| public static final int getWordEnd(JTextComponent c, int offs) throws BadLocationException { |
| Document doc = c.getDocument(); |
| Element line = getParagraphElement(c, offs); |
| if (line == null) { |
| throw new BadLocationException("No word at " + offs, offs); |
| } |
| int lineStart = line.getStartOffset(); |
| int lineEnd = Math.min(line.getEndOffset(), doc.getLength()); |
| |
| Segment seg = SegmentCache.getSharedSegment(); |
| doc.getText(lineStart, lineEnd - lineStart, seg); |
| if(seg.count > 0) { |
| BreakIterator words = BreakIterator.getWordInstance(c.getLocale()); |
| words.setText(seg); |
| int wordPosition = offs - lineStart + seg.offset; |
| if(wordPosition >= words.last()) { |
| wordPosition = words.last() - 1; |
| } |
| offs = lineStart + words.following(wordPosition) - seg.offset; |
| } |
| SegmentCache.releaseSharedSegment(seg); |
| return offs; |
| } |
| |
| /** |
| * Determines the start of the next word for the given location. |
| * Uses BreakIterator.getWordInstance() to actually get the words. |
| * |
| * @param c the editor |
| * @param offs the offset in the document >= 0 |
| * @return the location in the model of the word start >= 0 |
| * @exception BadLocationException if the offset is out of range |
| */ |
| public static final int getNextWord(JTextComponent c, int offs) throws BadLocationException { |
| int nextWord; |
| Element line = getParagraphElement(c, offs); |
| for (nextWord = getNextWordInParagraph(c, line, offs, false); |
| nextWord == BreakIterator.DONE; |
| nextWord = getNextWordInParagraph(c, line, offs, true)) { |
| |
| // didn't find in this line, try the next line |
| offs = line.getEndOffset(); |
| line = getParagraphElement(c, offs); |
| } |
| return nextWord; |
| } |
| |
| /** |
| * Finds the next word in the given elements text. The first |
| * parameter allows searching multiple paragraphs where even |
| * the first offset is desired. |
| * Returns the offset of the next word, or BreakIterator.DONE |
| * if there are no more words in the element. |
| */ |
| static int getNextWordInParagraph(JTextComponent c, Element line, int offs, boolean first) throws BadLocationException { |
| if (line == null) { |
| throw new BadLocationException("No more words", offs); |
| } |
| Document doc = line.getDocument(); |
| int lineStart = line.getStartOffset(); |
| int lineEnd = Math.min(line.getEndOffset(), doc.getLength()); |
| if ((offs >= lineEnd) || (offs < lineStart)) { |
| throw new BadLocationException("No more words", offs); |
| } |
| Segment seg = SegmentCache.getSharedSegment(); |
| doc.getText(lineStart, lineEnd - lineStart, seg); |
| BreakIterator words = BreakIterator.getWordInstance(c.getLocale()); |
| words.setText(seg); |
| if ((first && (words.first() == (seg.offset + offs - lineStart))) && |
| (! Character.isWhitespace(seg.array[words.first()]))) { |
| |
| return offs; |
| } |
| int wordPosition = words.following(seg.offset + offs - lineStart); |
| if ((wordPosition == BreakIterator.DONE) || |
| (wordPosition >= seg.offset + seg.count)) { |
| // there are no more words on this line. |
| return BreakIterator.DONE; |
| } |
| // if we haven't shot past the end... check to |
| // see if the current boundary represents whitespace. |
| // if so, we need to try again |
| char ch = seg.array[wordPosition]; |
| if (! Character.isWhitespace(ch)) { |
| return lineStart + wordPosition - seg.offset; |
| } |
| |
| // it was whitespace, try again. The assumption |
| // is that it must be a word start if the last |
| // one had whitespace following it. |
| wordPosition = words.next(); |
| if (wordPosition != BreakIterator.DONE) { |
| offs = lineStart + wordPosition - seg.offset; |
| if (offs != lineEnd) { |
| return offs; |
| } |
| } |
| SegmentCache.releaseSharedSegment(seg); |
| return BreakIterator.DONE; |
| } |
| |
| |
| /** |
| * Determine the start of the prev word for the given location. |
| * Uses BreakIterator.getWordInstance() to actually get the words. |
| * |
| * @param c the editor |
| * @param offs the offset in the document >= 0 |
| * @return the location in the model of the word start >= 0 |
| * @exception BadLocationException if the offset is out of range |
| */ |
| public static final int getPreviousWord(JTextComponent c, int offs) throws BadLocationException { |
| int prevWord; |
| Element line = getParagraphElement(c, offs); |
| for (prevWord = getPrevWordInParagraph(c, line, offs); |
| prevWord == BreakIterator.DONE; |
| prevWord = getPrevWordInParagraph(c, line, offs)) { |
| |
| // didn't find in this line, try the prev line |
| offs = line.getStartOffset() - 1; |
| line = getParagraphElement(c, offs); |
| } |
| return prevWord; |
| } |
| |
| /** |
| * Finds the previous word in the given elements text. The first |
| * parameter allows searching multiple paragraphs where even |
| * the first offset is desired. |
| * Returns the offset of the next word, or BreakIterator.DONE |
| * if there are no more words in the element. |
| */ |
| static int getPrevWordInParagraph(JTextComponent c, Element line, int offs) throws BadLocationException { |
| if (line == null) { |
| throw new BadLocationException("No more words", offs); |
| } |
| Document doc = line.getDocument(); |
| int lineStart = line.getStartOffset(); |
| int lineEnd = line.getEndOffset(); |
| if ((offs > lineEnd) || (offs < lineStart)) { |
| throw new BadLocationException("No more words", offs); |
| } |
| Segment seg = SegmentCache.getSharedSegment(); |
| doc.getText(lineStart, lineEnd - lineStart, seg); |
| BreakIterator words = BreakIterator.getWordInstance(c.getLocale()); |
| words.setText(seg); |
| if (words.following(seg.offset + offs - lineStart) == BreakIterator.DONE) { |
| words.last(); |
| } |
| int wordPosition = words.previous(); |
| if (wordPosition == (seg.offset + offs - lineStart)) { |
| wordPosition = words.previous(); |
| } |
| |
| if (wordPosition == BreakIterator.DONE) { |
| // there are no more words on this line. |
| return BreakIterator.DONE; |
| } |
| // if we haven't shot past the end... check to |
| // see if the current boundary represents whitespace. |
| // if so, we need to try again |
| char ch = seg.array[wordPosition]; |
| if (! Character.isWhitespace(ch)) { |
| return lineStart + wordPosition - seg.offset; |
| } |
| |
| // it was whitespace, try again. The assumption |
| // is that it must be a word start if the last |
| // one had whitespace following it. |
| wordPosition = words.previous(); |
| if (wordPosition != BreakIterator.DONE) { |
| return lineStart + wordPosition - seg.offset; |
| } |
| SegmentCache.releaseSharedSegment(seg); |
| return BreakIterator.DONE; |
| } |
| |
| /** |
| * Determines the element to use for a paragraph/line. |
| * |
| * @param c the editor |
| * @param offs the starting offset in the document >= 0 |
| * @return the element |
| */ |
| public static final Element getParagraphElement(JTextComponent c, int offs) { |
| Document doc = c.getDocument(); |
| if (doc instanceof StyledDocument) { |
| return ((StyledDocument)doc).getParagraphElement(offs); |
| } |
| Element map = doc.getDefaultRootElement(); |
| int index = map.getElementIndex(offs); |
| Element paragraph = map.getElement(index); |
| if ((offs >= paragraph.getStartOffset()) && (offs < paragraph.getEndOffset())) { |
| return paragraph; |
| } |
| return null; |
| } |
| |
| static boolean isComposedTextElement(Document doc, int offset) { |
| Element elem = doc.getDefaultRootElement(); |
| while (!elem.isLeaf()) { |
| elem = elem.getElement(elem.getElementIndex(offset)); |
| } |
| return isComposedTextElement(elem); |
| } |
| |
| static boolean isComposedTextElement(Element elem) { |
| AttributeSet as = elem.getAttributes(); |
| return isComposedTextAttributeDefined(as); |
| } |
| |
| static boolean isComposedTextAttributeDefined(AttributeSet as) { |
| return ((as != null) && |
| (as.isDefined(StyleConstants.ComposedTextAttribute))); |
| } |
| |
| /** |
| * Draws the given composed text passed from an input method. |
| * |
| * @param view View hosting text |
| * @param attr the attributes containing the composed text |
| * @param g the graphics context |
| * @param x the X origin |
| * @param y the Y origin |
| * @param p0 starting offset in the composed text to be rendered |
| * @param p1 ending offset in the composed text to be rendered |
| * @return the new insertion position |
| */ |
| static int drawComposedText(View view, AttributeSet attr, Graphics g, |
| int x, int y, int p0, int p1) |
| throws BadLocationException { |
| Graphics2D g2d = (Graphics2D)g; |
| AttributedString as = (AttributedString)attr.getAttribute( |
| StyleConstants.ComposedTextAttribute); |
| as.addAttribute(TextAttribute.FONT, g.getFont()); |
| |
| if (p0 >= p1) |
| return x; |
| |
| AttributedCharacterIterator aci = as.getIterator(null, p0, p1); |
| return x + (int)SwingUtilities2.drawString( |
| getJComponent(view), g2d,aci,x,y); |
| } |
| |
| /** |
| * Paints the composed text in a GlyphView |
| */ |
| static void paintComposedText(Graphics g, Rectangle alloc, GlyphView v) { |
| if (g instanceof Graphics2D) { |
| Graphics2D g2d = (Graphics2D) g; |
| int p0 = v.getStartOffset(); |
| int p1 = v.getEndOffset(); |
| AttributeSet attrSet = v.getElement().getAttributes(); |
| AttributedString as = |
| (AttributedString)attrSet.getAttribute(StyleConstants.ComposedTextAttribute); |
| int start = v.getElement().getStartOffset(); |
| int y = alloc.y + alloc.height - (int)v.getGlyphPainter().getDescent(v); |
| int x = alloc.x; |
| |
| //Add text attributes |
| as.addAttribute(TextAttribute.FONT, v.getFont()); |
| as.addAttribute(TextAttribute.FOREGROUND, v.getForeground()); |
| if (StyleConstants.isBold(v.getAttributes())) { |
| as.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD); |
| } |
| if (StyleConstants.isItalic(v.getAttributes())) { |
| as.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE); |
| } |
| if (v.isUnderline()) { |
| as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); |
| } |
| if (v.isStrikeThrough()) { |
| as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON); |
| } |
| if (v.isSuperscript()) { |
| as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER); |
| } |
| if (v.isSubscript()) { |
| as.addAttribute(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB); |
| } |
| |
| // draw |
| AttributedCharacterIterator aci = as.getIterator(null, p0 - start, p1 - start); |
| SwingUtilities2.drawString(getJComponent(v), |
| g2d,aci,x,y); |
| } |
| } |
| |
| /* |
| * Convenience function for determining ComponentOrientation. Helps us |
| * avoid having Munge directives throughout the code. |
| */ |
| static boolean isLeftToRight( java.awt.Component c ) { |
| return c.getComponentOrientation().isLeftToRight(); |
| } |
| |
| |
| /** |
| * Provides a way to determine the next visually represented model |
| * location that one might place a caret. Some views may not be visible, |
| * they might not be in the same order found in the model, or they just |
| * might not allow access to some of the locations in the model. |
| * <p> |
| * This implementation assumes the views are layed out in a logical |
| * manner. That is, that the view at index x + 1 is visually after |
| * the View at index x, and that the View at index x - 1 is visually |
| * before the View at x. There is support for reversing this behavior |
| * only if the passed in <code>View</code> is an instance of |
| * <code>CompositeView</code>. The <code>CompositeView</code> |
| * must then override the <code>flipEastAndWestAtEnds</code> method. |
| * |
| * @param v View to query |
| * @param pos the position to convert >= 0 |
| * @param a the allocated region to render into |
| * @param direction the direction from the current position that can |
| * be thought of as the arrow keys typically found on a keyboard; |
| * this may be one of the following: |
| * <ul> |
| * <li><code>SwingConstants.WEST</code> |
| * <li><code>SwingConstants.EAST</code> |
| * <li><code>SwingConstants.NORTH</code> |
| * <li><code>SwingConstants.SOUTH</code> |
| * </ul> |
| * @param biasRet an array contain the bias that was checked |
| * @return the location within the model that best represents the next |
| * location visual position |
| * @exception BadLocationException |
| * @exception IllegalArgumentException if <code>direction</code> is invalid |
| */ |
| static int getNextVisualPositionFrom(View v, int pos, Position.Bias b, |
| Shape alloc, int direction, |
| Position.Bias[] biasRet) |
| throws BadLocationException { |
| if (v.getViewCount() == 0) { |
| // Nothing to do. |
| return pos; |
| } |
| boolean top = (direction == SwingConstants.NORTH || |
| direction == SwingConstants.WEST); |
| int retValue; |
| if (pos == -1) { |
| // Start from the first View. |
| int childIndex = (top) ? v.getViewCount() - 1 : 0; |
| View child = v.getView(childIndex); |
| Shape childBounds = v.getChildAllocation(childIndex, alloc); |
| retValue = child.getNextVisualPositionFrom(pos, b, childBounds, |
| direction, biasRet); |
| if (retValue == -1 && !top && v.getViewCount() > 1) { |
| // Special case that should ONLY happen if first view |
| // isn't valid (can happen when end position is put at |
| // beginning of line. |
| child = v.getView(1); |
| childBounds = v.getChildAllocation(1, alloc); |
| retValue = child.getNextVisualPositionFrom(-1, biasRet[0], |
| childBounds, |
| direction, biasRet); |
| } |
| } |
| else { |
| int increment = (top) ? -1 : 1; |
| int childIndex; |
| if (b == Position.Bias.Backward && pos > 0) { |
| childIndex = v.getViewIndex(pos - 1, Position.Bias.Forward); |
| } |
| else { |
| childIndex = v.getViewIndex(pos, Position.Bias.Forward); |
| } |
| View child = v.getView(childIndex); |
| Shape childBounds = v.getChildAllocation(childIndex, alloc); |
| retValue = child.getNextVisualPositionFrom(pos, b, childBounds, |
| direction, biasRet); |
| if ((direction == SwingConstants.EAST || |
| direction == SwingConstants.WEST) && |
| (v instanceof CompositeView) && |
| ((CompositeView)v).flipEastAndWestAtEnds(pos, b)) { |
| increment *= -1; |
| } |
| childIndex += increment; |
| if (retValue == -1 && childIndex >= 0 && |
| childIndex < v.getViewCount()) { |
| child = v.getView(childIndex); |
| childBounds = v.getChildAllocation(childIndex, alloc); |
| retValue = child.getNextVisualPositionFrom( |
| -1, b, childBounds, direction, biasRet); |
| // If there is a bias change, it is a fake position |
| // and we should skip it. This is usually the result |
| // of two elements side be side flowing the same way. |
| if (retValue == pos && biasRet[0] != b) { |
| return getNextVisualPositionFrom(v, pos, biasRet[0], |
| alloc, direction, |
| biasRet); |
| } |
| } |
| else if (retValue != -1 && biasRet[0] != b && |
| ((increment == 1 && child.getEndOffset() == retValue) || |
| (increment == -1 && |
| child.getStartOffset() == retValue)) && |
| childIndex >= 0 && childIndex < v.getViewCount()) { |
| // Reached the end of a view, make sure the next view |
| // is a different direction. |
| child = v.getView(childIndex); |
| childBounds = v.getChildAllocation(childIndex, alloc); |
| Position.Bias originalBias = biasRet[0]; |
| int nextPos = child.getNextVisualPositionFrom( |
| -1, b, childBounds, direction, biasRet); |
| if (biasRet[0] == b) { |
| retValue = nextPos; |
| } |
| else { |
| biasRet[0] = originalBias; |
| } |
| } |
| } |
| return retValue; |
| } |
| } |