/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012-2014, Geomatys
*
* 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.
*/
package org.geotoolkit.cql;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.border.EmptyBorder;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.opengis.filter.Filter;
import org.opengis.filter.expression.Expression;
/**
* Swing CQL text pane. highlights syntax.
*
* @author Johann Sorel (Geomatys)
*/
public class JCQLTextPane extends JPanel implements KeyListener{
private final JTextPane guiText = new JTextPane();
private final JLabel guiError = new JLabel();
final Style styleDefault;
final Style styleComment;
final Style styleFunction;
final Style styleLiteral;
final Style styleParenthese;
final Style styleOperator;
final Style styleBinary;
final Style stylePropertyName;
final Style styleError;
public JCQLTextPane() {
super(new BorderLayout(0,0));
guiText.setBackground(Color.WHITE);
final JScrollPane scroll = new JScrollPane(guiText);
scroll.setBorder(new EmptyBorder(0, 0, 0, 0));
guiText.setBorder(new EmptyBorder(0, 0, 0, 0));
scroll.getInsets().set(0,0,0,0);
scroll.setViewportBorder(new EmptyBorder(0, 0, 0, 0));
scroll.getViewport().getInsets().set(0,0,0,0);
add(BorderLayout.CENTER,scroll);
add(BorderLayout.SOUTH,guiError);
guiText.addKeyListener(this);
styleDefault = guiText.addStyle("default", null);
StyleConstants.setForeground(styleDefault, Color.BLACK);
styleComment = guiText.addStyle("comment", null);
StyleConstants.setForeground(styleComment, Color.GRAY);
styleLiteral = guiText.addStyle("literal", null);
StyleConstants.setForeground(styleLiteral, new Color(0, 150, 0));
styleFunction = guiText.addStyle("function", null);
StyleConstants.setForeground(styleFunction, Color.MAGENTA);
styleParenthese = guiText.addStyle("parenthese", null);
StyleConstants.setForeground(styleParenthese, new Color(0, 0, 0));
styleOperator = guiText.addStyle("operator", null);
StyleConstants.setForeground(styleOperator, Color.BLACK);
StyleConstants.setBold(styleOperator, true);
styleBinary = guiText.addStyle("binary", null);
StyleConstants.setForeground(styleBinary, Color.BLACK);
StyleConstants.setBold(styleBinary, true);
stylePropertyName = guiText.addStyle("property", null);
StyleConstants.setForeground(stylePropertyName, Color.BLUE);
StyleConstants.setBold(stylePropertyName, true);
styleError = guiText.addStyle("error", null);
StyleConstants.setForeground(styleError, Color.RED);
StyleConstants.setBold(styleError, true);
}
public void setText(String cql){
guiText.setText(cql);
updateHightLight();
}
/**
* Insert text at current caret position
* @param text
*/
public void insertText(String text){
final int position = guiText.getCaretPosition();
final String cql = guiText.getText();
final StringBuilder sb = new StringBuilder();
sb.append(cql.substring(0,position));
sb.append(text);
sb.append(cql.substring(position));
guiText.setText(sb.toString());
guiText.setCaretPosition(position+text.length());
updateHightLight();
}
public void addText(String text){
guiText.setText(guiText.getText()+text);
updateHightLight();
}
public String getText(){
return guiText.getText();
}
public void setFilter(Filter filter){
setText(CQL.write(filter));
}
public void setExpression(Expression exp){
setText(CQL.write(exp));
}
public Filter getFilter() throws CQLException{
return CQL.parseFilter(guiText.getText());
}
public Expression getExpression() throws CQLException{
return CQL.parseExpression(guiText.getText());
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
updateHightLight();
}
private void updateHightLight(){
final StyledDocument doc = (StyledDocument) guiText.getDocument();
final String txt = guiText.getText();
final ParseTree tree = CQL.compile(txt);
doc.setCharacterAttributes(0, txt.length(), styleError, true);
syntaxHighLight(tree, doc, new AtomicInteger());
firePropertyChange("content", null, txt);
}
private void syntaxHighLight(ParseTree tree, StyledDocument doc, AtomicInteger position){
if(tree instanceof ParserRuleContext){
final ParserRuleContext prc = (ParserRuleContext) tree;
if(prc.exception!=null){
//error nodes
final Token tokenStart = prc.getStart();
Token tokenEnd = prc.getStop();
if(tokenEnd==null) tokenEnd = tokenStart;
final int offset = tokenStart.getStartIndex();
final int length = tokenEnd.getStopIndex()-tokenStart.getStartIndex() +1;
doc.setCharacterAttributes(offset, length, styleError, true);
return;
}
//special case for functions
if(prc instanceof CQLParser.ExpressionTermContext){
final CQLParser.ExpressionTermContext ctx = (CQLParser.ExpressionTermContext) prc;
if(ctx.NAME()!=null && ctx.LPAREN()!=null){
final int nbChild = tree.getChildCount();
for(int i=0;i<nbChild;i++){
final ParseTree pt = tree.getChild(i);
if(pt instanceof TerminalNode && ((TerminalNode)pt).getSymbol().getType() == CQLLexer.NAME){
final TerminalNode tn = (TerminalNode) pt;
// if index<0 = missing token
final Token token = tn.getSymbol();
final int offset = token.getStartIndex();
final int length = token.getStopIndex()-token.getStartIndex() +1;
position.addAndGet(length);
doc.setCharacterAttributes(offset, length, styleFunction, true);
}else{
syntaxHighLight(pt, doc, position);
}
}
return;
}
}
}
if(tree instanceof TerminalNode){
final TerminalNode tn = (TerminalNode) tree;
// if index<0 = missing token
final Token token = tn.getSymbol();
final int offset = token.getStartIndex();
final int length = token.getStopIndex()-token.getStartIndex() +1;
position.addAndGet(length);
switch(token.getType()){
case CQLLexer.COMMA :
case CQLLexer.UNARY :
case CQLLexer.MULT :
doc.setCharacterAttributes(offset, length, styleDefault, true);
break;
// EXpressions -------------------------------------------------
case CQLLexer.TEXT :
case CQLLexer.INT :
case CQLLexer.FLOAT :
case CQLLexer.DATE :
case CQLLexer.DURATION_P :
case CQLLexer.DURATION_T :
case CQLLexer.POINT :
case CQLLexer.LINESTRING :
case CQLLexer.POLYGON :
case CQLLexer.MPOINT :
case CQLLexer.MLINESTRING :
case CQLLexer.MPOLYGON :
doc.setCharacterAttributes(offset, length, styleLiteral, true);
break;
case CQLLexer.PROPERTY_NAME :
doc.setCharacterAttributes(offset, length, stylePropertyName, true);
break;
case CQLLexer.NAME :
if(tree.getChildCount()==0){
//property name
doc.setCharacterAttributes(offset, length, stylePropertyName, true);
}else{
//function name
doc.setCharacterAttributes(offset, length, styleFunction, true);
}
break;
case CQLLexer.RPAREN :
case CQLLexer.LPAREN :
doc.setCharacterAttributes(offset, length, styleParenthese, true);
break;
case CQLLexer.COMPARE :
case CQLLexer.LIKE :
case CQLLexer.IS :
case CQLLexer.BETWEEN :
case CQLLexer.IN :
doc.setCharacterAttributes(offset, length, styleOperator, true);
break;
case CQLLexer.AND :
case CQLLexer.OR :
case CQLLexer.NOT :
doc.setCharacterAttributes(offset, length, styleBinary, true);
break;
case CQLLexer.BBOX :
case CQLLexer.BEYOND :
case CQLLexer.CONTAINS :
case CQLLexer.CROSSES :
case CQLLexer.DISJOINT :
case CQLLexer.DWITHIN :
case CQLLexer.EQUALS :
case CQLLexer.INTERSECTS :
case CQLLexer.OVERLAPS :
case CQLLexer.TOUCHES :
case CQLLexer.WITHIN :
doc.setCharacterAttributes(offset, length, styleBinary, true);
break;
default :
doc.setCharacterAttributes(offset, length, styleError, true);
break;
}
}
final int nbChild = tree.getChildCount();
for(int i=0;i<nbChild;i++){
syntaxHighLight(tree.getChild(i), doc, position);
}
}
}