/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor;
import javax.swing.text.JTextComponent;
import java.awt.*;
/** Draw graphics functions as abstraction over various kinds of drawing. It's used
* for drawing into classic graphics, printing and measuring.
* Generally there are only the setters for some properties because
* the draw-engine doesn't retrieve the values that it previously
* set.
*
* @author Miloslav Metelka
* @version 1.00
*/
interface DrawGraphics {
/** Set foreground color */
public void setForeColor(Color foreColor);
/** Set background color */
public void setBackColor(Color backColor);
/** Inform the draw-graphics about the current
* background color of the component.
*/
public void setDefaultBackColor(Color defaultBackColor);
public void setStrikeThroughColor(Color strikeThroughColor);
public void setUnderlineColor(Color underlineColor);
public void setWaveUnderlineColor(Color waveUnderlineColor);
/** Set current font */
public void setFont(Font font);
/** Set the current x-coordinate */
public void setX(int x);
/** Set the current y-coordinate */
public void setY(int y);
/** Set the height of the line. */
public void setLineHeight(int lineHeight);
/** Set the ascent of the line. */
public void setLineAscent(int lineAscent);
/** Get the AWT-graphics to determine whether this draws to a graphics.
* This is useful for fast line numbering and others.
*/
public Graphics getGraphics();
/** Whether draw graphics supports displaying of line numbers.
* If not line number displaying is not done.
*/
public boolean supportsLineNumbers();
/** Initialize this draw graphics before drawing */
public void init(DrawContext ctx);
/** Called when whole drawing ends. Can be used to deallocate
* some resources etc.
*/
public void finish();
/** Fill rectangle at the current [x, y] with the current
* background color.
* @param width width of the rectangle to fill in points. The current x-coordinate
* must be increased by width automatically.
*/
public void fillRect(int width);
/** Draw characters from the specified offset in the buffer
* @param offset offset in the buffer for drawn text; if the text contains
* tabs, then offset is set to -1 and length contains the count
* of the space characters that correspond to the expanded tabs
* @param length length of the text being drawn
* @param width width of the text being drawn in points. The current
* x-coordinate must be increased by width automatically.
*/
public void drawChars(int offset, int length, int width);
/** Draw the expanded tab characters.
* @param offset offset in the buffer where the tab characters start.
* @param length number of the tab characters
* @param spaceCount number of spaces that replace the tabs
* @param width width of the spaces in points. The current x-coordinate
* must be increased by width automatically.
*/
public void drawTabs(int offset, int length, int spaceCount, int width);
/** Set character buffer from which the characters are drawn. */
public void setBuffer(char[] buffer);
/** This method is called to notify this draw graphics in response
* from targetPos parameter passed to draw().
* @param offset position that was reached during the drawing.
* @param ch character at offset
* @param charWidth visual width of the character ch
* @param ctx current draw context containing
* @return whether the drawing should continue or not. If it returns
* false it's guaranteed that this method will not be called again
* and the whole draw() method will be stopped. <BR>The only
* exception is when the -1 is used as the target offset
* when draw() is called which means that every offset
* is a potential target offset and must be checked.
* In this case the binary search is used when finding
* the target offset inside painted fragment. That greatly
* improves performance for long fragments because
* the font metrics measurements are relatively expensive.
*/
public boolean targetOffsetReached(int offset, char ch, int x,
int charWidth, DrawContext ctx);
/** EOL encountered and should be handled. */
public void eol();
/** Abstract draw-graphics that maintains a fg and bg color, font,
* current x and y coordinates.
*/
static abstract class AbstractDG implements DrawGraphics {
/** Current foreground color */
Color foreColor;
/** Current background color */
Color backColor;
/** Default background color */
Color defaultBackColor;
/** Current font */
Font font;
/** Character buffer from which the data are drawn */
char[] buffer;
/** Current x-coordinate */
int x;
/** Current y-coordinate */
int y;
/** Height of the line being drawn */
int lineHeight;
/** Ascent of the line being drawn */
int lineAscent;
public Color getForeColor() {
return foreColor;
}
public void setForeColor(Color foreColor) {
this.foreColor = foreColor;
}
public Color getBackColor() {
return backColor;
}
public void setBackColor(Color backColor) {
this.backColor = backColor;
}
public Color getDefaultBackColor() {
return defaultBackColor;
}
public void setDefaultBackColor(Color defaultBackColor) {
this.defaultBackColor = defaultBackColor;
}
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getLineHeight() {
return lineHeight;
}
public void setLineHeight(int lineHeight) {
this.lineHeight = lineHeight;
}
public int getLineAscent() {
return lineAscent;
}
public void setLineAscent(int lineAscent) {
this.lineAscent = lineAscent;
}
public char[] getBuffer() {
return buffer;
}
public void setBuffer(char[] buffer) {
this.buffer = buffer;
}
public void drawChars(int offset, int length, int width) {
x += width;
}
public void drawTabs(int offset, int length, int spaceCount, int width) {
x += width;
}
public void setStrikeThroughColor(Color strikeThroughColor) {
}
public void setUnderlineColor(Color underlineColor) {
}
public void setWaveUnderlineColor(Color waveUnderlineColor) {
}
}
static class SimpleDG extends AbstractDG {
public Graphics getGraphics() {
return null;
}
public boolean supportsLineNumbers() {
return false;
}
public void init(DrawContext ctx) {
}
public void finish() {
}
public void fillRect(int width) {
}
public boolean targetOffsetReached(int offset, char ch, int x,
int charWidth, DrawContext ctx) {
return true; // shouldn't reach this place
}
public void eol() {
}
}
/** Implementation of DrawGraphics to delegate to some Graphics.
* It optimizes the drawing by joining together the pieces of
* the text drawn with the same font and fg/bg color.
*/
static final class GraphicsDG extends SimpleDG {
private Graphics graphics;
/** Current graphics color */
private Color gColor;
/** Current graphics font */
private Font gFont;
/** Start of the chars that were not drawn yet. It can be -1
* to indicate the buffered characters were just flushed.
*/
private int startOffset = -1;
/** End of the chars that were not drawn yet */
private int endOffset;
/** X coordinate where the drawing of chars should occur */
private int startX;
/** Y coordinate where the drawing of chars should occur */
private int startY;
private int width;
private Color strikeThroughColor;
private Color underlineColor;
private Color waveUnderlineColor;
/** Alpha used for drawing the glyphs on the background */
private AlphaComposite alpha = null;
/** Access to annotations for this document which will be
* drawn on the background */
private Annotations annos = null;
GraphicsDG(Graphics graphics) {
this.graphics = graphics;
}
public void setForeColor(Color foreColor) {
if (!foreColor.equals(this.foreColor)) {
flush();
this.foreColor = foreColor;
}
}
public void setBackColor(Color backColor) {
if (!backColor.equals(this.backColor)) {
flush();
this.backColor = backColor;
}
}
public void setStrikeThroughColor(Color strikeThroughColor) {
if ((strikeThroughColor != this.strikeThroughColor)
&& (strikeThroughColor == null
|| !strikeThroughColor.equals(this.strikeThroughColor))
) {
flush();
this.strikeThroughColor = strikeThroughColor;
}
}
public void setUnderlineColor(Color underlineColor) {
if ((underlineColor != this.underlineColor)
&& (underlineColor == null
|| !underlineColor.equals(this.underlineColor))
) {
flush();
this.underlineColor = underlineColor;
}
}
public void setWaveUnderlineColor(Color waveUnderlineColor) {
if ((waveUnderlineColor != this.waveUnderlineColor)
&& (waveUnderlineColor == null
|| !waveUnderlineColor.equals(this.waveUnderlineColor))
) {
flush();
this.waveUnderlineColor = waveUnderlineColor;
}
}
public void setFont(Font font) {
if (!font.equals(this.font)) {
flush();
this.font = font;
}
}
public void setX(int x) {
if (x != this.x) {
flush();
this.x = x;
}
}
public void setY(int y) {
if (y != this.y) {
flush();
this.y = y;
}
}
public void init(DrawContext ctx) {
JTextComponent c = ctx.getEditorUI().getComponent();
gColor = graphics.getColor();
gFont = graphics.getFont();
// initialize reference to annotations
annos = ctx.getEditorUI().getDocument().getAnnotations();
}
public void finish() {
flush();
}
private void flush() {
if (startOffset < 0) {
return;
}
if (startOffset == endOffset) {
startOffset = -1;
return;
}
// First possibly fill the rectangle
fillRectImpl(startX, startY, x - startX);
if (AnnotationTypes.getTypes().isBackgroundDrawing().booleanValue()) {
if (alpha == null)
alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, AnnotationTypes.getTypes().getBackgroundGlyphAlpha().intValue() / 100f);
AnnotationDesc[] annosArray = annos.getPasiveAnnotations( (int)( (float)startY / (float)lineHeight ));
int glyphX=2;
if (annosArray != null) {
Graphics2D g2d = (Graphics2D) graphics;
Shape shape = graphics.getClip();
// set alpha composite
Composite origin = g2d.getComposite();
g2d.setComposite(alpha);
// clip the drawing area
Rectangle r = new Rectangle(startX, startY, x - startX, lineHeight);
r = r.intersection(shape.getBounds());
graphics.setClip(r);
for (int i=0; i < annosArray.length; i++) {
g2d.drawImage(annosArray[i].getGlyph(), glyphX, startY, null);
glyphX += annosArray[i].getGlyph().getWidth(null)+1;
}
// restore original clip region
graphics.setClip(shape);
// restore original ocmposite
g2d.setComposite(origin);
}
}
// Check whether the graphics uses right color
if (foreColor != gColor) {
graphics.setColor(foreColor);
gColor = foreColor;
}
// Check whether the graphics uses right font
if (font != gFont) {
graphics.setFont(font);
gFont = font;
}
graphics.drawChars(buffer, startOffset, endOffset - startOffset,
startX, startY + lineAscent);
if (strikeThroughColor != null) { // draw strike-through
FontMetricsCache.Info fmcInfo = FontMetricsCache.getInfo(font);
if (strikeThroughColor != gColor) {
graphics.setColor(strikeThroughColor);
gColor = strikeThroughColor;
}
graphics.fillRect(startX,
startY + (int)(fmcInfo.getStrikethroughOffset(graphics) + lineAscent + 0.5),
x - startX,
(int)(fmcInfo.getStrikethroughThickness(graphics) + 0.5)
);
}
if (waveUnderlineColor != null) { // draw wave underline
FontMetricsCache.Info fmcInfo = FontMetricsCache.getInfo(font);
if (waveUnderlineColor != gColor) {
graphics.setColor(waveUnderlineColor);
gColor = waveUnderlineColor;
}
int[] wf = {0, +1, 0, -1};
int x0 = startX;
// Add one to be right below the baseline
int y0 = (int)(startY + fmcInfo.getUnderlineOffset(graphics) + lineAscent + 1 + 0.5);
while (x0 <= x) {
graphics.drawLine(x0, y0 + wf[x0 % 4],
x0 + 1, y0 + wf[(x0 + 1) % 4]);
x0++;
}
}
if (underlineColor != null) { // draw underline
FontMetricsCache.Info fmcInfo = FontMetricsCache.getInfo(font);
if (underlineColor != gColor) {
graphics.setColor(underlineColor);
gColor = underlineColor;
}
// Add one pixel to the underline offset
graphics.fillRect(startX,
startY + (int)(fmcInfo.getUnderlineOffset(graphics) + lineAscent + 1.5),
x - startX,
(int)(fmcInfo.getUnderlineThickness(graphics) + 0.5)
);
}
startOffset = -1; // signal no characters to draw
}
public Graphics getGraphics() {
return graphics;
}
public boolean supportsLineNumbers() {
return true;
}
public void fillRect(int width) {
fillRectImpl(x, y, width);
x += width;
}
private void fillRectImpl(int rx, int ry, int width) {
if (width > 0) { // only for non-zero width
// only fill for different color than current background
if (!backColor.equals(defaultBackColor)) {
if (backColor != gColor) {
graphics.setColor(backColor);
gColor = backColor;
}
graphics.fillRect(rx, ry, width, lineHeight);
}
}
}
public void drawChars(int offset, int length, int width) {
if (length >= 0) {
if (startOffset < 0) { // no token yet
startOffset = offset;
endOffset = offset + length;
this.startX = x;
this.startY = y;
this.width = width;
} else { // already token before
endOffset += length;
}
}
x += width;
}
public void drawTabs(int offset, int length, int spaceCount, int width) {
if (width > 0) {
flush();
fillRectImpl(x, y, width);
x += width;
}
}
public void setBuffer(char[] buffer) {
flush();
this.buffer = buffer;
startOffset = -1;
}
public void eol() {
flush();
}
}
static final class PrintDG extends SimpleDG {
PrintContainer container;
/** Whether there were some paints already on the line */
boolean lineInited;
/** Construct the new print graphics
* @param container print container to which the tokens
* are added.
*/
public PrintDG(PrintContainer container) {
this.container = container;
}
public boolean supportsLineNumbers() {
return true;
}
public void drawChars(int offset, int length, int width) {
if (length > 0) {
char[] chars = new char[length];
System.arraycopy(buffer, offset, chars, 0, length);
container.add(chars, font, foreColor, backColor);
}
}
private void printSpaces(int spaceCount) {
char[] chars = new char[spaceCount];
System.arraycopy(Analyzer.getSpacesBuffer(spaceCount), 0, chars, 0, spaceCount);
container.add(chars, font, foreColor, backColor);
}
public void drawTabs(int offset, int length, int spaceCount, int width) {
printSpaces(spaceCount);
}
public void eol() {
if (!lineInited && container.initEmptyLines()) {
printSpaces(1);
}
container.eol();
lineInited = false; // signal that the next line is not inited yet
}
}
}