/*
 * SyntaxTextArea.java - Enhanced text component
 * Copyright (C) 1998, 1999 Slava Pestov
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package org.gjt.sp.jedit.syntax;

import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;

/**
 * An enhanced JEditorPane useful in text editors. It has
 * the following advantages over the standard Swing text components:
 * 
 *
 * @author Slava Pestov
 * @version $Id: SyntaxTextArea.java,v 1.2 2000/07/28 01:39:54 tonyj Exp $
 * @see org.gjt.sp.jedit.syntax.SyntaxEditorKit
 */
public class SyntaxTextArea extends JEditorPane
{
	/**
	 * The default editor kit for this text component.
	 */
	public static final EditorKit EDITOR_KIT = new SyntaxEditorKit();

	/**
	 * Creates a new SyntaxTextArea component.
	 */
	public SyntaxTextArea()
	{
		setCaret(new SyntaxCaret());
		
		setBorder(null);
		setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));

		lineHighlightColor = new Color(0xe0e0e0);
		bracketHighlightColor = new Color(0xffaaaa);
		lineSegment = new Segment();

		addCaretListener(new CaretHandler());
	}

	/**
	 * Returns the default editor kit for this text component.
	 */
	public EditorKit createDefaultEditorKit()
	{
		return EDITOR_KIT;
	}

	/**
	 * Sets the currently highlighted line.
	 * @param lineStart The start offset of the line in the document
	 * @param lineEnd The end offset of the line in the document
	 */
	public void setHighlightedLine(int lineStart, int lineEnd)
	{
		if(lineHighlightTag == null)
			return;

		try
		{
			getHighlighter().changeHighlight(lineHighlightTag,
				lineStart,lineEnd);
		}
		catch(BadLocationException bl)
		{
			bl.printStackTrace();
		}
	}

	/**
	 * Sets the line highlight color.
	 * @param color The line highlight color
	 */
	public void setLineHighlightColor(Color color)
	{
		lineHighlightColor = color;
	}

	/**
	 * Returns the line highlight color.
	 */
	public Color getLineHighlightColor()
	{
		return lineHighlightColor;
	}

	/**
	 * Sets the current line highlighting feature.
	 * @param lineHighlight True if the current line should be
	 * highlighted, false otherwise.
	 */
	public void setLineHighlight(boolean lineHighlight)
	{
		if(lineHighlightTag != null)
		{
			if(lineHighlight)
				return;
			else
			{
				getHighlighter().removeHighlight(lineHighlightTag);
				lineHighlightTag = null;
			}
		}
		else if(lineHighlight)
		{
			try
			{
				lineHighlightTag = getHighlighter().addHighlight(
					0,0,new CurrentLineHighlighter());
			}
			catch(BadLocationException bl)
			{
				bl.printStackTrace();
			}
		}
	}

	/**
	 * Returns true if current line highlighting is enabled, false
	 * otherwise.
	 */
	public boolean getLineHighlight()
	{
		return (lineHighlightTag != null);
	}

	/**
	 * Sets the highlighted bracket.
	 * @param bracketPos The offset of the bracket in the document
	 */
	public void setHighlightedBracket(int bracketPos)
	{
		if(bracketHighlightTag == null)
			return;

		try
		{
			if(bracketPos == -1)
				getHighlighter().changeHighlight(
					bracketHighlightTag,0,0);
			else
				getHighlighter().changeHighlight(
					bracketHighlightTag,bracketPos,
					bracketPos+1);
		}
		catch(BadLocationException bl)
		{
			bl.printStackTrace();
		}
	}

	/**
	 * Sets the bracket highlight color.
	 * @param color The bracket highlight color
	 */
	public void setBracketHighlightColor(Color color)
	{
		bracketHighlightColor = color;
	}

	/**
	 * Returns the bracket highlight color.
	 */
	public Color getBracketHighlightColor()
	{
		return bracketHighlightColor;
	}

	/**
	 * Sets the bracket highlighting feature.
	 * @param bracketHighlight True if matching brackets should be
	 * highlighted, false otherwise.
	 */
	public void setBracketHighlight(boolean bracketHighlight)
	{
		if(bracketHighlightTag != null)
		{
			if(bracketHighlight)
				return;
			else
			{
				getHighlighter().removeHighlight(bracketHighlightTag);
				bracketHighlightTag = null;
			}
		}
		else if(bracketHighlight)
		{
			try
			{
				bracketHighlightTag = getHighlighter()
					.addHighlight(0,0,new BracketHighlighter());
			}
			catch(BadLocationException bl)
			{
				bl.printStackTrace();
			}
		}
	}

	/**
	 * Returns true if bracket highlighting is enabled, false otherwise.
	 */
	public boolean getBracketHighlight()
	{
		return (bracketHighlightTag != null);
	}

	/**
	 * Sets the number of lines from the top or bottom of the
	 * text area from which autoscroll begins.
	 * @param lines The number of lines
	 */
	public void setElectricBorders(int lines)
	{
		electricLines = lines;
	}

	/**
	 * Returns the number of lines from the top or bottom of the
	 * text area from which autoscrolling begins.
	 */
	public int getElectricBorders()
	{
		return electricLines;
	}

	/**
	 * Sets the block caret.
	 * @param block True if a block caret should be drawn, false
	 * otherwise
	 */
	public void setBlockCaret(boolean block)
	{
		this.block = block;
	}

	/**
	 * Returns true if a block caret is enabled, false otherwise.
	 */
	public boolean getBlockCaret()
	{
		return block;
	}

	/**
	 * Sets the overwrite mode.
	 * @param overwrite True if newly inserted characters should
	 * overwrite existing ones, false if they should be inserted.
	 */
	public void setOverwrite(boolean overwrite)
	{
		this.overwrite = overwrite;
	}

	/**
	 * Returns true if overwrite mode is enabled, false otherwise.
	 */
	public boolean getOverwrite(boolean overwrite)
	{
		return overwrite;
	}

	/**
	 * Sets the overwrite mode flag to the opposite of it's
	 * current value.
	 */
	public void toggleOverwrite()
	{
		overwrite = !overwrite;
	}

	/**
	 * Updates the bracket and line highlighters.
	 */
	public void updateHighlighters()
	{
		Document doc = getDocument();
		int dot = getCaretPosition();
		Element map = doc.getDefaultRootElement();
		int lineNo = map.getElementIndex(dot);

		Element lineElement = map.getElement(lineNo);
		int start = lineElement.getStartOffset();
		int end = lineElement.getEndOffset();

		if(getSelectionStart() == getSelectionEnd())
		{
			if(lineNo != lastLine)
			{
				setHighlightedLine(start,end);
				lastLine = lineNo;
			}
		}
		
		try
		{
			if(dot != 0)
			{
				dot--;
				doc.getText(dot,1,lineSegment);
				char bracket = lineSegment.array
					[lineSegment.offset];
				int otherBracket;
				switch(bracket)
				{
				case '(':
					otherBracket = SyntaxUtilities.locateBracketForward(
						doc,dot,'(',')');
					break;
				case ')':
					otherBracket = SyntaxUtilities.locateBracketBackward(
						doc,dot,'(',')');
					break;
				case '[':
					otherBracket = SyntaxUtilities.locateBracketForward(
						doc,dot,'[',']');
					break;
				case ']':
					otherBracket = SyntaxUtilities.locateBracketBackward(
						doc,dot,'[',']');
					break;
				case '{':
					otherBracket = SyntaxUtilities.locateBracketForward(
						doc,dot,'{','}');
					break;
				case '}':
					otherBracket = SyntaxUtilities.locateBracketBackward(
						doc,dot,'{','}');
					break;
				default:
					otherBracket = -1;
					break;
				}
				setHighlightedBracket(otherBracket);
			}
			else
				setHighlightedBracket(-1);
		}
		catch(BadLocationException bl)
		{
			//bl.printStackTrace();
		}
	}

	/**
	 * Works like Caret.adjustVisibility(), but does `electric'
	 * border scrolling (caret is never on the first or last
	 * visible line, if it ends up there the display is scrolled
	 * up or down)
	 * @param rect The rectangle to scroll to
	 */
	public void doElectricScroll(Rectangle rect)
	{
		SwingUtilities.invokeLater(new SyntaxSafeScroller(rect));
	}

	// private members
	private Color lineHighlightColor;
	private Object lineHighlightTag;
	private Color bracketHighlightColor;
	private Object bracketHighlightTag;
	private int electricLines;
	private boolean block;
	private boolean overwrite;
	private Segment lineSegment;
	private int lastLine = -1;

	private void _replaceSelection(String content)
	{
		if(!overwrite || getSelectionStart() != getSelectionEnd())
		{
			replaceSelection(content);
			return;
		}

		int caret = getCaretPosition();
		Document doc = getDocument();
		Element map = doc.getDefaultRootElement();
		Element line = map.getElement(map.getElementIndex(caret));

		if(line.getEndOffset() - caret <= content.length())
		{
			replaceSelection(content);
			return;
		}

		try
		{
			doc.remove(caret,content.length());
			doc.insertString(caret,content,null);
		}
		catch(BadLocationException bl)
		{
			bl.printStackTrace();
		}
	}
		
	static class DefaultKeyTypedAction extends TextAction
	{
		public DefaultKeyTypedAction()
		{
			super(DefaultEditorKit.defaultKeyTypedAction);
		}

		public void actionPerformed(ActionEvent evt)
		{
			JTextComponent comp = getTextComponent(evt);
			String content = evt.getActionCommand();
			int modifiers = evt.getModifiers();

			if(content != null && content.length() != 0
				&& ((modifiers & ActionEvent.ALT_MASK) == 0)
				|| ((modifiers & ActionEvent.CTRL_MASK) != 0))
			{
				char c = content.charAt(0);
				if ((c >= 0x20) && (c != 0x7F))
				{
					if(comp instanceof SyntaxTextArea)
						((SyntaxTextArea)comp)._replaceSelection(content);
					else
						comp.replaceSelection(content);
				}
			}
		}
	}

	static class InsertKeyAction extends TextAction
	{
		public InsertKeyAction()
		{
			super("insert-key");
		}

		public void actionPerformed(ActionEvent evt)
		{
			JComponent comp = getTextComponent(evt);
			if(comp instanceof SyntaxTextArea)
			{
				((SyntaxTextArea)comp).toggleOverwrite();
				comp.repaint(); // to repaint caret
			}
		}
	}

	class SyntaxCaret extends DefaultCaret
	{
		public void adjustVisibility(Rectangle rect)
		{
			doElectricScroll(rect);
		}
	}

	class SyntaxSafeScroller implements Runnable
	{
		private Rectangle rect;

		SyntaxSafeScroller(Rectangle rect)
		{
			this.rect = rect;
		}

		public void run()
		{
			int height = getToolkit().getFontMetrics(
				getFont()).getHeight();
			int y = Math.max(0,rect.y - height * electricLines);
			int lines = height * electricLines * 2;
			if(y + lines + rect.height <= getHeight())
			{
				rect.y = y;
				rect.height += lines;
			}
			scrollRectToVisible(rect);
		}
	}
			
	class CurrentLineHighlighter implements Highlighter.HighlightPainter
	{
		public void paint(Graphics g, int p0, int p1, Shape bounds,
			JTextComponent textComponent)
		{
			if(lineHighlightTag == null || getSelectionStart()
				!= getSelectionEnd())
				return;
			FontMetrics metrics = g.getFontMetrics();
			Document doc = getDocument();
			int lineNo = doc.getDefaultRootElement()
				.getElementIndex(p0);
			Rectangle rect = (Rectangle)bounds;
			int height = metrics.getHeight();
			int x = rect.x;
			int y = rect.y + height * lineNo;
			g.setColor(lineHighlightColor);
			g.fillRect(x,y,rect.width,height);
		}
	}

	class BracketHighlighter implements Highlighter.HighlightPainter
	{
		public void paint(Graphics g, int p0, int p1, Shape bounds,
			JTextComponent textComponent)
		{
			if(getSelectionStart() != getSelectionEnd()
				|| bracketHighlightTag == null)
				return;
			if(p0 == p1)
				return;
			Rectangle bracket;
			Document doc = getDocument();
			FontMetrics metrics = g.getFontMetrics();
			try
			{
				bracket = modelToView(p0);
				doc.getText(p0,1,lineSegment);
				bracket.width += metrics.charWidth(lineSegment
					.array[lineSegment.offset]);
			}
			catch(BadLocationException bl)
			{
				bl.printStackTrace();
				return;
			}
			g.setColor(bracketHighlightColor);
			g.fillRect(bracket.x,bracket.y,
				bracket.width,bracket.height);
		}
	}

	class CaretHandler implements CaretListener
	{
		public void caretUpdate(CaretEvent evt)
		{
			updateHighlighters();
		}
	}

	static
	{
		Keymap map = JTextComponent.getKeymap(JTextComponent
			.DEFAULT_KEYMAP);
		map.setDefaultAction(new SyntaxTextArea.DefaultKeyTypedAction());
		map.addActionForKeyStroke(KeyStroke.getKeyStroke(
			KeyEvent.VK_INSERT,0),new SyntaxTextArea.InsertKeyAction());
	}
}

/*
 * ChangeLog:
 * $Log: SyntaxTextArea.java,v $
 * Revision 1.2  2000/07/28 01:39:54  tonyj
 * Fix bug which prevented selection area appearing correctly with JDK 1.3
 * (by getting rid of most of SyntaxAreaCaret implementation which did not appear
 * to do anything useful anyway).
 *
 * Revision 1.1.1.1  1999/10/21 01:48:13  tonyj
 * Initial import from SourceSafe
 *
 * Revision 1.19  1999/04/28 04:10:40  sp
 * Overwrite/overstrike mode
 *
 * Revision 1.18  1999/04/22 05:31:17  sp
 * Documentation updates, minor SyntaxTextArea update
 *
 * Revision 1.17  1999/04/19 05:38:20  sp
 * Syntax API changes
 *
 * Revision 1.16  1999/04/01 04:13:00  sp
 * Bug fixing for 1.5final
 *
 * Revision 1.15  1999/03/27 23:47:57  sp
 * Updated docs, view tweak, goto-line fix, next/prev error tweak
 *
 * Revision 1.14  1999/03/27 02:46:17  sp
 * SyntaxTextArea is now modular
 *
 */

 

Ads help maintain this website.