/* * Copyright 2008 Ayman Al-Sairafi ayman.alsairafi@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License * at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jsyntaxpane.actions; import java.awt.Component; import java.awt.Frame; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JComboBox; import javax.swing.MutableComboBoxModel; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import javax.swing.text.PlainDocument; import jsyntaxpane.SyntaxDocument; import jsyntaxpane.Token; /** * Various utility methods to work on JEditorPane and its SyntaxDocument * for use by Actions * * @author Ayman Al-Sairafi */ public class ActionUtils { /** * Perform Smart Indentation: pos must be on a line: this method will * use the previous lines indentation (number of spaces before any non-space * character or end of line) and return that as the prefix * @param line the line of text * @return */ public static String getIndent(String line) { if (line == null || line.length() == 0) { return ""; } int i = 0; while (i < line.length() && line.charAt(i) == ' ') { i++; } return line.substring(0, i); } /** * Return the lines that span the selection (split as an array of Strings) * if there is no selection then current line is returned. * * Note that the strings returned will not contain the terminating line feeds * * The text component will then have the full lines set as selection * @param target * @return String[] of lines spanning selection / or Dot */ public static String[] getSelectedLines(JTextComponent target) { String[] lines = null; try { PlainDocument pDoc = (PlainDocument) target.getDocument(); int start = pDoc.getParagraphElement(target.getSelectionStart()).getStartOffset(); int end; if (target.getSelectionStart() == target.getSelectionEnd()) { end = pDoc.getParagraphElement(target.getSelectionEnd()).getEndOffset(); } else { // if more than one line is selected, we need to subtract one from the end // so that we do not select the line with the caret and no selection in it end = pDoc.getParagraphElement(target.getSelectionEnd() - 1).getEndOffset(); } target.select(start, end); lines = pDoc.getText(start, end - start).split("\n"); target.select(start, end); } catch (BadLocationException ex) { Logger.getLogger(ActionUtils.class.getName()).log(Level.SEVERE, null, ex); lines = EMPTY_STRING_ARRAY; } return lines; } /** * Return the line of text at the TextComponent's current position * @param target * @return */ public static String getLine(JTextComponent target) { return getLineAt(target, target.getCaretPosition()); } /** * Return the line of text at the given position. The returned value may * be null. It will not contain the trailing new-line character. * @param target the text component * @param pos char position * @return */ public static String getLineAt(JTextComponent target, int pos) { String line = null; Document doc = target.getDocument(); if (doc instanceof PlainDocument) { PlainDocument pDoc = (PlainDocument) doc; int start = pDoc.getParagraphElement(pos).getStartOffset(); int end = pDoc.getParagraphElement(pos).getEndOffset(); try { line = doc.getText(start, end - start); if (line != null && line.endsWith("\n")) { line = line.substring(0, line.length() - 1); } } catch (BadLocationException ex) { Logger.getLogger(ActionUtils.class.getName()).log(Level.SEVERE, null, ex); } } return line; } /** * Returns the Frame that contains this component or null if the component * is not within a Window or the containing window is not a frame * @param comp * @return */ public static Frame getFrameFor(Component comp) { Window w = SwingUtilities.getWindowAncestor(comp); if (w != null && w instanceof Frame) { Frame frame = (Frame) w; return frame; } return null; } /** * Returns the the Token at pos as a String * @param doc * @param pos * @return */ public static String getTokenStringAt( SyntaxDocument doc, int pos) { String word = ""; Token t = doc.getTokenAt(pos); if (t != null) { try { word = doc.getText(t.start, t.length); } catch (BadLocationException ex) { Logger.getLogger(ActionUtils.class.getName()).log(Level.SEVERE, null, ex); } } return word; } /** * A helper function that will return the SyntaxDocument attached to the * given text component. Return null if the document is not a * SyntaxDocument, or if the text component is null * @param component * @return */ public static SyntaxDocument getSyntaxDocument(JTextComponent component) { if (component == null) { return null; } Document doc = component.getDocument(); if (doc instanceof SyntaxDocument) { return (SyntaxDocument) doc; } else { return null; } } /** * Gets the Line Number at the give position of the editor component. * The first line number is ZERO * @param editor * @param pos * @return line number * @throws javax.swing.text.BadLocationException */ public static int getLineNumber(JTextComponent editor, int pos) throws BadLocationException { if (getSyntaxDocument(editor) != null) { SyntaxDocument sdoc = getSyntaxDocument(editor); return sdoc.getLineNumberAt(pos); } else { Document doc = editor.getDocument(); return doc.getDefaultRootElement().getElementIndex(pos); } } /** * Gets the column number at given position of editor. The first column is * ZERO * @param editor * @param pos * @return the 0 based column number * @throws javax.swing.text.BadLocationException */ public static int getColumnNumber(JTextComponent editor, int pos) throws BadLocationException { Rectangle r = editor.modelToView(pos); int start = editor.viewToModel(new Point(0, r.y)); int column = pos - start; return column; } /** * Get the closest position within the document of the component that * has given line and column. * @param editor * @param line * @param column * @return the closest positon for the text component at given line and * column */ public static int getDocumentPosition(JTextComponent editor, int line, int column) { int lineHeight = editor.getFontMetrics(editor.getFont()).getHeight(); int charWidth = editor.getFontMetrics(editor.getFont()).charWidth('m'); int y = line * lineHeight; int x = column * charWidth; Point pt = new Point(x, y); int pos = editor.viewToModel(pt); return pos; } public static int getLineCount(JTextComponent pane) { SyntaxDocument sdoc = getSyntaxDocument(pane); if (sdoc != null) { return sdoc.getLineCount(); } int count = 0; try { int p = pane.getDocument().getLength() - 1; if (p > 0) { count = getLineNumber(pane, p); } } catch (BadLocationException ex) { Logger.getLogger(ActionUtils.class.getName()).log(Level.SEVERE, null, ex); } return count; } /** * Insert the given item into the combo box, and set it as first selected * item. If the item already exists, it is removed, so there are no * duplicates. * @param combo * @param item */ public static void insertIntoCombo(JComboBox combo, Object item) { MutableComboBoxModel model = (MutableComboBoxModel) combo.getModel(); if (model.getSize() == 0) { model.insertElementAt(item, 0); return; } Object o = model.getElementAt(0); if (o.equals(item)) { return; } model.removeElement(item); model.insertElementAt(item, 0); combo.setSelectedIndex(0); } /** * Repeat the string source repeat times. * If repeats == 0 then empty String is returned * if source is null, then empty string is returned * @param source * @param repeat * @return source String repeated repeat times. */ public static String repeatString(String source, int repeat) { if (repeat < 0) { throw new IllegalArgumentException("Cannot repeat " + repeat + " times."); } if (repeat == 0 || source == null || source.length() == 0) { return ""; } StringBuffer buffer = new StringBuffer(); for (int i = 0; i < repeat; i++) { buffer.append(source); } return buffer.toString(); } /** * Checks if the given string is null, empty or contains whitespace only * @param string * @return true if string is null, empty or contains whitespace only, false * otherwise. */ public static boolean isEmptyOrBlanks(String string) { if (string == null || string.length() == 0) { return true; } for (int i = 0; i < string.length(); i++) { char c = string.charAt(i); if (!Character.isWhitespace(c)) { return false; } } return true; } /** * Return the TabStop property for the given text component, or 0 if not * used * @param text * @return */ public static int getTabSize(JTextComponent text) { Integer tabs = (Integer) text.getDocument().getProperty(PlainDocument.tabSizeAttribute); return (null == tabs) ? 0 : tabs.intValue(); } /** * Insert the given String into the textcomponent. If the string contains * the | vertical BAr char, then it will not be inserted, and the cursor will * be set to its location. * If there are TWO vertical bars, then the text between them will be selected * <b>FIXME: add following feature * If the String is multi-line, then it will be indented with the same * indentattion as the line with pos.</b> * @param target * @param dot * @param toInsert * @throws javax.swing.text.BadLocationException */ public static void insertMagicString(JTextComponent target, int dot, String toInsert) throws BadLocationException { Document doc = target.getDocument(); if (toInsert.indexOf('|') >= 0) { int ofst = toInsert.indexOf('|'); int ofst2 = toInsert.indexOf('|', ofst + 1); toInsert = toInsert.replace("|", ""); doc.insertString(dot, toInsert, null); dot = target.getCaretPosition(); final int strLength = toInsert.length(); if (ofst2 > 0) { // note that we already removed the first |, so end offset is now // one less than what it was. target.select(dot + ofst - strLength, dot + ofst2 - strLength - 1); } else { target.setCaretPosition(dot + ofst -strLength); } } else { doc.insertString(dot, toInsert, null); } } // This is used internally to avoid NPE if we have no Strings static String[] EMPTY_STRING_ARRAY = new String[0]; // This is used to quickly create Strings of at most 16 spaces (using substring) static String SPACES = " "; }