All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jsyntaxpane.actions.ActionUtils Maven / Gradle / Ivy

There is a newer version: 5.3.2
Show newest version
/*
 * Copyright 2008 Ayman Al-Sairafi [email protected]
 * 
 * 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.awt.event.KeyEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.Action;
import javax.swing.JComboBox;
import javax.swing.JEditorPane;
import javax.swing.MutableComboBoxModel;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import jsyntaxpane.DefaultSyntaxKit;
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 {

	private ActionUtils() {
	}

	private static ActionUtils instance = null;

	/**
	 * Get the Singleton instance.  Will be created lazily.
	 * @return
	 */
	public static synchronized ActionUtils getInstance() {
		if(instance == null) {
			instance = new ActionUtils();
		}
		return instance;
	}

	/**
	 * Get the indentation of a line of text.  This is the subString from
	 * beginning of line to the first non-space char
	 * @param line the line of text
	 * @return indentation of line.
	 */
	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
	 * If the document is empty, then an empty string array is returned.  So
	 * you can always iterate over the returned array without a null check
	 *
	 * The text component will then have the full lines set as selection
	 * @param target
	 * @return String[] of lines spanning selection / or line containing 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 {
		// speedup if the pos is 0
		if(pos == 0) {
			return 0;
		}
		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 the first being 1
	 * @param column the first being 1
	 * @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 the item to insert. if it's null, then nothing is inserted
	 */
	public static void insertIntoCombo(JComboBox combo, Object item) {
		if(item == null) {
			return;
		}
		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);
	}

	public static void insertMagicString(JTextComponent target, String result) {
		try {
			insertMagicString(target, target.getCaretPosition(), result);
		} catch (BadLocationException ex) {
			Logger.getLogger(ActionUtils.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	/**
	 * 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
	 * If the toInsert String is multiLine, then indentation of all following lines
	 * will be the same as the first line.  TAB characters will be replaced by
	 * the number of spaces in the document's TAB property.
	 * @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();
		String[] lines = toInsert.split("\n");
		if (lines.length > 1) {
			// multi line strings will need to be indented
			String tabToSpaces = getTab(target);
			String currentLine = getLineAt(target, dot);
			String currentIndent = getIndent(currentLine);
			StringBuilder sb = new StringBuilder(toInsert.length());
			boolean firstLine = true;
			for (String l : lines) {
				if (!firstLine) {
					sb.append(currentIndent);
				}
				firstLine = false;
				// replace tabs with spaces.
				sb.append(l.replace("\t", tabToSpaces));
				sb.append("\n");
			}
			toInsert = sb.toString();
		}
		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();
			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);
		}
	}

	/**
	 * Expand the string template and replaces the selection with the expansion
	 * of the template.  The template String may contain any of the following
	 * special tags.
	 *
	 * 
  • {@code #{selection}} replaced with the selection, if any. If there is * no selection, then the {@code #{selection}} tag will be removed. *
  • {@code #{p:any text}} will be replaced by {@code any text} and then * set selection to {@code any text} * * This method properly handles indentation as follows: * The indentation of the whole block will match the indentation of the caret * line, or the line with the beginning of the selection, if the selection is * in whole line, i.e.e one or more lines of selected text. {@see selectLines()} * * @param target JEditorCOmponent to be affected * @param templateLines template split as a String array of lines. * * @see insertLinesTemplate */ public static void insertLinesTemplate(JTextComponent target, String[] templateLines) { // get some stuff we'll need: String thisIndent = getIndent(getLineAt(target, target.getSelectionStart())); String[] selLines = getSelectedLines(target); int selStart = -1, selEnd = -1; StringBuffer sb = new StringBuffer(); for (String tLine : templateLines) { int selNdx = tLine.indexOf("#{selection}"); if (selNdx >= 0) { // for each of the selected lines: for (String selLine : selLines) { sb.append(tLine.subSequence(0, selNdx)); sb.append(selLine); sb.append('\n'); } } else { sb.append(thisIndent); // now check for any ptags Matcher pm = PTAGS_PATTERN.matcher(tLine); int lineStart = sb.length(); while (pm.find()) { selStart = pm.start() + lineStart; pm.appendReplacement(sb, pm.group(1)); selEnd = sb.length(); } pm.appendTail(sb); sb.append('\n'); } } int ofst = target.getSelectionStart(); target.replaceSelection(sb.toString()); if (selStart >= 0) { // target.setCaretPosition(selStart); target.select(ofst + selStart, ofst + selEnd); } } /** * Expand the string template and replaces the selection with the expansion * of the template. The template String may contain any of the following * special tags. * *
  • {@code #{selection}} replaced with the selection, if any. If there is * no selection, then the {@code #{selection}} tag will be removed. *
  • {@code #{p:AnyText}} will be replaced by {@code any text} and then * set the text selection to {@code AnyText} * * This methood does NOT perform any indentation and the template should * generally span one line only * * @param target * @param template */ public static void insertSimpleTemplate(JTextComponent target, String template) { String selected = target.getSelectedText(); selected = (selected == null) ? "" : selected; StringBuffer sb = new StringBuffer(template.length()); Matcher pm = PTAGS_PATTERN.matcher(template.replace(TEMPLATE_SELECTION, selected)); int selStart = -1, selEnd = -1; int lineStart = 0; while (pm.find()) { selStart = pm.start() + lineStart; pm.appendReplacement(sb, pm.group(1)); selEnd = sb.length(); } pm.appendTail(sb); // String expanded = template.replace(TEMPLATE_SELECTION, selected); if (selStart >= 0) { selStart += target.getSelectionStart(); selEnd += target.getSelectionStart(); } target.replaceSelection(sb.toString()); if (selStart >= 0) { // target.setCaretPosition(selStart); target.select(selStart, selEnd); } } /** * If the selection is multi lined, then the full lines are selected, * otherwise, nothing is done. * @param target * @return true if the selection is multi-line, or a whole line */ public static boolean selectLines(JTextComponent target) { if (target.getSelectionStart() == target.getSelectionEnd()) { return false; } PlainDocument pDoc = (PlainDocument) target.getDocument(); Element es = pDoc.getParagraphElement(target.getSelectionStart()); // 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 Element ee = pDoc.getParagraphElement(target.getSelectionEnd() - 1); if (es.equals(ee) && ee.getEndOffset() != target.getSelectionEnd()) { return false; } int start = es.getStartOffset(); int end = ee.getEndOffset(); target.select(start, end - 1); return true; } /** * Sets the caret position of the given target to the given line and column * @param target * @param line the first being 1 * @param column the first being 1 */ public static void setCaretPosition(JTextComponent target, int line, int column) { int p = getDocumentPosition(target, line, column); target.setCaretPosition(p); } /** * Return a string with number of spaces equal to the tab-stop of the TextComponent * @param target * @return */ public static String getTab(JTextComponent target) { return SPACES.substring(0, getTabSize(target)); } /** * Searches all actions of a JTextComponent for ab action of the given class and returns * the first one that matches that class, or null if no Action is found * @param * @param target * @param aClass * @return Action object of that class or null */ public static T getAction(JTextComponent target, Class aClass) { for (Object k : target.getActionMap().allKeys()) { Action a = target.getActionMap().get(k); if (aClass.isInstance(a)) { @SuppressWarnings("unchecked") T t = (T) a; return t; } } return null; } /** * Return the DefaultSyntaxKit of this target, or null if the target does not * have a DefaultSyntaxKit * @param target * @return kit or null */ public static DefaultSyntaxKit getSyntaxKit(JTextComponent target) { DefaultSyntaxKit kit = null; if (target instanceof JEditorPane) { JEditorPane jEditorPane = (JEditorPane) target; EditorKit k = jEditorPane.getEditorKit(); if (k instanceof DefaultSyntaxKit) { kit = (DefaultSyntaxKit) k; } } return kit; } /** * Create and send a KeyPress KeyEvent to the component given * @param target Editor to get the action * @param v_key from KeyEvent.V_ constants * @param modifiers from KeyEvent.*MASK constants */ public static void sendKeyPress(JTextComponent target, int v_key, int modifiers) { KeyEvent ke = new KeyEvent(target, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), modifiers, v_key, KeyEvent.CHAR_UNDEFINED); target.dispatchEvent(ke); } // This is used internally to avoid NPE if we have no Strings final static String[] EMPTY_STRING_ARRAY = new String[0]; // This is used to quickly create Strings of at most 16 spaces (using substring) final static String SPACES = " "; /** * The Pattern to use for PTags in insertSimpleTemplate */ public static final Pattern PTAGS_PATTERN = Pattern.compile("\\#\\{p:([^}]*)\\}"); public static final String TEMPLATE_SELECTION = "#{selection}"; }




  • © 2015 - 2025 Weber Informatics LLC | Privacy Policy