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

org.fife.ui.rtextarea.RTextAreaUI Maven / Gradle / Ivy

/*
 * 04/25/2007
 *
 * RTextAreaUI.java - UI used by instances of RTextArea.
 *
 * This library is distributed under a modified BSD license.  See the included
 * LICENSE file for details.
 */
package org.fife.ui.rtextarea;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.border.Border;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;


/**
 * The UI used by instances of RTextArea.  This UI takes into
 * account all of the "extras" involved in an RTextArea, including
 * having a special caret (for insert and overwrite), background images,
 * highlighting the current line, etc.
 *
 * @author Robert Futrell
 * @version 0.5
 */
public class RTextAreaUI extends BasicTextAreaUI {

	private static final String SHARED_ACTION_MAP_NAME	= "RTextAreaUI.actionMap";
	private static final String SHARED_INPUT_MAP_NAME	= "RTextAreaUI.inputMap";

	protected RTextArea textArea;				// The text area for which we are the UI.

	private static final EditorKit DEFAULT_KIT = new RTextAreaEditorKit();
	private static final TransferHandler DEFAULT_TRANSFER_HANDLER =
										new RTATextTransferHandler();

	private static final String RTEXTAREA_KEYMAP_NAME	= "RTextAreaKeymap";


	/**
	 * Creates a UI for an RTextArea.
	 *
	 * @param textArea A text area.
	 * @return The UI.
	 */
	public static ComponentUI createUI(JComponent textArea) {
		return new RTextAreaUI(textArea);
	}


	/**
	 * Constructor.
	 *
	 * @param textArea An instance of RTextArea.
	 * @throws IllegalArgumentException If textArea is not an
	 *         instance of RTextArea.
	 */
	public RTextAreaUI(JComponent textArea) {
		if (!(textArea instanceof RTextArea)) {
			throw new IllegalArgumentException("RTextAreaUI is for " +
							 		"instances of RTextArea only!");
		}
		this.textArea = (RTextArea)textArea;
	}


	/**
	 * The Nimbus LAF (and any Synth laf might have similar issues) doesn't set
	 * many UIManager properties that BasicLAF UI's look for.  This causes
	 * problems for custom Basic-based UI's such as RTextAreaUI.  This method
	 * attempts to detect if Nimbus has been installed, and if so, sets proper
	 * values for some editor properties.
	 *
	 * @param editor The text area.
	 */
	private void correctNimbusDefaultProblems(JTextComponent editor) {

		// Don't check UIManager.getLookAndFeel().getName() for "Nimbus",
		// as other Synth-based LaFs might have not set these properties,
		// in which case we'll need to use our fallback values.

		// Check for null, but not for UIResource, for these properties,
		// because if Nimbus was installed these values would all be given
		// null values.  Another laf might have successfully installed
		// UIResource values, which we don't want to override.

		Color c = editor.getCaretColor();
		if (c==null) {
			editor.setCaretColor(RTextArea.getDefaultCaretColor());
		}

		c = editor.getSelectionColor();
		if (c==null) {
			c = UIManager.getColor("nimbusSelectionBackground");
			if (c==null) { // Not Nimbus, but still need a value - fallback
				c = UIManager.getColor("textHighlight");
				if (c==null) {
					c = new ColorUIResource(Color.BLUE);
				}
			}
			editor.setSelectionColor(c);
		}

		c = editor.getSelectedTextColor();
		if (c==null) {
			c = UIManager.getColor("nimbusSelectedText");
			if (c==null) { // Not Nimbus, but still need a value - fallback
				c = UIManager.getColor("textHighlightText");
				if (c==null) {
					c = new ColorUIResource(Color.WHITE);
				}
			}
			editor.setSelectedTextColor(c);
		}

		c = editor.getDisabledTextColor();
		if (c==null) {
			c = UIManager.getColor("nimbusDisabledText");
			if (c==null) { // Not Nimbus, but still need a value - fallback
				c = UIManager.getColor("textInactiveText");
				if (c==null) {
					c = new ColorUIResource(Color.DARK_GRAY);
				}
			}
			editor.setDisabledTextColor(c);
		}

		Border border = editor.getBorder();
		if (border==null) {
			editor.setBorder(new BasicBorders.MarginBorder());
		}

		Insets margin = editor.getMargin();
		if (margin==null) {
			editor.setMargin(new InsetsUIResource(2, 2, 2, 2));
		}

	}


	/**
	 * Creates the view for an element.  Returns a WrappedPlainView or
	 * PlainView.
	 *
	 * @param elem The element.
	 * @return The view.
	 */
	@Override
	public View create(Element elem) {
		if (textArea.getLineWrap()) {
			return new WrappedPlainView(elem, textArea.getWrapStyleWord());
		}
		else {
			return new PlainView(elem);
		}
	}


	/**
	 * Returns the default caret for an RTextArea.  This caret is
	 * capable of displaying itself differently for insert/overwrite modes.
	 *
	 * @return The caret.
	 */
	@Override
	protected Caret createCaret() {
		Caret caret = new ConfigurableCaret();
		caret.setBlinkRate(500);
		return caret;
	}


	@Override
	protected Highlighter createHighlighter() {
		return new RTextAreaHighlighter();
	}


	/**
	 * Creates the keymap for this text area.  This takes the super class's
	 * keymap, but sets the default keystroke to be RTextAreaEditorKit's
	 * DefaultKeyTypedAction.  This must be done to override the default
	 * keymap's default key-typed action.
	 *
	 * @return The keymap.
	 */
	@Override
	protected Keymap createKeymap() {

		// Load the keymap we'll be using (it's saved by
		// JTextComponent.addKeymap).
		Keymap map = JTextComponent.getKeymap(RTEXTAREA_KEYMAP_NAME);
		if (map==null) {
			Keymap parent = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
			map = JTextComponent.addKeymap(RTEXTAREA_KEYMAP_NAME, parent);
			map.setDefaultAction(new RTextAreaEditorKit.DefaultKeyTypedAction());
		}

		return map;

	}


	/**
	 * Creates a default action map.  This action map contains actions for all
	 * basic text area work - cut, copy, paste, select, caret motion, etc.

* * This isn't named createActionMap() because there is a * package-private member by that name in BasicTextAreaUI, * and some compilers will give warnings that we are not overriding that * method since it is package-private. * * @return The action map. */ protected ActionMap createRTextAreaActionMap() { // Get the actions of the text area (which in turn gets them from its // DefaultEditorKit). ActionMap map = new ActionMapUIResource(); Action[] actions = textArea.getActions(); int n = actions.length; for (Action a : actions) { map.put(a.getValue(Action.NAME), a); } // Not sure if we need these; not sure they are ever called // (check their NAMEs). map.put(TransferHandler.getCutAction().getValue(Action.NAME), TransferHandler.getCutAction()); map.put(TransferHandler.getCopyAction().getValue(Action.NAME), TransferHandler.getCopyAction()); map.put(TransferHandler.getPasteAction().getValue(Action.NAME), TransferHandler.getPasteAction()); return map; } /** * Returns the name to use to cache/fetch the shared action map. This * should be overridden by subclasses if the subclass has its own custom * editor kit to install, so its actions get picked up. * * @return The name of the cached action map. */ protected String getActionMapName() { return SHARED_ACTION_MAP_NAME; } /** * Fetches the EditorKit for the UI. * * @param tc the text component for which this UI is installed * @return the editor capabilities * @see TextUI#getEditorKit */ @Override public EditorKit getEditorKit(JTextComponent tc) { return DEFAULT_KIT; } /** * Returns the text area for which we are the UI. * * @return The text area. */ public RTextArea getRTextArea() { return textArea; } /** * Returns an action map to use by a text area.

* * This method is not named getActionMap() because there is * a package-private method in BasicTextAreaUI with that name. * Thus, creating a new method with that name causes certain compilers to * issue warnings that you are not actually overriding the original method * (since it is package-private). * * @return The action map. * @see #createRTextAreaActionMap() */ private ActionMap getRTextAreaActionMap() { // Get the UIManager-cached action map; if this is the first // RTextArea created, create the action map and cache it. ActionMap map = (ActionMap)UIManager.get(getActionMapName()); if (map==null) { map = createRTextAreaActionMap(); UIManager.put(getActionMapName(), map); } ActionMap componentMap = new ActionMapUIResource(); componentMap.put("requestFocus", new FocusAction()); if (map != null) { componentMap.setParent(map); } return componentMap; } /** * Get the InputMap to use for the UI.

* * This method is not named getInputMap() because there is * a package-private method in BasicTextAreaUI with that name. * Thus, creating a new method with that name causes certain compilers to * issue warnings that you are not actually overriding the original method * (since it is package-private). */ protected InputMap getRTextAreaInputMap() { InputMap map = new InputMapUIResource(); InputMap shared = (InputMap)UIManager.get(SHARED_INPUT_MAP_NAME); if (shared==null) { shared = new RTADefaultInputMap(); UIManager.put(SHARED_INPUT_MAP_NAME, shared); } //KeyStroke[] keys = shared.allKeys(); //for (int i=0; i " + shared.get(keys[i])); map.setParent(shared); return map; } /** * Gets the allocation to give the root View. Due * to an unfortunate set of historical events this * method is inappropriately named. The Rectangle * returned has nothing to do with visibility. * The component must have a non-zero positive size for * this translation to be computed. * * @return the bounding box for the root view */ @Override protected Rectangle getVisibleEditorRect() { Rectangle alloc = textArea.getBounds(); if ((alloc.width > 0) && (alloc.height > 0)) { alloc.x = alloc.y = 0; Insets insets = textArea.getInsets(); alloc.x += insets.left; alloc.y += insets.top; alloc.width -= insets.left + insets.right; alloc.height -= insets.top + insets.bottom; return alloc; } return null; } @Override protected void installDefaults() { super.installDefaults(); JTextComponent editor = getComponent(); editor.setFont(RTextAreaBase.getDefaultFont()); // Nimbus (and possibly other Synth lafs) doesn't play by BasicLaf // rules and doesn't set properties needed by custom BasicTextAreaUI's. correctNimbusDefaultProblems(editor); editor.setTransferHandler(DEFAULT_TRANSFER_HANDLER); } /** * {@inheritDoc} */ @Override protected void installKeyboardActions() { // NOTE: Don't call super.installKeyboardActions(), as that causes // JTextAreas to stop responding to certain keystrokes if an RTextArea // is the first-instantiated text area. This is because of the code // path installKeyboardActions() -> getActionMap() -> createActionMap(). // In BasicTextUI#createActionMap(), "editor.getActions()" is called, // and the current editor's returned Actions are used to create the // ActionMap, which is then cached and used in all future J/RTextAreas. // Unfortunately, RTextArea actions don't worn in JTextAreas. //super.installKeyboardActions(); RTextArea textArea = getRTextArea(); // backward compatibility support... keymaps for the UI // are now installed in the more friendly input map. textArea.setKeymap(createKeymap()); // Since BasicTextUI.getInputMap() is package-private, instead use // our own version here. InputMap map = getRTextAreaInputMap(); SwingUtilities.replaceUIInputMap(textArea,JComponent.WHEN_FOCUSED,map); // Same thing here with action map. ActionMap am = getRTextAreaActionMap(); if (am!=null) { SwingUtilities.replaceUIActionMap(textArea, am); } } /** * Installs this UI to the given text component. */ @Override public void installUI(JComponent c) { if (!(c instanceof RTextArea)) { throw new Error("RTextAreaUI needs an instance of RTextArea!"); } super.installUI(c); } @Override protected void paintBackground(Graphics g) { // Only fill in the background if an image isn't being used. Color bg = textArea.getBackground(); if (bg!=null) { g.setColor(bg); //g.fillRect(0, 0, textArea.getWidth(), textArea.getHeight()); Rectangle r = g.getClipBounds(); g.fillRect(r.x,r.y, r.width,r.height); } paintEditorAugmentations(g); } /** * Paints the highlighted current line, if it is enabled. * * @param g The graphics context with which to paint. * @param visibleRect The visible rectangle of the text area. */ protected void paintCurrentLineHighlight(Graphics g, Rectangle visibleRect) { if (textArea.getHighlightCurrentLine()) { Caret caret = textArea.getCaret(); if (caret.getDot()==caret.getMark()) { Color highlight = textArea.getCurrentLineHighlightColor(); // NOTE: We use the getLineHeight() method below instead // of currentCaretRect.height because of a bug where // currentCaretRect.height is incorrect when an RSyntaxTextArea // is first displayed (it is initialized with the text area's // font.getHeight() (via RTextArea), but isn't changed to // account for the syntax styles before it is displayed). //int height = textArea.currentCaretRect.height); int height = textArea.getLineHeight(); if (textArea.getFadeCurrentLineHighlight()) { Graphics2D g2d = (Graphics2D)g; Color bg = textArea.getBackground(); GradientPaint paint = new GradientPaint( visibleRect.x,0, highlight, visibleRect.x+visibleRect.width,0, bg==null ? Color.WHITE : bg); g2d.setPaint(paint); g2d.fillRect(visibleRect.x,textArea.currentCaretY, visibleRect.width, height); } else { g.setColor(highlight); g.fillRect(visibleRect.x,textArea.currentCaretY, visibleRect.width, height); } } } } /** * Paints editor augmentations added by RTextArea: highlighted lines, * current line highlight, and margin line. * * @param g The graphics context with which to paint. */ protected void paintEditorAugmentations(Graphics g) { Rectangle visibleRect = textArea.getVisibleRect(); paintLineHighlights(g); paintCurrentLineHighlight(g, visibleRect); paintMarginLine(g, visibleRect); } /** * Paints any line highlights. * * @param g The graphics context. */ protected void paintLineHighlights(Graphics g) { LineHighlightManager lhm = textArea.getLineHighlightManager(); if (lhm!=null) { lhm.paintLineHighlights(g); } } /** * Draws the "margin line" if enabled. * * @param g The graphics context to paint with. * @param visibleRect The visible rectangle of this text area. */ protected void paintMarginLine(Graphics g, Rectangle visibleRect) { if (textArea.isMarginLineEnabled()) { g.setColor(textArea.getMarginLineColor()); Insets insets = textArea.getInsets(); int marginLineX = textArea.getMarginLinePixelLocation() + (insets==null ? 0 : insets.left); g.drawLine(marginLineX,visibleRect.y, marginLineX,visibleRect.y+visibleRect.height); } } @Override protected void paintSafely(Graphics g) { // Paint editor augmentations if editor is not opaque because // paintBackground() is not called in this case if (!textArea.isOpaque()) { paintEditorAugmentations(g); } super.paintSafely(g); } /** * Returns the y-coordinate of the specified line.

* * The default implementation is equivalent to: *

	 * int startOffs = textArea.getLineStartOffset(line);
	 * return yForLineContaining(startOffs);
	 * 
* * Subclasses that can calculate this value more quickly than traditional * {@link #modelToView(JTextComponent, int)} calls should override this * method to do so. This method may be used when the entire bounding box * isn't needed, to speed up rendering. * * @param line The line number. * @return The y-coordinate of the top of the line, or -1 if * this text area doesn't yet have a positive size or the line is * hidden (i.e. from folding). * @throws BadLocationException If line isn't a valid line * number for this document. */ public int yForLine(int line) throws BadLocationException { int startOffs = textArea.getLineStartOffset(line); return yForLineContaining(startOffs); } /** * Returns the y-coordinate of the line containing an offset.

* * The default implementation is equivalent to: *

	 * int line = textArea.getLineOfOffset(offs);
	 * int startOffs = textArea.getLineStartOffset(line);
	 * return modelToView(startOffs).y;
	 * 
* * Subclasses that can calculate this value more quickly than traditional * {@link #modelToView(JTextComponent, int)} calls should override this * method to do so. This method may be used when the entire bounding box * isn't needed, to speed up rendering. * * @param offs The offset info the document. * @return The y-coordinate of the top of the offset, or -1 if * this text area doesn't yet have a positive size or the line is * hidden (i.e. from folding). * @throws BadLocationException If offs isn't a valid offset * into the document. */ public int yForLineContaining(int offs) throws BadLocationException { Rectangle r = modelToView(textArea, offs); return r!=null ? r.y : -1; } /** * Registered in the ActionMap. */ class FocusAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { textArea.requestFocus(); } @Override public boolean isEnabled() { return textArea.isEditable(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy