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