0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.editor;
0043:
0044: import java.awt.Rectangle;
0045: import java.awt.Frame;
0046: import java.awt.event.ActionEvent;
0047: import java.awt.event.KeyEvent;
0048: import java.util.logging.Level;
0049: import java.util.logging.Logger;
0050: import javax.swing.SwingUtilities;
0051: import javax.swing.Action;
0052: import javax.swing.KeyStroke;
0053: import javax.swing.text.JTextComponent;
0054: import javax.swing.text.BadLocationException;
0055: import javax.swing.text.EditorKit;
0056: import javax.swing.text.Document;
0057: import javax.swing.text.TextAction;
0058: import javax.swing.text.Caret;
0059: import javax.swing.plaf.TextUI;
0060: import javax.swing.text.Element;
0061: import javax.swing.text.View;
0062: import org.netbeans.lib.editor.util.CharSequenceUtilities;
0063: import org.netbeans.lib.editor.util.swing.DocumentUtilities;
0064: import org.openide.util.NbBundle;
0065:
0066: /**
0067: * Various useful editor functions. Some of the methods have
0068: * the same names and signatures like in javax.swing.Utilities but
0069: * there is also many other useful methods.
0070: * All the methods are static so there's no reason to instantiate Utilities.
0071: *
0072: * All the methods working with the document rely on that it is locked against
0073: * modification so they don't acquire document read/write lock by themselves
0074: * to guarantee the full thread safety of the execution.
0075: * It's the user's task to lock the document appropriately
0076: * before using methods described here.
0077: *
0078: * Most of the methods require org.netbeans.editor.BaseDocument instance
0079: * not just the javax.swing.text.Document.
0080: * The reason for that is to mark that the methods work on BaseDocument
0081: * instances only, not on generic documents. To convert the Document
0082: * to BaseDocument the simple conversion (BaseDocument)target.getDocument()
0083: * can be done or the method getDocument(target) can be called.
0084: * There are also other conversion methods like getEditorUI(), getKit()
0085: * or getKitClass().
0086: *
0087: * @author Miloslav Metelka
0088: * @version 0.10
0089: */
0090:
0091: public class Utilities {
0092:
0093: private static final String WRONG_POSITION_LOCALE = "wrong_position"; // NOI18N
0094:
0095: /** Switch the case to capital letters. Used in changeCase() */
0096: public static final int CASE_UPPER = 0;
0097:
0098: /** Switch the case to small letters. Used in changeCase() */
0099: public static final int CASE_LOWER = 1;
0100:
0101: /** Switch the case to reverse. Used in changeCase() */
0102: public static final int CASE_SWITCH = 2;
0103:
0104: /** Fake TextAction for getting the info of the focused component */
0105: private static TextAction focusedComponentAction;
0106:
0107: private Utilities() {
0108: // instantiation has no sense
0109: }
0110:
0111: /** Get the starting position of the row.
0112: * @param c text component to operate on
0113: * @param offset position in document where to start searching
0114: * @return position of the start of the row or -1 for invalid position
0115: */
0116: public static int getRowStart(JTextComponent c, int offset)
0117: throws BadLocationException {
0118: Rectangle r = c.modelToView(offset);
0119: if (r == null) {
0120: return -1;
0121: }
0122: EditorUI eui = getEditorUI(c);
0123: if (eui != null) {
0124: return c.viewToModel(new java.awt.Point(
0125: eui.textLeftMarginWidth, r.y));
0126: }
0127: return -1;
0128: }
0129:
0130: /** Get the starting position of the row.
0131: * @param doc document to operate on
0132: * @param offset position in document where to start searching
0133: * @return position of the start of the row or -1 for invalid position
0134: */
0135: public static int getRowStart(BaseDocument doc, int offset)
0136: throws BadLocationException {
0137: return getRowStart(doc, offset, 0);
0138: }
0139:
0140: /** Get the starting position of the row while providing relative count
0141: * of row how the given position should be shifted. This is the most
0142: * efficient way how to move by lines in the document based on some
0143: * position. There is no similair getRowEnd() method that would have
0144: * shifting parameter.
0145: * @param doc document to operate on
0146: * @param offset position in document where to start searching
0147: * @param lineShift shift the given offset forward/back relatively
0148: * by some amount of lines
0149: * @return position of the start of the row or -1 for invalid position
0150: */
0151: public static int getRowStart(BaseDocument doc, int offset,
0152: int lineShift) throws BadLocationException {
0153:
0154: checkOffsetValid(doc, offset);
0155:
0156: if (lineShift != 0) {
0157: Element lineRoot = doc.getParagraphElement(0)
0158: .getParentElement();
0159: int line = lineRoot.getElementIndex(offset);
0160: line += lineShift;
0161: if (line < 0 || line >= lineRoot.getElementCount()) {
0162: return -1; // invalid line shift
0163: }
0164: return lineRoot.getElement(line).getStartOffset();
0165:
0166: } else { // no shift
0167: return doc.getParagraphElement(offset).getStartOffset();
0168: }
0169: }
0170:
0171: /** Get the first non-white character on the line.
0172: * The document.isWhitespace() is used to test whether the particular
0173: * character is white space or not.
0174: * @param doc document to operate on
0175: * @param offset position in document anywhere on the line
0176: * @return position of the first non-white char on the line or -1
0177: * if there's no non-white character on that line.
0178: */
0179: public static int getRowFirstNonWhite(BaseDocument doc, int offset)
0180: throws BadLocationException {
0181:
0182: checkOffsetValid(doc, offset);
0183:
0184: Element lineElement = doc.getParagraphElement(offset);
0185: return getFirstNonWhiteFwd(doc, lineElement.getStartOffset(),
0186: lineElement.getEndOffset() - 1);
0187: }
0188:
0189: /** Get the last non-white character on the line.
0190: * The document.isWhitespace() is used to test whether the particular
0191: * character is white space or not.
0192: * @param doc document to operate on
0193: * @param offset position in document anywhere on the line
0194: * @return position of the last non-white char on the line or -1
0195: * if there's no non-white character on that line.
0196: */
0197: public static int getRowLastNonWhite(BaseDocument doc, int offset)
0198: throws BadLocationException {
0199:
0200: checkOffsetValid(doc, offset);
0201:
0202: Element lineElement = doc.getParagraphElement(offset);
0203: return getFirstNonWhiteBwd(doc, lineElement.getEndOffset() - 1,
0204: lineElement.getStartOffset());
0205: }
0206:
0207: /** Get indentation on the current line. If this line is white then
0208: * return -1.
0209: * @param doc document to operate on
0210: * @param offset position in document anywhere on the line
0211: * @return indentation or -1 if the line is white
0212: */
0213: public static int getRowIndent(BaseDocument doc, int offset)
0214: throws BadLocationException {
0215: offset = getRowFirstNonWhite(doc, offset);
0216: if (offset == -1) {
0217: return -1;
0218: }
0219: return doc.getVisColFromPos(offset);
0220: }
0221:
0222: /** Get indentation on the current line. If this line is white then
0223: * go either up or down an return indentation of the first non-white row.
0224: * The <tt>getRowFirstNonWhite()</tt> is used to find the indentation
0225: * on particular line.
0226: * @param doc document to operate on
0227: * @param offset position in document anywhere on the line
0228: * @param downDir if this flag is set to true then if the row is white
0229: * then the indentation of the next first non-white row is returned. If it's
0230: * false then the indentation of the previous first non-white row is returned.
0231: * @return indentation or -1 if there's no non-white line in the specified direction
0232: */
0233: public static int getRowIndent(BaseDocument doc, int offset,
0234: boolean downDir) throws BadLocationException {
0235: int p = getRowFirstNonWhite(doc, offset);
0236: if (p == -1) {
0237: p = getFirstNonWhiteRow(doc, offset, downDir);
0238: if (p == -1) {
0239: return -1; // non-white line not found
0240: }
0241: p = getRowFirstNonWhite(doc, p);
0242: if (p == -1) {
0243: return -1; // non-white line not found
0244: }
0245: }
0246: return doc.getVisColFromPos(p);
0247: }
0248:
0249: /** Get the end position of the row right before the new-line character.
0250: * @param c text component to operate on
0251: * @param offset position in document where to start searching
0252: * @param relLine shift offset forward/back by some amount of lines
0253: * @return position of the end of the row or -1 for invalid position
0254: */
0255: public static int getRowEnd(JTextComponent c, int offset)
0256: throws BadLocationException {
0257: Rectangle r = c.modelToView(offset);
0258: if (r == null) {
0259: return -1;
0260: }
0261: return c
0262: .viewToModel(new java.awt.Point(Integer.MAX_VALUE, r.y));
0263: }
0264:
0265: public static int getRowEnd(BaseDocument doc, int offset)
0266: throws BadLocationException {
0267: checkOffsetValid(doc, offset);
0268:
0269: return doc.getParagraphElement(offset).getEndOffset() - 1;
0270: }
0271:
0272: private static int findBestSpan(JTextComponent c, int lineBegin,
0273: int lineEnd, int x) throws BadLocationException {
0274: if (lineBegin == lineEnd) {
0275: return lineEnd;
0276: }
0277: int low = lineBegin;
0278: int high = lineEnd;
0279: while (low <= high) {
0280:
0281: if (high - low < 3) {
0282: int bestSpan = Integer.MAX_VALUE;
0283: int bestPos = -1;
0284: for (int i = low; i <= high; i++) {
0285: Rectangle tempRect = c.modelToView(i);
0286: if (Math.abs(x - tempRect.x) < bestSpan) {
0287: bestSpan = Math.abs(x - tempRect.x);
0288: bestPos = i;
0289: }
0290: }
0291: return bestPos;
0292: }
0293:
0294: int mid = (low + high) / 2;
0295:
0296: Rectangle tempRect = c.modelToView(mid);
0297: if (tempRect.x > x) {
0298: high = mid;
0299: } else if (tempRect.x < x) {
0300: low = mid;
0301: } else {
0302: return mid;
0303: }
0304: }
0305: return lineBegin;
0306: }
0307:
0308: /** Get the position that is one line above and visually at some
0309: * x-coordinate value.
0310: * @param doc document to operate on
0311: * @param offset position in document from which the current line is determined
0312: * @param x float x-coordinate value
0313: * @return position of the character that is at the one line above at
0314: * the required x-coordinate value
0315: */
0316: public static int getPositionAbove(JTextComponent c, int offset,
0317: int x) throws BadLocationException {
0318: int rowStart = getRowStart(c, offset);
0319: int endInit = c.getUI().getNextVisualPositionFrom(c, rowStart,
0320: null, javax.swing.SwingConstants.WEST, null);
0321:
0322: if (x == BaseKit.MAGIC_POSITION_MAX) {
0323: return endInit;
0324: }
0325:
0326: EditorUI eui = getEditorUI(c);
0327: if (eui == null) {
0328: return offset; //skip
0329: }
0330:
0331: Rectangle r = c.modelToView(endInit);
0332: if (r == null) {
0333: return offset; //skip
0334: }
0335:
0336: if (x == eui.textLeftMarginWidth) {
0337: return getRowStart(c, endInit);
0338: }
0339:
0340: int end = c.viewToModel(new java.awt.Point(Math.max(
0341: eui.textLeftMarginWidth, x + 2 * r.width), r.y));
0342: Rectangle tempRect = c.modelToView(end);
0343: if (tempRect == null || tempRect.x < x) {
0344: end = endInit;
0345: }
0346:
0347: int start = c.viewToModel(new java.awt.Point(Math.max(
0348: eui.textLeftMarginWidth, x - 2 * r.width), r.y));
0349: tempRect = c.modelToView(start);
0350: if (tempRect == null && tempRect.x > x) {
0351: start = getRowStart(c, end);
0352: }
0353:
0354: int best = findBestSpan(c, start, end, x);
0355:
0356: if (best < c.getDocument().getLength()) {
0357: // #56056
0358: int tmp = best + 1;
0359: int nextVisualPosition = c.getUI()
0360: .getNextVisualPositionFrom(c, tmp,
0361: javax.swing.text.Position.Bias.Backward,
0362: javax.swing.SwingConstants.WEST, null);
0363: if (nextVisualPosition < best && nextVisualPosition >= 0) {
0364: return nextVisualPosition;
0365: }
0366: }
0367:
0368: return best;
0369: }
0370:
0371: /** Get the position that is one line above and visually at some
0372: * x-coordinate value.
0373: * @param c text component to operate on
0374: * @param offset position in document from which the current line is determined
0375: * @param x float x-coordinate value
0376: * @return position of the character that is at the one line above at
0377: * the required x-coordinate value
0378: */
0379: public static int getPositionBelow(JTextComponent c, int offset,
0380: int x) throws BadLocationException {
0381: int startInit = getRowEnd(c, offset) + 1;
0382:
0383: Rectangle r = c.modelToView(startInit);
0384: if (r == null) {
0385: return offset; // skip
0386: }
0387:
0388: EditorUI eui = getEditorUI(c);
0389: if (eui != null && x == eui.textLeftMarginWidth) {
0390: return startInit;
0391: }
0392:
0393: int start = c.viewToModel(new java.awt.Point(Math.min(
0394: Integer.MAX_VALUE, r.x + x - 2 * r.width), r.y));
0395: Rectangle tempRect = c.modelToView(start);
0396: if (tempRect != null && tempRect.x > x) {
0397: start = startInit;
0398: }
0399:
0400: int end = c.viewToModel(new java.awt.Point(Math.min(
0401: Integer.MAX_VALUE, r.x + x + 2 * r.width), r.y));
0402: tempRect = c.modelToView(end);
0403: if (tempRect != null && tempRect.x < x) {
0404: end = getRowEnd(c, start);
0405: }
0406:
0407: int best = findBestSpan(c, start, end, x);
0408:
0409: if (best > 0) {
0410: // #70254 - make sure $best is not in collapsed fold area. Try
0411: // getNextVisualPositionFrom to EAST from the position $best-1.
0412: // If the resulted next visual position is not equal to $best,
0413: // $best is in the collapsed fold and foldEnd or foldStart
0414: // should be returned.
0415: int tmp = best - 1;
0416: int nextVisualPosition = c.getUI()
0417: .getNextVisualPositionFrom(c, tmp,
0418: javax.swing.text.Position.Bias.Forward,
0419: javax.swing.SwingConstants.EAST, null);
0420: if (nextVisualPosition > best
0421: && nextVisualPosition <= c.getDocument()
0422: .getLength()) {
0423: // We are in the collapsed fold, now try to find which position
0424: // is the best, whether foldEnd or foldStart
0425: tempRect = c.modelToView(nextVisualPosition);
0426: if (tempRect == null) {
0427: return nextVisualPosition;
0428: }
0429: int rightX = tempRect.x;
0430: int nextVisualPositionLeft = c
0431: .getUI()
0432: .getNextVisualPositionFrom(
0433: c,
0434: nextVisualPosition,
0435: javax.swing.text.Position.Bias.Backward,
0436: javax.swing.SwingConstants.WEST, null);
0437: tempRect = c.modelToView(nextVisualPositionLeft);
0438: if (tempRect == null) {
0439: return nextVisualPosition;
0440: }
0441: int leftX = tempRect.x;
0442:
0443: if (Math.abs(leftX - x) > Math.abs(rightX - x)) {
0444: return nextVisualPosition;
0445: } else {
0446: return nextVisualPositionLeft;
0447: }
0448: }
0449: }
0450:
0451: return best;
0452: }
0453:
0454: /** Get start of the current word. If there are no more words till
0455: * the begining of the document, this method returns -1.
0456: * @param c text component to operate on
0457: * @param offset position in document from which the current line is determined
0458: */
0459: public static int getWordStart(JTextComponent c, int offset)
0460: throws BadLocationException {
0461: return getWordStart((BaseDocument) c.getDocument(), offset);
0462: }
0463:
0464: public static int getWordStart(BaseDocument doc, int offset)
0465: throws BadLocationException {
0466: return doc.find(new FinderFactory.PreviousWordBwdFinder(doc,
0467: false, true), offset, 0);
0468: }
0469:
0470: public static int getWordEnd(JTextComponent c, int offset)
0471: throws BadLocationException {
0472: return getWordEnd((BaseDocument) c.getDocument(), offset);
0473: }
0474:
0475: public static int getWordEnd(BaseDocument doc, int offset)
0476: throws BadLocationException {
0477: int ret = doc.find(new FinderFactory.NextWordFwdFinder(doc,
0478: false, true), offset, -1);
0479: return (ret > 0) ? ret : doc.getLength();
0480: }
0481:
0482: public static int getNextWord(JTextComponent c, int offset)
0483: throws BadLocationException {
0484: int nextWordOffset = getNextWord(
0485: (BaseDocument) c.getDocument(), offset);
0486: int nextVisualPosition = -1;
0487: if (nextWordOffset > 0) {
0488: nextVisualPosition = c.getUI().getNextVisualPositionFrom(c,
0489: nextWordOffset - 1, null,
0490: javax.swing.SwingConstants.EAST, null);
0491: }
0492: return (nextVisualPosition == -1) ? nextWordOffset
0493: : nextVisualPosition;
0494: }
0495:
0496: public static int getNextWord(BaseDocument doc, int offset)
0497: throws BadLocationException {
0498: Finder nextWordFinder = (Finder) doc
0499: .getProperty(SettingsNames.NEXT_WORD_FINDER);
0500: offset = doc.find(nextWordFinder, offset, -1);
0501: if (offset < 0) {
0502: offset = doc.getLength();
0503: }
0504: return offset;
0505: }
0506:
0507: public static int getPreviousWord(JTextComponent c, int offset)
0508: throws BadLocationException {
0509: int prevWordOffset = getPreviousWord((BaseDocument) c
0510: .getDocument(), offset);
0511: int nextVisualPosition = c.getUI().getNextVisualPositionFrom(c,
0512: prevWordOffset, null, javax.swing.SwingConstants.WEST,
0513: null);
0514: if (nextVisualPosition == 0 && prevWordOffset == 0) {
0515: return 0;
0516: }
0517: return (nextVisualPosition + 1 == prevWordOffset) ? prevWordOffset
0518: : nextVisualPosition + 1;
0519: }
0520:
0521: public static int getPreviousWord(BaseDocument doc, int offset)
0522: throws BadLocationException {
0523: Finder prevWordFinder = (Finder) doc
0524: .getProperty(SettingsNames.PREVIOUS_WORD_FINDER);
0525: offset = doc.find(prevWordFinder, offset, 0);
0526: if (offset < 0) {
0527: offset = 0;
0528: }
0529: return offset;
0530: }
0531:
0532: /** Get first white character in document in forward direction
0533: * @param doc document to operate on
0534: * @param offset position in document where to start searching
0535: * @return position of the first white character or -1
0536: */
0537: public static int getFirstWhiteFwd(BaseDocument doc, int offset)
0538: throws BadLocationException {
0539: return getFirstWhiteFwd(doc, offset, -1);
0540: }
0541:
0542: /** Get first white character in document in forward direction
0543: * @param doc document to operate on
0544: * @param offset position in document where to start searching
0545: * @param limitPos position in document (greater or equal than offset) where
0546: * the search will stop reporting unsuccessful search by returning -1
0547: * @return position of the first non-white character or -1
0548: */
0549: public static int getFirstWhiteFwd(BaseDocument doc, int offset,
0550: int limitPos) throws BadLocationException {
0551: return doc.find(new FinderFactory.WhiteFwdFinder(doc), offset,
0552: limitPos);
0553: }
0554:
0555: /** Get first non-white character in document in forward direction
0556: * @param doc document to operate on
0557: * @param offset position in document where to start searching
0558: * @return position of the first non-white character or -1
0559: */
0560: public static int getFirstNonWhiteFwd(BaseDocument doc, int offset)
0561: throws BadLocationException {
0562: return getFirstNonWhiteFwd(doc, offset, -1);
0563: }
0564:
0565: /** Get first non-white character in document in forward direction
0566: * @param doc document to operate on
0567: * @param offset position in document where to start searching
0568: * @param limitPos position in document (greater or equal than offset) where
0569: * the search will stop reporting unsuccessful search by returning -1
0570: * @return position of the first non-white character or -1
0571: */
0572: public static int getFirstNonWhiteFwd(BaseDocument doc, int offset,
0573: int limitPos) throws BadLocationException {
0574: return doc.find(new FinderFactory.NonWhiteFwdFinder(doc),
0575: offset, limitPos);
0576: }
0577:
0578: /** Get first white character in document in backward direction.
0579: * The character right before the character at position offset will
0580: * be searched as first.
0581: * @param doc document to operate on
0582: * @param offset position in document where to start searching
0583: * @return position of the first white character or -1
0584: */
0585: public static int getFirstWhiteBwd(BaseDocument doc, int offset)
0586: throws BadLocationException {
0587: return getFirstWhiteBwd(doc, offset, 0);
0588: }
0589:
0590: /** Get first white character in document in backward direction.
0591: * The character right before the character at position offset will
0592: * be searched as first.
0593: * @param doc document to operate on
0594: * @param offset position in document where to start searching
0595: * @param limitPos position in document (lower or equal than offset) where
0596: * the search will stop reporting unsuccessful search by returning -1
0597: * @return position of the first white character or -1
0598: */
0599: public static int getFirstWhiteBwd(BaseDocument doc, int offset,
0600: int limitPos) throws BadLocationException {
0601: return doc.find(new FinderFactory.WhiteBwdFinder(doc), offset,
0602: limitPos);
0603: }
0604:
0605: /** Get first non-white character in document in backward direction.
0606: * The character right before the character at position offset will
0607: * be searched as first.
0608: * @param doc document to operate on
0609: * @param offset position in document where to start searching
0610: * @return position of the first non-white character or -1
0611: */
0612: public static int getFirstNonWhiteBwd(BaseDocument doc, int offset)
0613: throws BadLocationException {
0614: return getFirstNonWhiteBwd(doc, offset, 0);
0615: }
0616:
0617: /** Get first non-white character in document in backward direction.
0618: * The character right before the character at position offset will
0619: * be searched as first.
0620: * @param doc document to operate on
0621: * @param offset position in document where to start searching
0622: * @param limitPos position in document (lower or equal than offset) where
0623: * the search will stop reporting unsuccessful search by returning -1
0624: * @return position of the first non-white character or -1
0625: */
0626: public static int getFirstNonWhiteBwd(BaseDocument doc, int offset,
0627: int limitPos) throws BadLocationException {
0628: return doc.find(new FinderFactory.NonWhiteBwdFinder(doc),
0629: offset, limitPos);
0630: }
0631:
0632: /** Return line offset (line number - 1) for some position in the document
0633: * @param doc document to operate on
0634: * @param offset position in document where to start searching
0635: */
0636: public static int getLineOffset(BaseDocument doc, int offset)
0637: throws BadLocationException {
0638:
0639: checkOffsetValid(offset, doc.getLength() + 1);
0640:
0641: Element lineRoot = doc.getParagraphElement(0)
0642: .getParentElement();
0643: return lineRoot.getElementIndex(offset);
0644: }
0645:
0646: /** Return start offset of the line
0647: * @param lineIndex line index starting from 0
0648: * @return start position of the line or -1 if lineIndex was invalid
0649: */
0650: public static int getRowStartFromLineOffset(BaseDocument doc,
0651: int lineIndex) {
0652: Element lineRoot = doc.getParagraphElement(0)
0653: .getParentElement();
0654: if (lineIndex < 0 || lineIndex >= lineRoot.getElementCount()) {
0655: return -1; // invalid line number
0656:
0657: } else {
0658: return lineRoot.getElement(lineIndex).getStartOffset();
0659: }
0660: }
0661:
0662: /** Return visual column (with expanded tabs) on the line.
0663: * @param doc document to operate on
0664: * @param offset position in document for which the visual column should be found
0665: * @return visual column on the line determined by position
0666: */
0667: public static int getVisualColumn(BaseDocument doc, int offset)
0668: throws BadLocationException {
0669:
0670: int docLen = doc.getLength();
0671: if (offset == docLen + 1) { // at ending extra '\n' => make docLen to proceed without BLE
0672: offset = docLen;
0673: }
0674:
0675: return doc.getVisColFromPos(offset);
0676: }
0677:
0678: /** Get the identifier around the given position or null if there's no identifier
0679: * @see getIdentifierBlock()
0680: */
0681: public static String getIdentifier(BaseDocument doc, int offset)
0682: throws BadLocationException {
0683: int[] blk = getIdentifierBlock(doc, offset);
0684: return (blk != null) ? doc.getText(blk[0], blk[1] - blk[0])
0685: : null;
0686: }
0687:
0688: /** Get the identifier around the given position or null if there's no identifier
0689: * around the given position. The identifier is not verified against SyntaxSupport.isIdentifier().
0690: * @param c JTextComponent to work on
0691: * @param offset position in document - usually the caret.getDot()
0692: * @return the block (starting and ending position) enclosing the identifier
0693: * or null if no identifier was found
0694: */
0695: public static int[] getIdentifierBlock(JTextComponent c, int offset)
0696: throws BadLocationException {
0697: CharSequence id = null;
0698: int[] ret = null;
0699: Document doc = c.getDocument();
0700: int idStart = javax.swing.text.Utilities
0701: .getWordStart(c, offset);
0702: if (idStart >= 0) {
0703: int idEnd = javax.swing.text.Utilities.getWordEnd(c,
0704: idStart);
0705: if (idEnd >= 0) {
0706: id = DocumentUtilities.getText(doc, idStart, idEnd
0707: - idStart);
0708: ret = new int[] { idStart, idEnd };
0709: CharSequence trim = CharSequenceUtilities.trim(id);
0710: if (trim.length() == 0
0711: || (trim.length() == 1 && !Character
0712: .isJavaIdentifierPart(trim.charAt(0)))) {
0713: int prevWordStart = javax.swing.text.Utilities
0714: .getPreviousWord(c, offset);
0715: if (offset == javax.swing.text.Utilities
0716: .getWordEnd(c, prevWordStart)) {
0717: ret = new int[] { prevWordStart, offset };
0718: } else {
0719: return null;
0720: }
0721: } else if ((id != null)
0722: && (id.length() != 0)
0723: && (CharSequenceUtilities.indexOf(id, '.') != -1)) { //NOI18N
0724: int index = offset - idStart;
0725: int begin = CharSequenceUtilities.lastIndexOf(id
0726: .subSequence(0, index), '.');
0727: begin = (begin == -1) ? 0 : begin + 1; //first index after the dot, if exists
0728: int end = CharSequenceUtilities.indexOf(id, '.',
0729: index);
0730: end = (end == -1) ? id.length() : end;
0731: ret = new int[] { idStart + begin, idStart + end };
0732: }
0733: }
0734: }
0735: return ret;
0736: }
0737:
0738: /** Get the identifier around the given position or null if there's no identifier
0739: * around the given position. The identifier must be
0740: * accepted by SyntaxSupport.isIdnetifier() otherwise null is returned.
0741: * @param doc document to work on
0742: * @param offset position in document - usually the caret.getDot()
0743: * @return the block (starting and ending position) enclosing the identifier
0744: * or null if no identifier was found
0745: */
0746: public static int[] getIdentifierBlock(BaseDocument doc, int offset)
0747: throws BadLocationException {
0748: int[] ret = null;
0749: int idStart = getWordStart(doc, offset);
0750: if (idStart >= 0) {
0751: int idEnd = getWordEnd(doc, idStart);
0752: if (idEnd >= 0) {
0753: String id = doc.getText(idStart, idEnd - idStart);
0754: if (doc.getSyntaxSupport().isIdentifier(id)) {
0755: ret = new int[] { idStart, idEnd };
0756: } else { // not identifier by syntax support
0757: id = getWord(doc, offset); // try right at offset
0758: if (doc.getSyntaxSupport().isIdentifier(id)) {
0759: ret = new int[] { offset, offset + id.length() };
0760: }
0761: }
0762: }
0763: }
0764: return ret;
0765: }
0766:
0767: /** Get the word around the given position .
0768: * @param c component to work with
0769: * @param offset position in document - usually the caret.getDot()
0770: * @return the word.
0771: */
0772: public static String getWord(JTextComponent c, int offset)
0773: throws BadLocationException {
0774: int[] blk = getIdentifierBlock(c, offset);
0775: Document doc = c.getDocument();
0776: return (blk != null) ? doc.getText(blk[0], blk[1] - blk[0])
0777: : null;
0778: }
0779:
0780: /** Get the selection if there's any or get the identifier around
0781: * the position if there's no selection.
0782: * @param c component to work with
0783: * @param offset position in document - usually the caret.getDot()
0784: * @return the block (starting and ending position) enclosing the identifier
0785: * or null if no identifier was found
0786: */
0787: public static int[] getSelectionOrIdentifierBlock(JTextComponent c,
0788: int offset) throws BadLocationException {
0789: Document doc = c.getDocument();
0790: Caret caret = c.getCaret();
0791: int[] ret;
0792: if (Utilities.isSelectionShowing(caret)) {
0793: ret = new int[] { c.getSelectionStart(),
0794: c.getSelectionEnd() };
0795: } else if (doc instanceof BaseDocument) {
0796: ret = getIdentifierBlock((BaseDocument) doc, caret.getDot());
0797: } else {
0798: ret = getIdentifierBlock(c, offset);
0799: }
0800: return ret;
0801: }
0802:
0803: /** Get the selection or identifier at the current caret position
0804: * @see getSelectionOrIdentifierBlock(JTextComponent, int)
0805: */
0806: public static int[] getSelectionOrIdentifierBlock(JTextComponent c) {
0807: try {
0808: return getSelectionOrIdentifierBlock(c, c.getCaret()
0809: .getDot());
0810: } catch (BadLocationException e) {
0811: return null;
0812: }
0813: }
0814:
0815: /** Get the identifier before the given position (ending at given offset)
0816: * or null if there's no identifier
0817: */
0818: public static String getIdentifierBefore(BaseDocument doc,
0819: int offset) throws BadLocationException {
0820: int wordStart = getWordStart(doc, offset);
0821: if (wordStart != -1) {
0822: String word = new String(doc.getChars(wordStart, offset
0823: - wordStart), 0, offset - wordStart);
0824: if (doc.getSyntaxSupport().isIdentifier(word)) {
0825: return word;
0826: }
0827: }
0828: return null;
0829: }
0830:
0831: /** Get the selection if there's any or get the identifier around
0832: * the position if there's no selection.
0833: */
0834: public static String getSelectionOrIdentifier(JTextComponent c,
0835: int offset) throws BadLocationException {
0836: Document doc = c.getDocument();
0837: Caret caret = c.getCaret();
0838: String ret;
0839: if (Utilities.isSelectionShowing(caret)) {
0840: ret = c.getSelectedText();
0841: if (ret != null)
0842: return ret;
0843: }
0844: if (doc instanceof BaseDocument) {
0845: ret = getIdentifier((BaseDocument) doc, caret.getDot());
0846: } else {
0847: ret = getWord(c, offset);
0848: }
0849: return ret;
0850: }
0851:
0852: /** Get the selection or identifier at the current caret position */
0853: public static String getSelectionOrIdentifier(JTextComponent c) {
0854: try {
0855: return getSelectionOrIdentifier(c, c.getCaret().getDot());
0856: } catch (BadLocationException e) {
0857: return null;
0858: }
0859: }
0860:
0861: /** Get the word at given position.
0862: */
0863: public static String getWord(BaseDocument doc, int offset)
0864: throws BadLocationException {
0865: int wordEnd = getWordEnd(doc, offset);
0866: if (wordEnd != -1) {
0867: return new String(doc.getChars(offset, wordEnd - offset),
0868: 0, wordEnd - offset);
0869: }
0870: return null;
0871: }
0872:
0873: /** Change the case for specified part of document
0874: * @param doc document to operate on
0875: * @param offset position in document determines the changed area begining
0876: * @param len number of chars to change
0877: * @param type either CASE_CAPITAL, CASE_SMALL or CASE_SWITCH
0878: */
0879: public static boolean changeCase(BaseDocument doc, int offset,
0880: int len, int type) throws BadLocationException {
0881: char[] orig = doc.getChars(offset, len);
0882: char[] changed = (char[]) orig.clone();
0883: for (int i = 0; i < orig.length; i++) {
0884: switch (type) {
0885: case CASE_UPPER:
0886: changed[i] = Character.toUpperCase(orig[i]);
0887: break;
0888: case CASE_LOWER:
0889: changed[i] = Character.toLowerCase(orig[i]);
0890: break;
0891: case CASE_SWITCH:
0892: if (Character.isUpperCase(orig[i])) {
0893: changed[i] = Character.toLowerCase(orig[i]);
0894: } else if (Character.isLowerCase(orig[i])) {
0895: changed[i] = Character.toUpperCase(orig[i]);
0896: }
0897: break;
0898: }
0899: }
0900: // check chars for difference and possibly change document
0901: for (int i = 0; i < orig.length; i++) {
0902: if (orig[i] != changed[i]) {
0903: doc.atomicLock();
0904: try {
0905: doc.remove(offset, orig.length);
0906: doc.insertString(offset, new String(changed), null);
0907: } finally {
0908: doc.atomicUnlock();
0909: }
0910: return true; // changed
0911: }
0912: }
0913: return false;
0914: }
0915:
0916: /** Tests whether the line contains no characters except the ending new-line.
0917: * @param doc document to operate on
0918: * @param offset position anywhere on the tested line
0919: * @return whether the line is empty or not
0920: */
0921: public static boolean isRowEmpty(BaseDocument doc, int offset)
0922: throws BadLocationException {
0923: Element lineElement = doc.getParagraphElement(offset);
0924: return (lineElement.getStartOffset() + 1 == lineElement
0925: .getEndOffset());
0926: }
0927:
0928: public static int getFirstNonEmptyRow(BaseDocument doc, int offset,
0929: boolean downDir) throws BadLocationException {
0930: while (offset != -1 && isRowEmpty(doc, offset)) {
0931: offset = getRowStart(doc, offset, downDir ? +1 : -1);
0932: }
0933: return offset;
0934: }
0935:
0936: /** Tests whether the line contains only whitespace characters.
0937: * @param doc document to operate on
0938: * @param offset position anywhere on the tested line
0939: * @return whether the line is empty or not
0940: */
0941: public static boolean isRowWhite(BaseDocument doc, int offset)
0942: throws BadLocationException {
0943: Element lineElement = doc.getParagraphElement(offset);
0944: offset = doc.find(new FinderFactory.NonWhiteFwdFinder(doc),
0945: lineElement.getStartOffset(), lineElement
0946: .getEndOffset() - 1);
0947: return (offset == -1);
0948: }
0949:
0950: public static int getFirstNonWhiteRow(BaseDocument doc, int offset,
0951: boolean downDir) throws BadLocationException {
0952: if (isRowWhite(doc, offset)) {
0953: if (downDir) { // search down for non-white line
0954: offset = getFirstNonWhiteFwd(doc, offset);
0955: } else { // search up for non-white line
0956: offset = getFirstNonWhiteBwd(doc, offset);
0957: }
0958: }
0959: return offset;
0960: }
0961:
0962: /**
0963: * Reformat a block of code.
0964: * <br/>
0965: * The document should not be locked prior entering of this method.
0966: * <br/>
0967: * The method should be called from AWT thread so that the given offsets are more stable.
0968: *
0969: * @param doc document to work with
0970: * @param startOffset offset at which the formatting starts
0971: * @param endOffset offset at which the formatting ends
0972: * @return length of the reformatted code
0973: */
0974: public static int reformat(BaseDocument doc, int startOffset,
0975: int endOffset) throws BadLocationException {
0976: Formatter formatter = doc.getFormatter();
0977: formatter.reformatLock();
0978: doc.atomicLock();
0979: try {
0980: return formatter.reformat(doc, startOffset, endOffset);
0981: } finally {
0982: doc.atomicUnlock();
0983: formatter.reformatUnlock();
0984: }
0985: }
0986:
0987: /**
0988: * Reformat the line around the given position.
0989: * <br/>
0990: * The document should not be locked prior entering of this method.
0991: * <br/>
0992: * The method should be called from AWT thread so that the given offsets are more stable.
0993: *
0994: */
0995: public static void reformatLine(BaseDocument doc, int pos)
0996: throws BadLocationException {
0997: int lineStart = getRowStart(doc, pos);
0998: int lineEnd = getRowEnd(doc, pos);
0999: reformat(doc, lineStart, lineEnd);
1000: }
1001:
1002: /** Count of rows between these two positions */
1003: public static int getRowCount(BaseDocument doc, int startPos,
1004: int endPos) throws BadLocationException {
1005: if (startPos > endPos) {
1006: return 0;
1007: }
1008: Element lineRoot = doc.getParagraphElement(0)
1009: .getParentElement();
1010: return lineRoot.getElementIndex(endPos)
1011: - lineRoot.getElementIndex(startPos) + 1;
1012: }
1013:
1014: /** Get the total count of lines in the document */
1015: public static int getRowCount(BaseDocument doc) {
1016: return doc.getParagraphElement(0).getParentElement()
1017: .getElementCount();
1018: }
1019:
1020: /** @deprecated
1021: * @see Formatter.insertTabString()
1022: */
1023: public static String getTabInsertString(BaseDocument doc, int offset)
1024: throws BadLocationException {
1025: int col = getVisualColumn(doc, offset);
1026: Formatter f = doc.getFormatter();
1027: boolean expandTabs = f.expandTabs();
1028: if (expandTabs) {
1029: int spacesPerTab = f.getSpacesPerTab();
1030: int len = (col + spacesPerTab) / spacesPerTab
1031: * spacesPerTab - col;
1032: return new String(Analyzer.getSpacesBuffer(len), 0, len);
1033: } else { // insert pure tab
1034: return "\t"; // NOI18N
1035: }
1036: }
1037:
1038: /** Get the visual column corresponding to the position after pressing
1039: * the TAB key.
1040: * @param doc document to work with
1041: * @param offset position at which the TAB was pressed
1042: */
1043: public static int getNextTabColumn(BaseDocument doc, int offset)
1044: throws BadLocationException {
1045: int col = getVisualColumn(doc, offset);
1046: int tabSize = doc.getFormatter().getSpacesPerTab();
1047: return (col + tabSize) / tabSize * tabSize;
1048: }
1049:
1050: public static void setStatusText(JTextComponent c, String text) {
1051: EditorUI eui = getEditorUI(c);
1052: StatusBar sb = eui == null ? null : eui.getStatusBar();
1053: if (sb != null) {
1054: sb.setText(StatusBar.CELL_MAIN, text);
1055: }
1056: }
1057:
1058: public static void setStatusText(JTextComponent c, String text,
1059: Coloring extraColoring) {
1060: EditorUI eui = getEditorUI(c);
1061: StatusBar sb = eui == null ? null : eui.getStatusBar();
1062: if (sb != null) {
1063: sb.setText(StatusBar.CELL_MAIN, text, extraColoring);
1064: }
1065: }
1066:
1067: public static void setStatusBoldText(JTextComponent c, String text) {
1068: EditorUI eui = getEditorUI(c);
1069: StatusBar sb = eui == null ? null : eui.getStatusBar();
1070: if (sb != null) {
1071: sb.setBoldText(StatusBar.CELL_MAIN, text);
1072: }
1073: }
1074:
1075: public static String getStatusText(JTextComponent c) {
1076: EditorUI eui = getEditorUI(c);
1077: StatusBar sb = eui == null ? null : eui.getStatusBar();
1078: return (sb != null) ? sb.getText(StatusBar.CELL_MAIN) : null;
1079: }
1080:
1081: public static void clearStatusText(JTextComponent c) {
1082: setStatusText(c, ""); // NOI18N
1083: }
1084:
1085: public static void insertMark(BaseDocument doc, Mark mark,
1086: int offset) throws BadLocationException,
1087: InvalidMarkException {
1088: mark.insert(doc, offset);
1089: }
1090:
1091: public static void moveMark(BaseDocument doc, Mark mark,
1092: int newOffset) throws BadLocationException,
1093: InvalidMarkException {
1094: mark.move(doc, newOffset);
1095: }
1096:
1097: public static void returnFocus() {
1098: JTextComponent c = getLastActiveComponent();
1099: if (c != null) {
1100: requestFocus(c);
1101: }
1102: }
1103:
1104: public static void requestFocus(JTextComponent c) {
1105: if (c != null) {
1106: if (!ImplementationProvider.getDefault().activateComponent(
1107: c)) {
1108: Frame f = EditorUI.getParentFrame(c);
1109: if (f != null) {
1110: f.requestFocus();
1111: }
1112: c.requestFocus();
1113: }
1114: }
1115: }
1116:
1117: public static void runInEventDispatchThread(Runnable r) {
1118: if (SwingUtilities.isEventDispatchThread()) {
1119: r.run();
1120: } else {
1121: SwingUtilities.invokeLater(r);
1122: }
1123: }
1124:
1125: public static String debugPosition(BaseDocument doc, int offset) {
1126: String ret;
1127:
1128: if (offset >= 0) {
1129: try {
1130: int line = getLineOffset(doc, offset) + 1;
1131: int col = getVisualColumn(doc, offset) + 1;
1132: ret = String.valueOf(line) + ":" + String.valueOf(col); // NOI18N
1133: } catch (BadLocationException e) {
1134: ret = NbBundle.getBundle(BaseKit.class).getString(
1135: WRONG_POSITION_LOCALE)
1136: + ' ' + offset + " > " + doc.getLength(); // NOI18N
1137: }
1138: } else {
1139: ret = String.valueOf(offset);
1140: }
1141:
1142: return ret;
1143: }
1144:
1145: public static String offsetToLineColumnString(BaseDocument doc,
1146: int offset) {
1147: return String.valueOf(offset) + "["
1148: + debugPosition(doc, offset) + "]"; // NOI18N
1149: }
1150:
1151: /** Display the identity of the document together with the title property
1152: * and stream-description property.
1153: */
1154: public static String debugDocument(Document doc) {
1155: return "<"
1156: + System.identityHashCode(doc) // NOI18N
1157: + ", title='" + doc.getProperty(Document.TitleProperty)
1158: + "', stream='"
1159: + doc.getProperty(Document.StreamDescriptionProperty)
1160: + ", " + doc.toString() + ">"; // NOI18N
1161: }
1162:
1163: public static void performAction(Action a, ActionEvent evt,
1164: JTextComponent target) {
1165: if (a instanceof BaseAction) {
1166: ((BaseAction) a).actionPerformed(evt, target);
1167: } else {
1168: a.actionPerformed(evt);
1169: }
1170: }
1171:
1172: /** Returns last activated component. If the component was closed,
1173: * then previous component is returned */
1174: public static JTextComponent getLastActiveComponent() {
1175: return Registry.getMostActiveComponent();
1176: }
1177:
1178: /**
1179: * Fetches the text component that currently has focus. It delegates to
1180: * TextAction.getFocusedComponent().
1181: * @return the component
1182: */
1183: public static JTextComponent getFocusedComponent() {
1184: /** Fake action for getting the focused component */
1185: class FocusedComponentAction extends TextAction {
1186:
1187: FocusedComponentAction() {
1188: super ("focused-component"); // NOI18N
1189: }
1190:
1191: /** adding this method because of protected final getFocusedComponent */
1192: JTextComponent getFocusedComponent2() {
1193: return getFocusedComponent();
1194: }
1195:
1196: public void actionPerformed(ActionEvent evt) {
1197: }
1198: }
1199: ;
1200:
1201: if (focusedComponentAction == null) {
1202: focusedComponentAction = new FocusedComponentAction();
1203: }
1204:
1205: return ((FocusedComponentAction) focusedComponentAction)
1206: .getFocusedComponent2();
1207: }
1208:
1209: /** Helper method to obtain instance of EditorUI (extended UI)
1210: * from the existing JTextComponent.
1211: * It doesn't require any document locking.
1212: * @param target JTextComponent for which the extended UI should be obtained
1213: * @return extended ui instance or null if the component.getUI()
1214: * does not return BaseTextUI instance.
1215: */
1216: public static EditorUI getEditorUI(JTextComponent target) {
1217: TextUI ui = target.getUI();
1218: return (ui instanceof BaseTextUI) ? ((BaseTextUI) ui)
1219: .getEditorUI() : null;
1220: }
1221:
1222: /** Helper method to obtain instance of editor kit from existing JTextComponent.
1223: * If the kit of the component is not an instance
1224: * of the <tt>org.netbeans.editor.BaseKit</tt> the method returns null.
1225: * The method doesn't require any document locking.
1226: * @param target JTextComponent for which the editor kit should be obtained
1227: * @return BaseKit instance or null
1228: */
1229: public static BaseKit getKit(JTextComponent target) {
1230: if (target == null)
1231: return null; // #19574
1232: EditorKit ekit = target.getUI().getEditorKit(target);
1233: return (ekit instanceof BaseKit) ? (BaseKit) ekit : null;
1234: }
1235:
1236: /**
1237: * Gets the class of an editor kit installed in <code>JTextComponent</code>.
1238: * The method doesn't require any document locking.
1239: *
1240: * <div class="nonnormative">
1241: * <p>WARNING: The implementation class of an editor kit is most likely
1242: * not what you want. Please see {@link BaseKit#getKit(Class)} for more
1243: * details.
1244: *
1245: * <p>Unfortunatelly, there are still places in editor libraries where
1246: * an editor kit class is required.
1247: * One of them is the editor settings infrastructure built around the
1248: * <code>Settings</code> class. So, if you really need it go ahead and use it,
1249: * there is nothing wrong with the method itself.
1250: * </div>
1251: *
1252: * @param target The <code>JTextComponent</code> to get the kit class for.
1253: * Can be <code>null</code>.
1254: * @return The implementation class of the editor kit or <code>null</code>
1255: * if the <code>target</code> is <code>null</code>.
1256: */
1257: public static Class getKitClass(JTextComponent target) {
1258: EditorKit kit = (target != null) ? target.getUI().getEditorKit(
1259: target) : null;
1260: return (kit != null) ? kit.getClass() : null;
1261: }
1262:
1263: /** Helper method to obtain instance of BaseDocument from JTextComponent.
1264: * If the document of the component is not an instance
1265: * of the <tt>org.netbeans.editor.BaseDocument</tt> the method returns null.
1266: * The method doesn't require any document locking.
1267: * @param target JTextComponent for which the document should be obtained
1268: * @return BaseDocument instance or null
1269: */
1270: public static BaseDocument getDocument(JTextComponent target) {
1271: Document doc = target.getDocument();
1272: return (doc instanceof BaseDocument) ? (BaseDocument) doc
1273: : null;
1274: }
1275:
1276: /** Get the syntax-support class that belongs to the document of the given
1277: * component. Besides using directly this method, the <tt>SyntaxSupport</tt>
1278: * can be obtained by calling <tt>doc.getSyntaxSupport()</tt>.
1279: * The method can return null in case the document is not
1280: * an instance of the BaseDocument.
1281: * The method doesn't require any document locking.
1282: * @param target JTextComponent for which the syntax-support should be obtained
1283: * @return SyntaxSupport instance or null
1284: */
1285: public static SyntaxSupport getSyntaxSupport(JTextComponent target) {
1286: Document doc = target.getDocument();
1287: return (doc instanceof BaseDocument) ? ((BaseDocument) doc)
1288: .getSyntaxSupport() : null;
1289: }
1290:
1291: /**
1292: * Get first view in the hierarchy that is an instance of the given class.
1293: * It allows to skip various wrapper-views around the doc-view that holds
1294: * the child views for the lines.
1295: *
1296: * @param component component from which the root view is fetched.
1297: * @param rootViewClass class of the view to return.
1298: * @return view being instance of the requested class or null if there
1299: * is not one.
1300: */
1301: public static View getRootView(JTextComponent component,
1302: Class rootViewClass) {
1303: View view = null;
1304: TextUI textUI = component.getUI();
1305: if (textUI != null) {
1306: view = textUI.getRootView(component);
1307: while (view != null && !rootViewClass.isInstance(view)
1308: && view.getViewCount() == 1 // must be wrapper view
1309: ) {
1310: view = view.getView(0); // get the only child
1311: }
1312: }
1313:
1314: return view;
1315: }
1316:
1317: /**
1318: * Get the view that covers the whole area of the document
1319: * and holds a child view for each line in the document
1320: * (or for a bunch of lines in case there is a code folding present).
1321: */
1322: public static View getDocumentView(JTextComponent component) {
1323: return getRootView(component, DrawEngineDocView.class);
1324: }
1325:
1326: /**
1327: * Creates nice textual description of sequence of KeyStrokes. Usable for
1328: * displaying MultiKeyBindings. The keyStrokes are delimited by space.
1329: * @param Array of KeyStrokes representing the actual sequence.
1330: * @return String describing the KeyStroke sequence.
1331: */
1332: public static String keySequenceToString(KeyStroke[] seq) {
1333: StringBuffer sb = new StringBuffer();
1334: for (int i = 0; i < seq.length; i++) {
1335: if (i > 0)
1336: sb.append(' '); // NOI18N
1337: sb.append(keyStrokeToString(seq[i]));
1338: }
1339: return sb.toString();
1340: }
1341:
1342: /**
1343: * Creates nice textual representation of KeyStroke.
1344: * Modifiers and an actual key label are concated by plus signs
1345: * @param the KeyStroke to get description of
1346: * @return String describing the KeyStroke
1347: */
1348: public static String keyStrokeToString(KeyStroke stroke) {
1349: String modifText = KeyEvent.getKeyModifiersText(stroke
1350: .getModifiers());
1351: String keyText = (stroke.getKeyCode() == KeyEvent.VK_UNDEFINED) ? String
1352: .valueOf(stroke.getKeyChar())
1353: : getKeyText(stroke.getKeyCode());
1354: if (modifText.length() > 0)
1355: return modifText + '+' + keyText;
1356: else
1357: return keyText;
1358: }
1359:
1360: /** @return slight modification of what KeyEvent.getKeyText() returns.
1361: * The numpad Left, Right, Down, Up get extra result.
1362: */
1363: private static String getKeyText(int keyCode) {
1364: String ret = KeyEvent.getKeyText(keyCode);
1365: if (ret != null) {
1366: switch (keyCode) {
1367: case KeyEvent.VK_KP_DOWN:
1368: ret = prefixNumpad(ret, KeyEvent.VK_DOWN);
1369: break;
1370: case KeyEvent.VK_KP_LEFT:
1371: ret = prefixNumpad(ret, KeyEvent.VK_LEFT);
1372: break;
1373: case KeyEvent.VK_KP_RIGHT:
1374: ret = prefixNumpad(ret, KeyEvent.VK_RIGHT);
1375: break;
1376: case KeyEvent.VK_KP_UP:
1377: ret = prefixNumpad(ret, KeyEvent.VK_UP);
1378: break;
1379: }
1380: }
1381: return ret;
1382: }
1383:
1384: private static String prefixNumpad(String key, int testKeyCode) {
1385: if (key.equals(KeyEvent.getKeyText(testKeyCode))) {
1386: key = NbBundle.getBundle(BaseKit.class).getString(
1387: "key-prefix-numpad")
1388: + key;
1389: }
1390: return key;
1391: }
1392:
1393: private static void checkOffsetValid(Document doc, int offset)
1394: throws BadLocationException {
1395: checkOffsetValid(offset, doc.getLength());
1396: }
1397:
1398: private static void checkOffsetValid(int offset, int limitOffset)
1399: throws BadLocationException {
1400: if (offset < 0 || offset > limitOffset) {
1401: throw new BadLocationException("Invalid offset=" + offset // NOI18N
1402: + " not within <0, " + limitOffset + ">", // NOI18N
1403: offset);
1404: }
1405: }
1406:
1407: /**
1408: * Writes a <code>Throwable</code> to a log file.
1409: *
1410: * <p class="nonnormative">The method is internally using
1411: * <code>org.netbeans.editor</code> logger and <code>Level.INFO</code>.
1412: *
1413: * @param t The exception that will be logged.
1414: * @deprecated Use java.util.logging.Logger instead with the proper name,
1415: * log level and message.
1416: */
1417: public static void annotateLoggable(Throwable t) {
1418: Logger.getLogger("org.netbeans.editor")
1419: .log(Level.INFO, null, t); //NOI18N
1420: }
1421:
1422: /**
1423: * Check whether caret's selection is visible and there is at least
1424: * one selected character showing.
1425: *
1426: * @param caret non-null caret.
1427: * @return true if selection is visible and there is at least one selected character.
1428: */
1429: public static boolean isSelectionShowing(Caret caret) {
1430: return caret.isSelectionVisible()
1431: && caret.getDot() != caret.getMark();
1432: }
1433:
1434: /**
1435: * @see isSelectionShowing(Caret)
1436: * @param component non-null component.
1437: * @return if selection is showing for component's caret.
1438: */
1439: public static boolean isSelectionShowing(JTextComponent component) {
1440: Caret caret = component.getCaret();
1441: return (caret != null) && isSelectionShowing(caret);
1442: }
1443:
1444: /**
1445: * Gets the mime type of a document. If the mime type can't be determined
1446: * this method will return <code>null</code>. This method should work reliably
1447: * for Netbeans documents that have their mime type stored in a special
1448: * property. For any other documents it will probably just return <code>null</code>.
1449: *
1450: * @param doc The document to get the mime type for.
1451: *
1452: * @return The mime type of the document or <code>null</code>.
1453: * @see NbEditorDocument#MIME_TYPE_PROP
1454: */
1455: /* package */static String getMimeType(Document doc) {
1456: return (String) doc.getProperty("mimeType"); //NOI18N
1457: }
1458:
1459: /**
1460: * Gets the mime type of a document in <code>JTextComponent</code>. If
1461: * the mime type can't be determined this method will return <code>null</code>.
1462: * It tries to determine the document's mime type first and if that does not
1463: * work it uses mime type from the <code>EditorKit</code> attached to the
1464: * component.
1465: *
1466: * @param component The component to get the mime type for.
1467: *
1468: * @return The mime type of a document opened in the component or <code>null</code>.
1469: */
1470: /* package */static String getMimeType(JTextComponent component) {
1471: Document doc = component.getDocument();
1472: String mimeType = getMimeType(doc);
1473: if (mimeType == null) {
1474: EditorKit kit = component.getUI().getEditorKit(component);
1475: if (kit != null) {
1476: mimeType = kit.getContentType();
1477: }
1478: }
1479: return mimeType;
1480: }
1481:
1482: }
|