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-2007 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: package org.netbeans.jellytools;
0042:
0043: import java.awt.Component;
0044: import java.awt.Container;
0045: import java.awt.event.KeyEvent;
0046: import java.lang.reflect.Field;
0047: import java.lang.reflect.Method;
0048: import java.util.ArrayList;
0049: import java.util.Iterator;
0050: import javax.swing.JComponent;
0051: import javax.swing.JToolBar;
0052: import javax.swing.text.AbstractDocument;
0053: import javax.swing.text.BadLocationException;
0054: import javax.swing.text.Document;
0055: import javax.swing.text.JTextComponent;
0056: import javax.swing.text.StyledDocument;
0057: import org.netbeans.api.editor.fold.Fold;
0058: import org.netbeans.api.editor.fold.FoldHierarchy;
0059: import org.netbeans.api.editor.fold.FoldUtilities;
0060: import org.netbeans.core.windows.ModeImpl;
0061: import org.netbeans.core.windows.WindowManagerImpl;
0062: import org.netbeans.jemmy.ComponentChooser;
0063: import org.netbeans.jemmy.ComponentSearcher;
0064: import org.netbeans.jemmy.JemmyException;
0065: import org.netbeans.jemmy.QueueTool;
0066: import org.netbeans.jemmy.Timeouts;
0067: import org.netbeans.jemmy.Waitable;
0068: import org.netbeans.jemmy.Waiter;
0069: import org.netbeans.jemmy.operators.AbstractButtonOperator;
0070: import org.netbeans.jemmy.operators.ContainerOperator;
0071: import org.netbeans.jemmy.operators.JComboBoxOperator;
0072: import org.netbeans.jemmy.operators.JEditorPaneOperator;
0073: import org.netbeans.jemmy.operators.JLabelOperator;
0074: import org.openide.cookies.LineCookie;
0075: import org.openide.loaders.DataObject;
0076: import org.openide.text.Annotation;
0077: import org.openide.text.CloneableEditor;
0078: import org.openide.text.Line;
0079: import org.openide.text.NbDocument;
0080: import org.openide.text.Line.Set;
0081: import org.openide.windows.TopComponent;
0082:
0083: /**
0084: * Handle an editor top component in NetBeans IDE. It enables to get, select, insert or
0085: * delete text, move caret, work with annotations and with toolbar buttons.
0086: * Majority of operations is done by JEditorPane API calls. If you want
0087: * to do operations by key navigation, use methods of JEditorPaneOperator
0088: * instance by {@link #txtEditorPane()}. For example, call
0089: * <code>txtEditorPane().changeCaretPosition(int)</code> instead of
0090: * <code>{@link #setCaretPosition(int)}</code>.
0091: * <p>
0092: * Usage:<br>
0093: * <pre>
0094: EditorOperator eo = new EditorOperator(filename);
0095: eo.setCaretPositionToLine(10);
0096: eo.insert("// My new comment\n");
0097: eo.select("// My new comment");
0098: eo.deleteLine(10);
0099: eo.getToolbarButton("Toggle Bookmark").push();
0100: // discard changes and close
0101: eo.close(false);
0102: // save changes and close
0103: eo.close(true);
0104: // try to close all opened documents (confirmation dialog may appear)
0105: eo.closeAllDocuments();
0106: // close all opened documents and discard all changes
0107: eo.closeDiscardAll();
0108: * </pre>
0109: *
0110: * @author Jiri.Skrivanek@sun.com
0111: */
0112: public class EditorOperator extends TopComponentOperator {
0113:
0114: private static int WAIT_TIME = 60000;
0115:
0116: static {
0117: Timeouts.initDefault("EditorOperator.WaitModifiedTimeout",
0118: WAIT_TIME);
0119: }
0120:
0121: /** Components operators. */
0122: private JEditorPaneOperator _txtEditorPane;
0123: private JLabelOperator _lblRowColumn;
0124: private JLabelOperator _lblInputMode;
0125: private JLabelOperator _lblStatusBar;
0126:
0127: /** Waits for the first opened editor with given name.
0128: * If not active, it is activated.
0129: * @param filename name of file showed in the editor (it used to be label of tab)
0130: */
0131: public EditorOperator(String filename) {
0132: this (filename, 0);
0133: }
0134:
0135: /** Waits for index-th opened editor with given name.
0136: * If not active, it is activated.
0137: * @param filename name of file showed in the editor (it used to be label of tab)
0138: * @param index index of editor to be find
0139: */
0140: public EditorOperator(String filename, int index) {
0141: super (waitTopComponent(null, filename, index,
0142: new EditorSubchooser()));
0143: this .requestFocus(); // needed for pushKey() methods
0144: }
0145:
0146: /** Waits for first open editor with given name in specified container.
0147: * If not active, it is activated.
0148: * @param contOper container where to search
0149: * @param filename name of file showed in the editor (it used to be label of tab)
0150: */
0151: public EditorOperator(ContainerOperator contOper, String filename) {
0152: this (contOper, filename, 0);
0153: }
0154:
0155: /** Creates new operator instance for given component.
0156: * It is used in FormDesignerOperator.
0157: * @param editorComponent instance of editor
0158: */
0159: public EditorOperator(JComponent editorComponent) {
0160: super (editorComponent);
0161: }
0162:
0163: /** Waits for index-th opened editor with given name in specified container.
0164: * If not active, it is activated.
0165: * @param contOper container where to search
0166: * @param filename name of file showed in the editor (it used to be label of tab)
0167: * @param index index of editor to be find
0168: */
0169: public EditorOperator(ContainerOperator contOper, String filename,
0170: int index) {
0171: super (waitTopComponent(contOper, filename, index,
0172: new EditorSubchooser()));
0173: copyEnvironment(contOper);
0174: this .requestFocus(); // needed for pushKey() methods
0175: }
0176:
0177: /** Closes all opened documents and discards all changes by IDE API calls.
0178: * It works also if no file is modified, so it is a safe way how to close
0179: * documents and no block further execution.
0180: */
0181: public static void closeDiscardAll() {
0182: // run in dispatch thread
0183: ModeImpl mode = (ModeImpl) new QueueTool()
0184: .invokeSmoothly(new QueueTool.QueueAction("findMode") { // NOI18N
0185: public Object launch() {
0186: return WindowManagerImpl.getInstance()
0187: .findMode("editor"); //NOI18N
0188: }
0189: });
0190: Iterator iter = mode.getOpenedTopComponents().iterator();
0191: while (iter.hasNext()) {
0192: EditorOperator.close(iter.next(), false);
0193: }
0194: }
0195:
0196: /** Closes this editor by IDE API call and depending on given flag
0197: * it saves or discards changes.
0198: * @param save true - save changes, false - discard changes
0199: */
0200: public void close(boolean save) {
0201: if (save) {
0202: super .save();
0203: close();
0204: } else {
0205: closeDiscard();
0206: }
0207: }
0208:
0209: /** Closes top component. It saves it or not depending on given flag.
0210: * Other top components like VCS outputs are closed directly.
0211: * It is package private because it is also used by EditorWindowOperator.
0212: */
0213: static void close(final Object tc, boolean save) {
0214: // firstly test whether it is still opened (run in dispatch thread)
0215: Boolean isOpened = (Boolean) new QueueTool()
0216: .invokeSmoothly(new QueueTool.QueueAction("isOpened") { // NOI18N
0217: public Object launch() {
0218: return new Boolean(((TopComponent) tc)
0219: .isOpened());
0220: }
0221: });
0222: if (isOpened.booleanValue()) {
0223: // it is still opened => try to close (otherwise do nothing)
0224: TopComponentOperator tco = new TopComponentOperator(
0225: (TopComponent) tc);
0226: if (save) {
0227: tco.save();
0228: tco.close();
0229: } else {
0230: tco.closeDiscard();
0231: }
0232: }
0233: }
0234:
0235: /** Returns operator of currently shown editor pane.
0236: * @return JTabbedPaneOperator instance of editor pane
0237: */
0238: public JEditorPaneOperator txtEditorPane() {
0239: if (_txtEditorPane == null) {
0240: _txtEditorPane = new JEditorPaneOperator(this );
0241: }
0242: return _txtEditorPane;
0243: }
0244:
0245: /** Returns operator of label showing current row and column at the left
0246: * corner of the Source Editor window.
0247: * @return JLabelOperator instance of row:column label
0248: */
0249: public JLabelOperator lblRowColumn() {
0250: if (_lblRowColumn == null) {
0251: _lblRowColumn = new JLabelOperator(this , 0);
0252: }
0253: return _lblRowColumn;
0254: }
0255:
0256: /** Returns operator of label showing current input mode (INS/OVR -
0257: * insert/overwrite).
0258: * @return JLabelOperator instance of input mode label
0259: */
0260: public JLabelOperator lblInputMode() {
0261: if (_lblInputMode == null) {
0262: _lblInputMode = new JLabelOperator(this , 1);
0263: }
0264: return _lblInputMode;
0265: }
0266:
0267: /** Returns operator of status bar at the bottom of the Source Editor.
0268: * @return JLabelOperator instance of status bar
0269: */
0270: public JLabelOperator lblStatusBar() {
0271: if (_lblStatusBar == null) {
0272: _lblStatusBar = new JLabelOperator(this , 2);
0273: }
0274: return _lblStatusBar;
0275: }
0276:
0277: /************** Get, select, delete, insert text ************************/
0278:
0279: /** Gets text from the currently opened Editor window.
0280: * @return a string representing whole content of the Editor window
0281: * (including new line characters)
0282: */
0283: public String getText() {
0284: return txtEditorPane().getText();
0285: }
0286:
0287: /** Gets text from specified line.
0288: * It might fail on the last line of a file because of issues
0289: * http://www.netbeans.org/issues/show_bug.cgi?id=24434 and
0290: * http://www.netbeans.org/issues/show_bug.cgi?id=24433.
0291: * @param lineNumber number of line (beggining from 1)
0292: * @return a string representing content of the line including new line
0293: * character
0294: */
0295: public String getText(int lineNumber) {
0296: return ((Line) getLine(lineNumber)).getText();
0297: }
0298:
0299: /** Returns instance of org.openide.text.Line for given line number.
0300: * @param lineNumber number of line (beggining at 1)
0301: * @return org.openide.text.Line instance
0302: */
0303: private Object getLine(int lineNumber) {
0304: Document doc = txtEditorPane().getDocument();
0305: DataObject od = (DataObject) doc
0306: .getProperty(Document.StreamDescriptionProperty);
0307: Set set = ((LineCookie) od.getCookie(LineCookie.class))
0308: .getLineSet();
0309: try {
0310: return set.getCurrent(lineNumber - 1);
0311: } catch (IndexOutOfBoundsException e) {
0312: throw new JemmyException("Index must be > 0", e);
0313: }
0314: }
0315:
0316: /** Checks if editor window contains text specified as parameter text.
0317: * @param text text to compare to
0318: * @return true if text was found, false otherwise
0319: */
0320: public boolean contains(String text) {
0321: return getText().indexOf(text) != -1;
0322: }
0323:
0324: /** Selects whole line specified by its number. Caret will stand at the
0325: * next available line.
0326: * @param lineNumber number of line (beggining from 1)
0327: */
0328: public void select(int lineNumber) {
0329: int lineOffset = getLineOffset(lineNumber);
0330: setCaretPosition(lineOffset);
0331: txtEditorPane().moveCaretPosition(
0332: lineOffset + getText(lineNumber).length());
0333: }
0334:
0335: /** Selects text between line1 and line2 (both are included). Caret will
0336: * stand behing the selection (at the next line if available).
0337: * @param line1 number of line where to begin (beggining from 1)
0338: * @param line2 number of line where to finish (beggining from 1)
0339: */
0340: public void select(int line1, int line2) {
0341: setCaretPosition(getLineOffset(line1));
0342: txtEditorPane().moveCaretPosition(
0343: getLineOffset(line2) + getText(line2).length());
0344: }
0345:
0346: /** Selects text in specified line on position defined by column1
0347: * and column2 (both are included). Caret will stand at the end of
0348: * the selection.
0349: * @param lineNumber number of line (beggining from 1)
0350: * @param column1 column position where selection starts (beggining from 1)
0351: * @param column2 column position where selection ends (beggining from 1) */
0352: public void select(int lineNumber, int column1, int column2) {
0353: int lineOffset = getLineOffset(lineNumber);
0354: setCaretPosition(lineOffset + column1 - 1);
0355: txtEditorPane().moveCaretPosition(lineOffset + column2);
0356: }
0357:
0358: /** Selects index-th occurence of given text.
0359: * @param text text to be selected
0360: * @param index index of text occurence (first occurence has index 0)
0361: * @see #select(String)
0362: */
0363: public void select(String text, int index) {
0364: int position = txtEditorPane().getPositionByText(text, index);
0365: if (position == -1) {
0366: throw new JemmyException(index + "-th occurence of \""
0367: + text + "\" not found.");
0368: }
0369: setCaretPosition(position);
0370: txtEditorPane().moveCaretPosition(position + text.length());
0371: }
0372:
0373: /** Selects first occurence of given text.
0374: * @param text text to be selected
0375: * @see #select(String, int)
0376: */
0377: public void select(String text) {
0378: select(text, 0);
0379: }
0380:
0381: /** Replaces first occurence of oldText by newText.
0382: * @param oldText text to be replaced
0383: * @param newText text to write instead
0384: */
0385: public void replace(String oldText, String newText) {
0386: replace(oldText, newText, 0);
0387: }
0388:
0389: /** Replaced index-th occurence of oldText by newText.
0390: * @param oldText text to be replaced
0391: * @param newText text to write instead
0392: * @param index index of oldText occurence (first occurence has index 0)
0393: */
0394: public void replace(String oldText, String newText, int index) {
0395: select(oldText, index);
0396: txtEditorPane().replaceSelection(newText);
0397: }
0398:
0399: /** Inserts text to current position. Caret will stand at the end
0400: * of newly inserted text.
0401: * @param text a string to be inserted
0402: */
0403: public void insert(final String text) {
0404: final int offset = txtEditorPane().getCaretPosition();
0405: runMapping(new MapVoidAction("insertString") {
0406: public void map() {
0407: try {
0408: txtEditorPane().getDocument().insertString(offset,
0409: text, null);
0410: } catch (BadLocationException e) {
0411: throw new JemmyException("Cannot insert \"" + text
0412: + "\" to position " + offset + ".", e);
0413: }
0414: }
0415: });
0416: }
0417:
0418: /** Inserts text to position specified by line number and column.
0419: * Caret will stand at the end of newly inserted text.
0420: * @param text a string to be inserted
0421: * @param lineNumber number of line (beggining from 1)
0422: * @param column column position (beggining from 1)
0423: */
0424: public void insert(String text, int lineNumber, int column) {
0425: setCaretPosition(lineNumber, column);
0426: insert(text);
0427: }
0428:
0429: /** Deletes given number of characters from specified possition.
0430: * Position of caret will not change.
0431: * @param offset position inside document (0 means the beginning)
0432: * @param length number of characters to be deleted
0433: */
0434: public void delete(final int offset, final int length) {
0435: // run in dispatch thread
0436: runMapping(new MapVoidAction("remove") { // NOI18N
0437: public void map() {
0438: try {
0439: txtEditorPane().getDocument()
0440: .remove(offset, length);
0441: } catch (BadLocationException e) {
0442: throw new JemmyException("Cannot delete " + length
0443: + " characters from position " + offset
0444: + ".", e);
0445: }
0446: }
0447: });
0448: }
0449:
0450: /** Deletes given number of characters from current caret possition.
0451: * Position of caret will not change.
0452: * @param length number of characters to be deleted
0453: */
0454: public void delete(int length) {
0455: delete(txtEditorPane().getCaretPosition(), length);
0456: }
0457:
0458: /** Delete specified line.
0459: * Position of caret will not change.
0460: * @param line number of line (beggining from 1)
0461: */
0462: public void deleteLine(int line) {
0463: delete(getLineOffset(line), getText(line).length());
0464: }
0465:
0466: /** Deletes characters between column1 and column2 (both are included)
0467: * on the specified line.
0468: * @param lineNumber number of line (beggining from 1)
0469: * @param column1 column position where to start deleting (beggining from 1)
0470: * @param column2 column position where to stop deleting (beggining from 1) */
0471: public void delete(int lineNumber, int column1, int column2) {
0472: delete(getLineOffset(lineNumber) + column1 - 1, column2
0473: - column1 + 1);
0474: }
0475:
0476: /********************** Caret manipulation ************************/
0477:
0478: /** Returns current line number.
0479: * @return number of line where the caret stays (first line == 1)
0480: */
0481: public int getLineNumber() {
0482: StyledDocument doc = (StyledDocument) txtEditorPane()
0483: .getDocument();
0484: int offset = txtEditorPane().getCaretPosition();
0485: return NbDocument.findLineNumber(doc, offset) + 1;
0486: }
0487:
0488: /**
0489: * Pushes key of requested key code.
0490: * @param keyCode key code
0491: */
0492: public void pushKey(int keyCode) {
0493: // need to request focus before any key push
0494: this .requestFocus();
0495: txtEditorPane().pushKey(keyCode);
0496: }
0497:
0498: /** Pushes Home key (KeyEvent.VK_HOME) */
0499: public void pushHomeKey() {
0500: pushKey(KeyEvent.VK_HOME);
0501: }
0502:
0503: /** Pushes End key (KeyEvent.VK_END) */
0504: public void pushEndKey() {
0505: pushKey(KeyEvent.VK_END);
0506: }
0507:
0508: /** Pushes Tab key (KeyEvent.VK_TAB) */
0509: public void pushTabKey() {
0510: pushKey(KeyEvent.VK_TAB);
0511: }
0512:
0513: /** Pushes Down key (KeyEvent.VK_DOWN) */
0514: public void pushDownArrowKey() {
0515: pushKey(KeyEvent.VK_DOWN);
0516: }
0517:
0518: /** Pushes Up key (KeyEvent.VK_UP) */
0519: public void pushUpArrowKey() {
0520: pushKey(KeyEvent.VK_UP);
0521: }
0522:
0523: /** Returns offset of the beginning of a line.
0524: * @param lineNumber number of line (starts at 1)
0525: * @return offset offset of line from the beginning of a file
0526: */
0527: private int getLineOffset(int lineNumber) {
0528: try {
0529: StyledDocument doc = (StyledDocument) txtEditorPane()
0530: .getDocument();
0531: return NbDocument.findLineOffset(doc, lineNumber - 1);
0532: } catch (IndexOutOfBoundsException e) {
0533: throw new JemmyException("Invalid line number "
0534: + lineNumber, e);
0535: }
0536: }
0537:
0538: /** Sets caret position relatively to current position.
0539: * @param relativeMove count of charaters to move caret
0540: */
0541: public void setCaretPositionRelative(int relativeMove) {
0542: setCaretPosition(txtEditorPane().getCaretPosition()
0543: + relativeMove);
0544: }
0545:
0546: /** Sets caret position to the beginning of specified line.
0547: * Lines are numbered from 1, so setCaretPosition(1) will set caret
0548: * to the beginning of the first line.
0549: * @param lineNumber number of line (beggining from 1)
0550: */
0551: public void setCaretPositionToLine(int lineNumber) {
0552: txtEditorPane().setCaretPosition(getLineOffset(lineNumber));
0553: }
0554:
0555: /** Sets caret position to the end of specified line.
0556: * Lines are numbered from 1, so setCaretPosition(1) will set caret
0557: * to the end of the first line.
0558: * @param lineNumber number of line (beggining from 1)
0559: */
0560: public void setCaretPositionToEndOfLine(int lineNumber) {
0561: // getText returns contents of line plus \n, that's why we use length()-1
0562: txtEditorPane().setCaretPosition(
0563: getLineOffset(lineNumber)
0564: + getText(lineNumber).length() - 1);
0565: }
0566:
0567: /** Sets caret position to specified line and column
0568: * @param lineNumber line number where to set caret
0569: * @param column column where to set caret (1 means beginning of the row)
0570: */
0571: public void setCaretPosition(int lineNumber, int column) {
0572: setCaretPosition(getLineOffset(lineNumber) + column - 1);
0573: }
0574:
0575: /** Sets caret to desired position.
0576: * @param position a position to set caret to (number of characters from
0577: * the beggining of the file - 0 means beginning of the file).
0578: */
0579: public void setCaretPosition(int position) {
0580: if (position < 0 || position > getText().length()) {
0581: throw new JemmyException("Invalid caret position "
0582: + position);
0583: }
0584: txtEditorPane().setCaretPosition(position);
0585: }
0586:
0587: /** Sets caret position before or after index-th occurence of given string.
0588: * @param text text to be searched
0589: * @param index index of text occurence (first occurence has index 0)
0590: * @param before if true put caret before text, otherwise after.
0591: */
0592: public void setCaretPosition(String text, int index, boolean before) {
0593: setCaretPosition(txtEditorPane().getPositionByText(text, index)
0594: + (before ? 0 : text.length()));
0595: }
0596:
0597: /** Sets caret position before or after first occurence of given string.
0598: * @param text text to be searched
0599: * @param before if true put caret before text, otherwise after.
0600: */
0601: public void setCaretPosition(String text, boolean before) {
0602: setCaretPosition(text, 0, before);
0603: }
0604:
0605: /**************************** Annotations ******************************/
0606: /************** thanks to Jan Lahoda for valuable input ***************/
0607:
0608: /** Gets an array of annotations attached to given line.
0609: * @param lineNumber number of line (beggining from 1)
0610: * @return an array of org.openide.text.Annotation instances
0611: * @see #getAnnotationShortDescription
0612: * @see #getAnnotationType
0613: */
0614: public Object[] getAnnotations(int lineNumber) {
0615: ArrayList<Object> result = new ArrayList<Object>();
0616: try {
0617: Class annotationsClass = Class
0618: .forName("org.netbeans.editor.Annotations");
0619: Method getLineAnnotationsMethod = annotationsClass
0620: .getDeclaredMethod("getLineAnnotations",
0621: new Class[] { int.class });
0622: getLineAnnotationsMethod.setAccessible(true);
0623: Object lineAnnotations = getLineAnnotationsMethod.invoke(
0624: getAnnotationsInstance(),
0625: new Object[] { new Integer(lineNumber - 1) });
0626: if (lineAnnotations != null) {
0627: result = getAnnotations(lineAnnotations);
0628: }
0629: } catch (Exception e) {
0630: throw new JemmyException("getAnnotations failed.", e);
0631: }
0632: return result.toArray(new Annotation[result.size()]);
0633: }
0634:
0635: /**Gets all annotations for current editor (Document).
0636: * @return array of org.openide.text.Annotation containing all annotations
0637: * attached to this editor.
0638: * @see #getAnnotationShortDescription
0639: * @see #getAnnotationType
0640: */
0641: public Object[] getAnnotations() {
0642: ArrayList<Object> result = new ArrayList<Object>();
0643: try {
0644: Class annotationsClass = Class
0645: .forName("org.netbeans.editor.Annotations");
0646: Field lineAnnotationsArrayField = annotationsClass
0647: .getDeclaredField("lineAnnotationsArray");
0648: lineAnnotationsArrayField.setAccessible(true);
0649: ArrayList lineAnnotationsArray = (ArrayList) lineAnnotationsArrayField
0650: .get(getAnnotationsInstance());
0651: // loop through all lines
0652: for (int i = 0; i < lineAnnotationsArray.size(); i++) {
0653: result.addAll(getAnnotations(lineAnnotationsArray
0654: .get(i)));
0655: }
0656: } catch (Exception e) {
0657: throw new JemmyException("getAnnotations failed.", e);
0658: }
0659: return result.toArray(new Annotation[result.size()]);
0660: }
0661:
0662: /** Returns instance of org.netbeans.editor.Annotations object for this
0663: * document. */
0664: private Object getAnnotationsInstance() throws Exception {
0665: Class baseDocumentClass = Class
0666: .forName("org.netbeans.editor.BaseDocument");
0667: Method getAnnotationsMethod = baseDocumentClass
0668: .getDeclaredMethod("getAnnotations", (Class[]) null);
0669: getAnnotationsMethod.setAccessible(true);
0670: return getAnnotationsMethod.invoke(txtEditorPane()
0671: .getDocument(), (Object[]) null);
0672: }
0673:
0674: /** Returns ArrayList of org.openide.text.Annotation from given LineAnnotations
0675: * object. */
0676: private ArrayList<Object> getAnnotations(Object lineAnnotations)
0677: throws Exception {
0678: Class lineAnnotationsClass = Class
0679: .forName("org.netbeans.editor.Annotations$LineAnnotations");
0680: Class annotationDescDelegateClass = Class
0681: .forName("org.netbeans.modules.editor.NbEditorDocument$AnnotationDescDelegate");
0682: Field delegateField = annotationDescDelegateClass
0683: .getDeclaredField("delegate");
0684: delegateField.setAccessible(true);
0685:
0686: Method getAnnotationsMethod = lineAnnotationsClass
0687: .getDeclaredMethod("getAnnotations", (Class[]) null);
0688: getAnnotationsMethod.setAccessible(true);
0689: Iterator annotations = (Iterator) getAnnotationsMethod.invoke(
0690: lineAnnotations, (Object[]) null);
0691: ArrayList<Object> result = new ArrayList<Object>();
0692: for (Iterator it = annotations; it.hasNext();) {
0693: result.add(delegateField.get(it.next()));
0694: }
0695: return result;
0696: }
0697:
0698: /** Returns a string uniquely identifying annotation. For editor bookmark
0699: * it is for example
0700: * org.netbeans.modules.editor.NbEditorKit.BOOKMARK_ANNOTATION_TYPE.
0701: * @param annotation instance of org.openide.text.Annotation
0702: * @return a string uniquely identifying annotation
0703: * @see #getAnnotations()
0704: *@see #getAnnotations(int)
0705: */
0706: public static String getAnnotationType(Object annotation) {
0707: return ((Annotation) annotation).getAnnotationType();
0708: }
0709:
0710: /** Returns a short description of annotation. It is localized.
0711: * @param annotation instance of org.openide.text.Annotation
0712: * @return a short description of annotation according to current locale
0713: */
0714: public static String getAnnotationShortDescription(Object annotation) {
0715: return ((Annotation) annotation).getShortDescription();
0716: }
0717:
0718: /***************** Methods for toolbar manipulation *******************/
0719:
0720: /** Return AbstractButtonOperator representing a toolbar button found by given
0721: * tooltip within the Source Editor.
0722: * @param buttonTooltip tooltip of toolbar button
0723: * @return AbstractButtonOperator instance of found toolbar button
0724: */
0725: public AbstractButtonOperator getToolbarButton(String buttonTooltip) {
0726: ToolbarButtonChooser chooser = new ToolbarButtonChooser(
0727: buttonTooltip, getComparator());
0728: return new AbstractButtonOperator(AbstractButtonOperator
0729: .waitAbstractButton((Container) this .getSource(),
0730: chooser));
0731: }
0732:
0733: /** Return AbstractButtonOperator representing index-th toolbar button within
0734: * the Source Editor.
0735: * @param index index of toolbar button to find
0736: * @return AbstractButtonOperator instance of found toolbar button
0737: */
0738: public AbstractButtonOperator getToolbarButton(int index) {
0739: // finds JToolbar
0740: ComponentChooser chooser = new ComponentChooser() {
0741: public boolean checkComponent(Component comp) {
0742: return comp instanceof JToolBar;
0743: }
0744:
0745: public String getDescription() {
0746: return "javax.swing.JToolBar";
0747: }
0748: };
0749: Container toolbar = (Container) findComponent(
0750: (Container) getSource(), chooser);
0751: if (toolbar == null) {
0752: throw new JemmyException("Toolbar not present.");
0753: }
0754: // if "quick browse" combo box is present, skip first button (MetalComboBoxButton usualy)
0755: Component combo = JComboBoxOperator.findJComboBox(toolbar,
0756: ComponentSearcher.getTrueChooser("JComboBox"));
0757: if (combo != null) {
0758: index++;
0759: }
0760: return new AbstractButtonOperator(AbstractButtonOperator
0761: .waitAbstractButton((Container) toolbar,
0762: ComponentSearcher
0763: .getTrueChooser("AbstractButton"),
0764: index));
0765: }
0766:
0767: /** Chooser which can be used to find a component with given tooltip,
0768: * in this case a toolbar button.
0769: */
0770: private static class ToolbarButtonChooser implements
0771: ComponentChooser {
0772: private String buttonTooltip;
0773: private StringComparator comparator;
0774:
0775: public ToolbarButtonChooser(String buttonTooltip,
0776: StringComparator comparator) {
0777: this .buttonTooltip = buttonTooltip;
0778: this .comparator = comparator;
0779: }
0780:
0781: public boolean checkComponent(Component comp) {
0782: return comparator.equals(((JComponent) comp)
0783: .getToolTipText(), buttonTooltip);
0784: }
0785:
0786: public String getDescription() {
0787: return "Toolbar button with tooltip \"" + buttonTooltip
0788: + "\".";
0789: }
0790: }
0791:
0792: /********************************** Code folding **************************/
0793: /*************************** Thanks to Martin Roskanin ********************/
0794:
0795: /** Waits for code folding initialization. */
0796: public void waitFolding() {
0797: JTextComponent textComponent = (JTextComponent) txtEditorPane()
0798: .getSource();
0799: final AbstractDocument adoc = (AbstractDocument) txtEditorPane()
0800: .getDocument();
0801: // Dump fold hierarchy
0802: final FoldHierarchy hierarchy = FoldHierarchy
0803: .get(textComponent);
0804: getOutput().printTrace("Wait folding is initialized.");
0805: waitState(new ComponentChooser() {
0806: public boolean checkComponent(Component comp) {
0807: adoc.readLock();
0808: try {
0809: hierarchy.lock();
0810: try {
0811: return hierarchy.getRootFold().getFoldCount() > 0;
0812: } finally {
0813: hierarchy.unlock();
0814: }
0815: } finally {
0816: adoc.readUnlock();
0817: }
0818: }
0819:
0820: public String getDescription() {
0821: return ("Folding initialized"); // NOI18N
0822: }
0823: });
0824: }
0825:
0826: /** Waits for fold at cursor position is collapsed. */
0827: public void waitCollapsed() {
0828: getOutput().printTrace(
0829: "Wait fold is collapsed at line " + getLineNumber());
0830: waitState(new ComponentChooser() {
0831: public boolean checkComponent(Component comp) {
0832: return isCollapsed();
0833: }
0834:
0835: public String getDescription() {
0836: return ("Fold collapsed");
0837: }
0838: });
0839: }
0840:
0841: /** Waits for fold at cursor position is expanded. */
0842: public void waitExpanded() {
0843: getOutput().printTrace(
0844: "Wait fold is expanded at line " + getLineNumber());
0845: waitState(new ComponentChooser() {
0846: public boolean checkComponent(Component comp) {
0847: return !isCollapsed();
0848: }
0849:
0850: public String getDescription() {
0851: return ("Fold expanded");
0852: }
0853: });
0854: }
0855:
0856: /** Collapses fold at cursor position using CTRL+'-'. It waits until fold
0857: * is not collapsed.
0858: */
0859: public void collapseFold() {
0860: getOutput().printTrace(
0861: "Collapse fold at line " + getLineNumber());
0862: requestFocus();
0863: txtEditorPane().pushKey(KeyEvent.VK_SUBTRACT,
0864: KeyEvent.CTRL_DOWN_MASK);
0865: waitCollapsed();
0866: }
0867:
0868: /** Collapses fold at specified line using CTRL+'-'. It waits until fold
0869: * is not collapsed.
0870: * @param lineNumber number of line (starts at 1)
0871: */
0872: public void collapseFold(int lineNumber) {
0873: setCaretPositionToLine(lineNumber);
0874: collapseFold();
0875: }
0876:
0877: /** Expands fold at specified line using CTRL+'+'. It waits until fold
0878: * is not expanded.
0879: */
0880: public void expandFold() {
0881: getOutput()
0882: .printTrace("Expand fold at line " + getLineNumber());
0883: requestFocus();
0884: txtEditorPane().pushKey(KeyEvent.VK_ADD,
0885: KeyEvent.CTRL_DOWN_MASK);
0886: waitExpanded();
0887: }
0888:
0889: /** Expands fold at specified line using CTRL+'+'. It waits until fold
0890: * is not expanded.
0891: * @param lineNumber number of line (starts at 1)
0892: */
0893: public void expandFold(int lineNumber) {
0894: setCaretPositionToLine(lineNumber);
0895: expandFold();
0896: }
0897:
0898: /** Returns true if fold at cursor position is collapsed, false if it is
0899: * expanded.
0900: * @return true if fold is collapsed, false if it is expanded.
0901: */
0902: public boolean isCollapsed() {
0903: return isCollapsed(getLineNumber());
0904: }
0905:
0906: /** Returns true if fold at specified line is collapsed, false if it is
0907: * expanded.
0908: * @param lineNumber number of line (starts at 1)
0909: * @return true if fold is collapsed, false if it is expanded.
0910: */
0911: public boolean isCollapsed(int lineNumber) {
0912: JTextComponent textComponent = (JTextComponent) txtEditorPane()
0913: .getSource();
0914: FoldHierarchy hierarchy = FoldHierarchy.get(textComponent);
0915: int dot = getLineOffset(lineNumber);
0916: hierarchy.lock();
0917: try {
0918: try {
0919: int rowStart = javax.swing.text.Utilities.getRowStart(
0920: textComponent, dot);
0921: int rowEnd = javax.swing.text.Utilities.getRowEnd(
0922: textComponent, dot);
0923: Fold fold = getLineFold(hierarchy, dot, rowStart,
0924: rowEnd);
0925: if (fold != null) {
0926: return fold.isCollapsed();
0927: } else {
0928: throw new JemmyException(
0929: "No fold found at position " + dot + ".");
0930: }
0931: } catch (BadLocationException ble) {
0932: throw new JemmyException(
0933: "BadLocationException when seraching for fold.",
0934: ble);
0935: }
0936: } finally {
0937: hierarchy.unlock();
0938: }
0939: }
0940:
0941: /** Returns the fold that should be collapsed/expanded in the caret row
0942: * @param hierarchy hierarchy under which all folds should be collapsed/expanded.
0943: * @param dot caret position offset
0944: * @param lineStart offset of the start of line
0945: * @param lineEnd offset of the end of line
0946: * @return the fold that meet common criteria in accordance with the caret position
0947: */
0948: private static Fold getLineFold(FoldHierarchy hierarchy, int dot,
0949: int lineStart, int lineEnd) {
0950: Fold caretOffsetFold = FoldUtilities.findOffsetFold(hierarchy,
0951: dot);
0952:
0953: // beginning searching from the lineStart
0954: Fold fold = FoldUtilities.findNearestFold(hierarchy, lineStart);
0955:
0956: while (fold != null && (fold.getEndOffset() <= dot || // find next available fold if the 'fold' is one-line
0957: // or it has children and the caret is in the fold body
0958: // i.e. class A{ |public void method foo(){}}
0959: (!fold.isCollapsed() && fold.getFoldCount() > 0 && fold
0960: .getStartOffset() + 1 < dot))) {
0961:
0962: // look for next fold in forward direction
0963: Fold nextFold = FoldUtilities
0964: .findNearestFold(hierarchy,
0965: (fold.getFoldCount() > 0) ? fold
0966: .getStartOffset() + 1 : fold
0967: .getEndOffset());
0968: if (nextFold != null && nextFold.getStartOffset() < lineEnd) {
0969: if (nextFold == fold)
0970: return fold;
0971: fold = nextFold;
0972: } else {
0973: break;
0974: }
0975: }
0976:
0977: // a fold on the next line was found, returning fold at offset (in most cases inner class)
0978: if (fold == null || fold.getStartOffset() > lineEnd) {
0979: // in the case:
0980: // class A{
0981: // } |
0982: // try to find an offset fold on the offset of the line beginning
0983: if (caretOffsetFold == null) {
0984: caretOffsetFold = FoldUtilities.findOffsetFold(
0985: hierarchy, lineStart);
0986: }
0987: return caretOffsetFold;
0988: }
0989:
0990: // no fold at offset found, in this case return the fold
0991: if (caretOffsetFold == null) {
0992: return fold;
0993: }
0994:
0995: // skip possible inner class members validating if the innerclass fold is collapsed
0996: if (caretOffsetFold.isCollapsed()) {
0997: return caretOffsetFold;
0998: }
0999:
1000: // in the case:
1001: // class A{
1002: // public vo|id foo(){} }
1003: // 'fold' (in this case fold of the method foo) will be returned
1004: if (caretOffsetFold.getEndOffset() > fold.getEndOffset()
1005: && fold.getEndOffset() > dot) {
1006: return fold;
1007: }
1008:
1009: // class A{
1010: // |} public void method foo(){}
1011: // inner class fold will be returned
1012: if (fold.getStartOffset() > caretOffsetFold.getEndOffset()) {
1013: return caretOffsetFold;
1014: }
1015:
1016: // class A{
1017: // public void foo(){} |}
1018: // returning innerclass fold
1019: if (fold.getEndOffset() < dot) {
1020: return caretOffsetFold;
1021: }
1022: return fold;
1023: }
1024:
1025: /********************************** Miscellaneous **************************/
1026:
1027: /** Waits for given modified state of edited source.
1028: * @param modified boolean true waits for file state change to modified, false for change to
1029: * unmodified (saved).
1030: * Throws TimeoutExpiredException when EditorOperator.WaitModifiedTimeout expires.
1031: */
1032: public void waitModified(final boolean modified) {
1033: try {
1034: Waiter waiter = new Waiter(new Waitable() {
1035: public Object actionProduced(Object obj) {
1036: return isModified() == modified ? new Object()
1037: : null;
1038: }
1039:
1040: public String getDescription() {
1041: return ("Wait Modified State");
1042: }
1043: });
1044: Timeouts times = getTimeouts().cloneThis();
1045: times.setTimeout("Waiter.WaitingTime", times
1046: .getTimeout("EditorOperator.WaitModifiedTimeout"));
1047: waiter.setTimeouts(times);
1048: waiter.setOutput(getOutput());
1049: waiter.waitAction(null);
1050: } catch (InterruptedException e) {
1051: }
1052: }
1053:
1054: /** Saves content of this Editor by API. If it is not applicable or content
1055: * is not modified, it does nothing.
1056: */
1057: public void save() {
1058: super .save();
1059: if (getVerification()) {
1060: waitModified(false);
1061: }
1062: }
1063:
1064: /** Performs verification by accessing all sub-components */
1065: public void verify() {
1066: txtEditorPane();
1067: lblInputMode();
1068: lblRowColumn();
1069: lblStatusBar();
1070: }
1071:
1072: /** SubChooser to determine Editor TopComponent
1073: * Used in findTopComponent method.
1074: */
1075: public static final class EditorSubchooser implements
1076: ComponentChooser {
1077: /** Checks component.
1078: * @param comp component
1079: * @return true if component instance of CloneableEditor
1080: */
1081: public boolean checkComponent(Component comp) {
1082: return (comp instanceof CloneableEditor);
1083: }
1084:
1085: /** Description.
1086: * @return Description
1087: */
1088: public String getDescription() {
1089: return "org.openide.text.CloneableEditor";
1090: }
1091: }
1092: }
|