All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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
 * LICENSE 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.fife.io.UnicodeWriter;
import org.fife.ui.rtextarea.Gutter;
import org.fife.ui.rtextarea.RTextArea;
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;


/**
 * A theme is a set of fonts and colors to use to style RSyntaxTextArea and
 * RTextScrollPane.  Themes are defined in XML files that are validated against
 * org/fife/ui/rsyntaxtextarea/themes/theme.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 in the * org.fife.ui.rsyntaxtextarea.themes package, and can be loaded * via getClass().getResourceAsStream(...).

* * All fields are public to facilitate programmatic manipulation, but typically * you won't need to reference any fields directly, rather using the * load(), save(), and apply() methods * for various tasks.

* * 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 */ @SuppressWarnings({ "checkstyle:visibilitymodifier" }) public class Theme { public Font baseFont; public Color bgColor; public Color caretColor; public boolean useSelectionFG; public Color selectionFG; public Color selectionBG; public boolean selectionRoundedEdges; public Color currentLineHighlight; public boolean fadeCurrentLineHighlight; public Color tabLineColor; public Color marginLineColor; public Color markAllHighlightColor; public Color markOccurrencesColor; public boolean markOccurrencesBorder; public Color matchedBracketFG; public Color matchedBracketBG; public boolean matchedBracketHighlightBoth; public boolean matchedBracketAnimate; public Color hyperlinkFG; public Color[] secondaryLanguages; public SyntaxScheme scheme; public Color gutterBackgroundColor; public Color gutterBorderColor; public Color activeLineRangeColor; public boolean iconRowHeaderInheritsGutterBG; public Color lineNumberColor; public String lineNumberFont; public int lineNumberFontSize; public Color foldIndicatorFG; public Color foldBG; public Color armedFoldBG; /** * 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(); useSelectionFG = textArea.getUseSelectedTextColor(); selectionFG = textArea.getSelectedTextColor(); selectionBG = textArea.getSelectionColor(); selectionRoundedEdges = textArea.getRoundedSelectionEdges(); currentLineHighlight = textArea.getCurrentLineHighlightColor(); fadeCurrentLineHighlight = textArea.getFadeCurrentLineHighlight(); tabLineColor = textArea.getTabLineColor(); 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); gutter.setArmedFoldBackground(armedFoldBG); } } private static String colorToString(Color c) { int color = c.getRGB() & 0xffffff; StringBuilder stringBuilder = new StringBuilder(Integer.toHexString(color)); while (stringBuilder.length()<6) { stringBuilder.insert(0, "0"); } return stringBuilder.toString(); } /** * 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 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 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); try (BufferedInputStream bin = new BufferedInputStream(in)) { XmlHandler.load(theme, bin); } 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 { try (BufferedOutputStream bout = new BufferedOutputStream(out)) { 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(useSelectionFG)); 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("tabLine"); elem.setAttribute("color", colorToString(tabLineColor)); 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; i < secondaryLanguages.length; i++) { Color color = secondaryLanguages[i]; Element elem2 = doc.createElement("language"); elem2.setAttribute("index", Integer.toString(i + 1)); elem2.setAttribute("bg", color == null ? "" : colorToString(color)); elem.appendChild(elem2); } root.appendChild(elem); elem = doc.createElement("gutterBackground"); elem.setAttribute("color", colorToString(gutterBackgroundColor)); root.appendChild(elem); elem = doc.createElement("gutterBorder"); elem.setAttribute("color", colorToString(gutterBorderColor)); root.appendChild(elem); elem = doc.createElement("lineNumbers"); elem.setAttribute("fg", colorToString(lineNumberColor)); if (lineNumberFont != null) { elem.setAttribute("fontFamily", lineNumberFont); } if (lineNumberFontSize > 0) { elem.setAttribute("fontSize", Integer.toString(lineNumberFontSize)); } root.appendChild(elem); elem = doc.createElement("foldIndicator"); elem.setAttribute("fg", colorToString(foldIndicatorFG)); elem.setAttribute("iconBg", colorToString(foldBG)); elem.setAttribute("iconArmedBg", colorToString(armedFoldBG)); 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 (Field field : fields) { int value = field.getInt(null); if (value != TokenTypes.DEFAULT_NUM_TOKEN_TYPES) { Style style = scheme.getStyle(value); if (style != null) { Element elem2 = doc.createElement("style"); elem2.setAttribute("token", field.getName()); Color fg = style.foreground; if (fg != null) { elem2.setAttribute("fg", colorToString(fg)); } Color bg = style.background; if (bg != null) { elem2.setAttribute("bg", colorToString(bg)); } Font font = style.font; if (font != null) { if (!font.getFamily().equals( baseFont.getFamily())) { elem2.setAttribute("fontFamily", font.getFamily()); } if (font.getSize() != baseFont.getSize()) { elem2.setAttribute("fontSize", Integer.toString(font.getSize())); } if (font.isBold()) { elem2.setAttribute("bold", "true"); } if (font.isItalic()) { elem2.setAttribute("italic", "true"); } } if (style.underline) { elem2.setAttribute("underline", "true"); } elem.appendChild(elem2); } } } root.appendChild(elem); DOMSource source = new DOMSource(doc); // Use a writer instead of OutputStream to allow pretty printing. // See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6337981 StreamResult result = new StreamResult(new PrintWriter( new UnicodeWriter(bout, "UTF-8"))); TransformerFactory transFac = TransformerFactory.newInstance(); Transformer transformer = transFac.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "theme.dtd"); transformer.transform(source, result); } catch (RuntimeException re) { throw re; // FindBugs } catch (Exception e) { throw new IOException("Error generating XML: " + e.getMessage(), e); } } /** * 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. * @return The color. */ private static 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 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) { throw new IOException(se.toString()); } } private static 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) { 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()); theme.gutterBackgroundColor = theme.bgColor; } 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.parseBoolean(fadeStr); theme.fadeCurrentLineHighlight = fade; } else if ("tabLine".equals(qName)) { String color = attrs.getValue("color"); theme.tabLineColor = stringToColor(color); } else if ("foldIndicator".equals(qName)) { String color = attrs.getValue("fg"); theme.foldIndicatorFG = stringToColor(color); color = attrs.getValue("iconBg"); theme.foldBG = stringToColor(color); color = attrs.getValue("iconArmedBg"); // This field must have a value for downstream consumers to // function properly, so default to regular BG if not armed // variant isn't specified theme.armedFoldBG = stringToColor(color, theme.foldBG); } else if ("gutterBackground".equals(qName)) { String color = attrs.getValue("color"); if (color!=null) { theme.gutterBackgroundColor = 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 = Boolean.parseBoolean(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.parseBoolean(border); } 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.parseBoolean(highlightBoth); String animate = attrs.getValue("animate"); theme.matchedBracketAnimate = Boolean.parseBoolean(animate); } 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.useSelectionFG = Boolean.parseBoolean(useStr); 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.parseBoolean(roundedStr); } // 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 | 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.parseBoolean(boldStr); styleSpecified = true; } String italicStr = attrs.getValue("italic"); if (italicStr!=null) { italic = Boolean.parseBoolean(italicStr); 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.parseBoolean(ulineStr); theme.scheme.getStyle(index).underline = uline; } } } } @Override public void warning(SAXParseException e) throws SAXException { throw e; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy