0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: /**
0018: * @author Evgeniya G. Maenkova, Alexey A. Ivanov
0019: * @version $Revision$
0020: */package org.apache.harmony.awt.text;
0021:
0022: import java.awt.Color;
0023: import java.awt.Component;
0024: import java.awt.ComponentOrientation;
0025: import java.awt.Dimension;
0026: import java.awt.FontMetrics;
0027: import java.awt.Graphics;
0028: import java.awt.Insets;
0029: import java.awt.Point;
0030: import java.awt.Rectangle;
0031: import java.awt.Shape;
0032: import java.awt.Toolkit;
0033: import java.awt.datatransfer.Clipboard;
0034: import java.awt.datatransfer.DataFlavor;
0035: import java.awt.datatransfer.StringSelection;
0036: import java.awt.datatransfer.Transferable;
0037: import java.awt.datatransfer.UnsupportedFlavorException;
0038: import java.awt.event.InputMethodEvent;
0039: import java.awt.event.InputMethodListener;
0040: import java.awt.font.TextAttribute;
0041: import java.io.IOException;
0042: import java.text.AttributedCharacterIterator;
0043: import java.text.AttributedString;
0044: import java.text.BreakIterator;
0045: import java.text.CharacterIterator;
0046: import java.text.DateFormat;
0047: import java.text.Format.Field;
0048: import java.util.Calendar;
0049: import java.util.Date;
0050:
0051: import javax.swing.BoundedRangeModel;
0052: import javax.swing.JComponent;
0053: import javax.swing.JEditorPane;
0054: import javax.swing.JFormattedTextField;
0055: import javax.swing.SwingConstants;
0056: import javax.swing.text.AbstractDocument;
0057: import javax.swing.text.AttributeSet;
0058: import javax.swing.text.BadLocationException;
0059: import javax.swing.text.DateFormatter;
0060: import javax.swing.text.Document;
0061: import javax.swing.text.Element;
0062: import javax.swing.text.MutableAttributeSet;
0063: import javax.swing.text.PlainDocument;
0064: import javax.swing.text.Position;
0065: import javax.swing.text.Segment;
0066: import javax.swing.text.StyleConstants;
0067: import javax.swing.text.StyledDocument;
0068: import javax.swing.text.TabExpander;
0069: import javax.swing.text.View;
0070: import javax.swing.text.Position.Bias;
0071:
0072: import org.apache.harmony.awt.ComponentInternals;
0073: import org.apache.harmony.awt.internal.nls.Messages;
0074:
0075: public class TextUtils {
0076: public TextUtils() {
0077: }
0078:
0079: static final Position.Bias backward = Position.Bias.Backward;
0080:
0081: static final Position.Bias forward = Position.Bias.Forward;
0082:
0083: /**
0084: * Sets new value to dot or mark of the given component caret
0085: * depending on <code>isMovingCaret</code> value
0086: */
0087: public static final void changeCaretPosition(final TextKit textKit,
0088: final int newPos, final boolean isMovingCaret) {
0089:
0090: TextCaret caret = textKit.getCaret();
0091:
0092: Point pt = caret.getMagicCaretPosition();
0093: if (isMovingCaret) {
0094: caret.moveDot(newPos, Position.Bias.Forward);
0095: } else {
0096: caret.setDot(newPos, Position.Bias.Forward);
0097: }
0098: caret.setMagicCaretPosition(pt);
0099: }
0100:
0101: // TODO remove this method as duplicate
0102:
0103: /**
0104: * Sets new value to dot or mark of the given component caret
0105: * depending on <code>isMovingCaret</code> value
0106: */
0107: public static final void changeCaretPosition(final TextKit textKit,
0108: final int newPos, final boolean isMovingCaret,
0109: final Position.Bias newBias) {
0110:
0111: TextCaret caret = textKit.getCaret();
0112: Point pt = caret.getMagicCaretPosition();
0113: if (isMovingCaret) {
0114: caret.moveDot(newPos, newBias);
0115: } else {
0116: caret.setDot(newPos, newBias);
0117: }
0118: //TODO in original TextAction this is after not here
0119: //(after getCaretMagicPosition).
0120: //Probably there is mistake
0121: caret.setMagicCaretPosition(pt);
0122: }
0123:
0124: public static final int getNextWord(final Document doc,
0125: final int pos) throws BadLocationException {
0126:
0127: BreakIterator bi = BreakIterator.getWordInstance();
0128: int length = doc.getLength();
0129: if (pos < 0 || pos >= length) {
0130: // awt.2F=No more words
0131: throwException(Messages.getString("awt.2F"), pos); //$NON-NLS-1$
0132: }
0133: String content = null;
0134:
0135: content = doc.getText(0, doc.getLength());
0136: bi.setText(content);
0137:
0138: int iteratorNextWord = bi.following(pos);
0139: while (iteratorNextWord < length
0140: && Character.isWhitespace(content
0141: .charAt(iteratorNextWord))) {
0142: iteratorNextWord = bi.following(iteratorNextWord);
0143: }
0144: if (iteratorNextWord == length) {
0145: // awt.2F=No more words
0146: throwException(Messages.getString("awt.2F"), pos); //$NON-NLS-1$
0147: }
0148: return iteratorNextWord;
0149: }
0150:
0151: public static final int getPreviousWord(final Document doc,
0152: final int pos) throws BadLocationException {
0153:
0154: BreakIterator bi = BreakIterator.getWordInstance();
0155: int length = doc.getLength();
0156: if (pos < 0 || pos > length) {
0157: // awt.2F=No more words
0158: throwException(Messages.getString("awt.2F"), pos); //$NON-NLS-1$
0159: }
0160: String content = null;
0161:
0162: content = doc.getText(0, doc.getLength());
0163: bi.setText(content);
0164:
0165: int iteratorPrevWord = bi.preceding(pos);
0166: while (iteratorPrevWord > 0
0167: && ((content.charAt(iteratorPrevWord) == ' ' || content
0168: .charAt(iteratorPrevWord) == '\n') || content
0169: .charAt(iteratorPrevWord) == '\t')) {
0170: iteratorPrevWord = bi.preceding(iteratorPrevWord);
0171: }
0172: return iteratorPrevWord == -1 ? 0 : iteratorPrevWord;
0173: }
0174:
0175: public static final boolean isBidirectional(final Document document) {
0176: if (!(document instanceof AbstractDocument)) {
0177: return false;
0178: }
0179: Element bidiRoot = ((AbstractDocument) document)
0180: .getBidiRootElement();
0181: return (bidiRoot != null && bidiRoot.getElementCount() >= 2);
0182: }
0183:
0184: static final boolean isLTR(final Element element) {
0185: return isLTR(StyleConstants.getBidiLevel(element
0186: .getAttributes()));
0187: }
0188:
0189: public static final boolean isLTR(final int level) {
0190: return (level & 1) == 0;
0191: }
0192:
0193: static final boolean isRTL(final int level) {
0194: return (level & 1) == 1;
0195: }
0196:
0197: public static final int getTabbedTextWidth(final Segment s,
0198: final FontMetrics fm, final int x, final TabExpander t,
0199: final int pos) {
0200: return getTabbedTextEnd(s, fm, x, t, pos, false, null, 0) - x;
0201: }
0202:
0203: public static final int getTabbedTextOffset(final Segment s,
0204: final FontMetrics fm, final int start, final int end,
0205: final TabExpander t, final int pos) {
0206: return getTabbedTextOffset(s, fm, start, end, t, pos, true);
0207: }
0208:
0209: /**
0210: * If round equals false it needs that symbol is placed completely. If
0211: * round equals true it needs that more than half of the symbol is placed.
0212: */
0213: public static final int getTabbedTextOffset(final Segment s,
0214: final FontMetrics fm, final int start, final int end,
0215: final TabExpander t, final int pos, final boolean round) {
0216: String str = ""; //$NON-NLS-1$
0217: int segmentOffset = pos - s.getBeginIndex();
0218: boolean isTab = false;
0219: boolean isNullTabExpander = (t == null);
0220: int currentEnd = start < 0 ? 0 : start;
0221: int x1 = start < 0 ? start : 0;
0222: int currentIndex = 0;
0223: int prevEnd = currentEnd;
0224: int tabEnd = currentEnd;
0225: for (char c = s.first(); c != CharacterIterator.DONE; c = s
0226: .next()) {
0227: isTab = (c == '\t');
0228: if (isTab && !isNullTabExpander) {
0229: tabEnd = (int) t.nextTabStop(currentEnd, s.getIndex()
0230: + segmentOffset);
0231: str = ""; //$NON-NLS-1$
0232: } else {
0233: str += (isTab) ? ' ' : c;
0234: isTab = false;
0235: }
0236: int tmpEnd = tabEnd + x1;
0237: currentEnd = isTab ? tmpEnd : tmpEnd + fm.stringWidth(str);
0238: int delta = (round) ? (currentEnd - prevEnd) / 2 : 0;
0239: if (currentEnd > end + delta) {
0240: break;
0241: }
0242: currentIndex++;
0243: prevEnd = currentEnd;
0244: }
0245: return currentIndex;
0246: }
0247:
0248: public static final int drawTabbedText(final Segment s,
0249: final int x, final int y, final Graphics g,
0250: final TabExpander t, final int pos) {
0251: return getTabbedTextEnd(s, g.getFontMetrics(), x, t, pos, true,
0252: g, y);
0253: }
0254:
0255: public static final int drawComposedText(final TextKit textKit,
0256: final AttributedString text, final Graphics g, final int x,
0257: final int y) {
0258: text.addAttribute(TextAttribute.FONT, textKit.getComponent()
0259: .getFont());
0260: final AttributedCharacterIterator iterator = text.getIterator();
0261:
0262: g.drawString(iterator, x, y);
0263:
0264: final int width = g.getFontMetrics().getStringBounds(iterator,
0265: iterator.getBeginIndex(), iterator.getEndIndex(), g)
0266: .getBounds().width;
0267: return width + x;
0268: }
0269:
0270: public static final int getBreakLocation(final Segment s,
0271: final FontMetrics fm, final int start, final int end,
0272: final TabExpander t, final int pos) {
0273: int offset = s.offset;
0274: int index = TextUtils.getTabbedTextOffset(s, fm, start, end, t,
0275: pos, false);
0276: int fullIndex = offset + index;
0277:
0278: BreakIterator bi = BreakIterator.getWordInstance();
0279: bi.setText(s);
0280: if (bi.last() <= fullIndex) {
0281: return bi.last() - offset;
0282: }
0283: if (bi.isBoundary(fullIndex)) {
0284: return Character.isWhitespace(s.array[fullIndex]) ? index + 1
0285: : index;
0286: }
0287: int prev = bi.preceding(fullIndex);
0288: if (prev == bi.first()) {
0289: return index;
0290: }
0291: return prev - offset;
0292: }
0293:
0294: public static final int getRowStart(final TextKit tk, final int pos)
0295: throws BadLocationException {
0296: Dimension d = tk.getVisibleRect().getSize();
0297: if (d != null && (d.height == 0 || d.width == 0)) {
0298: return -1;
0299: }
0300: int length = tk.getDocument().getLength();
0301: if (pos < 0 || pos > length) {
0302: // awt.2A=Position not represented by view
0303: throwException(Messages.getString("awt.2A"), pos); //$NON-NLS-1$
0304: }
0305: int y = tk.modelToView(pos).y;
0306: Rectangle tmp = null;
0307: for (int i = pos - 1; i >= 0; i--) {
0308: tmp = tk.modelToView(i);
0309: if (tmp.y < y) {
0310: return i + 1;
0311: }
0312: }
0313: return 0;
0314: }
0315:
0316: public static final int getWordEnd(final TextKit tk, final int pos)
0317: throws BadLocationException {
0318:
0319: Document doc = tk.getDocument();
0320: BreakIterator bi = BreakIterator.getWordInstance();
0321: int length = doc.getLength();
0322: if (pos < 0 || pos > length) {
0323: // awt.2B=No word at {0}
0324: throwException(Messages.getString("awt.2B", pos), pos); //$NON-NLS-1$
0325: }
0326: String content = doc.getText(0, doc.getLength());
0327: bi.setText(content);
0328: return (pos < bi.last()) ? bi.following(pos) : pos;
0329: }
0330:
0331: public static final int getWordStart(final TextKit tk, final int pos)
0332: throws BadLocationException {
0333: Document doc = tk.getDocument();
0334: BreakIterator bi = BreakIterator.getWordInstance();
0335: int length = doc.getLength();
0336: if (pos < 0 || pos > length) {
0337: // awt.2B=No word at {0}
0338: throwException(Messages.getString("awt.2B", pos), pos); //$NON-NLS-1$
0339: }
0340: String content = null;
0341:
0342: content = doc.getText(0, doc.getLength());
0343: bi.setText(content);
0344: int iteratorWordStart = pos;
0345: if (pos < length - 1) {
0346: iteratorWordStart = bi.preceding(pos + 1);
0347: } else {
0348: bi.last();
0349: iteratorWordStart = bi.previous();
0350: }
0351: return iteratorWordStart;
0352: }
0353:
0354: /**
0355: * Finds row above, calculates modelToView for all the positions from this view
0356: * and selects the closest one.
0357: */
0358: public static final int getPositionAbove(final TextKit textKit,
0359: final int p, final int x) throws BadLocationException {
0360: int p0 = getRowStart(textKit, p);
0361: if (p0 <= 0) {
0362: return -1;
0363: }
0364: int end = p0 - 1;
0365: int offset = end;
0366: int dy = 0;
0367: Rectangle rect = textKit.modelToView(end);
0368: int lastY = rect.y;
0369: int dx = Math.abs(rect.x - x);
0370: int index = end;
0371: do {
0372: if (index != end) {
0373: rect = textKit.modelToView(index);
0374: }
0375: dy = rect.y - lastY;
0376: int locDiff = Math.abs(rect.x - x);
0377: if (locDiff <= dx && dy == 0) {
0378: dx = locDiff;
0379: offset = index;
0380: }
0381: index--;
0382: lastY = rect.y;
0383: } while (dx >= 1 && dy == 0 && index >= 0);
0384: return offset;
0385: }
0386:
0387: /**
0388: * Finds row below, calculates modelToView for all the positions from this view
0389: * and selects the closest one.
0390: */
0391: public static final int getPositionBelow(final TextKit textKit,
0392: final int p, final int x) throws BadLocationException {
0393: int p0 = getRowEnd(textKit, p);
0394: if (p0 == -1) {
0395: return -1;
0396: }
0397: int length = textKit.getDocument().getLength();
0398: if (p0 == length) {
0399: return p;
0400: }
0401: int start = p0 + 1;
0402: int offset = p0 + 1;
0403: int dy = 0;
0404: Rectangle rect = textKit.modelToView(start);
0405: int lastY = rect.y;
0406: int dx = Math.abs(rect.x - x);
0407: int index = start;
0408: do {
0409: rect = textKit.modelToView(index);
0410: dy = rect.y - lastY;
0411: int locDiff = Math.abs(rect.x - x);
0412: if (locDiff < dx && dy == 0) {
0413: dx = locDiff;
0414: offset = index;
0415: }
0416: index++;
0417: lastY = rect.y;
0418: } while (index <= length && dy == 0 && dx >= 1);
0419:
0420: return offset;
0421: }
0422:
0423: public static final TextKit getTextKit(final Component c) {
0424: return ComponentInternals.getComponentInternals().getTextKit(c);
0425: }
0426:
0427: public static final TextFieldKit getTextFieldKit(final Component c) {
0428: return ComponentInternals.getComponentInternals()
0429: .getTextFieldKit(c);
0430: }
0431:
0432: public static final int getRowEnd(final TextKit tk, final int pos)
0433: throws BadLocationException {
0434: Dimension d = tk.getVisibleRect().getSize();
0435: if (d != null && (d.height == 0 || d.width == 0)) {
0436: return -1;
0437: }
0438: int length = tk.getDocument().getLength();
0439: if (pos < 0 || pos > length) {
0440: // awt.2A=Position not represented by view
0441: throwException(Messages.getString("awt.2A"), pos); //$NON-NLS-1$
0442: }
0443:
0444: int y = tk.modelToView(pos).y;
0445: Rectangle r = null;
0446: for (int i = pos + 1; i <= length; i++) {
0447: r = tk.modelToView(i);
0448: if (r.y > y) {
0449: return i - 1;
0450: }
0451: }
0452: return length;
0453: }
0454:
0455: //TODO Probably here is a bug:
0456: //Why NORTH?
0457: //Where bias?
0458: public static final void setCurrentPositionAsMagic(
0459: final TextKit textKit) {
0460: TextCaret caret = textKit.getCaret();
0461: int newPos = caret.getDot();
0462: try {
0463: Point pt = textKit.modelToView(newPos, caret.getDotBias())
0464: .getLocation();
0465: caret.setMagicCaretPosition(newPos, SwingConstants.NORTH,
0466: pt);
0467: } catch (BadLocationException e) {
0468: }
0469: }
0470:
0471: /**
0472: * If the component document is an instance of AbstractDocument then its
0473: * {@link AbstractDocument#getParagraphElement(int)} method is called.
0474: * Otherwise {@link Document#getDefaultRootElement()} is used to search
0475: * paragraph element.
0476: */
0477: public static final Element getParagraphElement(final Document doc,
0478: final int p) {
0479: if (doc instanceof AbstractDocument) {
0480: AbstractDocument abstrDoc = (AbstractDocument) doc;
0481: abstrDoc.readLock();
0482: Element elem = null;
0483: int length = 0;
0484: boolean incorrectPosition = false;
0485: try {
0486: length = doc.getLength();
0487: incorrectPosition = (p < 0 || p > length)
0488: && (doc instanceof PlainDocument);
0489: if (!incorrectPosition) {
0490: elem = abstrDoc.getParagraphElement(p);
0491: }
0492: } finally {
0493: abstrDoc.readUnlock();
0494: }
0495: return elem;
0496: }
0497:
0498: Element root = doc.getDefaultRootElement();
0499: int index = root.getElementIndex(p);
0500: return index == -1 ? null : root.getElement(index);
0501: }
0502:
0503: /*
0504: * Returns end x-coordinate of tabbed text. Uses by drawTabbedText and
0505: * getTabbedTextWidth.
0506: */
0507: private static int getTabbedTextEnd(final Segment s,
0508: final FontMetrics fm, final int x, final TabExpander t,
0509: final int pos, final boolean needDraw, final Graphics g,
0510: final int y) {
0511: int x1 = x < 0 ? x : 0;
0512: int res = x < 0 ? 0 : x;
0513: String buffer = s.toString();
0514: int tabIndex = buffer.indexOf("\t"); //$NON-NLS-1$
0515: int currentIndex = pos - s.getBeginIndex() + s.offset;
0516: if (t == null) {
0517: String buf = buffer.replaceAll("\t", " "); //$NON-NLS-1$ //$NON-NLS-2$
0518: drawString(buf, needDraw, g, x, y);
0519: return fm.stringWidth(buf) + x;
0520: }
0521: String substr = null;
0522: int lastTabIndex = -1;
0523: while (tabIndex >= 0) {
0524: substr = buffer.substring(lastTabIndex + 1, tabIndex);
0525: drawString(substr, needDraw, g, res + x1, y);
0526: res = (int) t.nextTabStop(res + fm.stringWidth(substr),
0527: tabIndex + currentIndex);
0528: lastTabIndex = tabIndex;
0529: tabIndex = buffer.indexOf("\t", tabIndex + 1); //$NON-NLS-1$
0530: }
0531: int tmp = res + x1;
0532: substr = buffer.substring(lastTabIndex + 1, buffer.length());
0533: drawString(substr, needDraw, g, tmp, y);
0534: return (tabIndex >= 0) ? tmp : tmp + fm.stringWidth(substr);
0535: }
0536:
0537: private static void drawString(final String text,
0538: final boolean needDraw, final Graphics g, final int x,
0539: final int y) {
0540: if (needDraw) {
0541: g.drawString(text, x, y);
0542: }
0543: }
0544:
0545: public static final void readLock(final Document document) {
0546: if (document instanceof AbstractDocument) {
0547: ((AbstractDocument) document).readLock();
0548: }
0549: }
0550:
0551: public static final void readUnlock(final Document document) {
0552: if (document instanceof AbstractDocument) {
0553: ((AbstractDocument) document).readUnlock();
0554: }
0555: }
0556:
0557: private static void throwException(final String s, final int i)
0558: throws BadLocationException {
0559: throw new BadLocationException(s, i);
0560: }
0561:
0562: //---NextVisualPosition---------------------------------------------
0563: // for a stub (getNexVisualPositionFrom)
0564: public static final int getNextVisualPositionFrom(
0565: final TextKit textKit, final View v, final int pos,
0566: final Position.Bias bias, final Shape shape,
0567: final int direction, final Position.Bias[] biasRet)
0568: throws BadLocationException {
0569: int length = v.getDocument().getLength();
0570: if (pos < 0 || pos > length) {
0571: // awt.2C=Invalid position: {0}
0572: throwException(Messages.getString("awt.2C", pos), pos); //$NON-NLS-1$
0573: }
0574: biasRet[0] = Position.Bias.Forward;
0575: if (direction == SwingConstants.WEST
0576: || direction == SwingConstants.EAST) {
0577: return getNextVisualPosition(v, pos, bias, direction,
0578: biasRet);
0579: }
0580: Point pt = textKit.getCaret().getMagicCaretPosition();
0581: if (direction == SwingConstants.NORTH) {
0582: return TextUtils.getPositionAbove(textKit, pos,
0583: pt != null ? pt.x : v.modelToView(pos, shape, bias)
0584: .getBounds().x);
0585: } else if (direction == SwingConstants.SOUTH) {
0586: return TextUtils.getPositionBelow(textKit, pos,
0587: pt != null ? pt.x : v.modelToView(pos, shape, bias)
0588: .getBounds().x);
0589: }
0590:
0591: // awt.2D=Invalid direction
0592: throw new IllegalArgumentException(Messages.getString("awt.2D")); //$NON-NLS-1$
0593: }
0594:
0595: private static Element getElementByPosition(
0596: final Element rootElement, final int pos) {
0597: int index = rootElement.getElementIndex(pos);
0598: return rootElement.getElement(index);
0599: }
0600:
0601: private static int getNextVisualPosition(final View v,
0602: final int pos, final Position.Bias b0, final int direction,
0603: final Position.Bias[] biasRet) {
0604: boolean toWest = (direction == SwingConstants.WEST);
0605: Document document = v.getDocument();
0606: int length = document.getLength();
0607: if (!isBidirectional(document)) {
0608: return getTrivialVisualPosition(toWest, pos, b0, length,
0609: biasRet, true);
0610: }
0611: Element bidiRoot = ((AbstractDocument) document)
0612: .getBidiRootElement();
0613: Element elem = getElementByPosition(bidiRoot, pos);
0614: boolean isLTR = isLTR(elem);
0615: int start = elem.getStartOffset();
0616: int end = elem.getEndOffset() - 1;
0617:
0618: int posInNeighboringElement = toWest ? Math.max(start - 1, 0)
0619: : Math.min(end + 1, length);
0620: Element neighboringElement = getElementByPosition(bidiRoot,
0621: posInNeighboringElement);
0622:
0623: Element paragraph = getElementByPosition(document
0624: .getDefaultRootElement(), pos);
0625: int startParagraph = paragraph.getStartOffset();
0626: int endParagraph = paragraph.getEndOffset() - 1;
0627:
0628: boolean nextIsLTR = isLTR(getElementByPosition(bidiRoot, Math
0629: .min(endParagraph + 1, length)));
0630:
0631: int result = checkBoundaryCondition(pos, b0, biasRet,
0632: neighboringElement, isLTR, toWest, start, length,
0633: startParagraph, endParagraph, nextIsLTR);
0634:
0635: return result >= 0 ? result : getBidiVisualPosition(start, end,
0636: neighboringElement, pos, b0, biasRet, length, toWest,
0637: isLTR);
0638: }
0639:
0640: private static int checkBoundaryCondition(final int pos,
0641: final Position.Bias b0, final Position.Bias[] biasRet,
0642: final Element neighboringElement, final boolean isLTR,
0643: final boolean toWest, final int start, final int length,
0644: final int startParagraph, final int endParagraph,
0645: final boolean nextIsLTR) {
0646:
0647: if (toWest && isLTR) {
0648: if (pos == startParagraph) {
0649: return Math.max(0, pos - 1);
0650: } else if (neighboringElement.getStartOffset() == startParagraph
0651: && pos == start && b0 == backward) {
0652: return Math.max(0, startParagraph - 1);
0653: }
0654: } else if (!toWest) {
0655: if (b0 == forward && pos == length) {
0656: biasRet[0] = b0;
0657: return pos;
0658: }
0659: if (b0 == forward && pos == endParagraph) {
0660: if (nextIsLTR) {
0661: return pos + 1;
0662: }
0663: biasRet[0] = backward;
0664: return neighboringElement.getEndOffset();
0665: }
0666: }
0667: return -1;
0668: }
0669:
0670: static final int getBidiVisualPosition(final int start,
0671: final int end, final Element neighbouringElement,
0672: final int pos, final Position.Bias b0,
0673: final Position.Bias[] biasRet, final int length,
0674: final boolean toWest, final boolean isLTR) {
0675: boolean direction = toWest ^ isLTR;
0676: if (pos == end && direction && b0 == forward) {
0677: biasRet[0] = backward;
0678: return pos + 1;
0679: } else if (pos == start + 1 && pos <= end && !direction) {
0680: return pos - 1;
0681: } else if (pos == start) {
0682: if (direction) {
0683: return (b0 == forward) ? pos + 1 : pos - 1;
0684: }
0685: biasRet[0] = b0;
0686: return neighbouringElement.getStartOffset();
0687: } else {
0688: return getTrivialVisualPosition(toWest, pos, b0, length,
0689: biasRet, isLTR);
0690: }
0691: }
0692:
0693: private static int getTrivialVisualPosition(final boolean toWest,
0694: final int pos, final Position.Bias bias,
0695: final int docLength, final Position.Bias[] biasRet,
0696: final boolean isLTR) {
0697: boolean condition = (toWest && isLTR) || (!toWest && !isLTR);
0698: return condition ? Math.max(pos - 1, 0) : Math.min(pos + 1,
0699: docLength);
0700: }
0701:
0702: //-------TextField Methods
0703: public static final Shape getFieldViewAllocation(final View v,
0704: final TextFieldKit tfk, final Shape shape,
0705: final ComponentOrientation orientation) {
0706: if (tfk == null || shape == null) {
0707: return null;
0708: }
0709:
0710: Rectangle bounds = shape.getBounds();
0711: int prefWidth = (int) v.getPreferredSpan(View.X_AXIS);
0712: int height = (int) v.getPreferredSpan(View.Y_AXIS);
0713: int diff = bounds.width - prefWidth;
0714: int alignment = tfk.getHorizontalAlignment();
0715: boolean toLeft = isToLeft(orientation, alignment);
0716: int offset = 0;
0717: if (alignment == SwingConstants.CENTER) {
0718: offset = diff / 2;
0719: } else {
0720: offset = toLeft ? 0 : diff;
0721: }
0722: int extent = bounds.width - 1;
0723: int max = Math.max(extent, prefWidth);
0724: BoundedRangeModel brm = tfk.getHorizontalVisibility();
0725: int value = Math.min(brm.getValue(), max - extent);
0726: brm.setRangeProperties(value, extent, brm.getMinimum(), max,
0727: false);
0728:
0729: int x = (diff < 0 ? -value + tfk.getInsets().left : bounds.x
0730: + offset);
0731: int y = (bounds.height - height) / 2 + bounds.y;
0732: if (!toLeft) {
0733: x--;
0734: }
0735: return new Rectangle(x, y, prefWidth + 1, height);
0736: }
0737:
0738: private static boolean isToLeft(
0739: final ComponentOrientation orientation, final int alignment) {
0740: boolean isRTL = !orientation.isLeftToRight();
0741: return isRTL && alignment == SwingConstants.TRAILING || !isRTL
0742: && alignment == SwingConstants.LEADING
0743: || alignment == SwingConstants.LEFT;
0744: }
0745:
0746: //-------Highlight painting
0747:
0748: public static Shape paintLayer(final Graphics g, final int p0,
0749: final int p1, final Shape shape, final Color color,
0750: final View view, final boolean fill) {
0751: if (shape == null) {
0752: return null;
0753: }
0754: Shape result = null;
0755: try {
0756: result = view.modelToView(Math.min(p0, p1),
0757: Position.Bias.Forward, Math.max(p0, p1),
0758: Position.Bias.Backward, shape);
0759: Rectangle bounds = result.getBounds();
0760: g.setColor(color);
0761: if (fill) {
0762: g.fillRect(bounds.x, bounds.y, bounds.width,
0763: bounds.height);
0764: } else {
0765: g.drawRect(bounds.x, bounds.y, bounds.width - 1,
0766: bounds.height - 1);
0767: }
0768: } catch (final BadLocationException e) {
0769: }
0770: return result;
0771: }
0772:
0773: //------IM support
0774: public static ComposedTextParams getComposedTextParams(
0775: final TextKit tk) {
0776: Document doc = tk.getDocument();
0777: Object currentProperty = tk.getDocument().getProperty(
0778: PropertyNames.COMPOSED_TEXT_PROPERTY);
0779: if (!(currentProperty instanceof ComposedTextParams)) {
0780: ComposedTextParams result = new ComposedTextParams(doc);
0781: int caretPosition = tk.getCaret().getDot();
0782: result.setComposedTextStart(caretPosition);
0783: result.setLastCommittedTextStart(caretPosition);
0784: return result;
0785: }
0786: return (ComposedTextParams) currentProperty;
0787: }
0788:
0789: public static void processIMEvent(
0790: final InputMethodListener listener, final InputMethodEvent e) {
0791: if (e.getID() == InputMethodEvent.CARET_POSITION_CHANGED) {
0792: listener.caretPositionChanged(e);
0793: }
0794: if (e.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) {
0795: listener.inputMethodTextChanged(e);
0796: }
0797: }
0798:
0799: //----highlighter
0800: public static final Rectangle getBoundsByOffsets(final TextKit tk,
0801: final int p0, final int p1) {
0802: Rectangle r0 = null;
0803: Rectangle r1 = null;
0804: Rectangle rect = new Rectangle();
0805: if (tk == null) {
0806: return null;
0807: }
0808: try {
0809: r0 = tk.modelToView(p0, Position.Bias.Forward);
0810: r1 = tk.modelToView(p1, Position.Bias.Forward);
0811: } catch (final BadLocationException e) {
0812: }
0813: if (r0 == null || r1 == null) {
0814: return null;
0815: }
0816: if (r0.y == r1.y) {
0817: rect.x = Math.min(r0.x, r1.x);
0818: rect.y = r0.y;
0819: rect.width = Math.max(r0.x, r1.x) - rect.x + 1;
0820: rect.height = Math.max(r0.height, r1.height);
0821: return rect;
0822: }
0823: Rectangle visibleRect = tk.getVisibleRect();
0824: rect.x = visibleRect.x;
0825: rect.y = Math.min(r0.y, r1.y);
0826: rect.width = visibleRect.width;
0827: rect.height = Math.max(r0.y, r1.y) - rect.y
0828: + Math.max(r0.height, r1.height);
0829: return rect;
0830: }
0831:
0832: public static final void setNativeCaretPosition(
0833: final Rectangle rect, final Component comp) {
0834: ComponentInternals.getComponentInternals().setCaretPos(comp,
0835: rect.x, rect.y);
0836: }
0837:
0838: //-----clipboard operations (Perhaps, that's temporary solution.
0839: public static final void copy(final TextKit textKit) {
0840: exportToClipboard(textKit, getSystemClipboard(),
0841: ActionNames.COPY);
0842: }
0843:
0844: public static final void cut(final TextKit textKit) {
0845: exportToClipboard(textKit, getSystemClipboard(),
0846: ActionNames.MOVE);
0847: }
0848:
0849: public static final void paste(final TextKit textKit) {
0850: TextUtils.importData(textKit, getSystemClipboard().getContents(
0851: null));
0852: }
0853:
0854: public static final void exportToClipboard(final TextKit textKit,
0855: final Clipboard clipboard, final int action) {
0856: if (textKit == null) {
0857: return;
0858: }
0859: int realAction = (action & (getSourceActions(textKit)));
0860: Transferable transferable = TextUtils
0861: .createTransferable(textKit);
0862: if (realAction > 0 && transferable != null) {
0863: clipboard.setContents(transferable, null);
0864: }
0865: exportDone(textKit, transferable, realAction);
0866: }
0867:
0868: public static final Transferable createTransferable(
0869: final TextKit textKit) {
0870: String text = textKit.getSelectedText();
0871: return text != null ? new StringSelection(text) : null;
0872: }
0873:
0874: public static final void exportDone(final TextKit textKit,
0875: final Transferable transferable, final int action) {
0876: if (textKit != null && (action & ActionNames.MOVE) > 0) {
0877: textKit.replaceSelectedText(""); //$NON-NLS-1$
0878: }
0879: }
0880:
0881: public static final boolean importData(final TextKit textKit,
0882: final Transferable t) {
0883:
0884: if (t == null) {
0885: return false;
0886: }
0887: DataFlavor[] flavors = t.getTransferDataFlavors();
0888: DataFlavor flavor = null;
0889: for (DataFlavor element : flavors) {
0890: flavor = element;
0891: if (String.class.isAssignableFrom(flavor
0892: .getRepresentationClass())) {
0893: break;
0894: }
0895: flavor = null;
0896: }
0897: if (flavor != null) {
0898: try {
0899: String text = (String) t.getTransferData(flavor);
0900: textKit.replaceSelectedText(text);
0901: return true;
0902: } catch (UnsupportedFlavorException e) {
0903: return false;
0904: } catch (IOException e) {
0905: return false;
0906: }
0907: }
0908: return false;
0909: }
0910:
0911: public static final Clipboard getSystemClipboard() {
0912: return Toolkit.getDefaultToolkit().getSystemClipboard();
0913: }
0914:
0915: public static int getSourceActions(final TextKit textKit) {
0916: if (textKit != null
0917: && !"javax.swing.JPasswordField".equals(textKit.getClass())) { //$NON-NLS-1$
0918: return (textKit.isEditable()) ? ActionNames.COPY_OR_MOVE
0919: : ActionNames.COPY;
0920:
0921: }
0922: return ActionNames.NONE;
0923: }
0924:
0925: public static Rectangle scrollRectToVisible(Rectangle viewRect,
0926: Rectangle r) {
0927: Rectangle retVal = (Rectangle) viewRect.clone();
0928: retVal.x = -retVal.x;
0929: retVal.y = -retVal.y;
0930:
0931: int dx;
0932: int dy;
0933:
0934: if (r.x > 0) {
0935: if (r.x + r.width > viewRect.width) {
0936: int dx2 = r.x + r.width - viewRect.width;
0937: dx = Math.min(r.x, dx2);
0938: } else {
0939: dx = 0;
0940: }
0941: } else if (r.x < 0) {
0942: if (r.x + r.width < viewRect.width) {
0943: int dx2 = r.x + r.width - viewRect.width;
0944: dx = Math.max(r.x, dx2);
0945: } else {
0946: dx = 0;
0947: }
0948: } else {
0949: dx = 0;
0950: }
0951:
0952: if (r.y > 0) {
0953: if (r.y + r.height > viewRect.height) {
0954: int dy2 = r.y + r.height - viewRect.height;
0955: dy = Math.min(r.y, dy2);
0956: } else {
0957: dy = 0;
0958: }
0959: } else if (r.y < 0) {
0960: if (r.y + r.height < viewRect.height) {
0961: int dy2 = r.y + r.height - viewRect.height;
0962: dy = Math.max(r.y, dy2);
0963: } else {
0964: dy = 0;
0965: }
0966: } else {
0967: dy = 0;
0968: }
0969:
0970: if (dx != 0 || dy != 0) {
0971: int x = retVal.x + dx;
0972: int y = retVal.y + dy;
0973:
0974: retVal.x = x;
0975: retVal.y = y;
0976: }
0977:
0978: return retVal;
0979: }
0980:
0981: public static Rectangle getEditorRect(final JComponent component) {
0982: if (component == null) {
0983: return null;
0984: }
0985: Insets insets = component.getInsets();
0986: int left = 0;
0987: int top = 0;
0988: if (insets != null) {
0989: left = insets.left;
0990: top = insets.top;
0991: }
0992: Dimension r = component.getSize();
0993: return r.width == 0 || r.height == 0 ? null : new Rectangle(
0994: left, top, r.width - getHrzInsets(insets), r.height
0995: - getVrtInsets(insets));
0996: }
0997:
0998: public static int getHrzInsets(final Insets insets) {
0999: return (insets != null) ? insets.left + insets.right : 0;
1000: }
1001:
1002: public static int getVrtInsets(final Insets insets) {
1003: return (insets != null) ? insets.top + insets.bottom : 0;
1004: }
1005:
1006: public static void setCharacterAttributes(final AttributeSet attr,
1007: final boolean replace, final JEditorPane editorPane,
1008: final StyledDocument doc,
1009: final MutableAttributeSet inputAttrs) {
1010:
1011: final int selectionStart = editorPane.getSelectionStart();
1012: final int selectionEnd = editorPane.getSelectionEnd();
1013: doc.setCharacterAttributes(selectionStart, selectionEnd
1014: - selectionStart, attr, replace);
1015: if (selectionStart == selectionEnd) {
1016: if (replace) {
1017: inputAttrs.removeAttributes(inputAttrs
1018: .getAttributeNames());
1019: }
1020: inputAttrs.addAttributes(attr);
1021: }
1022: }
1023:
1024: public static void setParagraphAttributes(final AttributeSet attr,
1025: final boolean replace, final JEditorPane editorPane,
1026: final StyledDocument doc) {
1027: final int selectStart = editorPane.getSelectionStart();
1028: int intervalLength = Math.max(editorPane.getSelectionEnd()
1029: - selectStart, 1);
1030: doc.setParagraphAttributes(selectStart, intervalLength, attr,
1031: replace);
1032: }
1033:
1034: public static int getCalendarField(
1035: final JFormattedTextField textField) {
1036: DateFormatter formatter = (DateFormatter) textField
1037: .getFormatter();
1038: Field[] fields = formatter.getFields(textField
1039: .getCaretPosition());
1040:
1041: for (int i = textField.getCaretPosition(); fields.length == 0
1042: || i < 0; i--) {
1043: fields = formatter.getFields(i);
1044: }
1045: if (fields.length == 0) {
1046: int length = textField.getText().length();
1047: for (int i = textField.getCaretPosition(); fields.length == 0
1048: || i > length; i++) {
1049: fields = formatter.getFields(i);
1050: }
1051: }
1052: return ((DateFormat.Field) fields[0]).getCalendarField();
1053: }
1054:
1055: public static void selectCalendarField(
1056: final JFormattedTextField textField, final int calendarField) {
1057: boolean selecting = false;
1058: int length = textField.getText().length();
1059: for (int i = 0; i <= length; i++) {
1060: if (!selecting) {
1061: textField.setCaretPosition(i);
1062: } else {
1063: textField.moveCaretPosition(i);
1064: }
1065: if (getCalendarField(textField) == calendarField) {
1066: selecting = true;
1067: } else {
1068: if (selecting) {
1069: textField.moveCaretPosition(i - 1);
1070: return;
1071: }
1072: }
1073: }
1074: }
1075:
1076: public static Object getNextValue(final Date value,
1077: final int calendarField, final Comparable<Date> end) {
1078: Calendar calendar = Calendar.getInstance();
1079: calendar.setTime(value);
1080: calendar.add(calendarField, 1);
1081: Date result = calendar.getTime();
1082: return (end == null) ? result
1083: : (end.compareTo(result) < 0) ? null : result;
1084: }
1085:
1086: public static Object getPreviousValue(final Date value,
1087: final int calendarField, final Comparable<Date> start) {
1088: Calendar calendar = Calendar.getInstance();
1089: calendar.setTime(value);
1090: calendar.add(calendarField, -1);
1091: Date result = calendar.getTime();
1092: return (start == null) ? result
1093: : (start.compareTo(result) > 0) ? null : result;
1094: }
1095:
1096: /**
1097: * Transforms position at the document model coordinate space to the
1098: * coordinate space of the corresponding icon or component view.
1099: */
1100: public static Shape modelToIconOrComponentView(final View view,
1101: final int pos, final Shape shape, final Bias bias)
1102: throws BadLocationException {
1103:
1104: TextUtils.isPositionValid(view, pos);
1105:
1106: final Rectangle bounds = shape.getBounds();
1107: final int x = (pos == view.getStartOffset() ? bounds.x
1108: : bounds.x + bounds.width);
1109:
1110: return new Rectangle(x, bounds.y, 0, bounds.height);
1111: }
1112:
1113: /**
1114: * Throws BadLocationException if the position does not represent a
1115: * valid location in the associated document element.
1116: */
1117: public static void isPositionValid(final View view, final int pos)
1118: throws BadLocationException {
1119:
1120: if (pos < view.getStartOffset() || pos > view.getEndOffset()) {
1121: // awt.2E={0} not in range {1},{2}
1122: throw new BadLocationException(Messages.getString("awt.2E", //$NON-NLS-1$
1123: new Object[] { pos, view.getStartOffset(),
1124: view.getEndOffset() }), pos);
1125: }
1126: }
1127: }
|