/* * 04/07/2005 * * RTextAreaBase.java - The base class for an RTextArea. * * This library is distributed under a modified BSD license. See the included * RSyntaxTextArea.License.txt file for details. */ package org.fife.ui.rtextarea; import java.awt.AWTEvent; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Image; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ComponentEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.event.CaretEvent; import javax.swing.plaf.ColorUIResource; import javax.swing.plaf.TextUI; import javax.swing.text.AbstractDocument; import javax.swing.text.BadLocationException; import javax.swing.text.Caret; import javax.swing.text.StyleContext; /** * This is the base class for <code>RTextArea</code>; basically it's just an * extension of <code>javax.swing.JTextArea</code> adding a bunch of properties. * <p> * * This class is only supposed to be overridden by <code>RTextArea</code>. * * @author Robert Futrell * @version 0.8 */ public abstract class RTextAreaBase extends JTextArea { public static final String BACKGROUND_IMAGE_PROPERTY = "background.image"; public static final String CURRENT_LINE_HIGHLIGHT_COLOR_PROPERTY = "RTA.currentLineHighlightColor"; public static final String CURRENT_LINE_HIGHLIGHT_FADE_PROPERTY = "RTA.currentLineHighlightFade"; public static final String HIGHLIGHT_CURRENT_LINE_PROPERTY = "RTA.currentLineHighlight"; public static final String ROUNDED_SELECTION_PROPERTY = "RTA.roundedSelection"; private boolean tabsEmulatedWithSpaces; // If true, tabs will be expanded to spaces. private boolean highlightCurrentLine; // If true, the current line is highlighted. private Color currentLineColor; // The color used to highlight the current line. private boolean marginLineEnabled; // If true, paint a "margin line." private Color marginLineColor; // The color used to paint the margin line. private int marginLineX; // The x-location of the margin line. private int marginSizeInChars; // How many 'm' widths the margin line is over. private boolean fadeCurrentLineHighlight; // "Fade effect" for current line highlight. private boolean roundedSelectionEdges; private int previousCaretY; int currentCaretY; // Used to know when to rehighlight current line. private BackgroundPainterStrategy backgroundPainter; // Paints the background. private RTAMouseListener mouseListener; private static final Color DEFAULT_CARET_COLOR = new ColorUIResource(255,51,51); private static final Color DEFAULT_CURRENT_LINE_HIGHLIGHT_COLOR = new Color(255,255,170); private static final Color DEFAULT_MARGIN_LINE_COLOR = new Color(255,224,224); private static final int DEFAULT_TAB_SIZE = 4; private static final int DEFAULT_MARGIN_LINE_POSITION = 80; /** * Constructor. */ public RTextAreaBase() { init(); } /** * Constructor. * * @param doc The document for the editor. */ public RTextAreaBase(AbstractDocument doc) { super(doc); init(); } /** * Constructor. * * @param text The initial text to display. */ public RTextAreaBase(String text) { // Don't call super(text) to avoid NPE due to our funky RTextAreaUI... init(); setText(text); } /** * 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 RTextAreaBase(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 RTextAreaBase(String text, int rows, int cols) { // Don't call this super() due to NPE from our funky RTextAreaUI... //super(text, rows, cols); super(rows, cols); init(); setText(text); } /** * 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 RTextAreaBase(AbstractDocument doc, String text, int rows, int cols) { // Don't call super() with text due to NPE from our funky RTextAreaUI... super(doc, null/*text*/, rows, cols); init(); setText(text); } /** * Adds listeners that listen for changes to the current line, so we can * update our "current line highlight." This is needed only because of an * apparent difference between the JRE 1.4.2 and 1.5.0 (needed on 1.4.2, * not needed on 1.5.0). */ private void addCurrentLineHighlightListeners() { boolean add = true; MouseMotionListener[] mouseMotionListeners = getMouseMotionListeners(); for (int i=0; i<mouseMotionListeners.length; i++) { if (mouseMotionListeners[i]==mouseListener) { add = false; break; } } if (add==true) { //System.err.println("Adding mouse motion listener!"); addMouseMotionListener(mouseListener); } MouseListener[] mouseListeners = getMouseListeners(); for (int i=0; i<mouseListeners.length; i++) { if (mouseListeners[i]==mouseListener) { add = false; break; } } if (add==true) { //System.err.println("Adding mouse listener!"); addMouseListener(mouseListener); } } @Override public void addNotify() { super.addNotify(); // If the caret is not on the first line, we must recalculate the line // highlight y-offset after the text area is sized. This seems to be // the best way to do it. if (getCaretPosition() != 0) { SwingUtilities.invokeLater(new Runnable() { public void run() { possiblyUpdateCurrentLineHighlightLocation(); } }); } } /* * TODO: Figure out why RTextArea doesn't work with RTL orientation! */ // public void applyComponentOrientation(ComponentOrientation orientation) { // super.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); // } /** * Converts all instances of a number of spaces equal to a tab size * into a tab in this text area. * * @see #convertTabsToSpaces * @see #getTabsEmulated * @see #setTabsEmulated */ public void convertSpacesToTabs() { // FIXME: This is inefficient and will yield an OutOfMemoryError if // done on a large document. We should scan 1 line at a time and // replace; it'll be slower but safer. int caretPosition = getCaretPosition(); int tabSize = getTabSize(); String tabInSpaces = ""; for (int i=0; i<tabSize; i++) tabInSpaces += " "; String text = getText(); setText(text.replaceAll(tabInSpaces, "\t")); int newDocumentLength = getDocument().getLength(); // Place the caret back in its proper position. if (caretPosition<newDocumentLength) setCaretPosition(caretPosition); else setCaretPosition(newDocumentLength-1); } /** * Converts all instances of a tab into a number of spaces equivalent * to a tab in this text area. * * @see #convertSpacesToTabs * @see #getTabsEmulated * @see #setTabsEmulated */ public void convertTabsToSpaces() { // FIXME: This is inefficient and will yield an OutOfMemoryError if // done on a large document. We should scan 1 line at a time and // replace; it'll be slower but safer. int caretPosition = getCaretPosition(); int tabSize = getTabSize(); StringBuilder tabInSpaces = new StringBuilder(); for (int i=0; i<tabSize; i++) tabInSpaces.append(' '); String text = getText(); setText(text.replaceAll("\t", tabInSpaces.toString())); // Put caret back at same place in document. setCaretPosition(caretPosition); } /** * Returns the caret event/mouse listener for <code>RTextArea</code>s. * * @return The caret event/mouse listener. */ protected abstract RTAMouseListener createMouseListener(); /** * Returns the a real UI to install on this text component. Subclasses * can override this method to return an extended version of * <code>RTextAreaUI</code>. * * @return The UI. */ protected abstract RTextAreaUI createRTextAreaUI(); /** * Forces the current line highlight to be repainted. This hack is * necessary for those situations when the view (appearance) changes * but the caret's location hasn't (and thus the current line highlight * coordinates won't get changed). Examples of this are when * word wrap is toggled and when syntax styles are changed in an * <code>RSyntaxTextArea</code>. */ protected void forceCurrentLineHighlightRepaint() { // Check isShowing() to prevent BadLocationException // in constructor if linewrap is set to true. if (isShowing()) { // Changing previousCaretY makes us sure to get a repaint. previousCaretY = -1; // Trick it into checking for the need to repaint by firing // a false caret event. fireCaretUpdate(mouseListener); } } /** * Returns the <code>java.awt.Color</code> used as the background color for * this text area. If a <code>java.awt.Image</code> image is currently * being used instead, <code>null</code> is returned. * * @return The current background color, or <code>null</code> if an image * is currently the background. */ @Override public final Color getBackground() { Object bg = getBackgroundObject(); return (bg instanceof Color) ? (Color)bg : null; } /** * Returns the image currently used for the background. * If the current background is currently a <code>java.awt.Color</code> and * not a <code>java.awt.Image</code>, then <code>null</code> is returned. * * @return A <code>java.awt.Image</code> used for the background, or * <code>null</code> if the background is not an image. * @see #setBackgroundImage */ public final Image getBackgroundImage() { Object bg = getBackgroundObject(); return (bg instanceof Image) ? (Image)bg : null; } /** * Returns the <code>Object</code> representing the background for all * documents in this tabbed pane; either a <code>java.awt.Color</code> or a * <code>java.lang.Image</code> containing the image used as the background * for this text area. * * @return The <code>Object</code> used for the background. * @see #setBackgroundObject(Object newBackground) */ public final Object getBackgroundObject() { if (backgroundPainter==null) return null; return (backgroundPainter instanceof ImageBackgroundPainterStrategy) ? (Object)((ImageBackgroundPainterStrategy)backgroundPainter). getMasterImage() : (Object)((ColorBackgroundPainterStrategy)backgroundPainter). getColor(); } /** * Gets the line number that the caret is on. * * @return The zero-based line number that the caret is on. */ public final int getCaretLineNumber() { try { return getLineOfOffset(getCaretPosition()); } catch (BadLocationException ble) { return 0; // Never happens } } /** * Gets the position from the beginning of the current line that the caret * is on. * * @return The zero-based position from the beginning of the current line * that the caret is on. */ public final int getCaretOffsetFromLineStart() { try { int pos = getCaretPosition(); return pos - getLineStartOffset(getLineOfOffset(pos)); } catch (BadLocationException ble) { return 0; // Never happens } } /** * Returns the y-offset of the caret. * * @return The y-offset of the caret. */ protected int getCurrentCaretY() { return currentCaretY; } /** * Returns the color being used to highlight the current line. Note that * if highlighting the current line is turned off, you will not be seeing * this highlight. * * @return The color being used to highlight the current line. * @see #getHighlightCurrentLine() * @see #setHighlightCurrentLine(boolean) * @see #setCurrentLineHighlightColor */ public Color getCurrentLineHighlightColor() { return currentLineColor; } /** * Returns the default caret color. * * @return The default caret color. */ public static final Color getDefaultCaretColor() { return DEFAULT_CARET_COLOR; } /** * Returns the "default" color for highlighting the current line. Note * that this color was chosen only because it looks nice (to me) against a * white background. * * @return The default color for highlighting the current line. */ public static final Color getDefaultCurrentLineHighlightColor() { return DEFAULT_CURRENT_LINE_HIGHLIGHT_COLOR; } /** * Returns the default font for text areas. * * @return The default font. */ public static final Font getDefaultFont() { // Use StyleContext to get a composite font for better Asian language // support; see Sun bug S282887. StyleContext sc = StyleContext.getDefaultStyleContext(); Font font = null; if (isOSX()) { // Snow Leopard (1.6) uses Menlo as default monospaced font, // pre-Snow Leopard used Monaco. font = sc.getFont("Menlo", Font.PLAIN, 12); if (!"Menlo".equals(font.getFamily())) { font = sc.getFont("Monaco", Font.PLAIN, 12); if (!"Monaco".equals(font.getFamily())) { // Shouldn't happen font = sc.getFont("Monospaced", Font.PLAIN, 13); } } } else { // Consolas added in Vista, used by VS2010+. font = sc.getFont("Consolas", Font.PLAIN, 13); if (!"Consolas".equals(font.getFamily())) { font = sc.getFont("Monospaced", Font.PLAIN, 13); } } //System.out.println(font.getFamily() + ", " + font.getName()); return font; } /** * Returns the default foreground color for text in this text area. * * @return The default foreground color. */ public static final Color getDefaultForeground() { return Color.BLACK; } /** * Returns the default color for the margin line. * * @return The default margin line color. * @see #getMarginLineColor() * @see #setMarginLineColor(Color) */ public static final Color getDefaultMarginLineColor() { return DEFAULT_MARGIN_LINE_COLOR; } /** * Returns the default margin line position. * * @return The default margin line position. * @see #getMarginLinePosition * @see #setMarginLinePosition */ public static final int getDefaultMarginLinePosition() { return DEFAULT_MARGIN_LINE_POSITION; } /** * Returns the default tab size, in spaces. * * @return The default tab size. */ public static final int getDefaultTabSize() { return DEFAULT_TAB_SIZE; } /** * Returns whether the current line highlight is faded. * * @return Whether the current line highlight is faded. * @see #setFadeCurrentLineHighlight */ public boolean getFadeCurrentLineHighlight() { return fadeCurrentLineHighlight; } /** * Returns whether or not the current line is highlighted. * * @return Whether or the current line is highlighted. * @see #setHighlightCurrentLine(boolean) * @see #getCurrentLineHighlightColor * @see #setCurrentLineHighlightColor */ public boolean getHighlightCurrentLine() { return highlightCurrentLine; } /** * Returns the offset of the last character of the line that the caret is * on. * * @return The last offset of the line that the caret is currently on. */ public final int getLineEndOffsetOfCurrentLine() { try { return getLineEndOffset(getCaretLineNumber()); } catch (BadLocationException ble) { return 0; // Never happens } } /** * Returns the height of a line of text in this text area. * * @return The height of a line of text. */ public int getLineHeight() { return getRowHeight(); } /** * Returns the offset of the first character of the line that the caret is * on. * * @return The first offset of the line that the caret is currently on. */ public final int getLineStartOffsetOfCurrentLine() { try { return getLineStartOffset(getCaretLineNumber()); } catch (BadLocationException ble) { return 0; // Never happens } } /** * Returns the color used to paint the margin line. * * @return The margin line color. * @see #setMarginLineColor(Color) */ public Color getMarginLineColor() { return marginLineColor; } /** * Returns the margin line position (in pixels) from the left-hand side of * the text area. * * @return The margin line position. * @see #getDefaultMarginLinePosition * @see #setMarginLinePosition */ public int getMarginLinePixelLocation() { return marginLineX; } /** * Returns the margin line position (which is the number of 'm' widths in * the current font the margin line is over). * * @return The margin line position. * @see #getDefaultMarginLinePosition * @see #setMarginLinePosition */ public int getMarginLinePosition() { return marginSizeInChars; } /** * Returns whether selection edges are rounded in this text area. * * @return Whether selection edges are rounded. * @see #setRoundedSelectionEdges(boolean) */ public boolean getRoundedSelectionEdges() { return roundedSelectionEdges; } /** * Returns whether or not tabs are emulated with spaces (i.e., "soft" * tabs). * * @return <code>true</code> if tabs are emulated with spaces; * <code>false</code> if they aren't. * @see #setTabsEmulated */ public boolean getTabsEmulated() { return tabsEmulatedWithSpaces; } /** * Initializes this text area. */ protected void init() { // Sets the UI. Note that setUI() is overridden in RTextArea to only // update the popup menu; this method must be called to set the real // UI. This is done because the look and feel of an RTextArea is // independent of the installed Java look and feels. setRTextAreaUI(createRTextAreaUI()); // So we get notified when the component is resized. enableEvents(AWTEvent.COMPONENT_EVENT_MASK|AWTEvent.KEY_EVENT_MASK); // Defaults for various properties. setHighlightCurrentLine(true); setCurrentLineHighlightColor(getDefaultCurrentLineHighlightColor()); setMarginLineEnabled(false); setMarginLineColor(getDefaultMarginLineColor()); setMarginLinePosition(getDefaultMarginLinePosition()); setBackgroundObject(Color.WHITE); setWrapStyleWord(true);// We only support wrapping at word boundaries. setTabSize(5); setForeground(Color.BLACK); setTabsEmulated(false); // Stuff needed by the caret listener below. previousCaretY = currentCaretY = getInsets().top; // Stuff to highlight the current line. mouseListener = createMouseListener(); // Also acts as a focus listener so we can update our shared actions // (cut, copy, etc. on the popup menu). addFocusListener(mouseListener); addCurrentLineHighlightListeners(); } /** * Returns whether or not the margin line is being painted. * * @return Whether or not the margin line is being painted. * @see #setMarginLineEnabled */ public boolean isMarginLineEnabled() { return marginLineEnabled; } /** * Returns whether the OS we're running on is OS X. * * @return Whether the OS we're running on is OS X. */ public static boolean isOSX() { // Recommended at: // http://developer.apple.com/mac/library/technotes/tn2002/tn2110.html String osName = System.getProperty("os.name").toLowerCase(); return osName.startsWith("mac os x"); } /** * Paints the text area. * * @param g The graphics context with which to paint. */ @Override protected void paintComponent(Graphics g) { //long startTime = System.currentTimeMillis(); backgroundPainter.paint(g, getVisibleRect()); // Paint the main part of the text area. TextUI ui = getUI(); if (ui != null) { // Not allowed to modify g, so make a copy. Graphics scratchGraphics = g.create(); try { ui.update(scratchGraphics, this); } finally { scratchGraphics.dispose(); } } //long endTime = System.currentTimeMillis(); //System.err.println(endTime-startTime); } /** * Updates the current line highlight location. */ protected void possiblyUpdateCurrentLineHighlightLocation() { int width = getWidth(); int lineHeight = getLineHeight(); int dot = getCaretPosition(); // If we're wrapping lines we need to check the actual y-coordinate // of the caret, not just the line number, since a single logical // line can span multiple physical lines. if (getLineWrap()) { try { Rectangle temp = modelToView(dot); if (temp!=null) { currentCaretY = temp.y; } } catch (BadLocationException ble) { ble.printStackTrace(); // Should never happen. } } // No line wrap - we can simply check the line number (quicker). else { // Document doc = getDocument(); // if (doc!=null) { // Element map = doc.getDefaultRootElement(); // int caretLine = map.getElementIndex(dot); // Rectangle alloc = ((RTextAreaUI)getUI()). // getVisibleEditorRect(); // if (alloc!=null) // currentCaretY = alloc.y + caretLine*lineHeight; // } // Modified for code folding requirements try { Rectangle temp = modelToView(dot); if (temp!=null) { currentCaretY = temp.y; } } catch (BadLocationException ble) { ble.printStackTrace(); // Should never happen. } } // Repaint current line (to fill in entire highlight), and old line // (to erase entire old highlight) if necessary. Always repaint // current line in case selection is added or removed. repaint(0,currentCaretY, width,lineHeight); if (previousCaretY!=currentCaretY) { repaint(0,previousCaretY, width,lineHeight); } previousCaretY = currentCaretY; } /** * Overridden so we can tell when the text area is resized and update the * current-line highlight, if necessary (i.e., if it is enabled and if * lineWrap is enabled. * * @param e The component event about to be sent to all registered * <code>ComponentListener</code>s. */ @Override protected void processComponentEvent(ComponentEvent e) { // In line wrap mode, resizing the text area means that the caret's // "line" could change - not to a different logical line, but a // different physical one. So, here we force a repaint of the current // line's highlight if necessary. if (e.getID()==ComponentEvent.COMPONENT_RESIZED && getLineWrap()==true && getHighlightCurrentLine()) { previousCaretY = -1; // So we are sure to repaint. fireCaretUpdate(mouseListener); } super.processComponentEvent(e); } /** * Sets the background color of this text editor. Note that this is * equivalent to calling <code>setBackgroundObject(bg)</code>.<p> * * NOTE: the opaque property is set to <code>true</code> when the * background is set to a color with 1.0 alpha (by this method). When an * image is used for the background, opaque is set to false. This is * because we perform better when setOpaque is true, but if we use an * image for the background when opaque is true, we get on-screen * garbage when the user scrolls via the arrow keys. Thus we * need setOpaque to be false in that case.<p> * You never have to change the opaque property yourself; it is always done * for you. * * @param bg The color to use as the background color. */ @Override public void setBackground(Color bg) { Object oldBG = getBackgroundObject(); if (oldBG instanceof Color) { // Just change color of strategy. ((ColorBackgroundPainterStrategy)backgroundPainter). setColor(bg); } else { // Was an image painter... backgroundPainter = new ColorBackgroundPainterStrategy(bg); } setOpaque(bg==null || bg.getAlpha()==0xff); firePropertyChange("background", oldBG, bg); repaint(); } /** * Sets this image as the background image. This method fires a * property change event of type {@link #BACKGROUND_IMAGE_PROPERTY}.<p> * * NOTE: the opaque property is set to <code>true</code> when the * background is set to a color. When an image is used for the * background (by this method), opaque is set to false. This is because * we perform better when setOpaque is true, but if we use an * image for the background when opaque is true, we get on-screen * garbage when the user scrolls via the arrow keys. Thus we * need setOpaque to be false in that case.<p> * You never have to change the opaque property yourself; it is always done * for you. * * @param image The image to use as this text area's background. * @see #getBackgroundImage */ public void setBackgroundImage(Image image) { Object oldBG = getBackgroundObject(); if (oldBG instanceof Image) { // Just change image being displayed. ((ImageBackgroundPainterStrategy)backgroundPainter). setImage(image); } else { // Was a color strategy... ImageBackgroundPainterStrategy strategy = new BufferedImageBackgroundPainterStrategy(this); strategy.setImage(image); backgroundPainter = strategy; } setOpaque(false); firePropertyChange(BACKGROUND_IMAGE_PROPERTY, oldBG, image); repaint(); } /** * Makes the background into this <code>Object</code>. * * @param newBackground The <code>java.awt.Color</code> or * <code>java.awt.Image</code> object. If <code>newBackground</code> * is not either of these, the background is set to plain white. */ public void setBackgroundObject(Object newBackground) { if (newBackground instanceof Color) { setBackground((Color)newBackground); } else if (newBackground instanceof Image) { setBackgroundImage((Image)newBackground); } else { setBackground(Color.WHITE); } } /* * TODO: Figure out why RTextArea doesn't work with RTL (e.g. Arabic) * and fix it! */ // public void setComponentOrientation(ComponentOrientation o) { // if (!o.isLeftToRight()) { // o = ComponentOrientation.LEFT_TO_RIGHT; // } // super.setComponentOrientation(o); // } /** * Sets the color to use to highlight the current line. Note that if * highlighting the current line is turned off, you will not be able to * see this highlight. This method fires a property change of type * {@link #CURRENT_LINE_HIGHLIGHT_COLOR_PROPERTY}. * * @param color The color to use to highlight the current line. * @throws NullPointerException if <code>color</code> is <code>null</code>. * @see #getHighlightCurrentLine() * @see #setHighlightCurrentLine(boolean) * @see #getCurrentLineHighlightColor */ public void setCurrentLineHighlightColor(Color color) { if (color==null) throw new NullPointerException(); if (!color.equals(currentLineColor)) { Color old = currentLineColor; currentLineColor = color; firePropertyChange(CURRENT_LINE_HIGHLIGHT_COLOR_PROPERTY, old, color); } } /** * Sets whether the current line highlight should have a "fade" effect. * This method fires a property change event of type * <code>CURRENT_LINE_HIGHLIGHT_FADE_PROPERTY</code>. * * @param fade Whether the fade effect should be enabled. * @see #getFadeCurrentLineHighlight */ public void setFadeCurrentLineHighlight(boolean fade) { if (fade!=fadeCurrentLineHighlight) { fadeCurrentLineHighlight = fade; if (getHighlightCurrentLine()) forceCurrentLineHighlightRepaint(); firePropertyChange(CURRENT_LINE_HIGHLIGHT_FADE_PROPERTY, !fade, fade); } } /** * Sets the font for this text area. This is overridden only so that we * can update the size of the "current line highlight" and the location of * the "margin line," if necessary. * * @param font The font to use for this text component. */ @Override public void setFont(Font font) { if (font!=null && font.getSize()<=0) { throw new IllegalArgumentException("Font size must be > 0"); } super.setFont(font); if (font!=null) { updateMarginLineX(); if (highlightCurrentLine) possiblyUpdateCurrentLineHighlightLocation(); } } /** * Sets whether or not the current line is highlighted. This method * fires a property change of type {@link #HIGHLIGHT_CURRENT_LINE_PROPERTY}. * * @param highlight Whether or not to highlight the current line. * @see #getHighlightCurrentLine() * @see #getCurrentLineHighlightColor * @see #setCurrentLineHighlightColor */ public void setHighlightCurrentLine(boolean highlight) { if (highlight!=highlightCurrentLine) { highlightCurrentLine = highlight; firePropertyChange(HIGHLIGHT_CURRENT_LINE_PROPERTY, !highlight, highlight); repaint(); // Repaint entire width of line. } } /** * Sets whether or not word wrap is enabled. This is overridden so that * the "current line highlight" gets updated if it needs to be. * * @param wrap Whether or not word wrap should be enabled. */ @Override public void setLineWrap(boolean wrap) { super.setLineWrap(wrap); forceCurrentLineHighlightRepaint(); } /** * Overridden to update the current line highlight location. * * @param insets The new insets. */ @Override public void setMargin(Insets insets) { Insets old = getInsets(); int oldTop = old!=null ? old.top : 0; int newTop = insets!=null ? insets.top : 0; if (oldTop!=newTop) { // The entire editor will be automatically repainted if it is // visible, so no need to call repaint() or forceCurrentLine...(). previousCaretY = currentCaretY = newTop; } super.setMargin(insets); } /** * Sets the color used to paint the margin line. * * @param color The new margin line color. * @see #getDefaultMarginLineColor() * @see #getMarginLineColor() */ public void setMarginLineColor(Color color) { marginLineColor = color; if (marginLineEnabled) { Rectangle visibleRect = getVisibleRect(); repaint(marginLineX,visibleRect.y, 1,visibleRect.height); } } /** * Enables or disables the margin line. * * @param enabled Whether or not the margin line should be enabled. * @see #isMarginLineEnabled */ public void setMarginLineEnabled(boolean enabled) { if (enabled!=marginLineEnabled) { marginLineEnabled = enabled; if (marginLineEnabled) { Rectangle visibleRect = getVisibleRect(); repaint(marginLineX,visibleRect.y, 1,visibleRect.height); } } } /** * Sets the number of 'm' widths the margin line is over. * * @param size The margin size. * #see #getDefaultMarginLinePosition * @see #getMarginLinePosition */ public void setMarginLinePosition(int size) { marginSizeInChars = size; if (marginLineEnabled) { Rectangle visibleRect = getVisibleRect(); repaint(marginLineX,visibleRect.y, 1,visibleRect.height); updateMarginLineX(); repaint(marginLineX,visibleRect.y, 1,visibleRect.height); } } /** * Sets whether the edges of selections are rounded in this text area. * This method fires a property change of type * {@link #ROUNDED_SELECTION_PROPERTY}. * * @param rounded Whether selection edges should be rounded. * @see #getRoundedSelectionEdges() */ public void setRoundedSelectionEdges(boolean rounded) { if (roundedSelectionEdges!=rounded) { roundedSelectionEdges = rounded; Caret c = getCaret(); if (c instanceof ConfigurableCaret) { ((ConfigurableCaret)c).setRoundedSelectionEdges(rounded); if (c.getDot()!=c.getMark()) { // e.g., there's is a selection repaint(); } } firePropertyChange(ROUNDED_SELECTION_PROPERTY, !rounded, rounded); } } /** * Sets the UI for this <code>RTextArea</code>. Note that, for instances * of <code>RTextArea</code>, <code>setUI</code> only updates the popup * menu; this is because <code>RTextArea</code>s' look and feels are * independent of the Java Look and Feel. This method is here so * subclasses can set a UI (subclass of <code>RTextAreaUI</code>) if they * have to. * * @param ui The new UI. * @see #setUI */ protected void setRTextAreaUI(RTextAreaUI ui) { super.setUI(ui); // Workaround as setUI makes the text area opaque, even if we don't // want it to be. setOpaque(getBackgroundObject() instanceof Color); } /** * Changes whether or not tabs should be emulated with spaces (i.e., soft * tabs). Note that this affects all tabs inserted AFTER this call, not * tabs already in the document. For that, see * {@link #convertTabsToSpaces} and {@link #convertSpacesToTabs}. * * @param areEmulated Whether or not tabs should be emulated with spaces. * @see #convertSpacesToTabs * @see #convertTabsToSpaces * @see #getTabsEmulated */ public void setTabsEmulated(boolean areEmulated) { tabsEmulatedWithSpaces = areEmulated; } /** * Workaround, since in JDK1.4 it appears that <code>setTabSize()</code> * doesn't work for a <code>JTextArea</code> unless you use the constructor * specifying the number of rows and columns...<p> * Sets the number of characters to expand tabs to. This will be multiplied * by the maximum advance for variable width fonts. A PropertyChange event * ("tabSize") is fired when the tab size changes. * * @param size Number of characters to expand to. */ @Override public void setTabSize(int size) { super.setTabSize(size); boolean b = getLineWrap(); setLineWrap(!b); setLineWrap(b); } /** * This is here so subclasses such as <code>RSyntaxTextArea</code> that * have multiple fonts can define exactly what it means, for example, for * the margin line to be "80 characters" over. */ protected void updateMarginLineX() { Font font = getFont(); if (font == null) { marginLineX = 0; return; } marginLineX = getFontMetrics(font).charWidth('m') * marginSizeInChars; } /** * Returns the y-coordinate of the specified line. * * @param line The line number. * @return The y-coordinate of the top of the line, or <code>-1</code> if * this text area doesn't yet have a positive size or the line is * hidden (i.e. from folding). * @throws BadLocationException If <code>line</code> isn't a valid line * number for this document. */ public int yForLine(int line) throws BadLocationException { return ((RTextAreaUI)getUI()).yForLine(line); } /** * Returns the y-coordinate of the line containing an offset. * * @param offs The offset info the document. * @return The y-coordinate of the top of the offset, or <code>-1</code> if * this text area doesn't yet have a positive size or the line is * hidden (i.e. from folding). * @throws BadLocationException If <code>offs</code> isn't a valid offset * into the document. */ public int yForLineContaining(int offs) throws BadLocationException { return ((RTextAreaUI)getUI()).yForLineContaining(offs); } protected class RTAMouseListener extends CaretEvent implements MouseListener, MouseMotionListener, FocusListener { RTAMouseListener(RTextAreaBase textArea) { super(textArea); } public void focusGained(FocusEvent e) {} public void focusLost(FocusEvent e) {} public void mouseDragged(MouseEvent e) {} public void mouseMoved(MouseEvent e) {} public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} @Override public int getDot() { return dot; } @Override public int getMark() { return mark; } protected int dot; protected int mark; } }