org.fife.ui.rsyntaxtextarea.Theme Maven / Gradle / Ivy
/*
* 10/30/2011
*
* Theme.java - A color theme for RSyntaxTextArea.
*
* This library is distributed under a modified BSD license. See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.rsyntaxtextarea;
import java.awt.Color;
import java.awt.Font;
import java.awt.SystemColor;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import javax.swing.UIManager;
import javax.swing.plaf.ColorUIResource;
import javax.swing.text.StyleContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.fife.io.UnicodeWriter;
import org.fife.ui.rtextarea.Gutter;
import org.fife.ui.rtextarea.RTextArea;
/**
* A theme is a set of fonts and colors to use to style RSyntaxTextArea.
* Themes are defined in XML files that are validated against
* themes.dtd
. This provides applications and other consumers
* with an easy way to style RSyntaxTextArea without having to use the API.
*
* Sample themes are included in the source tree under the /themes
* folder, but are not a part of the built RSyntaxTextArea jar. Hosting
* applications are free to ship and use these themes as-is, modify them, or
* create their own.
*
* Note that to save a Theme
via {@link #save(OutputStream)},
* you must currently create a Theme
from a text area wrapped in
* an RTextScrollPane
, so that the color information for the
* gutter can be retrieved.
*
* @author Robert Futrell
* @version 1.0
*/
public class Theme {
private Font baseFont;
private Color bgColor;
private Color caretColor;
private boolean useSelctionFG;
private Color selectionFG;
private Color selectionBG;
private boolean selectionRoundedEdges;
private Color currentLineHighlight;
private boolean fadeCurrentLineHighlight;
private Color marginLineColor;
private Color markAllHighlightColor;
private Color markOccurrencesColor;
private boolean markOccurrencesBorder;
private Color matchedBracketFG;
private Color matchedBracketBG;
private boolean matchedBracketHighlightBoth;
private boolean matchedBracketAnimate;
private Color hyperlinkFG;
private Color[] secondaryLanguages;
private SyntaxScheme scheme;
private Color gutterBorderColor;
private Color activeLineRangeColor;
private boolean iconRowHeaderInheritsGutterBG;
private Color lineNumberColor;
private String lineNumberFont;
private int lineNumberFontSize;
private Color foldIndicatorFG;
private Color foldBG;
/**
* Private constructor, used when loading from a stream.
*
* @param baseFont The default font to use for any "base font" properties
* not specified in the theme XML. If this is null
,
* a default monospaced font will be used.
*/
private Theme(Font baseFont) {
// Optional fields that require a default value.
this.baseFont = baseFont!=null ? baseFont : RTextArea.getDefaultFont();
secondaryLanguages = new Color[3];
activeLineRangeColor = Gutter.DEFAULT_ACTIVE_LINE_RANGE_COLOR;
}
/**
* Creates a theme from an RSyntaxTextArea. It should be contained in
* an RTextScrollPane
to get all gutter color information.
*
* @param textArea The text area.
*/
public Theme(RSyntaxTextArea textArea) {
baseFont = textArea.getFont();
bgColor = textArea.getBackground();
caretColor = textArea.getCaretColor();
useSelctionFG = textArea.getUseSelectedTextColor();
selectionFG = textArea.getSelectedTextColor();
selectionBG = textArea.getSelectionColor();
selectionRoundedEdges = textArea.getRoundedSelectionEdges();
currentLineHighlight = textArea.getCurrentLineHighlightColor();
fadeCurrentLineHighlight = textArea.getFadeCurrentLineHighlight();
marginLineColor = textArea.getMarginLineColor();
markAllHighlightColor = textArea.getMarkAllHighlightColor();
markOccurrencesColor = textArea.getMarkOccurrencesColor();
markOccurrencesBorder = textArea.getPaintMarkOccurrencesBorder();
matchedBracketBG = textArea.getMatchedBracketBGColor();
matchedBracketFG = textArea.getMatchedBracketBorderColor();
matchedBracketHighlightBoth = textArea.getPaintMatchedBracketPair();
matchedBracketAnimate = textArea.getAnimateBracketMatching();
hyperlinkFG = textArea.getHyperlinkForeground();
int count = textArea.getSecondaryLanguageCount();
secondaryLanguages = new Color[count];
for (int i=0; i0 ? lineNumberFontSize :
baseFont.getSize();
Font font = getFont(fontName, Font.PLAIN, fontSize);
gutter.setLineNumberFont(font);
gutter.setFoldIndicatorForeground(foldIndicatorFG);
gutter.setFoldBackground(foldBG);
}
}
private static final String colorToString(Color c) {
int color = c.getRGB() & 0xffffff;
String str = Integer.toHexString(color);
while (str.length()<6) {
str = "0" + str;
}
return str;
}
/**
* Returns the default selection background color to use if "default" is
* specified in a theme.
*
* @return The default selection background to use.
* @see #getDefaultSelectionFG()
*/
private static final Color getDefaultBG() {
Color c = UIManager.getColor("nimbusLightBackground");
if (c==null) {
// Don't search for "text", as Nimbus defines that as the text
// component "text" color, but Basic LAFs use it for text
// component backgrounds! Nimbus also defines TextArea.background
// as too dark, not what it actually uses for text area backgrounds
c = UIManager.getColor("TextArea.background");
if (c==null) {
c = new ColorUIResource(SystemColor.text);
}
}
return c;
}
/**
* Returns the default selection background color to use if "default" is
* specified in a theme.
*
* @return The default selection background to use.
* @see #getDefaultSelectionFG()
*/
private static final Color getDefaultSelectionBG() {
Color c = UIManager.getColor("TextArea.selectionBackground");
if (c==null) {
c = UIManager.getColor("textHighlight");
if (c==null) {
c = UIManager.getColor("nimbusSelectionBackground");
if (c==null) {
c = new ColorUIResource(SystemColor.textHighlight);
}
}
}
return c;
}
/**
* Returns the default "selected text" color to use if "default" is
* specified in a theme.
*
* @return The default selection foreground color to use.
* @see #getDefaultSelectionBG()
*/
private static Color getDefaultSelectionFG() {
Color c = UIManager.getColor("TextArea.selectionForeground");
if (c==null) {
c = UIManager.getColor("textHighlightText");
if (c==null) {
c = UIManager.getColor("nimbusSelectedText");
if (c==null) {
c = new ColorUIResource(SystemColor.textHighlightText);
}
}
}
return c;
}
/**
* Returns the specified font.
*
* @param family The font family.
* @param style The style of font.
* @param size The size of the font.
* @return The font.
*/
private static Font getFont(String family, int style, int size) {
// Use StyleContext to get a composite font for Asian glyphs.
StyleContext sc = StyleContext.getDefaultStyleContext();
return sc.getFont(family, style, size);
}
/**
* Loads a theme.
*
* @param in The input stream to read from. This will be closed when this
* method returns.
* @return The theme.
* @throws IOException If an IO error occurs.
* @see #save(OutputStream)
*/
public static Theme load(InputStream in) throws IOException {
return load(in, null);
}
/**
* Loads a theme.
*
* @param in The input stream to read from. This will be closed when this
* method returns.
* @param baseFont The default font to use for any "base font" properties
* not specified in the theme XML. If this is null
,
* a default monospaced font will be used.
* @return The theme.
* @throws IOException If an IO error occurs.
* @see #save(OutputStream)
*/
public static Theme load(InputStream in, Font baseFont) throws IOException {
Theme theme = new Theme(baseFont);
BufferedInputStream bin = new BufferedInputStream(in);
try {
XmlHandler.load(theme, bin);
} finally {
bin.close();
}
return theme;
}
/**
* Saves this theme to an output stream.
*
* @param out The output stream to write to.
* @throws IOException If an IO error occurs.
* @see #load(InputStream)
*/
public void save(OutputStream out) throws IOException {
BufferedOutputStream bout = new BufferedOutputStream(out);
try {
DocumentBuilder db = DocumentBuilderFactory.newInstance().
newDocumentBuilder();
DOMImplementation impl = db.getDOMImplementation();
Document doc = impl.createDocument(null, "RSyntaxTheme", null);
Element root = doc.getDocumentElement();
root.setAttribute("version", "1.0");
Element elem = doc.createElement("baseFont");
if (!baseFont.getFamily().equals(
RSyntaxTextArea.getDefaultFont().getFamily())) {
elem.setAttribute("family", baseFont.getFamily());
}
elem.setAttribute("size", Integer.toString(baseFont.getSize()));
root.appendChild(elem);
elem = doc.createElement("background");
elem.setAttribute("color", colorToString(bgColor));
root.appendChild(elem);
elem = doc.createElement("caret");
elem.setAttribute("color", colorToString(caretColor));
root.appendChild(elem);
elem = doc.createElement("selection");
elem.setAttribute("useFG", Boolean.toString(useSelctionFG));
elem.setAttribute("fg", colorToString(selectionFG));
elem.setAttribute("bg", colorToString(selectionBG));
elem.setAttribute("roundedEdges", Boolean.toString(selectionRoundedEdges));
root.appendChild(elem);
elem = doc.createElement("currentLineHighlight");
elem.setAttribute("color", colorToString(currentLineHighlight));
elem.setAttribute("fade", Boolean.toString(fadeCurrentLineHighlight));
root.appendChild(elem);
elem = doc.createElement("marginLine");
elem.setAttribute("fg", colorToString(marginLineColor));
root.appendChild(elem);
elem = doc.createElement("markAllHighlight");
elem.setAttribute("color", colorToString(markAllHighlightColor));
root.appendChild(elem);
elem = doc.createElement("markOccurrencesHighlight");
elem.setAttribute("color", colorToString(markOccurrencesColor));
elem.setAttribute("border", Boolean.toString(markOccurrencesBorder));
root.appendChild(elem);
elem = doc.createElement("matchedBracket");
elem.setAttribute("fg", colorToString(matchedBracketFG));
elem.setAttribute("bg", colorToString(matchedBracketBG));
elem.setAttribute("highlightBoth", Boolean.toString(matchedBracketHighlightBoth));
elem.setAttribute("animate", Boolean.toString(matchedBracketAnimate));
root.appendChild(elem);
elem = doc.createElement("hyperlinks");
elem.setAttribute("fg", colorToString(hyperlinkFG));
root.appendChild(elem);
elem = doc.createElement("secondaryLanguages");
for (int i=0; i0) {
elem.setAttribute("lineNumberFontSize",
Integer.toString(lineNumberFontSize));
}
root.appendChild(elem);
elem = doc.createElement("foldIndicator");
elem.setAttribute("fg", colorToString(foldIndicatorFG));
elem.setAttribute("iconBg", colorToString(foldBG));
root.appendChild(elem);
elem = doc.createElement("iconRowHeader");
elem.setAttribute("activeLineRange", colorToString(activeLineRangeColor));
elem.setAttribute("inheritsGutterBG", Boolean.toString(iconRowHeaderInheritsGutterBG));
root.appendChild(elem);
elem = doc.createElement("tokenStyles");
Field[] fields = TokenTypes.class.getFields();
for (int i=0; i
* "$00ff00"
* "00ff00"
*
* will return new Color(0, 255, 0)
.
*
* @param s The string to evaluate.
* @return The color.
*/
private static final Color stringToColor(String s) {
return stringToColor(s, null);
}
/**
* Returns the color represented by a string. The input is expected to
* be a 6-digit hex string, optionally prefixed by a '$'. For example,
* either of the following:
* * "$00ff00" * "00ff00" ** will return
new Color(0, 255, 0)
.
*
* @param s The string to evaluate.
* @param defaultVal The color to use if s
is
* "default
".
* @return The color.
*/
private static final Color stringToColor(String s, Color defaultVal) {
if (s==null || "default".equalsIgnoreCase(s)) {
return defaultVal;
}
if (s.length()==6 || s.length()==7) {
if (s.charAt(0)=='$') {
s = s.substring(1);
}
return new Color(Integer.parseInt(s, 16));
}
return null;
}
/**
* Loads a SyntaxScheme
from an XML file.
*/
private static class XmlHandler extends DefaultHandler {
private Theme theme;
@Override
public void error(SAXParseException e) throws SAXException {
throw e;
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
throw e;
}
public static void load(Theme theme, InputStream in) throws IOException {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setValidating(true);
try {
SAXParser parser = spf.newSAXParser();
XMLReader reader = parser.getXMLReader();
XmlHandler handler = new XmlHandler();
handler.theme = theme;
reader.setEntityResolver(handler);
reader.setContentHandler(handler);
reader.setDTDHandler(handler);
reader.setErrorHandler(handler);
InputSource is = new InputSource(in);
is.setEncoding("UTF-8");
reader.parse(is);
} catch (/*SAX|ParserConfiguration*/Exception se) {
se.printStackTrace();
throw new IOException(se.toString());
}
}
private static final int parseInt(Attributes attrs, String attr,
int def) {
int value = def;
String temp = attrs.getValue(attr);
if (temp != null) {
try {
value = Integer.parseInt(temp);
} catch (NumberFormatException nfe) {
nfe.printStackTrace();
}
}
return value;
}
@Override
public InputSource resolveEntity(String publicID,
String systemID) throws SAXException {
return new InputSource(getClass().
getResourceAsStream("themes/theme.dtd"));
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attrs) {
if ("background".equals(qName)) {
String color = attrs.getValue("color");
if (color!=null) {
theme.bgColor = stringToColor(color, getDefaultBG());
}
else {
String img = attrs.getValue("image");
if (img!=null) {
throw new IllegalArgumentException("Not yet implemented");
}
}
}
// The base font to use in the editor.
else if ("baseFont".equals(qName)) {
int size = theme.baseFont.getSize();
String sizeStr = attrs.getValue("size");
if (sizeStr!=null) {
size = Integer.parseInt(sizeStr);
}
String family = attrs.getValue("family");
if (family!=null) {
theme.baseFont = getFont(family, Font.PLAIN, size);
}
else if (sizeStr!=null) {
// No family specified, keep original family
theme.baseFont = theme.baseFont.deriveFont(size*1f);
}
}
else if ("caret".equals(qName)) {
String color = attrs.getValue("color");
theme.caretColor = stringToColor(color);
}
else if ("currentLineHighlight".equals(qName)) {
String color = attrs.getValue("color");
theme.currentLineHighlight = stringToColor(color);
String fadeStr = attrs.getValue("fade");
boolean fade = Boolean.valueOf(fadeStr).booleanValue();
theme.fadeCurrentLineHighlight = fade;
}
else if ("foldIndicator".equals(qName)) {
String color = attrs.getValue("fg");
theme.foldIndicatorFG = stringToColor(color);
color = attrs.getValue("iconBg");
theme.foldBG = stringToColor(color);
}
else if ("gutterBorder".equals(qName)) {
String color = attrs.getValue("color");
theme.gutterBorderColor = stringToColor(color);
}
else if ("iconRowHeader".equals(qName)) {
String color = attrs.getValue("activeLineRange");
theme.activeLineRangeColor = stringToColor(color);
String inheritBGStr = attrs.getValue("inheritsGutterBG");
theme.iconRowHeaderInheritsGutterBG =
inheritBGStr==null ? false : Boolean.valueOf(inheritBGStr);
}
else if ("lineNumbers".equals(qName)) {
String color = attrs.getValue("fg");
theme.lineNumberColor = stringToColor(color);
theme.lineNumberFont = attrs.getValue("fontFamily");
theme.lineNumberFontSize = parseInt(attrs, "fontSize", -1);
}
else if ("marginLine".equals(qName)) {
String color = attrs.getValue("fg");
theme.marginLineColor = stringToColor(color);
}
else if ("markAllHighlight".equals(qName)) {
String color = attrs.getValue("color");
theme.markAllHighlightColor = stringToColor(color);
}
else if ("markOccurrencesHighlight".equals(qName)) {
String color = attrs.getValue("color");
theme.markOccurrencesColor = stringToColor(color);
String border = attrs.getValue("border");
theme.markOccurrencesBorder = Boolean.valueOf(border).booleanValue();
}
else if ("matchedBracket".equals(qName)) {
String fg = attrs.getValue("fg");
theme.matchedBracketFG = stringToColor(fg);
String bg = attrs.getValue("bg");
theme.matchedBracketBG = stringToColor(bg);
String highlightBoth = attrs.getValue("highlightBoth");
theme.matchedBracketHighlightBoth = Boolean.valueOf(highlightBoth).booleanValue();
String animate = attrs.getValue("animate");
theme.matchedBracketAnimate = Boolean.valueOf(animate).booleanValue();
}
else if ("hyperlinks".equals(qName)) {
String fg = attrs.getValue("fg");
theme.hyperlinkFG = stringToColor(fg);
}
else if ("language".equals(qName)) {
String indexStr = attrs.getValue("index");
int index = Integer.parseInt(indexStr) - 1;
if (theme.secondaryLanguages.length>index) { // Sanity
Color bg = stringToColor(attrs.getValue("bg"));
theme.secondaryLanguages[index] = bg;
}
}
else if ("selection".equals(qName)) {
String useStr = attrs.getValue("useFG");
theme.useSelctionFG = Boolean.valueOf(useStr).booleanValue();
String color = attrs.getValue("fg");
theme.selectionFG = stringToColor(color,
getDefaultSelectionFG());
//System.out.println(theme.selectionFG);
color = attrs.getValue("bg");
theme.selectionBG = stringToColor(color,
getDefaultSelectionBG());
String roundedStr = attrs.getValue("roundedEdges");
theme.selectionRoundedEdges = Boolean.valueOf(roundedStr).
booleanValue();
}
// Start of the syntax scheme definition
else if ("tokenStyles".equals(qName)) {
theme.scheme = new SyntaxScheme(theme.baseFont, false);
}
// A style in the syntax scheme
else if ("style".equals(qName)) {
String type = attrs.getValue("token");
Field field = null;
try {
field = Token.class.getField(type);
} catch (RuntimeException re) {
throw re; // FindBugs
} catch (Exception e) {
System.err.println("Invalid token type: " + type);
return;
}
if (field.getType()==int.class) {
int index = 0;
try {
index = field.getInt(theme.scheme);
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
} catch (IllegalAccessException e) {
e.printStackTrace();
return;
}
String fgStr = attrs.getValue("fg");
Color fg = stringToColor(fgStr);
theme.scheme.getStyle(index).foreground = fg;
String bgStr = attrs.getValue("bg");
Color bg = stringToColor(bgStr);
theme.scheme.getStyle(index).background = bg;
Font font = theme.baseFont;
String familyName = attrs.getValue("fontFamily");
if (familyName!=null) {
font = getFont(familyName, font.getStyle(),
font.getSize());
}
String sizeStr = attrs.getValue("fontSize");
if (sizeStr!=null) {
try {
float size = Float.parseFloat(sizeStr);
size = Math.max(size, 1f);
font = font.deriveFont(size);
} catch (NumberFormatException nfe) {
nfe.printStackTrace();
}
}
theme.scheme.getStyle(index).font = font;
boolean styleSpecified = false;
boolean bold = false;
boolean italic = false;
String boldStr = attrs.getValue("bold");
if (boldStr!=null) {
bold = Boolean.valueOf(boldStr).booleanValue();
styleSpecified = true;
}
String italicStr = attrs.getValue("italic");
if (italicStr!=null) {
italic = Boolean.valueOf(italicStr).booleanValue();
styleSpecified = true;
}
if (styleSpecified) {
int style = 0;
if (bold) { style |= Font.BOLD; }
if (italic) { style |= Font.ITALIC; }
Font orig = theme.scheme.getStyle(index).font;
theme.scheme.getStyle(index).font =
orig.deriveFont(style);
}
String ulineStr = attrs.getValue("underline");
if (ulineStr!=null) {
boolean uline= Boolean.valueOf(ulineStr).booleanValue();
theme.scheme.getStyle(index).underline = uline;
}
}
}
}
@Override
public void warning(SAXParseException e) throws SAXException {
throw e;
}
}
}