diff -r 836adbf7a2cd -r 3317bb8137f4 jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,1078 @@
+/*
+ * Copyright (c) 1997, 2013, 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 view
's container is a JComponent
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.
+ *
+ * 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 View
is an instance of
+ * CompositeView
. The CompositeView
+ * must then override the flipEastAndWestAtEnds
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:
+ *
SwingConstants.WEST
+ * SwingConstants.EAST
+ * SwingConstants.NORTH
+ * SwingConstants.SOUTH
+ * direction
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;
+ }
+}