/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This code has been written initially for Scilab (http://www.scilab.org/).
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.
*/
package org.geogebra.desktop.gui.editor;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.Map;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Position;
import javax.swing.text.Segment;
import javax.swing.text.Utilities;
import javax.swing.text.WrappedPlainView;
/**
*
* @author Calixte DENIZET
*
*/
public class GeoGebraView extends WrappedPlainView {
/**
* A tabulation can be rendered with a vertical line.
*/
public static final int TABVERTICAL = 0;
/**
* A tabulation can be rendered with a double-chevrons.
*/
public static final int TABDOUBLECHEVRONS = 1;
/**
* A tabulation can be rendered with an horizontal line.
*/
public static final int TABHORIZONTAL = 2;
/**
* A tabulation can be rendered with a character.
*/
public static final int TABCHARACTER = 3;
/**
* A tabulation can be rendered with nothing.
*/
public static final int TABNOTHING = 4;
private static final String DESKTOPHINTS = "awt.font.desktophints";
private ViewContext context;
private Lexer lexer;
private Document doc;
private Segment text = new Segment();
private boolean isTabViewable = true;
private boolean isWhiteViewable = true;
private boolean enable = true;
private int tabType;
private String tabCharacter = " ";
private final Rectangle rect = new Rectangle();
private Map desktopFontHints;
private boolean enableDesktopFontHints = true;
private int whiteHeight;
private int whiteWidth;
private boolean unselected = true;
/**
* The constructor to set this view for an element with a context
* (containing infos such as colors or fonts of the keywords).
*
* @param elem
* the element to view
* @param lexer
* the lexer to use
* @param context
* used to view the element
*/
GeoGebraView(Element elem, Lexer lexer, ViewContext context) {
super(elem);
this.context = context;
this.lexer = lexer;
this.doc = getDocument();
lexer.setDocument(doc);
setTabRepresentation(TABVERTICAL);
}
/**
* A tabulation can be drawn with a mark
*
* @param b
* true if viewable or not
*/
public void setTabViewable(boolean b) {
isTabViewable = b;
}
/**
* A white can be drawn with a mark
*
* @param b
* true if viewable or not
*/
public void setWhiteViewable(boolean b) {
isWhiteViewable = b;
}
/**
* @return the width of a white
*/
public int getWhiteWidth() {
return whiteWidth;
}
/**
* This method can be used to draw anything you want in the editor (such as
* the line of maximum recommanded chars).
*
* @param g
* the graphics where to draw
* @param a
* the shape bounding the visible area
* @overload paint method in WrappedPlainView
*/
@Override
public void paint(Graphics g, Shape a) {
super.paint(g, a);
}
/**
* A trick to be sure that all the line is covered by an highlight
* {@inheritDoc}
*/
@Override
public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1,
Shape a) throws BadLocationException {
Rectangle r = (Rectangle) super.modelToView(p0, b0, p1, b1, a);
r.width = ((Rectangle) a).width;
return r;
}
/**
* A trick to easily determine the y-coordinate of a the line n
*
* @param n
* the line number
* @return the y-coordinate of the line
*/
public int getLineAllocation(int n) {
rect.setLocation(0, 4); // Why 4 ?? Because it works with 4 !
try {
childAllocation(n, rect);
} catch (ArrayIndexOutOfBoundsException e) {
}
return rect.y;
}
/**
* Used when the font is changed in the pane
*/
public void reinitialize() {
desktopFontHints = null;
enableDesktopFontHints = true;
}
/**
* Very important method since we draw the text in this method !!
*
* @param g
* the graphics where to draw
* @param sx
* the x-coordinate where to draw
* @param sy
* the y-coordinate ... (guess the end of the sentence)
* @param p0
* the start of the text in the doc
* @param p1
* the end of the text in the doc
* @return the x-coordinate where to draw the next piece of text
* @throws BadLocationException
* if p0 and p1 are bad positions in the text
*/
@Override
protected int drawUnselectedText(Graphics g, int sx, int sy, int p0, int p1)
throws BadLocationException {
if (!enable) {
return super.drawUnselectedText(g, sx, sy, p0, p1);
}
if (enableDesktopFontHints && desktopFontHints == null) {
/*
* This hint is used to have antialiased fonts in the view in using
* the same method (differents way to antialias with LCD screen) as
* the desktop.
*/
desktopFontHints = (Map) Toolkit.getDefaultToolkit()
.getDesktopProperty(DESKTOPHINTS);
calculateHeight(((Graphics2D) g).getFontRenderContext(),
context.tokenFont);
enableDesktopFontHints = desktopFontHints != null;
}
if (enableDesktopFontHints) {
((Graphics2D) g).addRenderingHints(desktopFontHints);
}
g.setFont(context.tokenFont);
/*
* The lexer returns all tokens between the pos p0 and p1. The value of
* the returned token determinates the color and the font. The lines can
* be broken by the Pane so we must look at previous and next chars to
* know if p0 or p1 is "inside" a token.
*/
Element elem = doc.getDefaultRootElement();
Element line = elem.getElement(elem.getElementIndex(p0));
int prevTok = -1;
int tok = -1;
int mark = p0;
int start = p0;
int x = sx;
int y = sy;
boolean isBroken = false;
int startL = line.getStartOffset();
int endL = line.getEndOffset();
if (startL != start) {
// we are drawing a broken line
try {
lexer.setRange(startL, endL);
while (startL < start) {
tok = lexer.scan();
startL = lexer.start + lexer.yychar() + lexer.yylength();
}
isBroken = true;
} catch (IOException e) {
}
}
if (!isBroken) {
lexer.setRange(start, endL);
}
while (start < p1) {
try {
if (!isBroken) {
tok = lexer.scan();
} else {
isBroken = false;
}
} catch (IOException e) {
}
start = lexer.start + lexer.yychar();
int end = Math.min(p1, start + lexer.yylength());
if (end != mark) {
if (tok != prevTok) {
if (unselected) {
g.setColor(context.tokenColors[tok]);
} else {
g.setColor(Color.WHITE);
}
prevTok = tok;
}
doc.getText(mark, end - mark, text);
int w;
if ((context.tokenAttrib[tok] & 1) != 0) {
w = Utilities.getTabbedTextWidth(text, g.getFontMetrics(),
x, this, mark);
g.drawLine(x, y + 1, x + w, y + 1);
}
if ((context.tokenAttrib[tok] & 2) != 0) {
w = Utilities.getTabbedTextWidth(text, g.getFontMetrics(),
x, this, mark);
g.drawLine(x, y - whiteHeight, x + w, y - whiteHeight);
}
/*
* disabled background filling if ((context.tokenAttrib[tok] &
* 4) != 0) { w = Utilities.getTabbedTextWidth(text,
* g.getFontMetrics(), x, this, mark); FontMetrics fm =
* g.getFontMetrics(); int hgt = fm.getHeight(); int desc =
* fm.getDescent(); g.fillRect(x, y - hgt, w, hgt + desc);
* g.setColor(context.tokenColors[LexerConstants.DEFAULT]); }
*/
switch (tok) {
case LexerConstants.WHITE:
if (isWhiteViewable) {
w = Utilities.getTabbedTextWidth(text,
g.getFontMetrics(), x, this, mark);
g.drawLine(x + (w - 1) / 2, y - whiteHeight,
x + (w + 1) / 2, y - whiteHeight);
}
break;
case LexerConstants.TAB:
if (isTabViewable) {
paintTab(text, x, y, g, mark);
}
break;
case LexerConstants.UNKNOWN:
w = Utilities.getTabbedTextWidth(text, g.getFontMetrics(),
x, this, mark);
for (int i = 0; i < w; i += 4) {
g.drawLine(x + i, y + 2, x + i + 1, y + 2);
}
for (int i = 2; i < w; i += 4) {
g.drawLine(x + i, y + 1, x + i + 1, y + 1);
}
break;
default:
break;
}
x = Utilities.drawTabbedText(text, x, y, g, this, mark);
mark = end;
}
start = end;
}
return x;
}
/**
* Draw the selected text.
*
* @param g
* the graphics where to draw
* @param x
* the x-coordinate where to draw
* @param y
* the y-coordinate ... (guess the end pf the sentence)
* @param p0
* the start of the text in the doc
* @param p1
* the end of the text in the doc
* @return the x-coordinate where to draw the next piece of text
* @throws BadLocationException
* if p0 and p1 are bad positions in the text
*/
@Override
protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
throws BadLocationException {
unselected = false;
int z = drawUnselectedText(g, x, y, p0, p1);
unselected = true;
return z;
}
/**
* Used to give the way to represent a tabulation. By default TABVERTICAL is
* used.
*
* @param type
* must be TABVERTICAL or TABDOUBLECHEVRONS or TABHORIZONTAL If a
* bad value is given, then nothing will be drawn
*/
public void setTabRepresentation(int type) {
this.tabType = type;
}
/**
* Used to represent a tabulation with the given character ('|' or '#'
* or...)
*
* @param rep
* the char representing a tab
*/
public void setTabRepresentation(char rep) {
setTabRepresentation(TABCHARACTER);
this.tabCharacter = Character.toString(rep);
}
/**
* Method to paint a tabulation according to the setTabRepresentation.
*
* @param text
* the segment of text representing a tabulation
* @param x
* the x-coordinate where to draw
* @param y
* the y-coordinate where to draw
* @param g
* the graphics ... (yeah ! once again)
* @param start
* the position in the document
*/
protected void paintTab(Segment text, int x, int y, Graphics g, int start) {
FontMetrics fm = g.getFontMetrics();
int w = Utilities.getTabbedTextWidth(text, fm, x, this, start);
switch (tabType) {
case TABVERTICAL:
g.drawLine(x, y + 4, x, y + 4 - fm.getHeight());
break;
case TABDOUBLECHEVRONS:
g.drawString("\u00BB", x, y);
break;
case TABHORIZONTAL:
g.drawLine(x, y - whiteHeight, x + w - 1, y - whiteHeight);
break;
case TABCHARACTER:
g.drawString(tabCharacter, x, y);
break;
default:
}
}
/**
* Determinates the height of a '+' to have the vertical shift to draw a
* line which strokes the text or to draw the mark let by a white.
*
* @param frc
* a font context
* @param f
* the font where to take the '+'
*/
private void calculateHeight(FontRenderContext frc, Font f) {
TextLayout layout = new TextLayout("+", f, frc);
Rectangle2D rectangle = layout.getBounds();
whiteHeight = (int) Math.round(-rectangle.getY() / 2);
layout = new TextLayout("w", f, frc);
rectangle = layout.getBounds();
whiteWidth = (int) Math.round(rectangle.getWidth());
}
}