package tirateima.gui.editortexto;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.swing.JTextPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
import javax.swing.text.TabSet;
import javax.swing.text.TabStop;
import tirateima.gui.highlighting.c.CParser;
import tirateima.gui.highlighting.c.CParserConstants;
import tirateima.gui.highlighting.c.Token;
import tirateima.gui.highlighting.pascal.Anlex;
class FullLineHighlightPainter implements Highlighter.HighlightPainter {
Color color;
public FullLineHighlightPainter(Color c){
color = c;
}
public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) {
Rectangle r0 = null, r1 = null;
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(color);
// special case if p0 and p1 are on the same line
if (r0.y == r1.y) {
g.fillRect(0, r0.y, c.getWidth(), r0.height);
}else{
g.fillRect(0, r0.y, c.getWidth(), r1.y - r0.y + r1.height);
}
return;
}
}
/**
* Modela uma caixa de texto com código-fonte em Pascal.
*
* @author Renan e Luciano Santos
*/
@SuppressWarnings("serial")
public class CaixaTexto extends JTextPane implements DocumentListener{
private boolean analisando = false;
private boolean mudou = false;
private CParser parser = null;
private InputStream is = null;
/* Posição inicial de cada linha. */
List<Integer> linhas;
/*Define se o texto conterá higlighting ou não...*/
private boolean highlighted = true;
/*Define qual linha deve ser destacada. Se for menor que zero, nenhuma.*/
private int linha_destacada = -1;
/*Indices de acesso aos nomes dos estilos...*/
public static final String[] estilos = {"regular", "palavra_chave",
"literal", "comentario", "pontuacao", "num", "tipo", "diretivas"};
/*Cores do highlighting e do destaque.*/
private Color cores[] = {Color.BLACK, new Color(0, 0, 177)/*Dark Blue*/, new Color(64, 64, 255)/*Light Blue*/,
new Color(180,180,180),Color.RED, /*new Color(180, 50, 180)*/Color.MAGENTA,
new Color(0, 0, 177), new Color(0, 135, 0)/*Verde Escuro*/, new Color(255, 255, 0)};
/*Fonte básica...*/
Font fonte_basica = new Font("Courier New", Font.PLAIN, 14);
private Linguagem linguagem;
/*Constantes para identificar as cores a serem alteradas...*/
public static final int REGULAR = 0;
public static final int PALAVRA_CHAVE = 1;
public static final int LITERAL = 2;
public static final int COMENTARIO = 3;
public static final int PONTUACAO = 4;
public static final int NUMEROS = 5;
public static final int TIPO = 6;
public static final int DIRETIVAS = 7;
public static final int DESTAQUE = 8;
/**
* Constrói nova CaixaTexto usando o método construtor básico.
*/
public CaixaTexto() {
this("");
}
/**
* Constrói nova CaixaTexto.
*
* @param texto texto inicial da caixa de texto.
*/
public CaixaTexto(String texto){
super();
addStyles();
setTabs(this, 4);
setText(texto);
getStyledDocument().addDocumentListener(this);
addKeyListener(new KeyListener() {
public void keyPressed(KeyEvent e){}
public void keyReleased(KeyEvent e){}
public void keyTyped(KeyEvent e){
if(mudou){
parseText();
mudou = false;
}
}
});
}
public void setText(String texto){
try{
linhas = getLinhas(texto);
}catch(Exception e){
linhas = new ArrayList<Integer>();
}
if(this.linguagem == Linguagem.C)
parseTextC(texto);
else if (this.linguagem == Linguagem.PASCAL)
parseTextPascal(texto);
else
parseTextOutraLinguagem(texto);
}
public void setText(Reader reader,Linguagem linguagem) throws IOException{
this.linguagem = linguagem;
StringBuffer s = new StringBuffer("");
BufferedReader r = new BufferedReader(reader);
String linha = null;
try{
while((linha = r.readLine()) != null)
s.append(linha + "\n");
}catch(IOException e){
throw e;
}
this.setText(s.toString());
}
public void setHighlighted(boolean high){
if(highlighted != high){
highlighted = high;
parseText();
}
}
public void setMarcada(int linha){
if(linha != linha_destacada){
if(linha > linhas.size()){
linha_destacada = linhas.size();
}else{
linha_destacada = linha;
}
try{
DefaultHighlighter dh = (DefaultHighlighter) this.getHighlighter();
dh.removeAllHighlights();
if(linha_destacada > 0){
int i = linhas.get(linha_destacada - 1);
dh.addHighlight(i, i, new FullLineHighlightPainter(cores[DESTAQUE]));
}
this.update(this.getGraphics());
}catch(Exception e){
e.printStackTrace();
}
}
}
public void setBaseFont(Font f){
this.fonte_basica = f;
addStyles();
parseText();
}
/**
* Muda uma das cores de highlighting/destaque.
*
* Espera uma constante definindo qual cor deve ser mudada.
*
* @param const_cor Espera uma das constantes informando qual cor deve ser alterada.
* @param c Nova cor.
*/
public void setColor(int const_cor, Color c){
if((const_cor >= REGULAR) && (const_cor <= TIPO)){
cores[const_cor] = c;
}else{
cores[REGULAR] = c;
}
addStyles();
parseText();
}
public int getMarcada(){
return linha_destacada;
}
public String adicionaNumeracao(String texto){
StringBuffer sb = new StringBuffer();
int carret = 0;
for(int i=0; i<texto.length(); i++){
sb.append(i+1 + " ");
int index = texto.substring(carret).indexOf("\n");
if(index > -1){
sb.append(texto.substring(carret, carret + index) + "\n");
carret += ++index;
}else{
break;
}
}
return sb.toString();
}
public int getTotalLinhas(){
StringTokenizer st = new StringTokenizer(getText(), "\n");
return st.countTokens();
}
private void parseText(){
if(this.linguagem == Linguagem.C)
parseTextC(null);
else if(this.linguagem == Linguagem.PASCAL)
parseTextPascal(null);
else
parseTextOutraLinguagem(null);
}
/**
* Faz análise léxica PARA A LINGUAGEM C do arquivo fonte aplicando a ele
* estilos de acordo com a sintaxe da linguagem. Dessa forma, cada tipo de
* linguagem fica com uma cor.
*
* @param text
*/
private void parseTextC(String text){
String texto = text == null ? getText() : text;
super.setText("");
StyledDocument doc = getStyledDocument();
analisando = true;
if(highlighted){
if ((!texto.equals("")) && (texto != null)) {
try {
is = new ByteArrayInputStream(texto.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (parser == null)
parser = new CParser(is);
Token t = parser.getNextToken();
String estilo;
int line = 1;
boolean insert_line = true;
while(t.kind != CParserConstants.EOF){
if (t.kind == CParserConstants.LETTER)
System.out.println(t.kind + " "+ t.image);
estilo = estilos[REGULAR];
switch(t.kind){
case CParserConstants.IDENTIFIER:
estilo = estilos[REGULAR];
break;
case CParserConstants.DIRETIVAS:
estilo = estilos[DIRETIVAS];
break;
case CParserConstants.COMENTARIO_BLOCO:
estilo = estilos[COMENTARIO];
break;
case CParserConstants.COMENTARIO_LINHA:
estilo = estilos[COMENTARIO];
break;
case CParserConstants.INTEGER_LITERAL:
estilo = estilos[NUMEROS];
break;
case CParserConstants.DECIMAL_LITERAL:
estilo = estilos[NUMEROS];
break;
case CParserConstants.HEX_LITERAL:
estilo = estilos[NUMEROS];
break;
case CParserConstants.OCTAL_LITERAL:
estilo = estilos[NUMEROS];
break;
case CParserConstants.FLOATING_POINT_LITERAL:
estilo = estilos[NUMEROS];
break;
case CParserConstants.EXPONENT:
estilo = estilos[NUMEROS];
break;
case CParserConstants.CHARACTER_LITERAL:
estilo = estilos[LITERAL];
break;
case CParserConstants.STRING_LITERAL:
estilo = estilos[LITERAL];
break;
case CParserConstants.CONTINUE:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.VOLATILE:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.REGISTER:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.UNSIGNED:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.TYPEDEF:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.DFLT:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.DOUBLE:
estilo = estilos[TIPO];
break;
case CParserConstants.SIZEOF:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.SWITCH:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.RETURN:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.EXTERN:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.STRUCT:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.STATIC:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.SIGNED:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.WHILE:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.BREAK:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.UNION:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.CONST:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.FLOAT:
estilo = estilos[TIPO];
break;
case CParserConstants.SHORT:
estilo = estilos[TIPO];
break;
case CParserConstants.ELSE:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.CASE:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.LONG:
estilo = estilos[TIPO];
break;
case CParserConstants.ENUM:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.AUTO:
estilo = estilos[LITERAL];
break;
case CParserConstants.VOID:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.CHAR:
estilo = estilos[TIPO];
break;
case CParserConstants.GOTO:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.FOR:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.INT:
estilo = estilos[TIPO];
break;
case CParserConstants.IF:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.DO:
estilo = estilos[PALAVRA_CHAVE];
break;
case CParserConstants.LETTER:
estilo = estilos[LITERAL];
break;
case CParserConstants.DIGIT:
estilo = estilos[NUMEROS];
break;
default:
if ((t.kind >= 53) && (t.kind <= 98)) {
estilo = estilos[PONTUACAO];
}
}
try{
if(insert_line){
insert_line = false;
line++;
}
if("\n".equals(t.image)){
insert_line = true;
}
doc.insertString(doc.getLength(), t.image, doc.getStyle(estilo));
}catch(Exception e){}
t = parser.getNextToken();
}
}
}else{
try{
doc.insertString(0, texto, doc.getStyle(estilos[REGULAR]));
}catch(Exception e){}
}
analisando = false;
}
/**
* Faz análise léxica PARA A LINGUAGEM PASCAL do arquivo fonte aplicando a ele
* estilos de acordo com a sintaxe da linguagem. Dessa forma, cada tipo de
* linguagem fica com uma cor.
* @param text
*/
private void parseTextPascal(String text){
String texto = text == null ? getText() : text;
super.setText("");
StyledDocument doc = getStyledDocument();
analisando = true;
if(highlighted){
Anlex anlex = new Anlex(texto);
tirateima.gui.highlighting.pascal.Token t = anlex.getToken();
boolean coment_chave = false;
boolean coment_par = false;
String estilo;
int line = 1;
boolean insert_line = true;
while(t.getId() != tirateima.gui.highlighting.pascal.Token.EOB){
estilo = estilos[REGULAR];
switch(t.getId()){
case tirateima.gui.highlighting.pascal.Token.IDENTIFIER:
if(tirateima.gui.highlighting.pascal.Token.ehPalavraChave(t.getValor())){
estilo = estilos[PALAVRA_CHAVE];
}else if(tirateima.gui.highlighting.pascal.Token.ehTipo(t.getValor())){
estilo = estilos[TIPO];
}
break;
case tirateima.gui.highlighting.pascal.Token.STRING:
estilo = estilos[LITERAL];
break;
case tirateima.gui.highlighting.pascal.Token.BEGINCOMMENT_CH:
if(!coment_par){
estilo = estilos[COMENTARIO];
coment_chave = true;
}
break;
case tirateima.gui.highlighting.pascal.Token.ENDCOMMENT_CH:
if(coment_chave){
estilo = estilos[COMENTARIO];
coment_chave = false;
}
break;
case tirateima.gui.highlighting.pascal.Token.BEGINCOMMENT_PAR:
if(!coment_chave){
estilo = estilos[COMENTARIO];
coment_par = true;
}
break;
case tirateima.gui.highlighting.pascal.Token.ENDCOMMENT_PAR:
if(coment_par){
estilo = estilos[COMENTARIO];
coment_par = false;
}
break;
case tirateima.gui.highlighting.pascal.Token.PONT:
estilo = estilos[PONTUACAO];
break;
case tirateima.gui.highlighting.pascal.Token.NUM:
estilo = estilos[NUMEROS];
break;
}
if(coment_par || coment_chave)
estilo = estilos[COMENTARIO];
try{
if(insert_line){
// doc.insertString(doc.getLength(), line + " ", doc.getStyle(estilos[NUMEROS]));
insert_line = false;
line++;
}
if("\n".equals(t.getValor())){
insert_line = true;
}
doc.insertString(doc.getLength(), t.getValor(), doc.getStyle(estilo));
}catch(Exception e){}
t = anlex.getToken();
}
}else{
try{
doc.insertString(0, texto, doc.getStyle(estilos[REGULAR]));
}catch(Exception e){}
}
analisando = false;
}
/**
* Faz análise léxica PARA AS OUTRAS LINGUAGEMS.
*
* Nesse caso, por não ser uma linguagem reconhecida e suportada no
* tira-teima para fins de linguagens, simplesmente faz o parse sem
* highlighting, mas com as demais funcionalidades (como destaque de linha
* e quebra de linha)
*
* @param text
*/
private void parseTextOutraLinguagem(String text){
String texto = text == null ? getText() : text;
super.setText("");
StyledDocument doc = getStyledDocument();
analisando = true;
try{
doc.insertString(0, texto, doc.getStyle(estilos[REGULAR]));
}catch(Exception e){}
analisando = false;
}
void setTabs( JTextPane textPane, int charactersPerTab)
{
FontMetrics fm = textPane.getFontMetrics( textPane.getFont() );
int charWidth = fm.charWidth( 'w' );
int tabWidth = charWidth * charactersPerTab;
TabStop[] tabs = new TabStop[10];
for (int j = 0; j < tabs.length; j++)
{
int tab = j + 1;
tabs[j] = new TabStop( tab * tabWidth );
}
TabSet tabSet = new TabSet(tabs);
SimpleAttributeSet attributes = new SimpleAttributeSet();
StyleConstants.setTabSet(attributes, tabSet);
int length = textPane.getDocument().getLength();
textPane.getStyledDocument().setParagraphAttributes(0, length, attributes, false);
}
/**
* Divide o texto em linhas e retorna a posição de cada uma.
*
* @param origem Origem do texto.
* @return Posição inicial de cada linha no texto.
*
* @throws IOException
*/
private List<Integer> getLinhas(String origem) throws IOException{
List<Integer> lista = new ArrayList<Integer>();
BufferedReader br = new BufferedReader(new StringReader(origem));
String linha = null;
int i = 0;
while((linha = br.readLine()) != null){
lista.add(i);
/* Tamanho da linha + \n */
i += linha.length() + 1;
}
return lista;
}
private void addStyles(){
StyledDocument doc = getStyledDocument();
Style def = StyleContext.getDefaultStyleContext().
getStyle(StyleContext.DEFAULT_STYLE);
/*Remove estilos antes de adicionar...*/
for(int i = 0; i <= DIRETIVAS; i++){
doc.removeStyle(estilos[i]);
doc.addStyle(estilos[i], def);
}
/*Estilo para texto qualquer...*/
Style s = doc.getStyle("regular");
StyleConstants.setFontFamily(def, fonte_basica.getFamily());
StyleConstants.setFontSize(def, fonte_basica.getSize());
/*Estilo para palavras chave...*/
s = doc.getStyle("palavra_chave");
StyleConstants.setBold(s, true);
StyleConstants.setForeground(s, cores[PALAVRA_CHAVE]);
/*Estilo para literais...*/
s = doc.getStyle("literal");
StyleConstants.setBold(s, false);
StyleConstants.setForeground(s, cores[LITERAL]);
/*Estilo para comentários...*/
s = doc.getStyle("comentario");
StyleConstants.setBold(s, false);
StyleConstants.setItalic(s, true);
StyleConstants.setForeground(s, cores[COMENTARIO]);
/*Estilo para pontuação...*/
s = doc.getStyle("pontuacao");
StyleConstants.setBold(s, true);
StyleConstants.setItalic(s, false);
StyleConstants.setForeground(s, cores[PONTUACAO]);
/*Estilo para numeração...*/
s = doc.getStyle("num");
StyleConstants.setBold(s, false);
StyleConstants.setItalic(s, false);
StyleConstants.setForeground(s, cores[NUMEROS]);
/*Estilo para tipos...*/
s = doc.getStyle("tipo");
StyleConstants.setBold(s, true);
StyleConstants.setItalic(s, false);
StyleConstants.setForeground(s, cores[TIPO]);
/*Estilo para diretivas...*/
s = doc.getStyle("diretivas");
StyleConstants.setBold(s, true);
StyleConstants.setItalic(s, false);
StyleConstants.setForeground(s, cores[DIRETIVAS]);
}
public void insertUpdate(DocumentEvent e){
mudou();
}
public void removeUpdate(DocumentEvent e){
mudou();
}
public void changedUpdate(DocumentEvent e){}
private void mudou(){
if(!analisando){
mudou = true;
}
}
}