/******************************************************************
* JADE - Java Agent DEvelopment Framework is a framework to develop
* multi-agent systems in compliance with the FIPA specifications.
* Copyright (C) 2002 TILAB S.p.A.
*
* This file is donated by Acklin B.V. to the JADE project.
*
*
* GNU Lesser General Public License
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation,
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
* ***************************************************************/
package jade.tools.gui;
import java.awt.*;
import javax.swing.JComponent;
import javax.swing.text.*;
/**
* The text area repaint manager. It performs double buffering and paints
* lines of text.The original file is written by Slava Pestov (www.gjt.org)
* and altered to fit ACL/SL.
*
* @author Chris van Aart - Acklin B.V., the Netherlands & Slava Pestov
* @created June 14, 2002
*/
public class ACLTextAreaPainter extends JComponent implements TabExpander {
/**
* Creates a new repaint manager. This should be not be called directly.
*
* @param textArea Description of Parameter
*/
public ACLTextAreaPainter(ACLTextArea textArea) {
this.textArea = textArea;
currentLine = new Segment();
currentLineIndex = -1;
firstInvalid = lastInvalid = -1;
setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
setFont(new Font("Dialog", Font.PLAIN, 11));
setForeground(Color.black);
setBackground(Color.white);
styles = ACLSyntaxUtilities.getDefaultSyntaxStyles(this);
cols = 80;
rows = 5;
caretColor = Color.red;
selectionColor = new Color(0xccccff);
lineHighlightColor = new Color(0xe0e0e0);
;
lineHighlight = true;
bracketHighlightColor = Color.black;
bracketHighlight = true;
eolMarkerColor = new Color(0x009999);
eolMarkers = true;
copyAreaBroken = true;
}
/**
* Returns if this component can be traversed by pressing the Tab key. This
* returns false.
*
* @return The ManagingFocus value
*/
public final boolean isManagingFocus() {
return false;
}
/**
* Returns the syntax styles used to paint colorized text. Entry <i>n</i>
* will be used to paint tokens with id = <i>n</i> .
*
* @return The Styles value
* @see org.gjt.sp.jedit.syntax.Token
*/
public final ACLSytntaxStyle[] getStyles() {
return styles;
}
/**
* Returns the caret color.
*
* @return The CaretColor value
*/
public final Color getCaretColor() {
return caretColor;
}
/**
* Returns the selection color.
*
* @return The SelectionColor value
*/
public final Color getSelectionColor() {
return selectionColor;
}
/**
* Returns the line highlight color.
*
* @return The LineHighlightColor value
*/
public final Color getLineHighlightColor() {
return lineHighlightColor;
}
/**
* Returns true if line highlight is enabled, false otherwise.
*
* @return The LineHighlightEnabled value
*/
public final boolean isLineHighlightEnabled() {
return lineHighlight;
}
/**
* Returns the bracket highlight color.
*
* @return The BracketHighlightColor value
*/
public final Color getBracketHighlightColor() {
return bracketHighlightColor;
}
/**
* Returns true if bracket highlighting is enabled, false otherwise. When
* bracket highlighting is enabled, the bracket matching the one before the
* caret (if any) is highlighted.
*
* @return The BracketHighlightEnabled value
*/
public final boolean isBracketHighlightEnabled() {
return bracketHighlight;
}
/**
* Returns true if the caret should be drawn as a block, false otherwise.
*
* @return The BlockCaretEnabled value
*/
public final boolean isBlockCaretEnabled() {
return blockCaret;
}
/**
* Returns the EOL marker color.
*
* @return The EOLMarkerColor value
*/
public final Color getEOLMarkerColor() {
return eolMarkerColor;
}
/**
* Returns true if EOL markers are drawn, false otherwise.
*
* @return The EOLMarkerEnabled value
*/
public final boolean isEOLMarkerEnabled() {
return eolMarkers;
}
/**
* Sets the syntax styles used to paint colorized text. Entry <i>n</i> will
* be used to paint tokens with id = <i>n</i> .
*
* @param styles The syntax styles
* @see org.gjt.sp.jedit.syntax.Token
*/
public final void setStyles(ACLSytntaxStyle[] styles) {
this.styles = styles;
invalidateOffscreen();
repaint();
}
/**
* Sets the caret color.
*
* @param caretColor The caret color
*/
public final void setCaretColor(Color caretColor) {
this.caretColor = caretColor;
invalidateSelectedLines();
}
/**
* Sets the selection color.
*
* @param selectionColor The selection color
*/
public final void setSelectionColor(Color selectionColor) {
this.selectionColor = selectionColor;
invalidateSelectedLines();
}
/**
* Sets the line highlight color.
*
* @param lineHighlightColor The line highlight color
*/
public final void setLineHighlightColor(Color lineHighlightColor) {
this.lineHighlightColor = lineHighlightColor;
invalidateSelectedLines();
}
/**
* Enables or disables current line highlighting.
*
* @param lineHighlight True if current line highlight should be enabled,
* false otherwise
*/
public final void setLineHighlightEnabled(boolean lineHighlight) {
this.lineHighlight = lineHighlight;
invalidateSelectedLines();
}
/**
* Sets the bracket highlight color.
*
* @param bracketHighlightColor The bracket highlight color
*/
public final void setBracketHighlightColor(Color bracketHighlightColor) {
this.bracketHighlightColor = bracketHighlightColor;
invalidateLine(textArea.getBracketLine());
}
/**
* Enables or disables bracket highlighting. When bracket highlighting is
* enabled, the bracket matching the one before the caret (if any) is
* highlighted.
*
* @param bracketHighlight True if bracket highlighting should be enabled,
* false otherwise
*/
public final void setBracketHighlightEnabled(boolean bracketHighlight) {
this.bracketHighlight = bracketHighlight;
invalidateLine(textArea.getBracketLine());
}
/**
* Sets if the caret should be drawn as a block, false otherwise.
*
* @param blockCaret True if the caret should be drawn as a block, false
* otherwise.
*/
public final void setBlockCaretEnabled(boolean blockCaret) {
this.blockCaret = blockCaret;
invalidateSelectedLines();
}
/**
* Sets the EOL marker color.
*
* @param eolMarkerColor The EOL marker color
*/
public final void setEOLMarkerColor(Color eolMarkerColor) {
this.eolMarkerColor = eolMarkerColor;
invalidateOffscreen();
repaint();
}
/**
* Sets if EOL markers are to be drawn.
*
* @param eolMarkers True if EOL markers should be dranw, false otherwise
*/
public final void setEOLMarkerEnabled(boolean eolMarkers) {
this.eolMarkers = eolMarkers;
invalidateOffscreen();
repaint();
}
/**
* Queues a repaint of the changed lines only.
*/
public final void fastRepaint() {
if (firstInvalid == -1 && lastInvalid == -1) {
repaint();
}
else {
repaint(0, textArea.lineToY(firstInvalid)
+ fm.getLeading() + fm.getMaxDescent(),
getWidth(), (lastInvalid - firstInvalid + 1)
* fm.getHeight());
}
}
/**
* Repaints the specified line. This is equivalent to calling <code>_invalidateLine()</code>
* and <code>repaint()</code>.
*
* @param line The line
* @see #_invalidateLine(int)
*/
public final void invalidateLine(int line) {
_invalidateLine(line);
fastRepaint();
}
/**
* Repaints the specified line range. This is equivalent to calling <code>_invalidateLineRange()</code>
* then <code>repaint()</code>.
*
* @param firstLine The first line to repaint
* @param lastLine The last line to repaint
*/
public final void invalidateLineRange(int firstLine, int lastLine) {
_invalidateLineRange(firstLine, lastLine);
fastRepaint();
}
/**
* Repaints the lines containing the selection.
*/
public final void invalidateSelectedLines() {
invalidateLineRange(textArea.getSelectionStartLine(),
textArea.getSelectionEndLine());
}
/**
* Invalidates the offscreen graphics context. This should not be called
* directly.
*/
public final void invalidateOffscreen() {
offImg = null;
offGfx = null;
}
/**
* Returns the font metrics used by this component.
*
* @return The FontMetrics value
*/
public FontMetrics getFontMetrics() {
return fm;
}
/**
* Returns if the copyArea() should not be used.
*
* @return The CopyAreaBroken value
*/
public boolean isCopyAreaBroken() {
return copyAreaBroken;
}
/**
* Returns the painter's preferred size.
*
* @return The PreferredSize value
*/
public Dimension getPreferredSize() {
return super.getPreferredSize();
/*
Dimension dim = new Dimension();
dim.width = fm.charWidth('w') * cols;
dim.height = fm.getHeight() * rows;
return dim;
*/
}
/**
* Returns the painter's minimum size.
*
* @return The MinimumSize value
*/
public Dimension getMinimumSize() {
return super.getMinimumSize();
}
/**
* Sets the font for this component. This is overridden to update the
* cached font metrics and to recalculate which lines are visible.
*
* @param font The font
*/
public void setFont(Font font) {
super.setFont(font);
fm = textArea.getFontMetrics(font);
textArea.recalculateVisibleLines();
}
/**
* Disables the use of the copyArea() function (which is broken in JDK
* 1.2).
*
* @param copyAreaBroken The new CopyAreaBroken value
*/
public void setCopyAreaBroken(boolean copyAreaBroken) {
this.copyAreaBroken = copyAreaBroken;
}
/**
* Paints any lines that changed since the last paint to the offscreen
* graphics, then repaints the offscreen to the specified graphics context.
*
* @param g The graphics context
*/
public void update(Graphics g) {
tabSize = fm.charWidth('w') * ((Integer)textArea.getDocument().getProperty(
PlainDocument.tabSizeAttribute)).intValue();
// returns true if offscreen was created. When it's created,
// all lines, not just the invalid ones, need to be painted.
if (ensureOffscreenValid()) {
firstInvalid = textArea.getFirstLine();
lastInvalid = firstInvalid + textArea.getVisibleLines();
}
if (firstInvalid != -1 && lastInvalid != -1) {
int lineCount;
try {
if (firstInvalid == lastInvalid) {
lineCount = offscreenRepaintLine(firstInvalid,
textArea.getHorizontalOffset());
}
else {
lineCount = offscreenRepaintLineRange(
firstInvalid, lastInvalid);
}
if (lastInvalid - firstInvalid + 1 != lineCount) {
// XXX stupid hack
Rectangle clip = g.getClipBounds();
if (!clip.equals(getBounds())) {
repaint();
}
}
}
catch (Exception e) {
System.err.println("Error repainting line"
+ " range {" + firstInvalid + ","
+ lastInvalid + "}:");
e.printStackTrace();
}
firstInvalid = lastInvalid = -1;
}
g.drawImage(offImg, 0, 0, null);
}
/**
* Same as <code>update(g)</code>.
*
* @param g Description of Parameter
*/
public void paint(Graphics g) {
update(g);
}
/**
* Marks a line as needing a repaint, but doesn't actually repaint it until
* <code>repaint()</code> is called manually.
*
* @param line The line to invalidate
*/
public void _invalidateLine(int line) {
int firstVisible = textArea.getFirstLine();
int lastVisible = firstVisible + textArea.getVisibleLines();
if (line < firstVisible || line > lastVisible) {
return;
}
if (line >= firstInvalid && line <= lastInvalid) {
return;
}
if (firstInvalid == -1 && lastInvalid == -1) {
firstInvalid = lastInvalid = line;
}
else {
firstInvalid = Math.min(line, firstInvalid);
lastInvalid = Math.max(line, lastInvalid);
}
}
/**
* Marks a range of lines as needing a repaint, but doesn't actually
* repaint them until <code>repaint()</code> is called.
*
* @param firstLine The first line to invalidate
* @param lastLine The last line to invalidate
*/
public void _invalidateLineRange(int firstLine, int lastLine) {
int firstVisible = textArea.getFirstLine();
int lastVisible = firstVisible + textArea.getVisibleLines();
if (firstLine > lastLine) {
int tmp = firstLine;
firstLine = lastLine;
lastLine = tmp;
}
if (lastLine < firstVisible || firstLine > lastVisible) {
return;
}
if (firstInvalid == -1 && lastInvalid == -1) {
firstInvalid = firstLine;
lastInvalid = lastLine;
}
else {
if (firstLine >= firstInvalid && lastLine <= lastInvalid) {
return;
}
firstInvalid = Math.min(firstInvalid, firstLine);
lastInvalid = Math.max(lastInvalid, lastLine);
}
firstInvalid = Math.max(firstInvalid, firstVisible);
lastInvalid = Math.min(lastInvalid, lastVisible);
}
/**
* Simulates scrolling from <code>oldFirstLine</code> to <code>newFirstLine</code>
* by shifting the offscreen graphics and repainting any revealed lines.
* This should not be called directly; use <code>JEditTextArea.setFirstLine()</code>
* instead.
*
* @param oldFirstLine The old first line
* @param newFirstLine The new first line
* @see org.gjt.sp.jedit.textarea.JEditTextArea#setFirstLine(int)
*/
public void scrollRepaint(int oldFirstLine, int newFirstLine) {
if (offGfx == null) {
return;
}
int visibleLines = textArea.getVisibleLines();
// No point doing this crap if the user scrolled by >= visibleLines
if (copyAreaBroken || oldFirstLine + visibleLines <= newFirstLine
|| newFirstLine + visibleLines <= oldFirstLine) {
_invalidateLineRange(newFirstLine, newFirstLine + visibleLines + 1);
}
else {
int y = fm.getHeight() * (oldFirstLine - newFirstLine);
offGfx.copyArea(0, 0, offImg.getWidth(this), offImg.getHeight(this), 0, y);
if (oldFirstLine < newFirstLine) {
_invalidateLineRange(oldFirstLine + visibleLines - 1,
newFirstLine + visibleLines + 1);
}
else {
_invalidateLineRange(newFirstLine, oldFirstLine);
}
}
}
/**
* Implementation of TabExpander interface. Returns next tab stop after a
* specified point.
*
* @param x The x co-ordinate
* @param tabOffset Ignored
* @return The next tab stop after <i>x</i>
*/
public float nextTabStop(float x, int tabOffset) {
int offset = textArea.getHorizontalOffset();
int ntabs = ((int)x - offset) / tabSize;
return (ntabs + 1) * tabSize + offset;
}
protected boolean ensureOffscreenValid() {
if (offImg == null || offGfx == null) {
offImg = textArea.createImage(getWidth(), getHeight());
offGfx = offImg.getGraphics();
return true;
}
else {
return false;
}
}
protected int offscreenRepaintLineRange(int firstLine, int lastLine) {
if (offGfx == null) {
return 0;
}
int x = textArea.getHorizontalOffset();
int line;
for (line = firstLine; line <= lastLine; ) {
line += offscreenRepaintLine(line, x);
}
return line - firstLine;
}
protected int offscreenRepaintLine(int line, int x) {
ACLSLTokenMarker tokenMarker = textArea.getTokenMarker();
Font defaultFont = getFont();
Color defaultColor = getForeground();
int y = textArea.lineToY(line);
if (line < 0 || line >= textArea.getLineCount()) {
paintHighlight(line, y);
styles[ACLToken.INVALID].setGraphicsFlags(offGfx, defaultFont);
offGfx.drawString(".", 0, y + fm.getHeight());
return 1;
}
if (tokenMarker == null) {
currentLineIndex = line;
paintPlainLine(line, defaultFont, defaultColor, x, y);
return 1;
}
else {
int count = 0;
int lastVisibleLine = Math.min(textArea.getLineCount(),
textArea.getFirstLine() + textArea.getVisibleLines());
do {
currentLineIndex = line + count;
paintSyntaxLine(tokenMarker, currentLineIndex,
defaultFont, defaultColor, x, y);
y += fm.getHeight();
count++;
} while (tokenMarker.isNextLineRequested()
&& line + count < lastVisibleLine);
return count;
}
}
protected void paintPlainLine(int line, Font defaultFont,
Color defaultColor, int x, int y) {
paintHighlight(line, y);
textArea.getLineText(line, currentLine);
offGfx.setFont(defaultFont);
offGfx.setColor(defaultColor);
y += fm.getHeight();
x = Utilities.drawTabbedText(currentLine, x, y, offGfx, this, 0);
if (eolMarkers) {
offGfx.setColor(eolMarkerColor);
offGfx.drawString(".", x, y);
}
}
protected void paintSyntaxLine(ACLSLTokenMarker tokenMarker, int line,
Font defaultFont, Color defaultColor, int x, int y) {
textArea.getLineText(currentLineIndex, currentLine);
currentLineTokens = tokenMarker.markTokens(currentLine,
currentLineIndex);
paintHighlight(line, y);
offGfx.setFont(defaultFont);
offGfx.setColor(defaultColor);
styles = ACLSyntaxUtilities.getDefaultSyntaxStyles(this);
y += fm.getHeight();
x = ACLSyntaxUtilities.paintSyntaxLine(currentLine,
currentLineTokens, styles, this, offGfx, x, y);
if (eolMarkers) {
offGfx.setColor(eolMarkerColor);
offGfx.drawString(".", x, y);
}
}
protected void paintHighlight(int line, int y) {
/*
Clear the line's bounding rectangle
*/
int gap = fm.getMaxDescent() + fm.getLeading();
offGfx.setColor(getBackground());
offGfx.fillRect(0, y + gap, offImg.getWidth(this), fm.getHeight());
if (line >= textArea.getSelectionStartLine()
&& line <= textArea.getSelectionEndLine()) {
paintLineHighlight(line, y);
}
if (bracketHighlight && line == textArea.getBracketLine()) {
paintBracketHighlight(line, y);
}
if (line == textArea.getCaretLine()) {
paintCaret(line, y);
}
}
protected void paintLineHighlight(int line, int y) {
int height = fm.getHeight();
y += fm.getLeading() + fm.getMaxDescent();
int selectionStart = textArea.getSelectionStart();
int selectionEnd = textArea.getSelectionEnd();
if (selectionStart == selectionEnd) {
if (lineHighlight) {
offGfx.setColor(lineHighlightColor);
offGfx.fillRect(0, y, offImg.getWidth(this), height);
}
else {
offGfx.setColor(selectionColor);
int selectionStartLine = textArea.getSelectionStartLine();
int selectionEndLine = textArea.getSelectionEndLine();
int lineStart = textArea.getLineStartOffset(line);
int x1;
int x2;
if (selectionStartLine == selectionEndLine) {
x1 = textArea.offsetToX(line,
selectionStart - lineStart);
x2 = textArea.offsetToX(line,
selectionEnd - lineStart);
}
else if (line == selectionStartLine) {
x1 = textArea.offsetToX(line,
selectionStart - lineStart);
x2 = offImg.getWidth(this);
}
else if (line == selectionEndLine) {
x1 = 0;
x2 = textArea.offsetToX(line,
selectionEnd - lineStart);
}
else {
x1 = 0;
x2 = offImg.getWidth(this);
}
offGfx.fillRect(x1, y, x2 - x1, height);
}
}
}
protected void paintBracketHighlight(int line, int y) {
int position = textArea.getBracketPosition();
if (position == -1) {
return;
}
y += fm.getLeading() + fm.getMaxDescent();
int x = textArea.offsetToX(line, position);
offGfx.setColor(bracketHighlightColor);
// Hack!!! Since there is no fast way to get the character
// from the bracket matching routine, we use ( since all
// brackets probably have the same width anyway
offGfx.drawRect(x, y, fm.charWidth('(') - 1,
fm.getHeight() - 1);
}
protected void paintCaret(int line, int y) {
if (textArea.isCaretVisible()) {
int offset = textArea.getCaretPosition()
- textArea.getLineStartOffset(line);
int caretX = textArea.offsetToX(line, offset);
int caretWidth = ((blockCaret ||
textArea.isOverwriteEnabled()) ?
fm.charWidth('w') : 1);
y += fm.getLeading() + fm.getMaxDescent();
int height = fm.getHeight();
offGfx.setColor(caretColor);
if (textArea.isOverwriteEnabled()) {
offGfx.fillRect(caretX, y + height - 1,
caretWidth, 1);
}
else {
offGfx.drawRect(caretX, y, caretWidth - 1, height - 1);
}
}
}
protected static boolean copyAreaBroken;
// protected members
protected ACLTextArea textArea;
protected ACLSytntaxStyle[] styles;
protected Color caretColor;
protected Color selectionColor;
protected Color lineHighlightColor;
protected Color bracketHighlightColor;
protected Color eolMarkerColor;
protected boolean blockCaret;
protected boolean lineHighlight;
protected boolean bracketHighlight;
protected boolean eolMarkers;
protected int cols;
protected int rows;
protected int tabSize;
protected FontMetrics fm;
protected Graphics offGfx;
protected Image offImg;
protected int firstInvalid;
protected int lastInvalid;
// package-private members
int currentLineIndex;
ACLToken currentLineTokens;
Segment currentLine;
}
// ***EOF***