// Copyright (c) 2006 - 2008, Markus Strauch.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
package net.sf.sdedit.ui.components;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.KeyListener;
import javax.swing.AbstractAction;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
/**
* A <tt>TextArea</tt> is an advanced <tt>JTextArea</tt> with error marks
* and undo/redo function.
*
* @author Markus Strauch
*/
public class TextArea extends JTextPane implements UndoableEditListener,
Highlighter.HighlightPainter {
private final static long serialVersionUID = 0xAB343920;
private Highlighter highlighter;
private UndoManager undoManager;
private Object highlight;
private static final Color ERROR_COLOR = Color.RED;
private String EOL;
//
// UndoableEditListener method
//
/**
* Adds the <tt>UndoableEdit</tt> that has happened to the
* <tt>UndoManager</tt> associated with this <tt>TextArea</tt>.
*
* @param evt
* the undoable edit event encapsulating the
* <tt>UndoableEdit</tt>
*/
public void undoableEditHappened(UndoableEditEvent evt) {
undoManager.addEdit(evt.getEdit());
}
/**
* Allocates a new <tt>TextArea</tt>.
*/
@SuppressWarnings("serial")
public TextArea() {
undoManager = new UndoManager();
highlighter = new DefaultHighlighter();
setHighlighter(highlighter);
getActionMap().put("Undo", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
undo();
}
});
getInputMap().put(KeyStroke.getKeyStroke("control Z"), "Undo");
getActionMap().put("Redo", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
redo();
}
});
getInputMap().put(KeyStroke.getKeyStroke("control Y"), "Redo");
EOL = (String) getDocument().getProperty( DefaultEditorKit.EndOfLineStringProperty );
getDocument().putProperty( DefaultEditorKit.EndOfLineStringProperty, "\n" );
getDocument().addUndoableEditListener(this);
}
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
super.paintComponent(g);
}
/**
* Undoes the last action, if any has been performed.
*/
public void undo() {
try {
if (undoManager.canUndo()) {
undoManager.undo();
for (KeyListener kl : getKeyListeners()) {
kl.keyTyped(null);
kl.keyReleased(null);
}
}
} catch (CannotUndoException e) {
}
}
/**
* Redoes the last action, if any has been performed.
*/
public void redo() {
try {
if (undoManager.canRedo()) {
undoManager.redo();
for (KeyListener kl : getKeyListeners()) {
kl.keyTyped(null);
kl.keyReleased(null);
}
}
} catch (CannotRedoException e) {
}
}
/**
* Marks the characters between the two specified positions as erroneous.
* Before this another maybe existing mark will be removed. If the
* <tt>from</tt> position is less than 0, an error mark - if present -
* will be removed.
*
* @param from
* the position of the first character
* @param to
* the position of the last character
*/
public void markError(final int from, final int to) {
if (highlight != null) {
highlighter.removeHighlight(highlight);
}
if (from >= 0) {
try {
highlight = highlighter.addHighlight(from, to, this);
} catch (BadLocationException ble) {
ble.printStackTrace();
}
}
}
/**
* Returns the number of the line the cursor is in.
*
* @return the number of the line the cursor is in
*/
public int getCaretLine() {
final int caret = getCaretPosition();
final String text = getText();
int line = 0;
for (int i = 0; i < caret; i++) {
char c = text.charAt(i);
if (c == '\n') {
line++;
}
}
return line;
}
/**
* Returns the text displayed by this <tt>TextArea</tt>,
* optionally formatted such that end-of-line characters match
* the platform's behaviour.
*
* @param format flag denoting if end-of-line characters should be
* represented as usual for the platform (<tt>true</tt>) or by
* '\n' (<tt>false</tt>).
* @return the text displayed by this <tt>TextArea</tt>
*/
private String getText (boolean format) {
String text;
if (format) {
getDocument().putProperty( DefaultEditorKit.EndOfLineStringProperty, EOL );
text = super.getText();
getDocument().putProperty( DefaultEditorKit.EndOfLineStringProperty, "\n" );
} else {
text = super.getText();
}
return text;
}
/**
* Returns the text displayed by this <tt>TextArea</tt>, with
* a single '\n' used as an end-of-line character.
*
* @return the text displayed by this <tt>TextArea</tt>
*/
public String getText () {
return getText(false);
}
/**
* Returns the index of the character at the beginning of
* the current line (where the cursor is)
*
* @return the index of the character at the beginning of
* the current line (where the cursor is)
*/
public int getCurrentLineBegin() {
final int caret = getCaretPosition();
final String text = getText();
int i = Math.min(text.length() - 1, caret);
for (; i >= 0; i--) {
final char c = text.charAt(i);
if (c != '\n' && c != '\r') {
break;
}
}
for (; i >= 0; i--) {
final char c = text.charAt(i);
if (c == '\n' || c == '\r') {
return i + 2;
}
}
return 0;
}
// paint a thick line under one line of text, from r extending rightward
// to
// x2
private void paintLine(Graphics g, Rectangle r, int x2) {
int ytop = r.y + r.height - 3;
// g.fillRect(r.x, ytop, x2 - r.x, 3);
for (int x = r.x; x < x2 - r.x; x += 2) {
g.drawLine(x, ytop, x + 1, ytop + 1);
g.drawLine(x + 1, ytop + 1, x + 2, ytop);
}
}
// paint thick lines under a block of text
/**
* @see Highlighter.HighlightPainter#paint(Graphics, int, int, Shape,
* JTextComponent)
*/
public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) {
Rectangle r0 = null, r1 = null, rbounds = bounds.getBounds();
int xmax = rbounds.x + rbounds.width; // x coordinate of right
// edge
try { // convert positions to pixel coordinates
r0 = c.modelToView(p0);
r1 = c.modelToView(p1);
} catch (BadLocationException ex) {
return;
}
if ((r0 == null) || (r1 == null))
return;
g.setColor(ERROR_COLOR);
// special case if p0 and p1 are on the same line
if (r0.y == r1.y) {
paintLine(g, r0, r1.x);
return;
}
// first line, from p1 to end-of-line
paintLine(g, r0, xmax);
// all the full lines in between, if any (assumes that all lines
// have
// the same height--not a good assumption with
// JEditorPane/JTextPane)
r0.y += r0.height; // move r0 to next line
r0.x = rbounds.x; // move r0 to left edge
while (r0.y < r1.y) {
paintLine(g, r0, xmax);
r0.y += r0.height; // move r0 to next line
}
// last line, from beginning-of-line to p1
paintLine(g, r0, r1.x);
}
}