/* * 01/27/2004 * * RSyntaxTextArea.java - An extension of RTextArea that adds * the ability to syntax highlight certain programming languages. * Copyright (C) 2004 Robert Futrell * robert_futrell at users.sourceforge.net * http://fifesoft.com/rsyntaxtextarea * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ package org.fife.ui.rsyntaxtextarea; import java.awt.Color; import java.awt.Cursor; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.io.File; import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.event.CaretEvent; import javax.swing.event.EventListenerList; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.Highlighter; import org.fife.ui.rsyntaxtextarea.focusabletip.FocusableTip; import org.fife.ui.rsyntaxtextarea.parser.Parser; import org.fife.ui.rsyntaxtextarea.parser.ToolTipInfo; import org.fife.ui.rtextarea.Gutter; import org.fife.ui.rtextarea.RTextArea; import org.fife.ui.rtextarea.RTextAreaUI; import org.fife.ui.rtextarea.RTextScrollPane; /** * An extension of <code>RTextArea</code> that adds syntax highlighting of certain programming languages to its list of * features. Languages currently supported include: * <ul> * <li>ActionScript * <li>Assembler (X86) * <li>BBCode * <li>C * <li>C++ * <li>CSS * <li>C# * <li>Clojure * <li>Delphi * <li>Fortran * <li>Groovy * <li>HTML * <li>Java * <li>JavaScript * <li>JSP * <li>Lisp * <li>Lua * <li>Make * <li>MXML * <li>Perl * <li>PHP * <li>Ruby * <li>SAS * <li>Scala * <li>SQL * <li>Tcl * <li>UNIX shell scripts * <li>Windows batch * <li>XML files * </ul> * * Other added features include: * <ul> * <li>Bracket matching * <li>Auto-indentation * <li>Copy as RTF * <li>Clickable hyperlinks (if the language scanner being used supports it) * <li>A pluggable "parser" system that can be used to implement syntax validation, spell checking, etc. * </ul> * * It is recommended that you use an instance of {@link org.fife.ui.rtextarea.RTextScrollPane} instead of a regular * <code>JScrollPane</code> as this class allows you to add line numbers and bookmarks easily to your text area. * * @author Robert Futrell * @version 1.3 */ public class RSyntaxTextArea extends RTextArea implements SyntaxConstants { public static final String ANIMATE_BRACKET_MATCHING_PROPERTY = "RSTA.animateBracketMatching"; public static final String ANTIALIAS_PROPERTY = "RSTA.antiAlias"; public static final String AUTO_INDENT_PROPERTY = "RSTA.autoIndent"; public static final String BRACKET_MATCHING_PROPERTY = "RSTA.bracketMatching"; public static final String CLEAR_WHITESPACE_LINES_PROPERTY = "RSTA.clearWhitespaceLines"; public static final String CLOSE_CURLY_BRACES_PROPERTY = "RSTA.closeCurlyBraces"; public static final String CLOSE_MARKUP_TAGS_PROPERTY = "RSTA.closeMarkupTags"; public static final String EOL_VISIBLE_PROPERTY = "RSTA.eolMarkersVisible"; public static final String FOCUSABLE_TIPS_PROPERTY = "RSTA.focusableTips"; public static final String FRACTIONAL_FONTMETRICS_PROPERTY = "RSTA.fractionalFontMetrics"; public static final String HYPERLINKS_ENABLED_PROPERTY = "RSTA.hyperlinksEnabled"; public static final String MARK_OCCURRENCES_PROPERTY = "RSTA.markOccurrences"; public static final String MARKED_OCCURRENCES_CHANGED_PROPERTY = "RSTA.markedOccurrencesChanged"; public static final String PARSER_NOTICES_PROPERTY = "RSTA.parserNotices"; public static final String SYNTAX_SCHEME_PROPERTY = "RSTA.syntaxScheme"; public static final String SYNTAX_STYLE_PROPERTY = "RSTA.syntaxStyle"; public static final String VISIBLE_WHITESPACE_PROPERTY = "RSTA.visibleWhitespace"; private static final Color DEFAULT_BRACKET_MATCH_BG_COLOR = new Color(234, 234, 255); private static final Color DEFAULT_BRACKET_MATCH_BORDER_COLOR = new Color(0, 0, 128); private static final Color DEFAULT_SELECTION_COLOR = new Color(200, 200, 255); /** * The key for the syntax style to be highlighting. */ private String syntaxStyleKey; /** * The colors used for syntax highlighting. */ private SyntaxScheme syntaxScheme; /** * Handles code templates. */ private static CodeTemplateManager codeTemplateManager; /** * Whether or not templates are enabled. */ private static boolean templatesEnabled; /** * The rectangle surrounding the "matched bracket" if bracket matching is enabled. */ Rectangle match; /** * Colors used for the "matched bracket" if bracket matching is enabled. */ private Color matchedBracketBGColor; private Color matchedBracketBorderColor; /** * The location of the last matched bracket. */ private int lastBracketMatchPos; /** * Whether or not bracket matching is enabled. */ private boolean bracketMatchingEnabled; /** * Whether or not bracket matching is animated. */ private boolean animateBracketMatching; private BracketMatchingTimer bracketRepaintTimer; /** * Whether or not auto-indent is on. */ private boolean autoIndentEnabled; /** * Whether curly braces should be closed on Enter key presses, (if the current language supports it). */ private boolean closeCurlyBraces; /** * Whether closing markup tags should be automatically completed when "<code></</code>" is typed (if the current * language is a markup language). */ private boolean closeMarkupTags; /** * Whether or not lines with nothing but whitespace are "made empty." */ private boolean clearWhitespaceLines; /** * Whether we are displaying visible whitespace (spaces and tabs). */ private boolean whitespaceVisible; /** * Whether EOL markers should be visible at the end of each line. */ private boolean eolMarkersVisible; /** * Whether hyperlinks are enabled (must be supported by the syntax scheme being used). */ private boolean hyperlinksEnabled; /** * The color to use when painting hyperlinks. */ private Color hyperlinkFG; /** * Mask used to determine if the correct key is being held down to scan for hyperlinks (ctrl, meta, etc.). */ private int linkScanningMask; /** * Used during "Copy as RTF" operations. */ private RtfGenerator rtfGenerator; /** * Handles "mark occurrences" support. */ private MarkOccurrencesSupport markOccurrencesSupport; /** * The color used to render "marked occurrences." */ private Color markOccurrencesColor; /** * Metrics of the text area's font. */ private FontMetrics defaultFontMetrics; /** * Manages running the parser. */ private ParserManager parserManager; /** * Whether the editor is currently scanning for hyperlinks on mouse movement. */ private boolean isScanningForLinks; private int hoveredOverLinkOffset; /** * Whether "focusable" tool tips are used instead of standard ones. */ private boolean useFocusableTips; /** * The last focusable tip displayed. */ private FocusableTip focusableTip; private int lineHeight; // Height of a line of text; same for default, bold & italic. private int maxAscent; public int getMaxAscent() { return maxAscent; } private String aaHintFieldName; private Object aaHint; private boolean fractionalFontMetricsEnabled; /** * Constructor. */ public RSyntaxTextArea() { init(); } /** * Constructor. * * @param doc * The document for the editor. */ public RSyntaxTextArea(RSyntaxDocument doc) { super(doc); init(); } /** * Constructor. * * @param text * The initial text to display. */ public RSyntaxTextArea(String text) { super(text); init(); } /** * Constructor. * * @param rows * The number of rows to display. * @param cols * The number of columns to display. * @throws IllegalArgumentException * If either <code>rows</code> or <code>cols</code> is negative. */ public RSyntaxTextArea(int rows, int cols) { super(rows, cols); init(); } /** * Constructor. * * @param text * The initial text to display. * @param rows * The number of rows to display. * @param cols * The number of columns to display. * @throws IllegalArgumentException * If either <code>rows</code> or <code>cols</code> is negative. */ public RSyntaxTextArea(String text, int rows, int cols) { super(text, rows, cols); init(); } /** * Constructor. * * @param doc * The document for the editor. * @param text * The initial text to display. * @param rows * The number of rows to display. * @param cols * The number of columns to display. * @throws IllegalArgumentException * If either <code>rows</code> or <code>cols</code> is negative. */ public RSyntaxTextArea(RSyntaxDocument doc, String text, int rows, int cols) { super(doc, text, rows, cols); init(); } /** * Creates a new <code>RSyntaxTextArea</code>. * * @param textMode * Either <code>INSERT_MODE</code> or <code>OVERWRITE_MODE</code>. */ public RSyntaxTextArea(int textMode) { super(textMode); init(); } /** * Adds an "active line range" listener to this text area. * * @param l * The listener to add. * @see #removeActiveLineRangeListener(ActiveLineRangeListener) */ public void addActiveLineRangeListener(ActiveLineRangeListener l) { listenerList.add(ActiveLineRangeListener.class, l); } /** * Adds a hyperlink listener to this text area. * * @param l * The listener to add. * @see #removeHyperlinkListener(HyperlinkListener) */ public void addHyperlinkListener(HyperlinkListener l) { listenerList.add(HyperlinkListener.class, l); } /** * Updates the font metrics the first time we're displayed. */ public void addNotify() { super.addNotify(); // We know we've just been connected to a screen resource (by // definition), so initialize our font metrics objects. refreshFontMetrics(getGraphics2D(getGraphics())); // Re-start parsing if we were removed from one container and added // to another if (parserManager != null) { parserManager.restartParsing(); } } /** * Adds the parser to "validate" the source code in this text area. This can be anything from a spell checker to a * "compiler" that verifies source code. * * @param parser * The new parser. A value of <code>null</code> will do nothing. * @see #getParser(int) * @see #getParserCount() * @see #removeParser(Parser) */ public void addParser(Parser parser) { if (parserManager == null) { parserManager = new ParserManager(this); } parserManager.addParser(parser); } /** * Recalculates the height of a line in this text area and the maximum ascent of all fonts displayed. */ private void calculateLineHeight() { lineHeight = maxAscent = 0; // Each token style. for (int i = 0; i < syntaxScheme.styles.length; i++) { Style ss = syntaxScheme.styles[i]; if (ss != null && ss.font != null) { FontMetrics fm = getFontMetrics(ss.font); int height = fm.getHeight(); if (height > lineHeight) lineHeight = height; int ascent = fm.getMaxAscent(); if (ascent > maxAscent) maxAscent = ascent; } } // The text area's (default) font). Font temp = getFont(); FontMetrics fm = getFontMetrics(temp); int height = fm.getHeight(); if (height > lineHeight) { lineHeight = height; } int ascent = fm.getMaxAscent(); if (ascent > maxAscent) { maxAscent = ascent; } } /** * Removes all parsers from this text area. * * @see #removeParser(Parser) */ public void clearParsers() { if (parserManager != null) { parserManager.clearParsers(); } } /** * Clones a token list. This is necessary as tokens are reused in {@link RSyntaxDocument}, so we can't simply use * the ones we are handed from it. * * @param t * The token list to clone. * @return The clone of the token list. */ private Token cloneTokenList(Token t) { if (t == null) { return null; } Token clone = new DefaultToken(); clone.copyFrom(t); Token cloneEnd = clone; while ((t = t.getNextToken()) != null) { Token temp = new DefaultToken(); temp.copyFrom(t); cloneEnd.setNextToken(temp); cloneEnd = temp; } return clone; } /** * Copies the currently selected text to the system clipboard, with any necessary style information (font, * foreground color and background color). Does nothing for <code>null</code> selections. */ public void copyAsRtf() { int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); if (selStart == selEnd) { return; } // Make sure there is a system clipboard, and that we can write // to it. SecurityManager sm = System.getSecurityManager(); if (sm != null) { try { sm.checkSystemClipboardAccess(); } catch (SecurityException se) { UIManager.getLookAndFeel().provideErrorFeedback(null); return; } } Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard(); // Create the RTF selection. RtfGenerator gen = getRTFGenerator(); Token tokenList = getTokenListFor(selStart, selEnd); for (Token t = tokenList; t != null; t = t.getNextToken()) { if (t.isPaintable()) { if (t.textCount == 1 && t.text[t.textOffset] == '\n') { gen.appendNewline(); } else { Font font = getFontForTokenType(t.type); Color bg = getBackgroundForTokenType(t.type); boolean underline = getUnderlineForToken(t); // Small optimization - don't print fg color if this // is a whitespace color. Saves on RTF size. if (t.isWhitespace()) { gen.appendToDocNoFG(t.getLexeme(), font, bg, underline); } else { Color fg = getForegroundForToken(t); gen.appendToDoc(t.getLexeme(), font, fg, bg, underline); } } } } // Set the system clipboard contents to the RTF selection. RtfTransferable contents = new RtfTransferable(gen.getRtf().getBytes()); // System.out.println("*** " + new String(gen.getRtf().getBytes())); try { cb.setContents(contents, null); } catch (IllegalStateException ise) { UIManager.getLookAndFeel().provideErrorFeedback(null); return; } } /** * Returns the document to use for an <code>RSyntaxTextArea</code> * * @return The document. */ protected Document createDefaultModel() { return new RSyntaxDocument(SYNTAX_STYLE_NONE); } /** * Returns the caret event/mouse listener for <code>RTextArea</code>s. * * @return The caret event/mouse listener. */ protected RTAMouseListener createMouseListener() { return new RSyntaxTextAreaMutableCaretEvent(this); } /** * Returns the a real UI to install on this text area. * * @return The UI. */ protected RTextAreaUI createRTextAreaUI() { return new RSyntaxTextAreaUI(this); } /** * If the caret is on a bracket, this method finds the matching bracket, and if it exists, highlights it. */ protected final void doBracketMatching() { // We always need to repaint the "matched bracket" highlight if it // exists. if (match != null) { repaint(match); } // If a matching bracket is found, get its bounds and paint it! int pos = RSyntaxUtilities.getMatchingBracketPosition(this); if (pos > -1 && pos != lastBracketMatchPos) { try { match = modelToView(pos); if (match != null) { // Happens if we're not yet visible if (getAnimateBracketMatching()) { bracketRepaintTimer.restart(); } repaint(match); } } catch (BadLocationException ble) { ble.printStackTrace(); // Shouldn't happen. } } else if (pos == -1) { // Set match to null so the old value isn't still repainted. match = null; bracketRepaintTimer.stop(); } lastBracketMatchPos = pos; } /** * Notifies all listeners that a caret change has occurred. * * @param e * The caret event. */ protected void fireCaretUpdate(CaretEvent e) { super.fireCaretUpdate(e); if (isBracketMatchingEnabled()) { doBracketMatching(); } } /** * Notifies all listeners that the active line range has changed. * * @param min * The minimum "active" line, or <code>-1</code>. * @param max * The maximum "active" line, or <code>-1</code>. */ private void fireActiveLineRangeEvent(int min, int max) { ActiveLineRangeEvent e = null; // Lazily created // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ActiveLineRangeListener.class) { if (e == null) { e = new ActiveLineRangeEvent(this, min, max); } ((ActiveLineRangeListener) listeners[i + 1]).activeLineRangeChanged(e); } } } /** * Notifies all listeners that have registered interest for notification on this event type. The listener list is * processed last to first. * * @param e * The event to fire. * @see EventListenerList */ private void fireHyperlinkUpdate(HyperlinkEvent e) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == HyperlinkListener.class) { ((HyperlinkListener) listeners[i + 1]).hyperlinkUpdate(e); } } } /** * Notifies listeners that the marked occurrences for this text area have changed. */ void fireMarkedOccurrencesChanged() { firePropertyChange(RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, null, null); } /** * Fires a notification that the parser notices for this text area have changed. */ void fireParserNoticesChange() { firePropertyChange(PARSER_NOTICES_PROPERTY, null, null); } /** * Forces the given {@link Parser} to re-parse the content of this text area. * <p> * * This method can be useful when a <code>Parser</code> can be configured as to what notices it returns. For * example, if a Java language parser can be configured to set whether no serialVersionUID is a warning, error, or * ignored, this method can be called after changing the expected notice type to have the document re-parsed. * * @param parser * The index of the <code>Parser</code> to re-run. * @see #getParser(int) */ public void forceReparsing(int parser) { parserManager.forceReparsing(parser); } /** * Forces re-parsing with a specific parser. Note that if this parser is not installed on this text area, nothing * will happen. * * @param parser * The parser that should re-parse this text area's contents. This should be installed on this text area. * @return Whether the parser was installed on this text area. * @see #forceReparsing(int) */ public boolean forceReparsing(Parser parser) { for (int i = 0; i < getParserCount(); i++) { if (getParser(i) == parser) { forceReparsing(i); return true; } } return false; } /** * Returns whether bracket matching should be animated. * * @return Whether bracket matching should be animated. * @see #setAnimateBracketMatching(boolean) */ public boolean getAnimateBracketMatching() { return animateBracketMatching; } /** * Returns the background color for tokens of the specified type. * * @param type * The type of token. * @return The background color to use for that token type. If this value is <code>null</code> then this token type * has no special background color. * @see #getForegroundForToken(Token) */ public Color getBackgroundForTokenType(int type) { // Don't default to this.getBackground(), as Tokens simply don't // paint a background if they get a null Color. return syntaxScheme.styles[type].background; } /** * Returns whether curly braces should be automatically closed when a newline is entered after an opening curly * brace. Note that this property is only honored for languages that use curly braces to denote code blocks. * * @return Whether curly braces should be automatically closed. * @see #setCloseCurlyBraces(boolean) */ public boolean getCloseCurlyBraces() { return closeCurlyBraces; } /** * Returns whether closing markup tags should be automatically completed when "<code></</code>" is typed. Note * that this property is only honored for markup languages, such as HTML, XML and PHP. * * @return Whether closing markup tags should be automatically completed. * @see #setCloseMarkupTags(boolean) */ public boolean getCloseMarkupTags() { return closeMarkupTags; } /** * Returns the code template manager for all instances of <code>RSyntaxTextArea</code>. The manager is lazily * created. * * @return The code template manager. * @see #setTemplatesEnabled(boolean) */ public static synchronized CodeTemplateManager getCodeTemplateManager() { if (codeTemplateManager == null) { codeTemplateManager = new CodeTemplateManager(); } return codeTemplateManager; } /** * Returns the default bracket-match background color. * * @return The color. * @see #getDefaultBracketMatchBorderColor */ public static final Color getDefaultBracketMatchBGColor() { return DEFAULT_BRACKET_MATCH_BG_COLOR; } /** * Returns the default bracket-match border color. * * @return The color. * @see #getDefaultBracketMatchBGColor */ public static final Color getDefaultBracketMatchBorderColor() { return DEFAULT_BRACKET_MATCH_BORDER_COLOR; } /** * Returns the default selection color for this text area. This color was chosen because it's light and * <code>RSyntaxTextArea</code> does not change text color between selected/unselected text for contrast like * regular <code>JTextArea</code>s do. * * @return The default selection color. */ public static Color getDefaultSelectionColor() { return DEFAULT_SELECTION_COLOR; } /** * Returns the "default" syntax highlighting color scheme. The colors used are somewhat standard among syntax * highlighting text editors. * * @return The default syntax highlighting color scheme. * @see #restoreDefaultSyntaxScheme() * @see #getSyntaxScheme() * @see #setSyntaxScheme(SyntaxScheme) */ public SyntaxScheme getDefaultSyntaxScheme() { return new SyntaxScheme(getFont()); } /** * Returns whether an EOL marker should be drawn at the end of each line. * * @return Whether EOL markers should be visible. * @see #setEOLMarkersVisible(boolean) * @see #isWhitespaceVisible() */ public boolean getEOLMarkersVisible() { return eolMarkersVisible; } /** * Returns the font for tokens of the specified type. * * @param type * The type of token. * @return The font to use for that token type. * @see #getFontMetricsForTokenType(int) */ public Font getFontForTokenType(int type) { Font f = syntaxScheme.styles[type].font; return f != null ? f : getFont(); } /** * Returns the font metrics for tokens of the specified type. * * @param type * The type of token. * @return The font metrics to use for that token type. * @see #getFontForTokenType(int) */ public FontMetrics getFontMetricsForTokenType(int type) { FontMetrics fm = syntaxScheme.styles[type].fontMetrics; return fm != null ? fm : defaultFontMetrics; } /** * Returns the foreground color to use when painting a token. * * @param t * The token. * @return The foreground color to use for that token. This value is never <code>null</code>. * @see #getBackgroundForTokenType(int) */ public Color getForegroundForToken(Token t) { if (getHyperlinksEnabled() && t.isHyperlink() && hoveredOverLinkOffset == t.offset) { return hyperlinkFG; } return getForegroundForTokenType(t.type); } /** * Returns the foreground color to use when painting a token. This does not take into account whether the token is a * hyperlink. * * @param type * The token type. * @return The foreground color to use for that token. This value is never <code>null</code>. * @see #getForegroundForToken(Token) */ public Color getForegroundForTokenType(int type) { Color fg = syntaxScheme.styles[type].foreground; return fg != null ? fg : getForeground(); } /** * Returns whether fractional font metrics are enabled for this text area. * * @return Whether fractional font metrics are enabled. * @see #setFractionalFontMetricsEnabled * @see #getTextAntiAliasHint */ public boolean getFractionalFontMetricsEnabled() { return fractionalFontMetricsEnabled; } /** * Returns a <code>Graphics2D</code> version of the specified graphics that has been initialized with the proper * rendering hints. * * @param g * The graphics context for which to get a <code>Graphics2D</code>. * @return The <code>Graphics2D</code>. */ private final Graphics2D getGraphics2D(Graphics g) { Graphics2D g2d = (Graphics2D) g; if (aaHint != null) { g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, aaHint); } if (fractionalFontMetricsEnabled) { g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); } return g2d; } /** * Returns the color to use when painting hyperlinks. * * @return The color to use when painting hyperlinks. * @see #setHyperlinkForeground(Color) * @see #getHyperlinksEnabled() */ public Color getHyperlinkForeground() { return hyperlinkFG; } /** * Returns whether hyperlinks are enabled for this text area. * * @return Whether hyperlinks are enabled for this text area. * @see #setHyperlinksEnabled(boolean) */ public boolean getHyperlinksEnabled() { return hyperlinksEnabled; } /** * Returns the height to use for a line of text in this text area. * * @return The height of a line of text in this text area. */ public int getLineHeight() { // System.err.println("... getLineHeight() returning " + lineHeight); return lineHeight; } /** * Returns a list of "marked occurrences" in the text area. If there are no marked occurrences, this will be an * empty list. * * @return The list of marked occurrences. */ public List getMarkedOccurrences() { return ((RSyntaxTextAreaHighlighter) getHighlighter()). getMarkedOccurrences(); } /** * Returns whether "Mark Occurrences" is enabled. * * @return Whether "Mark Occurrences" is enabled. * @see #setMarkOccurrences(boolean) */ public boolean getMarkOccurrences() { return markOccurrencesSupport != null; } /** * Returns the color used to "mark occurrences." * * @return The mark occurrences color. * @see #setMarkOccurrencesColor(Color) */ public Color getMarkOccurrencesColor() { return markOccurrencesColor; } /** * Returns whether tokens of the specified type should have "mark occurrences" enabled for the current programming * language. * * @param type * The token type. * @return Whether tokens of this type should have "mark occurrences" enabled. */ boolean getMarkOccurrencesOfTokenType(int type) { RSyntaxDocument doc = (RSyntaxDocument) getDocument(); return doc.getMarkOccurrencesOfTokenType(type); } /** * Gets the color used as the background for a matched bracket. * * @return The color used. * @see #setMatchedBracketBGColor * @see #getMatchedBracketBorderColor */ public Color getMatchedBracketBGColor() { return matchedBracketBGColor; } /** * Gets the color used as the border for a matched bracket. * * @return The color used. * @see #setMatchedBracketBorderColor * @see #getMatchedBracketBGColor */ public Color getMatchedBracketBorderColor() { return matchedBracketBorderColor; } /** * Returns the matched bracket's rectangle, or <code>null</code> if there is currently no matched bracket. Note that * this shouldn't ever be called by the user. * * @return The rectangle surrounding the matched bracket. */ public final Rectangle getMatchRectangle() { return match; } /** * Returns the specified parser. * * @param index * The {@link Parser} to retrieve. * @return The <code>Parser</code>. * @see #getParserCount() * @see #addParser(Parser) */ public Parser getParser(int index) { return parserManager.getParser(index); } /** * Returns the number of parsers operating on this text area. * * @return The parser count. * @see #addParser(Parser) */ public int getParserCount() { return parserManager == null ? 0 : parserManager.getParserCount(); } /** * Returns a list of the current parser notices for this text area. This method (like most Swing methods) should * only be called on the EDT. * * @return The list of notices. This will be an empty list if there are none. */ public List getParserNotices() { return parserManager == null ? new ArrayList(0) : parserManager.getParserNotices(); } /** * Returns the RTF generator for this text area, lazily creating it if necessary. * * @return The RTF generator. */ private RtfGenerator getRTFGenerator() { if (rtfGenerator == null) { rtfGenerator = new RtfGenerator(); } else { rtfGenerator.reset(); } return rtfGenerator; } /** * If auto-indent is enabled, this method returns whether a new line after this one should be indented (based on the * standard indentation rules for the current programming language). For example, in Java, for a line containing: * * <pre> * for (int i=0; i<10; i++) { * </pre> * * the following line should be indented. * * @param line * The line to check. * @return Whether a line inserted after this one should be auto-indented. If auto-indentation is disabled, this * will always return <code>false</code>. * @see #isAutoIndentEnabled() */ public boolean getShouldIndentNextLine(int line) { if (isAutoIndentEnabled()) { RSyntaxDocument doc = (RSyntaxDocument) getDocument(); return doc.getShouldIndentNextLine(line); } return false; } /** * Returns what type of syntax highlighting this editor is doing. * * @return The style being used, such as {@link SyntaxConstants#SYNTAX_STYLE_JAVA}. * @see #setSyntaxEditingStyle(String) * @see SyntaxConstants */ public String getSyntaxEditingStyle() { return syntaxStyleKey; } /** * Returns all of the colors currently being used in syntax highlighting by this text component. * * @return An instance of <code>SyntaxScheme</code> that represents the colors currently being used for syntax * highlighting. * @see #setSyntaxScheme(SyntaxScheme) */ public SyntaxScheme getSyntaxScheme() { return syntaxScheme; } /** * Returns whether or not templates are enabled for all instances of <code>RSyntaxTextArea</code>. * * @return Whether templates are enabled. * @see #saveTemplates() * @see #setTemplateDirectory(String) * @see #setTemplatesEnabled(boolean) */ public static synchronized boolean getTemplatesEnabled() { return templatesEnabled; } /** * Returns the rendering hint used when antialiasing text in this editor. * * @return The name of a field in <code>java.awt.RenderingHints</code>, or <code>null</code> if no text antialiasing * is being done. * @see #setTextAntiAliasHint(String) * @see #getFractionalFontMetricsEnabled() */ public String getTextAntiAliasHint() { return aaHintFieldName; } /** * Returns a token list for the given range in the document. * * @param startOffs * The starting offset in the document. * @param endOffs * The end offset in the document. * @return The first token in the token list. */ private Token getTokenListFor(int startOffs, int endOffs) { Token tokenList = null; Token lastToken = null; Element map = getDocument().getDefaultRootElement(); int startLine = map.getElementIndex(startOffs); int endLine = map.getElementIndex(endOffs); for (int line = startLine; line <= endLine; line++) { Token t = getTokenListForLine(line); t = cloneTokenList(t); if (tokenList == null) { tokenList = t; lastToken = tokenList; } else { lastToken.setNextToken(t); } while (lastToken.getNextToken() != null && lastToken.getNextToken().isPaintable()) { lastToken = lastToken.getNextToken(); } if (line < endLine) { // Document offset MUST be correct to prevent exceptions // in getTokenListFor() int docOffs = map.getElement(line).getEndOffset() - 1; t = new DefaultToken(new char[] { '\n' }, 0, 0, docOffs, Token.WHITESPACE); lastToken.setNextToken(t); lastToken = t; } } // Trim the beginning and end of the token list so that it starts // at startOffs and ends at endOffs. // Be careful and check that startOffs is actually in the list. // startOffs can be < the token list's start if the end "newline" // character of a line is the first character selected (the token // list returned for that line will be null, so the first token in // the final token list will be from the next line and have a // starting offset > startOffs?). if (startOffs >= tokenList.offset) { while (!tokenList.containsPosition(startOffs)) { tokenList = tokenList.getNextToken(); } tokenList.makeStartAt(startOffs); } Token temp = tokenList; // Be careful to check temp for null here. It is possible that no // token contains endOffs, if endOffs is at the end of a line. while (temp != null && !temp.containsPosition(endOffs)) { temp = temp.getNextToken(); } if (temp != null) { temp.textCount = endOffs - temp.offset; temp.setNextToken(null); } return tokenList; } /** * Returns a list of tokens representing the given line. * * @param line * The line number to get tokens for. * @return A linked list of tokens representing the line's text. */ public Token getTokenListForLine(int line) { return ((RSyntaxDocument) getDocument()).getTokenListForLine(line); } /** * Returns the tool tip to display for a mouse event at the given location. This method is overridden to give a * registered parser a chance to display a tool tip (such as an error description when the mouse is over an error * highlight). * * @param e * The mouse event. */ public String getToolTipText(MouseEvent e) { // Check parsers for tool tips first. String text = null; URL imageBase = null; if (parserManager != null) { ToolTipInfo info = parserManager.getToolTipText(e); if (info != null) { // Should always be true text = info.getToolTipText(); // May be null imageBase = info.getImageBase(); // May be null } } if (text == null) { text = super.getToolTipText(e); } // Do we want to use "focusable" tips? if (getUseFocusableTips()) { if (text != null) { if (focusableTip == null) { focusableTip = new FocusableTip(this, parserManager); } focusableTip.setImageBase(imageBase); focusableTip.toolTipRequested(e, text); } // No tooltip text at new location - hide tip window if one is // currently visible else if (focusableTip != null) { focusableTip.possiblyDisposeOfTipWindow(); } return null; } return text; // Standard tool tips } /** * Returns whether the specified token should be underlined. A token is underlined if its syntax style includes * underlining, or if it is a hyperlink and hyperlinks are enabled. * * @param t * The token. * @return Whether the specified token should be underlined. */ public boolean getUnderlineForToken(Token t) { return (t.isHyperlink() && getHyperlinksEnabled()) || syntaxScheme.styles[t.type].underline; } /** * Returns whether "focusable" tool tips are used instead of standard ones. Focusable tool tips are tool tips that * the user can click on, resize, copy from, and click links in. * * @return Whether to use focusable tool tips. * @see #setUseFocusableTips(boolean) * @see FocusableTip */ public boolean getUseFocusableTips() { return useFocusableTips; } /** * Called by constructors to initialize common properties of the text editor. */ protected void init() { // Set some RSyntaxTextArea default values. syntaxStyleKey = SYNTAX_STYLE_NONE; setMatchedBracketBGColor(getDefaultBracketMatchBGColor()); setMatchedBracketBorderColor(getDefaultBracketMatchBorderColor()); setBracketMatchingEnabled(true); setAnimateBracketMatching(true); lastBracketMatchPos = -1; setSelectionColor(getDefaultSelectionColor()); // Set auto-indent related stuff. setAutoIndentEnabled(true); setCloseCurlyBraces(true); setCloseMarkupTags(true); setClearWhitespaceLinesEnabled(true); setHyperlinksEnabled(true); setLinkScanningMask(InputEvent.CTRL_DOWN_MASK); setHyperlinkForeground(Color.BLUE); isScanningForLinks = false; setUseFocusableTips(true); restoreDefaultSyntaxScheme(); } /** * Returns whether or not auto-indent is enabled. * * @return Whether or not auto-indent is enabled. * @see #setAutoIndentEnabled(boolean) */ public boolean isAutoIndentEnabled() { return autoIndentEnabled; } /** * Returns whether or not bracket matching is enabled. * * @return <code>true</code> iff bracket matching is enabled. * @see #setBracketMatchingEnabled */ public final boolean isBracketMatchingEnabled() { return bracketMatchingEnabled; } /** * Returns whether or not lines containing nothing but whitespace are made into blank lines when Enter is pressed in * them. * * @return Whether or not whitespace-only lines are cleared when the user presses Enter on them. * @see #setClearWhitespaceLinesEnabled(boolean) */ public boolean isClearWhitespaceLinesEnabled() { return clearWhitespaceLines; } /** * Returns whether whitespace (spaces and tabs) is visible. * * @return Whether whitespace is visible. * @see #setWhitespaceVisible(boolean) * @see #getEOLMarkersVisible() */ public boolean isWhitespaceVisible() { return whitespaceVisible; } /** * Returns the token at the specified position in the model. * * @param offs * The position in the model. * @return The token, or <code>null</code> if no token is at that position. * @see #viewToToken(Point) */ private Token modelToToken(int offs) { if (offs >= 0) { try { int line = getLineOfOffset(offs); Token t = getTokenListForLine(line); while (t != null && t.isPaintable()) { if (t.containsPosition(offs)) { return t; } t = t.getNextToken(); } } catch (BadLocationException ble) { ble.printStackTrace(); // Never happens } } return null; } /** * The <code>paintComponent</code> method is overridden so we apply any necessary rendering hints to the Graphics * object. */ protected void paintComponent(Graphics g) { super.paintComponent(getGraphics2D(g)); } private void refreshFontMetrics(Graphics2D g2d) { // It is assumed that any rendering hints are already applied to g2d. defaultFontMetrics = g2d.getFontMetrics(getFont()); syntaxScheme.refreshFontMetrics(g2d); if (getLineWrap() == false) { // HORRIBLE HACK! The un-wrapped view needs to refresh its cached // longest line information. SyntaxView sv = (SyntaxView) getUI().getRootView(this).getView(0); sv.calculateLongestLine(); } } /** * Removes an "active line range" listener from this text area. * * @param l * The listener to remove. * @see #removeActiveLineRangeListener(ActiveLineRangeListener) */ public void removeActiveLineRangeListener(ActiveLineRangeListener l) { listenerList.remove(ActiveLineRangeListener.class, l); } /** * Removes a hyperlink listener from this text area. * * @param l * The listener to remove. * @see #addHyperlinkListener(HyperlinkListener) */ public void removeHyperlinkListener(HyperlinkListener l) { listenerList.remove(HyperlinkListener.class, l); } /** * Overridden so we stop this text area's parsers, if any. */ public void removeNotify() { if (parserManager != null) { parserManager.stopParsing(); } super.removeNotify(); } /** * Removes a parser from this text area. * * @param parser * The {@link Parser} to remove. * @return Whether the parser was found and removed. * @see #clearParsers() * @see #addParser(Parser) * @see #getParser(int) */ public boolean removeParser(Parser parser) { boolean removed = false; if (parserManager != null) { removed = parserManager.removeParser(parser); } return removed; } /** * Sets the colors used for syntax highlighting to their defaults. * * @see #setSyntaxScheme(SyntaxScheme) * @see #getSyntaxScheme() * @see #getDefaultSyntaxScheme() */ public void restoreDefaultSyntaxScheme() { setSyntaxScheme(getDefaultSyntaxScheme()); } /** * Attempts to save all currently-known templates to the current template directory, as set by * <code>setTemplateDirectory</code>. Templates will be saved as XML files with names equal to their abbreviations; * for example, a template that expands on the word "forb" will be saved as <code>forb.xml</code>. * * @return Whether or not the save was successful. The save will be unsuccessful if the template directory does not * exist or if it has not been set (i.e., you have not yet called <code>setTemplateDirectory</code>). * @see #getTemplatesEnabled * @see #setTemplateDirectory * @see #setTemplatesEnabled */ public static synchronized boolean saveTemplates() { if (!getTemplatesEnabled()) { return false; } return getCodeTemplateManager().saveTemplates(); } /** * Sets the "active line range." Note that this <code>RSyntaxTextArea</code> itself does nothing with this * information, but if it is contained inside an {@link RTextScrollPane}, the active line range may be displayed in * the icon area of the {@link Gutter}. * <p> * * Note that basic users of <code>RSyntaxTextArea</code> will not call this method directly; rather, it is usually * called by instances of <code>LanguageSupport</code> in the <code>RSTALangaugeSupport</code> library. See <a * href="http://fifesoft.com">http://fifesoft.com</a> for more information about this library. * * @param min * The "minimum" line in the active line range, or <code>-1</code> if the range is being cleared. * @param max * The "maximum" line in the active line range, or <code>-1</code> if the range is being cleared. * @see #addActiveLineRangeListener(ActiveLineRangeListener) */ public void setActiveLineRange(int min, int max) { if (min == -1) { max = -1; // Force max to be -1 if min is. } fireActiveLineRangeEvent(min, max); } /** * Sets whether bracket matching should be animated. This fires a property change event of type * {@link #ANIMATE_BRACKET_MATCHING_PROPERTY}. * * @param animate * Whether to animate bracket matching. * @see #getAnimateBracketMatching() */ public void setAnimateBracketMatching(boolean animate) { if (animate != animateBracketMatching) { animateBracketMatching = animate; if (animate && bracketRepaintTimer == null) { bracketRepaintTimer = new BracketMatchingTimer(); } firePropertyChange(ANIMATE_BRACKET_MATCHING_PROPERTY, !animate, animate); } } /** * Sets whether or not auto-indent is enabled. This fires a property change event of type * {@link #AUTO_INDENT_PROPERTY}. * * @param enabled * Whether or not auto-indent is enabled. * @see #isAutoIndentEnabled() */ public void setAutoIndentEnabled(boolean enabled) { if (autoIndentEnabled != enabled) { autoIndentEnabled = enabled; firePropertyChange(AUTO_INDENT_PROPERTY, !enabled, enabled); } } /** * Sets whether bracket matching is enabled. This fires a property change event of type * {@link #BRACKET_MATCHING_PROPERTY}. * * @param enabled * Whether or not bracket matching should be enabled. * @see #isBracketMatchingEnabled() */ public void setBracketMatchingEnabled(boolean enabled) { if (enabled != bracketMatchingEnabled) { bracketMatchingEnabled = enabled; repaint(); firePropertyChange(BRACKET_MATCHING_PROPERTY, !enabled, enabled); } } /** * Sets whether or not lines containing nothing but whitespace are made into blank lines when Enter is pressed in * them. This method fires a property change event of type {@link #CLEAR_WHITESPACE_LINES_PROPERTY}. * * @param enabled * Whether or not whitespace-only lines are cleared when the user presses Enter on them. * @see #isClearWhitespaceLinesEnabled() */ public void setClearWhitespaceLinesEnabled(boolean enabled) { if (enabled != clearWhitespaceLines) { clearWhitespaceLines = enabled; firePropertyChange(CLEAR_WHITESPACE_LINES_PROPERTY, !enabled, enabled); } } /** * Toggles whether curly braces should be automatically closed when a newline is entered after an opening curly * brace. Note that this property is only honored for languages that use curly braces to denote code blocks. * <p> * * This method fires a property change event of type {@link #CLOSE_CURLY_BRACES_PROPERTY}. * * @param close * Whether curly braces should be automatically closed. * @see #getCloseCurlyBraces() */ public void setCloseCurlyBraces(boolean close) { if (close != closeCurlyBraces) { closeCurlyBraces = close; firePropertyChange(CLOSE_CURLY_BRACES_PROPERTY, !close, close); } } /** * Sets whether closing markup tags should be automatically completed when "<code></</code>" is typed. Note that * this property is only honored for markup languages, such as HTML, XML and PHP. * <p> * * This method fires a property change event of type {@link #CLOSE_MARKUP_TAGS_PROPERTY}. * * @param close * Whether closing markup tags should be automatically completed. * @see #getCloseMarkupTags() */ public void setCloseMarkupTags(boolean close) { if (close != closeMarkupTags) { closeMarkupTags = close; firePropertyChange(CLOSE_MARKUP_TAGS_PROPERTY, !close, close); } } /** * Sets the document used by this text area. This is overridden so that only instances of {@link RSyntaxDocument} * are accepted; for all others, an exception will be thrown. * * @param document * The new document for this text area. * @throws IllegalArgumentException * If the document is not an <code>RSyntaxDocument</code>. */ public void setDocument(Document document) { if (!(document instanceof RSyntaxDocument)) throw new IllegalArgumentException("Documents for " + "RSyntaxTextArea must be instances of " + "RSyntaxDocument!"); super.setDocument(document); } /** * Sets whether EOL markers are visible at the end of each line. This method fires a property change of type * {@link #EOL_VISIBLE_PROPERTY}. * * @param visible * Whether EOL markers are visible. * @see #getEOLMarkersVisible() * @see #setWhitespaceVisible(boolean) */ public void setEOLMarkersVisible(boolean visible) { if (visible != eolMarkersVisible) { eolMarkersVisible = visible; repaint(); firePropertyChange(EOL_VISIBLE_PROPERTY, !visible, visible); } } /** * Sets the font used by this text area. Note that this method does not alter the appearance of an * <code>RSyntaxTextArea</code> since it uses different fonts for each token type. * * @param font * The font. */ public void setFont(Font font) { Font old = super.getFont(); super.setFont(font); // Do this first. // Usually programmers keep a single font for all token types, but // may use bold or italic for styling some. SyntaxScheme scheme = getSyntaxScheme(); if (scheme != null && old != null) { scheme.changeBaseFont(old, font); calculateLineHeight(); } // We must be connected to a screen resource for our // graphics to be non-null. if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); // Updates the margin line. updateMarginLineX(); // Force the current line highlight to be repainted, even // though the caret's location hasn't changed. forceCurrentLineHighlightRepaint(); // Get line number border in text area to repaint again // since line heights have updated. firePropertyChange("font", old, font); // So parent JScrollPane will have its scrollbars updated. revalidate(); } } /** * Sets whether fractional font metrics are enabled. This method fires a property change event of type * {@link #FRACTIONAL_FONTMETRICS_PROPERTY}. * * @param enabled * Whether fractional font metrics are enabled. * @see #getFractionalFontMetricsEnabled() */ public void setFractionalFontMetricsEnabled(boolean enabled) { if (fractionalFontMetricsEnabled != enabled) { fractionalFontMetricsEnabled = enabled; // We must be connected to a screen resource for our graphics to be // non-null. if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); } firePropertyChange(FRACTIONAL_FONTMETRICS_PROPERTY, !enabled, enabled); } } /** * Sets the highlighter used by this text area. * * @param h * The highlighter. * @throws IllegalArgumentException * If <code>h</code> is not an instance of {@link RSyntaxTextAreaHighlighter}. */ public void setHighlighter(Highlighter h) { if (!(h instanceof RSyntaxTextAreaHighlighter)) { throw new IllegalArgumentException("RSyntaxTextArea requires " + "an RSyntaxTextAreaHighlighter for its Highlighter"); } super.setHighlighter(h); } /** * Sets the color to use when painting hyperlinks. * * @param fg * The color to use when painting hyperlinks. * @throws NullPointerException * If <code>fg</code> is <code>null</code>. * @see #getHyperlinkForeground() * @see #setHyperlinksEnabled(boolean) */ public void setHyperlinkForeground(Color fg) { if (fg == null) { throw new NullPointerException("fg cannot be null"); } hyperlinkFG = fg; } /** * Sets whether hyperlinks are enabled for this text area. This method fires a property change event of type * {@link #HYPERLINKS_ENABLED_PROPERTY}. * * @param enabled * Whether hyperlinks are enabled. * @see #getHyperlinksEnabled() */ public void setHyperlinksEnabled(boolean enabled) { if (this.hyperlinksEnabled != enabled) { this.hyperlinksEnabled = enabled; repaint(); firePropertyChange(HYPERLINKS_ENABLED_PROPERTY, !enabled, enabled); } } /** * Sets the mask for the key used to toggle whether we are scanning for hyperlinks with mouse hovering. * * @param mask * The mask to use. This should be a value such as {@link InputEvent#CTRL_DOWN_MASK} or * {@link InputEvent#META_DOWN_MASK}. For invalid values, behavior is undefined. * @see InputEvent */ public void setLinkScanningMask(int mask) { if (mask == InputEvent.CTRL_DOWN_MASK || mask == InputEvent.META_DOWN_MASK || mask == InputEvent.ALT_DOWN_MASK || mask == InputEvent.SHIFT_DOWN_MASK) { linkScanningMask = mask; } } /** * Toggles whether "mark occurrences" is enabled. This method fires a property change event of type * {@link #MARK_OCCURRENCES_PROPERTY}. * * @param markOccurrences * Whether "Mark Occurrences" should be enabled. * @see #getMarkOccurrences() * @see #setMarkOccurrencesColor(Color) */ public void setMarkOccurrences(boolean markOccurrences) { if (markOccurrences) { if (markOccurrencesSupport == null) { markOccurrencesSupport = new MarkOccurrencesSupport(); markOccurrencesSupport.install(this); firePropertyChange(MARK_OCCURRENCES_PROPERTY, false, true); } } else { if (markOccurrencesSupport != null) { markOccurrencesSupport.uninstall(); markOccurrencesSupport = null; firePropertyChange(MARK_OCCURRENCES_PROPERTY, true, false); } } } /** * Sets the "mark occurrences" color. * * @param color * The new color. This cannot be <code>null</code>. * @see #getMarkOccurrencesColor() * @see #setMarkOccurrences(boolean) */ public void setMarkOccurrencesColor(Color color) { markOccurrencesColor = color; if (markOccurrencesSupport != null) { markOccurrencesSupport.setColor(color); } } /** * Sets the color used as the background for a matched bracket. * * @param color * The color to use. * @see #getMatchedBracketBGColor * @see #setMatchedBracketBorderColor */ public void setMatchedBracketBGColor(Color color) { matchedBracketBGColor = color; if (match != null) repaint(); } /** * Sets the color used as the border for a matched bracket. * * @param color * The color to use. * @see #getMatchedBracketBorderColor * @see #setMatchedBracketBGColor */ public void setMatchedBracketBorderColor(Color color) { matchedBracketBorderColor = color; if (match != null) repaint(); } /** * Sets what type of syntax highlighting this editor is doing. This method fires a property change of type * {@link #SYNTAX_STYLE_PROPERTY}. * * @param styleKey * The syntax editing style to use, for example, {@link SyntaxConstants#SYNTAX_STYLE_NONE} or * {@link SyntaxConstants#SYNTAX_STYLE_JAVA}. * @see #getSyntaxEditingStyle() * @see SyntaxConstants */ public void setSyntaxEditingStyle(String styleKey) { if (styleKey == null) { styleKey = SYNTAX_STYLE_NONE; } if (!styleKey.equals(syntaxStyleKey)) { String oldStyle = syntaxStyleKey; syntaxStyleKey = styleKey; ((RSyntaxDocument) getDocument()).setSyntaxStyle(styleKey); firePropertyChange(SYNTAX_STYLE_PROPERTY, oldStyle, styleKey); } } /** * Sets all of the colors used in syntax highlighting to the colors specified. This uses a shallow copy of the color * scheme so that multiple text areas can share the same color scheme and have their properties changed * simultaneously. * <p> * * This method fires a property change event of type {@link #SYNTAX_SCHEME_PROPERTY}. * * @param scheme * The instance of <code>SyntaxScheme</code> to use. * @see #getSyntaxScheme() */ public void setSyntaxScheme(SyntaxScheme scheme) { // NOTE: We don't check whether colorScheme is the same as the // current scheme because DecreaseFontSizeAction and // IncreaseFontSizeAction need it this way. // FIXME: Find a way around this. SyntaxScheme old = this.syntaxScheme; this.syntaxScheme = scheme; // Recalculate the line height. We do this here instead of in // refreshFontMetrics() as this method is called less often and we // don't need the rendering hints to get the font's height. calculateLineHeight(); if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); } // Updates the margin line. updateMarginLineX(); // Force the current line highlight to be repainted, even though // the caret's location hasn't changed. forceCurrentLineHighlightRepaint(); // So encompassing JScrollPane will have its scrollbars updated. revalidate(); firePropertyChange(SYNTAX_SCHEME_PROPERTY, old, this.syntaxScheme); } /** * If templates are enabled, all currently-known templates are forgotten and all templates are loaded from all files * in the specified directory ending in "*.xml". If templates aren't enabled, nothing happens. * * @param dir * The directory containing files ending in extension <code>.xml</code> that contain templates to load. * @return <code>true</code> if the load was successful; <code>false</code> if either templates aren't currently * enabled or the load failed somehow (most likely, the directory doesn't exist). * @see #getTemplatesEnabled * @see #setTemplatesEnabled * @see #saveTemplates */ public static synchronized boolean setTemplateDirectory(String dir) { if (getTemplatesEnabled() && dir != null) { File directory = new File(dir); if (directory.isDirectory()) { return getCodeTemplateManager(). setTemplateDirectory(directory) > -1; } boolean created = directory.mkdir(); if (created) { return getCodeTemplateManager(). setTemplateDirectory(directory) > -1; } } return false; } /** * Enables or disables templates. * <p> * * Templates are a set of "shorthand identifiers" that you can configure so that you only have to type a short * identifier (such as "forb") to insert a larger amount of code into the document (such as: * <p> * * <pre> * for (<caret>) { * * } * </pre> * * Templates are a shared resource among all instances of <code>RSyntaxTextArea</code>; that is, templates can only * be enabled/disabled for all text areas globally, not individually, and all text areas have access of the same * templates. This should not be an issue; rather, it should be beneficial as it promotes uniformity among all text * areas in an application. * * @param enabled * Whether or not templates should be enabled. * @see #getTemplatesEnabled() */ public static synchronized void setTemplatesEnabled(boolean enabled) { templatesEnabled = enabled; } /** * Sets the rendering hint to use when anti-aliasing text in this editor. * * @param aaHintFieldName * The name of a field in <code>java.awt.RenderingHints</code>. If an unknown or unsupported field name * is specified (such as a 1.6+ hint being specified when this is a 1.4/1.5 JVM), <code>null</code> is * used instead. A value of <code>null</code> means "no antialiasing." * @see #getTextAntiAliasHint() */ public void setTextAntiAliasHint(String aaHintFieldName) { // System.out.println("Trying to set AA hint to: " + aaHintFieldName); // If the new AA hint is null, disable text anti-aliasing. if (aaHintFieldName == null && this.aaHintFieldName != null) { String old = this.aaHintFieldName; this.aaHint = null; this.aaHintFieldName = null; // We must be connected to a screen resource for our graphics // to be non-null. if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); } firePropertyChange(ANTIALIAS_PROPERTY, old, null); repaint(); } // Otherwise, if they're specifying a new hint type, use it instead. else if (aaHintFieldName != null && !aaHintFieldName.equals(this.aaHintFieldName)) { String old = this.aaHintFieldName; try { Field f = RenderingHints.class.getField(aaHintFieldName); this.aaHint = f.get(null); this.aaHintFieldName = aaHintFieldName; } catch (RuntimeException re) { // Re-throw (keep FindBugs happy) } catch (/* NoSuchField|IllegalAccess */Exception e) { this.aaHint = RenderingHints.VALUE_TEXT_ANTIALIAS_OFF; this.aaHintFieldName = "VALUE_TEXT_ANTIALIAS_OFF"; } // We must be connected to a screen resource for our graphics // to be non-null. if (isDisplayable()) { refreshFontMetrics(getGraphics2D(getGraphics())); } firePropertyChange(ANTIALIAS_PROPERTY, old, this.aaHintFieldName); repaint(); } // System.out.println("... Actual new value: " + this.aaHintFieldName); } /** * Sets whether "focusable" tool tips are used instead of standard ones. Focusable tool tips are tool tips that the * user can click on, resize, copy from, and clink links in. This method fires a property change event of type * {@link #FOCUSABLE_TIPS_PROPERTY}. * * @param use * Whether to use focusable tool tips. * @see #getUseFocusableTips() * @see FocusableTip */ public void setUseFocusableTips(boolean use) { if (use != useFocusableTips) { useFocusableTips = use; firePropertyChange(FOCUSABLE_TIPS_PROPERTY, !use, use); } } /** * Sets whether whitespace is visible. This method fires a property change of type * {@link #VISIBLE_WHITESPACE_PROPERTY}. * * @param visible * Whether whitespace should be visible. * @see #isWhitespaceVisible */ public void setWhitespaceVisible(boolean visible) { if (whitespaceVisible != visible) { whitespaceVisible = visible; ((RSyntaxDocument) getDocument()).setWhitespaceVisible( visible, this); repaint(); firePropertyChange(VISIBLE_WHITESPACE_PROPERTY, !visible, visible); } } /** * Returns the token at the specified position in the view. * * @param p * The position in the view. * @return The token, or <code>null</code> if no token is at that position. * @see #modelToToken(int) */ /* * TODO: This is a little inefficient. This should convert view coordinates to the underlying token (if any). The * way things currently are, we're calling getTokenListForLine() twice (once in viewToModel() and once here). */ private Token viewToToken(Point p) { return modelToToken(viewToModel(p)); } /** * A timer that animates the "bracket matching" animation. */ private class BracketMatchingTimer extends Timer implements ActionListener { private int pulseCount; public BracketMatchingTimer() { super(20, null); addActionListener(this); setCoalesce(false); } public void actionPerformed(ActionEvent e) { if (isBracketMatchingEnabled()) { if (match != null) { if (pulseCount < 5) { pulseCount++; match.x--; match.y--; match.width += 2; match.height += 2; repaint(match.x, match.y, match.width, match.height); } else if (pulseCount < 7) { pulseCount++; match.x++; match.y++; match.width -= 2; match.height -= 2; repaint(match.x - 2, match.y - 2, match.width + 5, match.height + 5); } else { stop(); pulseCount = 0; } } } } public void start() { match.x += 3; match.y += 3; match.width -= 6; match.height -= 6; // So animation can "grow" match pulseCount = 0; super.start(); } } /** * Handles hyperlinks. */ private class RSyntaxTextAreaMutableCaretEvent extends RTextAreaMutableCaretEvent { protected RSyntaxTextAreaMutableCaretEvent(RTextArea textArea) { super(textArea); } public void mouseClicked(MouseEvent e) { if (getHyperlinksEnabled() && isScanningForLinks && hoveredOverLinkOffset > -1) { Token t = modelToToken(hoveredOverLinkOffset); URL url = null; String desc = null; try { String temp = t.getLexeme(); // URI's need "http://" prefix for web URL's to work. if (temp.startsWith("www.")) { temp = "http://" + temp; } url = new URL(temp); } catch (MalformedURLException mue) { desc = mue.getMessage(); } HyperlinkEvent he = new HyperlinkEvent(this, HyperlinkEvent.EventType.ACTIVATED, url, desc); fireHyperlinkUpdate(he); } } public void mouseMoved(MouseEvent e) { super.mouseMoved(e); if (getHyperlinksEnabled()) { if ((e.getModifiersEx() & linkScanningMask) != 0) { isScanningForLinks = true; Token t = viewToToken(e.getPoint()); Cursor c2 = null; if (t != null && t.isHyperlink()) { hoveredOverLinkOffset = t.offset; c2 = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } else { c2 = Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR); hoveredOverLinkOffset = -1; } if (getCursor() != c2) { setCursor(c2); // TODO: Repaint just the affected line(s). repaint(); // Link either left or went into. } } else { if (isScanningForLinks) { Cursor c = getCursor(); isScanningForLinks = false; hoveredOverLinkOffset = -1; if (c != null && c.getType() == Cursor.HAND_CURSOR) { setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); repaint(); // TODO: Repaint just the affected line. } } } } } } }