package ui.renderer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.text.CharacterIterator;
import java.util.LinkedList;
import common.prettyprinter.PrettyAnnotation;
import common.prettyprinter.PrettyCharIterator;
import common.prettyprinter.PrettyString;
import expressions.Expression;
/**
* Class providing support to render an expression.
*
* @author marcell
*
*/
public class ExpressionRenderer extends AbstractRenderer {
private class CheckerResult {
public Dimension size;
public int rows;
public PrettyAnnotation annotation;
public CheckerResult(Dimension s, int r, PrettyAnnotation a) {
this.size = s;
this.rows = r;
this.annotation = a;
}
}
private Color alternativeColor = null;
private Dimension minSize;
private Dimension maxSize;
private Expression expression;
private CheckerResult bestCheckerReturn;
private LinkedList<CheckerResult> checkerResults = new LinkedList<CheckerResult>();
public void setAlternativeColor (Color alternativeColor) {
this.alternativeColor = alternativeColor;
}
public Dimension getMinSize () {
return this.minSize;
}
public Dimension getMaxSize() {
return this.maxSize;
}
private CheckerResult checkExpression (PrettyString prettyString, PrettyAnnotation annotation) {
Dimension d = new Dimension ();
int width = 0;
int height = 0;
PrettyCharIterator it = prettyString.toCharacterIterator();
int[] annoBreaks = null;
if (annotation != null) {
annoBreaks = annotation.getBreakOffsets();
}
int i = 0;
int rows = 0;
for (char c = it.first(); c != CharacterIterator.DONE; c = it.next(), i++) {
// first check if annotation are given
// that way I can check the raw string itself
if (annotation != null) {
// check if the one of the breakpoints is set at the current cursor pos
for (int j=0; j<annoBreaks.length; j++) {
if (annoBreaks[j] == i) {
height += fontHeight;
if (width > d.width) d.width = width;
// next line will be indentated by 10 pixels
width = 10;
rows++;
// finished to check the annotations for this string position
break;
}
}
}
switch (it.getStyle()) {
case NONE: width += textFontMetrics.stringWidth("" + c); break;
case KEYWORD: width += keywordFontMetrics.stringWidth("" + c); break;
case CONSTANT: width += constantFontMetrics.stringWidth("" + c); break;
}
}
// check the width and the height for the last line. there wouldn't be any breakpoint
if (width > d.width) d.width = width;
height += fontHeight;
d.height = height;
rows++;
if (this.minSize == null || this.minSize.width > d.width) {
this.minSize = d;
}
if (this.maxSize == null || this.maxSize.width < d.width) {
this.maxSize = d;
}
return new CheckerResult (d, rows, annotation);
}
/**
*
* @param gc
* @param font
*/
public ExpressionRenderer (Expression expression) {
this.expression = expression;
}
/**
* Calculates the "best" size needed for the Expression.
* <br>
* The returned Dimension may not fit the hint size.
*
* @param expression The environment containing the key-value-pairs
* @param hint A width the gui "wants" to spend for the environment
* @return The actual needed size for the environment
*/
public Dimension getNeededSize (int maxWidth) {
CheckerResult smallest = null;
CheckerResult biggest = null;
for (CheckerResult cr : this.checkerResults) {
if (smallest == null || cr.size.width < smallest.size.width) {
smallest = cr;
}
if (cr.size.width <= maxWidth && (biggest == null || cr.size.width > biggest.size.width)) {
biggest = cr;
}
}
this.bestCheckerReturn = biggest;
if (this.bestCheckerReturn == null) {
this.bestCheckerReturn = smallest;
}
return new Dimension (this.bestCheckerReturn.size);
}
public int getRowCount () {
if (this.bestCheckerReturn == null)
return 1;
return this.bestCheckerReturn.rows;
}
public void checkAnnotationSizes() {
this.checkerResults.clear();
PrettyString prettyString = this.expression.toPrettyString();
CheckerResult cr = checkExpression(prettyString, null);
this.checkerResults.add(cr);
for (PrettyAnnotation pa : prettyString.getAnnotations()) {
cr = checkExpression (prettyString, pa);
this.checkerResults.add(cr);
}
}
public void render(int x, int y, Expression underline, Graphics gc, int maxWidth) {
getNeededSize(maxWidth);
render (x, y, underline, gc);
}
/**
* Renders the Expression at the position given by (x, y).
* <br>
* It is guarenteed that the size needed for the rendering is exactly the
* Size calculated by {@link #getNeededSize(Expression, Dimension)}.
*
* @param x The horizontal coordinate to render
* @param y The vertical coordinate to render
* @param underline The Expression that should be underlined.
* @param gc The graphics context used to render the content.
*/
public void render(int x, int y, Expression underline, Graphics gc) {
PrettyString prettyString = this.expression.toPrettyString();
PrettyCharIterator it = prettyString.toCharacterIterator();
int[] annoBreaks = null;
if (this.bestCheckerReturn != null && this.bestCheckerReturn.annotation != null) {
annoBreaks = this.bestCheckerReturn.annotation.getBreakOffsets();
}
// get the offsets of the underline expression
int uStart = -1;
int uEnd = -1;
if (underline != null) {
try {
PrettyAnnotation ua = prettyString.getAnnotationForPrintable(underline);
uStart = ua.getStartOffset();
uEnd = ua.getEndOffset();
} catch (Exception e) { }
}
int i = 0;
int posX = x;
int posY = y + fontHeight - fontDescent;
for (char c = it.first(); c != CharacterIterator.DONE; c = it.next(), i++) {
if (this.bestCheckerReturn != null && this.bestCheckerReturn.annotation != null) {
for (int j=0; j<annoBreaks.length; j++) {
if (annoBreaks[j] == i) {
posY += fontHeight;
posX = x + 10;
break;
}
}
}
Color fontColor = null;
FontMetrics fontMetrics = null;
Font font = null;
switch (it.getStyle()) {
case NONE:
fontColor = textColor;
fontMetrics = textFontMetrics;
font = textFont;
break;
case KEYWORD:
fontColor = keywordColor;
fontMetrics = keywordFontMetrics;
font = keywordFont;
break;
case CONSTANT:
fontColor = constantColor;
fontMetrics = constantFontMetrics;
font = constantFont;
break;
}
if (alternativeColor != null) {
fontColor = alternativeColor;
}
int sx = posX;
posX += fontMetrics.stringWidth("" + c);
if (i >= uStart && i <= uEnd) {
gc.setColor(underlineColor);
gc.drawLine(sx,posY + 1, posX, posY + 1);
}
gc.setColor(fontColor);
gc.setFont(font);
gc.drawString("" + c, sx, posY);
}
}
}