/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* CALStyleContext.java
* Creation date: (1/18/01 6:39:38 PM)
* By: Luke Evans
*/
package org.openquark.gems.client.caleditor;
import java.awt.Color;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import javax.swing.text.AttributeSet;
import javax.swing.text.Element;
import org.openquark.cal.compiler.CALTokenTypes;
/**
* The collection of styles used by the CALEditor for chromacoding (syntax highlighting)
* the CAL source in CALEditor. We also act as the factory for creating editor views from
* the CAL MIME type (as encapsulated in the CALEditorKit).
* Creation date: (1/18/01 6:39:38 PM)
* @author Luke Evans
*/
public class CALStyleContext extends javax.swing.text.StyleContext implements javax.swing.text.ViewFactory {
private static final long serialVersionUID = 8476223245936819970L;
/**
* The styles representing the actual token types.
*/
private final javax.swing.text.Style[] tokenStyles;
/**
* Cache of foreground colours to represent the
* various tokens.
*/
transient private java.awt.Color[] tokenColours;
/**
* Cache of fonts to represent the various tokens.
*/
transient private java.awt.Font[] tokenFonts;
/**
* The current style listener. Currently *unicast*
*/
transient private CALSyntaxStyleListener styleListener;
/**
* This View uses lexical elements to determine the style of text elements which
* it renders. Taken from an example on www.javasoft.com in the Swing Connection
* Creation date: (1/18/01 7:00:20 PM)
* @author Luke Evans
*/
class CALView extends javax.swing.text.PlainView {
CALDocument.Scanner lexer;
boolean lexerValid;
/**
* Construct a simple colorized view of CAL source text.
*/
CALView(javax.swing.text.Element elem) {
super(elem);
CALDocument doc = (CALDocument) getDocument();
lexer = doc.createScanner();
lexerValid = false;
}
/**
* Renders using the given rendering surface and area
* on that surface. This is implemented to invalidate
* the lexical scanner after rendering so that the next
* request to drawUnselectedText will set a new range
* for the scanner.
*
* @param g the rendering surface to use
* @param a the allocated region to render into
*
* @see javax.swing.text.View#paint
*/
@Override
public void paint(java.awt.Graphics g, java.awt.Shape a) {
super.paint(g, a);
lexerValid = false;
}
/**
* Renders the given range in the model as normal unselected
* text. This is implemented to paint colors based upon the
* token-to-color translations. To reduce the number of calls
* to the Graphics object, text is batched up until a color
* change is detected or the entire requested range has been
* reached. Note: this function assumes that all the text
* in the specified range falls on a single line.
*
* @param g the graphics context
* @param x the starting X coordinate
* @param y the starting Y coordinate
* @param p0 the beginning position in the model
* @param p1 the ending position in the model
* @return the location of the end of the range
* @exception javax.swing.text.BadLocationException if the range is invalid
*/
@Override
protected int drawUnselectedText(java.awt.Graphics g, int x, int y, int p0, int p1) throws javax.swing.text.BadLocationException {
CALDocument doc = (CALDocument) getDocument();
java.awt.Color last = null;
//if (p0 > 0) {
//System.out.println ("drawUnselectedText (x = " + x + ", y = " + y + ", p0 = " + p0 + ", p1 = " + p1 + ")");
//}
// First check for lines tagged as comments
boolean comment = false;
Element line = doc.getLineElementForOffset(p0);
//javax.swing.text.Element root = doc.getDefaultRootElement();
//int lineNum = root.getElementIndex(p0);
CALDocument.CCommentInfo cInfo = null;
if (line != null) {
AttributeSet att = line.getAttributes();
if (att != null) {
// Grab the info about complex comments in this line if it exists.
cInfo = (CALDocument.CCommentInfo) att.getAttribute(CALDocument.CCommentAttribute);
/*
if (att.isDefined (doc.CommentAttribute)) {
comment = true;
Color fg = getForeground(CALTokenTypes.SL_COMMENT, "");
g.setColor (fg);
javax.swing.text.Segment text = getLineBuffer();
//System.out.println ("trying to print line. " + lineStart + ", " + lineEnd);
doc.getText(p0, p1 - p0, text);
//System.out.println("text for linew = " + text);
x = javax.swing.text.Utilities.drawTabbedText(text, x, y, g, this, p0);
}
*/
}
}
if (!comment) {
int mark = p0;
Point curComment = null;
class BufferedToken {
public antlr.Token token = null;
public int endOffset = -1;
BufferedToken(antlr.Token token, int endOffset) {
this.token = token;
this.endOffset = endOffset;
}
}
List<BufferedToken> bufferedTokens = new ArrayList<BufferedToken>();
for (; p0 < p1;) {
java.awt.Color fg = null;
int p = p1;
// Check if we are in a comment sub-section. If we are
// draw from the current point to the end of the comment section.
if (cInfo != null && curComment == null) {
for (int i = 0; i < cInfo.comments.length; ++i) {
if (p0 >= cInfo.comments[i].x + line.getStartOffset() && p0 < cInfo.comments[i].y + line.getStartOffset()) {
fg = getForeground(CALTokenTypes.SL_COMMENT, "");
p = cInfo.comments[i].y + line.getStartOffset();
break;
}
}
}
if (fg == null) {
antlr.Token token = null;
if (bufferedTokens.isEmpty()) {
if (updateScanner(p0)) {
token = lexer.getToken();
p = Math.min(lexer.getEndOffset(), p1);
}
} else {
BufferedToken bt = bufferedTokens.remove(0);
p = Math.min(bt.endOffset, p1);
token = bt.token;
}
p = (p <= p0) ? p1 : p;
if (token == null) {
// For some reason something went wrong with the lexing and we didn't get a token.
// This can happen when the user is in the middle of typing a string (e.g. - "abc).
// Just default to black text.
fg = java.awt.Color.black;
} else {
// Look ahead to see if we are dealing with a compound name.
if (token.getType() == CALTokenTypes.VAR_ID || token.getType() == CALTokenTypes.CONS_ID) {
boolean lookAhead = true;
boolean foundCompound = false;
StringBuilder sb = new StringBuilder();
sb.append(token.getText());
while (lookAhead) {
lookAhead = false;
int la = p;
antlr.Token nt = null;
int nl = -1;
if (updateScanner(la)) {
nl = lexer.getEndOffset();
nt = lexer.getToken();
}
bufferedTokens.add(new BufferedToken(nt, nl));
if (nt != null && nt.getType() == CALTokenTypes.DOT) {
la = nl;
antlr.Token nt2 = null;
if (updateScanner(la)) {
nl = lexer.getEndOffset();
nt2 = lexer.getToken();
}
bufferedTokens.add(new BufferedToken(nt2, nl));
if (nt2 != null && (nt2.getType() == CALTokenTypes.CONS_ID || nt2.getType() == CALTokenTypes.VAR_ID)) {
sb.append(nt.getText());
sb.append(nt2.getText());
p = nl;
// Want to continue scanning forward because a
// compound name can have multiple qualifiers.
lookAhead = true;
// Mark that we have found a compund with at least
// on qualifer.
foundCompound = true;
// Remove the two tokens we just buffered.
bufferedTokens.remove(0);
bufferedTokens.remove(0);
}
}
}
if (foundCompound) {
// The compound name will be in the string builder.
fg = getForeground(CALTokenTypes.VAR_ID, sb.toString());
}
}
if (fg == null) {
fg = getForeground(token.getType(), token.getText());
}
}
}
if (fg != last && last != null) {
// color change, flush what we have
g.setColor(last);
javax.swing.text.Segment text = getLineBuffer();
doc.getText(mark, p0 - mark, text);
x = javax.swing.text.Utilities.drawTabbedText(text, x, y, g, this, mark);
mark = p0;
}
last = fg;
p0 = p;
}
if (mark < p1) {
// flush remaining
if (last != null) {
g.setColor(last);
}
javax.swing.text.Segment text = getLineBuffer();
doc.getText(mark, p1 - mark, text);
x = javax.swing.text.Utilities.drawTabbedText(text, x, y, g, this, mark);
}
}
return x;
}
@Override
protected int drawSelectedText(java.awt.Graphics g, int x, int y, int p0, int p1) throws javax.swing.text.BadLocationException {
//System.out.println ("drawSelectedText (x = " + x + ", y = " + y + ", p0 = " + p0 + ", p1 = " + p1 + ")");
return super.drawSelectedText(g, x, y, p0, p1);
}
/**
* Update the scanner (if necessary) to point to the appropriate
* token for the given start position needed for rendering.
* @param p int - update the scanner to this starting position
* @return boolean - true if update was successful or false if errors
*/
boolean updateScanner(int p) {
try {
if (!lexerValid) {
CALDocument doc = (CALDocument) getDocument();
lexer.setRange(doc.getScannerStart(p), doc.getLength());
lexerValid = true;
}
while (lexer.getEndOffset() <= p) {
if (!lexer.scan()) {
// Oops, at the end of this stream (shouldn't happen unless
// we have very peculiar lexemes!)
return false;
}
}
return true;
} catch (Throwable e) {
// Can't adjust scanner... calling logic
// Will simply render the remaining text.
e.printStackTrace();
return false;
}
}
}
/**
* Constructs a set of styles to represent java lexical
* tokens. By default there are no colors or fonts specified.
*/
public CALStyleContext() {
super();
//javax.swing.text.Style root = getStyle(DEFAULT_STYLE);
tokenStyles = new javax.swing.text.Style[CALTokenTypes.EXPONENT + 1];
// Set up some detault colours for CAL syntax
tokenColours = new java.awt.Color[CALTokenTypes.EXPONENT + 1];
// Keywords
//see the CALLexer constructor for all the keywords
//todoBI this can get seriously out of date and affect the colour coding of tokens in the code gem.
java.awt.Color keywordColour = java.awt.Color.blue;
tokenColours[CALTokenTypes.LITERAL_case] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_of] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_if] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_then] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_else] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_let] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_in] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_module] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_friend] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_import] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_using] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_function] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_dataConstructor] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_typeConstructor] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_typeClass] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_public] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_protected] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_private] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_data] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_foreign] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_unsafe] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_jvm] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_deriving] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_instance] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_class] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_where] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_default] = keywordColour;
tokenColours[CALTokenTypes.LITERAL_primitive] = keywordColour;
// Literal numbers
java.awt.Color literalColour = java.awt.Color.green.darker().darker();
tokenColours[CALTokenTypes.CHAR_LITERAL] = literalColour;
tokenColours[CALTokenTypes.INTEGER_LITERAL] = literalColour;
tokenColours[CALTokenTypes.FLOAT_LITERAL] = literalColour;
tokenColours[CALTokenTypes.STRING_LITERAL] = literalColour;
// Operators within expressions
java.awt.Color operatorColour = java.awt.Color.blue;
tokenColours[CALTokenTypes.ASTERISK] = operatorColour;
tokenColours[CALTokenTypes.PLUS] = operatorColour;
tokenColours[CALTokenTypes.MINUS] = operatorColour;
tokenColours[CALTokenTypes.SOLIDUS] = operatorColour;
tokenColours[CALTokenTypes.PERCENT] = operatorColour;
tokenColours[CALTokenTypes.AMPERSANDAMPERSAND] = operatorColour;
tokenColours[CALTokenTypes.BARBAR] = operatorColour;
tokenColours[CALTokenTypes.PLUSPLUS] = operatorColour;
tokenColours[CALTokenTypes.EQUALSEQUALS] = operatorColour;
tokenColours[CALTokenTypes.GREATER_THAN] = operatorColour;
tokenColours[CALTokenTypes.GREATER_THAN_OR_EQUALS] = operatorColour;
tokenColours[CALTokenTypes.LESS_THAN] = operatorColour;
tokenColours[CALTokenTypes.LESS_THAN_OR_EQUALS] = operatorColour;
tokenColours[CALTokenTypes.NOT_EQUALS] = operatorColour;
tokenColours[CALTokenTypes.COLON] = operatorColour;
tokenColours[CALTokenTypes.POUND] = operatorColour;
tokenColours[CALTokenTypes.DOLLAR] = operatorColour;
tokenColours[CALTokenTypes.BACKQUOTE] = operatorColour;
// Separator syntax
java.awt.Color separatorColour = java.awt.Color.black;
tokenColours[CALTokenTypes.SEMICOLON] = separatorColour;
tokenColours[CALTokenTypes.EQUALS] = separatorColour;
tokenColours[CALTokenTypes.COLONCOLON] = separatorColour;
tokenColours[CALTokenTypes.RARROW] = separatorColour;
tokenColours[CALTokenTypes.BAR] = separatorColour;
// Comments
Color commentColour = Color.getHSBColor(118f / 360f, 0.77f, 0.74f);
tokenColours[CALTokenTypes.SL_COMMENT] = commentColour;
tokenColours[CALTokenTypes.ML_COMMENT] = commentColour;
// Set up some default styles for CAL
//javax.swing.text.Style parent = getStyle("numbers");
//if (parent == null) {
//parent = addStyle("numbers", root);
//}
//javax.swing.text.Style s = addStyle(null, parent);
//javax.swing.text.AttributeSet as = new javax.swing.text.AttributeSet();
//as.
//s.addAttributes();
//s.addAttribute(null, null); // TODO!
//tokenStyles[CALTokenTypes.FLOAT_LITERAL] = s;
// OR...
// For each scan value we're interested in...
// javax.swing.text.Style s = getStyleForScanValue(scan_number_or_manifest);
// StyleConstants.setForeground(s, thisColour);
// ...
}
/**
* Add a style listener to the editor.
* Creation date: (1/30/01 8:27:46 AM)
* @param listener org.openquark.gems.client.caleditor.CALSyntaxStyleListener the listener to add
* @exception java.util.TooManyListenersException we can't accept this listener.
*/
public void addCALSyntaxStyleListener(CALSyntaxStyleListener listener) throws java.util.TooManyListenersException {
// We're currently unicast only
if (styleListener != null) {
throw new java.util.TooManyListenersException("Can't add multiple listeners to unicast CALSyntaxStyleListener producer");
}
styleListener = listener;
}
/**
* Creates a view from the given structural element of a
* document.
*
* @param elem the piece of the document to build a view of
* @return the view
* @see javax.swing.text.View
*/
public javax.swing.text.View create(javax.swing.text.Element elem) {
return new CALView(elem);
}
/**
* Fetch the font to use for a lexical
* token with the given scan value.
*/
public java.awt.Font getFont(int code, String image) {
java.awt.Font suggestedFont = null;
if (tokenFonts == null) {
tokenFonts = new java.awt.Font[CALTokenTypes.EXPONENT + 1];
}
if (code < tokenFonts.length) {
java.awt.Font f = tokenFonts[code];
if (f == null) {
javax.swing.text.Style s = tokenStyles[code];
if (s != null) {
f = getFont(s);
} else {
f = null;
}
}
suggestedFont = f;
}
// Check with listener if we have one
if (styleListener != null) {
suggestedFont = styleListener.fontLookup(code, image, suggestedFont);
}
// Return whatever the result was
return suggestedFont;
}
/**
* Fetch the foreground colour to use for a lexical
* token with the given value.
*
* @param code int the scan code (token type) of the token
* @param image String the image of the token
* @return java.awt.Color the colour to use
*/
public java.awt.Color getForeground(int code, String image) {
//System.out.println ("getForeground(code = " + code + ", image = " + image + ")");
java.awt.Color suggestedColour = java.awt.Color.black;
if (tokenColours == null) {
tokenColours = new java.awt.Color[CALTokenTypes.EXPONENT + 1];
}
if ((code >= 0) && (code < tokenColours.length)) {
java.awt.Color c = tokenColours[code];
if (c == null) {
javax.swing.text.Style s = tokenStyles[code];
if (s != null) {
c = javax.swing.text.StyleConstants.getForeground(s);
} else {
c = java.awt.Color.black;
}
}
suggestedColour = c;
}
// Check with the listener if we have one and is not a comment.
if (styleListener != null && code != CALTokenTypes.SL_COMMENT && code != CALTokenTypes.ML_COMMENT) {
suggestedColour = styleListener.foreColourLookup(code, image, suggestedColour);
}
// Return the result
return suggestedColour;
}
/**
* Fetches the attribute set to use for the given
* scan code. The set is stored in a table to
* facilitate relatively fast access to use in
* conjunction with the scanner.
*/
public javax.swing.text.Style getStyleForScanValue(int code, String image) {
javax.swing.text.Style suggestedStyle = null;
if (code < tokenStyles.length) {
suggestedStyle = tokenStyles[code];
}
// Check with the listener if we have one
if (styleListener != null) {
suggestedStyle = styleListener.styleLookup(code, image, suggestedStyle);
}
// Return the result
return suggestedStyle;
}
/**
* Remove this style listener.
* Creation date: (1/30/01 8:28:22 AM)
* @param listener org.openquark.gems.client.caleditor.CALSyntaxStyleListener the listener to remove
*/
public void removeCALSyntaxStyleListener(CALSyntaxStyleListener listener) {
if (styleListener == listener) {
styleListener = null;
}
}
}