/*
 * 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() {
					System.out.println("Yo");
					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 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>.
	 *
	 * NOTE:  the opaque property is set to <code>true</code> when the
	 * background is set to a color (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(true);
		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;

	}


}